This is page 8 of 35. Use http://codebase.md/doobidoo/mcp-memory-service?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
--------------------------------------------------------------------------------
/docs/oauth-setup.md:
--------------------------------------------------------------------------------
```markdown
# OAuth 2.1 Dynamic Client Registration Setup
This guide explains how to configure and use OAuth 2.1 Dynamic Client Registration with MCP Memory Service to enable Claude Code HTTP transport integration.
## Overview
The MCP Memory Service now supports OAuth 2.1 Dynamic Client Registration (DCR) as specified in RFC 7591. This enables:
- **Claude Code HTTP Transport**: Direct integration with Claude Code's team collaboration features
- **Automated Client Registration**: Clients can register themselves without manual configuration
- **Secure Authentication**: JWT-based access tokens with proper scope validation
- **Backward Compatibility**: Existing API key authentication continues to work
## Quick Start
### 1. Enable OAuth
Set the OAuth environment variable:
```bash
export MCP_OAUTH_ENABLED=true
```
### 2. Start the Server
```bash
# Start with OAuth enabled
uv run memory server --http
# Or with HTTPS (recommended for production)
export MCP_HTTPS_ENABLED=true
export MCP_SSL_CERT_FILE=/path/to/cert.pem
export MCP_SSL_KEY_FILE=/path/to/key.pem
uv run memory server --http
```
### 3. Test OAuth Endpoints
```bash
# Test the OAuth implementation
python tests/integration/test_oauth_flow.py http://localhost:8000
```
## Configuration
### Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `MCP_OAUTH_ENABLED` | `true` | Enable/disable OAuth 2.1 endpoints |
| `MCP_OAUTH_SECRET_KEY` | Auto-generated | JWT signing key (set for persistence) |
| `MCP_OAUTH_ISSUER` | Auto-detected | OAuth issuer URL |
| `MCP_OAUTH_ACCESS_TOKEN_EXPIRE_MINUTES` | `60` | Access token lifetime |
| `MCP_OAUTH_AUTHORIZATION_CODE_EXPIRE_MINUTES` | `10` | Authorization code lifetime |
### Example Configuration
```bash
# Production configuration
export MCP_OAUTH_ENABLED=true
export MCP_OAUTH_SECRET_KEY="your-secure-secret-key-here"
export MCP_OAUTH_ISSUER="https://your-domain.com"
export MCP_HTTPS_ENABLED=true
# Development configuration
export MCP_OAUTH_ENABLED=true
export MCP_OAUTH_ISSUER="http://localhost:8000" # Match server port
```
## OAuth Endpoints
### Discovery Endpoints
- `GET /.well-known/oauth-authorization-server/mcp` - OAuth server metadata
- `GET /.well-known/openid-configuration/mcp` - OpenID Connect discovery
### OAuth Flow Endpoints
- `POST /oauth/register` - Dynamic client registration
- `GET /oauth/authorize` - Authorization endpoint
- `POST /oauth/token` - Token endpoint
### Management Endpoints
- `GET /oauth/clients/{client_id}` - Client information (debugging)
## Claude Code Integration
### Automatic Setup
Claude Code will automatically discover and register with the OAuth server:
1. **Discovery**: Claude Code requests `/.well-known/oauth-authorization-server/mcp`
2. **Registration**: Automatically registers as an OAuth client
3. **Authorization**: Redirects user for authorization (auto-approved in MVP)
4. **Token Exchange**: Exchanges authorization code for access token
5. **API Access**: Uses Bearer token for all HTTP transport requests
### Manual Configuration
If needed, you can manually configure Claude Code:
```json
{
"memoryService": {
"protocol": "http",
"http": {
"endpoint": "http://localhost:8000", # Use actual server endpoint
"oauth": {
"enabled": true,
"discoveryUrl": "http://localhost:8000/.well-known/oauth-authorization-server/mcp"
}
}
}
}
```
## API Authentication
### Bearer Token Authentication
All API endpoints support Bearer token authentication:
```bash
# Get access token via OAuth flow
export ACCESS_TOKEN="your-jwt-access-token"
# Use Bearer token for API requests
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
http://localhost:8000/api/memories
```
### Scope-Based Authorization
The OAuth system supports three scopes:
- **`read`**: Access to read-only endpoints
- **`write`**: Access to create/update endpoints
- **`admin`**: Access to administrative endpoints
### Backward Compatibility
API key authentication continues to work:
```bash
# Legacy API key authentication
export MCP_API_KEY="your-api-key"
curl -H "Authorization: Bearer $MCP_API_KEY" \
http://localhost:8000/api/memories
```
## Security Considerations
### Production Deployment
1. **Use HTTPS**: Always enable HTTPS in production
2. **Set Secret Key**: Provide a secure `MCP_OAUTH_SECRET_KEY`
3. **Secure Storage**: Consider persistent client storage for production
4. **Rate Limiting**: Implement rate limiting on OAuth endpoints
### OAuth 2.1 Compliance
The implementation follows OAuth 2.1 security requirements:
- HTTPS required for non-localhost URLs
- Secure client credential generation
- JWT access tokens with proper validation
- Authorization code expiration
- Proper redirect URI validation
## Troubleshooting
### Common Issues
**OAuth endpoints return 404**:
- Ensure `MCP_OAUTH_ENABLED=true`
- Restart the server after configuration changes
**Claude Code connection fails**:
- Check HTTPS configuration for production
- Verify OAuth discovery endpoint responds correctly
- Check server logs for OAuth errors
**Invalid token errors**:
- Verify `MCP_OAUTH_SECRET_KEY` is consistent
- Check token expiration times
- Ensure proper JWT format
### Debug Commands
```bash
# Test OAuth discovery
curl http://localhost:8000/.well-known/oauth-authorization-server/mcp
# Test client registration
curl -X POST http://localhost:8000/oauth/register \
-H "Content-Type: application/json" \
-d '{"client_name": "Test Client"}'
# Check server logs
tail -f logs/mcp-memory-service.log | grep -i oauth
```
## API Reference
### Client Registration Request
```json
{
"client_name": "My Application",
"redirect_uris": ["https://myapp.com/callback"],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"scope": "read write"
}
```
### Client Registration Response
```json
{
"client_id": "mcp_client_abc123",
"client_secret": "secret_xyz789",
"redirect_uris": ["https://myapp.com/callback"],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"token_endpoint_auth_method": "client_secret_basic"
}
```
### Token Response
```json
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read write"
}
```
## Development
### Running Tests
```bash
# Basic OAuth functionality test
python tests/integration/test_oauth_flow.py
# Full test suite
pytest tests/ -k oauth
# Manual testing with curl
./scripts/test_oauth_flow.sh
```
### Adding New Scopes
1. Update scope definitions in `oauth/models.py`
2. Add scope validation in `oauth/middleware.py`
3. Apply scope requirements to endpoints using `require_scope()`
For more information, see the [OAuth 2.1 specification](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1) and [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591).
```
--------------------------------------------------------------------------------
/src/mcp_memory_service/storage/factory.py:
--------------------------------------------------------------------------------
```python
# Copyright 2024 Heinrich Krupp
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Shared storage backend factory for the MCP Memory Service.
This module provides a single, shared factory function for creating storage backends,
eliminating code duplication between the MCP server and web interface initialization.
"""
import logging
from typing import Type
from .base import MemoryStorage
logger = logging.getLogger(__name__)
def _fallback_to_sqlite_vec() -> Type[MemoryStorage]:
"""
Helper function to fallback to SQLite-vec storage when other backends fail to import.
Returns:
SqliteVecMemoryStorage class
"""
logger.warning("Falling back to SQLite-vec storage")
from .sqlite_vec import SqliteVecMemoryStorage
return SqliteVecMemoryStorage
def get_storage_backend_class() -> Type[MemoryStorage]:
"""
Get storage backend class based on configuration.
Returns:
Storage backend class
"""
from ..config import STORAGE_BACKEND
backend = STORAGE_BACKEND.lower()
if backend == "sqlite-vec" or backend == "sqlite_vec":
from .sqlite_vec import SqliteVecMemoryStorage
return SqliteVecMemoryStorage
elif backend == "cloudflare":
try:
from .cloudflare import CloudflareStorage
return CloudflareStorage
except ImportError as e:
logger.error(f"Failed to import Cloudflare storage: {e}")
raise
elif backend == "hybrid":
try:
from .hybrid import HybridMemoryStorage
return HybridMemoryStorage
except ImportError as e:
logger.error(f"Failed to import Hybrid storage: {e}")
return _fallback_to_sqlite_vec()
else:
logger.warning(f"Unknown storage backend '{backend}', defaulting to SQLite-vec")
from .sqlite_vec import SqliteVecMemoryStorage
return SqliteVecMemoryStorage
async def create_storage_instance(sqlite_path: str, server_type: str = None) -> MemoryStorage:
"""
Create and initialize storage backend instance based on configuration.
Args:
sqlite_path: Path to SQLite database file (used for SQLite-vec and Hybrid backends)
server_type: Optional server type identifier ("mcp" or "http") to control hybrid sync ownership
Returns:
Initialized storage backend instance
"""
from ..config import (
STORAGE_BACKEND, EMBEDDING_MODEL_NAME,
CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID,
CLOUDFLARE_VECTORIZE_INDEX, CLOUDFLARE_D1_DATABASE_ID,
CLOUDFLARE_R2_BUCKET, CLOUDFLARE_EMBEDDING_MODEL,
CLOUDFLARE_LARGE_CONTENT_THRESHOLD, CLOUDFLARE_MAX_RETRIES,
CLOUDFLARE_BASE_DELAY,
HYBRID_SYNC_INTERVAL, HYBRID_BATCH_SIZE, HYBRID_SYNC_OWNER
)
logger.info(f"Creating storage backend instance (sqlite_path: {sqlite_path}, server_type: {server_type})...")
# Check if we should override hybrid backend based on sync ownership (v8.27.0+)
effective_backend = STORAGE_BACKEND
if STORAGE_BACKEND == 'hybrid' and server_type and HYBRID_SYNC_OWNER != 'both':
if HYBRID_SYNC_OWNER != server_type:
logger.info(
f"Sync ownership configured for '{HYBRID_SYNC_OWNER}' but this is '{server_type}' server. "
f"Using SQLite-vec storage instead of Hybrid to avoid duplicate sync queues."
)
effective_backend = 'sqlite_vec'
# Get storage class based on effective configuration
if effective_backend == 'sqlite_vec':
# Intentional switch to SQLite-vec (not a fallback/error case)
from .sqlite_vec import SqliteVecMemoryStorage
StorageClass = SqliteVecMemoryStorage
else:
# Use configured backend (hybrid or cloudflare)
StorageClass = get_storage_backend_class()
# Create storage instance based on backend type
if StorageClass.__name__ == "SqliteVecMemoryStorage":
storage = StorageClass(
db_path=sqlite_path,
embedding_model=EMBEDDING_MODEL_NAME
)
logger.info(f"Initialized SQLite-vec storage at {sqlite_path}")
elif StorageClass.__name__ == "CloudflareStorage":
storage = StorageClass(
api_token=CLOUDFLARE_API_TOKEN,
account_id=CLOUDFLARE_ACCOUNT_ID,
vectorize_index=CLOUDFLARE_VECTORIZE_INDEX,
d1_database_id=CLOUDFLARE_D1_DATABASE_ID,
r2_bucket=CLOUDFLARE_R2_BUCKET,
embedding_model=CLOUDFLARE_EMBEDDING_MODEL,
large_content_threshold=CLOUDFLARE_LARGE_CONTENT_THRESHOLD,
max_retries=CLOUDFLARE_MAX_RETRIES,
base_delay=CLOUDFLARE_BASE_DELAY
)
logger.info(f"Initialized Cloudflare storage with vectorize index: {CLOUDFLARE_VECTORIZE_INDEX}")
elif StorageClass.__name__ == "HybridMemoryStorage":
# Prepare Cloudflare configuration dict
cloudflare_config = None
if all([CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_VECTORIZE_INDEX, CLOUDFLARE_D1_DATABASE_ID]):
cloudflare_config = {
'api_token': CLOUDFLARE_API_TOKEN,
'account_id': CLOUDFLARE_ACCOUNT_ID,
'vectorize_index': CLOUDFLARE_VECTORIZE_INDEX,
'd1_database_id': CLOUDFLARE_D1_DATABASE_ID,
'r2_bucket': CLOUDFLARE_R2_BUCKET,
'embedding_model': CLOUDFLARE_EMBEDDING_MODEL,
'large_content_threshold': CLOUDFLARE_LARGE_CONTENT_THRESHOLD,
'max_retries': CLOUDFLARE_MAX_RETRIES,
'base_delay': CLOUDFLARE_BASE_DELAY
}
storage = StorageClass(
sqlite_db_path=sqlite_path,
embedding_model=EMBEDDING_MODEL_NAME,
cloudflare_config=cloudflare_config,
sync_interval=HYBRID_SYNC_INTERVAL,
batch_size=HYBRID_BATCH_SIZE
)
logger.info(f"Initialized hybrid storage with SQLite at {sqlite_path}")
else:
# Unknown storage backend - this should not happen as get_storage_backend_class
# already handles unknown backends by falling back to SQLite-vec
raise ValueError(f"Unsupported storage backend class: {StorageClass.__name__}")
# Initialize storage backend
await storage.initialize()
logger.info(f"Storage backend {StorageClass.__name__} initialized successfully")
return storage
```
--------------------------------------------------------------------------------
/tests/unit/test_fastapi_dependencies.py:
--------------------------------------------------------------------------------
```python
"""
Unit tests for FastAPI dependency injection.
These tests verify that the actual dependency injection chain works correctly,
catching issues like import-time default parameter evaluation.
Added to prevent production bugs like v8.12.0 where:
def get_memory_service(storage: MemoryStorage = get_storage())
was evaluated at import time when _storage was None.
"""
import pytest
import pytest_asyncio
import asyncio
import tempfile
import os
from unittest.mock import MagicMock
@pytest_asyncio.fixture
async def temp_storage():
"""Create a temporary storage for testing."""
from mcp_memory_service.storage.sqlite_vec import SqliteVecMemoryStorage
from mcp_memory_service.web.dependencies import set_storage
with tempfile.TemporaryDirectory() as tmpdir:
db_path = os.path.join(tmpdir, "test.db")
storage = SqliteVecMemoryStorage(db_path)
await storage.initialize()
set_storage(storage)
yield storage
storage.close()
@pytest.mark.asyncio
async def test_get_storage_dependency_callable(temp_storage):
"""Test that get_storage() dependency is callable without errors."""
from mcp_memory_service.web.dependencies import get_storage
# Should be callable
assert callable(get_storage)
# Should not raise when called
storage = get_storage()
assert storage is not None
assert storage is temp_storage
def test_get_memory_service_dependency_callable():
"""Test that get_memory_service() dependency is callable without errors."""
from mcp_memory_service.web.dependencies import get_memory_service
# Should be callable
assert callable(get_memory_service)
# Should not raise when called
try:
service = get_memory_service()
assert service is not None
except Exception as e:
pytest.fail(f"get_memory_service() raised unexpected exception: {e}")
def test_get_storage_uses_depends_not_default_param():
"""Test that get_storage is used via Depends(), not as default parameter.
This prevents the v8.12.0 bug where:
def get_memory_service(storage: MemoryStorage = get_storage())
was evaluated at import time.
"""
import inspect
from mcp_memory_service.web.dependencies import get_memory_service
from fastapi.params import Depends
# Get function signature
sig = inspect.signature(get_memory_service)
# Check if storage parameter exists
if 'storage' in sig.parameters:
storage_param = sig.parameters['storage']
# If it has a default, it should be Depends(...), not a function call
if storage_param.default != inspect.Parameter.empty:
# Default should be a Depends instance, not the result of get_storage()
# Check the type name since Depends is not a simple type
assert type(storage_param.default).__name__ == 'Depends', \
"storage parameter should use Depends(get_storage), not get_storage()"
@pytest.mark.asyncio
async def test_dependency_chain_storage_to_service(temp_storage):
"""Test that the dependency chain from storage → service works."""
from mcp_memory_service.web.dependencies import get_storage, get_memory_service
# Get storage
storage = get_storage()
assert storage is not None
# Get service (should use the storage)
service = get_memory_service()
assert service is not None
# Service should have a storage reference
assert hasattr(service, 'storage')
@pytest.mark.asyncio
async def test_get_storage_returns_singleton(temp_storage):
"""Test that get_storage() returns the same instance (singleton pattern)."""
from mcp_memory_service.web.dependencies import get_storage
storage1 = get_storage()
storage2 = get_storage()
# Should be the same instance
assert storage1 is storage2, "get_storage() should return singleton"
def test_get_memory_service_returns_new_instance():
"""Test that get_memory_service() returns new instances (not singleton)."""
from mcp_memory_service.web.dependencies import get_memory_service
service1 = get_memory_service()
service2 = get_memory_service()
# They use the same storage but are different service instances
# (This is OK because MemoryService is stateless)
assert isinstance(service1, type(service2))
def test_dependencies_module_has_required_functions():
"""Test that dependencies module exports required functions."""
from mcp_memory_service.web import dependencies
# Core dependency functions
assert hasattr(dependencies, 'get_storage')
assert hasattr(dependencies, 'get_memory_service')
# Should be callable
assert callable(dependencies.get_storage)
assert callable(dependencies.get_memory_service)
@pytest.mark.asyncio
async def test_storage_dependency_is_initialized(temp_storage):
"""Test that storage returned by get_storage() is properly initialized."""
from mcp_memory_service.web.dependencies import get_storage
storage = get_storage()
# Check it has expected methods (from base class)
assert hasattr(storage, 'store')
assert hasattr(storage, 'get_all_memories')
assert hasattr(storage, 'get_stats')
assert hasattr(storage, 'delete')
@pytest.mark.asyncio
async def test_async_dependencies_work(temp_storage):
"""Test that async dependencies work correctly.
Some storage operations are async, so we need to verify they work.
"""
from mcp_memory_service.web.dependencies import get_storage
storage = get_storage()
# get_stats is async and was the source of issue #191
stats = await storage.get_stats()
assert isinstance(stats, dict)
assert 'total_memories' in stats
def test_dependency_injection_doesnt_fail_on_import():
"""Test that importing dependencies module doesn't cause errors.
This catches import-time evaluation bugs.
"""
try:
# This should not raise
import mcp_memory_service.web.dependencies
import mcp_memory_service.web.app
# App should be created successfully
from mcp_memory_service.web.app import app
assert app is not None
except Exception as e:
pytest.fail(f"Import-time error in dependencies: {e}")
def test_memory_service_has_required_methods():
"""Test that MemoryService has all required methods."""
from mcp_memory_service.web.dependencies import get_memory_service
service = get_memory_service()
# Core methods from MemoryService class
required_methods = [
'store_memory',
'retrieve_memories',
'delete_memory',
'list_memories', # Not get_all_memories
'search_by_tag',
'get_memory_by_hash',
'health_check',
]
for method in required_methods:
assert hasattr(service, method), f"MemoryService missing {method}"
assert callable(getattr(service, method))
if __name__ == "__main__":
# Allow running tests directly for quick verification
pytest.main([__file__, "-v"])
```
--------------------------------------------------------------------------------
/docs/guides/chromadb-migration.md:
--------------------------------------------------------------------------------
```markdown
# ChromaDB Migration Guide
> **ChromaDB backend was removed in v8.0.0**. This guide helps you migrate to modern storage backends.
## Quick Migration Path
### Option 1: Hybrid Backend (Recommended)
Best choice for most users - combines fast local storage with cloud synchronization.
```bash
# 1. Backup your ChromaDB data (from chromadb-legacy branch)
git checkout chromadb-legacy
python scripts/migration/migrate_chroma_to_sqlite.py --backup ~/chromadb_backup.json
# 2. Switch to main branch and configure Hybrid backend
git checkout main
export MCP_MEMORY_STORAGE_BACKEND=hybrid
# 3. Configure Cloudflare credentials
export CLOUDFLARE_API_TOKEN="your-token"
export CLOUDFLARE_ACCOUNT_ID="your-account"
export CLOUDFLARE_D1_DATABASE_ID="your-d1-id"
export CLOUDFLARE_VECTORIZE_INDEX="mcp-memory-index"
# 4. Install and verify
python install.py --storage-backend hybrid
python scripts/validation/validate_configuration_complete.py
```
### Option 2: SQLite-vec (Local Only)
For single-device use without cloud synchronization.
```bash
# 1. Backup and migrate
git checkout chromadb-legacy
python scripts/migration/migrate_chroma_to_sqlite.py
# 2. Configure SQLite-vec backend
git checkout main
export MCP_MEMORY_STORAGE_BACKEND=sqlite_vec
# 3. Install
python install.py --storage-backend sqlite_vec
```
### Option 3: Cloudflare (Cloud Only)
For pure cloud storage without local database.
```bash
# 1. Backup ChromaDB data
git checkout chromadb-legacy
python scripts/migration/migrate_chroma_to_sqlite.py --backup ~/chromadb_backup.json
# 2. Switch to Cloudflare backend
git checkout main
export MCP_MEMORY_STORAGE_BACKEND=cloudflare
# 3. Configure Cloudflare credentials
export CLOUDFLARE_API_TOKEN="your-token"
export CLOUDFLARE_ACCOUNT_ID="your-account"
export CLOUDFLARE_D1_DATABASE_ID="your-d1-id"
export CLOUDFLARE_VECTORIZE_INDEX="mcp-memory-index"
# 4. Migrate data to Cloudflare
python scripts/migration/legacy/migrate_chroma_to_sqlite.py
python scripts/sync/sync_memory_backends.py --source sqlite_vec --target cloudflare
```
## Backend Comparison
| Feature | Hybrid ⭐ | SQLite-vec | Cloudflare | ChromaDB (Removed) |
|---------|----------|------------|------------|-------------------|
| **Performance** | 5ms (local) | 5ms | Network | 15ms |
| **Multi-device** | ✅ Yes | ❌ No | ✅ Yes | ❌ No |
| **Offline support** | ✅ Yes | ✅ Yes | ❌ No | ✅ Yes |
| **Cloud backup** | ✅ Auto | ❌ No | ✅ Native | ❌ No |
| **Dependencies** | Light | Minimal | None | Heavy (~2GB) |
| **Setup complexity** | Medium | Easy | Medium | Easy |
| **Status** | **Recommended** | Supported | Supported | **REMOVED** |
## Migration Script Details
### Using the Legacy Migration Script
The ChromaDB migration script is preserved in the legacy branch:
```bash
# From chromadb-legacy branch
python scripts/migration/migrate_chroma_to_sqlite.py [OPTIONS]
Options:
--source PATH Path to ChromaDB data (default: CHROMA_PATH from config)
--target PATH Path for SQLite database (default: SQLITE_VEC_PATH)
--backup PATH Create JSON backup of ChromaDB data
--validate Validate migration integrity
--dry-run Show what would be migrated without making changes
```
### Manual Migration Steps
If you prefer manual control:
1. **Export from ChromaDB**:
```bash
git checkout chromadb-legacy
python -c "
from mcp_memory_service.storage.chroma import ChromaMemoryStorage
import json
storage = ChromaMemoryStorage(path='./chroma_db')
memories = storage.get_all_memories()
with open('export.json', 'w') as f:
json.dump([m.to_dict() for m in memories], f)
"
```
2. **Import to new backend**:
```bash
git checkout main
python -c "
from mcp_memory_service.storage.sqlite_vec import SqliteVecMemoryStorage
import json
storage = SqliteVecMemoryStorage(db_path='./memory.db')
await storage.initialize()
with open('export.json') as f:
memories = json.load(f)
for mem in memories:
await storage.store(Memory.from_dict(mem))
"
```
## Data Validation
After migration, verify your data:
```bash
# Check memory count
python -c "
from mcp_memory_service.storage.factory import create_storage_instance
storage = await create_storage_instance('./memory.db')
count = len(await storage.get_all_memories())
print(f'Migrated {count} memories')
"
# Compare with backup
python scripts/validation/validate_migration.py \
--source ~/chromadb_backup.json \
--target ./memory.db
```
## Troubleshooting
### Issue: Migration script not found
**Solution**: The migration script is only available on the `chromadb-legacy` branch:
```bash
git checkout chromadb-legacy
python scripts/migration/migrate_chroma_to_sqlite.py
```
### Issue: Import errors for ChromaMemoryStorage
**Solution**: You must be on the `chromadb-legacy` branch to access ChromaDB code:
```bash
git checkout chromadb-legacy # ChromaDB code available
git checkout main # ChromaDB removed (v8.0.0+)
```
### Issue: "ChromaDB not installed" error
**Solution**: Install chromadb on the legacy branch:
```bash
git checkout chromadb-legacy
pip install chromadb>=0.5.0 sentence-transformers>=2.2.2
```
### Issue: Memory timestamps lost during migration
**Solution**: Use `--preserve-timestamps` flag:
```bash
python scripts/migration/migrate_chroma_to_sqlite.py --preserve-timestamps
```
### Issue: Large ChromaDB database migration is slow
**Solution**: Use batch mode for faster migration:
```bash
python scripts/migration/migrate_chroma_to_sqlite.py --batch-size 100
```
## Rollback Plan
If you need to rollback to ChromaDB (not recommended):
1. **Stay on v7.x releases** - Do not upgrade to v8.0.0
2. **Use chromadb-legacy branch** for reference
3. **Restore from backup**:
```bash
git checkout chromadb-legacy
python scripts/migration/restore_from_backup.py ~/chromadb_backup.json
```
## Post-Migration Checklist
- [ ] Backup completed successfully
- [ ] Migration script ran without errors
- [ ] Memory count matches between old and new backend
- [ ] Sample queries return expected results
- [ ] Configuration updated (`MCP_MEMORY_STORAGE_BACKEND`)
- [ ] Legacy ChromaDB data directory backed up
- [ ] Validation script passes
- [ ] Application tests pass
- [ ] Claude Desktop/Code integration works
## Support
- **Migration issues**: See [Issue #148](https://github.com/doobidoo/mcp-memory-service/issues/148)
- **Legacy branch**: [chromadb-legacy](https://github.com/doobidoo/mcp-memory-service/tree/chromadb-legacy)
- **Backend setup**: See [STORAGE_BACKENDS.md](./STORAGE_BACKENDS.md)
## Why Was ChromaDB Removed?
- **Performance**: 3x slower than SQLite-vec (15ms vs 5ms)
- **Dependencies**: Required ~2GB PyTorch download
- **Complexity**: 2,841 lines of code removed
- **Better alternatives**: Hybrid backend provides better performance with cloud sync
- **Maintenance**: Reduced long-term maintenance burden
The removal improves the project's maintainability while offering better performance through modern alternatives.
```
--------------------------------------------------------------------------------
/docs/development/code-quality/phase-2a-handle-get-prompt.md:
--------------------------------------------------------------------------------
```markdown
# Refactoring: handle_get_prompt() - Phase 2, Function #5
## Summary
Refactored `server.py::handle_get_prompt()` to reduce cyclomatic complexity and improve maintainability through Extract Method pattern.
**Metrics:**
- **Original Complexity:** 33
- **Refactored Main Function:** Complexity 6 (82% reduction)
- **Original Lines:** 208
- **Refactored Main Function:** 41 lines
- **Helper Functions Created:** 5
## Refactoring Strategy: Extract Method Pattern
The function contained a long if/elif/else chain handling 5 different prompt types. Each prompt type required 25-40 lines of specialized logic with high nesting and branching.
### Helper Functions Extracted
#### 1. `_prompt_memory_review()` - CC: 5
**Purpose:** Handle "memory_review" prompt type
**Responsibilities:**
- Parse time_period and focus_area arguments
- Retrieve memories from specified time period
- Format memories as prompt text with tags
**Location:** Lines ~1320-1347
**Input:** arguments dict
**Output:** List of PromptMessage objects
---
#### 2. `_prompt_memory_analysis()` - CC: 8
**Purpose:** Handle "memory_analysis" prompt type
**Responsibilities:**
- Parse tags and time_range arguments
- Retrieve relevant memories
- Analyze patterns (tag counts, memory types)
- Build analysis report text
**Location:** Lines ~1349-1388
**Input:** arguments dict
**Output:** List of PromptMessage objects
**Complexity Source:** Double-nested loops for pattern analysis (2 for loops)
---
#### 3. `_prompt_knowledge_export()` - CC: 8
**Purpose:** Handle "knowledge_export" prompt type
**Responsibilities:**
- Parse format_type and filter criteria
- Retrieve memories based on filter
- Format export in JSON/Markdown/Text based on format_type
- Build export text
**Location:** Lines ~1390-1428
**Input:** arguments dict
**Output:** List of PromptMessage objects
**Complexity Source:** Multiple format branches (if/elif/else)
---
#### 4. `_prompt_memory_cleanup()` - CC: 6
**Purpose:** Handle "memory_cleanup" prompt type
**Responsibilities:**
- Parse cleanup parameters
- Find duplicate memories
- Build cleanup report
- Provide recommendations
**Location:** Lines ~1430-1458
**Input:** arguments dict
**Output:** List of PromptMessage objects
**Complexity Source:** Nested loop for duplicate detection
---
#### 5. `_prompt_learning_session()` - CC: 5
**Purpose:** Handle "learning_session" prompt type
**Responsibilities:**
- Parse topic, key_points, and questions
- Create structured learning note
- Store as memory
- Return formatted response
**Location:** Lines ~1460-1494
**Input:** arguments dict
**Output:** List of PromptMessage objects
---
## Refactored `handle_get_prompt()` Function - CC: 6
**New Structure:**
```python
async def handle_get_prompt(self, name: str, arguments: dict):
await self._ensure_storage_initialized()
# Simple dispatch to specialized handlers
if name == "memory_review":
messages = await self._prompt_memory_review(arguments)
elif name == "memory_analysis":
messages = await self._prompt_memory_analysis(arguments)
# ... etc
else:
messages = [unknown_prompt_message]
return GetPromptResult(description=..., messages=messages)
```
**Lines:** 41 (vs 208 original)
**Control Flow:** Reduced from 33 branches to 6 (if/elif chain only)
## Benefits
### Code Quality
- ✅ **Single Responsibility:** Each function handles one prompt type
- ✅ **Testability:** Each prompt type can be unit tested independently
- ✅ **Readability:** Main function is now a simple dispatcher
- ✅ **Maintainability:** Changes to one prompt type isolated to its handler
- ✅ **Extensibility:** Adding new prompt types requires just another elif
### Complexity Distribution
```
handle_get_prompt: CC 6 (dispatcher)
_prompt_memory_review: CC 5 (simple retrieval + format)
_prompt_memory_analysis: CC 8 (pattern analysis)
_prompt_knowledge_export: CC 8 (multiple format branches)
_prompt_memory_cleanup: CC 6 (duplicate detection)
_prompt_learning_session: CC 5 (create + store)
```
**Total distributed complexity:** 38 (vs 33 monolithic)
**Max function complexity:** 8 (vs 33 monolithic) - 75% reduction in peak complexity
### Maintainability Improvements
- Prompt handlers are now 27-39 lines each (vs 208 for entire function)
- Clear naming convention (`_prompt_<type>`) makes intent obvious
- Easier to locate specific prompt logic
- Reduces cognitive load when reading main function
- New developers can understand each handler independently
## Backward Compatibility
✅ **Fully compatible** - No changes to:
- Function signature: `handle_get_prompt(name, arguments) -> GetPromptResult`
- Return values: Same GetPromptResult structure
- Argument processing: Same argument parsing
- All prompt types: Same behavior
## Testing Recommendations
### Unit Tests
- `test_prompt_memory_review()` - Test memory retrieval + formatting
- `test_prompt_memory_analysis()` - Test pattern analysis logic
- `test_prompt_knowledge_export()` - Test each format (JSON/MD/text)
- `test_prompt_memory_cleanup()` - Test duplicate detection
- `test_prompt_learning_session()` - Test storage logic
### Integration Tests
- Test all 5 prompt types through handle_get_prompt()
- Verify error handling for unknown prompts
- Test with various argument combinations
## Related Issues
- **Issue #246:** Code Quality Phase 2 - Reduce Function Complexity
- **Phase 2 Progress:** 4/27 high-risk functions completed
- ✅ `install.py::main()` - Complexity 62 → ~8
- ✅ `sqlite_vec.py::initialize()` - Complexity 38 → Reduced
- ✅ `install_package()` - Complexity 33 → 7
- ✅ `handle_get_prompt()` - Complexity 33 → 6 (THIS REFACTORING)
## Files Modified
- `src/mcp_memory_service/server.py`: Refactored `handle_get_prompt()` with 5 helper methods
## Git Commit
Use semantic commit message:
```
refactor: reduce handle_get_prompt() complexity from 33 to 6 (82% reduction)
Extract prompt type handlers:
- _prompt_memory_review (CC 5) - Memory retrieval + formatting
- _prompt_memory_analysis (CC 8) - Pattern analysis
- _prompt_knowledge_export (CC 8) - Multi-format export
- _prompt_memory_cleanup (CC 6) - Duplicate detection
- _prompt_learning_session (CC 5) - Learning note creation
Main dispatcher now 41 lines (vs 208 original) with CC 6.
All handlers individually testable and maintainable.
Addresses issue #246 Phase 2, function #5 in refactoring plan.
```
## Code Review Checklist
- [x] Code compiles without errors
- [x] All handlers extract correctly
- [x] Dispatcher logic correct
- [x] No changes to external API
- [x] Backward compatible
- [x] Complexity reduced
- [ ] All tests pass (manual verification needed)
- [ ] Integration tested
## Future Improvements
1. **Prompt Registry:** Create a dictionary-based prompt registry for even simpler dispatch
2. **Configuration:** Make prompt definitions configurable
3. **Validation:** Add argument schema validation for each prompt type
4. **Documentation:** Auto-generate prompt documentation from handler implementations
```
--------------------------------------------------------------------------------
/tests/bridge/mock_responses.js:
--------------------------------------------------------------------------------
```javascript
/**
* Mock Server Responses for Testing
*
* These responses match the ACTUAL behavior of the MCP Memory Service API,
* not what we might assume or hope it returns.
*/
const mockResponses = {
// Health endpoint responses
health: {
healthy: {
status: 200, // NOT 204 or other codes
body: {
status: 'healthy',
version: '6.6.1',
timestamp: '2025-08-24T12:00:00Z',
uptime_seconds: 3600,
storage_type: 'sqlite_vec',
statistics: {
total_memories: 100,
total_tags: 25
}
}
},
unhealthy: {
status: 503,
body: {
status: 'unhealthy',
error: 'Database connection failed'
}
}
},
// Memory storage responses - CRITICAL: Server returns 200, not 201!
memories: {
createSuccess: {
status: 200, // ACTUAL: Returns 200, not 201 for creation!
body: {
success: true, // Key field for determining actual success
message: 'Memory stored successfully',
content_hash: 'abc123def456',
memory: {
content: 'Test memory content',
content_hash: 'abc123def456',
tags: ['test', 'source:test-client'],
memory_type: 'note',
metadata: {
hostname: 'test-client'
},
created_at: 1756054456.123,
created_at_iso: '2025-08-24T12:00:00.123Z',
updated_at: 1756054456.123,
updated_at_iso: '2025-08-24T12:00:00.123Z'
}
}
},
duplicate: {
status: 200, // SAME status code as success!
body: {
success: false, // This field determines it's a duplicate
message: 'Duplicate content detected',
content_hash: 'abc123def456',
memory: null
}
},
invalidRequest: {
status: 400,
body: {
detail: 'Invalid request: content is required'
}
},
unauthorized: {
status: 401,
body: {
detail: 'Invalid API key'
}
},
serverError: {
status: 500,
body: {
detail: 'Internal server error'
}
}
},
// Memory retrieval/search responses
search: {
withResults: {
status: 200,
body: {
results: [
{
memory: {
content: 'Matching memory content',
content_hash: 'hash1',
tags: ['test', 'search'],
memory_type: 'note',
created_at_iso: '2025-08-24T11:00:00Z',
metadata: {}
},
relevance_score: 0.95
},
{
memory: {
content: 'Another matching memory',
content_hash: 'hash2',
tags: ['test'],
memory_type: 'reference',
created_at_iso: '2025-08-24T10:00:00Z',
metadata: {}
},
relevance_score: 0.87
}
]
}
},
empty: {
status: 200,
body: {
results: []
}
}
},
// Tag search responses
tagSearch: {
withResults: {
status: 200,
body: {
memories: [
{
content: 'Memory with specific tag',
content_hash: 'tag_hash1',
tags: ['specific-tag', 'other-tag'],
memory_type: 'note',
created_at_iso: '2025-08-24T09:00:00Z'
}
]
}
},
empty: {
status: 200,
body: {
memories: []
}
}
},
// Delete memory responses
deleteMemory: {
success: {
status: 200,
body: {
success: true,
message: 'Memory deleted successfully'
}
},
notFound: {
status: 404,
body: {
detail: 'Memory not found'
}
}
},
// Edge cases and error conditions
edgeCases: {
// When the /api path is missing (404 because endpoint wrong)
missingApiPath: {
status: 404,
body: {
detail: 'Not Found'
}
},
// Network timeout
timeout: {
error: new Error('ETIMEDOUT')
},
// Connection refused
connectionRefused: {
error: new Error('ECONNREFUSED')
},
// Invalid JSON response
invalidJson: {
status: 200,
body: 'This is not JSON', // String instead of object
raw: true
},
// HTML error page instead of JSON
htmlError: {
status: 500,
body: '<html><body>500 Internal Server Error</body></html>',
raw: true,
contentType: 'text/html'
}
}
};
/**
* Helper function to create a mock HTTP response object
*/
function createMockResponse(mockData) {
if (mockData.error) {
throw mockData.error;
}
return {
statusCode: mockData.status,
headers: {
'content-type': mockData.contentType || 'application/json'
},
on: (event, callback) => {
if (event === 'data') {
const data = mockData.raw ?
mockData.body :
JSON.stringify(mockData.body);
callback(Buffer.from(data));
} else if (event === 'end') {
callback();
}
}
};
}
/**
* Helper to create a mock request object
*/
function createMockRequest() {
const req = {
on: (event, callback) => {
if (event === 'error') {
// Store error handler
req.errorHandler = callback;
} else if (event === 'timeout') {
req.timeoutHandler = callback;
}
},
write: () => {},
end: () => {},
destroy: () => {},
setTimeout: () => {}
};
return req;
}
module.exports = {
mockResponses,
createMockResponse,
createMockRequest
};
```
--------------------------------------------------------------------------------
/scripts/server/run_memory_server.py:
--------------------------------------------------------------------------------
```python
#!/usr/bin/env python3
# Copyright 2024 Heinrich Krupp
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Direct runner for MCP Memory Service.
This script directly imports and runs the memory server without going through the installation process.
"""
import os
import sys
import importlib.util
import importlib.machinery
import traceback
# Disable sitecustomize.py and other import hooks to prevent recursion issues
os.environ["PYTHONNOUSERSITE"] = "1" # Disable user site-packages
os.environ["PYTHONPATH"] = "" # Clear PYTHONPATH
# Set environment variables to prevent pip from installing dependencies
os.environ["PIP_NO_DEPENDENCIES"] = "1"
os.environ["PIP_NO_INSTALL"] = "1"
# Set environment variables for better cross-platform compatibility
os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
# For Windows with limited GPU memory, use smaller chunks
os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "max_split_size_mb:128"
# Set ChromaDB path if provided via environment variables
if "MCP_MEMORY_CHROMA_PATH" in os.environ:
print(f"Using ChromaDB path: {os.environ['MCP_MEMORY_CHROMA_PATH']}", file=sys.stderr, flush=True)
# Set backups path if provided via environment variables
if "MCP_MEMORY_BACKUPS_PATH" in os.environ:
print(f"Using backups path: {os.environ['MCP_MEMORY_BACKUPS_PATH']}", file=sys.stderr, flush=True)
def print_info(text):
"""Print formatted info text."""
print(f"[INFO] {text}", file=sys.stderr, flush=True)
def print_error(text):
"""Print formatted error text."""
print(f"[ERROR] {text}", file=sys.stderr, flush=True)
def print_success(text):
"""Print formatted success text."""
print(f"[SUCCESS] {text}", file=sys.stderr, flush=True)
def print_warning(text):
"""Print formatted warning text."""
print(f"[WARNING] {text}", file=sys.stderr, flush=True)
def run_memory_server():
"""Run the MCP Memory Service directly."""
print_info("Starting MCP Memory Service")
# Save original sys.path and meta_path
original_sys_path = sys.path.copy()
original_meta_path = sys.meta_path
# Temporarily disable import hooks
sys.meta_path = [finder for finder in sys.meta_path
if not hasattr(finder, 'find_spec') or
not hasattr(finder, 'blocked_packages')]
try:
# Get the directory of this script
script_dir = os.path.dirname(os.path.abspath(__file__))
# Go up two directories from scripts/server/ to reach the project root
project_root = os.path.dirname(os.path.dirname(script_dir))
# Add src directory to path if it exists (prioritize local development)
src_dir = os.path.join(project_root, "src")
if os.path.exists(src_dir):
print_info(f"Adding {src_dir} to sys.path (prioritized for development)")
# Remove any existing mcp_memory_service from sys.modules to avoid conflicts
modules_to_remove = [key for key in sys.modules.keys() if key.startswith('mcp_memory_service')]
for module in modules_to_remove:
print_info(f"Removing conflicting module: {module}")
del sys.modules[module]
# Insert src at the very beginning to override any installed packages
if src_dir in sys.path:
sys.path.remove(src_dir)
sys.path.insert(0, src_dir)
else:
# Add site-packages to sys.path only if src doesn't exist
site_packages = os.path.join(sys.prefix, 'Lib', 'site-packages')
if site_packages not in sys.path:
sys.path.insert(0, site_packages)
# Try direct import from src directory
server_path = os.path.join(src_dir, "mcp_memory_service", "server.py")
if os.path.exists(server_path):
print_info(f"Found server module at {server_path}")
# Use importlib to load the module directly from the file
module_name = "mcp_memory_service.server"
spec = importlib.util.spec_from_file_location(module_name, server_path)
if spec is None:
print_error(f"Could not create spec from file: {server_path}")
sys.exit(1)
server = importlib.util.module_from_spec(spec)
sys.modules[module_name] = server # Add to sys.modules to avoid import issues
spec.loader.exec_module(server)
print_success("Successfully imported mcp_memory_service.server from file")
else:
# Try to import using importlib
print_info("Attempting to import mcp_memory_service.server using importlib")
# First try to find the module in site-packages
server_spec = importlib.machinery.PathFinder.find_spec('mcp_memory_service.server', [site_packages])
# If not found, try to find it in src directory
if server_spec is None and os.path.exists(src_dir):
server_spec = importlib.machinery.PathFinder.find_spec('mcp_memory_service.server', [src_dir])
if server_spec is None:
print_error("Could not find mcp_memory_service.server module spec")
sys.exit(1)
# Load the server module
server = importlib.util.module_from_spec(server_spec)
server_spec.loader.exec_module(server)
print_success("Successfully imported mcp_memory_service.server")
# Run the memory server with error handling
try:
print_info("Calling mcp_memory_service.server.main()")
server.main()
except Exception as e:
print_error(f"Error running memory server: {e}")
traceback.print_exc(file=sys.stderr)
sys.exit(1)
except ImportError as e:
print_error(f"Failed to import mcp_memory_service.server: {e}")
traceback.print_exc(file=sys.stderr)
sys.exit(1)
except Exception as e:
print_error(f"Error setting up memory server: {e}")
traceback.print_exc(file=sys.stderr)
sys.exit(1)
finally:
# Restore original sys.path and meta_path
sys.path = original_sys_path
sys.meta_path = original_meta_path
if __name__ == "__main__":
try:
run_memory_server()
except KeyboardInterrupt:
print_info("Script interrupted by user")
sys.exit(0)
except Exception as e:
print_error(f"Unhandled exception: {e}")
traceback.print_exc(file=sys.stderr)
sys.exit(1)
```
--------------------------------------------------------------------------------
/docs/development/code-quality/phase-2b-session-summary.md:
--------------------------------------------------------------------------------
```markdown
# Phase 2b Session Summary
## Session Goal
Reduce code duplication from 4.9% to <3% by consolidating remaining 5 clone groups.
## Work Completed
### Group 3: Document Chunk Processing ✅ COMPLETED
**Status**: Successfully consolidated and tested
**Impact**: ~25-40 lines eliminated
#### What Was Done
- Extracted `_process_and_store_chunk()` helper function in `document_processing.py`
- Consolidated 3 duplicated chunk-to-memory processing blocks across:
- `src/mcp_memory_service/cli/ingestion.py:275-299` (25 lines)
- `src/mcp_memory_service/server.py:3857-3881` (25 lines)
- `src/mcp_memory_service/web/api/documents.py:526-556` (31 lines)
#### Benefits
- Reduced code duplication in document processing pipeline
- Improved maintainability of chunk handling logic
- Consistent error handling across entry points
- Support for extra metadata, memory types, and context tags
#### Testing
- ✅ All document processing tests pass (26 tests)
- ✅ All ingestion tests pass (loader tests, chunking, etc.)
- ✅ No regression in memory service functionality
- ✅ Syntax validation passes
#### Commit
```
commit b3ac4a2
refactor: Phase 2b-1 - Consolidate chunk processing logic (Group 3)
- Extract _process_and_store_chunk() helper function
- Consolidate 3 duplicated blocks (81 lines total)
- Reduced code duplication and improved maintainability
```
---
## Remaining Groups Analysis
### Group 0: Test/Script Duplication ⏭️ DEFERRED
**Files**: Test helper patterns across multiple test scripts
- `claude-hooks/install_hooks.py:180-203` (24 lines)
- `scripts/testing/test_memory_simple.py:91-102` (12 lines)
- `scripts/testing/test_search_api.py:79-96` (18 lines)
**Assessment**: These are request/response handling patterns in test scripts with different error reporting needs. Low priority as they don't affect production code.
**Why Deferred**:
- Test/script files have different error handling conventions
- Would require creating shared test utilities module
- Lower impact on production code quality
- Risk of breaking test-specific error reporting
---
### Group 1: Error Handling Pattern (Install Utilities) ⏭️ DEFERRED
**Files**: Version checking and error fallback patterns
- `scripts/installation/install.py:68-77` (10 lines)
- `scripts/installation/install.py:839-849` (11 lines)
- `src/mcp_memory_service/utils/port_detection.py:70-84` (15 lines)
**Assessment**: Complex error handling patterns with different exception types and fallback logic. Would require careful refactoring to maintain semantic meaning.
**Why Deferred**:
- Spans installation scripts and core utilities
- Different error recovery semantics for each instance
- Requires deep understanding of fallback requirements
- Risk of breaking installation process
---
### Group 2: Migration/Initialization Output ⏭️ DEFERRED
**Files**: Status message and initialization output patterns
- `scripts/installation/install.py:1617-1628` (12 lines)
- `scripts/migration/migrate_v5_enhanced.py:591-601` (11 lines)
- `src/mcp_memory_service/server.py:3948-3957` (10 lines)
**Assessment**: Output/logging patterns for user-facing status messages. These are context-specific and serve different purposes (CLI output, migration reporting, diagnostics).
**Why Deferred**:
- Different output contexts (installation, migration, diagnostics)
- User-facing messages require careful wording
- Would need extensive testing across all contexts
- Risk of losing important semantic distinctions
---
### Group 4: Storage Health Validation (High-Risk) ⏭️ DEFERRED
**Files**: Storage backend validation logic
- `src/mcp_memory_service/server.py:3369-3428` (60 lines)
- `src/mcp_memory_service/server.py:3380-3428` (49 lines overlap)
- `src/mcp_memory_service/server.py:3391-3428` (38 lines overlap)
**Assessment**: Complex nested validation logic for different storage backends (SQLite-vec, Cloudflare, Hybrid). The overlapping line ranges indicate deeply nested if-else branches with error handling at multiple levels.
**Why High Risk for Refactoring**:
1. **Nested Validation Logic**: Each storage type has cascading conditional checks with specific error messages
2. **State-Dependent Behavior**: Validation depends on storage initialization state
3. **Multiple Error Paths**: Different error recovery strategies for each backend
4. **Performance Critical**: Health check is used during startup and monitoring
5. **Integration Risk**: Changes could affect server startup timing and reliability
6. **Testing Complexity**: Would need comprehensive testing of all three storage backends plus all error conditions
**Refactoring Challenges**:
- Extracting a helper would require handling branching logic carefully
- Each backend has unique validation requirements
- Error messages are specific to help debugging storage issues
- Any regression could prevent server startup
**Recommendation**: Leave as-is. The code is well-documented and the business logic is appropriately matched to the domain complexity.
---
## Current Duplication Status
**Estimated After Group 3**: 4.5-4.7% (down from 4.9%)
- Eliminated ~40-50 effective lines through consolidation
- Created reusable helper for future document processing use cases
**Path to <3%**:
To reach <3% would require consolidating Groups 1, 2, and 4:
- Group 1: 36 total lines, medium risk
- Group 2: 33 total lines, medium risk
- Group 4: 147 total lines, **HIGH RISK**
Total estimated consolidation: ~215 lines from remaining groups
- But Groups 1 & 2 have lower consolidation benefit due to semantic differences
- Group 4 has high refactoring risk relative to benefit
---
## Recommendations for Future Work
### Phase 3 Strategy
If further duplication reduction is needed, prioritize in this order:
1. **Group 1 (Medium Priority)**
- Extract error handling helpers for version/configuration checks
- Create `utils/installation_helpers.py` for shared patterns
- Estimated savings: ~25 effective lines
2. **Group 2 (Medium Priority)**
- Create output formatting helper for status messages
- Consolidate user-facing message templates
- Estimated savings: ~20 effective lines
3. **Group 4 (Low Priority, High Risk)**
- Only if duplication metric becomes critical
- Requires comprehensive refactoring with full test suite coverage
- Consider extracting per-backend validators as separate methods
- Estimated savings: ~80-100 effective lines, but high regression risk
### Testing Requirements for Future Work
- Full integration tests for Groups 1 & 2
- Multi-backend health check tests for Group 4
- Installation flow tests with fallback scenarios
- Migration validation under various database states
---
## Conclusion
Successfully completed Group 3 consolidation, creating a reusable helper function for document chunk processing. This represents a meaningful reduction in duplication while maintaining code clarity and maintainability.
The remaining 4 groups have lower priority or higher risk profiles:
- Groups 0, 1, 2 are lower impact (test/utility code)
- Group 4 is high risk with nested logic across multiple backends
**Current Achievement**: ~25-40 lines consolidated with 100% test pass rate and no regressions.
```
--------------------------------------------------------------------------------
/scripts/sync/export_memories.py:
--------------------------------------------------------------------------------
```python
#!/usr/bin/env python3
# Copyright 2024 Heinrich Krupp
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Export memories from SQLite-vec database to JSON format.
This script exports all memories from a local SQLite-vec database,
preserving timestamps, metadata, and adding source tracking for
multi-machine synchronization.
"""
import asyncio
import sys
import logging
import argparse
import platform
from pathlib import Path
from datetime import datetime
# Add project src to path
project_root = Path(__file__).parent.parent.parent
sys.path.insert(0, str(project_root / "src"))
from mcp_memory_service.storage.sqlite_vec import SqliteVecMemoryStorage
from mcp_memory_service.sync.exporter import MemoryExporter
from mcp_memory_service.config import SQLITE_VEC_PATH, STORAGE_BACKEND
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def get_default_db_path() -> Path:
"""Get the default database path for this platform."""
if STORAGE_BACKEND == 'sqlite_vec' and SQLITE_VEC_PATH:
return Path(SQLITE_VEC_PATH)
else:
# Fallback to BASE_DIR if not using sqlite_vec backend
from mcp_memory_service.config import BASE_DIR
return Path(BASE_DIR) / "sqlite_vec.db"
def get_default_output_filename() -> str:
"""Generate a default output filename based on machine and timestamp."""
machine_name = platform.node().lower().replace(' ', '-')
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
return f"{machine_name}_memories_export_{timestamp}.json"
async def export_memories(
db_path: Path,
output_file: Path,
include_embeddings: bool = False,
filter_tags: list = None
):
"""Export memories from database to JSON file."""
logger.info(f"Starting memory export from {db_path}")
# Check if database exists
if not db_path.exists():
logger.error(f"Database not found at {db_path}")
logger.info("Available storage locations:")
logger.info(f" Default: {get_default_db_path()}")
return False
try:
# Initialize storage
logger.info("Initializing SQLite-vec storage...")
storage = SqliteVecMemoryStorage(str(db_path))
await storage.initialize()
# Create exporter
exporter = MemoryExporter(storage)
# Show summary first
logger.info("Analyzing database...")
summary = await exporter.export_summary()
logger.info(f"Database analysis:")
logger.info(f" Total memories: {summary['total_memories']}")
logger.info(f" Machine: {summary['machine_name']}")
logger.info(f" Date range: {summary['date_range']['earliest']} to {summary['date_range']['latest']}")
logger.info(f" Memory types: {summary['memory_types']}")
logger.info(f" Top tags: {list(summary['tag_counts'].items())[:5]}")
logger.info(f" Estimated size: {summary['estimated_json_size_mb']:.1f} MB")
# Perform export
logger.info(f"Exporting to {output_file}...")
result = await exporter.export_to_json(
output_file=output_file,
include_embeddings=include_embeddings,
filter_tags=filter_tags
)
if result["success"]:
logger.info("Export completed successfully!")
logger.info(f" Exported: {result['exported_count']} memories")
logger.info(f" Output file: {result['output_file']}")
logger.info(f" File size: {result['file_size_bytes'] / 1024 / 1024:.2f} MB")
logger.info(f" Source machine: {result['source_machine']}")
# Show next steps
logger.info("")
logger.info("Next steps:")
logger.info("1. Transfer this JSON file to your central server")
logger.info("2. Run import_memories.py on the central server")
logger.info("3. Set up Litestream for ongoing synchronization")
return True
else:
logger.error("Export failed")
return False
except Exception as e:
logger.error(f"Export failed: {str(e)}")
return False
async def main():
"""Main function."""
parser = argparse.ArgumentParser(
description="Export memories from SQLite-vec database to JSON",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Export all memories with default settings
python export_memories.py
# Export from specific database
python export_memories.py --db-path /path/to/sqlite_vec.db
# Export to specific file
python export_memories.py --output my_export.json
# Export only memories with specific tags
python export_memories.py --filter-tags claude-code,architecture
# Include embedding vectors (increases file size significantly)
python export_memories.py --include-embeddings
"""
)
parser.add_argument(
"--db-path",
type=Path,
default=get_default_db_path(),
help=f"Path to SQLite-vec database (default: {get_default_db_path()})"
)
parser.add_argument(
"--output",
type=Path,
default=get_default_output_filename(),
help=f"Output JSON file (default: {get_default_output_filename()})"
)
parser.add_argument(
"--include-embeddings",
action="store_true",
help="Include embedding vectors in export (increases file size)"
)
parser.add_argument(
"--filter-tags",
nargs="*",
help="Only export memories with these tags"
)
parser.add_argument(
"--verbose",
action="store_true",
help="Enable verbose logging"
)
args = parser.parse_args()
# Set logging level
if args.verbose:
logging.getLogger().setLevel(logging.DEBUG)
# Show configuration
logger.info("Memory Export Configuration:")
logger.info(f" Database: {args.db_path}")
logger.info(f" Output: {args.output}")
logger.info(f" Include embeddings: {args.include_embeddings}")
logger.info(f" Filter tags: {args.filter_tags}")
logger.info(f" Platform: {platform.system()} {platform.release()}")
logger.info("")
# Run export
success = await export_memories(
db_path=args.db_path,
output_file=args.output,
include_embeddings=args.include_embeddings,
filter_tags=args.filter_tags
)
sys.exit(0 if success else 1)
if __name__ == "__main__":
asyncio.run(main())
```
--------------------------------------------------------------------------------
/docs/CLAUDE_CODE_QUICK_REFERENCE.md:
--------------------------------------------------------------------------------
```markdown
# Claude Code Quick Reference for MCP Memory Service
**One-page cheat sheet for efficient development with Claude Code**
---
## 🎯 Essential Keybindings
| Key | Action | Use Case |
|-----|--------|----------|
| `Shift+Tab` | Auto-accept edits | Fast iteration on suggested changes |
| `Esc` | Cancel operation | Stop unwanted actions |
| `Ctrl+R` | Verbose output | Debug when things go wrong |
| `#` | Create memory | Store important decisions |
| `@` | Add to context | Include files/dirs (`@src/`, `@tests/`) |
| `!` | Bash mode | Quick shell commands |
---
## 🚀 Common Tasks
### Memory Operations
```bash
# Store information
/memory-store "Hybrid backend uses SQLite primary + Cloudflare secondary"
# Retrieve information
/memory-recall "how to configure Cloudflare backend"
# Check service health
/memory-health
```
### Development Workflow
```bash
# 1. Start with context
@src/mcp_memory_service/storage/
@tests/test_storage.py
# 2. Make changes incrementally
# Accept suggestions with Shift+Tab
# 3. Test immediately
pytest tests/test_storage.py -v
# 4. Document decisions
/memory-store "Changed X because Y"
```
### Backend Configuration
```bash
# Check current backend
python scripts/server/check_http_server.py -v
# Validate configuration
python scripts/validation/validate_configuration_complete.py
# Diagnose issues
python scripts/validation/diagnose_backend_config.py
```
### Synchronization
```bash
# Check sync status
python scripts/sync/sync_memory_backends.py --status
# Preview sync (dry run)
python scripts/sync/sync_memory_backends.py --dry-run
# Execute sync
python scripts/sync/sync_memory_backends.py --direction bidirectional
```
---
## 🏗️ Project-Specific Context
### Key Files to Add
| Purpose | Files to Include |
|---------|-----------------|
| **Storage backends** | `@src/mcp_memory_service/storage/` |
| **MCP protocol** | `@src/mcp_memory_service/server.py` |
| **Web interface** | `@src/mcp_memory_service/web/` |
| **Configuration** | `@.env.example`, `@src/mcp_memory_service/config.py` |
| **Tests** | `@tests/test_*.py` |
| **Scripts** | `@scripts/server/`, `@scripts/sync/` |
### Common Debugging Patterns
```bash
# 1. HTTP Server not responding
python scripts/server/check_http_server.py -v
tasklist | findstr python # Check if running
scripts/server/start_http_server.bat # Restart
# 2. Wrong backend active
python scripts/validation/diagnose_backend_config.py
# Check: .env file, environment variables, Claude Desktop config
# 3. Missing memories
python scripts/sync/sync_memory_backends.py --status
# Compare: Cloudflare count vs SQLite count
# 4. Service logs
@http_server.log # Add to context for troubleshooting
```
---
## 📚 Architecture Quick Reference
### Storage Backends
| Backend | Performance | Use Case | Config Variable |
|---------|-------------|----------|-----------------|
| **Hybrid** ⭐ | 5ms read | Production (recommended) | `MCP_MEMORY_STORAGE_BACKEND=hybrid` |
| **SQLite-vec** | 5ms read | Development, single-user | `MCP_MEMORY_STORAGE_BACKEND=sqlite_vec` |
| **Cloudflare** | Network-dependent | Legacy cloud-only | `MCP_MEMORY_STORAGE_BACKEND=cloudflare` |
### Key Directories
```
mcp-memory-service/
├── src/mcp_memory_service/
│ ├── server.py # MCP protocol implementation
│ ├── storage/
│ │ ├── base.py # Abstract storage interface
│ │ ├── sqlite_vec.py # SQLite-vec backend
│ │ ├── cloudflare.py # Cloudflare backend
│ │ └── hybrid.py # Hybrid backend (recommended)
│ ├── web/
│ │ ├── app.py # FastAPI server
│ │ └── static/ # Dashboard UI
│ └── config.py # Configuration management
├── scripts/
│ ├── server/ # HTTP server management
│ ├── sync/ # Backend synchronization
│ └── validation/ # Configuration validation
└── tests/ # Test suite
```
---
## 🔧 Environment Variables
**Essential Configuration** (in `.env` file):
```bash
# Backend Selection
MCP_MEMORY_STORAGE_BACKEND=hybrid # hybrid|sqlite_vec|cloudflare
# Cloudflare (required for hybrid/cloudflare backends)
CLOUDFLARE_API_TOKEN=your-token
CLOUDFLARE_ACCOUNT_ID=your-account
CLOUDFLARE_D1_DATABASE_ID=your-d1-id
CLOUDFLARE_VECTORIZE_INDEX=mcp-memory-index
# Hybrid-Specific
MCP_HYBRID_SYNC_INTERVAL=300 # 5 minutes
MCP_HYBRID_BATCH_SIZE=50
MCP_HYBRID_SYNC_ON_STARTUP=true
# HTTP Server
MCP_HTTP_ENABLED=true
MCP_HTTPS_ENABLED=true
MCP_API_KEY=your-generated-key
```
---
## 🐛 Troubleshooting Checklist
### HTTP Server Issues
- [ ] Check if server is running: `python scripts/server/check_http_server.py -v`
- [ ] Review logs: `@http_server.log`
- [ ] Restart server: `scripts/server/start_http_server.bat`
- [ ] Verify port 8000 is free: `netstat -ano | findstr :8000`
### Backend Configuration Issues
- [ ] Run diagnostic: `python scripts/validation/diagnose_backend_config.py`
- [ ] Check `.env` file exists and has correct values
- [ ] Verify Cloudflare credentials are valid
- [ ] Confirm environment variables loaded: check server startup logs
### Missing Memories
- [ ] Check sync status: `python scripts/sync/sync_memory_backends.py --status`
- [ ] Compare memory counts: Cloudflare vs SQLite
- [ ] Run manual sync: `python scripts/sync/sync_memory_backends.py --dry-run`
- [ ] Check for duplicates: Look for content hash matches
### Performance Issues
- [ ] Verify backend: Hybrid should show ~5ms read times
- [ ] Check disk space: Litestream requires adequate space
- [ ] Monitor background sync: Check `http_server.log` for sync logs
- [ ] Review embedding model cache: Should be loaded once
---
## 💡 Pro Tips
### Efficient Context Management
```bash
# Start specific, expand as needed
@src/mcp_memory_service/storage/hybrid.py # Specific file
@src/mcp_memory_service/storage/ # Whole module if needed
# Remove context when done
# Use Esc to cancel unnecessary context additions
```
### Multi-Step Tasks
```bash
# Always use TodoWrite for complex tasks
# Claude will create and manage task list automatically
# Example: "Implement new backend"
# 1. Research existing backends
# 2. Create new backend class
# 3. Implement abstract methods
# 4. Add configuration
# 5. Write tests
# 6. Update documentation
```
### Testing Strategy
```bash
# Test incrementally
pytest tests/test_storage.py::TestHybridBackend -v
# Run full suite before committing
pytest tests/ -v
# Check coverage
pytest tests/ --cov=src/mcp_memory_service --cov-report=term
```
### Git Workflow with Claude Code
```bash
# Let Claude help with commits
git status # Claude reviews changes
git diff # Claude explains changes
# Use semantic commits
git commit -m "feat: add new backend support"
git commit -m "fix: resolve sync timing issue"
git commit -m "docs: update configuration guide"
```
---
## 📖 Additional Resources
- **Full Documentation**: `@CLAUDE.md` (project-specific guide)
- **Global Best Practices**: `~/.claude/CLAUDE.md` (cross-project)
- **Wiki**: https://github.com/doobidoo/mcp-memory-service/wiki
- **Troubleshooting**: See Wiki for comprehensive troubleshooting guide
---
**Last Updated**: 2025-10-08
```
--------------------------------------------------------------------------------
/docs/technical/development.md:
--------------------------------------------------------------------------------
```markdown
# MCP Memory Service - Development Guidelines
## Commands
- Run memory server: `python scripts/run_memory_server.py`
- Run tests: `pytest tests/`
- Run specific test: `pytest tests/test_memory_ops.py::test_store_memory -v`
- Check environment: `python scripts/verify_environment_enhanced.py`
- Windows installation: `python scripts/install_windows.py`
- Build package: `python -m build`
## Installation Guidelines
- Always install in a virtual environment: `python -m venv venv`
- Use `install.py` for cross-platform installation
- Windows requires special PyTorch installation with correct index URL:
```bash
pip install torch==2.1.0 torchvision==2.1.0 torchaudio==2.1.0 --index-url https://download.pytorch.org/whl/cu118
```
- For recursion errors, run: `python scripts/fix_sitecustomize.py`
## Code Style
- Python 3.10+ with type hints
- Use dataclasses for models (see `models/memory.py`)
- Triple-quoted docstrings for modules and functions
- Async/await pattern for all I/O operations
- Error handling with specific exception types and informative messages
- Logging with appropriate levels for different severity
- Commit messages follow semantic release format: `type(scope): message`
## Project Structure
- `src/mcp_memory_service/` - Core package code
- `models/` - Data models
- `storage/` - Database abstraction
- `utils/` - Helper functions
- `server.py` - MCP protocol implementation
- `scripts/` - Utility scripts
- `memory_wrapper.py` - Windows wrapper script
- `install.py` - Cross-platform installation script
## Dependencies
- ChromaDB (0.5.23) for vector database
- sentence-transformers (>=2.2.2) for embeddings
- PyTorch (platform-specific installation)
- MCP protocol (>=1.0.0, <2.0.0) for client-server communication
## Troubleshooting
### Common Issues
- For Windows installation issues, use `scripts/install_windows.py`
- Apple Silicon requires Python 3.10+ built for ARM64
- CUDA issues: verify with `torch.cuda.is_available()`
- For MCP protocol issues, check `server.py` for required methods
### MCP Server Configuration Issues
If you encounter MCP server failures or "ModuleNotFoundError" issues:
#### Missing http_server_manager Module
**Symptoms:**
- Server fails with "No module named 'mcp_memory_service.utils.http_server_manager'"
- MCP server shows as "failed" in Claude Code
**Diagnosis:**
1. Test server directly: `python -m src.mcp_memory_service.server --debug`
2. Check if the error occurs during eager storage initialization
3. Look for HTTP server coordination mode detection
**Solution:**
The `http_server_manager.py` module handles multi-client coordination. If missing, create it with:
- `auto_start_http_server_if_needed()` function
- Port detection and server startup logic
- Integration with existing `port_detection.py` utilities
#### Storage Backend Issues
**Symptoms:**
- "vec0 constructor error: Unknown table option" (older sqlite-vec versions)
- Server initialization fails with storage errors
**Diagnosis:**
1. Test each backend independently:
- ChromaDB: `python scripts/run_memory_server.py --debug`
- SQLite-vec: `MCP_MEMORY_STORAGE_BACKEND=sqlite_vec python -m src.mcp_memory_service.server --debug`
2. Check database health: Use MCP tools to call `check_database_health`
**Solution:**
- Ensure sqlite-vec is properly installed and compatible
- Both backends should work when properly configured
- SQLite-vec uses direct storage mode when HTTP coordination fails
#### MCP Configuration Cleanup
When multiple servers conflict or fail:
1. **Backup configurations:**
```bash
cp .mcp.json .mcp.json.backup
```
2. **Remove failing servers** from `.mcp.json` while keeping working ones
3. **Test each backend separately:**
- ChromaDB backend: Uses `scripts/run_memory_server.py`
- SQLite-vec backend: Uses `python -m src.mcp_memory_service.server` with `MCP_MEMORY_STORAGE_BACKEND=sqlite_vec`
4. **Verify functionality** through MCP tools before re-enabling servers
### Collaborative Debugging with Claude Code
When troubleshooting complex MCP issues, consider this collaborative approach between developer and Claude Code:
#### Systematic Problem-Solving Partnership
1. **Developer identifies the symptom** (e.g., "memory server shows as failed")
2. **Claude Code conducts comprehensive diagnosis:**
- Tests current functionality with MCP tools
- Examines server logs and error messages
- Checks configuration files and git history
- Identifies root causes through systematic analysis
3. **Collaborative investigation approach:**
- **Developer requests:** "Check both ways - verify current service works AND fix the failing alternative"
- **Claude Code responds:** Creates todo lists, tests each component independently, provides detailed analysis
- **Developer provides context:** Domain knowledge, constraints, preferences
4. **Methodical fix implementation:**
- Back up configurations before changes
- Fix issues incrementally with testing at each step
- Document the process for future reference
- Commit meaningful changes with proper commit messages
#### Benefits of This Approach
- **Comprehensive coverage:** Nothing gets missed when both perspectives combine
- **Knowledge transfer:** Claude Code documents the process, developer retains understanding
- **Systematic methodology:** Todo lists and step-by-step verification prevent overlooked issues
- **Persistent knowledge:** Using the memory service itself to store troubleshooting solutions
#### Example Workflow
```
Developer: "MCP memory server is failing"
↓
Claude Code: Creates todo list, tests current state, identifies missing module
↓
Developer: "Let's fix it but keep the working parts"
↓
Claude Code: Backs up configs, fixes incrementally, tests each component
↓
Developer: "This should be documented"
↓
Claude Code: Updates documentation, memorizes the solution
↓
Both: Commit, push, and ensure knowledge is preserved
```
This collaborative model leverages Claude Code's systematic analysis capabilities with the developer's domain expertise and decision-making authority.
## Debugging with MCP-Inspector
To debug the MCP-MEMORY-SERVICE using the [MCP-Inspector](https://modelcontextprotocol.io/docs/tools/inspector) tool, you can use the following command pattern:
```bash
MCP_MEMORY_CHROMA_PATH="/path/to/your/chroma_db" MCP_MEMORY_BACKUPS_PATH="/path/to/your/backups" npx @modelcontextprotocol/inspector uv --directory /path/to/mcp-memory-service run memory
```
Replace the paths with your specific directories:
- `/path/to/your/chroma_db`: Location where Chroma database files are stored
- `/path/to/your/backups`: Location for memory backups
- `/path/to/mcp-memory-service`: Directory containing the MCP-MEMORY-SERVICE code
For example:
```bash
MCP_MEMORY_CHROMA_PATH="~/Library/Mobile Documents/com~apple~CloudDocs/AI/claude-memory/chroma_db" MCP_MEMORY_BACKUPS_PATH="~/Library/Mobile Documents/com~apple~CloudDocs/AI/claude-memory/backups" npx @modelcontextprotocol/inspector uv --directory ~/Documents/GitHub/mcp-memory-service run memory
```
This command sets the required environment variables and runs the memory service through the inspector tool for debugging purposes.
```
--------------------------------------------------------------------------------
/scripts/pr/run_pyscn_analysis.sh:
--------------------------------------------------------------------------------
```bash
#!/bin/bash
# scripts/pr/run_pyscn_analysis.sh - Run pyscn comprehensive code quality analysis
#
# Usage:
# bash scripts/pr/run_pyscn_analysis.sh [--pr PR_NUMBER] [--threshold SCORE]
#
# Options:
# --pr PR_NUMBER Post results as PR comment (requires gh CLI)
# --threshold SCORE Minimum health score (default: 50, blocks below this)
#
# Examples:
# bash scripts/pr/run_pyscn_analysis.sh # Local analysis
# bash scripts/pr/run_pyscn_analysis.sh --pr 123 # Analyze and comment on PR #123
# bash scripts/pr/run_pyscn_analysis.sh --threshold 70 # Require health score ≥70
set -e
# Colors for output
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Parse arguments
PR_NUMBER=""
THRESHOLD=50 # Default: block if health score <50
while [[ $# -gt 0 ]]; do
case $1 in
--pr)
PR_NUMBER="$2"
shift 2
;;
--threshold)
THRESHOLD="$2"
shift 2
;;
*)
echo "Unknown option: $1"
echo "Usage: $0 [--pr PR_NUMBER] [--threshold SCORE]"
exit 1
;;
esac
done
# Check for pyscn
if ! command -v pyscn &> /dev/null; then
echo -e "${RED}❌ pyscn not found${NC}"
echo ""
echo "Install pyscn with:"
echo " pip install pyscn"
echo ""
echo "Repository: https://github.com/ludo-technologies/pyscn"
exit 1
fi
echo -e "${BLUE}=== pyscn Code Quality Analysis ===${NC}"
echo ""
# Create reports directory if needed
mkdir -p .pyscn/reports
# Run pyscn analysis
echo "Running pyscn analysis (this may take 30-60 seconds)..."
echo ""
# Generate timestamp for report
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
REPORT_FILE=".pyscn/reports/analyze_${TIMESTAMP}.html"
JSON_FILE=".pyscn/reports/analyze_${TIMESTAMP}.json"
# Run analysis (HTML report)
if pyscn analyze . --output "$REPORT_FILE" 2>&1 | tee /tmp/pyscn_output.log; then
echo -e "${GREEN}✓${NC} Analysis complete"
else
echo -e "${RED}❌ Analysis failed${NC}"
cat /tmp/pyscn_output.log
exit 1
fi
# Extract metrics from HTML report using grep/sed
# Note: This is a simple parser - adjust patterns if pyscn output format changes
HEALTH_SCORE=$(grep -o 'Health Score: [0-9]*' "$REPORT_FILE" | head -1 | grep -o '[0-9]*' || echo "0")
COMPLEXITY_SCORE=$(grep -o '<span class="score-value">[0-9]*</span>' "$REPORT_FILE" | head -1 | sed 's/<[^>]*>//g' || echo "0")
DEAD_CODE_SCORE=$(grep -o '<span class="score-value">[0-9]*</span>' "$REPORT_FILE" | sed -n '2p' | sed 's/<[^>]*>//g' || echo "0")
DUPLICATION_SCORE=$(grep -o '<span class="score-value">[0-9]*</span>' "$REPORT_FILE" | sed -n '3p' | sed 's/<[^>]*>//g' || echo "0")
# Extract detailed metrics
TOTAL_FUNCTIONS=$(grep -o '<div class="metric-value">[0-9]*</div>' "$REPORT_FILE" | head -1 | sed 's/<[^>]*>//g' || echo "0")
AVG_COMPLEXITY=$(grep -o '<div class="metric-value">[0-9.]*</div>' "$REPORT_FILE" | sed -n '3p' | sed 's/<[^>]*>//g' || echo "0")
MAX_COMPLEXITY=$(grep -o '<div class="metric-value">[0-9]*</div>' "$REPORT_FILE" | sed -n '3p' | sed 's/<[^>]*>//g' || echo "0")
DUPLICATION_PCT=$(grep -o '<div class="metric-value">[0-9.]*%</div>' "$REPORT_FILE" | head -1 | sed 's/<[^>]*>//g' || echo "0%")
DEAD_CODE_ISSUES=$(grep -o '<div class="metric-value">[0-9]*</div>' "$REPORT_FILE" | sed -n '4p' | sed 's/<[^>]*>//g' || echo "0")
ARCHITECTURE_VIOLATIONS=$(grep -o '<div class="metric-value">[0-9]*</div>' "$REPORT_FILE" | tail -2 | head -1 | sed 's/<[^>]*>//g' || echo "0")
echo ""
echo -e "${BLUE}=== Analysis Results ===${NC}"
echo ""
echo "📊 Overall Health Score: $HEALTH_SCORE/100"
echo ""
echo "Quality Metrics:"
echo " - Complexity: $COMPLEXITY_SCORE/100 (Avg: $AVG_COMPLEXITY, Max: $MAX_COMPLEXITY)"
echo " - Dead Code: $DEAD_CODE_SCORE/100 ($DEAD_CODE_ISSUES issues)"
echo " - Duplication: $DUPLICATION_SCORE/100 ($DUPLICATION_PCT duplication)"
echo ""
echo "📄 Report: $REPORT_FILE"
echo ""
# Determine status
EXIT_CODE=0
STATUS="✅ PASSED"
EMOJI="✅"
COLOR=$GREEN
if [ "$HEALTH_SCORE" -lt "$THRESHOLD" ]; then
STATUS="🔴 BLOCKED"
EMOJI="🔴"
COLOR=$RED
EXIT_CODE=1
elif [ "$HEALTH_SCORE" -lt 70 ]; then
STATUS="⚠️ WARNING"
EMOJI="⚠️"
COLOR=$YELLOW
fi
echo -e "${COLOR}${STATUS}${NC} - Health score: $HEALTH_SCORE (threshold: $THRESHOLD)"
echo ""
# Generate recommendations
RECOMMENDATIONS=""
if [ "$HEALTH_SCORE" -lt 50 ]; then
RECOMMENDATIONS="**🚨 Critical Action Required:**
- Health score below 50 is a release blocker
- Focus on top 5 highest complexity functions
- Remove dead code before proceeding
"
elif [ "$HEALTH_SCORE" -lt 70 ]; then
RECOMMENDATIONS="**⚠️ Improvement Recommended:**
- Plan refactoring sprint within 2 weeks
- Track high-complexity functions on project board
- Review duplication patterns for consolidation opportunities
"
fi
# Check for critical issues
CRITICAL_COMPLEXITY=""
if [ "$MAX_COMPLEXITY" -gt 10 ]; then
CRITICAL_COMPLEXITY="- ⚠️ Functions with complexity >10 detected (Max: $MAX_COMPLEXITY)
"
fi
CRITICAL_DUPLICATION=""
DUPLICATION_NUM=$(echo "$DUPLICATION_PCT" | sed 's/%//')
if (( $(echo "$DUPLICATION_NUM > 5.0" | bc -l) )); then
CRITICAL_DUPLICATION="- ⚠️ Code duplication above 5% threshold ($DUPLICATION_PCT)
"
fi
# Post to PR if requested
if [ -n "$PR_NUMBER" ]; then
if ! command -v gh &> /dev/null; then
echo -e "${YELLOW}⚠️ gh CLI not found, skipping PR comment${NC}"
else
echo "Posting results to PR #$PR_NUMBER..."
COMMENT_BODY="## ${EMOJI} pyscn Code Quality Analysis
**Health Score:** $HEALTH_SCORE/100
### Quality Metrics
| Metric | Score | Details |
|--------|-------|---------|
| 🔢 Complexity | $COMPLEXITY_SCORE/100 | Avg: $AVG_COMPLEXITY, Max: $MAX_COMPLEXITY |
| 💀 Dead Code | $DEAD_CODE_SCORE/100 | $DEAD_CODE_ISSUES issues |
| 📋 Duplication | $DUPLICATION_SCORE/100 | $DUPLICATION_PCT code duplication |
| 🏗️ Architecture | N/A | $ARCHITECTURE_VIOLATIONS violations |
### Status
$STATUS (Threshold: $THRESHOLD)
${CRITICAL_COMPLEXITY}${CRITICAL_DUPLICATION}${RECOMMENDATIONS}
### Full Report
View detailed analysis: [HTML Report](.pyscn/reports/analyze_${TIMESTAMP}.html)
---
<details>
<summary>📖 About pyscn</summary>
pyscn (Python Static Code Navigator) provides comprehensive static analysis including:
- Cyclomatic complexity analysis
- Dead code detection
- Code duplication (clone detection)
- Coupling metrics (CBO)
- Dependency graph analysis
- Architecture violation detection
Repository: https://github.com/ludo-technologies/pyscn
</details>"
echo "$COMMENT_BODY" | gh pr comment "$PR_NUMBER" --body-file -
echo -e "${GREEN}✓${NC} Posted comment to PR #$PR_NUMBER"
fi
fi
# Summary
echo ""
echo -e "${BLUE}=== Summary ===${NC}"
echo ""
if [ $EXIT_CODE -eq 0 ]; then
echo -e "${GREEN}✅ Quality checks passed${NC}"
echo ""
echo "Health score ($HEALTH_SCORE) meets threshold ($THRESHOLD)"
else
echo -e "${RED}❌ Quality checks failed${NC}"
echo ""
echo "Health score ($HEALTH_SCORE) below threshold ($THRESHOLD)"
echo ""
echo "Action required before merging:"
echo " 1. Review full report: open $REPORT_FILE"
echo " 2. Address high-complexity functions (complexity >10)"
echo " 3. Remove dead code ($DEAD_CODE_ISSUES issues)"
echo " 4. Reduce duplication where feasible"
echo ""
fi
exit $EXIT_CODE
```
--------------------------------------------------------------------------------
/src/mcp_memory_service/sync/exporter.py:
--------------------------------------------------------------------------------
```python
# Copyright 2024 Heinrich Krupp
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Memory export functionality for database synchronization.
"""
import json
import os
import platform
import logging
from datetime import datetime
from pathlib import Path
from typing import List, Dict, Any, Optional
from ..models.memory import Memory
from ..storage.base import MemoryStorage
logger = logging.getLogger(__name__)
class MemoryExporter:
"""
Exports memories from a storage backend to JSON format.
Preserves all metadata, timestamps, and adds source tracking for
multi-machine synchronization workflows.
"""
def __init__(self, storage: MemoryStorage):
"""
Initialize the exporter.
Args:
storage: The memory storage backend to export from
"""
self.storage = storage
self.machine_name = self._get_machine_name()
def _get_machine_name(self) -> str:
"""Get a unique machine identifier."""
# Try various methods to get a unique machine name
machine_name = (
os.environ.get('COMPUTERNAME') or # Windows
os.environ.get('HOSTNAME') or # Linux/macOS
platform.node() or # Platform module
'unknown-machine'
)
return machine_name.lower().replace(' ', '-')
async def export_to_json(
self,
output_file: Path,
include_embeddings: bool = False,
filter_tags: Optional[List[str]] = None
) -> Dict[str, Any]:
"""
Export all memories to JSON format.
Args:
output_file: Path to write the JSON export
include_embeddings: Whether to include embedding vectors (large)
filter_tags: Only export memories with these tags (optional)
Returns:
Export metadata dictionary with statistics
"""
logger.info(f"Starting memory export to {output_file}")
# Get all memories from storage
all_memories = await self._get_filtered_memories(filter_tags)
# Create export metadata
export_metadata = {
"source_machine": self.machine_name,
"export_timestamp": datetime.now().isoformat(),
"total_memories": len(all_memories),
"database_path": str(self.storage.db_path) if hasattr(self.storage, 'db_path') else 'unknown',
"platform": platform.system(),
"python_version": platform.python_version(),
"include_embeddings": include_embeddings,
"filter_tags": filter_tags,
"exporter_version": "5.0.1"
}
# Convert memories to exportable format
exported_memories = []
for memory in all_memories:
memory_dict = await self._memory_to_dict(memory, include_embeddings)
exported_memories.append(memory_dict)
# Create final export structure
export_data = {
"export_metadata": export_metadata,
"memories": exported_memories
}
# Write to JSON file
output_file.parent.mkdir(parents=True, exist_ok=True)
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(export_data, f, indent=2, ensure_ascii=False)
# Log success
file_size = output_file.stat().st_size
logger.info(f"Export completed: {len(all_memories)} memories written to {output_file}")
logger.info(f"File size: {file_size / 1024 / 1024:.2f} MB")
# Return summary statistics
return {
"success": True,
"exported_count": len(all_memories),
"output_file": str(output_file),
"file_size_bytes": file_size,
"source_machine": self.machine_name,
"export_timestamp": export_metadata["export_timestamp"]
}
async def _get_filtered_memories(self, filter_tags: Optional[List[str]]) -> List[Memory]:
"""Get memories with optional tag filtering."""
if not filter_tags:
# Get all memories
return await self.storage.get_all_memories()
# Filter by tags if specified
filtered_memories = []
all_memories = await self.storage.get_all_memories()
for memory in all_memories:
if any(tag in memory.tags for tag in filter_tags):
filtered_memories.append(memory)
return filtered_memories
async def _memory_to_dict(self, memory: Memory, include_embeddings: bool) -> Dict[str, Any]:
"""Convert a Memory object to a dictionary for JSON export."""
memory_dict = {
"content": memory.content,
"content_hash": memory.content_hash,
"tags": memory.tags,
"created_at": memory.created_at, # Preserve original timestamp
"updated_at": memory.updated_at,
"memory_type": memory.memory_type,
"metadata": memory.metadata or {},
"export_source": self.machine_name
}
# Add embedding if requested and available
if include_embeddings and hasattr(memory, 'embedding') and memory.embedding:
memory_dict["embedding"] = memory.embedding.tolist() if hasattr(memory.embedding, 'tolist') else memory.embedding
return memory_dict
async def export_summary(self) -> Dict[str, Any]:
"""
Get a summary of what would be exported without actually exporting.
Returns:
Summary statistics about the memories in the database
"""
all_memories = await self.storage.get_all_memories()
# Analyze tags
tag_counts = {}
memory_types = {}
date_range = {"earliest": None, "latest": None}
for memory in all_memories:
# Count tags
for tag in memory.tags:
tag_counts[tag] = tag_counts.get(tag, 0) + 1
# Count memory types
memory_types[memory.memory_type] = memory_types.get(memory.memory_type, 0) + 1
# Track date range
if date_range["earliest"] is None or memory.created_at < date_range["earliest"]:
date_range["earliest"] = memory.created_at
if date_range["latest"] is None or memory.created_at > date_range["latest"]:
date_range["latest"] = memory.created_at
return {
"total_memories": len(all_memories),
"machine_name": self.machine_name,
"tag_counts": dict(sorted(tag_counts.items(), key=lambda x: x[1], reverse=True)),
"memory_types": memory_types,
"date_range": date_range,
"estimated_json_size_mb": len(all_memories) * 0.001 # Rough estimate
}
```
--------------------------------------------------------------------------------
/scripts/testing/test_memory_api.py:
--------------------------------------------------------------------------------
```python
#!/usr/bin/env python3
# Copyright 2024 Heinrich Krupp
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Test script for memory CRUD operations via HTTP API.
This script tests the basic memory operations to ensure they work correctly.
"""
import asyncio
import aiohttp
import json
import time
from typing import Dict, Any
BASE_URL = "http://localhost:8000"
async def test_memory_crud():
"""Test the complete CRUD workflow for memories."""
async with aiohttp.ClientSession() as session:
print("Testing Memory CRUD Operations")
print("=" * 50)
# Test 1: Health check
print("\n[1] Testing health check...")
try:
async with session.get(f"{BASE_URL}/api/health") as resp:
if resp.status == 200:
health = await resp.json()
print(f"[PASS] Health check passed: {health['status']}")
else:
print(f"[FAIL] Health check failed: {resp.status}")
return
except Exception as e:
print(f"[FAIL] Cannot connect to server: {e}")
print("NOTE: Make sure the server is running with: python scripts/run_http_server.py")
return
# Test 2: Store a memory
print("\n[2] Testing memory storage...")
test_memory = {
"content": "This is a test memory for CRUD operations",
"tags": ["test", "crud", "api"],
"memory_type": "test",
"metadata": {"test_run": time.time(), "importance": "high"}
}
try:
async with session.post(
f"{BASE_URL}/api/memories",
json=test_memory,
headers={"Content-Type": "application/json"}
) as resp:
if resp.status == 200:
result = await resp.json()
if result["success"]:
content_hash = result["content_hash"]
print(f"[PASS] Memory stored successfully")
print(f" Content hash: {content_hash[:16]}...")
print(f" Message: {result['message']}")
else:
print(f"[FAIL] Memory storage failed: {result['message']}")
return
else:
print(f"[FAIL] Memory storage failed: {resp.status}")
error = await resp.text()
print(f" Error: {error}")
return
except Exception as e:
print(f"[FAIL] Memory storage error: {e}")
return
# Test 3: List memories
print("\n[3] Testing memory listing...")
try:
async with session.get(f"{BASE_URL}/api/memories?page=1&page_size=5") as resp:
if resp.status == 200:
result = await resp.json()
memories = result["memories"]
print(f"✅ Retrieved {len(memories)} memories")
print(f" Total memories: {result['total']}")
print(f" Page: {result['page']}, Has more: {result['has_more']}")
if memories:
print(f" First memory preview: {memories[0]['content'][:50]}...")
else:
print(f"❌ Memory listing failed: {resp.status}")
error = await resp.text()
print(f" Error: {error}")
except Exception as e:
print(f"❌ Memory listing error: {e}")
# Test 4: Get specific memory
print("\n4️⃣ Testing specific memory retrieval...")
try:
async with session.get(f"{BASE_URL}/api/memories/{content_hash}") as resp:
if resp.status == 200:
memory = await resp.json()
print(f"✅ Retrieved specific memory")
print(f" Content: {memory['content'][:50]}...")
print(f" Tags: {memory['tags']}")
print(f" Type: {memory['memory_type']}")
elif resp.status == 404:
print(f"⚠️ Memory not found (this might be expected if get-by-hash isn't implemented)")
else:
print(f"❌ Memory retrieval failed: {resp.status}")
error = await resp.text()
print(f" Error: {error}")
except Exception as e:
print(f"❌ Memory retrieval error: {e}")
# Test 5: Filter by tag
print("\n5️⃣ Testing tag filtering...")
try:
async with session.get(f"{BASE_URL}/api/memories?tag=test") as resp:
if resp.status == 200:
result = await resp.json()
memories = result["memories"]
print(f"✅ Retrieved {len(memories)} memories with tag 'test'")
if memories:
for memory in memories[:2]: # Show first 2
print(f" - {memory['content'][:40]}... (tags: {memory['tags']})")
else:
print(f"❌ Tag filtering failed: {resp.status}")
except Exception as e:
print(f"❌ Tag filtering error: {e}")
# Test 6: Delete memory
print("\n6️⃣ Testing memory deletion...")
try:
async with session.delete(f"{BASE_URL}/api/memories/{content_hash}") as resp:
if resp.status == 200:
result = await resp.json()
if result["success"]:
print(f"✅ Memory deleted successfully")
print(f" Message: {result['message']}")
else:
print(f"❌ Memory deletion failed: {result['message']}")
else:
print(f"❌ Memory deletion failed: {resp.status}")
error = await resp.text()
print(f" Error: {error}")
except Exception as e:
print(f"❌ Memory deletion error: {e}")
# Test 7: Verify deletion
print("\n7️⃣ Verifying memory deletion...")
try:
async with session.get(f"{BASE_URL}/api/memories/{content_hash}") as resp:
if resp.status == 404:
print(f"✅ Memory successfully deleted (404 as expected)")
elif resp.status == 200:
print(f"⚠️ Memory still exists after deletion")
else:
print(f"❓ Unexpected response: {resp.status}")
except Exception as e:
print(f"❌ Deletion verification error: {e}")
print("\n" + "=" * 50)
print("🎉 Memory CRUD testing completed!")
if __name__ == "__main__":
asyncio.run(test_memory_crud())
```
--------------------------------------------------------------------------------
/docs/first-time-setup.md:
--------------------------------------------------------------------------------
```markdown
# First-Time Setup Guide
This guide explains what to expect when running MCP Memory Service for the first time.
## 🎯 What to Expect on First Run
When you start the MCP Memory Service for the first time, you'll see several warnings and messages. **This is completely normal behavior** as the service initializes and downloads necessary components.
## 📋 Normal First-Time Warnings
### 1. Snapshots Directory Warning
```
WARNING:mcp_memory_service.storage.sqlite_vec:Failed to load from cache: No snapshots directory
```
**What it means:**
- The service is checking for previously downloaded embedding models
- On first run, no cache exists yet, so this warning appears
- The service will automatically download the model
**This is normal:** ✅ Expected on first run
### 2. TRANSFORMERS_CACHE Warning
```
WARNING: Using TRANSFORMERS_CACHE is deprecated
```
**What it means:**
- This is an informational warning from the Hugging Face library
- It doesn't affect the service functionality
- The service handles caching internally
**This is normal:** ✅ Can be safely ignored
### 3. Model Download Progress
```
Downloading model 'all-MiniLM-L6-v2'...
```
**What it means:**
- The service is downloading the embedding model (approximately 25MB)
- This happens only once on first setup
- Download time: 1-2 minutes on average internet connection
**This is normal:** ✅ One-time download
## 🚦 Success Indicators
After successful first-time setup, you should see:
```
INFO: SQLite-vec storage initialized successfully with embedding dimension: 384
INFO: Memory service started on port 8443
INFO: Ready to accept connections
```
## 📊 First-Time Setup Timeline
| Step | Duration | What's Happening |
|------|----------|-----------------|
| 1. Service Start | Instant | Loading configuration |
| 2. Cache Check | 1-2 seconds | Checking for existing models |
| 3. Model Download | 1-2 minutes | Downloading embedding model (~25MB) |
| 4. Model Loading | 5-10 seconds | Loading model into memory |
| 5. Database Init | 2-3 seconds | Creating database structure |
| 6. Ready | - | Service is ready to use |
**Total first-time setup:** 2-3 minutes
## 🔄 Subsequent Runs
After the first successful run:
- No download warnings will appear
- Model loads from cache (5-10 seconds)
- Service starts much faster (10-15 seconds total)
## 🐍 Python 3.13 Compatibility
### Known Issues
Python 3.13 users may encounter installation issues with **sqlite-vec** due to missing pre-built wheels. The installer now includes automatic fallback methods:
1. **Automatic Retry Logic**: Tries multiple installation strategies
2. **Source Building**: Attempts to build from source if wheels unavailable
3. **GitHub Installation**: Falls back to installing directly from repository
4. **Backend Switching**: Option to switch to ChromaDB if sqlite-vec fails
### Recommended Solutions
If you encounter sqlite-vec installation failures on Python 3.13:
**Option 1: Use Python 3.12 (Recommended)**
```bash
# macOS
brew install [email protected]
python3.12 -m venv .venv
source .venv/bin/activate
python install.py
# Ubuntu/Linux
sudo apt install python3.12 python3.12-venv
python3.12 -m venv .venv
source .venv/bin/activate
python install.py
```
**Option 2: Use ChromaDB Backend**
```bash
python install.py --storage-backend chromadb
```
**Option 3: Manual sqlite-vec Installation**
```bash
# Try building from source
pip install --no-binary :all: sqlite-vec
# Or install from GitHub
pip install git+https://github.com/asg017/sqlite-vec.git#subdirectory=python
```
## 🍎 macOS SQLite Extension Issues
### Problem: `AttributeError: 'sqlite3.Connection' object has no attribute 'enable_load_extension'`
This error occurs on **macOS with system Python** because it's not compiled with SQLite extension support.
**Why this happens:**
- macOS system Python lacks `--enable-loadable-sqlite-extensions`
- The bundled SQLite library doesn't support loadable extensions
- This is a security-focused default configuration
**Solutions:**
**Option 1: Homebrew Python (Recommended)**
```bash
# Install Python via Homebrew (includes extension support)
brew install python
hash -r # Refresh command cache
python3 --version # Verify you're using Homebrew Python
# Then install MCP Memory Service
python3 install.py
```
**Option 2: pyenv with Extension Support**
```bash
# Install pyenv if not already installed
brew install pyenv
# Install Python with extension support
PYTHON_CONFIGURE_OPTS="--enable-loadable-sqlite-extensions" pyenv install 3.12.0
pyenv local 3.12.0
# Verify extension support
python3 -c "import sqlite3; conn=sqlite3.connect(':memory:'); conn.enable_load_extension(True); print('Extensions supported!')"
```
**Option 3: Use ChromaDB Backend**
```bash
# ChromaDB doesn't require SQLite extensions
python3 install.py --storage-backend chromadb
```
### Verification
Check if your Python supports extensions:
```bash
python3 -c "
import sqlite3
conn = sqlite3.connect(':memory:')
print('✅ Extension support available' if hasattr(conn, 'enable_load_extension') else '❌ No extension support')
"
```
## 🐧 Ubuntu/Linux Specific Notes
For Ubuntu 24 and other Linux distributions:
### Prerequisites
```bash
# System dependencies
sudo apt update
sudo apt install python3.10 python3.10-venv python3.10-dev python3-pip
sudo apt install build-essential libblas3 liblapack3 liblapack-dev libblas-dev gfortran
```
### Recommended Setup
```bash
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install the service
python install.py
# Start the service
uv run memory server
```
## 🔧 Troubleshooting First-Time Issues
### Issue: Download Fails
**Solution:**
- Check internet connection
- Verify firewall/proxy settings
- Clear cache and retry: `rm -rf ~/.cache/huggingface`
### Issue: "No module named 'sentence_transformers'"
**Solution:**
```bash
pip install sentence-transformers torch
```
### Issue: Permission Denied
**Solution:**
```bash
# Fix permissions
chmod +x scripts/*.sh
sudo chown -R $USER:$USER ~/.mcp_memory_service/
```
### Issue: Service Doesn't Start After Download
**Solution:**
1. Check logs: `uv run memory server --debug`
2. Verify installation: `python scripts/verify_environment.py`
3. Restart with clean state:
```bash
rm -rf ~/.mcp_memory_service
uv run memory server
```
## ✅ Verification
To verify successful setup:
```bash
# Check service health
curl -k https://localhost:8443/api/health
# Or using the CLI
uv run memory health
```
Expected response:
```json
{
"status": "healthy",
"storage_backend": "sqlite_vec",
"model_loaded": true
}
```
## 🎉 Setup Complete!
Once you see the success indicators and the warnings have disappeared on subsequent runs, your MCP Memory Service is fully operational and ready to use!
### Next Steps:
- [Configure Claude Desktop](../README.md#claude-desktop-integration)
- [Store your first memory](../README.md#basic-usage)
- [Explore the API](https://github.com/doobidoo/mcp-memory-service/wiki)
## 📝 Notes
- The model download is a one-time operation
- Downloaded models are cached in `~/.cache/huggingface/`
- The service creates a database in `~/.mcp_memory_service/`
- All warnings shown during first-time setup are expected behavior
- If you see different errors (not warnings), check the [Troubleshooting Guide](troubleshooting/general.md)
---
Remember: **First-time warnings are normal!** The service is working correctly and setting itself up for optimal performance.
```
--------------------------------------------------------------------------------
/tests/performance/test_hybrid_live.py:
--------------------------------------------------------------------------------
```python
#!/usr/bin/env python3
"""
Live performance test of the hybrid storage backend implementation.
Demonstrates performance, functionality, and sync capabilities under load.
"""
import asyncio
import sys
import time
import tempfile
import os
from pathlib import Path
# Add src to path for standalone execution
sys.path.insert(0, str(Path(__file__).parent.parent.parent / 'src'))
from mcp_memory_service.storage.hybrid import HybridMemoryStorage
from mcp_memory_service.models.memory import Memory
import hashlib
async def test_hybrid_storage():
"""Test the hybrid storage implementation with live demonstrations."""
print("🚀 Testing Hybrid Storage Backend")
print("=" * 50)
# Create a temporary SQLite database for testing
with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as tmp_file:
db_path = tmp_file.name
try:
# Initialize hybrid storage (without Cloudflare for this demo)
print("📍 Step 1: Initializing Hybrid Storage")
storage = HybridMemoryStorage(
sqlite_db_path=db_path,
embedding_model="all-MiniLM-L6-v2",
cloudflare_config=None, # Will operate in SQLite-only mode
sync_interval=30, # Short interval for demo
batch_size=5
)
print(" Initializing storage backend...")
await storage.initialize()
print(f" ✅ Storage initialized")
print(f" 📊 Primary: {storage.primary.__class__.__name__}")
print(f" 📊 Secondary: {storage.secondary.__class__.__name__ if storage.secondary else 'None (SQLite-only mode)'}")
print(f" 📊 Sync Service: {'Running' if storage.sync_service and storage.sync_service.is_running else 'Disabled'}")
print()
# Test 1: Performance measurement
print("📍 Step 2: Performance Test")
memories_to_test = []
for i in range(5):
content = f"Performance test memory #{i+1} - testing hybrid storage speed"
content_hash = hashlib.sha256(content.encode()).hexdigest()
memory = Memory(
content=content,
content_hash=content_hash,
tags=["hybrid", "test", f"batch_{i}"],
memory_type="performance_test",
metadata={"test_batch": i, "test_type": "performance"},
created_at=time.time()
)
memories_to_test.append(memory)
# Measure write performance
print(" Testing write performance...")
write_times = []
for i, memory in enumerate(memories_to_test):
start_time = time.time()
success, message = await storage.store(memory)
duration = time.time() - start_time
write_times.append(duration)
if success:
print(f" ✅ Write #{i+1}: {duration*1000:.1f}ms")
else:
print(f" ❌ Write #{i+1} failed: {message}")
avg_write_time = sum(write_times) / len(write_times)
print(f" 📊 Average write time: {avg_write_time*1000:.1f}ms")
print()
# Test 2: Read performance
print("📍 Step 3: Read Performance Test")
read_times = []
for i in range(3):
start_time = time.time()
results = await storage.retrieve("performance test", n_results=5)
duration = time.time() - start_time
read_times.append(duration)
print(f" ✅ Read #{i+1}: {duration*1000:.1f}ms ({len(results)} results)")
avg_read_time = sum(read_times) / len(read_times)
print(f" 📊 Average read time: {avg_read_time*1000:.1f}ms")
print()
# Test 3: Different operations
print("📍 Step 4: Testing Various Operations")
# Search by tags
start_time = time.time()
tagged_memories = await storage.search_by_tags(["hybrid"])
tag_search_time = time.time() - start_time
print(f" ✅ Tag search: {tag_search_time*1000:.1f}ms ({len(tagged_memories)} results)")
# Get stats
start_time = time.time()
stats = await storage.get_stats()
stats_time = time.time() - start_time
print(f" ✅ Stats retrieval: {stats_time*1000:.1f}ms")
print(f" - Backend: {stats.get('storage_backend')}")
print(f" - Total memories: {stats.get('total_memories', 0)}")
print(f" - Sync enabled: {stats.get('sync_enabled', False)}")
# Test delete
if memories_to_test:
test_memory = memories_to_test[0]
start_time = time.time()
success, message = await storage.delete(test_memory.content_hash)
delete_time = time.time() - start_time
print(f" ✅ Delete operation: {delete_time*1000:.1f}ms ({'Success' if success else 'Failed'})")
print()
# Test 4: Concurrent operations
print("📍 Step 5: Concurrent Operations Test")
async def store_memory(content_suffix):
content = f"Concurrent test memory {content_suffix}"
content_hash = hashlib.sha256(content.encode()).hexdigest()
memory = Memory(
content=content,
content_hash=content_hash,
tags=["concurrent", "hybrid"],
memory_type="concurrent_test",
metadata={"test_id": content_suffix},
created_at=time.time()
)
return await storage.store(memory)
start_time = time.time()
concurrent_tasks = [store_memory(i) for i in range(10)]
results = await asyncio.gather(*concurrent_tasks)
concurrent_time = time.time() - start_time
successful_ops = sum(1 for success, _ in results if success)
print(f" ✅ Concurrent operations: {concurrent_time*1000:.1f}ms")
print(f" - Operations: 10 concurrent stores")
print(f" - Successful: {successful_ops}/10")
print(f" - Avg per operation: {(concurrent_time/10)*1000:.1f}ms")
print()
# Final stats
print("📍 Step 6: Final Statistics")
final_stats = await storage.get_stats()
print(f" 📊 Total memories stored: {final_stats.get('total_memories', 0)}")
print(f" 📊 Storage backend: {final_stats.get('storage_backend')}")
if storage.sync_service:
sync_status = await storage.sync_service.get_sync_status()
print(f" 📊 Sync queue size: {sync_status.get('queue_size', 0)}")
print(f" 📊 Operations processed: {sync_status.get('stats', {}).get('operations_processed', 0)}")
print()
print("🎉 Hybrid Storage Test Complete!")
print("=" * 50)
# Performance summary
print("📊 PERFORMANCE SUMMARY:")
print(f" • Average Write: {avg_write_time*1000:.1f}ms")
print(f" • Average Read: {avg_read_time*1000:.1f}ms")
print(f" • Tag Search: {tag_search_time*1000:.1f}ms")
print(f" • Stats Query: {stats_time*1000:.1f}ms")
print(f" • Delete Op: {delete_time*1000:.1f}ms")
print(f" • Concurrent: {(concurrent_time/10)*1000:.1f}ms per op")
# Cleanup
await storage.close()
finally:
# Clean up temp file
if os.path.exists(db_path):
os.unlink(db_path)
if __name__ == "__main__":
print("🚀 Hybrid Storage Live Demo")
print("Testing the new hybrid backend implementation...")
print()
# Run the async test
asyncio.run(test_hybrid_storage())
```
--------------------------------------------------------------------------------
/.github/workflows/publish-and-test.yml:
--------------------------------------------------------------------------------
```yaml
name: Publish and Test (Tags)
on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:
jobs:
test-uvx-compatibility:
runs-on: ubuntu-latest
name: Test uvx compatibility
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.cargo/env
- name: Install package locally
run: |
source $HOME/.cargo/env
uv pip install --system -e .
- name: Test entry point
run: |
python -c "import mcp_memory_service.server; print('✓ Package can be imported')"
python -m mcp_memory_service.server --version
- name: Test uvx functionality
run: |
source $HOME/.cargo/env
# uvx is now part of uv itself, no separate installation needed
uv --version
# Build wheel for uvx testing
uv build
# Test if uvx command is available
which uvx || echo "uvx command provided by uv"
# Test package structure compatibility
echo "✓ Package structure compatible with uvx"
test-docker-build:
runs-on: ubuntu-latest
name: Test Docker build
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Debug - Check required files
run: |
echo "=== Checking required files for Docker build ==="
echo "Dockerfile exists:" && ls -la tools/docker/Dockerfile
echo "Source directory exists:" && ls -la src/
echo "Entrypoint scripts exist:" && ls -la tools/docker/docker-entrypoint*.sh
echo "Utils scripts exist:" && ls -la scripts/utils/
echo "pyproject.toml exists:" && ls -la pyproject.toml
echo "uv.lock exists:" && ls -la uv.lock
- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./tools/docker/Dockerfile
platforms: linux/amd64
push: false
load: true
tags: mcp-memory-service:test
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
SKIP_MODEL_DOWNLOAD=true
- name: Test Docker image
run: |
# Test image can be created (override entrypoint to run python directly)
docker run --rm --entrypoint="" mcp-memory-service:test python -c "print('✓ Docker image works')"
# Test that the server can show help
docker run --rm mcp-memory-service:test --help > /dev/null && echo "✓ Server help works"
publish-docker:
needs: [test-uvx-compatibility, test-docker-build]
runs-on: ubuntu-latest
name: Publish Docker image
if: github.event_name != 'pull_request'
permissions:
contents: read
packages: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Debug - Check required files for GHCR build
run: |
echo "=== Checking required files for GHCR Docker build ==="
echo "Dockerfile exists:" && ls -la tools/docker/Dockerfile
echo "Source directory exists:" && ls -la src/
echo "Entrypoint scripts exist:" && ls -la tools/docker/docker-entrypoint*.sh
echo "Utils scripts exist:" && ls -la scripts/utils/
echo "pyproject.toml exists:" && ls -la pyproject.toml
echo "uv.lock exists:" && ls -la uv.lock
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/doobidoo/mcp-memory-service
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./tools/docker/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
SKIP_MODEL_DOWNLOAD=true
outputs: type=registry
publish-pypi:
needs: [test-uvx-compatibility, test-docker-build]
runs-on: ubuntu-latest
name: Publish to PyPI
if: github.event_name != 'pull_request'
permissions:
id-token: write
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install build dependencies
run: |
python -m pip install --upgrade pip
python -m pip install build twine
- name: Build package
run: python -m build
- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: |
twine upload dist/*
update-documentation:
needs: [publish-docker, publish-pypi]
runs-on: ubuntu-latest
name: Update documentation
if: github.event_name != 'pull_request'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Update README with GitHub Container Registry info
run: |
echo "Docker image published successfully!" >> docker-publish.log
echo "Available at: ghcr.io/doobidoo/mcp-memory-service" >> docker-publish.log
- name: Create/update installation docs
run: |
mkdir -p docs/installation
cat > docs/installation/github-container-registry.md << 'EOF'
# GitHub Container Registry Installation
The MCP Memory Service is now available on GitHub Container Registry for easy installation.
## Quick Start
```bash
# Pull the latest image
docker pull ghcr.io/doobidoo/mcp-memory-service:latest
# Run with default settings
docker run -d -p 8000:8000 \
-v $(pwd)/data/chroma_db:/app/chroma_db \
-v $(pwd)/data/backups:/app/backups \
ghcr.io/doobidoo/mcp-memory-service:latest
# Run in standalone mode
docker run -d -p 8000:8000 \
-e MCP_STANDALONE_MODE=1 \
-v $(pwd)/data/chroma_db:/app/chroma_db \
-v $(pwd)/data/backups:/app/backups \
ghcr.io/doobidoo/mcp-memory-service:latest
```
## Available Tags
- `latest` - Latest stable release
- `main` - Latest development version
- `v*.*.*` - Specific version tags
## uvx Installation
You can also install using uvx (included with uv):
```bash
# Install uv if not already installed
pip install uv
# Or use the installer script:
# curl -LsSf https://astral.sh/uv/install.sh | sh
# Install and run the memory service with uvx
uvx mcp-memory-service
```
EOF
```
--------------------------------------------------------------------------------
/docs/research/code-execution-interface-summary.md:
--------------------------------------------------------------------------------
```markdown
# Code Execution Interface - Executive Summary
## Issue #206 Implementation Guide
**TL;DR:** Implement Python code API to reduce token consumption by 90-95% while maintaining full backward compatibility. Start with session hooks (75% reduction, 109M+ tokens saved annually).
---
## The Problem
Current MCP tool-based architecture consumes excessive tokens:
- **Session hooks:** 3,600-9,600 tokens per session start
- **Search operations:** 2,625 tokens for 5 results
- **Document ingestion:** 57,400 tokens for 50 PDFs
- **Annual waste:** 17-379 million tokens across user base
## The Solution
Replace verbose MCP tool calls with direct Python code execution:
```python
# Before (625 tokens)
result = await mcp.call_tool('retrieve_memory', {
'query': 'architecture decisions',
'limit': 5,
'similarity_threshold': 0.7
})
# After (25 tokens)
from mcp_memory_service.api import search
results = search('architecture decisions', limit=5)
```
## Key Components
### 1. Compact Data Types (91% size reduction)
```python
# CompactMemory: 73 tokens vs 820 tokens for full Memory
class CompactMemory(NamedTuple):
hash: str # 8-char hash
preview: str # First 200 chars
tags: tuple[str] # Immutable tags
created: float # Unix timestamp
score: float # Relevance score
```
### 2. Simple API Functions
```python
from mcp_memory_service.api import search, store, health
# Search (20 tokens)
results = search("query", limit=5)
# Store (15 tokens)
hash = store("content", tags=['note'])
# Health (5 tokens)
info = health()
```
### 3. Hook Integration
```javascript
// Node.js hook with fallback
try {
// Code execution (fast, efficient)
const output = execSync('python -c "from mcp_memory_service.api import search; ..."');
memories = parseCompactResults(output);
} catch (error) {
// Fallback to MCP tools
memories = await mcpClient.retrieve_memory({...});
}
```
## Expected Impact
| Metric | Current | Target | Improvement |
|--------|---------|--------|-------------|
| Session hooks | 3,600-9,600 tokens | 900-2,400 | **75% reduction** |
| Search (5 results) | 2,625 tokens | 385 | **85% reduction** |
| Store memory | 150 tokens | 15 | **90% reduction** |
| Annual savings (10 users) | - | 109.5M tokens | **$16.43/year** |
| Annual savings (100 users) | - | 2.19B tokens | **$328.50/year** |
## Implementation Timeline
```
Week 1-2: Core Infrastructure
├─ Compact types (CompactMemory, CompactSearchResult)
├─ API functions (search, store, health)
├─ Tests and benchmarks
└─ Documentation
Week 3: Session Hook Migration (HIGHEST PRIORITY)
├─ Update session-start.js
├─ Add fallback mechanism
├─ Validate 75% reduction
└─ Deploy to beta
Week 4-5: Search Operations
├─ Mid-conversation hooks
├─ Topic-change hooks
└─ Document best practices
Week 6+: Polish & Extensions
```
## Technical Architecture
### Filesystem Structure
```
src/mcp_memory_service/
├── api/ # NEW: Code execution interface
│ ├── __init__.py # Public API exports
│ ├── compact.py # Compact result types
│ ├── search.py # Search operations
│ ├── storage.py # Storage operations
│ └── utils.py # Utilities
├── models/
│ └── compact.py # NEW: CompactMemory types
└── server.py # EXISTING: MCP server (unchanged)
```
### Key Design Decisions
1. **NamedTuple for Compact Types**
- ✅ Fast (C-based), immutable, type-safe
- ✅ 60-90% size reduction
- ✅ Clear field names
2. **Sync Wrappers**
- ✅ Hide asyncio complexity
- ✅ Connection reuse
- ✅ <10ms overhead
3. **Backward Compatibility**
- ✅ MCP tools continue working
- ✅ Gradual opt-in migration
- ✅ Zero breaking changes
## Validation from Research
### Industry Alignment
- ✅ **Anthropic (Nov 2025):** Official MCP code execution announcement
- ✅ **CodeAgents Framework:** 70%+ token reduction demonstrated
- ✅ **mcp-python-interpreter:** Proven code execution patterns
### Performance Benchmarks
- ✅ **NamedTuple:** 8% faster creation, fastest access
- ✅ **Compact types:** 85-91% token reduction measured
- ✅ **Real-world savings:** 109M+ tokens annually (conservative)
## Risk Assessment
| Risk | Severity | Mitigation |
|------|----------|------------|
| Breaking changes | HIGH | Parallel operation, fallback mechanism |
| Performance degradation | MEDIUM | Benchmarks show <5ms overhead |
| Async/sync mismatch | LOW | Sync wrappers with asyncio.run() |
| Connection management | LOW | Global instance with reuse |
| Error handling | LOW | Expand function for debugging |
**Overall Risk:** LOW - Incremental approach with proven patterns
## Success Criteria
### Phase 1 (Core Infrastructure)
- ✅ API functions work in sync context
- ✅ Token reduction ≥85% measured
- ✅ Performance overhead <5ms
- ✅ Unit tests ≥90% coverage
### Phase 2 (Session Hooks)
- ✅ Token reduction ≥75% in production
- ✅ Hook execution <500ms
- ✅ Fallback mechanism validates
- ✅ Zero user-reported issues
### Phase 3 (Search Operations)
- ✅ Token reduction ≥90%
- ✅ Migration guide complete
- ✅ Community feedback positive
## Next Steps
### Immediate Actions (This Week)
1. Create `src/mcp_memory_service/api/` directory structure
2. Implement `CompactMemory` and `CompactSearchResult` types
3. Write `search()`, `store()`, `health()` functions
4. Add unit tests with token benchmarks
### Priority Tasks
1. **Session hook migration** - Highest impact (3,600 → 900 tokens)
2. **Documentation** - API reference with examples
3. **Beta testing** - Validate with real users
4. **Metrics collection** - Measure actual token savings
## Recommended Reading
### Full Research Document
- `docs/research/code-execution-interface-implementation.md`
- Comprehensive analysis (10,000+ words)
- Detailed code examples
- Industry research findings
- Complete implementation guide
### Key Sections
1. **Architecture Recommendations** (Section 3)
2. **Implementation Examples** (Section 4)
3. **Migration Approach** (Section 7)
4. **Success Metrics** (Section 8)
---
## Quick Reference
### Token Savings Calculator
```python
# Current MCP approach
tool_schema = 125 tokens
full_memory = 820 tokens per result
total = tool_schema + (full_memory * num_results)
# Example: 5 results
current = 125 + (820 * 5) = 4,225 tokens
# Code execution approach
import_cost = 10 tokens (once)
compact_memory = 73 tokens per result
total = import_cost + (compact_memory * num_results)
# Example: 5 results
new = 10 + (73 * 5) = 375 tokens
# Reduction
reduction = (4225 - 375) / 4225 * 100 = 91.1%
```
### Common Patterns
```python
# Pattern 1: Simple search
from mcp_memory_service.api import search
results = search("query", limit=5)
for m in results.memories:
print(f"{m.hash}: {m.preview[:50]}")
# Pattern 2: Tag filtering
from mcp_memory_service.api import search_by_tag
results = search_by_tag(['architecture', 'decision'], limit=10)
# Pattern 3: Store with fallback
from mcp_memory_service.api import store
try:
hash = store("content", tags=['note'])
print(f"Stored: {hash}")
except Exception as e:
# Fallback to MCP tool
await mcp_client.store_memory({...})
# Pattern 4: Health check
from mcp_memory_service.api import health
info = health()
if info.ready:
print(f"Backend: {info.backend}, Count: {info.count}")
```
---
**Document Version:** 1.0
**Last Updated:** November 6, 2025
**Status:** Ready for Implementation
**Estimated Effort:** 2-3 weeks (Phase 1-2)
**ROI:** 109M+ tokens saved annually (conservative)
```
--------------------------------------------------------------------------------
/scripts/pr/quality_gate.sh:
--------------------------------------------------------------------------------
```bash
#!/bin/bash
# scripts/pr/quality_gate.sh - Run all quality checks before PR review
#
# Usage: bash scripts/pr/quality_gate.sh <PR_NUMBER> [--with-pyscn]
# Example: bash scripts/pr/quality_gate.sh 123
# Example: bash scripts/pr/quality_gate.sh 123 --with-pyscn # Comprehensive analysis
set -e
PR_NUMBER=$1
RUN_PYSCN=false
# Parse arguments
shift # Remove PR_NUMBER from arguments
while [[ $# -gt 0 ]]; do
case $1 in
--with-pyscn)
RUN_PYSCN=true
shift
;;
*)
echo "Unknown option: $1"
echo "Usage: $0 <PR_NUMBER> [--with-pyscn]"
exit 1
;;
esac
done
if [ -z "$PR_NUMBER" ]; then
echo "Usage: $0 <PR_NUMBER> [--with-pyscn]"
exit 1
fi
if ! command -v gh &> /dev/null; then
echo "Error: GitHub CLI (gh) is not installed"
exit 1
fi
if ! command -v gemini &> /dev/null; then
echo "Error: Gemini CLI is not installed"
exit 1
fi
echo "=== PR Quality Gate for #$PR_NUMBER ==="
echo ""
exit_code=0
warnings=()
critical_issues=()
# Get changed Python files
echo "Fetching changed files..."
changed_files=$(gh pr diff $PR_NUMBER --name-only | grep '\.py$' || echo "")
if [ -z "$changed_files" ]; then
echo "No Python files changed in this PR."
exit 0
fi
echo "Changed Python files:"
echo "$changed_files"
echo ""
# Check 1: Code complexity
echo "=== Check 1: Code Complexity ==="
echo "$changed_files" | while IFS= read -r file; do
if [ -z "$file" ]; then
continue
fi
if [ ! -f "$file" ]; then
echo "Skipping $file (file not found in working directory)"
continue
fi
echo "Analyzing: $file"
result=$(gemini "Analyze code complexity. Rate each function 1-10 (1=simple, 10=very complex). Report ONLY functions with score >7 in format 'FunctionName: Score X - Reason'. File content:
$(cat "$file")" 2>&1 || echo "")
if echo "$result" | grep -qi "score [89]\|score 10"; then
warnings+=("High complexity in $file: $result")
exit_code=1
fi
done
echo ""
# Check 2: Security scan
echo "=== Check 2: Security Vulnerabilities ==="
echo "$changed_files" | while IFS= read -r file; do
if [ -z "$file" ]; then
continue
fi
if [ ! -f "$file" ]; then
continue
fi
echo "Scanning: $file"
# Request machine-parseable output (similar to pre-commit hook)
result=$(gemini "Security audit. Check for: SQL injection (raw SQL), XSS (unescaped HTML), command injection (os.system, subprocess with shell=True), path traversal, hardcoded secrets.
IMPORTANT: Output format:
- If ANY vulnerability found, start response with: VULNERABILITY_DETECTED: [type]
- If NO vulnerabilities found, start response with: SECURITY_CLEAN
- Then provide details
File content:
$(cat "$file")" 2>&1 || echo "SECURITY_CLEAN")
# Check for machine-parseable vulnerability marker (more reliable than grep)
if echo "$result" | grep -q "^VULNERABILITY_DETECTED:"; then
critical_issues+=("🔴 Security issue in $file: $result")
exit_code=2
fi
done
echo ""
# Check 3: Test coverage
echo "=== Check 3: Test Coverage ==="
test_files=$(gh pr diff $PR_NUMBER --name-only | grep -c '^tests/.*\.py$' || echo "0")
# Count code files directly from changed_files
code_files=$(echo "$changed_files" | grep -c '\.py$' || echo "0")
if [ $code_files -gt 0 ] && [ $test_files -eq 0 ]; then
warnings+=("No test files added/modified despite $code_files code file(s) changed")
if [ $exit_code -eq 0 ]; then
exit_code=1
fi
fi
echo "Code files changed: $code_files"
echo "Test files changed: $test_files"
echo ""
# Check 4: Breaking changes
echo "=== Check 4: Breaking Changes ==="
head_branch=$(gh pr view $PR_NUMBER --json headRefName --jq '.headRefName')
# Get API-related changes
api_changes=$(git diff origin/main...origin/$head_branch -- \
src/mcp_memory_service/tools.py \
src/mcp_memory_service/web/api/ \
2>/dev/null || echo "")
if [ ! -z "$api_changes" ]; then
echo "Analyzing API changes..."
# Truncate to 200 lines (increased from 100) to capture more context while preventing overflow
# Note: Large PRs may still lose context, but this is a reasonable trade-off for performance
breaking_result=$(gemini "Analyze for breaking changes. Breaking changes include: removed functions/endpoints, changed signatures (parameters removed/reordered), changed return types, renamed public APIs, changed HTTP paths/methods. Report ONLY if breaking changes found with severity (CRITICAL/HIGH/MEDIUM). Changes:
$(echo "$api_changes" | head -200)" 2>&1 || echo "")
if echo "$breaking_result" | grep -qi "breaking\|CRITICAL\|HIGH"; then
warnings+=("⚠️ Potential breaking changes detected: $breaking_result")
if [ $exit_code -eq 0 ]; then
exit_code=1
fi
fi
else
echo "No API changes detected"
fi
echo ""
# Check 5: pyscn comprehensive analysis (optional)
if [ "$RUN_PYSCN" = true ]; then
echo "=== Check 5: pyscn Comprehensive Analysis ==="
if command -v pyscn &> /dev/null; then
echo "Running pyscn static analysis..."
# Run pyscn analysis (will post comment if successful)
if bash scripts/pr/run_pyscn_analysis.sh --pr $PR_NUMBER --threshold 50; then
echo "✅ pyscn analysis passed"
else
echo "⚠️ pyscn analysis found quality issues"
# Note: Don't block on pyscn failures (informational only for now)
# exit_code can be set to 1 here if you want to block on pyscn failures
fi
else
echo "⚠️ pyscn not installed, skipping comprehensive analysis"
echo "Install with: pip install pyscn"
fi
echo ""
fi
# Report results
echo "=== Quality Gate Summary ==="
echo ""
if [ $exit_code -eq 0 ]; then
echo "✅ ALL CHECKS PASSED"
echo ""
echo "Quality Gate Results:"
echo "- Code complexity: ✅ OK"
echo "- Security scan: ✅ OK"
echo "- Test coverage: ✅ OK"
echo "- Breaking changes: ✅ None detected"
if [ "$RUN_PYSCN" = true ]; then
echo "- pyscn analysis: ✅ OK (see separate comment)"
fi
echo ""
pyscn_note=""
if [ "$RUN_PYSCN" = true ]; then
pyscn_note="
- ✅ pyscn comprehensive analysis: See detailed report comment"
fi
gh pr comment $PR_NUMBER --body "✅ **Quality Gate PASSED**
All automated checks completed successfully:
- ✅ Code complexity: OK
- ✅ Security scan: OK
- ✅ Test coverage: OK
- ✅ Breaking changes: None detected${pyscn_note}
PR is ready for Gemini review."
elif [ $exit_code -eq 2 ]; then
echo "🔴 CRITICAL FAILURES"
echo ""
for issue in "${critical_issues[@]}"; do
echo "$issue"
done
echo ""
# Format issues for comment
issues_md=$(printf '%s\n' "${critical_issues[@]}" | sed 's/^/- /')
gh pr comment $PR_NUMBER --body "🔴 **Quality Gate FAILED - CRITICAL**
Security vulnerabilities detected. PR is blocked until issues are resolved.
$issues_md
**Action Required:**
Run \`bash scripts/security/scan_vulnerabilities.sh\` locally and fix all security issues before proceeding."
else
echo "⚠️ WARNINGS (non-blocking)"
echo ""
for warning in "${warnings[@]}"; do
echo "- $warning"
done
echo ""
# Format warnings for comment
warnings_md=$(printf '%s\n' "${warnings[@]}" | sed 's/^/- /')
gh pr comment $PR_NUMBER --body "⚠️ **Quality Gate WARNINGS**
Some checks require attention (non-blocking):
$warnings_md
**Recommendation:**
Consider addressing these issues before requesting review to improve code quality."
fi
exit $exit_code
```
--------------------------------------------------------------------------------
/docs/enhancement-roadmap-issue-14.md:
--------------------------------------------------------------------------------
```markdown
# Memory Awareness Enhancement Roadmap - Issue #14
## Executive Summary
This roadmap outlines the transformation of GitHub issue #14 from a basic external utility to a sophisticated memory-aware Claude Code experience leveraging advanced features like hooks, project awareness, and MCP deep integration.
## Phase 1: Automatic Memory Awareness (Weeks 1-2)
### 1.1 Session Startup Hooks
**Goal**: Automatically inject relevant memories when starting a Claude Code session
**Implementation**:
```javascript
// claude-hooks/session-start.js
export async function onSessionStart(context) {
const projectContext = await detectProjectContext(context.workingDirectory);
const relevantMemories = await queryMemoryService({
tags: [projectContext.name, 'key-decisions', 'recent-insights'],
timeRange: 'last-2-weeks',
limit: 8
});
if (relevantMemories.length > 0) {
await injectSystemMessage(`
Recent project context from memory:
${formatMemoriesForContext(relevantMemories)}
`);
}
}
```
**Features**:
- Project detection based on git repository and directory structure
- Smart memory filtering by project relevance and recency
- Automatic context injection without user intervention
### 1.2 Project-Aware Memory Selection
**Goal**: Intelligently select memories based on current project context
**Implementation**:
```python
# Enhanced memory retrieval with project awareness
class ProjectAwareMemoryRetrieval:
def select_relevant_memories(self, project_context):
# Score memories by relevance to current project
memories = self.memory_service.search_by_tags([
project_context.name,
f"tech:{project_context.language}",
"decisions", "architecture", "bugs-fixed"
])
# Apply relevance scoring
scored_memories = self.score_by_relevance(memories, project_context)
return scored_memories[:10]
```
### 1.3 Conversation Context Injection
**Goal**: Seamlessly inject memory context into conversation flow
**Deliverables**:
- Session initialization hooks
- Project context detection algorithm
- Memory relevance scoring system
- Context formatting and injection utilities
## Phase 2: Intelligent Context Updates (Weeks 3-4)
### 2.1 Dynamic Memory Loading
**Goal**: Update memory context as conversation topics evolve
**Implementation**:
```javascript
// claude-hooks/topic-change.js
export async function onTopicChange(context, newTopics) {
const additionalMemories = await queryMemoryService({
semanticSearch: newTopics,
excludeAlreadyLoaded: context.loadedMemoryHashes,
limit: 5
});
if (additionalMemories.length > 0) {
await updateContext(`
Additional relevant context:
${formatMemoriesForContext(additionalMemories)}
`);
}
}
```
### 2.2 Conversation Continuity
**Goal**: Link conversations across sessions for seamless continuity
**Features**:
- Cross-session conversation linking
- Session outcome consolidation
- Persistent conversation threads
### 2.3 Smart Memory Filtering
**Goal**: AI-powered memory selection based on conversation analysis
**Implementation**:
- Natural language processing for topic extraction
- Semantic similarity matching
- Relevance decay algorithms
- User preference learning
## Phase 3: Advanced Integration Features (Weeks 5-6)
### 3.1 Auto-Tagging Conversations
**Goal**: Automatically categorize and tag conversation outcomes
**Implementation**:
```javascript
// claude-hooks/session-end.js
export async function onSessionEnd(context) {
const sessionSummary = await analyzeSession(context.conversation);
const autoTags = await generateTags(sessionSummary);
await storeMemory({
content: sessionSummary,
tags: [...autoTags, 'session-outcome', context.project.name],
type: 'session-summary'
});
}
```
### 3.2 Memory Consolidation System
**Goal**: Intelligent organization of session insights and outcomes
**Features**:
- Duplicate detection and merging
- Insight extraction and categorization
- Knowledge graph building
- Memory lifecycle management
### 3.3 Cross-Session Intelligence
**Goal**: Maintain knowledge continuity across different coding sessions
**Implementation**:
- Session relationship mapping
- Progressive memory building
- Context evolution tracking
- Learning pattern recognition
## Technical Architecture
### Core Components
1. **Memory Hook System**
- Session lifecycle hooks
- Project context detection
- Dynamic memory injection
2. **Intelligent Memory Selection**
- Relevance scoring algorithms
- Topic analysis and matching
- Context-aware filtering
3. **Context Management**
- Dynamic context updates
- Memory formatting utilities
- Conversation continuity tracking
4. **Integration Layer**
- Claude Code hooks interface
- MCP Memory Service connector
- Project structure analysis
### API Enhancements
```python
# New memory service endpoints for Claude Code integration
@app.post("/claude-code/session-context")
async def get_session_context(project: ProjectContext):
"""Get relevant memories for Claude Code session initialization."""
@app.post("/claude-code/dynamic-context")
async def get_dynamic_context(topics: List[str], exclude: List[str]):
"""Get additional context based on evolving conversation topics."""
@app.post("/claude-code/consolidate-session")
async def consolidate_session(session_data: SessionData):
"""Store and organize session outcomes with intelligent tagging."""
```
## Success Metrics
### Phase 1 Targets
- ✅ 100% automatic session context injection
- ✅ <2 second session startup time with memory loading
- ✅ 90%+ relevant memory selection accuracy
### Phase 2 Targets
- ✅ Real-time context updates based on conversation flow
- ✅ 95%+ conversation continuity across sessions
- ✅ Intelligent topic detection and memory matching
### Phase 3 Targets
- ✅ Fully autonomous memory management
- ✅ Cross-session knowledge building
- ✅ Adaptive learning from user interactions
## Implementation Priority
**High Priority (Phase 1)**:
1. Session startup hooks for automatic memory injection
2. Project-aware memory selection algorithms
3. Basic context injection utilities
**Medium Priority (Phase 2)**:
1. Dynamic memory loading based on conversation topics
2. Cross-session conversation linking
3. Smart memory filtering enhancements
**Enhancement Priority (Phase 3)**:
1. Auto-tagging and session outcome consolidation
2. Advanced memory organization systems
3. Machine learning-based relevance optimization
## Risk Mitigation
### Technical Risks
- **Performance Impact**: Implement lazy loading and caching strategies
- **Context Overload**: Smart filtering and relevance-based selection
- **Memory Service Availability**: Graceful degradation without memory service
### User Experience Risks
- **Information Overload**: Configurable memory injection levels
- **Privacy Concerns**: Clear memory access controls and user preferences
- **Learning Curve**: Seamless integration with minimal configuration required
## Conclusion
This enhancement transforms issue #14 from a basic utility into a revolutionary memory-aware Claude Code experience. By leveraging Claude Code's advanced features, we can deliver the original vision of automatic memory context injection while providing intelligent, context-aware assistance that learns and adapts to user patterns.
The phased approach ensures incremental value delivery while building towards a sophisticated memory-aware development environment that fundamentally changes how developers interact with their code and project knowledge.
```
--------------------------------------------------------------------------------
/scripts/maintenance/cleanup_organize.py:
--------------------------------------------------------------------------------
```python
#!/usr/bin/env python3
# Copyright 2024 Heinrich Krupp
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
MCP-MEMORY-SERVICE Cleanup and Organization Script
This script implements the cleanup plan documented in CLEANUP_PLAN.md.
It creates the necessary directory structure, moves files to their new locations,
and prepares everything for committing to the new branch.
"""
import os
import sys
import shutil
from pathlib import Path
import subprocess
import datetime
def run_command(command):
"""Run a shell command and print the result."""
print(f"Running: {command}")
result = subprocess.run(command, shell=True, capture_output=True, text=True)
if result.returncode != 0:
print(f"ERROR: {result.stderr}")
return False
print(f"SUCCESS: {result.stdout}")
return True
def create_directory(path):
"""Create a directory if it doesn't exist."""
if not os.path.exists(path):
print(f"Creating directory: {path}")
os.makedirs(path)
else:
print(f"Directory already exists: {path}")
def move_file(src, dest):
"""Move a file from src to dest, creating destination directory if needed."""
dest_dir = os.path.dirname(dest)
# Only try to create the directory if dest_dir is not empty
if dest_dir and not os.path.exists(dest_dir):
os.makedirs(dest_dir)
if os.path.exists(src):
print(f"Moving {src} to {dest}")
shutil.move(src, dest)
else:
print(f"WARNING: Source file does not exist: {src}")
def copy_file(src, dest):
"""Copy a file from src to dest, creating destination directory if needed."""
dest_dir = os.path.dirname(dest)
# Only try to create the directory if dest_dir is not empty
if dest_dir and not os.path.exists(dest_dir):
os.makedirs(dest_dir)
if os.path.exists(src):
print(f"Copying {src} to {dest}")
shutil.copy2(src, dest)
else:
print(f"WARNING: Source file does not exist: {src}")
def create_readme(path, content):
"""Create a README.md file with the given content."""
with open(path, 'w') as f:
f.write(content)
print(f"Created README: {path}")
def main():
# Change to the repository root directory
repo_root = os.getcwd()
print(f"Working in repository root: {repo_root}")
# 1. Create test directory structure
test_dirs = [
"tests/integration",
"tests/unit",
"tests/performance"
]
for directory in test_dirs:
create_directory(directory)
# 2. Create documentation directory structure
doc_dirs = [
"docs/guides",
"docs/implementation",
"docs/api",
"docs/examples"
]
for directory in doc_dirs:
create_directory(directory)
# 3. Create archive directory
today = datetime.date.today().strftime("%Y-%m-%d")
archive_dir = f"archive/{today}"
create_directory(archive_dir)
# 4. Move test files
test_files = [
("test_chromadb.py", "tests/unit/test_chroma.py"),
("test_health_check_fixes.py", "tests/integration/test_storage.py"),
("test_issue_5_fix.py", "tests/unit/test_tags.py"),
("test_performance_optimizations.py", "tests/performance/test_caching.py")
]
for src, dest in test_files:
move_file(src, dest)
# 5. Move documentation files
doc_files = [
("HEALTH_CHECK_FIXES_SUMMARY.md", "docs/implementation/health_checks.md"),
("PERFORMANCE_OPTIMIZATION_SUMMARY.md", "docs/implementation/performance.md"),
("CLAUDE.md", "docs/guides/claude_integration.md")
]
for src, dest in doc_files:
move_file(src, dest)
# 6. Archive backup files
archive_files = [
("backup_performance_optimization", f"{archive_dir}/backup_performance_optimization")
]
for src, dest in archive_files:
if os.path.exists(src):
if os.path.exists(dest):
print(f"Archive destination already exists: {dest}")
else:
shutil.copytree(src, dest)
print(f"Archived {src} to {dest}")
else:
print(f"WARNING: Source directory does not exist: {src}")
# 7. Update CHANGELOG.md
if os.path.exists("CHANGELOG.md.new"):
move_file("CHANGELOG.md.new", "CHANGELOG.md")
# 8. Create test README files
test_readme = """# MCP-MEMORY-SERVICE Tests
This directory contains tests for the MCP-MEMORY-SERVICE project.
## Directory Structure
- `integration/` - Integration tests between components
- `unit/` - Unit tests for individual components
- `performance/` - Performance benchmarks
## Running Tests
```bash
# Run all tests
pytest
# Run specific test category
pytest tests/unit/
pytest tests/integration/
pytest tests/performance/
```
"""
create_readme("tests/README.md", test_readme)
# 9. Create docs README file
docs_readme = """# MCP-MEMORY-SERVICE Documentation
This directory contains documentation for the MCP-MEMORY-SERVICE project.
## Directory Structure
- `guides/` - User guides and tutorials
- `implementation/` - Implementation details and technical documentation
- `api/` - API reference documentation
- `examples/` - Example code and usage patterns
"""
create_readme("docs/README.md", docs_readme)
# 10. Create archive README file
archive_readme = f"""# Archive Directory
This directory contains archived files from previous versions of MCP-MEMORY-SERVICE.
## {today}/
- `backup_performance_optimization/` - Backup files from performance optimization work
"""
create_readme(f"{archive_dir}/README.md", archive_readme)
# 11. Create pytest.ini
pytest_ini = """[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
markers =
unit: unit tests
integration: integration tests
performance: performance tests
"""
with open("pytest.ini", 'w') as f:
f.write(pytest_ini)
print("Created pytest.ini")
# 12. Create conftest.py
conftest = """import pytest
import os
import sys
import tempfile
import shutil
# Add src directory to Python path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
@pytest.fixture
def temp_db_path():
'''Create a temporary directory for ChromaDB testing.'''
temp_dir = tempfile.mkdtemp()
yield temp_dir
# Clean up after test
shutil.rmtree(temp_dir)
"""
with open("tests/conftest.py", 'w') as f:
f.write(conftest)
print("Created tests/conftest.py")
print("\nCleanup and organization completed!")
print("Next steps:")
print("1. Verify all files are in their correct locations")
print("2. Run tests to ensure everything still works")
print("3. Create a new git branch and commit the changes")
print(" git checkout -b feature/cleanup-and-organization")
print(" git add .")
print(" git commit -m \"Organize tests, documentation, and archive old files\"")
print("4. Push the branch for hardware testing")
print(" git push origin feature/cleanup-and-organization")
if __name__ == "__main__":
main()
```
--------------------------------------------------------------------------------
/archive/docs-removed-2025-08-23/complete-setup-guide.md:
--------------------------------------------------------------------------------
```markdown
# MCP Memory Service - Complete Setup Guide
## ✅ Successfully Configured Features
### 🧠 **Consolidation System**
- **Exponential Decay**: Memory aging with retention periods
- **Creative Associations**: Auto-discovery of memory connections
- **Semantic Clustering**: DBSCAN algorithm grouping
- **Memory Compression**: 500-char summaries with originals preserved
- **Controlled Forgetting**: Relevance-based archival
- **Automated Scheduling**: Daily/weekly/monthly runs
### 🌐 **mDNS Multi-Client HTTPS**
- **Service Name**: `memory.local` (clean, no port needed)
- **Service Type**: `_http._tcp.local.` (standard HTTP service)
- **Port**: 443 (standard HTTPS)
- **Auto-Discovery**: Zero-configuration client setup
- **HTTPS**: Self-signed certificates (auto-generated)
- **Multi-Interface**: Available on all network interfaces
- **Real-time Updates**: Server-Sent Events (SSE)
- **Security**: Non-root binding via CAP_NET_BIND_SERVICE
### 🚀 **Auto-Startup Service**
- **Systemd Integration**: Starts on boot automatically
- **Auto-Restart**: Recovers from failures
- **User Service**: Runs as regular user (not root)
- **Logging**: Integrated with systemd journal
## 📋 **Complete Installation Steps**
### **1. Initial Setup**
```bash
# Create virtual environment and install
python3 -m venv venv
source venv/bin/activate
python install.py --server-mode --enable-http-api
```
### **2. Configure Auto-Startup**
```bash
# Install systemd service
bash install_service.sh
# Update service configuration (fixed version)
./update_service.sh
# Start service
sudo systemctl start mcp-memory
# Verify it's running
sudo systemctl status mcp-memory
```
### **3. Configure Firewall**
```bash
# Allow mDNS discovery
sudo ufw allow 5353/udp
# Allow HTTPS server
sudo ufw allow 8000/tcp
```
## 🔧 **Service Configuration**
### **Environment Variables**
```bash
MCP_CONSOLIDATION_ENABLED=true
MCP_MDNS_ENABLED=true
MCP_HTTPS_ENABLED=true
MCP_MDNS_SERVICE_NAME="memory"
MCP_HTTP_HOST=0.0.0.0
MCP_HTTP_PORT=8000
MCP_MEMORY_STORAGE_BACKEND=sqlite_vec
MCP_API_KEY=mcp-0b1ccbde2197a08dcb12d41af4044be6
```
### **Consolidation Settings**
- **Retention Periods**: Critical (365d), Reference (180d), Standard (30d), Temporary (7d)
- **Association Discovery**: Similarity range 0.3-0.7, max 100 pairs per run
- **Clustering**: DBSCAN algorithm, minimum 5 memories per cluster
- **Compression**: 500 character summaries, preserve originals
- **Forgetting**: Relevance threshold 0.1, 90-day access threshold
- **Schedule**: Daily 2AM, Weekly Sunday 3AM, Monthly 1st 4AM
## 🌐 **Access Points**
### **Local Access**
- **Dashboard**: https://localhost:443 or https://memory.local
- **API Documentation**: https://memory.local/api/docs
- **Health Check**: https://memory.local/api/health
- **SSE Events**: https://memory.local/api/events
- **Connection Stats**: https://memory.local/api/events/stats
### **Network Access**
- **Clean mDNS**: https://memory.local (no port needed!)
- **mDNS Discovery**: `memory._http._tcp.local.`
- **Auto-Discovery**: Clients find service automatically
- **Standard Port**: 443 (HTTPS default)
## 🛠️ **Service Management**
### **Using Control Script**
```bash
./service_control.sh start # Start service
./service_control.sh stop # Stop service
./service_control.sh restart # Restart service
./service_control.sh status # Show status
./service_control.sh logs # View live logs
./service_control.sh health # Test API health
```
### **Direct systemctl Commands**
```bash
sudo systemctl start mcp-memory # Start
sudo systemctl stop mcp-memory # Stop
sudo systemctl restart mcp-memory # Restart
sudo systemctl status mcp-memory # Status
sudo systemctl enable mcp-memory # Enable startup
sudo systemctl disable mcp-memory # Disable startup
sudo journalctl -u mcp-memory -f # Live logs
```
## 🔍 **Verification Tests**
### **1. Service Status**
```bash
sudo systemctl status mcp-memory
# Should show: Active: active (running)
```
### **2. API Health**
```bash
curl -k https://localhost:8000/api/health
# Should return: {"status": "healthy", ...}
```
### **3. mDNS Discovery**
```bash
avahi-browse -t _mcp-memory._tcp
# Should show: memory on multiple interfaces
```
### **4. HTTPS Certificate**
```bash
openssl s_client -connect localhost:8000 -servername localhost < /dev/null
# Should show certificate details
```
## 📁 **File Structure**
### **Core Files**
- `mcp-memory.service` - Systemd service configuration
- `install_service.sh` - Service installation script
- `service_control.sh` - Service management script
- `update_service.sh` - Configuration update script
- `test_service.sh` - Debug testing script
### **Setup Scripts**
- `setup_consolidation_mdns.sh` - Manual startup script
- `COMPLETE_SETUP_GUIDE.md` - This comprehensive guide
- `STARTUP_SETUP_GUIDE.md` - Original startup guide
## 🔐 **Security Configuration**
### **API Authentication**
- **API Key**: `mcp-0b1ccbde2197a08dcb12d41af4044be6`
- **HTTPS Only**: Self-signed certificates for development
- **Local Network**: mDNS discovery limited to local network
### **Systemd Security**
- **User Service**: Runs as `hkr` user (not root)
- **Working Directory**: `/home/hkr/repositories/mcp-memory-service`
- **No Privilege Escalation**: NoNewPrivileges removed for compatibility
## 🎯 **Client Configuration**
### **Claude Desktop Auto-Discovery**
```json
{
"mcpServers": {
"memory": {
"command": "node",
"args": ["/path/to/examples/http-mcp-bridge.js"],
"env": {
"MCP_MEMORY_AUTO_DISCOVER": "true",
"MCP_MEMORY_PREFER_HTTPS": "true",
"MCP_MEMORY_API_KEY": "mcp-0b1ccbde2197a08dcb12d41af4044be6"
}
}
}
}
```
### **Manual Connection**
```json
{
"mcpServers": {
"memory": {
"command": "node",
"args": ["/path/to/examples/http-mcp-bridge.js"],
"env": {
"MCP_MEMORY_HTTP_ENDPOINT": "https://memory.local/api",
"MCP_MEMORY_API_KEY": "mcp-0b1ccbde2197a08dcb12d41af4044be6"
}
}
}
}
```
## 🚨 **Troubleshooting**
### **Service Won't Start**
```bash
# Check detailed logs
sudo journalctl -u mcp-memory --no-pager -n 50
# Test manual startup
./test_service.sh
# Verify virtual environment
ls -la venv/bin/python
```
### **Can't Connect to API**
```bash
# Check if service is listening
ss -tlnp | grep :443
# Test local connection
curl -k https://memory.local/api/health
# Check firewall
sudo ufw status
```
### **No mDNS Discovery**
```bash
# Test mDNS
avahi-browse -t _http._tcp | grep memory
# Test resolution
avahi-resolve-host-name memory.local
# Check network interfaces
ip addr show
# Verify multicast support
ping 224.0.0.251
```
### **Port 443 Conflicts (Pi-hole, etc.)**
```bash
# Check what's using port 443
sudo netstat -tlnp | grep :443
# Disable conflicting services (example: Pi-hole)
sudo systemctl stop pihole-FTL
sudo systemctl disable pihole-FTL
sudo systemctl stop lighttpd
sudo systemctl disable lighttpd
# Then restart memory service
sudo systemctl restart mcp-memory
```
## ✅ **Success Indicators**
When everything is working correctly, you should see:
1. **Service Status**: `Active: active (running)`
2. **API Response**: `{"status": "healthy"}`
3. **mDNS Discovery**: Service visible on multiple interfaces
4. **HTTPS Access**: Dashboard accessible at https://localhost:8000
5. **Auto-Startup**: Service starts automatically on boot
6. **Consolidation**: Logs show consolidation system enabled
7. **Client Connections**: Multiple clients can connect simultaneously
---
**🎉 Your MCP Memory Service is now fully operational with consolidation, mDNS auto-discovery, HTTPS, and automatic startup!**
```
--------------------------------------------------------------------------------
/scripts/pr/auto_review.sh:
--------------------------------------------------------------------------------
```bash
#!/bin/bash
# scripts/pr/auto_review.sh - Automated PR review loop using Gemini CLI
#
# Usage: bash scripts/pr/auto_review.sh <PR_NUMBER> [MAX_ITERATIONS] [SAFE_FIX_MODE]
# Example: bash scripts/pr/auto_review.sh 123 5 true
set -e
# Get script directory for sourcing helpers
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Source GraphQL helpers for thread resolution
if [ -f "$SCRIPT_DIR/lib/graphql_helpers.sh" ]; then
source "$SCRIPT_DIR/lib/graphql_helpers.sh"
GRAPHQL_AVAILABLE=true
else
echo "Warning: GraphQL helpers not available, thread auto-resolution disabled"
GRAPHQL_AVAILABLE=false
fi
PR_NUMBER=$1
MAX_ITERATIONS=${2:-5}
SAFE_FIX_MODE=${3:-true}
if [ -z "$PR_NUMBER" ]; then
echo "Usage: $0 <PR_NUMBER> [MAX_ITERATIONS] [SAFE_FIX_MODE]"
echo "Example: $0 123 5 true"
exit 1
fi
# Check dependencies
if ! command -v gh &> /dev/null; then
echo "Error: GitHub CLI (gh) is not installed"
echo "Install: https://cli.github.com/"
exit 1
fi
if ! command -v gemini &> /dev/null; then
echo "Error: Gemini CLI is not installed"
exit 1
fi
echo "=== Automated PR Review Loop ==="
echo "PR Number: #$PR_NUMBER"
echo "Max Iterations: $MAX_ITERATIONS"
echo "Safe Fix Mode: $SAFE_FIX_MODE"
echo "GraphQL Thread Resolution: $([ "$GRAPHQL_AVAILABLE" = true ] && echo "Enabled" || echo "Disabled")"
echo ""
# Get repository from git remote (portable across forks)
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || echo "doobidoo/mcp-memory-service")
iteration=1
approved=false
while [ $iteration -le $MAX_ITERATIONS ] && [ "$approved" = false ]; do
echo "=== Iteration $iteration/$MAX_ITERATIONS ==="
# Trigger Gemini review (use /gemini review for inline comments)
echo "Requesting Gemini code review (inline comments)..."
gh pr comment $PR_NUMBER --body "/gemini review"
# Wait for Gemini to process
echo "Waiting for Gemini review (90 seconds)..."
sleep 90
# Fetch latest review status and comments
echo "Fetching review feedback..."
# Get review state (APPROVED, CHANGES_REQUESTED, COMMENTED)
review_state=$(gh pr view $PR_NUMBER --json reviews --jq '[.reviews[] | select(.author.login == "gemini-code-assist[bot]")] | last | .state')
# Fetch actual comment bodies for categorization first
review_comments=$(gh api "repos/$REPO/pulls/$PR_NUMBER/comments" | \
jq -r '[.[] | select(.user.login == "gemini-code-assist[bot]")] | .[] | "- \(.path):\(.line) - \(.body[0:200])"' | \
head -50)
# Get inline review comments count
review_comments_count=$(gh api "repos/$REPO/pulls/$PR_NUMBER/comments" | jq '[.[] | select(.user.login == "gemini-code-assist[bot]")] | length')
echo "Review State: $review_state"
echo "Inline Comments: $review_comments_count"
# Display thread status if GraphQL available
if [ "$GRAPHQL_AVAILABLE" = true ]; then
thread_stats=$(get_thread_stats "$PR_NUMBER" 2>/dev/null || echo '{"total":0,"resolved":0,"unresolved":0}')
total_threads=$(echo "$thread_stats" | jq -r '.total // 0')
resolved_threads=$(echo "$thread_stats" | jq -r '.resolved // 0')
unresolved_threads=$(echo "$thread_stats" | jq -r '.unresolved // 0')
echo "Review Threads: $total_threads total, $resolved_threads resolved, $unresolved_threads unresolved"
fi
echo ""
# Check if approved
if [ "$review_state" = "APPROVED" ]; then
echo "✅ PR approved by Gemini!"
approved=true
break
fi
# If no inline comments, we're done
if [ "$review_comments_count" -eq 0 ] && [ "$review_state" != "CHANGES_REQUESTED" ]; then
echo "✅ No issues found in review"
approved=true
break
fi
# Extract actionable issues
if [ "$SAFE_FIX_MODE" = true ]; then
echo "Analyzing feedback for safe auto-fixes..."
# Get PR diff
pr_diff=$(gh pr diff $PR_NUMBER)
# Use Gemini to categorize issues (request JSON format)
categorization=$(gemini "Categorize these code review comments into a JSON object.
Review feedback:
$review_comments
Categories:
- safe: Simple fixes (formatting, imports, type hints, docstrings, variable renaming)
- unsafe: Logic changes, API modifications, security-critical code
- non_code: Documentation, discussion, questions
IMPORTANT: Output ONLY valid JSON in this exact format:
{
\"safe\": [\"issue 1\", \"issue 2\"],
\"unsafe\": [\"issue 1\"],
\"non_code\": [\"comment 1\"]
}")
echo "$categorization"
# Extract safe issues using jq
safe_issues=$(echo "$categorization" | jq -r '.safe[]' 2>/dev/null || echo "")
if [ -z "$safe_issues" ]; then
echo "No safe auto-fixable issues found. Manual intervention required."
break
fi
echo ""
echo "Safe issues to auto-fix:"
echo "$safe_issues"
echo ""
# Generate fixes for safe issues
echo "Generating code fixes..."
fixes=$(gemini "Generate git diff patches for these safe fixes:
Issues to fix:
$safe_issues
Current code (PR diff):
$pr_diff
Output only the git diff patch that can be applied with 'git apply'. Include file paths and line numbers.")
# Use mktemp for patch file
patch_file=$(mktemp -t pr_fixes_${PR_NUMBER}_${iteration}.XXXXXX)
echo "$fixes" > "$patch_file"
# Attempt to apply fixes
echo "Attempting to apply fixes..."
if git apply --check "$patch_file" 2>/dev/null; then
git apply "$patch_file"
git add -A
# Create commit message
commit_msg="fix: apply Gemini review feedback (iteration $iteration)
Addressed:
$safe_issues
Co-Authored-By: Gemini Code Assist <[email protected]>"
git commit -m "$commit_msg"
git push
echo "✅ Fixes applied and pushed"
# Auto-resolve review threads for files we just fixed
if [ "$GRAPHQL_AVAILABLE" = true ]; then
echo ""
echo "Resolving review threads for fixed code..."
# Get the commit SHA we just created
latest_commit=$(git rev-parse HEAD)
# Run thread resolution in auto mode
if bash "$SCRIPT_DIR/resolve_threads.sh" "$PR_NUMBER" "$latest_commit" --auto 2>&1 | grep -q "Resolved:"; then
echo "✅ Review threads auto-resolved"
else
echo "ℹ️ No threads needed resolution"
fi
fi
# Clean up temp file
rm -f "$patch_file"
else
echo "⚠️ Could not auto-apply fixes. Patch saved to $patch_file"
echo "Manual application required."
break
fi
else
echo "Manual fix mode enabled. Review feedback above and apply manually."
break
fi
iteration=$((iteration + 1))
echo ""
echo "Waiting 10 seconds before next iteration..."
sleep 10
done
echo ""
echo "=== Review Loop Complete ==="
if [ "$approved" = true ]; then
echo "🎉 PR #$PR_NUMBER is approved and ready to merge!"
gh pr comment $PR_NUMBER --body "✅ **Automated Review Complete**
All review iterations completed successfully. PR is approved and ready for merge.
Iterations: $((iteration - 1))/$MAX_ITERATIONS"
exit 0
else
echo "⚠️ Max iterations reached or manual intervention needed"
gh pr comment $PR_NUMBER --body "⚠️ **Automated Review Incomplete**
Review loop completed $((iteration - 1)) iterations but approval not received.
Manual review and intervention may be required.
Please review the latest feedback and apply necessary changes."
exit 1
fi
```
--------------------------------------------------------------------------------
/scripts/service/service_utils.py:
--------------------------------------------------------------------------------
```python
#!/usr/bin/env python3
"""
Shared utilities for cross-platform service installation.
Provides common functionality for all platform-specific service installers.
"""
import os
import sys
import json
import secrets
import platform
import subprocess
from pathlib import Path
from typing import Dict, Optional, Tuple
def get_project_root() -> Path:
"""Get the project root directory."""
current_file = Path(__file__).resolve()
return current_file.parent.parent
def get_python_executable() -> str:
"""Get the current Python executable path."""
return sys.executable
def get_venv_path() -> Optional[Path]:
"""Get the virtual environment path if in a venv."""
if hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix):
return Path(sys.prefix)
return None
def generate_api_key() -> str:
"""Generate a secure API key for the service."""
return f"mcp-{secrets.token_hex(16)}"
def get_service_paths() -> Dict[str, Path]:
"""Get common paths used by the service."""
project_root = get_project_root()
paths = {
'project_root': project_root,
'scripts_dir': project_root / 'scripts',
'src_dir': project_root / 'src',
'venv_dir': get_venv_path() or project_root / 'venv',
'config_dir': Path.home() / '.mcp_memory_service',
'log_dir': Path.home() / '.mcp_memory_service' / 'logs',
}
# Ensure config and log directories exist
paths['config_dir'].mkdir(parents=True, exist_ok=True)
paths['log_dir'].mkdir(parents=True, exist_ok=True)
return paths
def get_service_environment() -> Dict[str, str]:
"""Get environment variables for the service."""
paths = get_service_paths()
venv_path = get_venv_path()
env = {
'PYTHONPATH': str(paths['src_dir']),
'MCP_MEMORY_STORAGE_BACKEND': os.getenv('MCP_MEMORY_STORAGE_BACKEND', 'sqlite_vec'),
'MCP_HTTP_ENABLED': 'true',
'MCP_HTTP_HOST': '0.0.0.0',
'MCP_HTTP_PORT': '8000',
'MCP_HTTPS_ENABLED': 'true',
'MCP_MDNS_ENABLED': 'true',
'MCP_MDNS_SERVICE_NAME': 'memory',
'MCP_CONSOLIDATION_ENABLED': 'true',
}
# Add venv to PATH if available
if venv_path:
bin_dir = venv_path / ('Scripts' if platform.system() == 'Windows' else 'bin')
current_path = os.environ.get('PATH', '')
env['PATH'] = f"{bin_dir}{os.pathsep}{current_path}"
return env
def save_service_config(config: Dict[str, any]) -> Path:
"""Save service configuration to file."""
paths = get_service_paths()
config_file = paths['config_dir'] / 'service_config.json'
with open(config_file, 'w') as f:
json.dump(config, f, indent=2)
return config_file
def load_service_config() -> Optional[Dict[str, any]]:
"""Load service configuration from file."""
paths = get_service_paths()
config_file = paths['config_dir'] / 'service_config.json'
if config_file.exists():
with open(config_file, 'r') as f:
return json.load(f)
return None
def check_dependencies() -> Tuple[bool, str]:
"""Check if all required dependencies are installed."""
try:
# Check Python version
if sys.version_info < (3, 10):
return False, f"Python 3.10+ required, found {sys.version}"
# Check if in virtual environment (recommended)
if not get_venv_path():
print("WARNING: Not running in a virtual environment")
# Check core dependencies
required_modules = [
'mcp',
'chromadb',
'sentence_transformers',
]
missing = []
for module in required_modules:
try:
__import__(module)
except ImportError:
missing.append(module)
if missing:
return False, f"Missing dependencies: {', '.join(missing)}"
return True, "All dependencies satisfied"
except Exception as e:
return False, f"Error checking dependencies: {str(e)}"
def get_service_command() -> list:
"""Get the command to run the service."""
paths = get_service_paths()
python_exe = get_python_executable()
# Use HTTP server script if available, otherwise fall back to main server
http_server = paths['scripts_dir'] / 'run_http_server.py'
main_server = paths['scripts_dir'] / 'run_memory_server.py'
if http_server.exists():
return [python_exe, str(http_server)]
elif main_server.exists():
return [python_exe, str(main_server)]
else:
# Fall back to module execution
return [python_exe, '-m', 'mcp_memory_service.server']
def test_service_startup() -> Tuple[bool, str]:
"""Test if the service can start successfully."""
try:
cmd = get_service_command()
env = os.environ.copy()
env.update(get_service_environment())
# Try to start the service briefly
proc = subprocess.Popen(
cmd,
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
# Give it a moment to start
import time
time.sleep(2)
# Check if process is still running
if proc.poll() is None:
# Service started successfully, terminate it
proc.terminate()
proc.wait(timeout=5)
return True, "Service starts successfully"
else:
# Process exited, get error
stdout, stderr = proc.communicate()
error_msg = stderr or stdout or "Unknown error"
return False, f"Service failed to start: {error_msg}"
except Exception as e:
return False, f"Error testing service: {str(e)}"
def print_service_info(api_key: str, platform_specific_info: Dict[str, str] = None):
"""Print service installation information."""
print("\n" + "=" * 60)
print("✅ MCP Memory Service Installed Successfully!")
print("=" * 60)
print("\n📌 Service Information:")
print(f" API Key: {api_key}")
print(f" Dashboard: https://localhost:8000")
print(f" API Docs: https://localhost:8000/api/docs")
print(f" Health Check: https://localhost:8000/api/health")
if platform_specific_info:
print("\n🖥️ Platform-Specific Commands:")
for key, value in platform_specific_info.items():
print(f" {key}: {value}")
print("\n📝 Configuration:")
config = load_service_config()
if config:
print(f" Config File: {get_service_paths()['config_dir'] / 'service_config.json'}")
print(f" Log Directory: {get_service_paths()['log_dir']}")
print("\n" + "=" * 60)
def is_admin() -> bool:
"""Check if running with administrative privileges."""
system = platform.system()
if system == "Windows":
try:
import ctypes
return ctypes.windll.shell32.IsUserAnAdmin() != 0
except:
return False
else: # Unix-like systems
return os.geteuid() == 0
def require_admin(message: str = None):
"""Ensure the script is running with admin privileges."""
if not is_admin():
system = platform.system()
if message:
print(f"\n❌ {message}")
if system == "Windows":
print("\nPlease run this script as Administrator:")
print(" Right-click on your terminal and select 'Run as Administrator'")
else:
print("\nPlease run this script with sudo:")
script_name = sys.argv[0]
print(f" sudo {' '.join(sys.argv)}")
sys.exit(1)
```