This is page 40 of 47. Use http://codebase.md/doobidoo/mcp-memory-service?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .claude
│ ├── agents
│ │ ├── amp-bridge.md
│ │ ├── amp-pr-automator.md
│ │ ├── code-quality-guard.md
│ │ ├── gemini-pr-automator.md
│ │ └── github-release-manager.md
│ ├── settings.local.json.backup
│ └── settings.local.json.local
├── .commit-message
├── .dockerignore
├── .env.example
├── .env.sqlite.backup
├── .envnn#
├── .gitattributes
├── .github
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ ├── feature_request.yml
│ │ └── performance_issue.yml
│ ├── pull_request_template.md
│ └── workflows
│ ├── bridge-tests.yml
│ ├── CACHE_FIX.md
│ ├── claude-code-review.yml
│ ├── claude.yml
│ ├── cleanup-images.yml.disabled
│ ├── dev-setup-validation.yml
│ ├── docker-publish.yml
│ ├── LATEST_FIXES.md
│ ├── main-optimized.yml.disabled
│ ├── main.yml
│ ├── publish-and-test.yml
│ ├── README_OPTIMIZATION.md
│ ├── release-tag.yml.disabled
│ ├── release.yml
│ ├── roadmap-review-reminder.yml
│ ├── SECRET_CONDITIONAL_FIX.md
│ └── WORKFLOW_FIXES.md
├── .gitignore
├── .mcp.json.backup
├── .mcp.json.template
├── .pyscn
│ ├── .gitignore
│ └── reports
│ └── analyze_20251123_214224.html
├── AGENTS.md
├── archive
│ ├── deployment
│ │ ├── deploy_fastmcp_fixed.sh
│ │ ├── deploy_http_with_mcp.sh
│ │ └── deploy_mcp_v4.sh
│ ├── deployment-configs
│ │ ├── empty_config.yml
│ │ └── smithery.yaml
│ ├── development
│ │ └── test_fastmcp.py
│ ├── docs-removed-2025-08-23
│ │ ├── authentication.md
│ │ ├── claude_integration.md
│ │ ├── claude-code-compatibility.md
│ │ ├── claude-code-integration.md
│ │ ├── claude-code-quickstart.md
│ │ ├── claude-desktop-setup.md
│ │ ├── complete-setup-guide.md
│ │ ├── database-synchronization.md
│ │ ├── development
│ │ │ ├── autonomous-memory-consolidation.md
│ │ │ ├── CLEANUP_PLAN.md
│ │ │ ├── CLEANUP_README.md
│ │ │ ├── CLEANUP_SUMMARY.md
│ │ │ ├── dream-inspired-memory-consolidation.md
│ │ │ ├── hybrid-slm-memory-consolidation.md
│ │ │ ├── mcp-milestone.md
│ │ │ ├── multi-client-architecture.md
│ │ │ ├── test-results.md
│ │ │ └── TIMESTAMP_FIX_SUMMARY.md
│ │ ├── distributed-sync.md
│ │ ├── invocation_guide.md
│ │ ├── macos-intel.md
│ │ ├── master-guide.md
│ │ ├── mcp-client-configuration.md
│ │ ├── multi-client-server.md
│ │ ├── service-installation.md
│ │ ├── sessions
│ │ │ └── MCP_ENHANCEMENT_SESSION_MEMORY_v4.1.0.md
│ │ ├── UBUNTU_SETUP.md
│ │ ├── ubuntu.md
│ │ ├── windows-setup.md
│ │ └── windows.md
│ ├── docs-root-cleanup-2025-08-23
│ │ ├── AWESOME_LIST_SUBMISSION.md
│ │ ├── CLOUDFLARE_IMPLEMENTATION.md
│ │ ├── DOCUMENTATION_ANALYSIS.md
│ │ ├── DOCUMENTATION_CLEANUP_PLAN.md
│ │ ├── DOCUMENTATION_CONSOLIDATION_COMPLETE.md
│ │ ├── LITESTREAM_SETUP_GUIDE.md
│ │ ├── lm_studio_system_prompt.md
│ │ ├── PYTORCH_DOWNLOAD_FIX.md
│ │ └── README-ORIGINAL-BACKUP.md
│ ├── investigations
│ │ └── MACOS_HOOKS_INVESTIGATION.md
│ ├── litestream-configs-v6.3.0
│ │ ├── install_service.sh
│ │ ├── litestream_master_config_fixed.yml
│ │ ├── litestream_master_config.yml
│ │ ├── litestream_replica_config_fixed.yml
│ │ ├── litestream_replica_config.yml
│ │ ├── litestream_replica_simple.yml
│ │ ├── litestream-http.service
│ │ ├── litestream.service
│ │ └── requirements-cloudflare.txt
│ ├── release-notes
│ │ └── release-notes-v7.1.4.md
│ └── setup-development
│ ├── README.md
│ ├── setup_consolidation_mdns.sh
│ ├── STARTUP_SETUP_GUIDE.md
│ └── test_service.sh
├── CHANGELOG-HISTORIC.md
├── CHANGELOG.md
├── claude_commands
│ ├── memory-context.md
│ ├── memory-health.md
│ ├── memory-ingest-dir.md
│ ├── memory-ingest.md
│ ├── memory-recall.md
│ ├── memory-search.md
│ ├── memory-store.md
│ ├── README.md
│ └── session-start.md
├── claude-hooks
│ ├── config.json
│ ├── config.template.json
│ ├── CONFIGURATION.md
│ ├── core
│ │ ├── memory-retrieval.js
│ │ ├── mid-conversation.js
│ │ ├── session-end.js
│ │ ├── session-start.js
│ │ └── topic-change.js
│ ├── debug-pattern-test.js
│ ├── install_claude_hooks_windows.ps1
│ ├── install_hooks.py
│ ├── memory-mode-controller.js
│ ├── MIGRATION.md
│ ├── README-NATURAL-TRIGGERS.md
│ ├── README-phase2.md
│ ├── README.md
│ ├── simple-test.js
│ ├── statusline.sh
│ ├── test-adaptive-weights.js
│ ├── test-dual-protocol-hook.js
│ ├── test-mcp-hook.js
│ ├── test-natural-triggers.js
│ ├── test-recency-scoring.js
│ ├── tests
│ │ ├── integration-test.js
│ │ ├── phase2-integration-test.js
│ │ ├── test-code-execution.js
│ │ ├── test-cross-session.json
│ │ ├── test-session-tracking.json
│ │ └── test-threading.json
│ ├── utilities
│ │ ├── adaptive-pattern-detector.js
│ │ ├── context-formatter.js
│ │ ├── context-shift-detector.js
│ │ ├── conversation-analyzer.js
│ │ ├── dynamic-context-updater.js
│ │ ├── git-analyzer.js
│ │ ├── mcp-client.js
│ │ ├── memory-client.js
│ │ ├── memory-scorer.js
│ │ ├── performance-manager.js
│ │ ├── project-detector.js
│ │ ├── session-tracker.js
│ │ ├── tiered-conversation-monitor.js
│ │ └── version-checker.js
│ └── WINDOWS-SESSIONSTART-BUG.md
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Development-Sprint-November-2025.md
├── docs
│ ├── amp-cli-bridge.md
│ ├── api
│ │ ├── code-execution-interface.md
│ │ ├── memory-metadata-api.md
│ │ ├── PHASE1_IMPLEMENTATION_SUMMARY.md
│ │ ├── PHASE2_IMPLEMENTATION_SUMMARY.md
│ │ ├── PHASE2_REPORT.md
│ │ └── tag-standardization.md
│ ├── architecture
│ │ ├── search-enhancement-spec.md
│ │ └── search-examples.md
│ ├── architecture.md
│ ├── archive
│ │ └── obsolete-workflows
│ │ ├── load_memory_context.md
│ │ └── README.md
│ ├── assets
│ │ └── images
│ │ ├── dashboard-v3.3.0-preview.png
│ │ ├── memory-awareness-hooks-example.png
│ │ ├── project-infographic.svg
│ │ └── README.md
│ ├── CLAUDE_CODE_QUICK_REFERENCE.md
│ ├── cloudflare-setup.md
│ ├── deployment
│ │ ├── docker.md
│ │ ├── dual-service.md
│ │ ├── production-guide.md
│ │ └── systemd-service.md
│ ├── development
│ │ ├── ai-agent-instructions.md
│ │ ├── code-quality
│ │ │ ├── phase-2a-completion.md
│ │ │ ├── phase-2a-handle-get-prompt.md
│ │ │ ├── phase-2a-index.md
│ │ │ ├── phase-2a-install-package.md
│ │ │ └── phase-2b-session-summary.md
│ │ ├── code-quality-workflow.md
│ │ ├── dashboard-workflow.md
│ │ ├── issue-management.md
│ │ ├── pr-review-guide.md
│ │ ├── refactoring-notes.md
│ │ ├── release-checklist.md
│ │ └── todo-tracker.md
│ ├── docker-optimized-build.md
│ ├── document-ingestion.md
│ ├── DOCUMENTATION_AUDIT.md
│ ├── enhancement-roadmap-issue-14.md
│ ├── examples
│ │ ├── analysis-scripts.js
│ │ ├── maintenance-session-example.md
│ │ ├── memory-distribution-chart.jsx
│ │ └── tag-schema.json
│ ├── first-time-setup.md
│ ├── glama-deployment.md
│ ├── guides
│ │ ├── advanced-command-examples.md
│ │ ├── chromadb-migration.md
│ │ ├── commands-vs-mcp-server.md
│ │ ├── mcp-enhancements.md
│ │ ├── mdns-service-discovery.md
│ │ ├── memory-consolidation-guide.md
│ │ ├── migration.md
│ │ ├── scripts.md
│ │ └── STORAGE_BACKENDS.md
│ ├── HOOK_IMPROVEMENTS.md
│ ├── hooks
│ │ └── phase2-code-execution-migration.md
│ ├── http-server-management.md
│ ├── ide-compatability.md
│ ├── IMAGE_RETENTION_POLICY.md
│ ├── images
│ │ └── dashboard-placeholder.md
│ ├── implementation
│ │ ├── health_checks.md
│ │ └── performance.md
│ ├── IMPLEMENTATION_PLAN_HTTP_SSE.md
│ ├── integration
│ │ ├── homebrew.md
│ │ └── multi-client.md
│ ├── integrations
│ │ ├── gemini.md
│ │ ├── groq-bridge.md
│ │ ├── groq-integration-summary.md
│ │ └── groq-model-comparison.md
│ ├── integrations.md
│ ├── legacy
│ │ └── dual-protocol-hooks.md
│ ├── LM_STUDIO_COMPATIBILITY.md
│ ├── maintenance
│ │ └── memory-maintenance.md
│ ├── mastery
│ │ ├── api-reference.md
│ │ ├── architecture-overview.md
│ │ ├── configuration-guide.md
│ │ ├── local-setup-and-run.md
│ │ ├── testing-guide.md
│ │ └── troubleshooting.md
│ ├── migration
│ │ └── code-execution-api-quick-start.md
│ ├── natural-memory-triggers
│ │ ├── cli-reference.md
│ │ ├── installation-guide.md
│ │ └── performance-optimization.md
│ ├── oauth-setup.md
│ ├── pr-graphql-integration.md
│ ├── quick-setup-cloudflare-dual-environment.md
│ ├── README.md
│ ├── remote-configuration-wiki-section.md
│ ├── research
│ │ ├── code-execution-interface-implementation.md
│ │ └── code-execution-interface-summary.md
│ ├── ROADMAP.md
│ ├── sqlite-vec-backend.md
│ ├── statistics
│ │ ├── charts
│ │ │ ├── activity_patterns.png
│ │ │ ├── contributors.png
│ │ │ ├── growth_trajectory.png
│ │ │ ├── monthly_activity.png
│ │ │ └── october_sprint.png
│ │ ├── data
│ │ │ ├── activity_by_day.csv
│ │ │ ├── activity_by_hour.csv
│ │ │ ├── contributors.csv
│ │ │ └── monthly_activity.csv
│ │ ├── generate_charts.py
│ │ └── REPOSITORY_STATISTICS.md
│ ├── technical
│ │ ├── development.md
│ │ ├── memory-migration.md
│ │ ├── migration-log.md
│ │ ├── sqlite-vec-embedding-fixes.md
│ │ └── tag-storage.md
│ ├── testing
│ │ └── regression-tests.md
│ ├── testing-cloudflare-backend.md
│ ├── troubleshooting
│ │ ├── cloudflare-api-token-setup.md
│ │ ├── cloudflare-authentication.md
│ │ ├── general.md
│ │ ├── hooks-quick-reference.md
│ │ ├── pr162-schema-caching-issue.md
│ │ ├── session-end-hooks.md
│ │ └── sync-issues.md
│ └── tutorials
│ ├── advanced-techniques.md
│ ├── data-analysis.md
│ └── demo-session-walkthrough.md
├── examples
│ ├── claude_desktop_config_template.json
│ ├── claude_desktop_config_windows.json
│ ├── claude-desktop-http-config.json
│ ├── config
│ │ └── claude_desktop_config.json
│ ├── http-mcp-bridge.js
│ ├── memory_export_template.json
│ ├── README.md
│ ├── setup
│ │ └── setup_multi_client_complete.py
│ └── start_https_example.sh
├── install_service.py
├── install.py
├── LICENSE
├── NOTICE
├── pyproject.toml
├── pytest.ini
├── README.md
├── run_server.py
├── scripts
│ ├── .claude
│ │ └── settings.local.json
│ ├── archive
│ │ └── check_missing_timestamps.py
│ ├── backup
│ │ ├── backup_memories.py
│ │ ├── backup_sqlite_vec.sh
│ │ ├── export_distributable_memories.sh
│ │ └── restore_memories.py
│ ├── benchmarks
│ │ ├── benchmark_code_execution_api.py
│ │ ├── benchmark_hybrid_sync.py
│ │ └── benchmark_server_caching.py
│ ├── database
│ │ ├── analyze_sqlite_vec_db.py
│ │ ├── check_sqlite_vec_status.py
│ │ ├── db_health_check.py
│ │ └── simple_timestamp_check.py
│ ├── development
│ │ ├── debug_server_initialization.py
│ │ ├── find_orphaned_files.py
│ │ ├── fix_mdns.sh
│ │ ├── fix_sitecustomize.py
│ │ ├── remote_ingest.sh
│ │ ├── setup-git-merge-drivers.sh
│ │ ├── uv-lock-merge.sh
│ │ └── verify_hybrid_sync.py
│ ├── hooks
│ │ └── pre-commit
│ ├── installation
│ │ ├── install_linux_service.py
│ │ ├── install_macos_service.py
│ │ ├── install_uv.py
│ │ ├── install_windows_service.py
│ │ ├── install.py
│ │ ├── setup_backup_cron.sh
│ │ ├── setup_claude_mcp.sh
│ │ └── setup_cloudflare_resources.py
│ ├── linux
│ │ ├── service_status.sh
│ │ ├── start_service.sh
│ │ ├── stop_service.sh
│ │ ├── uninstall_service.sh
│ │ └── view_logs.sh
│ ├── maintenance
│ │ ├── assign_memory_types.py
│ │ ├── check_memory_types.py
│ │ ├── cleanup_corrupted_encoding.py
│ │ ├── cleanup_memories.py
│ │ ├── cleanup_organize.py
│ │ ├── consolidate_memory_types.py
│ │ ├── consolidation_mappings.json
│ │ ├── delete_orphaned_vectors_fixed.py
│ │ ├── fast_cleanup_duplicates_with_tracking.sh
│ │ ├── find_all_duplicates.py
│ │ ├── find_cloudflare_duplicates.py
│ │ ├── find_duplicates.py
│ │ ├── memory-types.md
│ │ ├── README.md
│ │ ├── recover_timestamps_from_cloudflare.py
│ │ ├── regenerate_embeddings.py
│ │ ├── repair_malformed_tags.py
│ │ ├── repair_memories.py
│ │ ├── repair_sqlite_vec_embeddings.py
│ │ ├── repair_zero_embeddings.py
│ │ ├── restore_from_json_export.py
│ │ └── scan_todos.sh
│ ├── migration
│ │ ├── cleanup_mcp_timestamps.py
│ │ ├── legacy
│ │ │ └── migrate_chroma_to_sqlite.py
│ │ ├── mcp-migration.py
│ │ ├── migrate_sqlite_vec_embeddings.py
│ │ ├── migrate_storage.py
│ │ ├── migrate_tags.py
│ │ ├── migrate_timestamps.py
│ │ ├── migrate_to_cloudflare.py
│ │ ├── migrate_to_sqlite_vec.py
│ │ ├── migrate_v5_enhanced.py
│ │ ├── TIMESTAMP_CLEANUP_README.md
│ │ └── verify_mcp_timestamps.py
│ ├── pr
│ │ ├── amp_collect_results.sh
│ │ ├── amp_detect_breaking_changes.sh
│ │ ├── amp_generate_tests.sh
│ │ ├── amp_pr_review.sh
│ │ ├── amp_quality_gate.sh
│ │ ├── amp_suggest_fixes.sh
│ │ ├── auto_review.sh
│ │ ├── detect_breaking_changes.sh
│ │ ├── generate_tests.sh
│ │ ├── lib
│ │ │ └── graphql_helpers.sh
│ │ ├── quality_gate.sh
│ │ ├── resolve_threads.sh
│ │ ├── run_pyscn_analysis.sh
│ │ ├── run_quality_checks.sh
│ │ ├── thread_status.sh
│ │ └── watch_reviews.sh
│ ├── quality
│ │ ├── fix_dead_code_install.sh
│ │ ├── phase1_dead_code_analysis.md
│ │ ├── phase2_complexity_analysis.md
│ │ ├── README_PHASE1.md
│ │ ├── README_PHASE2.md
│ │ ├── track_pyscn_metrics.sh
│ │ └── weekly_quality_review.sh
│ ├── README.md
│ ├── run
│ │ ├── run_mcp_memory.sh
│ │ ├── run-with-uv.sh
│ │ └── start_sqlite_vec.sh
│ ├── run_memory_server.py
│ ├── server
│ │ ├── check_http_server.py
│ │ ├── check_server_health.py
│ │ ├── memory_offline.py
│ │ ├── preload_models.py
│ │ ├── run_http_server.py
│ │ ├── run_memory_server.py
│ │ ├── start_http_server.bat
│ │ └── start_http_server.sh
│ ├── service
│ │ ├── deploy_dual_services.sh
│ │ ├── install_http_service.sh
│ │ ├── mcp-memory-http.service
│ │ ├── mcp-memory.service
│ │ ├── memory_service_manager.sh
│ │ ├── service_control.sh
│ │ ├── service_utils.py
│ │ └── update_service.sh
│ ├── sync
│ │ ├── check_drift.py
│ │ ├── claude_sync_commands.py
│ │ ├── export_memories.py
│ │ ├── import_memories.py
│ │ ├── litestream
│ │ │ ├── apply_local_changes.sh
│ │ │ ├── enhanced_memory_store.sh
│ │ │ ├── init_staging_db.sh
│ │ │ ├── io.litestream.replication.plist
│ │ │ ├── manual_sync.sh
│ │ │ ├── memory_sync.sh
│ │ │ ├── pull_remote_changes.sh
│ │ │ ├── push_to_remote.sh
│ │ │ ├── README.md
│ │ │ ├── resolve_conflicts.sh
│ │ │ ├── setup_local_litestream.sh
│ │ │ ├── setup_remote_litestream.sh
│ │ │ ├── staging_db_init.sql
│ │ │ ├── stash_local_changes.sh
│ │ │ ├── sync_from_remote_noconfig.sh
│ │ │ └── sync_from_remote.sh
│ │ ├── README.md
│ │ ├── safe_cloudflare_update.sh
│ │ ├── sync_memory_backends.py
│ │ └── sync_now.py
│ ├── testing
│ │ ├── run_complete_test.py
│ │ ├── run_memory_test.sh
│ │ ├── simple_test.py
│ │ ├── test_cleanup_logic.py
│ │ ├── test_cloudflare_backend.py
│ │ ├── test_docker_functionality.py
│ │ ├── test_installation.py
│ │ ├── test_mdns.py
│ │ ├── test_memory_api.py
│ │ ├── test_memory_simple.py
│ │ ├── test_migration.py
│ │ ├── test_search_api.py
│ │ ├── test_sqlite_vec_embeddings.py
│ │ ├── test_sse_events.py
│ │ ├── test-connection.py
│ │ └── test-hook.js
│ ├── utils
│ │ ├── claude_commands_utils.py
│ │ ├── generate_personalized_claude_md.sh
│ │ ├── groq
│ │ ├── groq_agent_bridge.py
│ │ ├── list-collections.py
│ │ ├── memory_wrapper_uv.py
│ │ ├── query_memories.py
│ │ ├── smithery_wrapper.py
│ │ ├── test_groq_bridge.sh
│ │ └── uv_wrapper.py
│ └── validation
│ ├── check_dev_setup.py
│ ├── check_documentation_links.py
│ ├── diagnose_backend_config.py
│ ├── validate_configuration_complete.py
│ ├── validate_memories.py
│ ├── validate_migration.py
│ ├── validate_timestamp_integrity.py
│ ├── verify_environment.py
│ ├── verify_pytorch_windows.py
│ └── verify_torch.py
├── SECURITY.md
├── selective_timestamp_recovery.py
├── SPONSORS.md
├── src
│ └── mcp_memory_service
│ ├── __init__.py
│ ├── api
│ │ ├── __init__.py
│ │ ├── client.py
│ │ ├── operations.py
│ │ ├── sync_wrapper.py
│ │ └── types.py
│ ├── backup
│ │ ├── __init__.py
│ │ └── scheduler.py
│ ├── cli
│ │ ├── __init__.py
│ │ ├── ingestion.py
│ │ ├── main.py
│ │ └── utils.py
│ ├── config.py
│ ├── consolidation
│ │ ├── __init__.py
│ │ ├── associations.py
│ │ ├── base.py
│ │ ├── clustering.py
│ │ ├── compression.py
│ │ ├── consolidator.py
│ │ ├── decay.py
│ │ ├── forgetting.py
│ │ ├── health.py
│ │ └── scheduler.py
│ ├── dependency_check.py
│ ├── discovery
│ │ ├── __init__.py
│ │ ├── client.py
│ │ └── mdns_service.py
│ ├── embeddings
│ │ ├── __init__.py
│ │ └── onnx_embeddings.py
│ ├── ingestion
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── chunker.py
│ │ ├── csv_loader.py
│ │ ├── json_loader.py
│ │ ├── pdf_loader.py
│ │ ├── registry.py
│ │ ├── semtools_loader.py
│ │ └── text_loader.py
│ ├── lm_studio_compat.py
│ ├── mcp_server.py
│ ├── models
│ │ ├── __init__.py
│ │ └── memory.py
│ ├── server.py
│ ├── services
│ │ ├── __init__.py
│ │ └── memory_service.py
│ ├── storage
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── cloudflare.py
│ │ ├── factory.py
│ │ ├── http_client.py
│ │ ├── hybrid.py
│ │ └── sqlite_vec.py
│ ├── sync
│ │ ├── __init__.py
│ │ ├── exporter.py
│ │ ├── importer.py
│ │ └── litestream_config.py
│ ├── utils
│ │ ├── __init__.py
│ │ ├── cache_manager.py
│ │ ├── content_splitter.py
│ │ ├── db_utils.py
│ │ ├── debug.py
│ │ ├── document_processing.py
│ │ ├── gpu_detection.py
│ │ ├── hashing.py
│ │ ├── http_server_manager.py
│ │ ├── port_detection.py
│ │ ├── system_detection.py
│ │ └── time_parser.py
│ └── web
│ ├── __init__.py
│ ├── api
│ │ ├── __init__.py
│ │ ├── analytics.py
│ │ ├── backup.py
│ │ ├── consolidation.py
│ │ ├── documents.py
│ │ ├── events.py
│ │ ├── health.py
│ │ ├── manage.py
│ │ ├── mcp.py
│ │ ├── memories.py
│ │ ├── search.py
│ │ └── sync.py
│ ├── app.py
│ ├── dependencies.py
│ ├── oauth
│ │ ├── __init__.py
│ │ ├── authorization.py
│ │ ├── discovery.py
│ │ ├── middleware.py
│ │ ├── models.py
│ │ ├── registration.py
│ │ └── storage.py
│ ├── sse.py
│ └── static
│ ├── app.js
│ ├── index.html
│ ├── README.md
│ ├── sse_test.html
│ └── style.css
├── start_http_debug.bat
├── start_http_server.sh
├── test_document.txt
├── test_version_checker.js
├── tests
│ ├── __init__.py
│ ├── api
│ │ ├── __init__.py
│ │ ├── test_compact_types.py
│ │ └── test_operations.py
│ ├── bridge
│ │ ├── mock_responses.js
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ └── test_http_mcp_bridge.js
│ ├── conftest.py
│ ├── consolidation
│ │ ├── __init__.py
│ │ ├── conftest.py
│ │ ├── test_associations.py
│ │ ├── test_clustering.py
│ │ ├── test_compression.py
│ │ ├── test_consolidator.py
│ │ ├── test_decay.py
│ │ └── test_forgetting.py
│ ├── contracts
│ │ └── api-specification.yml
│ ├── integration
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── test_api_key_fallback.py
│ │ ├── test_api_memories_chronological.py
│ │ ├── test_api_tag_time_search.py
│ │ ├── test_api_with_memory_service.py
│ │ ├── test_bridge_integration.js
│ │ ├── test_cli_interfaces.py
│ │ ├── test_cloudflare_connection.py
│ │ ├── test_concurrent_clients.py
│ │ ├── test_data_serialization_consistency.py
│ │ ├── test_http_server_startup.py
│ │ ├── test_mcp_memory.py
│ │ ├── test_mdns_integration.py
│ │ ├── test_oauth_basic_auth.py
│ │ ├── test_oauth_flow.py
│ │ ├── test_server_handlers.py
│ │ └── test_store_memory.py
│ ├── performance
│ │ ├── test_background_sync.py
│ │ └── test_hybrid_live.py
│ ├── README.md
│ ├── smithery
│ │ └── test_smithery.py
│ ├── sqlite
│ │ └── simple_sqlite_vec_test.py
│ ├── test_client.py
│ ├── test_content_splitting.py
│ ├── test_database.py
│ ├── test_hybrid_cloudflare_limits.py
│ ├── test_hybrid_storage.py
│ ├── test_memory_ops.py
│ ├── test_semantic_search.py
│ ├── test_sqlite_vec_storage.py
│ ├── test_time_parser.py
│ ├── test_timestamp_preservation.py
│ ├── timestamp
│ │ ├── test_hook_vs_manual_storage.py
│ │ ├── test_issue99_final_validation.py
│ │ ├── test_search_retrieval_inconsistency.py
│ │ ├── test_timestamp_issue.py
│ │ └── test_timestamp_simple.py
│ └── unit
│ ├── conftest.py
│ ├── test_cloudflare_storage.py
│ ├── test_csv_loader.py
│ ├── test_fastapi_dependencies.py
│ ├── test_import.py
│ ├── test_json_loader.py
│ ├── test_mdns_simple.py
│ ├── test_mdns.py
│ ├── test_memory_service.py
│ ├── test_memory.py
│ ├── test_semtools_loader.py
│ ├── test_storage_interface_compatibility.py
│ └── test_tag_time_filtering.py
├── tools
│ ├── docker
│ │ ├── DEPRECATED.md
│ │ ├── docker-compose.http.yml
│ │ ├── docker-compose.pythonpath.yml
│ │ ├── docker-compose.standalone.yml
│ │ ├── docker-compose.uv.yml
│ │ ├── docker-compose.yml
│ │ ├── docker-entrypoint-persistent.sh
│ │ ├── docker-entrypoint-unified.sh
│ │ ├── docker-entrypoint.sh
│ │ ├── Dockerfile
│ │ ├── Dockerfile.glama
│ │ ├── Dockerfile.slim
│ │ ├── README.md
│ │ └── test-docker-modes.sh
│ └── README.md
└── uv.lock
```
# Files
--------------------------------------------------------------------------------
/src/mcp_memory_service/web/static/index.html:
--------------------------------------------------------------------------------
```html
1 | <!DOCTYPE html>
2 | <html lang="en">
3 | <head>
4 | <meta charset="UTF-8">
5 | <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 | <title>MCP Memory Service - Dashboard</title>
7 | <link rel="stylesheet" href="/static/style.css">
8 | <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🧠</text></svg>">
9 | </head>
10 | <body>
11 | <!-- Main Application Container -->
12 | <div id="app" class="app-container">
13 |
14 | <!-- Header with Navigation -->
15 | <header class="app-header">
16 | <div class="header-content">
17 | <div class="logo-section">
18 | <h1 class="app-title">🧠 MCP Memory</h1>
19 | <span id="versionBadge" class="version-badge">Loading...</span>
20 | </div>
21 |
22 | <!-- SVG Icon Definitions -->
23 | <svg style="display: none;" aria-hidden="true">
24 | <defs>
25 | <symbol id="info-icon" viewBox="0 0 24 24">
26 | <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none"/>
27 | <path d="M12 8v.01M12 12v4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
28 | </symbol>
29 | </defs>
30 | </svg>
31 |
32 | <!-- Quick Search Bar -->
33 | <div class="search-section">
34 | <div class="search-container">
35 | <input type="text"
36 | id="quickSearch"
37 | class="search-input"
38 | placeholder="🔍 Search your memories..."
39 | autocomplete="off">
40 | <button class="search-btn" type="button" aria-label="Search">
41 | <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
42 | <path d="M21.71 20.29L18 16.61A9 9 0 1 0 16.61 18l3.68 3.68a1 1 0 0 0 1.42-1.42zM11 18a7 7 0 1 1 7-7 7 7 0 0 1-7 7z"/>
43 | </svg>
44 | </button>
45 | </div>
46 | </div>
47 |
48 | <!-- Action Buttons -->
49 | <div class="action-buttons">
50 | <button id="addMemoryBtn" class="btn btn-primary" title="Add New Memory">
51 | <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
52 | <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
53 | </svg>
54 | <span class="btn-text">Add Memory</span>
55 | </button>
56 | <button id="themeToggleBtn" class="btn btn-secondary" title="Toggle Dark Mode">
57 | <svg id="sunIcon" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
58 | <circle cx="12" cy="12" r="5"/>
59 | <line x1="12" y1="1" x2="12" y2="3"/>
60 | <line x1="12" y1="21" x2="12" y2="23"/>
61 | <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
62 | <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
63 | <line x1="1" y1="12" x2="3" y2="12"/>
64 | <line x1="21" y1="12" x2="23" y2="12"/>
65 | <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
66 | <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
67 | </svg>
68 | <svg id="moonIcon" class="hidden" width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
69 | <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
70 | </svg>
71 | </button>
72 | <button id="settingsBtn" class="btn btn-secondary" title="Settings">
73 | <svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
74 | <circle cx="12" cy="12" r="3"/>
75 | <path d="M12 1v6m0 6v10M1 12h6m6 0h10"/>
76 | <path d="M4.22 4.22l4.24 4.24m7.08 7.08l4.24 4.24M4.22 19.78l4.24-4.24m7.08-7.08l4.24-4.24"/>
77 | </svg>
78 | </button>
79 | </div>
80 | </div>
81 | </header>
82 |
83 | <!-- Main Navigation -->
84 | <nav class="main-nav">
85 | <div class="nav-container">
86 | <button class="nav-item active" data-view="dashboard">
87 | <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
88 | <path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z"/>
89 | </svg>
90 | Dashboard
91 | </button>
92 | <button class="nav-item" data-view="search">
93 | <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
94 | <path d="M21.71 20.29L18 16.61A9 9 0 1 0 16.61 18l3.68 3.68a1 1 0 0 0 1.42-1.42zM11 18a7 7 0 1 1 7-7 7 7 0 0 1-7 7z"/>
95 | </svg>
96 | Search
97 | </button>
98 | <button class="nav-item" data-view="browse">
99 | <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
100 | <path d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-1 9H9V9h10v2zm-4 4H9v-2h6v2zm4-8H9V5h10v2z"/>
101 | </svg>
102 | Browse
103 | </button>
104 | <button class="nav-item" data-view="documents">
105 | <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
106 | <path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
107 | </svg>
108 | Documents
109 | </button>
110 | <button class="nav-item" data-view="manage">
111 | <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
112 | <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
113 | </svg>
114 | Manage
115 | </button>
116 | <button class="nav-item" data-view="analytics">
117 | <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
118 | <path d="M5 9.2h3V19H5zM10.6 5h2.8v14h-2.8zm5.6 8H19v6h-2.8z"/>
119 | </svg>
120 | Analytics
121 | </button>
122 | <button class="nav-item" data-view="apiDocs">
123 | <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
124 | <path d="M14,17H7V15H14M17,13H7V11H17M17,9H7V7H17M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z"/>
125 | </svg>
126 | API Docs
127 | </button>
128 | </div>
129 | </nav>
130 |
131 | <!-- Main Content Area -->
132 | <main class="main-content">
133 | <!-- Dashboard View -->
134 | <div id="dashboardView" class="view-container active">
135 | <div class="dashboard-grid">
136 | <!-- Welcome Section -->
137 | <section class="welcome-card">
138 | <h2>Welcome to your Memory Dashboard</h2>
139 | <p>Manage your AI memories with semantic search, real-time updates, and intelligent organization.</p>
140 | <div class="quick-stats">
141 | <div class="stat-item">
142 | <span class="stat-number" id="totalMemories">—</span>
143 | <span class="stat-label">Total Memories</span>
144 | </div>
145 | <div class="stat-item">
146 | <span class="stat-number" id="recentMemories">—</span>
147 | <span class="stat-label">This Week</span>
148 | </div>
149 | <div class="stat-item">
150 | <span class="stat-number" id="uniqueTags">—</span>
151 | <span class="stat-label">Tags</span>
152 | </div>
153 | </div>
154 | </section>
155 |
156 | <!-- Recent Memories -->
157 | <section class="recent-memories">
158 | <h3>Recent Memories</h3>
159 | <div id="recentMemoriesList" class="memory-list">
160 | <!-- Dynamic content will be loaded here -->
161 | </div>
162 | </section>
163 |
164 | <!-- Quick Actions -->
165 | <section class="quick-actions">
166 | <h3>Quick Actions</h3>
167 | <div class="action-grid">
168 | <button class="action-card" data-action="quick-search">
169 | <svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
170 | <path d="M21.71 20.29L18 16.61A9 9 0 1 0 16.61 18l3.68 3.68a1 1 0 0 0 1.42-1.42zM11 18a7 7 0 1 1 7-7 7 7 0 0 1-7 7z"/>
171 | </svg>
172 | <span>Advanced Search</span>
173 | </button>
174 | <button class="action-card" data-action="add-memory">
175 | <svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
176 | <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
177 | </svg>
178 | <span>Add Memory</span>
179 | </button>
180 | <button class="action-card" data-action="browse-tags">
181 | <svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
182 | <path d="M17.63 5.84C17.27 5.33 16.67 5 16 5L5 5.01C3.9 5.01 3 5.9 3 7v10c0 1.1.9 1.99 2 1.99L16 19c.67 0 1.27-.33 1.63-.84L22 12l-4.37-6.16z"/>
183 | </svg>
184 | <span>Browse Tags</span>
185 | </button>
186 | <button class="action-card" data-action="export-data">
187 | <svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
188 | <path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
189 | </svg>
190 | <span>Export Data</span>
191 | </button>
192 | </div>
193 |
194 | <!-- Compact Sync Control (Hybrid Mode Only) -->
195 | <div id="syncControl" class="sync-control-compact">
196 | <div class="sync-row">
197 | <div class="sync-status">
198 | <span id="syncStatusDot" class="sync-dot"></span>
199 | <span id="syncStatusText" class="sync-text-sm">Checking...</span>
200 | </div>
201 | <span id="syncProgress" class="sync-progress-sm"></span>
202 | <div class="sync-buttons-sm">
203 | <button type="button" id="pauseSyncButton" class="btn-icon-sm" title="Pause sync">
204 | <svg width="14" height="14" fill="currentColor" viewBox="0 0 24 24">
205 | <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
206 | </svg>
207 | </button>
208 | <button type="button" id="resumeSyncButton" class="btn-icon-sm" title="Resume sync">
209 | <svg width="14" height="14" fill="currentColor" viewBox="0 0 24 24">
210 | <path d="M8 5v14l11-7z"/>
211 | </svg>
212 | </button>
213 | <button type="button" id="forceSyncButton" class="btn-icon-sm btn-icon-primary" title="Sync now">
214 | <svg width="14" height="14" fill="currentColor" viewBox="0 0 24 24">
215 | <path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/>
216 | </svg>
217 | </button>
218 | </div>
219 | </div>
220 | </div>
221 | </section>
222 | </div>
223 | </div>
224 |
225 | <!-- Search View -->
226 | <div id="searchView" class="view-container">
227 | <div class="search-layout">
228 | <!-- Advanced Search Panel -->
229 | <aside class="search-filters">
230 | <div class="filters-header">
231 | <h3>Search Filters
232 | <span class="tooltip" title="Filters work together: combine tags, dates, and types to narrow your search">
233 | <svg width="16" height="16" fill="currentColor"><use href="#info-icon"/></svg>
234 | </span>
235 | </h3>
236 | <div class="mode-toggle-compact">
237 | <label class="toggle-switch">
238 | <input type="checkbox" id="liveSearchToggle" checked>
239 | <span class="slider"></span>
240 | </label>
241 | <span class="tooltip" title="Toggle between live search (updates as you type) and manual search mode">
242 | <svg width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
243 | <path d="M13 2L3 14h8l-1 8 10-12h-8l1-8z" fill="currentColor"/>
244 | </svg>
245 | </span>
246 | </div>
247 | </div>
248 |
249 | <!-- Search Mode Indicator -->
250 | <div class="search-mode-indicator">
251 | <span class="mode-status">
252 | <span id="searchModeText">Live Search</span> mode
253 | </span>
254 | </div>
255 |
256 | <div class="filter-section">
257 | <label for="tagFilter">Tags
258 | <span class="tooltip" title="Enter tags separated by commas (e.g., work, coding, important)">
259 | <svg width="16" height="16" fill="currentColor"><use href="#info-icon"/></svg>
260 | </span>
261 | </label>
262 | <input type="text" id="tagFilter" placeholder="e.g., work, coding, important">
263 | <small class="help-text">Separate multiple tags with commas</small>
264 | </div>
265 |
266 | <div class="filter-section">
267 | <label for="dateFilter">Date Range
268 | <span class="tooltip" title="Filter memories by when they were created">
269 | <svg width="16" height="16" fill="currentColor"><use href="#info-icon"/></svg>
270 | </span>
271 | </label>
272 | <select id="dateFilter">
273 | <option value="">All time</option>
274 | <option value="today">Today</option>
275 | <option value="yesterday">Yesterday</option>
276 | <option value="week">This week</option>
277 | <option value="month">This month</option>
278 | <option value="quarter">This quarter</option>
279 | <option value="year">This year</option>
280 | </select>
281 | <small class="help-text">Select a time period to filter memories</small>
282 | </div>
283 |
284 | <div class="filter-section">
285 | <label for="typeFilter">Content Type
286 | <span class="tooltip" title="Filter by the type of content stored">
287 | <svg width="16" height="16" fill="currentColor"><use href="#info-icon"/></svg>
288 | </span>
289 | </label>
290 | <select id="typeFilter">
291 | <option value="">All types</option>
292 | <option value="note">Notes</option>
293 | <option value="code">Code</option>
294 | <option value="reference">References</option>
295 | <option value="idea">Ideas</option>
296 | </select>
297 | <small class="help-text">Choose the type of memories to show</small>
298 | </div>
299 |
300 | <!-- Filter Actions -->
301 | <div class="filter-actions">
302 | <button type="button" id="applyFiltersBtn" class="btn btn-primary">
303 | <svg width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
304 | <path d="M21.71 20.29L18 16.61A9 9 0 1 0 16.61 18l3.68 3.68a1 1 0 0 0 1.42-1.42zM11 18a7 7 0 1 1 7-7 7 7 0 0 1-7 7z"/>
305 | </svg>
306 | Search
307 | </button>
308 | <button type="button" id="clearFiltersBtn" class="btn btn-secondary">
309 | <svg width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
310 | <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
311 | </svg>
312 | Clear All
313 | </button>
314 | </div>
315 |
316 | <!-- Active Filters Display -->
317 | <div id="activeFilters" class="active-filters" style="display: none;">
318 | <h4>Active Filters:</h4>
319 | <div id="activeFiltersList" class="filter-pills">
320 | <!-- Dynamic filter pills will be added here -->
321 | </div>
322 | </div>
323 | </aside>
324 |
325 | <!-- Search Results -->
326 | <div class="search-results">
327 | <div class="search-header">
328 | <h2>Search Results</h2>
329 | <div class="results-meta">
330 | <span id="resultsCount">0 results</span>
331 | <div class="view-options">
332 | <button class="view-btn active" data-view="grid">Grid</button>
333 | <button class="view-btn" data-view="list">List</button>
334 | </div>
335 | </div>
336 | </div>
337 | <div id="searchResultsList" class="memory-grid">
338 | <!-- Dynamic search results will be loaded here -->
339 | </div>
340 | </div>
341 | </div>
342 | </div>
343 |
344 | <!-- Documents View -->
345 | <div id="documentsView" class="view-container">
346 | <div class="documents-layout">
347 | <!-- Document Upload Section -->
348 | <section class="documents-section">
349 | <h2>📄 Document Ingestion</h2>
350 | <div class="upload-area">
351 | <div id="dropZone" class="drop-zone">
352 | <div class="drop-zone-content">
353 | <svg width="48" height="48" fill="currentColor" viewBox="0 0 24 24">
354 | <path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
355 | </svg>
356 | <h3>Drag & drop files here</h3>
357 | <p>or <button id="fileSelectBtn" class="link-button">browse to select files</button></p>
358 | <p class="supported-formats">Supported formats: PDF, TXT, MD, JSON</p>
359 | <input type="file" id="fileInput" multiple accept=".pdf,.txt,.md,.json" style="display: none;">
360 | </div>
361 | </div>
362 | </div>
363 |
364 | <!-- Upload Configuration -->
365 | <div class="upload-config">
366 | <div class="config-section">
367 | <label for="docTags">Tags (comma-separated)</label>
368 | <input type="text" id="docTags" placeholder="e.g., documentation, reference, manual" class="form-control">
369 | <small class="form-help">Tags will be applied to all files. Use spaces or commas as separators.</small>
370 | </div>
371 |
372 | <!-- Processing Mode Toggle (shown when multiple files selected) -->
373 | <div class="config-section" id="processingModeSection" style="display: none;">
374 | <label>Processing Mode <span class="info-icon info-icon-processing" title="Click for processing mode explanation">ℹ️</span></label>
375 | <div class="processing-mode-toggle">
376 | <button type="button" id="batchModeBtn" class="mode-btn active" data-mode="batch">
377 | <svg width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
378 | <path d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-1 9H9V9h10v2zm-4 4H9v-2h6v2zm4-8H9V5h10v2z"/>
379 | </svg>
380 | Batch Processing
381 | </button>
382 | <button type="button" id="individualModeBtn" class="mode-btn" data-mode="individual">
383 | <svg width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
384 | <path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
385 | </svg>
386 | Individual Processing
387 | </button>
388 | </div>
389 | <div class="mode-description" id="modeDescription">
390 | <small>All selected files will be processed together with the same tags.</small>
391 | </div>
392 | </div>
393 | <div class="config-row">
394 | <div class="config-section">
395 | <label for="chunkSize">
396 | Chunk Size: <span id="chunkSizeValue">1000</span> chars
397 | <span class="info-icon" title="Click for chunking recommendations">ℹ️</span>
398 | </label>
399 | <input type="range" id="chunkSize" min="500" max="2000" value="1000" step="100" class="form-control">
400 | </div>
401 | <div class="config-section">
402 | <label for="chunkOverlap">
403 | Overlap: <span id="chunkOverlapValue">200</span> chars
404 | <span class="info-icon info-icon-overlap" title="Click for overlap explanation">ℹ️</span>
405 | </label>
406 | <input type="range" id="chunkOverlap" min="0" max="500" value="200" step="50" class="form-control">
407 | </div>
408 | </div>
409 |
410 | <!-- Chunking Help Section (collapsible) -->
411 | <div id="chunkingHelpSection" class="chunking-help" style="display: none;">
412 | <div class="help-header">
413 | <strong>📚 Chunking Configuration Guide</strong>
414 | <button class="close-help" data-action="hideChunkingHelp">×</button>
415 | </div>
416 | <div class="help-content">
417 | <p class="help-note">
418 | <strong>Note:</strong> Actual chunk sizes may vary as the system respects paragraph boundaries
419 | to maintain semantic coherence.
420 | </p>
421 |
422 | <div class="help-recommendations">
423 | <div class="help-option">
424 | <div class="help-option-header">
425 | <strong>✅ Default (1000 chars, 200 overlap)</strong>
426 | <span class="help-tag">Recommended</span>
427 | </div>
428 | <p><strong>Best for:</strong> Technical documentation, reference manuals, knowledge bases</p>
429 | <p><strong>Why:</strong> Paragraph-aware chunking preserves complete thoughts and context</p>
430 | </div>
431 |
432 | <div class="help-option">
433 | <div class="help-option-header">
434 | <strong>🔍 Smaller Chunks (500 chars, 100 overlap)</strong>
435 | </div>
436 | <p><strong>Best for:</strong> Dense technical docs, code documentation, API references</p>
437 | <p><strong>Trade-off:</strong> More granular retrieval but may split paragraphs more aggressively</p>
438 | </div>
439 |
440 | <div class="help-option">
441 | <div class="help-option-header">
442 | <strong>📖 Larger Chunks (2000 chars, 400 overlap)</strong>
443 | </div>
444 | <p><strong>Best for:</strong> Narrative documents, articles, blogs, long-form content</p>
445 | <p><strong>Trade-off:</strong> Better context but less precise retrieval</p>
446 | </div>
447 | </div>
448 |
449 | <div class="help-tips">
450 | <strong>💡 Tips:</strong>
451 | <ul>
452 | <li><strong>Overlap</strong> helps maintain context across chunk boundaries</li>
453 | <li><strong>Higher overlap</strong> = better continuity but more redundancy</li>
454 | <li>Test different settings on a sample document to find optimal configuration</li>
455 | <li>Chunks preserve complete sentences and paragraphs when possible</li>
456 | </ul>
457 | </div>
458 | </div>
459 | </div>
460 |
461 | <!-- Overlap Help Section (collapsible) -->
462 | <div id="overlapHelpSection" class="chunking-help overlap-help" style="display: none;">
463 | <div class="help-header">
464 | <strong>🔗 Chunk Overlap Explained</strong>
465 | <button class="close-help" data-action="hideOverlapHelp">×</button>
466 | </div>
467 | <div class="help-content">
468 | <p class="help-note">
469 | <strong>What is overlap?</strong> Overlap is the number of characters that are duplicated
470 | between consecutive chunks. This helps maintain context across chunk boundaries.
471 | </p>
472 |
473 | <div class="help-example">
474 | <strong>Visual Example:</strong>
475 | <div class="overlap-diagram">
476 | <div class="chunk-demo">
477 | <span class="chunk-part unique">Chunk 1 unique content...</span>
478 | <span class="chunk-part overlap">shared overlap region</span>
479 | </div>
480 | <div class="chunk-demo">
481 | <span class="chunk-part overlap">shared overlap region</span>
482 | <span class="chunk-part unique">Chunk 2 unique content...</span>
483 | </div>
484 | </div>
485 | </div>
486 |
487 | <div class="help-recommendations">
488 | <div class="help-option">
489 | <div class="help-option-header">
490 | <strong>🎯 No Overlap (0 chars)</strong>
491 | </div>
492 | <p><strong>Best for:</strong> Maximum storage efficiency, no redundancy needed</p>
493 | <p><strong>Trade-off:</strong> Context may be lost at chunk boundaries</p>
494 | </div>
495 |
496 | <div class="help-option">
497 | <div class="help-option-header">
498 | <strong>✅ Medium Overlap (200 chars)</strong>
499 | <span class="help-tag">Recommended</span>
500 | </div>
501 | <p><strong>Best for:</strong> Most documents - balances context and efficiency</p>
502 | <p><strong>Why:</strong> Preserves 1-2 sentences of context across boundaries</p>
503 | </div>
504 |
505 | <div class="help-option">
506 | <div class="help-option-header">
507 | <strong>🔄 High Overlap (400+ chars)</strong>
508 | </div>
509 | <p><strong>Best for:</strong> Complex technical content requiring maximum context</p>
510 | <p><strong>Trade-off:</strong> More storage and processing, higher redundancy</p>
511 | </div>
512 | </div>
513 |
514 | <div class="help-tips">
515 | <strong>💡 Guidelines:</strong>
516 | <ul>
517 | <li><strong>Rule of thumb:</strong> Overlap should be 15-25% of chunk size</li>
518 | <li><strong>Small chunks (500)</strong> → Use 100-150 overlap</li>
519 | <li><strong>Medium chunks (1000)</strong> → Use 200-250 overlap</li>
520 | <li><strong>Large chunks (2000)</strong> → Use 400-500 overlap</li>
521 | <li>Higher overlap helps with search accuracy but increases storage</li>
522 | <li>Zero overlap is fine for well-structured documents with clear sections</li>
523 | </ul>
524 | </div>
525 | </div>
526 | </div>
527 |
528 | <!-- Processing Mode Help Section (collapsible) -->
529 | <div id="processingModeHelpSection" class="chunking-help processing-mode-help" style="display: none;">
530 | <div class="help-header">
531 | <strong>⚙️ Processing Mode Options</strong>
532 | <button class="close-help" data-action="hideProcessingModeHelp">×</button>
533 | </div>
534 | <div class="help-content">
535 | <p class="help-note">
536 | <strong>When uploading multiple files,</strong> choose how they should be processed.
537 | Both modes apply the same tags to all files.
538 | </p>
539 |
540 | <div class="help-recommendations">
541 | <div class="help-option">
542 | <div class="help-option-header">
543 | <strong>📦 Batch Processing</strong>
544 | <span class="help-tag">Default</span>
545 | </div>
546 | <p><strong>What it does:</strong> Uploads all files together as one operation</p>
547 | <p><strong>Best for:</strong> Similar files, fast bulk processing, when you want files grouped together</p>
548 | <p><strong>Pros:</strong> Faster, simpler, single progress indicator</p>
549 | <p><strong>Cons:</strong> One file failure can affect the whole batch</p>
550 | </div>
551 |
552 | <div class="help-option">
553 | <div class="help-option-header">
554 | <strong>🔄 Individual Processing</strong>
555 | </div>
556 | <p><strong>What it does:</strong> Uploads each file separately with individual API calls</p>
557 | <p><strong>Best for:</strong> Mixed file types, when you want error isolation, or need individual progress tracking</p>
558 | <p><strong>Pros:</strong> Better error handling, individual progress, more robust</p>
559 | <p><strong>Cons:</strong> Slightly slower due to sequential processing</p>
560 | </div>
561 | </div>
562 |
563 | <div class="help-tips">
564 | <strong>💡 When to choose each mode:</strong>
565 | <ul>
566 | <li><strong>Batch mode:</strong> When files are similar and you want them processed quickly together</li>
567 | <li><strong>Individual mode:</strong> When files might have different processing requirements or you want to ensure all files get processed even if some fail</li>
568 | <li><strong>Single files:</strong> Always processed individually (no choice needed)</li>
569 | <li>Both modes apply the same tags to all files</li>
570 | </ul>
571 | </div>
572 | </div>
573 | </div>
574 |
575 | <div class="config-section">
576 | <label for="memoryType">Memory Type</label>
577 | <select id="memoryType" class="form-control">
578 | <option value="document">Document</option>
579 | <option value="reference">Reference</option>
580 | <option value="knowledge">Knowledge</option>
581 | <option value="note">Note</option>
582 | </select>
583 | </div>
584 | </div>
585 |
586 | <!-- Upload Button -->
587 | <div class="upload-actions">
588 | <button id="uploadBtn" class="btn btn-primary" disabled>
589 | <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
590 | <path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
591 | </svg>
592 | Upload & Ingest
593 | </button>
594 | </div>
595 | </section>
596 |
597 | <!-- Upload Queue/History Section -->
598 | <section class="documents-section">
599 | <h2>📊 Upload History</h2>
600 | <div id="uploadHistory" class="upload-history">
601 | <div class="loading-spinner"></div>
602 | <p>Loading upload history...</p>
603 | </div>
604 | </section>
605 |
606 | <!-- Document Content Search Section -->
607 | <section class="documents-section">
608 | <h2>🔍 Search Ingested Content</h2>
609 | <p class="section-description">Search within your uploaded documents to verify content is indexed</p>
610 | <div class="doc-search-container">
611 | <input type="text"
612 | id="docSearchInput"
613 | class="search-input"
614 | placeholder="Search for content within ingested documents...">
615 | <button id="docSearchBtn" class="btn btn-primary">Search Documents</button>
616 | </div>
617 | <div id="docSearchResults" class="doc-search-results" style="display: none;">
618 | <div class="search-results-header">
619 | <h3>Search Results</h3>
620 | <span id="docSearchCount" class="results-count">0 results</span>
621 | </div>
622 | <div id="docSearchResultsList" class="search-results-list">
623 | <!-- Results will be populated here -->
624 | </div>
625 | </div>
626 | </section>
627 | </div>
628 | </div>
629 |
630 | <!-- Other views (Browse, Manage, Analytics) will be added in subsequent phases -->
631 | <div id="browseView" class="view-container">
632 | <div class="view-header">
633 | <h2>Browse by Tags</h2>
634 | <p>Explore your memories organized by tags</p>
635 | </div>
636 |
637 | <div class="browse-content">
638 | <div id="tagsCloudContainer" class="tags-cloud">
639 | <!-- Dynamic tags will be loaded here -->
640 | </div>
641 |
642 | <div id="taggedMemoriesContainer" class="tagged-memories" style="display: none;">
643 | <div class="section-header">
644 | <h3 id="selectedTagTitle">Memories tagged with: <span id="selectedTagName"></span></h3>
645 | <button id="clearTagFilter" class="btn btn-secondary">Show All Tags</button>
646 | </div>
647 | <div id="taggedMemoriesList" class="memory-grid">
648 | <!-- Filtered memories will be shown here -->
649 | </div>
650 | </div>
651 | </div>
652 | </div>
653 |
654 | <!-- Manage View -->
655 | <div id="manageView" class="view-container">
656 | <div class="manage-layout">
657 | <!-- Bulk Operations Section -->
658 | <section class="manage-section">
659 | <h2>🧹 Bulk Operations</h2>
660 | <div class="bulk-ops-grid">
661 | <div class="bulk-op-card">
662 | <h3>Delete by Tag</h3>
663 | <p>Remove all memories with a specific tag</p>
664 | <div class="op-controls">
665 | <select id="deleteTagSelect" class="form-control">
666 | <option value="">Select tag...</option>
667 | </select>
668 | <button id="deleteByTagBtn" class="btn btn-danger">Delete</button>
669 | </div>
670 | </div>
671 |
672 | <div class="bulk-op-card">
673 | <h3>Cleanup Duplicates</h3>
674 | <p>Remove duplicate memories based on content</p>
675 | <div class="op-controls">
676 | <button id="cleanupDuplicatesBtn" class="btn btn-warning">Run Cleanup</button>
677 | </div>
678 | </div>
679 |
680 | <div class="bulk-op-card">
681 | <h3>Delete by Date</h3>
682 | <p>Remove memories older than a specific date</p>
683 | <div class="op-controls">
684 | <input type="date" id="deleteDateInput" class="form-control">
685 | <button id="deleteByDateBtn" class="btn btn-danger">Delete</button>
686 | </div>
687 | </div>
688 | </div>
689 | </section>
690 |
691 | <!-- Tag Management Section -->
692 | <section class="manage-section">
693 | <h2>🏷️ Tag Management</h2>
694 | <div id="tagManagementContainer">
695 | <div class="loading-spinner"></div>
696 | <p>Loading tag statistics...</p>
697 | </div>
698 | </section>
699 |
700 | <!-- System Operations Section -->
701 | <section class="manage-section">
702 | <h2>⚙️ System Maintenance</h2>
703 | <div class="system-ops-grid">
704 | <div class="system-op-card">
705 | <h3>Database Optimization</h3>
706 | <p>Optimize database performance</p>
707 | <button id="optimizeDbBtn" class="btn btn-secondary" disabled>Optimize DB</button>
708 | </div>
709 |
710 | <div class="system-op-card">
711 | <h3>Rebuild Search Index</h3>
712 | <p>Rebuild search indexes for better performance</p>
713 | <button id="rebuildIndexBtn" class="btn btn-secondary" disabled>Rebuild Index</button>
714 | </div>
715 | </div>
716 | </section>
717 | </div>
718 | </div>
719 |
720 | <!-- Analytics View -->
721 | <div id="analyticsView" class="view-container">
722 | <div class="analytics-layout">
723 | <!-- Key Metrics Cards -->
724 | <section class="analytics-section">
725 | <h2>📊 Key Metrics</h2>
726 | <div class="metrics-grid">
727 | <div class="metric-card">
728 | <div class="metric-value" id="analyticsTotalMemories">-</div>
729 | <div class="metric-label">Total Memories</div>
730 | </div>
731 | <div class="metric-card">
732 | <div class="metric-value" id="analyticsThisWeek">-</div>
733 | <div class="metric-label">This Week</div>
734 | </div>
735 | <div class="metric-card">
736 | <div class="metric-value" id="analyticsUniqueTags">-</div>
737 | <div class="metric-label">Unique Tags</div>
738 | </div>
739 | <div class="metric-card">
740 | <div class="metric-value" id="analyticsDbSize">-</div>
741 | <div class="metric-label">Database Size</div>
742 | </div>
743 | </div>
744 | </section>
745 |
746 | <!-- Charts Section -->
747 | <section class="analytics-section">
748 | <h2>📈 Trends & Charts</h2>
749 | <div class="charts-grid">
750 | <div class="chart-card">
751 | <h3>Memory Growth Over Time</h3>
752 | <div class="chart-controls">
753 | <select id="growthPeriodSelect" class="form-control">
754 | <option value="week">Last Week</option>
755 | <option value="month" selected>Last Month</option>
756 | <option value="quarter">Last Quarter</option>
757 | <option value="year">Last Year</option>
758 | </select>
759 | </div>
760 | <div id="memoryGrowthChart" class="chart-container">
761 | <div class="loading-spinner"></div>
762 | <p>Loading chart...</p>
763 | </div>
764 | </div>
765 |
766 | <div class="chart-card">
767 | <h3>Tag Usage Distribution</h3>
768 | <div id="tagUsageChart" class="chart-container">
769 | <div class="loading-spinner"></div>
770 | <p>Loading chart...</p>
771 | </div>
772 | </div>
773 |
774 | <div class="chart-card">
775 | <h3>Memory Types Distribution</h3>
776 | <div id="memoryTypesChart" class="chart-container">
777 | <div class="loading-spinner"></div>
778 | <p>Loading chart...</p>
779 | </div>
780 | </div>
781 |
782 | <div class="chart-card">
783 | <h3>Activity Heatmap</h3>
784 | <div class="chart-controls">
785 | <select id="heatmapPeriodSelect" class="form-control">
786 | <option value="90" selected>Last 90 Days</option>
787 | <option value="180">Last 6 Months</option>
788 | <option value="365">Last Year</option>
789 | </select>
790 | </div>
791 | <div id="activityHeatmapChart" class="chart-container">
792 | <div class="loading-spinner"></div>
793 | <p>Loading heatmap...</p>
794 | </div>
795 | </div>
796 | </section>
797 |
798 | <!-- Detailed Analytics -->
799 | <section class="analytics-section">
800 | <h2>📋 Detailed Reports</h2>
801 | <div class="reports-grid">
802 | <div class="report-card">
803 | <h3>Top Tags</h3>
804 | <div class="chart-controls">
805 | <select id="topTagsPeriodSelect" class="form-control">
806 | <option value="7d">Last 7 Days</option>
807 | <option value="30d" selected>Last 30 Days</option>
808 | <option value="90d">Last 90 Days</option>
809 | <option value="all">All Time</option>
810 | </select>
811 | </div>
812 | <div id="topTagsList" class="report-content">
813 | <div class="loading-spinner"></div>
814 | <p>Loading...</p>
815 | </div>
816 | </div>
817 |
818 | <div class="report-card">
819 | <h3>Recent Activity</h3>
820 | <div class="chart-controls">
821 | <select id="activityGranularitySelect" class="form-control">
822 | <option value="hourly">By Hour</option>
823 | <option value="daily" selected>By Day</option>
824 | <option value="weekly">By Week</option>
825 | </select>
826 | </div>
827 | <div id="recentActivityList" class="report-content">
828 | <div class="loading-spinner"></div>
829 | <p>Loading...</p>
830 | </div>
831 | </div>
832 |
833 | <div class="report-card">
834 | <h3>Storage Report</h3>
835 | <div id="storageReport" class="report-content">
836 | <div class="loading-spinner"></div>
837 | <p>Loading...</p>
838 | </div>
839 | </div>
840 | </div>
841 | </section>
842 | </div>
843 | </div>
844 |
845 | <!-- API Documentation View -->
846 | <div id="apiDocsView" class="view-container">
847 | <div class="api-docs-header">
848 | <h2>🔗 API Documentation</h2>
849 | <p>Comprehensive REST API endpoints for MCP Memory Service</p>
850 | <div class="api-docs-links">
851 | <a href="/api/docs" target="_blank" class="btn btn-primary">
852 | <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
853 | <path d="M14,17H7V15H14M17,13H7V11H17M17,9H7V7H17M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z"/>
854 | </svg>
855 | Interactive Swagger UI
856 | </a>
857 | <a href="/api/redoc" target="_blank" class="btn btn-secondary">
858 | <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
859 | <path d="M14,17H7V15H14M17,13H7V11H17M17,9H7V7H17M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z"/>
860 | </svg>
861 | ReDoc Documentation
862 | </a>
863 | <a href="/api-overview" target="_blank" class="btn btn-secondary">
864 | <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
865 | <path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z"/>
866 | </svg>
867 | API Overview Page
868 | </a>
869 | </div>
870 | </div>
871 |
872 | <div class="api-endpoints-grid">
873 | <!-- Memory Management -->
874 | <div class="endpoint-section">
875 | <h3>💾 Memory Management</h3>
876 | <div class="endpoint-list">
877 | <div class="endpoint-item">
878 | <span class="method post">POST</span>
879 | <span class="path">/api/memories</span>
880 | <span class="description">Store a new memory with automatic embedding generation</span>
881 | </div>
882 | <div class="endpoint-item">
883 | <span class="method get">GET</span>
884 | <span class="path">/api/memories</span>
885 | <span class="description">List all memories with pagination support</span>
886 | </div>
887 | <div class="endpoint-item">
888 | <span class="method get">GET</span>
889 | <span class="path">/api/memories/{content_hash}</span>
890 | <span class="description">Retrieve a specific memory by content hash</span>
891 | </div>
892 | <div class="endpoint-item">
893 | <span class="method delete">DELETE</span>
894 | <span class="path">/api/memories/{content_hash}</span>
895 | <span class="description">Delete a memory and its embeddings</span>
896 | </div>
897 | </div>
898 | </div>
899 |
900 | <!-- Search Operations -->
901 | <div class="endpoint-section">
902 | <h3>🔍 Search Operations</h3>
903 | <div class="endpoint-list">
904 | <div class="endpoint-item">
905 | <span class="method post">POST</span>
906 | <span class="path">/api/search</span>
907 | <span class="description">Semantic similarity search using embeddings</span>
908 | </div>
909 | <div class="endpoint-item">
910 | <span class="method post">POST</span>
911 | <span class="path">/api/search/by-tag</span>
912 | <span class="description">Search memories by tags (AND/OR logic)</span>
913 | </div>
914 | <div class="endpoint-item">
915 | <span class="method post">POST</span>
916 | <span class="path">/api/search/by-time</span>
917 | <span class="description">Natural language time-based queries</span>
918 | </div>
919 | <div class="endpoint-item">
920 | <span class="method get">GET</span>
921 | <span class="path">/api/search/similar/{content_hash}</span>
922 | <span class="description">Find memories similar to a specific one</span>
923 | </div>
924 | </div>
925 | </div>
926 |
927 | <!-- Real-time Events -->
928 | <div class="endpoint-section">
929 | <h3>📡 Real-time Events</h3>
930 | <div class="endpoint-list">
931 | <div class="endpoint-item">
932 | <span class="method get">GET</span>
933 | <span class="path">/api/events</span>
934 | <span class="description">Subscribe to real-time memory events stream</span>
935 | </div>
936 | <div class="endpoint-item">
937 | <span class="method get">GET</span>
938 | <span class="path">/api/events/stats</span>
939 | <span class="description">View SSE connection statistics</span>
940 | </div>
941 | </div>
942 | </div>
943 |
944 | <!-- Health & Status -->
945 | <div class="endpoint-section">
946 | <h3>🏥 Health & Status</h3>
947 | <div class="endpoint-list">
948 | <div class="endpoint-item">
949 | <span class="method get">GET</span>
950 | <span class="path">/api/health</span>
951 | <span class="description">Quick health check endpoint</span>
952 | </div>
953 | <div class="endpoint-item">
954 | <span class="method get">GET</span>
955 | <span class="path">/api/health/detailed</span>
956 | <span class="description">Detailed health with database statistics</span>
957 | </div>
958 | </div>
959 | </div>
960 | </div>
961 | </div>
962 | </main>
963 |
964 | <!-- Footer -->
965 | <footer class="app-footer">
966 | <div class="footer-content">
967 | <div class="footer-section">
968 | <h4>Documentation</h4>
969 | <ul class="footer-links">
970 | <li><a href="https://github.com/doobidoo/mcp-memory-service/wiki" target="_blank" rel="noopener">
971 | 📚 Wiki Home
972 | </a></li>
973 | <li><a href="https://github.com/doobidoo/mcp-memory-service/wiki/07-TROUBLESHOOTING" target="_blank" rel="noopener">
974 | 🔧 Troubleshooting Guide
975 | </a></li>
976 | <li><a href="https://github.com/doobidoo/mcp-memory-service/wiki/07-TROUBLESHOOTING#backend-configuration-issues" target="_blank" rel="noopener">
977 | ⚙️ Configuration Issues
978 | </a></li>
979 | </ul>
980 | </div>
981 |
982 | <div class="footer-section">
983 | <h4>Resources</h4>
984 | <ul class="footer-links">
985 | <li><a href="https://github.com/doobidoo/mcp-memory-service" target="_blank" rel="noopener">
986 | <svg width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
987 | <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
988 | </svg>
989 | GitHub Repository
990 | </a></li>
991 | <li><a href="https://doobidoo.github.io" target="_blank" rel="noopener">
992 | 🌐 Portfolio
993 | </a></li>
994 | <li><a href="/api/docs" target="_blank">
995 | 📖 API Documentation
996 | </a></li>
997 | </ul>
998 | </div>
999 |
1000 | <div class="footer-section">
1001 | <h4>About</h4>
1002 | <p class="footer-description">
1003 | MCP Memory Service - Semantic memory management for AI assistants
1004 | </p>
1005 | <div class="footer-license">
1006 | <svg width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
1007 | <path d="M14,17H7V15H14M17,13H7V11H17M17,9H7V7H17M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z"/>
1008 | </svg>
1009 | <a href="https://github.com/doobidoo/mcp-memory-service/blob/main/LICENSE" target="_blank" rel="noopener">
1010 | Licensed under Apache 2.0
1011 | </a>
1012 | </div>
1013 | <div class="footer-copyright">
1014 | © 2024 Heinrich Krupp
1015 | </div>
1016 | </div>
1017 | </div>
1018 | </footer>
1019 |
1020 | <!-- Memory Detail Modal -->
1021 | <div id="memoryModal" class="modal-overlay">
1022 | <div class="modal-content">
1023 | <div class="modal-header">
1024 | <h3 id="modalTitle">Memory Details</h3>
1025 | <button class="modal-close" aria-label="Close">
1026 | <svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
1027 | <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
1028 | </svg>
1029 | </button>
1030 | </div>
1031 | <div class="modal-body">
1032 | <div id="modalContent">
1033 | <!-- Dynamic memory content -->
1034 | </div>
1035 | </div>
1036 | <div class="modal-footer">
1037 | <button class="btn btn-secondary" id="editMemoryBtn">Edit</button>
1038 | <button class="btn btn-danger" id="deleteMemoryBtn">Delete</button>
1039 | <button class="btn btn-primary" id="shareMemoryBtn">Share</button>
1040 | </div>
1041 | </div>
1042 | </div>
1043 |
1044 | <!-- Add Memory Modal -->
1045 | <div id="addMemoryModal" class="modal-overlay">
1046 | <div class="modal-content">
1047 | <div class="modal-header">
1048 | <h3>Add New Memory</h3>
1049 | <button class="modal-close" aria-label="Close">
1050 | <svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
1051 | <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
1052 | </svg>
1053 | </button>
1054 | </div>
1055 | <div class="modal-body">
1056 | <form id="addMemoryForm">
1057 | <div class="form-group">
1058 | <label for="memoryContent">Content</label>
1059 | <textarea id="memoryContent" rows="6" placeholder="Enter your memory content..."></textarea>
1060 | </div>
1061 | <div class="form-group">
1062 | <label for="memoryTags">Tags (comma-separated)</label>
1063 | <input type="text" id="memoryTags" placeholder="e.g., coding, javascript, api">
1064 | </div>
1065 | <div class="form-group">
1066 | <label for="memoryType">Type</label>
1067 | <select id="memoryType">
1068 | <option value="note">Note</option>
1069 | <option value="code">Code</option>
1070 | <option value="reference">Reference</option>
1071 | <option value="idea">Idea</option>
1072 | </select>
1073 | </div>
1074 | </form>
1075 | </div>
1076 | <div class="modal-footer">
1077 | <button class="btn btn-secondary" id="cancelAddBtn">Cancel</button>
1078 | <button class="btn btn-primary" id="saveMemoryBtn">Save Memory</button>
1079 | </div>
1080 | </div>
1081 | </div>
1082 |
1083 | <!-- Settings Modal -->
1084 | <div id="settingsModal" class="modal-overlay">
1085 | <div class="modal-content">
1086 | <div class="modal-header">
1087 | <h3>Settings</h3>
1088 | <button class="modal-close" aria-label="Close">
1089 | <svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
1090 | <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
1091 | </svg>
1092 | </button>
1093 | </div>
1094 | <div class="modal-body">
1095 | <form id="settingsForm">
1096 | <h4 class="settings-section-heading">Preferences</h4>
1097 | <div class="form-group">
1098 | <label for="themeSelect">Theme</label>
1099 | <select id="themeSelect">
1100 | <option value="light">Light</option>
1101 | <option value="dark">Dark</option>
1102 | </select>
1103 | </div>
1104 | <div class="form-group">
1105 | <label for="viewDensity">View Density</label>
1106 | <select id="viewDensity">
1107 | <option value="comfortable">Comfortable</option>
1108 | <option value="compact">Compact</option>
1109 | </select>
1110 | </div>
1111 | <div class="form-group">
1112 | <label for="previewLines">Memory Preview Lines</label>
1113 | <input type="number" id="previewLines" min="1" max="10" value="3">
1114 | </div>
1115 |
1116 | <hr class="settings-divider">
1117 |
1118 | <h4 class="settings-section-heading" style="margin-bottom: var(--space-4);">System Information</h4>
1119 | <div class="system-info">
1120 | <div class="info-row">
1121 | <span class="info-label">Version:</span>
1122 | <span class="info-value" id="settingsVersion">Loading...</span>
1123 | </div>
1124 | <div class="info-row">
1125 | <span class="info-label">Storage Backend:</span>
1126 | <span class="info-value" id="settingsBackend">Loading...</span>
1127 | </div>
1128 | <div class="info-row">
1129 | <span class="info-label">Primary Backend:</span>
1130 | <span class="info-value" id="settingsPrimaryBackend">Loading...</span>
1131 | </div>
1132 | <div class="info-row">
1133 | <span class="info-label">Embedding Model:</span>
1134 | <span class="info-value" id="settingsEmbeddingModel">Loading...</span>
1135 | </div>
1136 | <div class="info-row">
1137 | <span class="info-label">Embedding Dimensions:</span>
1138 | <span class="info-value" id="settingsEmbeddingDim">Loading...</span>
1139 | </div>
1140 | <div class="info-row">
1141 | <span class="info-label">Database Size:</span>
1142 | <span class="info-value" id="settingsDbSize">Loading...</span>
1143 | </div>
1144 | <div class="info-row">
1145 | <span class="info-label">Total Memories:</span>
1146 | <span class="info-value" id="settingsTotalMemories">Loading...</span>
1147 | </div>
1148 | <div class="info-row">
1149 | <span class="info-label">Uptime:</span>
1150 | <span class="info-value" id="settingsUptime">Loading...</span>
1151 | </div>
1152 | </div>
1153 |
1154 | <hr class="settings-divider">
1155 |
1156 | <h4 class="settings-section-heading">Backup & Restore</h4>
1157 | <div class="backup-settings">
1158 | <div class="info-row">
1159 | <span class="info-label">Last Backup:</span>
1160 | <span class="info-value" id="settingsLastBackup">Never</span>
1161 | </div>
1162 | <div class="info-row">
1163 | <span class="info-label">Backup Count:</span>
1164 | <span class="info-value" id="settingsBackupCount">0</span>
1165 | </div>
1166 | <div class="info-row">
1167 | <span class="info-label">Next Scheduled:</span>
1168 | <span class="info-value" id="settingsNextBackup">-</span>
1169 | </div>
1170 | <div class="backup-actions">
1171 | <button id="backupNowButton" class="btn btn-sm btn-primary">Create Backup Now</button>
1172 | <button id="viewBackupsButton" class="btn btn-sm btn-secondary">View Backups</button>
1173 | </div>
1174 | </div>
1175 | </form>
1176 | </div>
1177 | <div class="modal-footer">
1178 | <button class="btn btn-secondary" id="cancelSettingsBtn">Cancel</button>
1179 | <button class="btn btn-primary" id="saveSettingsBtn">Save Settings</button>
1180 | </div>
1181 | </div>
1182 | </div>
1183 |
1184 | <!-- Loading Overlay -->
1185 | <div id="loadingOverlay" class="loading-overlay">
1186 | <div class="loading-spinner"></div>
1187 | <p>Loading...</p>
1188 | </div>
1189 |
1190 | <!-- Toast Notifications -->
1191 | <div id="toastContainer" class="toast-container"></div>
1192 | </div>
1193 |
1194 | <!-- Connection Status Indicator -->
1195 | <div id="connectionStatus" class="connection-status">
1196 | <div class="status-indicator"></div>
1197 | <span class="status-text">Connected</span>
1198 | </div>
1199 |
1200 | <!-- Memory Viewer Modal -->
1201 | <div id="memoryViewerModal" class="modal" style="display: none;">
1202 | <div class="modal-content memory-viewer-content">
1203 | <div class="modal-header">
1204 | <h2>📝 Document Memory Chunks</h2>
1205 | <button class="modal-close" data-action="closeMemoryViewer">×</button>
1206 | </div>
1207 | <div class="modal-body">
1208 | <div class="document-info">
1209 | <h3 id="memoryViewerFilename">Loading...</h3>
1210 | <p id="memoryViewerStats" class="text-muted">0 chunks found</p>
1211 | </div>
1212 | <div class="memory-chunks-container">
1213 | <div id="memoryChunksList" class="memory-chunks-list">
1214 | <!-- Chunks will be populated here -->
1215 | </div>
1216 | </div>
1217 | </div>
1218 | <div class="modal-footer">
1219 | <button class="btn btn-secondary" data-action="closeMemoryViewer">Close</button>
1220 | </div>
1221 | </div>
1222 | </div>
1223 |
1224 | <script src="/static/app.js?v=8.27.2-TOAST-AND-ICON"></script>
1225 | <link rel="stylesheet" href="/static/style.css?v=8.27.2-TOAST-AND-ICON">
1226 | </body>
1227 | </html>
```
--------------------------------------------------------------------------------
/scripts/installation/install.py:
--------------------------------------------------------------------------------
```python
1 | #!/usr/bin/env python3
2 | """
3 | Installation script for MCP Memory Service with cross-platform compatibility.
4 | This script guides users through the installation process with the appropriate
5 | dependencies for their platform.
6 | """
7 | import os
8 | import sys
9 | import platform
10 | import subprocess
11 | import argparse
12 | import shutil
13 | from pathlib import Path
14 | from typing import Tuple, Dict, Any, Optional
15 | import re
16 |
17 | # Import shared GPU detection utilities
18 | try:
19 | from mcp_memory_service.utils.gpu_detection import detect_gpu as shared_detect_gpu
20 | except ImportError:
21 | # Fallback for scripts directory context
22 | sys.path.insert(0, str(Path(__file__).parent.parent.parent))
23 | from src.mcp_memory_service.utils.gpu_detection import detect_gpu as shared_detect_gpu
24 |
25 | def is_python_version_at_least(major, minor):
26 | """Check if current Python version is at least the specified version.
27 |
28 | Args:
29 | major: Major version number
30 | minor: Minor version number
31 |
32 | Returns:
33 | bool: True if current Python version >= specified version
34 | """
35 | return sys.version_info >= (major, minor)
36 |
37 | def get_python_version_string():
38 | """Get Python version as a string (e.g., '3.12').
39 |
40 | Returns:
41 | str: Python version string
42 | """
43 | return f"{sys.version_info.major}.{sys.version_info.minor}"
44 |
45 | def get_package_version():
46 | """Get the current package version from pyproject.toml.
47 |
48 | Returns:
49 | str: The version string from pyproject.toml or fallback version
50 | """
51 | fallback_version = "7.2.0"
52 |
53 | try:
54 | # Get path to pyproject.toml relative to this script
55 | pyproject_path = Path(__file__).parent.parent.parent / "pyproject.toml"
56 |
57 | if not pyproject_path.exists():
58 | print_warning(f"pyproject.toml not found at {pyproject_path}, using fallback version {fallback_version}")
59 | return fallback_version
60 |
61 | with open(pyproject_path, "r", encoding="utf-8") as f:
62 | content = f.read()
63 |
64 | # Extract version using regex - matches standard pyproject.toml format
65 | version_pattern = r'^version\s*=\s*["\']([^"\'\n]+)["\']'
66 | version_match = re.search(version_pattern, content, re.MULTILINE)
67 |
68 | if version_match:
69 | version = version_match.group(1).strip()
70 | if version: # Ensure non-empty version
71 | return version
72 | else:
73 | print_warning("Empty version found in pyproject.toml, using fallback")
74 | return fallback_version
75 | else:
76 | print_warning("Version not found in pyproject.toml, using fallback")
77 | return fallback_version
78 |
79 | except (OSError, IOError) as e:
80 | print_warning(f"Failed to read pyproject.toml: {e}, using fallback version {fallback_version}")
81 | return fallback_version
82 | except Exception as e:
83 | print_warning(f"Unexpected error parsing version: {e}, using fallback version {fallback_version}")
84 | return fallback_version
85 |
86 | def print_header(text):
87 | """Print a formatted header."""
88 | print("\n" + "=" * 80)
89 | print(f" {text}")
90 | print("=" * 80)
91 |
92 | def print_step(step, text):
93 | """Print a formatted step."""
94 | print(f"\n[{step}] {text}")
95 |
96 | def print_info(text):
97 | """Print formatted info text."""
98 | print(f" → {text}")
99 |
100 | def print_error(text):
101 | """Print formatted error text."""
102 | print(f" ❌ ERROR: {text}")
103 |
104 | def print_success(text):
105 | """Print formatted success text."""
106 | print(f" ✅ {text}")
107 |
108 | def print_warning(text):
109 | """Print formatted warning text."""
110 | print(f" ⚠️ {text}")
111 |
112 | def run_command_safe(cmd, success_msg=None, error_msg=None, silent=False,
113 | timeout=None, fallback_in_venv=False):
114 | """
115 | Run a subprocess command with standardized error handling.
116 |
117 | Args:
118 | cmd: Command to run (list of strings)
119 | success_msg: Message to print on success
120 | error_msg: Custom error message
121 | silent: If True, suppress stdout/stderr
122 | timeout: Command timeout in seconds
123 | fallback_in_venv: If True and command fails, warn instead of error when in virtual environment
124 |
125 | Returns:
126 | tuple: (success: bool, result: subprocess.CompletedProcess or None)
127 | """
128 | # Validate command input
129 | if not cmd or not isinstance(cmd, (list, tuple)):
130 | print_error("Invalid command: must be a non-empty list or tuple")
131 | return False, None
132 |
133 | if not all(isinstance(arg, (str, int, float)) for arg in cmd if arg is not None):
134 | print_error("Invalid command arguments: all arguments must be strings, numbers, or None")
135 | return False, None
136 |
137 | # Filter out None values and convert to strings
138 | cmd_clean = [str(arg) for arg in cmd if arg is not None]
139 | if not cmd_clean:
140 | print_error("Command is empty after filtering")
141 | return False, None
142 |
143 | try:
144 | kwargs = {'capture_output': False, 'text': True}
145 | if silent:
146 | kwargs.update({'stdout': subprocess.DEVNULL, 'stderr': subprocess.DEVNULL})
147 | if timeout:
148 | kwargs['timeout'] = timeout
149 |
150 | result = subprocess.run(cmd_clean, check=True, **kwargs)
151 | if success_msg:
152 | print_success(success_msg)
153 | return True, result
154 | except subprocess.TimeoutExpired as e:
155 | if error_msg:
156 | timeout_msg = error_msg
157 | elif hasattr(e, 'timeout') and e.timeout:
158 | timeout_msg = f"Command timed out after {e.timeout}s"
159 | else:
160 | timeout_msg = "Command timed out"
161 | print_error(timeout_msg)
162 | return False, None
163 | except subprocess.CalledProcessError as e:
164 | if fallback_in_venv:
165 | in_venv = sys.prefix != sys.base_prefix
166 | if in_venv:
167 | fallback_msg = error_msg or "Command failed, but you're in a virtual environment. If you're using an alternative package manager, this may be normal."
168 | print_warning(fallback_msg)
169 | print_warning("Note: Installation may not have succeeded. Please verify manually if needed.")
170 | return True, None # Proceed anyway in venv with warning
171 |
172 | if error_msg:
173 | print_error(error_msg)
174 | else:
175 | # Safe command formatting for error messages
176 | cmd_str = ' '.join(f'"{arg}"' if ' ' in str(arg) else str(arg) for arg in cmd_clean)
177 | print_error(f"Command failed (exit code {e.returncode}): {cmd_str}")
178 | return False, None
179 | except FileNotFoundError:
180 | if error_msg:
181 | print_error(error_msg)
182 | else:
183 | print_error(f"Command not found: {cmd_clean[0]}")
184 | return False, None
185 | except PermissionError:
186 | permission_msg = error_msg or f"Permission denied executing: {cmd_clean[0]}"
187 | print_error(permission_msg)
188 | return False, None
189 |
190 | def install_package_safe(package, success_msg=None, error_msg=None, fallback_in_venv=True):
191 | """
192 | Install a Python package with standardized error handling.
193 |
194 | Args:
195 | package: Package name or requirement string
196 | success_msg: Message to print on success
197 | error_msg: Custom error message
198 | fallback_in_venv: If True, warn instead of error when in virtual environment
199 |
200 | Returns:
201 | bool: True if installation succeeded OR if fallback was applied (see warning messages)
202 | """
203 | cmd = [sys.executable, '-m', 'pip', 'install', package]
204 | default_success = success_msg or f"{package} installed successfully"
205 | default_error = error_msg or f"Failed to install {package}"
206 |
207 | if fallback_in_venv:
208 | default_error += ". If you're using an alternative package manager like uv, please install manually."
209 |
210 | success, _ = run_command_safe(
211 | cmd,
212 | success_msg=default_success,
213 | error_msg=default_error,
214 | silent=True,
215 | fallback_in_venv=fallback_in_venv
216 | )
217 | return success
218 |
219 | def detect_system():
220 | """Detect the system architecture and platform."""
221 | system = platform.system().lower()
222 | machine = platform.machine().lower()
223 | python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
224 |
225 | is_windows = system == "windows"
226 | is_macos = system == "darwin"
227 | is_linux = system == "linux"
228 | is_arm = machine in ("arm64", "aarch64")
229 | is_x86 = machine in ("x86_64", "amd64", "x64")
230 |
231 | print_info(f"System: {platform.system()} {platform.release()}")
232 | print_info(f"Architecture: {machine}")
233 | print_info(f"Python: {python_version}")
234 |
235 | # Check for virtual environment
236 | in_venv = sys.prefix != sys.base_prefix
237 | if not in_venv:
238 | print_warning("Not running in a virtual environment. It's recommended to install in a virtual environment.")
239 | else:
240 | print_info(f"Virtual environment: {sys.prefix}")
241 |
242 | # Check for Homebrew PyTorch installation
243 | has_homebrew_pytorch = False
244 | homebrew_pytorch_version = None
245 | if is_macos:
246 | try:
247 | # Check if pytorch is installed via brew
248 | result = subprocess.run(
249 | ['brew', 'list', 'pytorch', '--version'],
250 | capture_output=True,
251 | text=True
252 | )
253 | if result.returncode == 0:
254 | has_homebrew_pytorch = True
255 | # Extract version from output
256 | version_line = result.stdout.strip()
257 | homebrew_pytorch_version = version_line.split()[1] if len(version_line.split()) > 1 else "Unknown"
258 | print_info(f"Detected Homebrew PyTorch installation: {homebrew_pytorch_version}")
259 | except (subprocess.SubprocessError, FileNotFoundError):
260 | pass
261 |
262 | return {
263 | "system": system,
264 | "machine": machine,
265 | "python_version": python_version,
266 | "is_windows": is_windows,
267 | "is_macos": is_macos,
268 | "is_linux": is_linux,
269 | "is_arm": is_arm,
270 | "is_x86": is_x86,
271 | "in_venv": in_venv,
272 | "has_homebrew_pytorch": has_homebrew_pytorch,
273 | "homebrew_pytorch_version": homebrew_pytorch_version
274 | }
275 |
276 | def detect_gpu():
277 | """Detect GPU and acceleration capabilities.
278 |
279 | Wrapper function that uses the shared GPU detection module.
280 | """
281 | system_info = detect_system()
282 |
283 | # Use shared GPU detection module
284 | gpu_info = shared_detect_gpu(system_info)
285 |
286 | # Print GPU information (maintain installer output format)
287 | if gpu_info.get("has_cuda"):
288 | cuda_version = gpu_info.get("cuda_version")
289 | print_info(f"CUDA detected: {cuda_version or 'Unknown version'}")
290 | if gpu_info.get("has_rocm"):
291 | rocm_version = gpu_info.get("rocm_version")
292 | print_info(f"ROCm detected: {rocm_version or 'Unknown version'}")
293 | if gpu_info.get("has_mps"):
294 | print_info("Apple Metal Performance Shaders (MPS) detected")
295 | if gpu_info.get("has_directml"):
296 | directml_version = gpu_info.get("directml_version")
297 | if directml_version:
298 | print_info(f"DirectML detected: {directml_version}")
299 | else:
300 | print_info("DirectML detected")
301 |
302 | if not (gpu_info.get("has_cuda") or gpu_info.get("has_rocm") or
303 | gpu_info.get("has_mps") or gpu_info.get("has_directml")):
304 | print_info("No GPU acceleration detected, will use CPU-only mode")
305 |
306 | return gpu_info
307 |
308 | def check_dependencies():
309 | """Check for required dependencies.
310 |
311 | Note on package managers:
312 | - Traditional virtual environments (venv, virtualenv) include pip by default
313 | - Alternative package managers like uv may not include pip or may manage packages differently
314 | - We attempt multiple detection methods for pip and only fail if:
315 | a) We're not in a virtual environment, or
316 | b) We can't detect pip AND can't install dependencies
317 |
318 | We proceed with installation even if pip isn't detected when in a virtual environment,
319 | assuming an alternative package manager (like uv) is handling dependencies.
320 |
321 | Returns:
322 | bool: True if all dependencies are met, False otherwise.
323 | """
324 | print_step("2", "Checking dependencies")
325 |
326 | # Check for pip
327 | pip_installed = False
328 |
329 | # Try subprocess check first
330 | success, _ = run_command_safe(
331 | [sys.executable, '-m', 'pip', '--version'],
332 | success_msg="pip is installed",
333 | silent=True,
334 | fallback_in_venv=True
335 | )
336 |
337 | if success:
338 | pip_installed = True
339 | else:
340 | # Fallback to import check
341 | try:
342 | import pip
343 | pip_installed = True
344 | print_info(f"pip is installed: {pip.__version__}")
345 | except ImportError:
346 | # Check if we're in a virtual environment
347 | in_venv = sys.prefix != sys.base_prefix
348 | if in_venv:
349 | print_warning("pip could not be detected, but you're in a virtual environment. "
350 | "If you're using uv or another alternative package manager, this is normal. "
351 | "Continuing installation...")
352 | pip_installed = True # Proceed anyway
353 | else:
354 | print_error("pip is not installed. Please install pip first.")
355 | return False
356 |
357 | # Check for setuptools
358 | try:
359 | import setuptools
360 | print_info(f"setuptools is installed: {setuptools.__version__}")
361 | except ImportError:
362 | print_warning("setuptools is not installed. Will attempt to install it.")
363 | # If pip is available, use it to install setuptools
364 | if pip_installed:
365 | success = install_package_safe("setuptools")
366 | if not success:
367 | return False
368 | else:
369 | # Should be unreachable since pip_installed would only be False if we returned earlier
370 | print_error("Cannot install setuptools without pip. Please install setuptools manually.")
371 | return False
372 |
373 | # Check for wheel
374 | try:
375 | import wheel
376 | print_info(f"wheel is installed: {wheel.__version__}")
377 | except ImportError:
378 | print_warning("wheel is not installed. Will attempt to install it.")
379 | # If pip is available, use it to install wheel
380 | if pip_installed:
381 | success = install_package_safe("wheel")
382 | if not success:
383 | return False
384 | else:
385 | # Should be unreachable since pip_installed would only be False if we returned earlier
386 | print_error("Cannot install wheel without pip. Please install wheel manually.")
387 | return False
388 |
389 | return True
390 |
391 | def install_pytorch_platform_specific(system_info, gpu_info):
392 | """Install PyTorch with platform-specific configurations."""
393 | if system_info["is_windows"]:
394 | return install_pytorch_windows(gpu_info)
395 | elif system_info["is_macos"] and system_info["is_x86"]:
396 | return install_pytorch_macos_intel()
397 | else:
398 | # For other platforms, let the regular installer handle it
399 | return True
400 |
401 | def install_pytorch_macos_intel():
402 | """Install PyTorch specifically for macOS with Intel CPUs."""
403 | print_step("3a", "Installing PyTorch for macOS Intel CPU")
404 |
405 | # Use the versions known to work well on macOS Intel and with Python 3.13+
406 | try:
407 | # For Python 3.13+, we need newer PyTorch versions
408 | python_version = sys.version_info
409 |
410 | if python_version >= (3, 13):
411 | # For Python 3.13+, try to install latest compatible version
412 | print_info(f"Installing PyTorch for macOS Intel (Python {python_version.major}.{python_version.minor})...")
413 | print_info("Attempting to install latest PyTorch compatible with Python 3.13...")
414 |
415 | # Try to install without version specifiers to get latest compatible version
416 | cmd = [
417 | sys.executable, '-m', 'pip', 'install',
418 | "torch", "torchvision", "torchaudio"
419 | ]
420 | success, _ = run_command_safe(
421 | cmd,
422 | success_msg="Latest PyTorch installed successfully",
423 | silent=False
424 | )
425 |
426 | if success:
427 | st_version = "3.0.0" # Newer sentence-transformers for newer PyTorch
428 | else:
429 | print_warning("Failed to install latest PyTorch, trying fallback version...")
430 | # Fallback to a specific version
431 | torch_version = "2.1.0"
432 | torch_vision_version = "0.16.0"
433 | torch_audio_version = "2.1.0"
434 | st_version = "3.0.0"
435 |
436 | print_info(f"Trying fallback to PyTorch {torch_version}...")
437 |
438 | cmd = [
439 | sys.executable, '-m', 'pip', 'install',
440 | f"torch=={torch_version}",
441 | f"torchvision=={torch_vision_version}",
442 | f"torchaudio=={torch_audio_version}"
443 | ]
444 | success, _ = run_command_safe(
445 | cmd,
446 | success_msg=f"PyTorch {torch_version} installed successfully",
447 | error_msg="Failed to install PyTorch fallback version",
448 | silent=False
449 | )
450 | if not success:
451 | return False
452 | else:
453 | # Use traditional versions for older Python
454 | torch_version = "1.13.1"
455 | torch_vision_version = "0.14.1"
456 | torch_audio_version = "0.13.1"
457 | st_version = "2.2.2"
458 |
459 | print_info(f"Installing PyTorch {torch_version} for macOS Intel (Python {python_version.major}.{python_version.minor})...")
460 |
461 | # Install PyTorch first with compatible version
462 | packages = [f"torch=={torch_version}", f"torchvision=={torch_vision_version}", f"torchaudio=={torch_audio_version}"]
463 | success, _ = run_command_safe(
464 | [sys.executable, '-m', 'pip', 'install'] + packages,
465 | success_msg=f"PyTorch {torch_version} installed successfully"
466 | )
467 | if not success:
468 | raise RuntimeError(f"Failed to install PyTorch {torch_version}")
469 |
470 | # Install a compatible version of sentence-transformers
471 | print_info(f"Installing sentence-transformers {st_version}...")
472 | success, _ = run_command_safe(
473 | [sys.executable, '-m', 'pip', 'install', f"sentence-transformers=={st_version}"],
474 | success_msg=f"sentence-transformers {st_version} installed successfully"
475 | )
476 | if not success:
477 | raise RuntimeError(f"Failed to install sentence-transformers {st_version}")
478 |
479 | print_success(f"PyTorch {torch_version} and sentence-transformers {st_version} installed successfully for macOS Intel")
480 | return True
481 | except RuntimeError as e:
482 | print_error(f"Failed to install PyTorch for macOS Intel: {e}")
483 |
484 | # Provide fallback instructions
485 | if python_version >= (3, 13):
486 | print_warning("You may need to manually install compatible versions for Python 3.13+ on Intel macOS:")
487 | print_info("pip install torch==2.3.0 torchvision==0.18.0 torchaudio==2.3.0")
488 | print_info("pip install sentence-transformers==3.0.0")
489 | else:
490 | print_warning("You may need to manually install compatible versions for Intel macOS:")
491 | print_info("pip install torch==1.13.1 torchvision==0.14.1 torchaudio==0.13.1")
492 | print_info("pip install sentence-transformers==2.2.2")
493 |
494 | return False
495 |
496 | def install_pytorch_windows(gpu_info):
497 | """Install PyTorch on Windows using the appropriate index URL."""
498 | print_step("3a", "Installing PyTorch for Windows")
499 |
500 | # Determine the appropriate PyTorch index URL based on GPU
501 | if gpu_info["has_cuda"]:
502 | # Get CUDA version and determine appropriate index URL
503 | cuda_version = gpu_info.get("cuda_version", "")
504 |
505 | # Extract major version from CUDA version string
506 | cuda_major = None
507 | if cuda_version:
508 | # Try to extract the major version (e.g., "11.8" -> "11")
509 | try:
510 | cuda_major = cuda_version.split('.')[0]
511 | except (IndexError, AttributeError):
512 | pass
513 |
514 | # Default to cu118 if we couldn't determine the version or it's not a common one
515 | if cuda_major == "12":
516 | cuda_suffix = "cu121" # CUDA 12.x
517 | print_info(f"Detected CUDA {cuda_version}, using cu121 channel")
518 | elif cuda_major == "11":
519 | cuda_suffix = "cu118" # CUDA 11.x
520 | print_info(f"Detected CUDA {cuda_version}, using cu118 channel")
521 | elif cuda_major == "10":
522 | cuda_suffix = "cu102" # CUDA 10.x
523 | print_info(f"Detected CUDA {cuda_version}, using cu102 channel")
524 | else:
525 | # Default to cu118 as a safe choice for newer NVIDIA GPUs
526 | cuda_suffix = "cu118"
527 | print_info(f"Using default cu118 channel for CUDA {cuda_version}")
528 |
529 | index_url = f"https://download.pytorch.org/whl/{cuda_suffix}"
530 | else:
531 | # CPU-only version
532 | index_url = "https://download.pytorch.org/whl/cpu"
533 | print_info("Using CPU-only PyTorch for Windows")
534 |
535 | # Install PyTorch with the appropriate index URL
536 | try:
537 | # Use a stable version that's known to have Windows wheels
538 | torch_version = "2.1.0" # This version has Windows wheels available
539 |
540 | cmd = [
541 | sys.executable, '-m', 'pip', 'install',
542 | f"torch=={torch_version}",
543 | f"torchvision=={torch_version}",
544 | f"torchaudio=={torch_version}",
545 | f"--index-url={index_url}"
546 | ]
547 |
548 | success, _ = run_command_safe(
549 | cmd,
550 | success_msg="PyTorch installed successfully for Windows",
551 | error_msg="Failed to install PyTorch for Windows",
552 | silent=False
553 | )
554 | if not success:
555 | return False
556 |
557 | # Check if DirectML is needed
558 | if gpu_info["has_directml"]:
559 | success = install_package_safe(
560 | "torch-directml>=0.2.0",
561 | success_msg="torch-directml installed successfully for DirectML support",
562 | error_msg="Failed to install torch-directml"
563 | )
564 | if not success:
565 | print_warning("DirectML support may not be available")
566 |
567 | print_success("PyTorch installed successfully for Windows")
568 | return True
569 | except RuntimeError as e:
570 | print_error(f"Failed to install PyTorch for Windows: {e}")
571 | print_warning("You may need to manually install PyTorch using instructions from https://pytorch.org/get-started/locally/")
572 | return False
573 |
574 | def detect_storage_backend_compatibility(system_info, gpu_info):
575 | """Detect which storage backends are compatible with the current environment."""
576 | print_step("3a", "Analyzing storage backend compatibility")
577 |
578 | compatibility = {
579 | "cloudflare": {"supported": True, "issues": [], "recommendation": "production"},
580 | "sqlite_vec": {"supported": True, "issues": [], "recommendation": "development"},
581 | "chromadb": {"supported": True, "issues": [], "recommendation": "team"},
582 | "hybrid": {"supported": True, "issues": [], "recommendation": "recommended"}
583 | }
584 |
585 | # Check ChromaDB compatibility issues
586 | chromadb_issues = []
587 |
588 | # macOS Intel compatibility issues
589 | if system_info["is_macos"] and system_info["is_x86"]:
590 | chromadb_issues.append("ChromaDB has known installation issues on older macOS Intel systems")
591 | chromadb_issues.append("May require specific dependency versions")
592 | compatibility["chromadb"]["recommendation"] = "problematic"
593 | compatibility["sqlite_vec"]["recommendation"] = "recommended"
594 |
595 | # Memory constraints
596 | total_memory_gb = 0
597 | try:
598 | import psutil
599 | total_memory_gb = psutil.virtual_memory().total / (1024**3)
600 | except ImportError:
601 | # Fallback memory detection
602 | try:
603 | with open('/proc/meminfo', 'r') as f:
604 | for line in f:
605 | if line.startswith('MemTotal:'):
606 | total_memory_gb = int(line.split()[1]) / (1024**2)
607 | break
608 | except (FileNotFoundError, IOError):
609 | pass
610 |
611 | if total_memory_gb > 0 and total_memory_gb < 4:
612 | chromadb_issues.append(f"System has {total_memory_gb:.1f}GB RAM - ChromaDB may consume significant memory")
613 | compatibility["sqlite_vec"]["recommendation"] = "recommended"
614 |
615 | # Older Python versions
616 | python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
617 | if sys.version_info < (3, 9):
618 | chromadb_issues.append(f"Python {python_version} may have ChromaDB compatibility issues")
619 |
620 | # ARM architecture considerations
621 | if system_info["is_arm"]:
622 | print_info("ARM architecture detected - both backends should work well")
623 |
624 | compatibility["chromadb"]["issues"] = chromadb_issues
625 |
626 | # Print compatibility analysis
627 | print_info("Storage Backend Compatibility Analysis:")
628 |
629 | for backend, info in compatibility.items():
630 | status = "✅" if info["supported"] else "❌"
631 | rec_text = {
632 | "production": "☁️ PRODUCTION (Cloud)",
633 | "development": "🪶 DEVELOPMENT (Local)",
634 | "team": "👥 TEAM (Multi-client)",
635 | "recommended": "🌟 RECOMMENDED",
636 | "default": "📦 Standard",
637 | "problematic": "⚠️ May have issues",
638 | "lightweight": "🪶 Lightweight"
639 | }.get(info["recommendation"], "")
640 |
641 | print_info(f" {status} {backend.upper()}: {rec_text}")
642 |
643 | if info["issues"]:
644 | for issue in info["issues"]:
645 | print_info(f" • {issue}")
646 |
647 | return compatibility
648 |
649 | def choose_storage_backend(system_info, gpu_info, args):
650 | """Choose storage backend based on environment and user preferences."""
651 | compatibility = detect_storage_backend_compatibility(system_info, gpu_info)
652 |
653 | # Check if user specified a backend via environment
654 | env_backend = os.environ.get('MCP_MEMORY_STORAGE_BACKEND')
655 | if env_backend:
656 | print_info(f"Using storage backend from environment: {env_backend}")
657 | return env_backend
658 |
659 | # Check for command line argument (we'll add this)
660 | if hasattr(args, 'storage_backend') and args.storage_backend:
661 | print_info(f"Using storage backend from command line: {args.storage_backend}")
662 | return args.storage_backend
663 |
664 | # Auto-select based on compatibility
665 | recommended_backend = None
666 | for backend, info in compatibility.items():
667 | if info["recommendation"] == "recommended":
668 | recommended_backend = backend
669 | break
670 |
671 | if not recommended_backend:
672 | recommended_backend = "sqlite_vec" # Default fallback for local development
673 |
674 | # Interactive backend selection
675 | print_step("3b", "Storage Backend Selection")
676 | print_info("Choose the storage backend that best fits your use case:")
677 | print_info("")
678 | print_info("Usage scenarios:")
679 | print_info(" 1. Production/Shared (Cloudflare) - Cloud storage, multi-user access, requires credentials")
680 | print_info(" 2. Development/Personal (SQLite-vec) - Local, lightweight, single-user")
681 | print_info(" 3. Team/Multi-client (ChromaDB) - Local server, multiple clients")
682 | print_info(" 4. Hybrid (Recommended) - Fast local SQLite + background Cloudflare sync")
683 | print_info(" 5. Auto-detect - Try optimal backend based on your system")
684 | print_info("")
685 |
686 | # Show compatibility analysis
687 | for i, (backend, info) in enumerate(compatibility.items(), 1):
688 | if backend == "auto_detect":
689 | continue
690 | status = "✅" if info["supported"] else "❌"
691 | rec_text = {
692 | "production": "☁️ PRODUCTION (Cloud)",
693 | "development": "🪶 DEVELOPMENT (Local)",
694 | "team": "👥 TEAM (Multi-client)",
695 | "recommended": "🌟 RECOMMENDED",
696 | "problematic": "⚠️ May have issues"
697 | }.get(info["recommendation"], "")
698 | print_info(f" {status} {i}. {backend.upper()}: {rec_text}")
699 | if info["issues"]:
700 | for issue in info["issues"]:
701 | print_info(f" • {issue}")
702 |
703 | print_info("")
704 | default_choice = "2" if compatibility["chromadb"]["recommendation"] == "problematic" else "2"
705 |
706 | while True:
707 | try:
708 | choice = input(f"Choose storage backend [1-5] (default: 4 - hybrid): ").strip()
709 | if not choice:
710 | choice = "4" # Default to hybrid
711 |
712 | if choice == "1":
713 | return "cloudflare"
714 | elif choice == "2":
715 | return "sqlite_vec"
716 | elif choice == "3":
717 | return "chromadb"
718 | elif choice == "4":
719 | return "hybrid"
720 | elif choice == "5":
721 | return "auto_detect"
722 | else:
723 | print_error("Please enter 1, 2, 3, 4, or 5")
724 | except (EOFError, KeyboardInterrupt):
725 | print_info(f"\nUsing recommended backend: hybrid")
726 | return "hybrid"
727 |
728 | def setup_cloudflare_credentials():
729 | """Interactive setup of Cloudflare credentials."""
730 | print_step("3c", "Cloudflare Backend Setup")
731 | print_info("Cloudflare backend requires API credentials for D1 database and Vectorize index.")
732 | print_info("You'll need:")
733 | print_info(" • Cloudflare API Token (with D1 and Vectorize permissions)")
734 | print_info(" • Account ID")
735 | print_info(" • D1 Database ID")
736 | print_info(" • Vectorize Index name")
737 | print_info("")
738 | print_info("Visit https://dash.cloudflare.com to get these credentials.")
739 | print_info("")
740 |
741 | credentials = {}
742 |
743 | try:
744 | # Get API Token
745 | while True:
746 | token = input("Enter Cloudflare API Token: ").strip()
747 | if token:
748 | credentials['CLOUDFLARE_API_TOKEN'] = token
749 | break
750 | print_error("API token is required")
751 |
752 | # Get Account ID
753 | while True:
754 | account_id = input("Enter Cloudflare Account ID: ").strip()
755 | if account_id:
756 | credentials['CLOUDFLARE_ACCOUNT_ID'] = account_id
757 | break
758 | print_error("Account ID is required")
759 |
760 | # Get D1 Database ID
761 | while True:
762 | d1_id = input("Enter D1 Database ID: ").strip()
763 | if d1_id:
764 | credentials['CLOUDFLARE_D1_DATABASE_ID'] = d1_id
765 | break
766 | print_error("D1 Database ID is required")
767 |
768 | # Get Vectorize Index
769 | vectorize_index = input("Enter Vectorize Index name (default: mcp-memory-index): ").strip()
770 | if not vectorize_index:
771 | vectorize_index = "mcp-memory-index"
772 | credentials['CLOUDFLARE_VECTORIZE_INDEX'] = vectorize_index
773 |
774 | # Set storage backend
775 | credentials['MCP_MEMORY_STORAGE_BACKEND'] = 'cloudflare'
776 |
777 | return credentials
778 |
779 | except (EOFError, KeyboardInterrupt):
780 | print_info("\nCloudflare setup cancelled.")
781 | return None
782 |
783 | def save_credentials_to_env(credentials):
784 | """Save credentials to .env file and current environment."""
785 | env_file = Path('.env')
786 |
787 | # Read existing .env content if it exists
788 | existing_lines = []
789 | if env_file.exists():
790 | with open(env_file, 'r') as f:
791 | existing_lines = f.readlines()
792 |
793 | # Filter out any existing Cloudflare variables
794 | filtered_lines = [
795 | line for line in existing_lines
796 | if not any(key in line for key in credentials.keys())
797 | ]
798 |
799 | # Add new credentials
800 | with open(env_file, 'w') as f:
801 | # Write existing non-Cloudflare lines
802 | f.writelines(filtered_lines)
803 |
804 | # Add separator if file wasn't empty
805 | if filtered_lines and not filtered_lines[-1].endswith('\n'):
806 | f.write('\n')
807 | if filtered_lines:
808 | f.write('\n# Cloudflare Backend Configuration\n')
809 |
810 | # Write Cloudflare credentials
811 | for key, value in credentials.items():
812 | f.write(f'{key}={value}\n')
813 |
814 | # Also set credentials in current environment for immediate use
815 | for key, value in credentials.items():
816 | os.environ[key] = value
817 |
818 | print_success(f"Credentials saved to .env file and current environment")
819 |
820 | def test_cloudflare_connection(credentials):
821 | """Test Cloudflare API connection."""
822 | print_info("Testing Cloudflare API connection...")
823 |
824 | try:
825 | import requests
826 |
827 | headers = {
828 | 'Authorization': f"Bearer {credentials['CLOUDFLARE_API_TOKEN']}",
829 | 'Content-Type': 'application/json'
830 | }
831 |
832 | # Test API token validity
833 | response = requests.get(
834 | "https://api.cloudflare.com/client/v4/user/tokens/verify",
835 | headers=headers,
836 | timeout=10
837 | )
838 |
839 | if response.status_code == 200:
840 | data = response.json()
841 | if data.get("success"):
842 | print_success("API token is valid")
843 | return True
844 | else:
845 | print_error(f"API token validation failed: {data.get('errors')}")
846 | return False
847 | else:
848 | print_error(f"API connection failed with status {response.status_code}")
849 | return False
850 |
851 | except ImportError:
852 | print_warning("Could not test connection (requests not installed)")
853 | print_info("Connection will be tested when the service starts")
854 | return True
855 | except Exception as e:
856 | print_warning(f"Could not test connection: {e}")
857 | print_info("Connection will be tested when the service starts")
858 | return True
859 |
860 | def install_storage_backend(backend, system_info):
861 | """Install the chosen storage backend."""
862 | print_step("3c", f"Installing {backend} storage backend")
863 |
864 | if backend == "cloudflare":
865 | print_info("Cloudflare backend uses cloud services - no local dependencies needed")
866 |
867 | # Setup credentials interactively
868 | credentials = setup_cloudflare_credentials()
869 | if not credentials:
870 | print_warning("Cloudflare setup cancelled. Falling back to SQLite-vec.")
871 | return install_storage_backend("sqlite_vec", system_info)
872 |
873 | # Save credentials to .env file
874 | save_credentials_to_env(credentials)
875 |
876 | # Test connection
877 | connection_ok = test_cloudflare_connection(credentials)
878 | if connection_ok:
879 | print_success("Cloudflare backend configured successfully")
880 | return "cloudflare"
881 | else:
882 | print_warning("Cloudflare connection test failed. You can continue and fix credentials later.")
883 | fallback = input("Continue with Cloudflare anyway? [y/N]: ").strip().lower()
884 | if fallback.startswith('y'):
885 | return "cloudflare"
886 | else:
887 | print_info("Falling back to SQLite-vec for local development.")
888 | return install_storage_backend("sqlite_vec", system_info)
889 |
890 | elif backend == "sqlite_vec":
891 | return install_package_safe(
892 | "sqlite-vec",
893 | success_msg="SQLite-vec installed successfully",
894 | error_msg="Failed to install SQLite-vec"
895 | )
896 |
897 | elif backend == "hybrid":
898 | print_info("Hybrid backend combines fast local SQLite with background Cloudflare sync")
899 |
900 | # First install SQLite-vec for local storage
901 | print_info("Installing SQLite-vec for local storage...")
902 | sqlite_success = install_package_safe(
903 | "sqlite-vec",
904 | success_msg="SQLite-vec installed successfully",
905 | error_msg="Failed to install SQLite-vec"
906 | )
907 | if not sqlite_success:
908 | print_error("Hybrid backend requires SQLite-vec. Installation failed.")
909 | return False
910 |
911 | # Setup Cloudflare credentials for cloud sync
912 | print_info("Configuring Cloudflare for background synchronization...")
913 | credentials = setup_cloudflare_credentials()
914 | if not credentials:
915 | print_warning("Cloudflare setup cancelled.")
916 | fallback = input("Continue with SQLite-vec only? [Y/n]: ").strip().lower()
917 | if not fallback or fallback.startswith('y'):
918 | print_info("Falling back to SQLite-vec for local-only operation.")
919 | return "sqlite_vec"
920 | else:
921 | return False
922 |
923 | # Update credentials to set hybrid backend
924 | credentials['MCP_MEMORY_STORAGE_BACKEND'] = 'hybrid'
925 |
926 | # Save credentials to .env file
927 | save_credentials_to_env(credentials)
928 |
929 | # Test connection
930 | connection_ok = test_cloudflare_connection(credentials)
931 | if connection_ok:
932 | print_success("Hybrid backend configured successfully")
933 | print_info(" • Local storage: SQLite-vec (5ms reads)")
934 | print_info(" • Cloud sync: Cloudflare (background)")
935 | return "hybrid"
936 | else:
937 | print_warning("Cloudflare connection test failed.")
938 | fallback = input("Continue with hybrid (will sync when connection available)? [Y/n]: ").strip().lower()
939 | if not fallback or fallback.startswith('y'):
940 | print_info("Hybrid backend will sync to Cloudflare when connection is available")
941 | return "hybrid"
942 | else:
943 | print_info("Falling back to SQLite-vec for local development.")
944 | return "sqlite_vec"
945 |
946 | elif backend == "chromadb":
947 | chromadb_version = "0.5.23"
948 | success = install_package_safe(
949 | f"chromadb=={chromadb_version}",
950 | success_msg=f"ChromaDB {chromadb_version} installed successfully",
951 | error_msg="Failed to install ChromaDB"
952 | )
953 | if not success:
954 | print_info("This is a known issue on some systems (especially older macOS Intel)")
955 | return success
956 |
957 | elif backend == "auto_detect":
958 | print_info("Attempting auto-detection...")
959 | print_info("Auto-detect will prioritize local backends (SQLite-vec, ChromaDB)")
960 | print_info("For production use, manually select Cloudflare backend.")
961 |
962 | # For auto-detect, try SQLite-vec first (most reliable)
963 | print_info("Trying SQLite-vec installation...")
964 | if install_storage_backend("sqlite_vec", system_info):
965 | print_success("SQLite-vec installed successfully")
966 | return "sqlite_vec"
967 |
968 | print_warning("SQLite-vec installation failed, trying ChromaDB...")
969 | if install_storage_backend("chromadb", system_info):
970 | print_success("ChromaDB installed successfully as fallback")
971 | return "chromadb"
972 |
973 | print_error("All local storage backends failed to install")
974 | print_info("Consider manually configuring Cloudflare backend for production use")
975 | return False
976 |
977 | return False
978 |
979 | def _detect_installer_command():
980 | """Detect available package installer (pip or uv)."""
981 | # Check if pip is available
982 | pip_available, _ = run_command_safe(
983 | [sys.executable, '-m', 'pip', '--version'],
984 | silent=True
985 | )
986 |
987 | # Detect if uv is available
988 | uv_path = shutil.which("uv")
989 | uv_available = uv_path is not None
990 |
991 | # Decide installer command prefix
992 | if pip_available:
993 | return [sys.executable, '-m', 'pip']
994 | elif uv_available:
995 | print_warning("pip not found, but uv detected. Using 'uv pip' for installation.")
996 | return ['uv', 'pip']
997 | else:
998 | print_error("Neither pip nor uv detected. Cannot install packages.")
999 | return None
1000 |
1001 | def _setup_storage_and_gpu_environment(args, system_info, gpu_info, env):
1002 | """Set up storage backend and GPU environment variables."""
1003 | # Choose and install storage backend
1004 | chosen_backend = choose_storage_backend(system_info, gpu_info, args)
1005 |
1006 | # Check if chromadb was chosen but flag not provided
1007 | if chosen_backend == "chromadb" and not args.with_chromadb:
1008 | print_warning("ChromaDB backend selected but --with-chromadb flag not provided")
1009 | print_info("ChromaDB requires heavy ML dependencies (~1-2GB).")
1010 | print_info("To use ChromaDB, run: python scripts/installation/install.py --with-chromadb")
1011 | print_info("Switching to SQLite-vec backend instead...")
1012 | chosen_backend = "sqlite_vec"
1013 |
1014 | # ChromaDB automatically includes ML dependencies
1015 | if args.with_chromadb:
1016 | args.with_ml = True
1017 |
1018 | if chosen_backend == "auto_detect":
1019 | # Handle auto-detection case - prefer sqlite_vec if chromadb not explicitly requested
1020 | if not args.with_chromadb:
1021 | print_info("Auto-detection: Using SQLite-vec (lightweight option)")
1022 | chosen_backend = "sqlite_vec"
1023 | else:
1024 | actual_backend = install_storage_backend(chosen_backend, system_info)
1025 | if not actual_backend:
1026 | print_error("Failed to install any storage backend")
1027 | return False
1028 | chosen_backend = actual_backend
1029 | else:
1030 | # Install the chosen backend
1031 | if not install_storage_backend(chosen_backend, system_info):
1032 | print_error(f"Failed to install {chosen_backend} storage backend")
1033 | return False
1034 |
1035 | # Set environment variable for chosen backend
1036 | if chosen_backend in ["sqlite_vec", "hybrid", "cloudflare"]:
1037 | env['MCP_MEMORY_STORAGE_BACKEND'] = chosen_backend
1038 | if chosen_backend == "sqlite_vec":
1039 | print_info("Configured to use SQLite-vec storage backend")
1040 | elif chosen_backend == "hybrid":
1041 | print_info("Configured to use Hybrid storage backend (SQLite + Cloudflare)")
1042 | elif chosen_backend == "cloudflare":
1043 | print_info("Configured to use Cloudflare storage backend")
1044 | else:
1045 | env['MCP_MEMORY_STORAGE_BACKEND'] = 'chromadb'
1046 | print_info("Configured to use ChromaDB storage backend")
1047 |
1048 | # Set environment variables based on detected GPU
1049 | if gpu_info.get("has_cuda"):
1050 | print_info("Configuring for CUDA installation")
1051 | elif gpu_info.get("has_rocm"):
1052 | print_info("Configuring for ROCm installation")
1053 | env['MCP_MEMORY_USE_ROCM'] = '1'
1054 | elif gpu_info.get("has_mps"):
1055 | print_info("Configuring for Apple Silicon MPS installation")
1056 | env['PYTORCH_ENABLE_MPS_FALLBACK'] = '1'
1057 | elif gpu_info.get("has_directml"):
1058 | print_info("Configuring for DirectML installation")
1059 | env['MCP_MEMORY_USE_DIRECTML'] = '1'
1060 | else:
1061 | print_info("Configuring for CPU-only installation")
1062 | env['MCP_MEMORY_USE_ONNX'] = '1'
1063 |
1064 | # Check for Homebrew PyTorch installation
1065 | using_homebrew_pytorch = False
1066 | if system_info.get("has_homebrew_pytorch"):
1067 | print_info(f"Using existing Homebrew PyTorch installation (version: {system_info.get('homebrew_pytorch_version')})")
1068 | using_homebrew_pytorch = True
1069 | # Set the environment variable to use ONNX for embeddings
1070 | env['MCP_MEMORY_USE_ONNX'] = '1'
1071 | # Skip the PyTorch installation step
1072 | pytorch_installed = True
1073 | else:
1074 | # Handle platform-specific PyTorch installation
1075 | pytorch_installed = install_pytorch_platform_specific(system_info, gpu_info)
1076 | if not pytorch_installed:
1077 | print_warning("Platform-specific PyTorch installation failed, but will continue with package installation")
1078 |
1079 | try:
1080 | # SQLite-vec with ONNX for macOS with homebrew PyTorch or compatibility issues
1081 | if (system_info["is_macos"] and system_info["is_x86"] and
1082 | (sys.version_info >= (3, 13) or using_homebrew_pytorch or args.skip_pytorch)):
1083 |
1084 | if using_homebrew_pytorch:
1085 | print_info("Using Homebrew PyTorch - installing with SQLite-vec + ONNX configuration")
1086 | elif args.skip_pytorch:
1087 | print_info("Skipping PyTorch installation - using SQLite-vec + ONNX configuration")
1088 | else:
1089 | print_info("Using Python 3.13+ on macOS Intel - using SQLite-vec + ONNX configuration")
1090 |
1091 | # First try to install without ML dependencies
1092 | try:
1093 | cmd = installer_cmd + ['install', '--no-deps'] + install_mode + ['.']
1094 | success, _ = run_command_safe(
1095 | cmd,
1096 | success_msg="Package installed with --no-deps successfully",
1097 | error_msg="Failed to install package with --no-deps",
1098 | silent=False
1099 | )
1100 | if not success:
1101 | raise Exception("Installation failed")
1102 |
1103 | # Install core dependencies except torch/sentence-transformers
1104 | print_info("Installing core dependencies except ML libraries...")
1105 |
1106 | # Create a list of dependencies to install
1107 | # Note: mcp and tokenizers are already in core dependencies
1108 | dependencies = [
1109 | "onnxruntime>=1.14.1" # ONNX runtime for lightweight embeddings
1110 | ]
1111 |
1112 | # Add backend-specific dependencies (sqlite-vec, mcp, tokenizers are already in core)
1113 | if args.with_chromadb:
1114 | dependencies.append("chromadb==0.5.23")
1115 |
1116 | # Install dependencies
1117 | cmd = [sys.executable, '-m', 'pip', 'install'] + dependencies
1118 | success, _ = run_command_safe(
1119 | cmd,
1120 | success_msg="Core dependencies installed successfully",
1121 | error_msg="Failed to install core dependencies",
1122 | silent=False
1123 | )
1124 | if not success:
1125 | raise Exception("Core dependency installation failed")
1126 |
1127 | # Set environment variables for ONNX
1128 | print_info("Configuring to use ONNX runtime for inference without PyTorch...")
1129 | env['MCP_MEMORY_USE_ONNX'] = '1'
1130 | if chosen_backend != "sqlite_vec":
1131 | print_info("Switching to SQLite-vec backend for better compatibility")
1132 | env['MCP_MEMORY_STORAGE_BACKEND'] = 'sqlite_vec'
1133 |
1134 | print_success("MCP Memory Service installed successfully (SQLite-vec + ONNX)")
1135 |
1136 | if using_homebrew_pytorch:
1137 | print_info("Using Homebrew PyTorch installation for embedding generation")
1138 | print_info("Environment configured to use SQLite-vec backend and ONNX runtime")
1139 | else:
1140 | print_warning("ML libraries (PyTorch/sentence-transformers) were not installed due to compatibility issues")
1141 | print_info("The service will use ONNX runtime for inference instead")
1142 |
1143 | return True
1144 | except Exception as e:
1145 | print_error(f"Failed to install with ONNX approach: {e}")
1146 | # Fall through to try standard installation
1147 |
1148 | # Standard installation with appropriate optional dependencies
1149 | install_target = ['.']
1150 |
1151 | # Determine which optional dependencies to include based on backend and flags
1152 | if args.with_chromadb:
1153 | install_target = ['.[chromadb]']
1154 | print_info("Installing with ChromaDB backend support (includes ML dependencies)")
1155 | elif args.with_ml:
1156 | if chosen_backend == "sqlite_vec":
1157 | install_target = ['.[sqlite-ml]']
1158 | print_info("Installing SQLite-vec with full ML capabilities (torch + sentence-transformers)")
1159 | else:
1160 | install_target = ['.[ml]']
1161 | print_info("Installing with ML dependencies for semantic search and embeddings")
1162 | elif chosen_backend == "sqlite_vec":
1163 | install_target = ['.[sqlite]']
1164 | print_info("Installing SQLite-vec with lightweight ONNX embeddings (recommended)")
1165 | print_info("For full ML capabilities with SQLite-vec, use --with-ml flag")
1166 | else:
1167 | print_info("Installing lightweight version (no ML dependencies by default)")
1168 | print_info("For full functionality, use --with-ml flag or install with: pip install mcp-memory-service[ml]")
1169 |
1170 | cmd = installer_cmd + ['install'] + install_mode + install_target
1171 | success, _ = run_command_safe(
1172 | cmd,
1173 | success_msg="MCP Memory Service installed successfully",
1174 | error_msg="Failed to install MCP Memory Service",
1175 | silent=False
1176 | )
1177 | return success
1178 | except Exception as e:
1179 | print_error(f"Failed to install MCP Memory Service: {e}")
1180 |
1181 | # Special handling for macOS with compatibility issues
1182 | if system_info["is_macos"] and system_info["is_x86"]:
1183 | print_warning("Installation on macOS Intel is challenging")
1184 | print_info("Try manually installing with:")
1185 | print_info("1. pip install --no-deps .")
1186 | print_info("2. pip install sqlite-vec>=0.1.0 mcp>=1.0.0,<2.0.0 onnxruntime>=1.14.1")
1187 | print_info("3. export MCP_MEMORY_USE_ONNX=1")
1188 | print_info("4. export MCP_MEMORY_STORAGE_BACKEND=sqlite_vec")
1189 |
1190 | if system_info.get("has_homebrew_pytorch"):
1191 | print_info("Homebrew PyTorch was detected but installation still failed.")
1192 | print_info("Try running: python install.py --storage-backend sqlite_vec --skip-pytorch")
1193 |
1194 | return False
1195 |
1196 | def get_platform_base_dir() -> Path:
1197 | """Get platform-specific base directory for MCP Memory storage.
1198 |
1199 | Returns:
1200 | Path: Platform-appropriate base directory
1201 | """
1202 | home_dir = Path.home()
1203 |
1204 | PLATFORM_PATHS = {
1205 | 'Darwin': home_dir / 'Library' / 'Application Support' / 'mcp-memory',
1206 | 'Windows': Path(os.environ.get('LOCALAPPDATA', '')) / 'mcp-memory',
1207 | }
1208 |
1209 | system = platform.system()
1210 | return PLATFORM_PATHS.get(system, home_dir / '.local' / 'share' / 'mcp-memory')
1211 |
1212 |
1213 | def setup_storage_directories(backend: str, base_dir: Path, args) -> Tuple[Path, Path, bool]:
1214 | """Setup storage and backup directories for the specified backend.
1215 |
1216 | Args:
1217 | backend: Storage backend type
1218 | base_dir: Base directory for storage
1219 | args: Command line arguments
1220 |
1221 | Returns:
1222 | Tuple of (storage_path, backups_path, success)
1223 | """
1224 | if backend in ['sqlite_vec', 'hybrid', 'cloudflare']:
1225 | storage_path = args.chroma_path or (base_dir / 'sqlite_vec.db')
1226 | storage_dir = storage_path.parent if storage_path.name.endswith('.db') else storage_path
1227 | else: # chromadb
1228 | storage_path = args.chroma_path or (base_dir / 'chroma_db')
1229 | storage_dir = storage_path
1230 |
1231 | backups_path = args.backups_path or (base_dir / 'backups')
1232 |
1233 | try:
1234 | os.makedirs(storage_dir, exist_ok=True)
1235 | os.makedirs(backups_path, exist_ok=True)
1236 |
1237 | # Test writability
1238 | test_file = Path(storage_dir) / '.write_test'
1239 | test_file.write_text('test')
1240 | test_file.unlink()
1241 |
1242 | print_info(f"Storage path: {storage_path}")
1243 | print_info(f"Backups path: {backups_path}")
1244 | return storage_path, backups_path, True
1245 |
1246 | except (OSError, IOError, PermissionError) as e:
1247 | print_error(f"Failed to configure storage paths: {e}")
1248 | return storage_path, backups_path, False
1249 | except Exception as e:
1250 | print_error(f"Unexpected error configuring storage paths: {e}")
1251 | return storage_path, backups_path, False
1252 |
1253 |
1254 | def build_mcp_env_config(storage_backend: str, storage_path: Path,
1255 | backups_path: Path) -> Dict[str, str]:
1256 | """Build MCP environment configuration for Claude Desktop.
1257 |
1258 | Args:
1259 | storage_backend: Type of storage backend
1260 | storage_path: Path to storage directory/file
1261 | backups_path: Path to backups directory
1262 |
1263 | Returns:
1264 | Dict of environment variables for MCP configuration
1265 | """
1266 | env_config = {
1267 | "MCP_MEMORY_BACKUPS_PATH": str(backups_path),
1268 | "MCP_MEMORY_STORAGE_BACKEND": storage_backend
1269 | }
1270 |
1271 | if storage_backend in ['sqlite_vec', 'hybrid']:
1272 | env_config["MCP_MEMORY_SQLITE_PATH"] = str(storage_path)
1273 | env_config["MCP_MEMORY_SQLITE_PRAGMAS"] = "busy_timeout=15000,cache_size=20000"
1274 |
1275 | if storage_backend in ['hybrid', 'cloudflare']:
1276 | cloudflare_vars = [
1277 | 'CLOUDFLARE_API_TOKEN',
1278 | 'CLOUDFLARE_ACCOUNT_ID',
1279 | 'CLOUDFLARE_D1_DATABASE_ID',
1280 | 'CLOUDFLARE_VECTORIZE_INDEX'
1281 | ]
1282 | for var in cloudflare_vars:
1283 | value = os.environ.get(var)
1284 | if value:
1285 | env_config[var] = value
1286 |
1287 | if storage_backend == 'chromadb':
1288 | env_config["MCP_MEMORY_CHROMA_PATH"] = str(storage_path)
1289 |
1290 | return env_config
1291 |
1292 |
1293 | def update_claude_config_file(config_path: Path, env_config: Dict[str, str],
1294 | project_root: Path, is_windows: bool) -> bool:
1295 | """Update Claude Desktop configuration file with MCP Memory settings.
1296 |
1297 | Args:
1298 | config_path: Path to Claude config file
1299 | env_config: Environment configuration dictionary
1300 | project_root: Root directory of the project
1301 | is_windows: Whether running on Windows
1302 |
1303 | Returns:
1304 | bool: True if update succeeded
1305 | """
1306 | try:
1307 | config_text = config_path.read_text()
1308 | config = json.loads(config_text)
1309 |
1310 | if not isinstance(config, dict):
1311 | print_warning(f"Invalid config format in {config_path}")
1312 | return False
1313 |
1314 | if 'mcpServers' not in config:
1315 | config['mcpServers'] = {}
1316 |
1317 | # Create server configuration
1318 | if is_windows:
1319 | script_path = str((project_root / "memory_wrapper.py").resolve())
1320 | config['mcpServers']['memory'] = {
1321 | "command": "python",
1322 | "args": [script_path],
1323 | "env": env_config
1324 | }
1325 | else:
1326 | config['mcpServers']['memory'] = {
1327 | "command": "uv",
1328 | "args": [
1329 | "--directory",
1330 | str(project_root.resolve()),
1331 | "run",
1332 | "memory"
1333 | ],
1334 | "env": env_config
1335 | }
1336 |
1337 | config_path.write_text(json.dumps(config, indent=2))
1338 | print_success("Updated Claude Desktop configuration")
1339 | return True
1340 |
1341 | except (OSError, PermissionError, json.JSONDecodeError) as e:
1342 | print_warning(f"Failed to update Claude Desktop configuration: {e}")
1343 | return False
1344 |
1345 |
1346 | def configure_paths(args):
1347 | """Configure paths for the MCP Memory Service."""
1348 | print_step("4", "Configuring paths")
1349 |
1350 | # Get system info
1351 | system_info = detect_system()
1352 |
1353 | # Get platform-specific base directory
1354 | base_dir = get_platform_base_dir()
1355 | storage_backend = os.environ.get('MCP_MEMORY_STORAGE_BACKEND', 'chromadb')
1356 |
1357 | # Setup storage directories
1358 | storage_path, backups_path, success = setup_storage_directories(
1359 | storage_backend, base_dir, args
1360 | )
1361 | if not success:
1362 | print_warning("Continuing with Claude Desktop configuration despite storage setup failure")
1363 |
1364 | # Test backups directory
1365 | try:
1366 | test_file = Path(backups_path) / '.write_test'
1367 | test_file.write_text('test')
1368 | test_file.unlink()
1369 | print_success("Storage directories created and are writable")
1370 | except (OSError, PermissionError) as e:
1371 | print_error(f"Failed to test backups directory: {e}")
1372 | print_warning("Continuing with Claude Desktop configuration")
1373 |
1374 | # Configure Claude Desktop
1375 | env_config = build_mcp_env_config(storage_backend, storage_path, backups_path)
1376 | project_root = Path(__file__).parent.parent.parent
1377 |
1378 | home_dir = Path.home()
1379 | claude_config_paths = [
1380 | home_dir / 'Library' / 'Application Support' / 'Claude' / 'claude_desktop_config.json',
1381 | home_dir / '.config' / 'Claude' / 'claude_desktop_config.json',
1382 | Path('claude_config') / 'claude_desktop_config.json'
1383 | ]
1384 |
1385 | for config_path in claude_config_paths:
1386 | if config_path.exists():
1387 | print_info(f"Found Claude Desktop config at {config_path}")
1388 | if update_claude_config_file(config_path, env_config, project_root,
1389 | system_info["is_windows"]):
1390 | break
1391 |
1392 | return True
1393 |
1394 | def verify_installation():
1395 | """Verify the installation."""
1396 | print_step("5", "Verifying installation")
1397 |
1398 | # Get system info
1399 | system_info = detect_system()
1400 |
1401 | # Check if the package is installed
1402 | try:
1403 | import mcp_memory_service
1404 | print_success(f"MCP Memory Service is installed: {mcp_memory_service.__file__}")
1405 | except ImportError:
1406 | print_error("MCP Memory Service is not installed correctly")
1407 | return False
1408 |
1409 | # Check if the entry point is available
1410 | memory_script = shutil.which('memory')
1411 | if memory_script:
1412 | print_success(f"Memory command is available: {memory_script}")
1413 | else:
1414 | print_warning("Memory command is not available in PATH")
1415 |
1416 | # Check storage backend installation
1417 | storage_backend = os.environ.get('MCP_MEMORY_STORAGE_BACKEND', 'sqlite_vec')
1418 |
1419 | if storage_backend == 'sqlite_vec':
1420 | try:
1421 | import sqlite_vec
1422 | print_success(f"SQLite-vec is installed: {sqlite_vec.__version__}")
1423 | except ImportError:
1424 | print_error("SQLite-vec is not installed correctly")
1425 | return False
1426 | elif storage_backend == 'chromadb':
1427 | try:
1428 | import chromadb
1429 | print_success(f"ChromaDB is installed: {chromadb.__version__}")
1430 | except ImportError:
1431 | print_error("ChromaDB is not installed correctly")
1432 | return False
1433 |
1434 | # Check for ONNX runtime
1435 | try:
1436 | import onnxruntime
1437 | print_success(f"ONNX Runtime is installed: {onnxruntime.__version__}")
1438 | use_onnx = os.environ.get('MCP_MEMORY_USE_ONNX', '').lower() in ('1', 'true', 'yes')
1439 | if use_onnx:
1440 | print_info("Environment configured to use ONNX runtime for embeddings")
1441 | # Check for tokenizers (required for ONNX)
1442 | try:
1443 | import tokenizers
1444 | print_success(f"Tokenizers is installed: {tokenizers.__version__}")
1445 | except ImportError:
1446 | print_warning("Tokenizers not installed but required for ONNX embeddings")
1447 | print_info("Install with: pip install tokenizers>=0.20.0")
1448 | except ImportError:
1449 | print_warning("ONNX Runtime is not installed. This is recommended for PyTorch-free operation.")
1450 | print_info("Install with: pip install onnxruntime>=1.14.1 tokenizers>=0.20.0")
1451 |
1452 | # Check for Homebrew PyTorch
1453 | homebrew_pytorch = False
1454 | if system_info.get("has_homebrew_pytorch"):
1455 | homebrew_pytorch = True
1456 | print_success(f"Homebrew PyTorch detected: {system_info.get('homebrew_pytorch_version')}")
1457 | print_info("Using system-installed PyTorch instead of pip version")
1458 |
1459 | # Check ML dependencies as optional
1460 | pytorch_installed = False
1461 | try:
1462 | import torch
1463 | pytorch_installed = True
1464 | print_info(f"PyTorch is installed: {torch.__version__}")
1465 |
1466 | # Check for CUDA
1467 | if torch.cuda.is_available():
1468 | print_success(f"CUDA is available: {torch.version.cuda}")
1469 | print_info(f"GPU: {torch.cuda.get_device_name(0)}")
1470 | # Check for MPS (Apple Silicon)
1471 | elif hasattr(torch.backends, 'mps') and torch.backends.mps.is_available():
1472 | print_success("MPS (Metal Performance Shaders) is available")
1473 | # Check for DirectML
1474 | else:
1475 | try:
1476 | import torch_directml
1477 | version = getattr(torch_directml, '__version__', 'Unknown version')
1478 | print_success(f"DirectML is available: {version}")
1479 | except ImportError:
1480 | print_info("Using CPU-only PyTorch")
1481 |
1482 | # For macOS Intel, verify compatibility with sentence-transformers
1483 | if system_info["is_macos"] and system_info["is_x86"]:
1484 | torch_version = torch.__version__.split('.')
1485 | major, minor = int(torch_version[0]), int(torch_version[1])
1486 |
1487 | print_info(f"Verifying torch compatibility on macOS Intel (v{major}.{minor})")
1488 | if major < 1 or (major == 1 and minor < 6):
1489 | print_warning(f"PyTorch version {torch.__version__} may be too old for sentence-transformers")
1490 | elif major > 2 or (major == 2 and minor > 1):
1491 | print_warning(f"PyTorch version {torch.__version__} may be too new for sentence-transformers 2.2.2")
1492 | print_info("If you encounter issues, try downgrading to torch 2.0.1")
1493 |
1494 | except ImportError:
1495 | print_warning("PyTorch is not installed via pip. This is okay for basic operation with SQLite-vec backend.")
1496 | if homebrew_pytorch:
1497 | print_info("Using Homebrew PyTorch installation instead of pip version")
1498 | else:
1499 | print_info("For full functionality including embedding generation, install with: pip install 'mcp-memory-service[ml]'")
1500 | pytorch_installed = False
1501 |
1502 | # Check if sentence-transformers is installed correctly (only if PyTorch is installed)
1503 | if pytorch_installed or homebrew_pytorch:
1504 | try:
1505 | import sentence_transformers
1506 | print_success(f"sentence-transformers is installed: {sentence_transformers.__version__}")
1507 |
1508 | if pytorch_installed:
1509 | # Verify compatibility between torch and sentence-transformers
1510 | st_version = sentence_transformers.__version__.split('.')
1511 | torch_version = torch.__version__.split('.')
1512 |
1513 | st_major, st_minor = int(st_version[0]), int(st_version[1])
1514 | torch_major, torch_minor = int(torch_version[0]), int(torch_version[1])
1515 |
1516 | # Specific compatibility check for macOS Intel
1517 | if system_info["is_macos"] and system_info["is_x86"]:
1518 | if st_major >= 3 and (torch_major < 1 or (torch_major == 1 and torch_minor < 11)):
1519 | print_warning(f"sentence-transformers {sentence_transformers.__version__} requires torch>=1.11.0")
1520 | print_info("This may cause runtime issues - consider downgrading sentence-transformers to 2.2.2")
1521 |
1522 | # Verify by trying to load a model (minimal test)
1523 | try:
1524 | print_info("Testing sentence-transformers model loading...")
1525 | test_model = sentence_transformers.SentenceTransformer('paraphrase-MiniLM-L3-v2')
1526 | print_success("Successfully loaded test model")
1527 | except Exception as e:
1528 | print_warning(f"Model loading test failed: {e}")
1529 | print_warning("There may be compatibility issues between PyTorch and sentence-transformers")
1530 |
1531 | except ImportError:
1532 | print_warning("sentence-transformers is not installed. This is okay for basic operation with SQLite-vec backend.")
1533 | print_info("For full functionality including embedding generation, install with: pip install 'mcp-memory-service[ml]'")
1534 |
1535 | # Check for SQLite-vec + ONNX configuration
1536 | if storage_backend == 'sqlite_vec' and os.environ.get('MCP_MEMORY_USE_ONNX', '').lower() in ('1', 'true', 'yes'):
1537 | print_success("SQLite-vec + ONNX configuration is set up correctly")
1538 | print_info("This configuration can run without PyTorch dependency")
1539 |
1540 | try:
1541 | # Import the key components to verify installation
1542 | from mcp_memory_service.storage.sqlite_vec import SqliteVecMemoryStorage
1543 | from mcp_memory_service.models.memory import Memory
1544 | print_success("SQLite-vec + ONNX components loaded successfully")
1545 |
1546 | # Check paths
1547 | sqlite_path = os.environ.get('MCP_MEMORY_SQLITE_PATH', '')
1548 | if sqlite_path:
1549 | print_info(f"SQLite-vec database path: {sqlite_path}")
1550 | else:
1551 | print_warning("MCP_MEMORY_SQLITE_PATH is not set")
1552 |
1553 | backups_path = os.environ.get('MCP_MEMORY_BACKUPS_PATH', '')
1554 | if backups_path:
1555 | print_info(f"Backups path: {backups_path}")
1556 | else:
1557 | print_warning("MCP_MEMORY_BACKUPS_PATH is not set")
1558 |
1559 | except ImportError as e:
1560 | print_error(f"Failed to import SQLite-vec components: {e}")
1561 | return False
1562 |
1563 | # Check if MCP Memory Service package is installed correctly
1564 | try:
1565 | import mcp_memory_service
1566 | print_success(f"MCP Memory Service is installed correctly")
1567 | return True
1568 | except ImportError:
1569 | print_error("MCP Memory Service is not installed correctly")
1570 | return False
1571 |
1572 | def install_claude_hooks(args, system_info):
1573 | """Install Claude Code memory awareness hooks."""
1574 | print_step("5", "Installing Claude Code Memory Awareness Hooks")
1575 |
1576 | try:
1577 | # Check if Claude Code is available
1578 | claude_available = shutil.which("claude") is not None
1579 | if not claude_available:
1580 | print_warning("Claude Code CLI not found")
1581 | print_info("You can install hooks manually later using:")
1582 | print_info(" cd claude-hooks && python install_hooks.py --basic")
1583 | return
1584 |
1585 | print_info("Claude Code CLI detected")
1586 |
1587 | # Use unified Python installer for cross-platform compatibility
1588 | claude_hooks_dir = Path(__file__).parent.parent.parent / "claude-hooks"
1589 | unified_installer = claude_hooks_dir / "install_hooks.py"
1590 |
1591 | if not unified_installer.exists():
1592 | print_error("Unified hook installer not found at expected location")
1593 | print_info("Please ensure the unified installer is available:")
1594 | print_info(f" Expected: {unified_installer}")
1595 | return
1596 |
1597 | # Prepare installer command with appropriate options
1598 | version = get_package_version()
1599 | if args.install_natural_triggers:
1600 | print_info(f"Installing Natural Memory Triggers v{version}...")
1601 | installer_cmd = [sys.executable, str(unified_installer), "--natural-triggers"]
1602 | else:
1603 | print_info("Installing standard memory awareness hooks...")
1604 | installer_cmd = [sys.executable, str(unified_installer), "--basic"]
1605 |
1606 | # Run the unified Python installer
1607 | print_info(f"Running unified hook installer: {unified_installer.name}")
1608 | result = subprocess.run(
1609 | installer_cmd,
1610 | cwd=str(claude_hooks_dir),
1611 | capture_output=True,
1612 | text=True
1613 | )
1614 |
1615 | if result.returncode == 0:
1616 | print_success("Claude Code memory awareness hooks installed successfully")
1617 | if args.install_natural_triggers:
1618 | print_info(f"✅ Natural Memory Triggers v{version} enabled")
1619 | print_info("✅ Intelligent trigger detection with 85%+ accuracy")
1620 | print_info("✅ Multi-tier performance optimization")
1621 | print_info("✅ CLI management tools available")
1622 | print_info("")
1623 | print_info("Manage Natural Memory Triggers:")
1624 | print_info(" node ~/.claude/hooks/memory-mode-controller.js status")
1625 | print_info(" node ~/.claude/hooks/memory-mode-controller.js profile balanced")
1626 | else:
1627 | print_info("✅ Standard memory awareness hooks enabled")
1628 | print_info("✅ Session-start and session-end hooks active")
1629 | else:
1630 | print_warning("Hook installation completed with warnings")
1631 | if result.stdout:
1632 | print_info("Installer output:")
1633 | print_info(result.stdout)
1634 | if result.stderr:
1635 | print_warning("Installer warnings:")
1636 | print_warning(result.stderr)
1637 |
1638 | except Exception as e:
1639 | print_warning(f"Failed to install hooks automatically: {e}")
1640 | print_info("You can install hooks manually later using the unified installer:")
1641 | print_info(" cd claude-hooks && python install_hooks.py --basic")
1642 | if args.install_natural_triggers:
1643 | print_info("For Natural Memory Triggers:")
1644 | print_info(" cd claude-hooks && python install_hooks.py --natural-triggers")
1645 |
1646 | def detect_development_context():
1647 | """Detect if user is likely a developer (has .git directory).
1648 |
1649 | Returns:
1650 | bool: True if .git directory exists
1651 | """
1652 | git_dir = Path(".git")
1653 | return git_dir.exists() and git_dir.is_dir()
1654 |
1655 | def recommend_editable_install(args):
1656 | """Recommend editable install for developers.
1657 |
1658 | If .git directory detected and --dev not specified, prompts user to use
1659 | editable install mode. This prevents the common "stale venv vs source code"
1660 | issue where MCP servers load from site-packages instead of source files.
1661 |
1662 | Args:
1663 | args: Parsed command line arguments
1664 |
1665 | Returns:
1666 | bool: True if editable install should be used
1667 | """
1668 | # If already in dev mode, nothing to do
1669 | if args.dev:
1670 | return True
1671 |
1672 | # Detect development context
1673 | if detect_development_context():
1674 | print_warning("Detected git repository - you may be a developer!")
1675 | print("")
1676 | print_info("For development, EDITABLE install is strongly recommended:")
1677 | print_info(" pip install -e .")
1678 | print("")
1679 | print_info("Why this matters:")
1680 | print_info(" • MCP servers load from site-packages, not source files")
1681 | print_info(" • Without -e flag, source changes won't take effect")
1682 | print_info(" • System restart won't help - it just relaunches stale code")
1683 | print_info(" • Common symptom: Code shows v8.23.0 but server reports v8.5.3")
1684 | print("")
1685 | print_info("Editable install ensures:")
1686 | print_info(" • Source changes take effect immediately (after server restart)")
1687 | print_info(" • No need to reinstall package after every change")
1688 | print_info(" • Easier debugging and development workflow")
1689 | print("")
1690 |
1691 | response = input("Install in EDITABLE mode (recommended for development)? [Y/n]: ").lower().strip()
1692 | if response == '' or response == 'y' or response == 'yes':
1693 | args.dev = True
1694 | print_success("Enabled editable install mode")
1695 | return True
1696 | else:
1697 | print_warning("Proceeding with standard install (not editable)")
1698 | print_warning("Remember: You'll need to reinstall after every source change!")
1699 | return False
1700 |
1701 | return False
1702 |
1703 | def main():
1704 | """Main installation function."""
1705 | parser = argparse.ArgumentParser(description="Install MCP Memory Service")
1706 | parser.add_argument('--dev', action='store_true', help='Install in development mode')
1707 | parser.add_argument('--chroma-path', type=str, help='Path to ChromaDB storage')
1708 | parser.add_argument('--backups-path', type=str, help='Path to backups storage')
1709 | parser.add_argument('--force-compatible-deps', action='store_true',
1710 | help='Force compatible versions of PyTorch (2.0.1) and sentence-transformers (2.2.2)')
1711 | parser.add_argument('--fallback-deps', action='store_true',
1712 | help='Use fallback versions of PyTorch (1.13.1) and sentence-transformers (2.2.2)')
1713 | parser.add_argument('--storage-backend', choices=['cloudflare', 'sqlite_vec', 'chromadb', 'hybrid', 'auto_detect'],
1714 | help='Choose storage backend: cloudflare (production), sqlite_vec (development), chromadb (team), hybrid (production + local), or auto_detect')
1715 | parser.add_argument('--skip-pytorch', action='store_true',
1716 | help='Skip PyTorch installation and use ONNX runtime with SQLite-vec backend instead')
1717 | parser.add_argument('--use-homebrew-pytorch', action='store_true',
1718 | help='Use existing Homebrew PyTorch installation instead of pip version')
1719 | parser.add_argument('--install-hooks', action='store_true',
1720 | help='Install Claude Code memory awareness hooks after main installation')
1721 | parser.add_argument('--install-natural-triggers', action='store_true',
1722 | help='Install Natural Memory Triggers (requires Claude Code)')
1723 | parser.add_argument('--with-ml', action='store_true',
1724 | help='Include ML dependencies (torch, sentence-transformers) for semantic search and embeddings')
1725 | parser.add_argument('--with-chromadb', action='store_true',
1726 | help='Include ChromaDB backend support (automatically includes ML dependencies)')
1727 | args = parser.parse_args()
1728 |
1729 | # Check if this is a development context and recommend editable install
1730 | recommend_editable_install(args)
1731 |
1732 | print_header("MCP Memory Service Installation")
1733 |
1734 | # Step 1: Detect system
1735 | print_step("1", "Detecting system")
1736 | system_info = detect_system()
1737 |
1738 | # Check if user requested force-compatible dependencies for macOS Intel
1739 | if args.force_compatible_deps:
1740 | if system_info["is_macos"] and system_info["is_x86"]:
1741 | print_info("Installing compatible dependencies as requested...")
1742 | # Select versions based on Python version
1743 | python_version = sys.version_info
1744 | if python_version >= (3, 13):
1745 | # Python 3.13+ compatible versions
1746 | torch_version = "2.3.0"
1747 | torch_vision_version = "0.18.0"
1748 | torch_audio_version = "2.3.0"
1749 | st_version = "3.0.0"
1750 | else:
1751 | # Older Python versions
1752 | torch_version = "2.0.1"
1753 | torch_vision_version = "2.0.1"
1754 | torch_audio_version = "2.0.1"
1755 | st_version = "2.2.2"
1756 |
1757 | cmd = [
1758 | sys.executable, '-m', 'pip', 'install',
1759 | f"torch=={torch_version}", f"torchvision=={torch_vision_version}", f"torchaudio=={torch_audio_version}",
1760 | f"sentence-transformers=={st_version}"
1761 | ]
1762 | success, _ = run_command_safe(
1763 | cmd,
1764 | success_msg="Compatible dependencies installed successfully",
1765 | error_msg="Failed to install compatible dependencies",
1766 | silent=False
1767 | )
1768 | else:
1769 | print_warning("--force-compatible-deps is only applicable for macOS with Intel CPUs")
1770 |
1771 | # Check if user requested fallback dependencies for troubleshooting
1772 | if args.fallback_deps:
1773 | print_info("Installing fallback dependencies as requested...")
1774 | # Select versions based on Python version
1775 | python_version = sys.version_info
1776 | if python_version >= (3, 13):
1777 | # Python 3.13+ compatible fallback versions
1778 | torch_version = "2.3.0"
1779 | torch_vision_version = "0.18.0"
1780 | torch_audio_version = "2.3.0"
1781 | st_version = "3.0.0"
1782 | else:
1783 | # Older Python fallback versions
1784 | torch_version = "1.13.1"
1785 | torch_vision_version = "0.14.1"
1786 | torch_audio_version = "0.13.1"
1787 | st_version = "2.2.2"
1788 |
1789 | cmd = [
1790 | sys.executable, '-m', 'pip', 'install',
1791 | f"torch=={torch_version}", f"torchvision=={torch_vision_version}", f"torchaudio=={torch_audio_version}",
1792 | f"sentence-transformers=={st_version}"
1793 | ]
1794 | success, _ = run_command_safe(
1795 | cmd,
1796 | success_msg="Fallback dependencies installed successfully",
1797 | error_msg="Failed to install fallback dependencies",
1798 | silent=False
1799 | )
1800 |
1801 | # Step 2: Check dependencies
1802 | if not check_dependencies():
1803 | sys.exit(1)
1804 |
1805 | # Step 3: Install package
1806 | if not install_package(args):
1807 | # If installation fails and we're on macOS Intel, suggest using the force-compatible-deps option
1808 | if system_info["is_macos"] and system_info["is_x86"]:
1809 | print_warning("Installation failed on macOS Intel.")
1810 | print_info("Try running the script with '--force-compatible-deps' to force compatible versions:")
1811 | print_info("python install.py --force-compatible-deps")
1812 | sys.exit(1)
1813 |
1814 | # Step 4: Configure paths
1815 | if not configure_paths(args):
1816 | print_warning("Path configuration failed, but installation may still work")
1817 |
1818 | # Step 5: Verify installation
1819 | if not verify_installation():
1820 | print_warning("Installation verification failed, but installation may still work")
1821 | # If verification fails and we're on macOS Intel, suggest using the force-compatible-deps option
1822 | if system_info["is_macos"] and system_info["is_x86"]:
1823 | python_version = sys.version_info
1824 | print_info("For macOS Intel compatibility issues, try these steps:")
1825 | print_info("1. First uninstall current packages: pip uninstall -y torch torchvision torchaudio sentence-transformers")
1826 | print_info("2. Then reinstall with compatible versions: python install.py --force-compatible-deps")
1827 |
1828 | if python_version >= (3, 13):
1829 | print_info("For Python 3.13+, you may need to manually install the following:")
1830 | print_info("pip install torch==2.3.0 torchvision==0.18.0 torchaudio==2.3.0")
1831 | print_info("pip install sentence-transformers==3.0.0")
1832 |
1833 | print_header("Installation Complete")
1834 |
1835 | # Get final storage backend info
1836 | final_backend = os.environ.get('MCP_MEMORY_STORAGE_BACKEND', 'chromadb')
1837 | use_onnx = os.environ.get('MCP_MEMORY_USE_ONNX', '').lower() in ('1', 'true', 'yes')
1838 |
1839 | print_info("You can now run the MCP Memory Service using the 'memory' command")
1840 | print_info(f"Storage Backend: {final_backend.upper()}")
1841 |
1842 | if final_backend == 'sqlite_vec':
1843 | print_info("✅ Using SQLite-vec - lightweight, fast, minimal dependencies")
1844 | print_info(" • No complex dependencies or build issues")
1845 | print_info(" • Excellent performance for typical use cases")
1846 | elif final_backend == 'hybrid':
1847 | print_info("✅ Using Hybrid Backend - RECOMMENDED for production")
1848 | print_info(" • Fast local SQLite-vec storage (5ms reads)")
1849 | print_info(" • Background Cloudflare sync for multi-device access")
1850 | print_info(" • SQLite pragmas configured for concurrent access")
1851 | print_info(" • Zero user-facing latency for cloud operations")
1852 | elif final_backend == 'cloudflare':
1853 | print_info("✅ Using Cloudflare Backend - cloud-only storage")
1854 | print_info(" • Distributed edge storage with D1 database")
1855 | print_info(" • Vectorize index for semantic search")
1856 | print_info(" • Multi-device synchronization")
1857 | else:
1858 | print_info("✅ Using ChromaDB - full-featured vector database")
1859 | print_info(" • Advanced features and extensive ecosystem")
1860 |
1861 | if use_onnx:
1862 | print_info("✅ Using ONNX Runtime for inference")
1863 | print_info(" • Compatible with Homebrew PyTorch")
1864 | print_info(" • Reduced dependencies for better compatibility")
1865 |
1866 | # Show ML dependencies status
1867 | if args.with_ml or args.with_chromadb:
1868 | print_info("✅ ML Dependencies Installed")
1869 | print_info(" • Full semantic search and embedding generation enabled")
1870 | print_info(" • PyTorch and sentence-transformers available")
1871 | else:
1872 | print_info("ℹ️ Lightweight Installation (No ML Dependencies)")
1873 | print_info(" • Basic functionality without semantic search")
1874 | print_info(" • To enable full features: pip install mcp-memory-service[ml]")
1875 |
1876 | print_info("For more information, see the README.md file")
1877 |
1878 | # Install hooks if requested
1879 | if args.install_hooks or args.install_natural_triggers:
1880 | install_claude_hooks(args, system_info)
1881 |
1882 | # Print macOS Intel specific information if applicable
1883 | if system_info["is_macos"] and system_info["is_x86"]:
1884 | print_info("\nMacOS Intel Notes:")
1885 |
1886 | if system_info.get("has_homebrew_pytorch"):
1887 | print_info("- Using Homebrew PyTorch installation: " + system_info.get("homebrew_pytorch_version", "Unknown"))
1888 | print_info("- The MCP Memory Service is configured to use SQLite-vec + ONNX runtime")
1889 | print_info("- To start the memory service, use:")
1890 | print_info(" export MCP_MEMORY_USE_ONNX=1")
1891 | print_info(" export MCP_MEMORY_STORAGE_BACKEND=sqlite_vec")
1892 | print_info(" memory")
1893 | else:
1894 | print_info("- If you encounter issues, try the --force-compatible-deps option")
1895 |
1896 | python_version = sys.version_info
1897 | if python_version >= (3, 13):
1898 | print_info("- For optimal performance on Intel Macs with Python 3.13+, torch==2.3.0 and sentence-transformers==3.0.0 are recommended")
1899 | print_info("- You can manually install these versions with:")
1900 | print_info(" pip install torch==2.3.0 torchvision==0.18.0 torchaudio==2.3.0 sentence-transformers==3.0.0")
1901 | else:
1902 | print_info("- For optimal performance on Intel Macs, torch==2.0.1 and sentence-transformers==2.2.2 are recommended")
1903 | print_info("- You can manually install these versions with:")
1904 | print_info(" pip install torch==2.0.1 torchvision==2.0.1 torchaudio==2.0.1 sentence-transformers==2.2.2")
1905 |
1906 | print_info("\nTroubleshooting Tips:")
1907 | print_info("- If you have a Homebrew PyTorch installation, use: --use-homebrew-pytorch")
1908 | print_info("- To completely skip PyTorch installation, use: --skip-pytorch")
1909 | print_info("- To force the SQLite-vec backend, use: --storage-backend sqlite_vec")
1910 | print_info("- For lightweight installation without ML, use: (default behavior)")
1911 | print_info("- For full ML capabilities, use: --with-ml")
1912 | print_info("- For ChromaDB with ML, use: --with-chromadb")
1913 | print_info("- For a quick test, try running: python test_memory.py")
1914 | print_info("- To install Claude Code hooks: --install-hooks")
1915 | print_info("- To install Natural Memory Triggers: --install-natural-triggers")
1916 |
1917 | if __name__ == "__main__":
1918 | main()
```