This is page 19 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
--------------------------------------------------------------------------------
/src/mcp_memory_service/consolidation/clustering.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.
"""Semantic clustering system for memory organization."""
import uuid
import numpy as np
from typing import List, Dict, Any, Optional, Tuple
from datetime import datetime
from collections import Counter
import re
try:
from sklearn.cluster import DBSCAN
from sklearn.cluster import AgglomerativeClustering
from sklearn.metrics import silhouette_score
SKLEARN_AVAILABLE = True
except ImportError:
SKLEARN_AVAILABLE = False
from .base import ConsolidationBase, ConsolidationConfig, MemoryCluster
from ..models.memory import Memory
class SemanticClusteringEngine(ConsolidationBase):
"""
Creates semantic clusters of related memories for organization and compression.
Uses embedding-based clustering algorithms (DBSCAN, Hierarchical) to group
semantically similar memories, enabling efficient compression and retrieval.
"""
def __init__(self, config: ConsolidationConfig):
super().__init__(config)
self.min_cluster_size = config.min_cluster_size
self.algorithm = config.clustering_algorithm
if not SKLEARN_AVAILABLE:
self.logger.warning("sklearn not available, using simple clustering fallback")
self.algorithm = 'simple'
async def process(self, memories: List[Memory], **kwargs) -> List[MemoryCluster]:
"""Create semantic clusters from memories."""
if not self._validate_memories(memories) or len(memories) < self.min_cluster_size:
return []
# Filter memories with embeddings
memories_with_embeddings = [m for m in memories if m.embedding]
if len(memories_with_embeddings) < self.min_cluster_size:
self.logger.warning(f"Only {len(memories_with_embeddings)} memories have embeddings, need at least {self.min_cluster_size}")
return []
# Extract embeddings matrix
embeddings = np.array([m.embedding for m in memories_with_embeddings])
# Perform clustering
if self.algorithm == 'dbscan':
cluster_labels = await self._dbscan_clustering(embeddings)
elif self.algorithm == 'hierarchical':
cluster_labels = await self._hierarchical_clustering(embeddings)
else:
cluster_labels = await self._simple_clustering(embeddings)
# Create cluster objects
clusters = await self._create_clusters(memories_with_embeddings, cluster_labels, embeddings)
# Filter by minimum cluster size
valid_clusters = [c for c in clusters if len(c.memory_hashes) >= self.min_cluster_size]
self.logger.info(f"Created {len(valid_clusters)} valid clusters from {len(memories_with_embeddings)} memories")
return valid_clusters
async def _dbscan_clustering(self, embeddings: np.ndarray) -> np.ndarray:
"""Perform DBSCAN clustering on embeddings."""
if not SKLEARN_AVAILABLE:
return await self._simple_clustering(embeddings)
# Adaptive epsilon based on data size and dimensionality
n_samples, n_features = embeddings.shape
eps = 0.5 - (n_samples / 10000) * 0.1 # Decrease eps for larger datasets
eps = max(0.2, min(0.7, eps)) # Clamp between 0.2 and 0.7
min_samples = max(2, self.min_cluster_size // 2)
clustering = DBSCAN(eps=eps, min_samples=min_samples, metric='cosine')
labels = clustering.fit_predict(embeddings)
self.logger.debug(f"DBSCAN: eps={eps}, min_samples={min_samples}, found {len(set(labels))} clusters")
return labels
async def _hierarchical_clustering(self, embeddings: np.ndarray) -> np.ndarray:
"""Perform hierarchical clustering on embeddings."""
if not SKLEARN_AVAILABLE:
return await self._simple_clustering(embeddings)
# Estimate number of clusters (heuristic: sqrt of samples / 2)
n_samples = embeddings.shape[0]
n_clusters = max(2, min(n_samples // self.min_cluster_size, int(np.sqrt(n_samples) / 2)))
clustering = AgglomerativeClustering(
n_clusters=n_clusters,
metric='cosine',
linkage='average'
)
labels = clustering.fit_predict(embeddings)
self.logger.debug(f"Hierarchical: n_clusters={n_clusters}, found {len(set(labels))} clusters")
return labels
async def _simple_clustering(self, embeddings: np.ndarray) -> np.ndarray:
"""Simple fallback clustering using cosine similarity threshold."""
n_samples = embeddings.shape[0]
labels = np.full(n_samples, -1) # Start with all as noise
current_cluster = 0
similarity_threshold = 0.7 # Threshold for grouping
for i in range(n_samples):
if labels[i] != -1: # Already assigned
continue
# Start new cluster
cluster_members = [i]
labels[i] = current_cluster
# Find similar memories
for j in range(i + 1, n_samples):
if labels[j] != -1: # Already assigned
continue
# Calculate cosine similarity
similarity = np.dot(embeddings[i], embeddings[j]) / (
np.linalg.norm(embeddings[i]) * np.linalg.norm(embeddings[j])
)
if similarity >= similarity_threshold:
labels[j] = current_cluster
cluster_members.append(j)
# Only keep cluster if it meets minimum size
if len(cluster_members) >= self.min_cluster_size:
current_cluster += 1
else:
# Mark as noise
for member in cluster_members:
labels[member] = -1
self.logger.debug(f"Simple clustering: threshold={similarity_threshold}, found {current_cluster} clusters")
return labels
async def _create_clusters(
self,
memories: List[Memory],
labels: np.ndarray,
embeddings: np.ndarray
) -> List[MemoryCluster]:
"""Create MemoryCluster objects from clustering results."""
clusters = []
unique_labels = set(labels)
for label in unique_labels:
if label == -1: # Skip noise points
continue
# Get memories in this cluster
cluster_indices = np.where(labels == label)[0]
cluster_memories = [memories[i] for i in cluster_indices]
cluster_embeddings = embeddings[cluster_indices]
if len(cluster_memories) < self.min_cluster_size:
continue
# Calculate centroid embedding
centroid = np.mean(cluster_embeddings, axis=0)
# Calculate coherence score (average cosine similarity to centroid)
coherence_scores = []
for embedding in cluster_embeddings:
similarity = np.dot(embedding, centroid) / (
np.linalg.norm(embedding) * np.linalg.norm(centroid)
)
coherence_scores.append(similarity)
coherence_score = np.mean(coherence_scores)
# Extract theme keywords
theme_keywords = await self._extract_theme_keywords(cluster_memories)
# Create cluster
cluster = MemoryCluster(
cluster_id=str(uuid.uuid4()),
memory_hashes=[m.content_hash for m in cluster_memories],
centroid_embedding=centroid.tolist(),
coherence_score=float(coherence_score),
created_at=datetime.now(),
theme_keywords=theme_keywords,
metadata={
'algorithm': self.algorithm,
'cluster_size': len(cluster_memories),
'average_memory_age': self._calculate_average_age(cluster_memories),
'tag_distribution': self._analyze_tag_distribution(cluster_memories)
}
)
clusters.append(cluster)
return clusters
async def _extract_theme_keywords(self, memories: List[Memory]) -> List[str]:
"""Extract theme keywords that represent the cluster."""
# Combine all content
all_text = ' '.join([m.content for m in memories])
# Collect all tags
all_tags = []
for memory in memories:
all_tags.extend(memory.tags)
# Count tag frequency
tag_counts = Counter(all_tags)
# Extract frequent words from content (simple approach)
words = re.findall(r'\b[a-zA-Z]{4,}\b', all_text.lower())
word_counts = Counter(words)
# Remove common stop words
stop_words = {
'this', 'that', 'with', 'have', 'will', 'from', 'they', 'know',
'want', 'been', 'good', 'much', 'some', 'time', 'very', 'when',
'come', 'here', 'just', 'like', 'long', 'make', 'many', 'over',
'such', 'take', 'than', 'them', 'well', 'were', 'what', 'work',
'your', 'could', 'should', 'would', 'there', 'their', 'these',
'about', 'after', 'again', 'before', 'being', 'between', 'during',
'under', 'where', 'while', 'other', 'through', 'against'
}
# Filter and get top words
filtered_words = {word: count for word, count in word_counts.items()
if word not in stop_words and count > 1}
# Combine tags and words, prioritize tags
theme_keywords = []
# Add top tags (weight by frequency)
for tag, count in tag_counts.most_common(5):
if count > 1: # Tag appears in multiple memories
theme_keywords.append(tag)
# Add top words
for word, count in sorted(filtered_words.items(), key=lambda x: x[1], reverse=True)[:10]:
if word not in theme_keywords:
theme_keywords.append(word)
return theme_keywords[:10] # Limit to top 10
def _calculate_average_age(self, memories: List[Memory]) -> float:
"""Calculate average age of memories in days."""
now = datetime.now()
ages = []
for memory in memories:
if memory.created_at:
created_dt = datetime.utcfromtimestamp(memory.created_at)
age_days = (now - created_dt).days
ages.append(age_days)
elif memory.timestamp:
age_days = (now - memory.timestamp).days
ages.append(age_days)
return sum(ages) / len(ages) if ages else 0.0
def _analyze_tag_distribution(self, memories: List[Memory]) -> Dict[str, int]:
"""Analyze tag distribution within the cluster."""
all_tags = []
for memory in memories:
all_tags.extend(memory.tags)
return dict(Counter(all_tags))
async def merge_similar_clusters(
self,
clusters: List[MemoryCluster],
similarity_threshold: float = 0.8
) -> List[MemoryCluster]:
"""Merge clusters that are very similar to each other."""
if len(clusters) <= 1:
return clusters
# Calculate pairwise similarities between cluster centroids
centroids = np.array([cluster.centroid_embedding for cluster in clusters])
merged = [False] * len(clusters)
result_clusters = []
for i, cluster1 in enumerate(clusters):
if merged[i]:
continue
# Start with current cluster
merge_group = [i]
merged[i] = True
# Find similar clusters to merge
for j in range(i + 1, len(clusters)):
if merged[j]:
continue
# Calculate cosine similarity between centroids
similarity = np.dot(centroids[i], centroids[j]) / (
np.linalg.norm(centroids[i]) * np.linalg.norm(centroids[j])
)
if similarity >= similarity_threshold:
merge_group.append(j)
merged[j] = True
# Create merged cluster
if len(merge_group) == 1:
# No merging needed
result_clusters.append(clusters[i])
else:
# Merge clusters
merged_cluster = await self._merge_cluster_group(
[clusters[idx] for idx in merge_group]
)
result_clusters.append(merged_cluster)
self.logger.info(f"Merged {len(clusters)} clusters into {len(result_clusters)}")
return result_clusters
async def _merge_cluster_group(self, clusters: List[MemoryCluster]) -> MemoryCluster:
"""Merge a group of similar clusters into one."""
# Combine all memory hashes
all_memory_hashes = []
for cluster in clusters:
all_memory_hashes.extend(cluster.memory_hashes)
# Calculate new centroid (average of all centroids weighted by cluster size)
total_size = sum(len(cluster.memory_hashes) for cluster in clusters)
weighted_centroid = np.zeros(len(clusters[0].centroid_embedding))
for cluster in clusters:
weight = len(cluster.memory_hashes) / total_size
centroid = np.array(cluster.centroid_embedding)
weighted_centroid += weight * centroid
# Combine theme keywords
all_keywords = []
for cluster in clusters:
all_keywords.extend(cluster.theme_keywords)
keyword_counts = Counter(all_keywords)
merged_keywords = [kw for kw, count in keyword_counts.most_common(10)]
# Calculate average coherence score
total_memories = sum(len(cluster.memory_hashes) for cluster in clusters)
weighted_coherence = sum(
cluster.coherence_score * len(cluster.memory_hashes) / total_memories
for cluster in clusters
)
return MemoryCluster(
cluster_id=str(uuid.uuid4()),
memory_hashes=all_memory_hashes,
centroid_embedding=weighted_centroid.tolist(),
coherence_score=weighted_coherence,
created_at=datetime.now(),
theme_keywords=merged_keywords,
metadata={
'algorithm': f"{self.algorithm}_merged",
'cluster_size': len(all_memory_hashes),
'merged_from': [cluster.cluster_id for cluster in clusters],
'merge_timestamp': datetime.now().isoformat()
}
)
```
--------------------------------------------------------------------------------
/tests/integration/test_cli_interfaces.py:
--------------------------------------------------------------------------------
```python
#!/usr/bin/env python3
"""
Integration tests for CLI interfaces to prevent conflicts and ensure compatibility.
This module tests the different CLI entry points to ensure they work correctly
and that the compatibility layer functions as expected.
"""
import subprocess
import pytest
import warnings
import sys
import os
from pathlib import Path
# Add src to path
current_dir = Path(__file__).parent
src_dir = current_dir.parent.parent / "src"
sys.path.insert(0, str(src_dir))
from mcp_memory_service.cli.main import memory_server_main, main as cli_main
class TestCLIInterfaces:
"""Test CLI interface compatibility and functionality."""
def test_memory_command_backward_compatibility(self):
"""Test that 'uv run memory' (without server) starts the MCP server for backward compatibility."""
result = subprocess.run(
["uv", "run", "memory", "--help"],
capture_output=True,
text=True,
timeout=10,
cwd=current_dir.parent.parent
)
# Should show help text (not start server) when --help is provided
assert result.returncode == 0
assert "MCP Memory Service" in result.stdout
def test_memory_command_exists(self):
"""Test that the memory command is available."""
result = subprocess.run(
["uv", "run", "memory", "--help"],
capture_output=True,
text=True,
cwd=current_dir.parent.parent
)
assert result.returncode == 0
assert "MCP Memory Service" in result.stdout
assert "server" in result.stdout
assert "status" in result.stdout
def test_memory_server_command_exists(self):
"""Test that the memory-server command is available."""
result = subprocess.run(
["uv", "run", "memory-server", "--help"],
capture_output=True,
text=True,
cwd=current_dir.parent.parent
)
assert result.returncode == 0
assert "MCP Memory Service" in result.stdout
# Should show deprecation warning in stderr
assert "deprecated" in result.stderr.lower()
def test_mcp_memory_server_command_exists(self):
"""Test that the mcp-memory-server command is available."""
result = subprocess.run(
["uv", "run", "mcp-memory-server", "--help"],
capture_output=True,
text=True,
cwd=current_dir.parent.parent
)
# This might have different behavior or missing dependencies
# 0 for success, 1 for import error (missing fastmcp), 2 for argument error
assert result.returncode in [0, 1, 2]
# If it failed due to missing fastmcp dependency, that's expected
if result.returncode == 1 and "fastmcp" in result.stderr:
pytest.skip("mcp-memory-server requires FastMCP which is not installed")
def test_memory_server_version_flag(self):
"""Test that memory-server --version works and shows deprecation warning."""
result = subprocess.run(
["uv", "run", "memory-server", "--version"],
capture_output=True,
text=True,
cwd=current_dir.parent.parent
)
assert result.returncode == 0
assert "8.24.0" in result.stdout
assert "deprecated" in result.stderr.lower()
def test_memory_server_vs_memory_server_subcommand(self):
"""Test that both memory-server and memory server provide similar functionality."""
# Test memory-server --help
result1 = subprocess.run(
["uv", "run", "memory-server", "--help"],
capture_output=True,
text=True,
cwd=current_dir.parent.parent
)
# Test memory server --help
result2 = subprocess.run(
["uv", "run", "memory", "server", "--help"],
capture_output=True,
text=True,
cwd=current_dir.parent.parent
)
assert result1.returncode == 0
assert result2.returncode == 0
# Both should mention debug option
assert "--debug" in result1.stdout
assert "--debug" in result2.stdout
# Note: --chroma-path removed in v8.0.0
def test_compatibility_wrapper_deprecation_warning(self):
"""Test that the compatibility wrapper issues deprecation warnings."""
# Capture warnings when calling memory_server_main
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always") # Ensure all warnings are caught
# Mock sys.argv to test argument parsing
original_argv = sys.argv
try:
sys.argv = ["memory-server", "--version"]
# This will raise SystemExit due to --version, which is expected
with pytest.raises(SystemExit) as exc_info:
memory_server_main()
assert exc_info.value.code == 0 # Should exit successfully
finally:
sys.argv = original_argv
# Check that deprecation warning was issued
deprecation_warnings = [warning for warning in w if issubclass(warning.category, DeprecationWarning)]
assert len(deprecation_warnings) > 0
assert "deprecated" in str(deprecation_warnings[0].message).lower()
assert "memory server" in str(deprecation_warnings[0].message)
def test_argument_compatibility(self):
"""Test that arguments are properly passed through compatibility wrapper."""
# Test with --debug flag
result = subprocess.run(
["uv", "run", "memory-server", "--help"],
capture_output=True,
text=True,
cwd=current_dir.parent.parent
)
assert result.returncode == 0
assert "--debug" in result.stdout
assert "--version" in result.stdout
# Note: --chroma-path removed in v8.0.0
def test_no_cli_conflicts_during_import(self):
"""Test that importing CLI modules doesn't cause conflicts."""
try:
# These imports should work without conflicts
from mcp_memory_service.cli.main import main, memory_server_main
from mcp_memory_service import server
# Check that functions exist and are callable
assert callable(main)
assert callable(memory_server_main)
assert callable(server.main)
# Should not raise any import errors or conflicts
except ImportError as e:
pytest.fail(f"CLI import conflict detected: {str(e)}")
class TestCLIFunctionality:
"""Test actual CLI functionality to ensure commands work end-to-end."""
def test_memory_status_command(self):
"""Test that memory status command works."""
result = subprocess.run(
["uv", "run", "memory", "status"],
capture_output=True,
text=True,
timeout=30,
cwd=current_dir.parent.parent
)
# Status command might fail if no storage is available, but should not crash
# Return code 0 = success, 1 = expected error (e.g., no storage)
assert result.returncode in [0, 1]
if result.returncode == 0:
assert "MCP Memory Service Status" in result.stdout
assert "Version: 8.24.0" in result.stdout
else:
# If it fails, should have a meaningful error message
assert len(result.stderr) > 0 or len(result.stdout) > 0
class TestCLIRobustness:
"""Test CLI robustness and edge cases."""
def test_environment_variable_passing(self):
"""Test that CLI arguments correctly set environment variables."""
import os
import subprocess
# Test that --debug flag affects application behavior
env = os.environ.copy()
env.pop('MCP_DEBUG', None) # Ensure not already set
result = subprocess.run(
["uv", "run", "python", "-c", """
import os
import sys
from mcp_memory_service.cli.main import cli
from click.testing import CliRunner
# Test that --debug flag is recognized and sets debug mode
runner = CliRunner()
result = runner.invoke(cli, ['server', '--debug', '--help'])
print(f'EXIT_CODE:{result.exit_code}')
print(f'DEBUG_IN_OUTPUT:{\"--debug\" in result.output}')
"""],
capture_output=True,
text=True,
cwd=current_dir.parent.parent,
env=env,
timeout=10
)
# Should succeed and recognize --debug flag
assert result.returncode == 0
assert 'EXIT_CODE:0' in result.stdout
assert 'DEBUG_IN_OUTPUT:True' in result.stdout
def test_cli_error_handling(self):
"""Test that CLI handles errors gracefully."""
# Test invalid storage backend
result = subprocess.run(
["uv", "run", "memory", "server", "--storage-backend", "invalid"],
capture_output=True,
text=True,
cwd=current_dir.parent.parent,
timeout=10
)
# Should fail with clear error message
assert result.returncode != 0
assert len(result.stderr) > 0 or "invalid" in result.stdout.lower()
def test_memory_server_argument_parity(self):
"""Test that memory-server and memory server support the same core arguments."""
import subprocess
# Test memory server arguments
result1 = subprocess.run(
["uv", "run", "memory", "server", "--help"],
capture_output=True,
text=True,
cwd=current_dir.parent.parent
)
# Test memory-server arguments
result2 = subprocess.run(
["uv", "run", "memory-server", "--help"],
capture_output=True,
text=True,
cwd=current_dir.parent.parent
)
assert result1.returncode == 0
assert result2.returncode == 0
# Both should support debug argument
assert "--debug" in result1.stdout
assert "--debug" in result2.stdout
# Note: --chroma-path removed in v8.0.0
def test_entry_point_isolation(self):
"""Test that different entry points don't interfere with each other."""
# Test that we can import all entry points without conflicts
try:
# Import main CLI
from mcp_memory_service.cli.main import main, memory_server_main
# Import server main
from mcp_memory_service.server import main as server_main
# Verify they're different functions
assert main != memory_server_main
assert main != server_main
assert memory_server_main != server_main
# Verify they're all callable
assert callable(main)
assert callable(memory_server_main)
assert callable(server_main)
except ImportError as e:
pytest.fail(f"Entry point isolation failed: {e}")
def test_backward_compatibility_deprecation_warning(self):
"""Test that using 'memory' without subcommand shows deprecation warning."""
import warnings
import sys
from mcp_memory_service.cli.main import cli
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
# Mock sys.argv to test backward compatibility
original_argv = sys.argv
try:
# This simulates 'uv run memory' without subcommand
sys.argv = ["memory"]
with pytest.raises(SystemExit): # Server will try to start and exit
cli(standalone_mode=False)
except Exception:
# Expected - server can't actually start in test environment
pass
finally:
sys.argv = original_argv
# Verify backward compatibility deprecation warning
deprecation_warnings = [warning for warning in w if issubclass(warning.category, DeprecationWarning)]
assert len(deprecation_warnings) > 0
warning_msg = str(deprecation_warnings[0].message)
assert "without a subcommand is deprecated" in warning_msg
assert "memory server" in warning_msg
assert "backward compatibility will be removed" in warning_msg
def test_deprecation_warning_format(self):
"""Test that deprecation warning has proper format and information."""
import warnings
import sys
from mcp_memory_service.cli.main import memory_server_main
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
# Mock sys.argv for the compatibility wrapper
original_argv = sys.argv
try:
sys.argv = ["memory-server", "--version"]
with pytest.raises(SystemExit): # --version causes system exit
memory_server_main()
finally:
sys.argv = original_argv
# Verify deprecation warning content
deprecation_warnings = [warning for warning in w if issubclass(warning.category, DeprecationWarning)]
assert len(deprecation_warnings) > 0
warning_msg = str(deprecation_warnings[0].message)
assert "memory-server" in warning_msg.lower()
assert "deprecated" in warning_msg.lower()
assert "memory server" in warning_msg.lower()
assert "removed" in warning_msg.lower()
class TestCLIPerformance:
"""Test CLI performance characteristics."""
def test_cli_startup_time(self):
"""Test that CLI commands start reasonably quickly."""
import time
import subprocess
start_time = time.time()
result = subprocess.run(
["uv", "run", "memory", "--help"],
capture_output=True,
text=True,
timeout=30,
cwd=current_dir.parent.parent
)
elapsed = time.time() - start_time
assert result.returncode == 0
# CLI help should respond within 30 seconds (generous timeout for CI)
assert elapsed < 30
# Log performance for monitoring
print(f"CLI startup took {elapsed:.2f} seconds")
def test_memory_version_performance(self):
"""Test that version commands are fast."""
import time
import subprocess
# Test both version commands
commands = [
["uv", "run", "memory", "--version"],
["uv", "run", "memory-server", "--version"]
]
for cmd in commands:
start_time = time.time()
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=15,
cwd=current_dir.parent.parent
)
elapsed = time.time() - start_time
assert result.returncode == 0
assert "8.24.0" in result.stdout
# Version should be very fast
assert elapsed < 15
print(f"Version command {' '.join(cmd[2:])} took {elapsed:.2f} seconds")
if __name__ == "__main__":
pytest.main([__file__, "-v"])
```
--------------------------------------------------------------------------------
/scripts/validation/validate_configuration_complete.py:
--------------------------------------------------------------------------------
```python
#!/usr/bin/env python3
"""
Comprehensive Configuration Validation Script for MCP Memory Service
This unified script validates all configuration aspects:
- Claude Code global configuration (~/.claude.json)
- Claude Desktop configuration (claude_desktop_config.json)
- Project .env file configuration
- Cross-configuration consistency
- API token validation
- Cloudflare credentials validation
Consolidates functionality from validate_config.py and validate_configuration.py
"""
import os
import sys
import json
import re
import logging
from pathlib import Path
from typing import Dict, Any, Optional, List, Tuple
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
logger = logging.getLogger(__name__)
class ComprehensiveConfigValidator:
"""Unified configuration validator for all MCP Memory Service configurations."""
def __init__(self):
"""Initialize validator with all configuration paths and requirements."""
self.project_root = Path(__file__).parent.parent.parent
self.env_file = self.project_root / '.env'
# Platform-specific Claude Desktop config paths
if os.name == 'nt': # Windows
self.claude_desktop_config_file = Path.home() / 'AppData' / 'Roaming' / 'Claude' / 'claude_desktop_config.json'
else: # macOS/Linux
self.claude_desktop_config_file = Path.home() / '.config' / 'claude' / 'claude_desktop_config.json'
# Claude Code global config (different from Claude Desktop)
self.claude_code_config_file = Path.home() / '.claude.json'
# Local project MCP config (should usually not exist for memory service)
self.local_mcp_config_file = self.project_root / '.mcp.json'
# Required environment variables for Cloudflare backend
self.required_vars = [
'MCP_MEMORY_STORAGE_BACKEND',
'CLOUDFLARE_API_TOKEN',
'CLOUDFLARE_ACCOUNT_ID',
'CLOUDFLARE_D1_DATABASE_ID',
'CLOUDFLARE_VECTORIZE_INDEX'
]
# Optional but commonly used variables
self.optional_vars = [
'MCP_MEMORY_BACKUPS_PATH',
'MCP_MEMORY_SQLITE_PATH'
]
# Results tracking
self.issues = []
self.error_count = 0
self.warning_count = 0
self.success_count = 0
def load_json_safe(self, file_path: Path) -> Optional[Dict]:
"""Load JSON file safely, return None if not found or invalid."""
try:
if file_path.exists():
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except (json.JSONDecodeError, FileNotFoundError, PermissionError) as e:
self.add_warning(f"Could not load {file_path}: {e}")
return None
def load_env_file(self) -> Dict[str, str]:
"""Load environment variables from .env file."""
env_vars = {}
if not self.env_file.exists():
self.add_warning(f"No .env file found at {self.env_file}")
return env_vars
try:
with open(self.env_file, 'r', encoding='utf-8') as f:
for line_num, line in enumerate(f, 1):
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
env_vars[key.strip()] = value.strip()
except Exception as e:
self.add_error(f"Failed to load .env file: {e}")
return env_vars
def add_error(self, message: str):
"""Add error message and increment counter."""
self.issues.append(f"ERROR: {message}")
self.error_count += 1
def add_warning(self, message: str):
"""Add warning message and increment counter."""
self.issues.append(f"WARNING: {message}")
self.warning_count += 1
def add_success(self, message: str):
"""Add success message and increment counter."""
self.issues.append(f"SUCCESS: {message}")
self.success_count += 1
def validate_env_file(self) -> Dict[str, str]:
"""Validate .env file configuration."""
env_vars = self.load_env_file()
# Check for required variables
missing_vars = []
for var in self.required_vars:
if var not in env_vars or not env_vars[var].strip():
missing_vars.append(var)
if missing_vars:
self.add_error(f"Missing required variables in .env file: {missing_vars}")
else:
self.add_success("All required variables present in .env file")
# Check backend setting
backend = env_vars.get('MCP_MEMORY_STORAGE_BACKEND', '').lower()
if backend == 'cloudflare':
self.add_success(".env file configured for Cloudflare backend")
elif backend:
self.add_warning(f".env file configured for '{backend}' backend (not Cloudflare)")
else:
self.add_error("MCP_MEMORY_STORAGE_BACKEND not set in .env file")
return env_vars
def validate_claude_desktop_config(self) -> Optional[Dict[str, str]]:
"""Validate Claude Desktop configuration."""
config = self.load_json_safe(self.claude_desktop_config_file)
if not config:
self.add_error(f"Could not load Claude Desktop config from {self.claude_desktop_config_file}")
return None
# Extract memory server configuration
mcp_servers = config.get('mcpServers', {})
memory_server = mcp_servers.get('memory', {})
if not memory_server:
self.add_error("Memory server not found in Claude Desktop configuration")
return None
self.add_success("Memory server found in Claude Desktop configuration")
# Get environment variables from memory server config
memory_env = memory_server.get('env', {})
# Check required variables
missing_vars = []
for var in self.required_vars:
if var not in memory_env or not str(memory_env[var]).strip():
missing_vars.append(var)
if missing_vars:
self.add_error(f"Missing required variables in Claude Desktop config: {missing_vars}")
else:
self.add_success("All required variables present in Claude Desktop config")
return memory_env
def validate_claude_code_config(self):
"""Validate Claude Code global configuration (different from Claude Desktop)."""
config = self.load_json_safe(self.claude_code_config_file)
if not config:
self.add_warning(f"Claude Code config not found at {self.claude_code_config_file} (this is optional)")
return
# Check for memory server configurations in projects
memory_configs = []
projects = config.get('projects', {})
for project_path, project_config in projects.items():
mcp_servers = project_config.get('mcpServers', {})
if 'memory' in mcp_servers:
memory_config = mcp_servers['memory']
backend = memory_config.get('env', {}).get('MCP_MEMORY_STORAGE_BACKEND', 'unknown')
memory_configs.append((project_path, backend))
if memory_configs:
cloudflare_configs = [cfg for cfg in memory_configs if cfg[1] == 'cloudflare']
non_cloudflare_configs = [cfg for cfg in memory_configs if cfg[1] != 'cloudflare']
if cloudflare_configs:
self.add_success(f"Found {len(cloudflare_configs)} Cloudflare memory configurations in Claude Code")
if non_cloudflare_configs:
self.add_warning(f"Found {len(non_cloudflare_configs)} non-Cloudflare memory configurations in Claude Code")
else:
self.add_warning("No memory server configurations found in Claude Code (this is optional)")
def validate_local_mcp_config(self):
"""Check for conflicting local .mcp.json files."""
if self.local_mcp_config_file.exists():
config = self.load_json_safe(self.local_mcp_config_file)
if config and 'memory' in config.get('mcpServers', {}):
self.add_error("Local .mcp.json contains memory server configuration (conflicts with global config)")
else:
self.add_success("Local .mcp.json exists but does not conflict with memory configuration")
else:
self.add_success("No local .mcp.json found (using global configuration)")
def compare_configurations(self, env_config: Dict[str, str], claude_desktop_config: Optional[Dict[str, str]]):
"""Compare configurations between .env and Claude Desktop config."""
if not claude_desktop_config:
self.add_error("Cannot compare configurations - Claude Desktop config not available")
return
# Compare each required variable
differences = []
for var in self.required_vars:
env_value = env_config.get(var, '<MISSING>')
claude_value = str(claude_desktop_config.get(var, '<MISSING>'))
if env_value != claude_value:
differences.append((var, env_value, claude_value))
if differences:
self.add_warning(f"Found {len(differences)} configuration differences between .env and Claude Desktop config:")
for var, env_val, claude_val in differences:
self.add_warning(f" {var}: .env='{env_val[:50]}...' vs Claude='{claude_val[:50]}...'")
else:
self.add_success("All configurations match between .env and Claude Desktop config")
def validate_api_token_format(self, token: str) -> Tuple[bool, str]:
"""Validate API token format and detect known invalid tokens."""
if not token or token == '<MISSING>':
return False, "Token is missing"
if len(token) < 20:
return False, "Token appears too short"
if not any(c.isalnum() for c in token):
return False, "Token should contain alphanumeric characters"
# Check for known placeholder/invalid tokens
invalid_tokens = [
'your_token_here',
'replace_with_token',
'mkdXbb-iplcHNBRQ5tfqV3Sh_7eALYBpO4e3Di1m' # Known invalid token
]
if token in invalid_tokens:
return False, "Token appears to be a placeholder or known invalid token"
return True, "Token format appears valid"
def validate_api_tokens(self, env_config: Dict[str, str], claude_desktop_config: Optional[Dict[str, str]]):
"""Validate API tokens in both configurations."""
# Check .env token
env_token = env_config.get('CLOUDFLARE_API_TOKEN', '')
is_valid, message = self.validate_api_token_format(env_token)
if is_valid:
self.add_success(f".env API token format: {message}")
else:
self.add_error(f".env API token: {message}")
# Check Claude Desktop token
if claude_desktop_config:
claude_token = str(claude_desktop_config.get('CLOUDFLARE_API_TOKEN', ''))
is_valid, message = self.validate_api_token_format(claude_token)
if is_valid:
self.add_success(f"Claude Desktop API token format: {message}")
else:
self.add_error(f"Claude Desktop API token: {message}")
def check_environment_conflicts(self):
"""Check for conflicting environment configurations."""
# Check for conflicting .env files
env_files = list(self.project_root.glob('.env*'))
# Exclude legitimate backup files
conflicting_files = [
f for f in env_files
if f.name.endswith('.sqlite') and not f.name.endswith('.backup') and f.name != '.env.sqlite'
]
if conflicting_files:
self.add_warning(f"Potentially conflicting environment files found: {[str(f.name) for f in conflicting_files]}")
else:
self.add_success("No conflicting environment files detected")
def run_comprehensive_validation(self) -> bool:
"""Run complete configuration validation across all sources."""
print("Comprehensive MCP Memory Service Configuration Validation")
print("=" * 70)
# 1. Environment file validation
print("\n1. Environment File (.env) Validation:")
env_config = self.validate_env_file()
self._print_section_results()
# 2. Claude Desktop configuration validation
print("\n2. Claude Desktop Configuration Validation:")
claude_desktop_config = self.validate_claude_desktop_config()
self._print_section_results()
# 3. Claude Code configuration validation (optional)
print("\n3. Claude Code Global Configuration Check:")
self.validate_claude_code_config()
self._print_section_results()
# 4. Local MCP configuration check
print("\n4. Local Project Configuration Check:")
self.validate_local_mcp_config()
self._print_section_results()
# 5. Cross-configuration comparison
print("\n5. Cross-Configuration Consistency Check:")
self.compare_configurations(env_config, claude_desktop_config)
self._print_section_results()
# 6. API token validation
print("\n6. API Token Validation:")
self.validate_api_tokens(env_config, claude_desktop_config)
self._print_section_results()
# 7. Environment conflicts check
print("\n7. Environment Conflicts Check:")
self.check_environment_conflicts()
self._print_section_results()
# Final summary
self._print_final_summary()
return self.error_count == 0
def _print_section_results(self):
"""Print results for the current section."""
# Print only new issues since last call
current_total = len(self.issues)
if hasattr(self, '_last_printed_index'):
start_index = self._last_printed_index
else:
start_index = 0
for issue in self.issues[start_index:]:
print(f" {issue}")
self._last_printed_index = current_total
def _print_final_summary(self):
"""Print comprehensive final summary."""
print("\n" + "=" * 70)
print("VALIDATION SUMMARY")
print("=" * 70)
if self.error_count == 0:
print("CONFIGURATION VALIDATION PASSED!")
print(f" SUCCESS: {self.success_count} checks passed")
if self.warning_count > 0:
print(f" WARNING: {self.warning_count} warnings (non-critical)")
print("\nYour MCP Memory Service configuration appears to be correct.")
print("You should be able to use the memory service with Cloudflare backend.")
else:
print("CONFIGURATION VALIDATION FAILED!")
print(f" ERROR: {self.error_count} critical errors found")
print(f" WARNING: {self.warning_count} warnings")
print(f" SUCCESS: {self.success_count} checks passed")
print("\nPlease fix the critical errors above before using the memory service.")
print(f"\nConfiguration files checked:")
print(f" • .env file: {self.env_file}")
print(f" • Claude Desktop config: {self.claude_desktop_config_file}")
print(f" • Claude Code config: {self.claude_code_config_file}")
print(f" • Local MCP config: {self.local_mcp_config_file}")
def main():
"""Main validation function."""
validator = ComprehensiveConfigValidator()
success = validator.run_comprehensive_validation()
return 0 if success else 1
if __name__ == "__main__":
sys.exit(main())
```
--------------------------------------------------------------------------------
/claude-hooks/core/session-end.js:
--------------------------------------------------------------------------------
```javascript
/**
* Claude Code Session End Hook
* Automatically consolidates session outcomes and stores them as memories
*/
const fs = require('fs').promises;
const path = require('path');
const https = require('https');
const http = require('http');
// Import utilities
const { detectProjectContext } = require('../utilities/project-detector');
const { formatSessionConsolidation } = require('../utilities/context-formatter');
/**
* Load hook configuration
*/
async function loadConfig() {
try {
const configPath = path.join(__dirname, '../config.json');
const configData = await fs.readFile(configPath, 'utf8');
return JSON.parse(configData);
} catch (error) {
console.warn('[Memory Hook] Using default configuration:', error.message);
return {
memoryService: {
http: {
endpoint: 'http://127.0.0.1:8000',
apiKey: 'test-key-123'
},
defaultTags: ['claude-code', 'auto-generated'],
enableSessionConsolidation: true
},
sessionAnalysis: {
extractTopics: true,
extractDecisions: true,
extractInsights: true,
extractCodeChanges: true,
extractNextSteps: true,
minSessionLength: 100 // Minimum characters for meaningful session
}
};
}
}
/**
* Analyze conversation to extract key information
*/
function analyzeConversation(conversationData) {
try {
const analysis = {
topics: [],
decisions: [],
insights: [],
codeChanges: [],
nextSteps: [],
sessionLength: 0,
confidence: 0
};
if (!conversationData || !conversationData.messages) {
return analysis;
}
const messages = conversationData.messages;
const conversationText = messages.map(msg => msg.content || '').join('\n').toLowerCase();
analysis.sessionLength = conversationText.length;
// Extract topics (simple keyword matching)
const topicKeywords = {
'implementation': /implement|implementing|implementation|build|building|create|creating/g,
'debugging': /debug|debugging|bug|error|fix|fixing|issue|problem/g,
'architecture': /architecture|design|structure|pattern|framework|system/g,
'performance': /performance|optimization|speed|memory|efficient|faster/g,
'testing': /test|testing|unit test|integration|coverage|spec/g,
'deployment': /deploy|deployment|production|staging|release/g,
'configuration': /config|configuration|setup|environment|settings/g,
'database': /database|db|sql|query|schema|migration/g,
'api': /api|endpoint|rest|graphql|service|interface/g,
'ui': /ui|interface|frontend|component|styling|css|html/g
};
Object.entries(topicKeywords).forEach(([topic, regex]) => {
if (conversationText.match(regex)) {
analysis.topics.push(topic);
}
});
// Extract decisions (look for decision language)
const decisionPatterns = [
/decided to|decision to|chose to|choosing|will use|going with/g,
/better to|prefer|recommend|should use|opt for/g,
/concluded that|determined that|agreed to/g
];
messages.forEach(msg => {
const content = (msg.content || '').toLowerCase();
decisionPatterns.forEach(pattern => {
const matches = content.match(pattern);
if (matches) {
// Extract sentences containing decisions
const sentences = msg.content.split(/[.!?]+/);
sentences.forEach(sentence => {
if (pattern.test(sentence.toLowerCase()) && sentence.length > 20) {
analysis.decisions.push(sentence.trim());
}
});
}
});
});
// Extract insights (look for learning language)
const insightPatterns = [
/learned that|discovered|realized|found out|turns out/g,
/insight|understanding|conclusion|takeaway|lesson/g,
/important to note|key finding|observation/g
];
messages.forEach(msg => {
const content = (msg.content || '').toLowerCase();
insightPatterns.forEach(pattern => {
if (pattern.test(content)) {
const sentences = msg.content.split(/[.!?]+/);
sentences.forEach(sentence => {
if (pattern.test(sentence.toLowerCase()) && sentence.length > 20) {
analysis.insights.push(sentence.trim());
}
});
}
});
});
// Extract code changes (look for technical implementations)
const codePatterns = [
/added|created|implemented|built|wrote/g,
/modified|updated|changed|refactored|improved/g,
/fixed|resolved|corrected|patched/g
];
messages.forEach(msg => {
const content = msg.content || '';
if (content.includes('```') || /\.(js|py|rs|go|java|cpp|c|ts|jsx|tsx)/.test(content)) {
// This message contains code
const lowerContent = content.toLowerCase();
codePatterns.forEach(pattern => {
if (pattern.test(lowerContent)) {
const sentences = content.split(/[.!?]+/);
sentences.forEach(sentence => {
if (pattern.test(sentence.toLowerCase()) && sentence.length > 15) {
analysis.codeChanges.push(sentence.trim());
}
});
}
});
}
});
// Extract next steps (look for future language)
const nextStepsPatterns = [
/next|todo|need to|should|will|plan to|going to/g,
/follow up|continue|proceed|implement next|work on/g,
/remaining|still need|outstanding|future/g
];
messages.forEach(msg => {
const content = (msg.content || '').toLowerCase();
nextStepsPatterns.forEach(pattern => {
if (pattern.test(content)) {
const sentences = msg.content.split(/[.!?]+/);
sentences.forEach(sentence => {
if (pattern.test(sentence.toLowerCase()) && sentence.length > 15) {
analysis.nextSteps.push(sentence.trim());
}
});
}
});
});
// Calculate confidence based on extracted information
const totalExtracted = analysis.topics.length + analysis.decisions.length +
analysis.insights.length + analysis.codeChanges.length +
analysis.nextSteps.length;
analysis.confidence = Math.min(1.0, totalExtracted / 10); // Max confidence at 10+ items
// Limit arrays to prevent overwhelming output
analysis.topics = analysis.topics.slice(0, 5);
analysis.decisions = analysis.decisions.slice(0, 3);
analysis.insights = analysis.insights.slice(0, 3);
analysis.codeChanges = analysis.codeChanges.slice(0, 4);
analysis.nextSteps = analysis.nextSteps.slice(0, 4);
return analysis;
} catch (error) {
console.error('[Memory Hook] Error analyzing conversation:', error.message);
return {
topics: [],
decisions: [],
insights: [],
codeChanges: [],
nextSteps: [],
sessionLength: 0,
confidence: 0,
error: error.message
};
}
}
/**
* Store session consolidation to memory service
*/
function storeSessionMemory(endpoint, apiKey, content, projectContext, analysis) {
return new Promise((resolve, reject) => {
const url = new URL('/api/memories', endpoint);
const isHttps = url.protocol === 'https:';
const requestModule = isHttps ? https : http;
// Generate tags based on analysis and project context
const tags = [
'claude-code-session',
'session-consolidation',
projectContext.name,
`language:${projectContext.language}`,
...analysis.topics.slice(0, 3), // Top 3 topics as tags
...projectContext.frameworks.slice(0, 2), // Top 2 frameworks
`confidence:${Math.round(analysis.confidence * 100)}`
].filter(Boolean);
const postData = JSON.stringify({
content: content,
tags: tags,
memory_type: 'session-summary',
metadata: {
session_analysis: {
topics: analysis.topics,
decisions_count: analysis.decisions.length,
insights_count: analysis.insights.length,
code_changes_count: analysis.codeChanges.length,
next_steps_count: analysis.nextSteps.length,
session_length: analysis.sessionLength,
confidence: analysis.confidence
},
project_context: {
name: projectContext.name,
language: projectContext.language,
frameworks: projectContext.frameworks
},
generated_by: 'claude-code-session-end-hook',
generated_at: new Date().toISOString()
}
});
const options = {
hostname: url.hostname,
port: url.port || (isHttps ? 8443 : 8000),
path: url.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData),
'Authorization': `Bearer ${apiKey}`
}
};
// Only set rejectUnauthorized for HTTPS
if (isHttps) {
options.rejectUnauthorized = false; // For self-signed certificates
}
const req = requestModule.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
const response = JSON.parse(data);
resolve(response);
} catch (parseError) {
resolve({ success: false, error: 'Parse error', data });
}
});
});
req.on('error', (error) => {
resolve({ success: false, error: error.message });
});
req.write(postData);
req.end();
});
}
/**
* Main session end hook function
*/
async function onSessionEnd(context) {
try {
console.log('[Memory Hook] Session ending - consolidating outcomes...');
// Load configuration
const config = await loadConfig();
if (!config.memoryService.enableSessionConsolidation) {
console.log('[Memory Hook] Session consolidation disabled in config');
return;
}
// Check if session is meaningful enough to store
if (context.conversation && context.conversation.messages) {
const totalLength = context.conversation.messages
.map(msg => (msg.content || '').length)
.reduce((sum, len) => sum + len, 0);
if (totalLength < config.sessionAnalysis.minSessionLength) {
console.log('[Memory Hook] Session too short for consolidation');
return;
}
}
// Detect project context
const projectContext = await detectProjectContext(context.workingDirectory || process.cwd());
console.log(`[Memory Hook] Consolidating session for project: ${projectContext.name}`);
// Analyze conversation
const analysis = analyzeConversation(context.conversation);
if (analysis.confidence < 0.1) {
console.log('[Memory Hook] Session analysis confidence too low, skipping consolidation');
return;
}
console.log(`[Memory Hook] Session analysis: ${analysis.topics.length} topics, ${analysis.decisions.length} decisions, confidence: ${(analysis.confidence * 100).toFixed(1)}%`);
// Format session consolidation
const consolidation = formatSessionConsolidation(analysis, projectContext);
// Get endpoint and apiKey from new config structure
const endpoint = config.memoryService?.http?.endpoint || config.memoryService?.endpoint || 'http://127.0.0.1:8000';
const apiKey = config.memoryService?.http?.apiKey || config.memoryService?.apiKey || 'test-key-123';
// Store to memory service
const result = await storeSessionMemory(
endpoint,
apiKey,
consolidation,
projectContext,
analysis
);
if (result.success || result.content_hash) {
console.log(`[Memory Hook] Session consolidation stored successfully`);
if (result.content_hash) {
console.log(`[Memory Hook] Memory hash: ${result.content_hash.substring(0, 8)}...`);
}
} else {
console.warn('[Memory Hook] Failed to store session consolidation:', result.error || 'Unknown error');
}
} catch (error) {
console.error('[Memory Hook] Error in session end:', error.message);
// Fail gracefully - don't prevent session from ending
}
}
/**
* Hook metadata for Claude Code
*/
module.exports = {
name: 'memory-awareness-session-end',
version: '1.0.0',
description: 'Automatically consolidate and store session outcomes',
trigger: 'session-end',
handler: onSessionEnd,
config: {
async: true,
timeout: 15000, // 15 second timeout
priority: 'normal'
}
};
// Direct execution support for testing
if (require.main === module) {
// Test the hook with mock context
const mockConversation = {
messages: [
{
role: 'user',
content: 'I need to implement a memory awareness system for Claude Code'
},
{
role: 'assistant',
content: 'I\'ll help you create a memory awareness system. We decided to use hooks for session management and implement automatic context injection.'
},
{
role: 'user',
content: 'Great! I learned that we need project detection and memory scoring algorithms.'
},
{
role: 'assistant',
content: 'Exactly. I implemented the project detector in project-detector.js and created scoring algorithms. Next we need to test the complete system.'
}
]
};
const mockContext = {
workingDirectory: process.cwd(),
sessionId: 'test-session',
conversation: mockConversation
};
onSessionEnd(mockContext)
.then(() => console.log('Session end hook test completed'))
.catch(error => console.error('Session end hook test failed:', error));
}
```
--------------------------------------------------------------------------------
/archive/docs-removed-2025-08-23/development/multi-client-architecture.md:
--------------------------------------------------------------------------------
```markdown
# Multi-Client Architecture Documentation
This document provides technical details about the multi-client architecture implementation in the MCP Memory Service, specifically focusing on the integrated setup functionality added to `install.py`.
## Overview
The multi-client architecture enables multiple MCP applications to safely share the same memory database concurrently. The latest implementation integrates this setup directly into the main installation process, making it universally accessible to any MCP-compatible application.
## Architecture Components
### 1. Client Detection System
#### Detection Strategy
The system uses a multi-pronged approach to detect MCP clients:
```python
def detect_mcp_clients():
"""Detect installed MCP-compatible applications."""
clients = {}
# Pattern-based detection for known applications
detection_patterns = {
'claude_desktop': [
Path.home() / "AppData" / "Roaming" / "Claude" / "claude_desktop_config.json", # Windows
Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json", # macOS
Path.home() / ".config" / "Claude" / "claude_desktop_config.json" # Linux
],
'vscode_mcp': [
# VS Code settings.json locations with MCP extension detection
],
'continue': [
# Continue IDE configuration locations
],
'generic_mcp': [
# Generic MCP configuration file locations
]
}
```
#### Detection Logic
1. **File-based Detection**: Checks for configuration files in standard locations
2. **Content Analysis**: Examines configuration files for MCP-related settings
3. **CLI Detection**: Tests for command-line tools (e.g., Claude Code)
4. **Extension Detection**: Identifies IDE extensions that support MCP
### 2. Configuration Management
#### Configuration Abstraction
Each client type has a dedicated configuration handler:
```python
class MCPClientConfigurator:
"""Base class for MCP client configuration."""
def detect(self) -> bool:
"""Detect if this client is installed."""
raise NotImplementedError
def configure(self, config: MCPConfig) -> bool:
"""Configure the client for multi-client access."""
raise NotImplementedError
def validate(self) -> bool:
"""Validate the configuration."""
raise NotImplementedError
class ClaudeDesktopConfigurator(MCPClientConfigurator):
"""Configure Claude Desktop for multi-client access."""
def configure(self, config: MCPConfig) -> bool:
# Update claude_desktop_config.json
pass
class ContinueIDEConfigurator(MCPClientConfigurator):
"""Configure Continue IDE for multi-client access."""
def configure(self, config: MCPConfig) -> bool:
# Update Continue configuration files
pass
```
#### Configuration Template System
Universal configuration templates ensure consistency:
```python
class MCPConfig:
"""Standard MCP configuration structure."""
def __init__(self, repo_path: str):
self.repo_path = repo_path
self.base_config = {
"command": "uv",
"args": ["--directory", repo_path, "run", "memory"],
"env": {
"MCP_MEMORY_STORAGE_BACKEND": "sqlite_vec",
"MCP_MEMORY_SQLITE_PRAGMAS": "busy_timeout=15000,cache_size=20000",
"LOG_LEVEL": "INFO"
}
}
def for_client(self, client_type: str) -> dict:
"""Generate client-specific configuration."""
config = self.base_config.copy()
if client_type == "claude_desktop":
# Claude Desktop specific adjustments
pass
elif client_type == "continue":
# Continue IDE specific adjustments
pass
return config
```
### 3. WAL Mode Coordination
#### SQLite WAL Implementation
Write-Ahead Logging mode enables safe concurrent access:
```python
async def test_wal_mode_coordination():
"""Test WAL mode storage coordination for multi-client access."""
# Create test database with WAL mode
storage = SqliteVecMemoryStorage(test_db_path)
await storage.initialize()
# WAL mode pragmas are applied in storage initialization:
# PRAGMA journal_mode=WAL;
# PRAGMA busy_timeout=15000;
# PRAGMA cache_size=20000;
# PRAGMA synchronous=NORMAL;
# Test concurrent access patterns
storage2 = SqliteVecMemoryStorage(test_db_path)
await storage2.initialize()
# Verify both can read/write safely
success = await test_concurrent_operations(storage, storage2)
return success
```
#### Concurrency Model
- **Multiple Readers**: Any number of clients can read simultaneously
- **Single Writer**: One client writes at a time, with automatic queuing
- **Retry Logic**: Exponential backoff for lock conflicts
- **Timeout Handling**: 15-second timeout prevents deadlocks
### 4. Integration Flow
#### Installation Integration Points
The multi-client setup integrates at specific points in the installation flow:
```python
def main():
"""Main installation function with multi-client integration."""
# 1. System detection and backend selection
system_info = detect_system()
final_backend = determine_backend(args, system_info)
# 2. Core installation (dependencies, package, paths)
install_success = install_package(args)
configure_paths(args)
verify_installation()
# 3. Multi-client integration point
if should_offer_multi_client_setup(args, final_backend):
if prompt_user_for_multi_client() or args.setup_multi_client:
setup_universal_multi_client_access(system_info, args)
# 4. Final configuration and completion
complete_installation()
```
#### Decision Logic
Multi-client setup is offered based on:
```python
def should_offer_multi_client_setup(args, final_backend):
"""Intelligent decision logic for multi-client offering."""
# Required: SQLite-vec backend (only backend supporting multi-client)
if final_backend != "sqlite_vec":
return False
# Skip in automated/server environments
if args.server_mode or args.skip_multi_client_prompt:
return False
# Always beneficial for development environments
return True
```
### 5. Error Handling and Fallbacks
#### Layered Error Handling
The system implements multiple fallback layers:
```python
def setup_universal_multi_client_access(system_info, args):
"""Configure multi-client access with comprehensive error handling."""
try:
# Layer 1: WAL mode validation
if not test_wal_mode_coordination():
raise MCPSetupError("WAL mode coordination test failed")
# Layer 2: Client detection and configuration
clients = detect_mcp_clients()
success_count = configure_detected_clients(clients, system_info)
# Layer 3: Environment setup
setup_shared_environment()
# Layer 4: Generic configuration (always succeeds)
provide_generic_configuration()
return True
except MCPSetupError as e:
# Graceful degradation: provide manual instructions
print_error(f"Automated setup failed: {e}")
provide_manual_setup_instructions()
return False
```
#### Fallback Mechanisms
1. **Automated → Manual**: If automated setup fails, provide manual instructions
2. **Specific → Generic**: If client-specific config fails, use generic templates
3. **Integrated → Standalone**: Direct users to standalone setup script
4. **Setup → Documentation**: Always provide comprehensive documentation
### 6. Extensibility Framework
#### Adding New Client Support
The architecture is designed for easy extension:
```python
# 1. Add detection pattern
def detect_new_client():
"""Detect NewClient MCP application."""
config_paths = [
# Platform-specific configuration file locations
]
for path in config_paths:
if path.exists() and is_mcp_configured(path):
return path
return None
# 2. Add configuration handler
def configure_new_client_multi_client(config_path):
"""Configure NewClient for multi-client access."""
try:
# Read existing configuration
config = read_client_config(config_path)
# Apply multi-client settings
config.update(generate_mcp_config())
# Write updated configuration
write_client_config(config_path, config)
print_info(" [OK] NewClient: Updated configuration")
return True
except Exception as e:
print_warning(f" -> NewClient configuration failed: {e}")
return False
# 3. Register in detection system
def detect_mcp_clients():
clients = {}
# ... existing detection logic ...
# Add new client detection
new_client_path = detect_new_client()
if new_client_path:
clients['new_client'] = new_client_path
return clients
```
#### Plugin Architecture Potential
The current implementation could evolve into a plugin system:
```python
class MCPClientPlugin:
"""Base class for MCP client plugins."""
name: str
priority: int
def detect(self) -> Optional[Path]:
"""Detect client installation."""
pass
def configure(self, config: MCPConfig, config_path: Path) -> bool:
"""Configure client for multi-client access."""
pass
def validate(self, config_path: Path) -> bool:
"""Validate client configuration."""
pass
# Plugin registration system
REGISTERED_PLUGINS = [
ClaudeDesktopPlugin(),
ContinueIDEPlugin(),
VSCodeMCPPlugin(),
CursorIDEPlugin(),
GenericMCPPlugin(), # Fallback plugin
]
```
## Technical Decisions
### Why SQLite-vec Only?
Multi-client support is limited to SQLite-vec backend because:
1. **WAL Mode Support**: SQLite's WAL mode provides robust concurrent access
2. **File-based Storage**: Single database file simplifies sharing
3. **Performance**: SQLite is optimized for multi-reader scenarios
4. **Reliability**: Well-tested concurrency mechanisms
5. **Simplicity**: No network coordination required
### Why Integrated Setup?
Integration into the main installer provides:
1. **Discoverability**: Users learn about multi-client capabilities during installation
2. **Convenience**: One-step setup for all MCP applications
3. **Consistency**: Uniform configuration across all clients
4. **Future-proofing**: Automatic support for new MCP applications
### Configuration Strategy
Direct configuration file modification was chosen over:
- **Environment variables only**: Would require manual client restart
- **Network-based coordination**: Adds complexity and failure points
- **Copy-paste instructions**: Reduces user experience and increases errors
## Performance Considerations
### Database Performance
- **WAL Mode**: Allows concurrent readers without blocking
- **Cache Size**: 20MB cache improves multi-client performance
- **Busy Timeout**: 15-second timeout prevents deadlocks
- **Synchronous Mode**: NORMAL mode balances safety and performance
### Memory Usage
- **Shared Database**: Single database reduces total memory usage
- **Connection Pooling**: Each client maintains its own connection pool
- **Cache Coordination**: WAL mode provides implicit cache coordination
### Startup Performance
- **Lazy Initialization**: Clients initialize storage on first use
- **Fast Detection**: Configuration file checking is optimized
- **Minimal Overhead**: Setup adds <1 second to installation time
## Security Considerations
### File System Security
- **Path Validation**: All configuration paths are validated before modification
- **Backup Creation**: Original configurations are backed up before changes
- **Permission Checks**: Write permissions verified before attempting changes
### Configuration Security
- **Template Validation**: All configuration templates are validated
- **Injection Prevention**: No user input is directly inserted into configurations
- **Safe Defaults**: Conservative defaults used for security-sensitive settings
## Testing Strategy
### Unit Testing
```python
def test_client_detection():
"""Test MCP client detection functionality."""
# Mock configuration files
with mock_config_files():
clients = detect_mcp_clients()
assert 'claude_desktop' in clients
assert 'continue' in clients
def test_configuration_generation():
"""Test MCP configuration generation."""
config = MCPConfig("/test/repo")
claude_config = config.for_client("claude_desktop")
assert claude_config["env"]["MCP_MEMORY_STORAGE_BACKEND"] == "sqlite_vec"
assert "busy_timeout" in claude_config["env"]["MCP_MEMORY_SQLITE_PRAGMAS"]
```
### Integration Testing
```python
async def test_multi_client_coordination():
"""Test actual multi-client database coordination."""
# Create test database
db_path = create_test_database()
# Initialize multiple storage instances
storage1 = SqliteVecMemoryStorage(db_path)
storage2 = SqliteVecMemoryStorage(db_path)
await storage1.initialize()
await storage2.initialize()
# Test concurrent operations
success = await test_concurrent_read_write(storage1, storage2)
assert success
```
### End-to-End Testing
```python
def test_full_installation_flow():
"""Test complete installation with multi-client setup."""
with temporary_environment():
# Run installer with multi-client setup
result = run_installer(["--setup-multi-client", "--storage-backend", "sqlite_vec"])
assert result.success
assert result.multi_client_configured
assert validate_client_configurations()
```
## Monitoring and Observability
### Logging Framework
```python
# Multi-client specific logging
logger = logging.getLogger("mcp.multi_client")
def setup_universal_multi_client_access(system_info, args):
logger.info("Starting universal multi-client setup")
clients = detect_mcp_clients()
logger.info(f"Detected {len(clients)} MCP clients: {list(clients.keys())}")
for client_type, config_path in clients.items():
try:
success = configure_client(client_type, config_path)
logger.info(f"Client {client_type} configuration: {'success' if success else 'failed'}")
except Exception as e:
logger.error(f"Client {client_type} configuration error: {e}")
```
### Metrics Collection
```python
# Installation metrics
class InstallationMetrics:
def __init__(self):
self.clients_detected = 0
self.clients_configured = 0
self.configuration_errors = []
self.setup_duration = 0
def record_client_detection(self, client_type: str):
self.clients_detected += 1
def record_configuration_success(self, client_type: str):
self.clients_configured += 1
def record_configuration_error(self, client_type: str, error: str):
self.configuration_errors.append((client_type, error))
```
## Future Enhancements
### Planned Improvements
1. **HTTP Coordination**: Advanced coordination for 3+ clients
2. **Configuration Validation**: Real-time validation of client configurations
3. **Auto-Updates**: Automatic configuration updates for new MCP versions
4. **Cloud Sync**: Multi-device memory synchronization
5. **Plugin System**: Formal plugin architecture for client support
### Research Areas
1. **Conflict Resolution**: Advanced merge strategies for concurrent edits
2. **Performance Optimization**: Database sharding for large-scale deployments
3. **Security Enhancements**: Encrypted inter-client communication
4. **Mobile Support**: Extension to mobile MCP applications
This architecture provides a robust, extensible foundation for universal multi-client support in the MCP Memory Service ecosystem.
```
--------------------------------------------------------------------------------
/docs/deployment/docker.md:
--------------------------------------------------------------------------------
```markdown
# Docker Deployment Guide
This comprehensive guide covers deploying MCP Memory Service using Docker, including various configurations for different use cases and environments.
## Overview
MCP Memory Service provides Docker support with multiple deployment configurations:
- **Standard Mode**: For MCP clients (Claude Desktop, VS Code, etc.)
- **Standalone Mode**: For testing and development (prevents boot loops)
- **HTTP/SSE Mode**: For web services and multi-client access
- **Production Mode**: For scalable server deployments
## Prerequisites
- **Docker** 20.10+ installed on your system
- **Docker Compose** 2.0+ (recommended for simplified deployment)
- Basic knowledge of Docker concepts
- Sufficient disk space for Docker images and container volumes
## Quick Start
### Using Docker Compose (Recommended)
```bash
# Clone the repository
git clone https://github.com/doobidoo/mcp-memory-service.git
cd mcp-memory-service
# Start with standard configuration
docker-compose up -d
# View logs
docker-compose logs -f
```
This will:
- Build a Docker image for the Memory Service
- Create persistent volumes for the database and backups
- Start the service configured for MCP clients
## Docker Compose Configurations
### 1. Standard Configuration (`docker-compose.yml`)
**Best for**: MCP clients like Claude Desktop, VS Code with MCP extension
```yaml
version: '3.8'
services:
mcp-memory-service:
build: .
stdin_open: true
tty: true
volumes:
- ./data/chroma_db:/app/chroma_db
- ./data/backups:/app/backups
environment:
- MCP_MEMORY_STORAGE_BACKEND=chromadb
restart: unless-stopped
```
```bash
# Deploy standard configuration
docker-compose up -d
```
### 2. Standalone Configuration (`docker-compose.standalone.yml`)
**Best for**: Testing, development, and preventing boot loops when no MCP client is connected
```yaml
version: '3.8'
services:
mcp-memory-service:
build: .
stdin_open: true
tty: true
ports:
- "8000:8000"
volumes:
- ./data/chroma_db:/app/chroma_db
- ./data/backups:/app/backups
environment:
- MCP_STANDALONE_MODE=1
- MCP_HTTP_HOST=0.0.0.0
- MCP_HTTP_PORT=8000
restart: unless-stopped
```
```bash
# Deploy standalone configuration
docker-compose -f docker-compose.standalone.yml up -d
# Test connectivity
curl http://localhost:8000/health
```
### 3. UV Configuration (`docker-compose.uv.yml`)
**Best for**: Enhanced dependency management with UV package manager
```yaml
version: '3.8'
services:
mcp-memory-service:
build: .
stdin_open: true
tty: true
ports:
- "8000:8000"
volumes:
- ./data/chroma_db:/app/chroma_db
- ./data/backups:/app/backups
environment:
- UV_ACTIVE=1
- MCP_MEMORY_STORAGE_BACKEND=sqlite_vec
restart: unless-stopped
```
### 4. Python Path Configuration (`docker-compose.pythonpath.yml`)
**Best for**: Custom Python path configurations and development mode
```bash
# Deploy with Python path configuration
docker-compose -f docker-compose.pythonpath.yml up -d
```
## Manual Docker Commands
### Basic Docker Deployment
```bash
# Build the Docker image
docker build -t mcp-memory-service .
# Create directories for persistent storage
mkdir -p ./data/chroma_db ./data/backups
# Run in standard mode (for MCP clients)
docker run -d --name memory-service \
-v $(pwd)/data/chroma_db:/app/chroma_db \
-v $(pwd)/data/backups:/app/backups \
-e MCP_MEMORY_STORAGE_BACKEND=chromadb \
--stdin --tty \
mcp-memory-service
# Run in standalone/HTTP mode
docker run -d -p 8000:8000 --name memory-service \
-v $(pwd)/data/chroma_db:/app/chroma_db \
-v $(pwd)/data/backups:/app/backups \
-e MCP_STANDALONE_MODE=1 \
-e MCP_HTTP_HOST=0.0.0.0 \
-e MCP_HTTP_PORT=8000 \
--stdin --tty \
mcp-memory-service
```
### Using Specific Docker Images
```bash
# Use pre-built Glama deployment image
docker run -d -p 8000:8000 \
-v $(pwd)/data:/app/data \
-e MCP_API_KEY=your-api-key \
--name memory-service \
mcp-memory-service:glama
# Use SQLite-vec optimized image
docker run -d -p 8000:8000 \
-v $(pwd)/data:/app/data \
-e MCP_MEMORY_STORAGE_BACKEND=sqlite_vec \
--name memory-service \
mcp-memory-service:sqlite-vec
```
## Environment Configuration
### Core Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `MCP_MEMORY_STORAGE_BACKEND` | `chromadb` | Storage backend (chromadb, sqlite_vec) |
| `MCP_HTTP_HOST` | `0.0.0.0` | HTTP server bind address |
| `MCP_HTTP_PORT` | `8000` | HTTP server port |
| `MCP_STANDALONE_MODE` | `false` | Enable standalone HTTP mode |
| `MCP_API_KEY` | `none` | API key for authentication |
### Docker-Specific Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `DOCKER_CONTAINER` | `auto-detect` | Indicates running in Docker |
| `UV_ACTIVE` | `false` | Use UV package manager |
| `PYTHONPATH` | `/app/src` | Python module search path |
### Storage Configuration
```bash
# ChromaDB backend
docker run -d \
-e MCP_MEMORY_STORAGE_BACKEND=chromadb \
-e MCP_MEMORY_CHROMA_PATH=/app/chroma_db \
-v $(pwd)/data/chroma_db:/app/chroma_db \
mcp-memory-service
# SQLite-vec backend (recommended for containers)
docker run -d \
-e MCP_MEMORY_STORAGE_BACKEND=sqlite_vec \
-e MCP_MEMORY_SQLITE_PATH=/app/sqlite_data/memory.db \
-v $(pwd)/data/sqlite_data:/app/sqlite_data \
mcp-memory-service
```
## Production Deployment
### Docker Swarm Deployment
```yaml
# docker-stack.yml
version: '3.8'
services:
mcp-memory-service:
image: mcp-memory-service:latest
ports:
- "8000:8000"
environment:
- MCP_MEMORY_STORAGE_BACKEND=sqlite_vec
- MCP_HTTP_HOST=0.0.0.0
- MCP_API_KEY=REDACTED
volumes:
- memory_data:/app/data
secrets:
- api_key
deploy:
replicas: 3
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
resources:
limits:
cpus: '1.0'
memory: 2G
reservations:
cpus: '0.5'
memory: 1G
volumes:
memory_data:
secrets:
api_key:
external: true
```
```bash
# Deploy to Docker Swarm
docker stack deploy -c docker-stack.yml mcp-memory
```
### Kubernetes Deployment
```yaml
# k8s-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-memory-service
spec:
replicas: 3
selector:
matchLabels:
app: mcp-memory-service
template:
metadata:
labels:
app: mcp-memory-service
spec:
containers:
- name: mcp-memory-service
image: mcp-memory-service:latest
ports:
- containerPort: 8000
env:
- name: MCP_MEMORY_STORAGE_BACKEND
value: "sqlite_vec"
- name: MCP_HTTP_HOST
value: "0.0.0.0"
- name: MCP_API_KEY
valueFrom:
secretKeyRef:
name: mcp-api-key
key: api-key
volumeMounts:
- name: data-volume
mountPath: /app/data
resources:
limits:
cpu: 1000m
memory: 2Gi
requests:
cpu: 500m
memory: 1Gi
volumes:
- name: data-volume
persistentVolumeClaim:
claimName: mcp-memory-pvc
---
apiVersion: v1
kind: Service
metadata:
name: mcp-memory-service
spec:
selector:
app: mcp-memory-service
ports:
- port: 80
targetPort: 8000
type: LoadBalancer
```
## Volume Management
### Data Persistence
```bash
# Create named volumes
docker volume create mcp_memory_data
docker volume create mcp_memory_backups
# Use named volumes
docker run -d \
-v mcp_memory_data:/app/data \
-v mcp_memory_backups:/app/backups \
mcp-memory-service
# Backup volumes
docker run --rm \
-v mcp_memory_data:/data \
-v $(pwd)/backup:/backup \
alpine tar czf /backup/mcp_memory_$(date +%Y%m%d).tar.gz /data
```
### Database Migration
```bash
# Export data from running container
docker exec memory-service python scripts/backup_memories.py
# Import data to new container
docker cp ./backup.json new-memory-service:/app/
docker exec new-memory-service python scripts/restore_memories.py /app/backup.json
```
## Monitoring and Logging
### Container Health Checks
```yaml
# Add to docker-compose.yml
services:
mcp-memory-service:
build: .
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
```
### Log Management
```bash
# View container logs
docker-compose logs -f mcp-memory-service
# Configure log rotation
docker-compose -f docker-compose.yml -f docker-compose.logging.yml up -d
```
```yaml
# docker-compose.logging.yml
version: '3.8'
services:
mcp-memory-service:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
```
### Monitoring with Prometheus
```yaml
# docker-compose.monitoring.yml
version: '3.8'
services:
mcp-memory-service:
environment:
- MCP_MEMORY_ENABLE_METRICS=true
- MCP_MEMORY_METRICS_PORT=9090
ports:
- "9090:9090"
prometheus:
image: prom/prometheus
ports:
- "9091:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
```
## Troubleshooting
### Common Docker Issues
#### 1. Container Boot Loop
**Symptom**: Container exits immediately with code 0
**Solution**: Use standalone mode or ensure proper TTY configuration:
```yaml
services:
mcp-memory-service:
stdin_open: true
tty: true
environment:
- MCP_STANDALONE_MODE=1
```
#### 2. Permission Issues
**Symptom**: Permission denied errors in container
**Solution**: Fix volume permissions:
```bash
# Set proper ownership
sudo chown -R 1000:1000 ./data
# Or run with specific user
docker run --user $(id -u):$(id -g) mcp-memory-service
```
#### 3. Storage Backend Issues
**Symptom**: Database initialization failures
**Solution**: Use SQLite-vec for containers:
```bash
docker run -d \
-e MCP_MEMORY_STORAGE_BACKEND=sqlite_vec \
-v $(pwd)/data:/app/data \
mcp-memory-service
```
#### 4. Network Connectivity
**Symptom**: Cannot connect to containerized service
**Solution**: Check port mapping and firewall:
```bash
# Test container networking
docker exec memory-service netstat -tlnp
# Check port mapping
docker port memory-service
# Test external connectivity
curl http://localhost:8000/health
```
#### 5. Model Download Issues
**Symptom**: `OSError: We couldn't connect to 'https://huggingface.co'` when starting container
**Issue**: Container cannot download sentence-transformer models due to network restrictions
**Solutions**:
1. **Pre-download models and mount cache (Recommended)**:
```bash
# Step 1: Download models on host machine first
python -c "from sentence_transformers import SentenceTransformer; \
model = SentenceTransformer('all-MiniLM-L6-v2'); \
print('Model downloaded successfully')"
# Step 2: Run container with model cache mounted
docker run -d --name memory-service \
-v ~/.cache/huggingface:/root/.cache/huggingface \
-v $(pwd)/data/chroma_db:/app/chroma_db \
-e MCP_MEMORY_STORAGE_BACKEND=chromadb \
mcp-memory-service
```
2. **Configure proxy for Docker Desktop (Windows/Corporate networks)**:
```bash
# With proxy environment variables
docker run -d --name memory-service \
-e HTTPS_PROXY=http://your-proxy:port \
-e HTTP_PROXY=http://your-proxy:port \
-e NO_PROXY=localhost,127.0.0.1 \
-v $(pwd)/data:/app/data \
mcp-memory-service
```
3. **Use offline mode with pre-cached models**:
```bash
# Ensure models are in mounted volume, then run offline
docker run -d --name memory-service \
-v ~/.cache/huggingface:/root/.cache/huggingface \
-e HF_HUB_OFFLINE=1 \
-e TRANSFORMERS_OFFLINE=1 \
-e HF_DATASETS_OFFLINE=1 \
-v $(pwd)/data:/app/data \
mcp-memory-service
```
4. **Docker Compose with model cache**:
```yaml
# docker-compose.yml
version: '3.8'
services:
mcp-memory-service:
build: .
volumes:
# Mount model cache from host
- ${HOME}/.cache/huggingface:/root/.cache/huggingface
- ./data/chroma_db:/app/chroma_db
- ./data/backups:/app/backups
environment:
- MCP_MEMORY_STORAGE_BACKEND=chromadb
# Optional: force offline mode if models are pre-cached
# - HF_HUB_OFFLINE=1
# - TRANSFORMERS_OFFLINE=1
```
**Prevention**: Always mount the Hugging Face cache directory as a volume to persist models between container runs and avoid re-downloading.
### Diagnostic Commands
#### Container Status
```bash
# Check container status
docker ps -a
# View container logs
docker logs memory-service
# Execute commands in container
docker exec -it memory-service bash
# Check resource usage
docker stats memory-service
```
#### Service Health
```bash
# Test HTTP endpoints
curl http://localhost:8000/health
curl http://localhost:8000/stats
# Check database connectivity
docker exec memory-service python -c "
from src.mcp_memory_service.storage.sqlite_vec import SqliteVecStorage
storage = SqliteVecStorage()
print('Database accessible')
"
```
#### Model Cache Verification
```bash
# Check if models are cached on host
ls -la ~/.cache/huggingface/hub/
# Verify model availability in container
docker exec memory-service ls -la /root/.cache/huggingface/hub/
# Test model loading in container
docker exec memory-service python -c "
from sentence_transformers import SentenceTransformer
try:
model = SentenceTransformer('all-MiniLM-L6-v2')
print('✅ Model loaded successfully')
except Exception as e:
print(f'❌ Model loading failed: {e}')
"
```
## Security Considerations
### API Key Authentication
```bash
# Generate secure API key
API_KEY=$(openssl rand -hex 32)
# Use with Docker
docker run -d \
-e MCP_API_KEY=$API_KEY \
-p 8000:8000 \
mcp-memory-service
```
### HTTPS Configuration
```yaml
# docker-compose.https.yml
services:
mcp-memory-service:
environment:
- MCP_HTTPS_ENABLED=true
- MCP_HTTP_PORT=8443
- MCP_SSL_CERT_FILE=/app/certs/cert.pem
- MCP_SSL_KEY_FILE=/app/certs/key.pem
volumes:
- ./certs:/app/certs:ro
ports:
- "8443:8443"
```
### Container Security
```bash
# Run with security options
docker run -d \
--security-opt no-new-privileges:true \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
--read-only \
--tmpfs /tmp \
mcp-memory-service
```
## Performance Optimization
### Resource Limits
```yaml
services:
mcp-memory-service:
deploy:
resources:
limits:
cpus: '2.0'
memory: 4G
reservations:
cpus: '1.0'
memory: 2G
```
### Multi-Stage Builds
```dockerfile
# Optimized Dockerfile
FROM python:3.11-slim as builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "src/mcp_memory_service/server.py"]
```
## Development Workflow
### Development with Docker
```bash
# Development with live reload
docker-compose -f docker-compose.dev.yml up
# Run tests in container
docker exec memory-service pytest tests/
# Debug with interactive shell
docker exec -it memory-service bash
```
### Building Custom Images
```bash
# Build with specific tag
docker build -t mcp-memory-service:v1.2.3 .
# Build for multiple platforms
docker buildx build --platform linux/amd64,linux/arm64 -t mcp-memory-service:latest .
# Push to registry
docker push mcp-memory-service:latest
```
## Related Documentation
- [Installation Guide](../installation/master-guide.md) - General installation instructions
- [Multi-Client Setup](../integration/multi-client.md) - Multi-client configuration
- [Ubuntu Setup](../platforms/ubuntu.md) - Ubuntu Docker deployment
- [Windows Setup](../platforms/windows.md) - Windows Docker deployment
- [Troubleshooting](../troubleshooting/general.md) - Docker-specific troubleshooting
```
--------------------------------------------------------------------------------
/docs/architecture/search-examples.md:
--------------------------------------------------------------------------------
```markdown
# Advanced Hybrid Search - Real-World Usage Examples
## API Usage Examples
### Example 1: Project Troubleshooting Scenario
**Scenario**: Developer needs to find all information about deployment issues in Project Alpha
**REST API Call:**
```bash
curl -X POST "https://localhost:8443/api/search/advanced" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-key" \
-d '{
"query": "Project Alpha database timeout deployment error",
"search_mode": "hybrid",
"n_results": 20,
"consolidate_related": true,
"include_context": true,
"filters": {
"memory_types": ["task", "decision", "note", "reference"],
"tags": ["project-alpha", "deployment", "database"],
"time_range": "last 2 weeks",
"metadata_filters": {
"priority": ["high", "critical"],
"status": ["in-progress", "completed", "failed"]
}
},
"ranking_options": {
"semantic_weight": 0.5,
"keyword_weight": 0.4,
"recency_weight": 0.1,
"boost_exact_matches": true
}
}'
```
**Response:**
```json
{
"results": [
{
"primary_memory": {
"content": "Project Alpha production deployment failed at 2024-01-15 10:30 AM. Database connection timeout after 15 seconds. Error: Connection pool exhausted. Impact: 500+ users affected. Rolling back to previous version.",
"content_hash": "pa_deploy_error_20240115",
"tags": ["project-alpha", "deployment", "database", "production", "error"],
"memory_type": "task",
"created_at_iso": "2024-01-15T10:35:00Z",
"metadata": {
"priority": "critical",
"status": "in-progress",
"project_id": "alpha-001",
"environment": "production",
"impact_level": "high"
}
},
"similarity_score": 0.98,
"relevance_reason": "Exact match: 'Project Alpha', 'database timeout', 'deployment error' + high semantic similarity",
"consolidation": {
"related_memories": [
{
"content": "DECISION: Increase database connection timeout from 15s to 45s for Project Alpha. Approved by Tech Lead. Implementation scheduled for next deployment window.",
"content_hash": "pa_timeout_decision_20240115",
"relationship": "solution",
"similarity_score": 0.89,
"memory_type": "decision",
"relevance_reason": "Direct solution to the identified problem"
},
{
"content": "Project Alpha database configuration: Connection pool size: 20, Timeout: 15s, Retry attempts: 3. Performance baseline established.",
"content_hash": "pa_db_config_baseline",
"relationship": "context",
"similarity_score": 0.85,
"memory_type": "reference",
"relevance_reason": "Configuration context for troubleshooting"
},
{
"content": "Post-deployment monitoring shows Project Alpha database connections stabilized after timeout increase. No further timeout errors in 48 hours.",
"content_hash": "pa_monitor_success_20240117",
"relationship": "follow_up",
"similarity_score": 0.82,
"memory_type": "note",
"relevance_reason": "Follow-up results and validation"
}
],
"topic_cluster": "project-alpha-database-deployment",
"consolidation_summary": "Database timeout deployment issue resolved by increasing connection timeout from 15s to 45s. Monitoring confirms successful resolution.",
"timeline": [
{
"date": "2024-01-15T10:30:00Z",
"event": "Deployment failure detected",
"type": "problem"
},
{
"date": "2024-01-15T14:00:00Z",
"event": "Solution decided and approved",
"type": "solution"
},
{
"date": "2024-01-16T09:00:00Z",
"event": "Fix implemented and deployed",
"type": "implementation"
},
{
"date": "2024-01-17T10:00:00Z",
"event": "Success validated through monitoring",
"type": "validation"
}
]
}
}
],
"consolidated_topics": [
{
"topic": "Project Alpha Database Issues",
"memory_count": 8,
"key_themes": ["timeout", "connection pool", "performance", "monitoring"],
"timeline": "2024-01-10 to 2024-01-18",
"status": "resolved"
},
{
"topic": "Project Alpha Deployment Process",
"memory_count": 15,
"key_themes": ["rollback procedures", "deployment windows", "approval process"],
"timeline": "2024-01-01 to present",
"status": "ongoing"
}
],
"search_intelligence": {
"query_analysis": {
"intent": "troubleshooting",
"entities": ["Project Alpha", "database timeout", "deployment error"],
"confidence": 0.94,
"suggested_filters": ["infrastructure", "database", "production"],
"query_type": "problem_resolution"
},
"recommendations": [
"Search for 'Project Alpha monitoring dashboard' for real-time metrics",
"Consider searching 'database performance optimization' for preventive measures",
"Review memories tagged with 'post-mortem' for similar incident analysis"
],
"related_searches": [
"Project Alpha performance metrics",
"database connection pool tuning",
"deployment rollback procedures"
]
},
"performance_metrics": {
"total_processing_time_ms": 87,
"semantic_search_time_ms": 34,
"keyword_search_time_ms": 12,
"consolidation_time_ms": 28,
"relationship_mapping_time_ms": 13
}
}
```
### Example 2: Knowledge Discovery Scenario
**Scenario**: Product manager wants to understand all decisions made about user authentication
**REST API Call:**
```bash
curl -X POST "https://localhost:8443/api/search/advanced" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-key" \
-d '{
"query": "user authentication security decisions",
"search_mode": "auto",
"n_results": 25,
"consolidate_related": true,
"include_context": true,
"filters": {
"memory_types": ["decision", "note"],
"time_range": "last 6 months",
"metadata_filters": {
"category": ["security", "architecture", "user-experience"]
}
}
}'
```
## MCP API Usage Examples
### Example 1: MCP Tool via HTTP Bridge
**Request:**
```http
POST /api/mcp/tools/call
Content-Type: application/json
{
"tool_name": "advanced_memory_search",
"arguments": {
"query": "API rate limiting implementation discussion",
"search_mode": "hybrid",
"consolidate_related": true,
"max_results": 15,
"filters": {
"memory_types": ["decision", "task", "reference"],
"tags": ["api", "rate-limiting", "performance"],
"time_range": "last month"
}
}
}
```
**Response:**
```json
{
"success": true,
"result": {
"search_results": [
{
"primary_content": "DECISION: Implement token bucket rate limiting for public API endpoints. Limit: 1000 requests/hour per API key. Burst capacity: 100 requests. Approved by architecture team.",
"content_hash": "api_rate_limit_decision_001",
"relevance_score": 0.95,
"memory_type": "decision",
"tags": ["api", "rate-limiting", "architecture", "approved"],
"created_at": "2024-01-10T14:30:00Z",
"consolidation": {
"related_content": [
{
"content": "Research: Token bucket vs sliding window rate limiting algorithms. Token bucket provides better burst handling for API scenarios.",
"relationship": "background_research",
"memory_type": "reference"
},
{
"content": "TASK: Implement rate limiting middleware in Express.js API server. Use redis for distributed rate limit storage. Due: 2024-01-20",
"relationship": "implementation_task",
"memory_type": "task",
"status": "completed"
}
],
"topic_summary": "API rate limiting decision with token bucket algorithm, researched and implemented successfully"
}
}
],
"total_found": 8,
"consolidated_topics": [
{
"topic": "API Security & Performance",
"memory_count": 12,
"key_themes": ["rate limiting", "authentication", "caching", "monitoring"]
}
],
"processing_time_ms": 45
}
}
```
### Example 2: Direct MCP Server Call (Claude Desktop)
**MCP Tool Definition:**
```json
{
"name": "advanced_memory_search",
"description": "Search memories using hybrid semantic + keyword search with automatic content consolidation",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query combining keywords and concepts"
},
"search_mode": {
"type": "string",
"enum": ["hybrid", "semantic", "keyword", "auto"],
"default": "hybrid",
"description": "Search strategy to use"
},
"consolidate_related": {
"type": "boolean",
"default": true,
"description": "Automatically group related memories and provide context"
},
"max_results": {
"type": "integer",
"default": 10,
"minimum": 1,
"maximum": 50,
"description": "Maximum number of results to return"
},
"filters": {
"type": "object",
"properties": {
"memory_types": {
"type": "array",
"items": {"type": "string"},
"description": "Filter by memory types: task, decision, note, reference"
},
"tags": {
"type": "array",
"items": {"type": "string"},
"description": "Filter by specific tags"
},
"time_range": {
"type": "string",
"description": "Natural language time filter: 'last week', 'this month', etc."
},
"metadata_filters": {
"type": "object",
"description": "Filter by metadata fields and values"
}
}
}
},
"required": ["query"]
}
}
```
**Example Usage in Claude Desktop:**
**User Query:** "Find all discussions about microservices architecture decisions"
**MCP Tool Call:**
```json
{
"tool": "advanced_memory_search",
"arguments": {
"query": "microservices architecture decisions discussion",
"search_mode": "hybrid",
"consolidate_related": true,
"max_results": 20,
"filters": {
"memory_types": ["decision", "note", "reference"],
"tags": ["microservices", "architecture"],
"time_range": "last 3 months"
}
}
}
```
**Claude's Response Based on Tool Result:**
"I found 15 memories related to microservices architecture decisions from the last 3 months. Here's what I discovered:
## Key Architecture Decisions
**Primary Decision (Jan 15, 2024):**
*DECISION: Adopt microservices architecture for the new customer platform. Services: User Management, Order Processing, Payment Gateway, Notification Service. Each service will have its own database and API. Approved by architecture committee.*
**Related Context:**
- **Research Background**: Comparison study of monolith vs microservices showed 40% better scalability for our use case
- **Implementation Plan**: 6-month phased migration starting with User Management service
- **Success Metrics**: Service response time < 200ms, 99.9% uptime target
## Consolidated Topics Found:
1. **Service Communication** (8 memories)
- Event-driven architecture using RabbitMQ
- API gateway implementation with Kong
- Service discovery with Consul
2. **Data Management** (4 memories)
- Database per service pattern
- Event sourcing for audit trails
- Data consistency strategies
3. **DevOps & Deployment** (3 memories)
- Docker containerization approach
- Kubernetes orchestration setup
- CI/CD pipeline modifications
The search found strong relationships between the architecture decisions and their implementation details, providing a complete picture of your microservices adoption journey."
### Example 3: Project Consolidation MCP Tool
**MCP Tool Definition:**
```json
{
"name": "consolidate_project_memories",
"description": "Automatically gather and organize all memories related to a specific project",
"inputSchema": {
"type": "object",
"properties": {
"project_identifier": {
"type": "string",
"description": "Project name or identifier (e.g., 'Project Alpha', 'mobile-app-v2')"
},
"consolidation_depth": {
"type": "string",
"enum": ["shallow", "deep", "comprehensive"],
"default": "deep",
"description": "How extensively to search for related content"
},
"include_timeline": {
"type": "boolean",
"default": true,
"description": "Generate chronological timeline of project events"
}
},
"required": ["project_identifier"]
}
}
```
**Usage Example:**
```json
{
"tool": "consolidate_project_memories",
"arguments": {
"project_identifier": "mobile app redesign",
"consolidation_depth": "comprehensive",
"include_timeline": true
}
}
```
**Tool Response:**
```json
{
"project_overview": {
"name": "Mobile App Redesign",
"total_memories": 47,
"date_range": "2023-11-01 to 2024-01-20",
"status": "in_progress",
"key_stakeholders": ["Product Team", "UX Design", "Mobile Dev Team"]
},
"timeline": [
{
"date": "2023-11-01",
"event": "Project kickoff and requirements gathering",
"type": "milestone",
"memories": 3
},
{
"date": "2023-11-15",
"event": "UX wireframes and user research completed",
"type": "deliverable",
"memories": 8
},
{
"date": "2023-12-01",
"event": "Technical architecture decisions finalized",
"type": "decision",
"memories": 5
}
],
"key_decisions": [
{
"content": "DECISION: Use React Native for cross-platform development. Allows 80% code sharing between iOS/Android. Team already familiar with React.",
"impact": "high",
"date": "2023-11-20"
}
],
"outstanding_issues": [
{
"content": "ISSUE: Performance concerns with large image galleries in React Native. Need optimization strategy.",
"priority": "high",
"status": "open",
"assigned_to": "Mobile Dev Team"
}
],
"related_projects": [
{
"name": "API v2 Migration",
"relationship": "dependency",
"status": "completed"
}
]
}
```
## Claude Code Integration Examples
### Example 1: Claude Code Slash Command
```bash
# Enhanced memory search with consolidation
claude /memory-search-advanced "database performance optimization" --consolidate --filters="tags:database,performance;type:decision,reference"
# Quick project overview
claude /memory-project-overview "Project Beta" --timeline --issues
# Intelligent search with auto-suggestions
claude /memory-smart-search "user feedback login problems" --auto-expand --suggest-actions
```
### Example 2: Claude Code Hook Integration
**Session Hook Usage:**
```javascript
// .claude/hooks/memory-enhanced-search.js
module.exports = {
name: "enhanced-memory-search",
description: "Automatically use hybrid search for memory queries",
trigger: "before_memory_search",
async execute(context) {
// Automatically enhance memory searches with hybrid mode
if (context.tool === "retrieve_memory") {
context.arguments.search_mode = "hybrid";
context.arguments.consolidate_related = true;
context.arguments.include_context = true;
}
return context;
}
};
```
These examples demonstrate how the Advanced Hybrid Search enhancement provides rich, contextual, and intelligent search capabilities through both REST API and MCP interfaces, making it easy for users to find and understand related information in their memory store.
```
--------------------------------------------------------------------------------
/tests/timestamp/test_hook_vs_manual_storage.py:
--------------------------------------------------------------------------------
```python
#!/usr/bin/env python3
"""
Test script to compare hook-generated vs manual memory storage for Issue #99.
This test validates timestamp handling, tag consistency, and discoverability
between different memory creation methods.
"""
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src'))
import asyncio
import time
import json
import tempfile
import httpx
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional
from mcp_memory_service.models.memory import Memory
from mcp_memory_service.utils.hashing import generate_content_hash
from mcp_memory_service.utils.time_parser import extract_time_expression
from mcp_memory_service.storage.sqlite_vec import SqliteVecMemoryStorage
class HookVsManualStorageTest:
"""Test suite comparing hook-generated and manual memory storage."""
def __init__(self, storage_backend: str = "sqlite_vec"):
self.storage_backend = storage_backend
self.storage = None
self.test_memories_created = []
async def setup(self):
"""Set up test environment and storage."""
print(f"=== Setting up {self.storage_backend} storage for testing ===")
if self.storage_backend == "sqlite_vec":
# Create temporary database for testing
self.temp_db = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
self.temp_db.close()
self.storage = SqliteVecMemoryStorage(
db_path=self.temp_db.name,
embedding_model="all-MiniLM-L6-v2"
)
await self.storage.initialize()
print(f"✅ SQLite-Vec storage initialized: {self.temp_db.name}")
async def cleanup(self):
"""Clean up test environment."""
# Note: SqliteVecMemoryStorage doesn't have a close() method
self.storage = None
if hasattr(self, 'temp_db') and os.path.exists(self.temp_db.name):
os.unlink(self.temp_db.name)
print("✅ Test database cleaned up")
def create_hook_style_memory(self, content: str, project_context: Dict) -> Memory:
"""Create a memory as hooks would create it (with auto-generated tags)."""
# Simulate hook behavior - generate tags like session-end.js does
hook_tags = [
'claude-code-session',
'session-consolidation',
project_context.get('name', 'unknown-project'),
f"language:{project_context.get('language', 'unknown')}",
*project_context.get('frameworks', [])[:2], # Top 2 frameworks
]
# Filter out None/empty tags
hook_tags = [tag for tag in hook_tags if tag]
memory = Memory(
content=content,
content_hash=generate_content_hash(content),
tags=hook_tags,
memory_type='session-summary',
metadata={
'session_analysis': {
'topics': ['test-topic'],
'decisions_count': 1,
'insights_count': 1,
'confidence': 0.85
},
'project_context': project_context,
'generated_by': 'claude-code-session-end-hook',
'generated_at': datetime.now().isoformat()
}
)
return memory
def create_manual_memory(self, content: str, user_tags: List[str] = None) -> Memory:
"""Create a memory as manual /memory-store would create it."""
# Manual memories typically have user-provided tags, not auto-generated ones
manual_tags = user_tags or []
memory = Memory(
content=content,
content_hash=generate_content_hash(content),
tags=manual_tags,
memory_type='note',
metadata={
'created_by': 'manual-storage',
'source': 'user-input'
}
)
return memory
async def test_timestamp_consistency(self):
"""Test 1: Compare timestamp handling between hook and manual memories."""
print("\n🧪 Test 1: Timestamp Consistency")
print("-" * 50)
# Create memories with slight time differences
base_time = time.time()
project_context = {
'name': 'mcp-memory-service',
'language': 'python',
'frameworks': ['fastapi', 'chromadb']
}
# Hook-style memory
hook_memory = self.create_hook_style_memory(
"Implemented timestamp standardization for Issue #99",
project_context
)
# Manual memory created shortly after
time.sleep(0.1) # Small delay to test precision
manual_memory = self.create_manual_memory(
"Fixed timestamp precision issue in memory storage",
['timestamp-fix', 'issue-99', 'debugging']
)
# Store both memories
await self.storage.store(hook_memory)
await self.storage.store(manual_memory)
self.test_memories_created.extend([hook_memory.content_hash, manual_memory.content_hash])
print(f"Hook memory timestamps:")
print(f" created_at: {hook_memory.created_at}")
print(f" created_at_iso: {hook_memory.created_at_iso}")
print(f" Type check: {type(hook_memory.created_at)} / {type(hook_memory.created_at_iso)}")
print(f"\nManual memory timestamps:")
print(f" created_at: {manual_memory.created_at}")
print(f" created_at_iso: {manual_memory.created_at_iso}")
print(f" Type check: {type(manual_memory.created_at)} / {type(manual_memory.created_at_iso)}")
# Check if both have proper timestamps
hook_has_timestamps = (hook_memory.created_at is not None and
hook_memory.created_at_iso is not None)
manual_has_timestamps = (manual_memory.created_at is not None and
manual_memory.created_at_iso is not None)
print(f"\nTimestamp validation:")
print(f" Hook memory has complete timestamps: {hook_has_timestamps}")
print(f" Manual memory has complete timestamps: {manual_has_timestamps}")
if hook_has_timestamps and manual_has_timestamps:
print("✅ Both memory types have consistent timestamp formats")
else:
print("❌ Timestamp inconsistency detected!")
return {
'hook_has_timestamps': hook_has_timestamps,
'manual_has_timestamps': manual_has_timestamps,
'hook_memory': hook_memory,
'manual_memory': manual_memory
}
async def test_tag_consistency(self):
"""Test 2: Compare tag patterns between hook and manual memories."""
print("\n🧪 Test 2: Tag Consistency Analysis")
print("-" * 50)
project_context = {
'name': 'test-project',
'language': 'typescript',
'frameworks': ['react', 'node']
}
# Create hook memory
hook_memory = self.create_hook_style_memory(
"Testing tag consistency between storage methods",
project_context
)
# Create manual memory with content-appropriate tags
manual_memory = self.create_manual_memory(
"Testing tag consistency between storage methods",
['testing', 'tag-consistency', 'storage-methods', 'validation']
)
await self.storage.store(hook_memory)
await self.storage.store(manual_memory)
self.test_memories_created.extend([hook_memory.content_hash, manual_memory.content_hash])
print(f"Hook memory tags: {hook_memory.tags}")
print(f"Manual memory tags: {manual_memory.tags}")
# Analyze tag patterns
hook_has_auto_tags = any('claude-code' in tag for tag in hook_memory.tags)
manual_has_content_tags = len(manual_memory.tags) > 0 and not any('auto-generated' in tag for tag in manual_memory.tags)
print(f"\nTag analysis:")
print(f" Hook memory has auto-generated tags: {hook_has_auto_tags}")
print(f" Manual memory has content-relevant tags: {manual_has_content_tags}")
print(f" Hook tag count: {len(hook_memory.tags)}")
print(f" Manual tag count: {len(manual_memory.tags)}")
if hook_has_auto_tags and manual_has_content_tags:
print("✅ Tag patterns are appropriately different and content-relevant")
else:
print("❌ Tag pattern issues detected")
return {
'hook_tags': hook_memory.tags,
'manual_tags': manual_memory.tags,
'hook_has_auto_tags': hook_has_auto_tags,
'manual_has_content_tags': manual_has_content_tags
}
async def test_time_based_search_consistency(self):
"""Test 3: Verify both memory types are discoverable in time-based searches."""
print("\n🧪 Test 3: Time-Based Search Discoverability")
print("-" * 50)
# Create memories with known timestamps
current_time = time.time()
yesterday_time = current_time - (24 * 60 * 60) # 24 hours ago
# Create hook memory with specific timestamp
hook_memory = self.create_hook_style_memory(
"Hook memory created yesterday for search testing",
{'name': 'search-test', 'language': 'python', 'frameworks': []}
)
hook_memory.created_at = yesterday_time
hook_memory.created_at_iso = datetime.fromtimestamp(yesterday_time).isoformat() + "Z"
# Create manual memory with specific timestamp
manual_memory = self.create_manual_memory(
"Manual memory created yesterday for search testing",
['search-test', 'yesterday', 'discoverability']
)
manual_memory.created_at = yesterday_time + 100 # Slightly later
manual_memory.created_at_iso = datetime.fromtimestamp(yesterday_time + 100).isoformat() + "Z"
await self.storage.store(hook_memory)
await self.storage.store(manual_memory)
self.test_memories_created.extend([hook_memory.content_hash, manual_memory.content_hash])
# Test time-based recall
query = "yesterday"
cleaned_query, (start_ts, end_ts) = extract_time_expression(query)
if start_ts and end_ts:
print(f"Search range: {datetime.fromtimestamp(start_ts)} to {datetime.fromtimestamp(end_ts)}")
print(f"Hook memory timestamp: {datetime.fromtimestamp(hook_memory.created_at)}")
print(f"Manual memory timestamp: {datetime.fromtimestamp(manual_memory.created_at)}")
# Check if memories fall within range
hook_in_range = start_ts <= hook_memory.created_at <= end_ts
manual_in_range = start_ts <= manual_memory.created_at <= end_ts
print(f"\nTime range analysis:")
print(f" Hook memory in range: {hook_in_range}")
print(f" Manual memory in range: {manual_in_range}")
if hook_in_range and manual_in_range:
print("✅ Both memory types would be discoverable in time-based searches")
return {'discoverability_consistent': True}
else:
print("❌ Time-based search discoverability inconsistent")
return {'discoverability_consistent': False}
else:
print("⚠️ Could not parse time expression for testing")
return {'discoverability_consistent': None}
async def test_metadata_structure_comparison(self):
"""Test 4: Compare metadata structure between hook and manual memories."""
print("\n🧪 Test 4: Metadata Structure Comparison")
print("-" * 50)
# Create memories with different metadata patterns
hook_memory = self.create_hook_style_memory(
"Testing metadata structure consistency",
{'name': 'metadata-test', 'language': 'javascript', 'frameworks': ['express']}
)
manual_memory = self.create_manual_memory(
"Testing metadata structure consistency",
['metadata-test', 'structure-analysis']
)
await self.storage.store(hook_memory)
await self.storage.store(manual_memory)
self.test_memories_created.extend([hook_memory.content_hash, manual_memory.content_hash])
# Analyze metadata structures
hook_metadata_keys = set(hook_memory.metadata.keys()) if hook_memory.metadata else set()
manual_metadata_keys = set(manual_memory.metadata.keys()) if manual_memory.metadata else set()
print(f"Hook memory metadata keys: {sorted(hook_metadata_keys)}")
print(f"Manual memory metadata keys: {sorted(manual_metadata_keys)}")
# Check for required fields
hook_has_timestamps = hasattr(hook_memory, 'created_at_iso') and hook_memory.created_at_iso is not None
manual_has_timestamps = hasattr(manual_memory, 'created_at_iso') and manual_memory.created_at_iso is not None
print(f"\nMetadata analysis:")
print(f" Hook memory has ISO timestamp: {hook_has_timestamps}")
print(f" Manual memory has ISO timestamp: {manual_has_timestamps}")
print(f" Hook metadata structure: {bool(hook_memory.metadata)}")
print(f" Manual metadata structure: {bool(manual_memory.metadata)}")
return {
'hook_metadata_keys': hook_metadata_keys,
'manual_metadata_keys': manual_metadata_keys,
'metadata_consistency': hook_has_timestamps and manual_has_timestamps
}
async def run_all_tests(self):
"""Run all tests and compile results."""
print("=" * 70)
print("MCP Memory Service: Hook vs Manual Storage Consistency Tests")
print("Testing for Issue #99 - Memory Storage Inconsistency")
print("=" * 70)
try:
await self.setup()
# Run individual tests
timestamp_results = await self.test_timestamp_consistency()
tag_results = await self.test_tag_consistency()
search_results = await self.test_time_based_search_consistency()
metadata_results = await self.test_metadata_structure_comparison()
# Compile overall results
print("\n" + "=" * 70)
print("TEST SUMMARY")
print("=" * 70)
tests_passed = 0
total_tests = 4
if timestamp_results.get('hook_has_timestamps') and timestamp_results.get('manual_has_timestamps'):
print("✅ PASS: Timestamp Consistency")
tests_passed += 1
else:
print("❌ FAIL: Timestamp Consistency")
if tag_results.get('hook_has_auto_tags') and tag_results.get('manual_has_content_tags'):
print("✅ PASS: Tag Pattern Appropriateness")
tests_passed += 1
else:
print("❌ FAIL: Tag Pattern Issues")
if search_results.get('discoverability_consistent'):
print("✅ PASS: Time-Based Search Discoverability")
tests_passed += 1
elif search_results.get('discoverability_consistent') is False:
print("❌ FAIL: Time-Based Search Discoverability")
else:
print("⚠️ SKIP: Time-Based Search Test (parsing issue)")
if metadata_results.get('metadata_consistency'):
print("✅ PASS: Metadata Structure Consistency")
tests_passed += 1
else:
print("❌ FAIL: Metadata Structure Consistency")
print(f"\nOverall Result: {tests_passed}/{total_tests} tests passed")
if tests_passed == total_tests:
print("🎉 All tests passed! No storage inconsistency detected.")
return True
else:
print("⚠️ Storage inconsistencies detected - Issue #99 confirmed.")
return False
finally:
await self.cleanup()
async def main():
"""Main test execution."""
test_suite = HookVsManualStorageTest("sqlite_vec")
success = await test_suite.run_all_tests()
return 0 if success else 1
if __name__ == "__main__":
exit_code = asyncio.run(main())
sys.exit(exit_code)
```
--------------------------------------------------------------------------------
/src/mcp_memory_service/cli/ingestion.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.
"""
CLI commands for document ingestion.
"""
import asyncio
import logging
import sys
import time
from pathlib import Path
from typing import List, Optional
import click
from ..ingestion import get_loader_for_file, is_supported_file, SUPPORTED_FORMATS
from ..models.memory import Memory
from ..utils import create_memory_from_chunk, _process_and_store_chunk
logger = logging.getLogger(__name__)
def add_ingestion_commands(cli_group):
"""Add ingestion commands to a Click CLI group."""
cli_group.add_command(ingest_document)
cli_group.add_command(ingest_directory)
cli_group.add_command(list_formats)
@click.command()
@click.argument('file_path', type=click.Path(exists=True, path_type=Path))
@click.option('--tags', '-t', multiple=True, help='Tags to apply to all memories (can be used multiple times)')
@click.option('--chunk-size', '-c', default=1000, help='Target size for text chunks in characters')
@click.option('--chunk-overlap', '-o', default=200, help='Characters to overlap between chunks')
@click.option('--memory-type', '-m', default='document', help='Type label for created memories')
@click.option('--storage-backend', '-s', default='sqlite_vec',
type=click.Choice(['sqlite_vec', 'sqlite-vec', 'cloudflare', 'hybrid']), help='Storage backend to use')
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose output')
def ingest_document(file_path: Path, tags: tuple, chunk_size: int, chunk_overlap: int,
memory_type: str, storage_backend: str, verbose: bool):
"""
Ingest a single document file into the memory database.
Supports multiple formats including PDF, text, and Markdown files.
The document will be parsed, chunked intelligently, and stored as multiple memories.
Examples:
memory ingest-document manual.pdf --tags documentation,manual
memory ingest-document README.md --chunk-size 500 --verbose
memory ingest-document data.txt --memory-type reference --tags important
"""
if verbose:
logging.basicConfig(level=logging.INFO)
click.echo(f"📄 Processing document: {file_path}")
async def run_ingestion():
from .utils import get_storage
try:
# Initialize storage
storage = await get_storage(storage_backend)
# Get appropriate document loader
loader = get_loader_for_file(file_path)
if loader is None:
click.echo(f"❌ Error: Unsupported file format: {file_path.suffix}", err=True)
return False
# Configure loader
loader.chunk_size = chunk_size
loader.chunk_overlap = chunk_overlap
if verbose:
click.echo(f"🔧 Using loader: {loader.__class__.__name__}")
click.echo(f"⚙️ Chunk size: {chunk_size}, Overlap: {chunk_overlap}")
start_time = time.time()
chunks_processed = 0
chunks_stored = 0
errors = []
# Extract and store chunks
with click.progressbar(length=0, label='Processing chunks') as bar:
async for chunk in loader.extract_chunks(file_path):
chunks_processed += 1
bar.length = chunks_processed
bar.update(1)
try:
# Combine CLI tags with chunk metadata tags
all_tags = list(tags)
if chunk.metadata.get('tags'):
# Handle tags from chunk metadata (can be string or list)
chunk_tags = chunk.metadata['tags']
if isinstance(chunk_tags, str):
# Split comma-separated string into list
chunk_tags = [tag.strip() for tag in chunk_tags.split(',') if tag.strip()]
all_tags.extend(chunk_tags)
# Create memory object
memory = Memory(
content=chunk.content,
content_hash=generate_content_hash(chunk.content, chunk.metadata),
tags=list(set(all_tags)), # Remove duplicates
memory_type=memory_type,
metadata=chunk.metadata
)
# Store the memory
success, error = await storage.store(memory)
if success:
chunks_stored += 1
else:
errors.append(f"Chunk {chunk.chunk_index}: {error}")
if verbose:
click.echo(f"⚠️ Error storing chunk {chunk.chunk_index}: {error}")
except Exception as e:
errors.append(f"Chunk {chunk.chunk_index}: {str(e)}")
if verbose:
click.echo(f"⚠️ Exception in chunk {chunk.chunk_index}: {str(e)}")
processing_time = time.time() - start_time
success_rate = (chunks_stored / chunks_processed * 100) if chunks_processed > 0 else 0
# Display results
click.echo(f"\n✅ Document ingestion completed: {file_path.name}")
click.echo(f"📄 Chunks processed: {chunks_processed}")
click.echo(f"💾 Chunks stored: {chunks_stored}")
click.echo(f"⚡ Success rate: {success_rate:.1f}%")
click.echo(f"⏱️ Processing time: {processing_time:.2f} seconds")
if errors:
click.echo(f"⚠️ Errors encountered: {len(errors)}")
if verbose:
for error in errors[:5]: # Show first 5 errors
click.echo(f" - {error}")
if len(errors) > 5:
click.echo(f" ... and {len(errors) - 5} more errors")
return chunks_stored > 0
except Exception as e:
click.echo(f"❌ Error ingesting document: {str(e)}", err=True)
if verbose:
import traceback
click.echo(traceback.format_exc(), err=True)
return False
finally:
if 'storage' in locals():
await storage.close()
success = asyncio.run(run_ingestion())
sys.exit(0 if success else 1)
@click.command()
@click.argument('directory_path', type=click.Path(exists=True, file_okay=False, path_type=Path))
@click.option('--tags', '-t', multiple=True, help='Tags to apply to all memories (can be used multiple times)')
@click.option('--recursive', '-r', is_flag=True, default=True, help='Process subdirectories recursively')
@click.option('--extensions', '-e', multiple=True, help='File extensions to process (default: all supported)')
@click.option('--chunk-size', '-c', default=1000, help='Target size for text chunks in characters')
@click.option('--max-files', default=100, help='Maximum number of files to process')
@click.option('--storage-backend', '-s', default='sqlite_vec',
type=click.Choice(['sqlite_vec', 'sqlite-vec', 'cloudflare', 'hybrid']), help='Storage backend to use')
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose output')
@click.option('--dry-run', is_flag=True, help='Show what would be processed without storing')
def ingest_directory(directory_path: Path, tags: tuple, recursive: bool, extensions: tuple,
chunk_size: int, max_files: int, storage_backend: str, verbose: bool, dry_run: bool):
"""
Batch ingest all supported documents from a directory.
Recursively processes all supported file types in the directory,
creating memories with consistent tagging and metadata.
Examples:
memory ingest-directory ./docs --tags knowledge-base --recursive
memory ingest-directory ./manuals --extensions pdf,md --max-files 50
memory ingest-directory ./content --dry-run --verbose
"""
if verbose:
logging.basicConfig(level=logging.INFO)
click.echo(f"📁 Processing directory: {directory_path}")
async def run_batch_ingestion():
from .utils import get_storage
try:
# Initialize storage (unless dry run)
storage = None if dry_run else await get_storage(storage_backend)
# Determine file extensions to process
if extensions:
file_extensions = [ext.lstrip('.') for ext in extensions]
else:
file_extensions = list(SUPPORTED_FORMATS.keys())
if verbose:
click.echo(f"🔍 Looking for extensions: {', '.join(file_extensions)}")
click.echo(f"📊 Max files: {max_files}, Recursive: {recursive}")
# Find all supported files
all_files = []
for ext in file_extensions:
ext_pattern = f"*.{ext.lstrip('.')}"
if recursive:
files = list(directory_path.rglob(ext_pattern))
else:
files = list(directory_path.glob(ext_pattern))
all_files.extend(files)
# Remove duplicates and filter supported files
unique_files = []
seen = set()
for file_path in all_files:
if file_path not in seen and is_supported_file(file_path):
unique_files.append(file_path)
seen.add(file_path)
# Limit number of files
files_to_process = unique_files[:max_files]
if not files_to_process:
click.echo(f"❌ No supported files found in directory: {directory_path}")
return False
click.echo(f"📋 Found {len(files_to_process)} files to process")
if dry_run:
click.echo("🔍 DRY RUN - Files that would be processed:")
for file_path in files_to_process:
click.echo(f" 📄 {file_path}")
return True
start_time = time.time()
total_chunks_processed = 0
total_chunks_stored = 0
files_processed = 0
files_failed = 0
all_errors = []
# Process each file with progress bar
with click.progressbar(files_to_process, label='Processing files') as files_bar:
for file_path in files_bar:
try:
if verbose:
click.echo(f"\n🔄 Processing: {file_path.name}")
# Get appropriate document loader
loader = get_loader_for_file(file_path)
if loader is None:
all_errors.append(f"{file_path.name}: Unsupported format")
files_failed += 1
continue
# Configure loader
loader.chunk_size = chunk_size
file_chunks_processed = 0
file_chunks_stored = 0
# Extract and store chunks from this file
async for chunk in loader.extract_chunks(file_path):
file_chunks_processed += 1
total_chunks_processed += 1
# Process and store the chunk
success, error = await _process_and_store_chunk(
chunk,
storage,
file_path.name,
base_tags=list(tags),
context_tags={
"source_dir": directory_path.name,
"file_type": file_path.suffix.lstrip('.')
}
)
if success:
file_chunks_stored += 1
total_chunks_stored += 1
else:
all_errors.append(error)
if file_chunks_stored > 0:
files_processed += 1
if verbose:
click.echo(f" ✅ {file_chunks_stored}/{file_chunks_processed} chunks stored")
else:
files_failed += 1
if verbose:
click.echo(f" ❌ No chunks stored")
except Exception as e:
files_failed += 1
all_errors.append(f"{file_path.name}: {str(e)}")
if verbose:
click.echo(f" ❌ Error: {str(e)}")
processing_time = time.time() - start_time
success_rate = (total_chunks_stored / total_chunks_processed * 100) if total_chunks_processed > 0 else 0
# Display results
click.echo(f"\n✅ Directory ingestion completed: {directory_path.name}")
click.echo(f"📁 Files processed: {files_processed}/{len(files_to_process)}")
click.echo(f"📄 Total chunks processed: {total_chunks_processed}")
click.echo(f"💾 Total chunks stored: {total_chunks_stored}")
click.echo(f"⚡ Success rate: {success_rate:.1f}%")
click.echo(f"⏱️ Processing time: {processing_time:.2f} seconds")
if files_failed > 0:
click.echo(f"❌ Files failed: {files_failed}")
if all_errors:
click.echo(f"⚠️ Total errors: {len(all_errors)}")
if verbose:
error_limit = 10
for error in all_errors[:error_limit]:
click.echo(f" - {error}")
if len(all_errors) > error_limit:
click.echo(f" ... and {len(all_errors) - error_limit} more errors")
return total_chunks_stored > 0
except Exception as e:
click.echo(f"❌ Error in batch ingestion: {str(e)}", err=True)
if verbose:
import traceback
click.echo(traceback.format_exc(), err=True)
return False
finally:
if storage:
await storage.close()
success = asyncio.run(run_batch_ingestion())
sys.exit(0 if success else 1)
@click.command()
def list_formats() -> None:
"""
List all supported document formats for ingestion.
Shows file extensions and descriptions of supported document types.
"""
click.echo("📋 Supported document formats for ingestion:\n")
for ext, description in SUPPORTED_FORMATS.items():
click.echo(f" 📄 .{ext:<8} - {description}")
click.echo(f"\n✨ Total: {len(SUPPORTED_FORMATS)} supported formats")
click.echo("\nExamples:")
click.echo(" memory ingest-document manual.pdf")
click.echo(" memory ingest-directory ./docs --extensions pdf,md")
```
--------------------------------------------------------------------------------
/claude-hooks/utilities/git-analyzer.js:
--------------------------------------------------------------------------------
```javascript
/**
* Git Context Analyzer
* Analyzes git repository history and changelog to provide development context for memory retrieval
*/
const fs = require('fs').promises;
const path = require('path');
const { execSync } = require('child_process');
/**
* Get recent commit history with detailed information
*/
async function getRecentCommits(workingDir, options = {}) {
try {
const {
days = 14,
maxCommits = 20,
includeMerges = false
} = options;
// Build git log command
let gitCommand = `git log --pretty=format:"%H|%aI|%s|%an" --max-count=${maxCommits}`;
if (!includeMerges) {
gitCommand += ' --no-merges';
}
// Add time filter
const sinceDate = new Date();
sinceDate.setDate(sinceDate.getDate() - days);
gitCommand += ` --since="${sinceDate.toISOString()}"`;
const output = execSync(gitCommand, {
cwd: path.resolve(workingDir),
encoding: 'utf8',
timeout: 10000
});
if (!output.trim()) {
return [];
}
const commits = output.trim().split('\n').map(line => {
const [hash, date, message, author] = line.split('|');
return {
hash: hash?.substring(0, 8),
fullHash: hash,
date: new Date(date),
message: message || '',
author: author || '',
daysSinceCommit: Math.floor((new Date() - new Date(date)) / (1000 * 60 * 60 * 24))
};
});
// Get file changes for recent commits (last 5 commits for performance)
const recentCommits = commits.slice(0, Math.min(5, commits.length));
for (const commit of recentCommits) {
try {
const filesOutput = execSync(`git show --name-only --pretty="" ${commit.fullHash}`, {
cwd: path.resolve(workingDir),
encoding: 'utf8',
timeout: 5000
});
commit.files = filesOutput.trim().split('\n').filter(f => f.length > 0);
} catch (error) {
commit.files = [];
}
}
return commits;
} catch (error) {
// Silently fail for non-git directories
return [];
}
}
/**
* Parse CHANGELOG.md for recent entries
*/
async function parseChangelog(workingDir) {
try {
const changelogPath = path.join(workingDir, 'CHANGELOG.md');
try {
await fs.access(changelogPath);
} catch {
// Try alternative locations
const altPaths = ['changelog.md', 'HISTORY.md', 'RELEASES.md'];
let found = false;
for (const altPath of altPaths) {
try {
await fs.access(path.join(workingDir, altPath));
changelogPath = path.join(workingDir, altPath);
found = true;
break;
} catch {}
}
if (!found) return null;
}
const content = await fs.readFile(changelogPath, 'utf8');
// Parse changelog entries (assuming standard markdown format)
const entries = [];
const lines = content.split('\n');
let currentVersion = null;
let currentDate = null;
let currentChanges = [];
for (const line of lines) {
// Match version headers: ## [1.0.0] - 2024-08-25 or ## v1.0.0
const versionMatch = line.match(/^##\s*\[?v?([^\]]+)\]?\s*-?\s*(.*)$/);
if (versionMatch) {
// Save previous entry
if (currentVersion && currentChanges.length > 0) {
entries.push({
version: currentVersion,
date: currentDate,
changes: currentChanges.slice(),
raw: currentChanges.join('\n')
});
}
currentVersion = versionMatch[1];
currentDate = versionMatch[2] || null;
currentChanges = [];
continue;
}
// Collect changes under current version
if (currentVersion && line.trim()) {
// Skip section headers like "### Added", "### Fixed"
if (!line.match(/^###\s/)) {
currentChanges.push(line.trim());
}
}
}
// Don't forget the last entry
if (currentVersion && currentChanges.length > 0) {
entries.push({
version: currentVersion,
date: currentDate,
changes: currentChanges.slice(),
raw: currentChanges.join('\n')
});
}
// Return only recent entries (last 3 versions or entries from last 30 days)
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - 30);
const recentEntries = entries.slice(0, 3).filter(entry => {
if (!entry.date) return true; // Include entries without dates
try {
const entryDate = new Date(entry.date);
return entryDate >= cutoffDate;
} catch {
return true; // Include entries with unparseable dates
}
});
return recentEntries.length > 0 ? recentEntries : null;
} catch (error) {
// Silently fail if changelog not found or not readable
return null;
}
}
/**
* Extract development keywords from git history and changelog
*/
function extractDevelopmentKeywords(commits = [], changelogEntries = null) {
const keywords = new Set();
const themes = new Set();
const filePatterns = new Set();
// Extract from commit messages
commits.forEach(commit => {
const message = commit.message.toLowerCase();
// Extract action keywords (feat, fix, refactor, etc.)
const actionMatch = message.match(/^(feat|fix|refactor|docs|test|chore|improve|add|update|enhance)([:(]|\s)/);
if (actionMatch) {
keywords.add(actionMatch[1]);
}
// Extract key technical terms (avoid very common words)
// Expanded to capture more development-specific keywords
const technicalTerms = message.match(/\b(hook|memory|context|retrieval|phase|query|storage|backend|session|git|recent|scoring|config|timestamp|parsing|sort|sorting|date|age|dashboard|analytics|footer|layout|async|sync|bugfix|release|version|embedding|consolidation|stats|display|grid|css|api|endpoint|server|http|mcp|client|protocol)\b/g);
if (technicalTerms) {
technicalTerms.forEach(term => keywords.add(term));
}
// Extract version numbers (v8.5.12, v8.5.13, etc.)
const versionMatch = message.match(/v?\d+\.\d+\.\d+/g);
if (versionMatch) {
versionMatch.forEach(version => keywords.add(version));
}
// Extract file-based themes
if (commit.files) {
commit.files.forEach(file => {
const basename = path.basename(file, path.extname(file));
if (basename.length > 2) {
filePatterns.add(basename);
}
// Extract directory themes
const dir = path.dirname(file);
if (dir !== '.' && dir !== '/' && !dir.startsWith('.')) {
themes.add(dir.split('/')[0]); // First directory level
}
});
}
});
// Extract from changelog entries
if (changelogEntries) {
changelogEntries.forEach(entry => {
const text = entry.raw.toLowerCase();
// Extract technical keywords (expanded for better coverage)
const changelogTerms = text.match(/\b(added|fixed|improved|enhanced|updated|removed|deprecated|breaking|feature|bug|performance|security|bugfix|release|dashboard|hooks|timestamp|parsing|sorting|analytics|footer|async|sync|embedding|consolidation|memory|retrieval|scoring)\b/g);
if (changelogTerms) {
changelogTerms.forEach(term => keywords.add(term));
}
// Extract version numbers from changelog
const changelogVersions = text.match(/v?\d+\.\d+\.\d+/g);
if (changelogVersions) {
changelogVersions.forEach(version => keywords.add(version));
}
// Extract version-specific themes
if (entry.version) {
themes.add(`v${entry.version}`);
themes.add(`version-${entry.version}`);
}
});
}
return {
keywords: Array.from(keywords).slice(0, 20), // Increased from 15 to capture more relevant terms
themes: Array.from(themes).slice(0, 12), // Increased from 10
filePatterns: Array.from(filePatterns).slice(0, 12), // Increased from 10
recentCommitMessages: commits.slice(0, 5).map(c => c.message)
};
}
/**
* Build git-aware search queries
*/
function buildGitContextQuery(projectContext, gitContext, userMessage = '') {
try {
const queries = [];
const baseProject = projectContext.name || 'project';
// Query 1: Recent development focus
if (gitContext.keywords.length > 0) {
const devKeywords = gitContext.keywords.slice(0, 8).join(' ');
const recentQuery = `${baseProject} recent development ${devKeywords}`;
queries.push({
type: 'recent-development',
semanticQuery: userMessage ? `${recentQuery} ${userMessage}` : recentQuery,
weight: 1.0,
source: 'git-commits'
});
}
// Query 2: File-based context
if (gitContext.filePatterns.length > 0) {
const fileContext = gitContext.filePatterns.slice(0, 5).join(' ');
const fileQuery = `${baseProject} ${fileContext} implementation changes`;
queries.push({
type: 'file-context',
semanticQuery: userMessage ? `${fileQuery} ${userMessage}` : fileQuery,
weight: 0.8,
source: 'git-files'
});
}
// Query 3: Version/theme context
if (gitContext.themes.length > 0) {
const themeContext = gitContext.themes.slice(0, 5).join(' ');
const themeQuery = `${baseProject} ${themeContext} features decisions`;
queries.push({
type: 'theme-context',
semanticQuery: userMessage ? `${themeQuery} ${userMessage}` : themeQuery,
weight: 0.6,
source: 'git-themes'
});
}
// Query 4: Commit message context (most recent)
if (gitContext.recentCommitMessages.length > 0) {
const recentMessage = gitContext.recentCommitMessages[0];
const commitQuery = `${baseProject} ${recentMessage}`;
queries.push({
type: 'commit-context',
semanticQuery: userMessage ? `${commitQuery} ${userMessage}` : commitQuery,
weight: 0.9,
source: 'recent-commit'
});
}
return queries;
} catch (error) {
// Return empty queries on error
return [];
}
}
/**
* Get current git branch information
*/
function getCurrentGitInfo(workingDir) {
try {
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
cwd: path.resolve(workingDir),
encoding: 'utf8',
timeout: 3000
}).trim();
const lastCommit = execSync('git log -1 --pretty=format:"%h %s"', {
cwd: path.resolve(workingDir),
encoding: 'utf8',
timeout: 3000
}).trim();
const hasChanges = execSync('git status --porcelain', {
cwd: path.resolve(workingDir),
encoding: 'utf8',
timeout: 3000
}).trim().length > 0;
return {
branch,
lastCommit,
hasUncommittedChanges: hasChanges,
isGitRepo: true
};
} catch (error) {
return {
branch: null,
lastCommit: null,
hasUncommittedChanges: false,
isGitRepo: false
};
}
}
/**
* Main function to analyze git context for memory retrieval
*/
async function analyzeGitContext(workingDir, options = {}) {
try {
const {
commitLookback = 14,
maxCommits = 20,
includeChangelog = true,
verbose = false
} = options;
// Get basic git info
const gitInfo = getCurrentGitInfo(workingDir);
if (!gitInfo.isGitRepo) {
return null;
}
// Get recent commits
const commits = await getRecentCommits(workingDir, {
days: commitLookback,
maxCommits
});
// Parse changelog if enabled
const changelogEntries = includeChangelog ? await parseChangelog(workingDir) : null;
// Extract development context
const developmentKeywords = extractDevelopmentKeywords(commits, changelogEntries);
const context = {
gitInfo,
commits: commits.slice(0, 10), // Limit for performance
changelogEntries,
developmentKeywords,
analysisTimestamp: new Date().toISOString(),
repositoryActivity: {
recentCommitCount: commits.length,
activeDays: Math.max(1, Math.min(commitLookback, commits.length > 0 ? commits[0].daysSinceCommit : commitLookback)),
hasChangelog: changelogEntries !== null,
developmentIntensity: commits.length > 5 ? 'high' : commits.length > 2 ? 'medium' : 'low'
}
};
if (verbose) {
console.log(`[Git Analyzer] Analyzed ${commits.length} commits, ${changelogEntries?.length || 0} changelog entries`);
console.log(`[Git Analyzer] Keywords: ${developmentKeywords.keywords.join(', ')}`);
}
return context;
} catch (error) {
if (options.verbose) {
console.warn(`[Git Analyzer] Error analyzing context: ${error.message}`);
}
return null;
}
}
module.exports = {
analyzeGitContext,
getRecentCommits,
parseChangelog,
extractDevelopmentKeywords,
buildGitContextQuery,
getCurrentGitInfo
};
// Direct execution support for testing
if (require.main === module) {
// Test the git analyzer
analyzeGitContext(process.cwd(), { verbose: true })
.then(context => {
if (context) {
console.log('\n=== GIT CONTEXT ANALYSIS ===');
console.log(`Repository: ${context.gitInfo.branch} (${context.commits.length} recent commits)`);
console.log(`Development keywords: ${context.developmentKeywords.keywords.join(', ')}`);
console.log(`File patterns: ${context.developmentKeywords.filePatterns.join(', ')}`);
console.log(`Themes: ${context.developmentKeywords.themes.join(', ')}`);
if (context.changelogEntries) {
console.log(`Changelog entries: ${context.changelogEntries.length}`);
context.changelogEntries.forEach(entry => {
console.log(` - ${entry.version} (${entry.changes.length} changes)`);
});
}
// Test query building
const queries = buildGitContextQuery({ name: 'test-project' }, context.developmentKeywords);
console.log(`\nGenerated ${queries.length} git-aware queries:`);
queries.forEach((query, idx) => {
console.log(` ${idx + 1}. [${query.type}] ${query.semanticQuery}`);
});
} else {
console.log('No git context available');
}
})
.catch(error => console.error('Git analysis failed:', error));
}
```
--------------------------------------------------------------------------------
/tests/timestamp/test_search_retrieval_inconsistency.py:
--------------------------------------------------------------------------------
```python
#!/usr/bin/env python3
"""
Test script to identify the exact root cause of Issue #99 search inconsistency.
Based on investigation, the issue appears to be in timestamp field mapping
between storage and retrieval in ChromaDB where different timestamp fields
are used for querying vs storing.
"""
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src'))
import asyncio
import json
import tempfile
import time
from datetime import datetime, timedelta
from typing import Dict, List, Any
from mcp_memory_service.models.memory import Memory
from mcp_memory_service.utils.hashing import generate_content_hash
from mcp_memory_service.utils.time_parser import extract_time_expression
from mcp_memory_service.storage.sqlite_vec import SqliteVecMemoryStorage
class SearchRetrievalInconsistencyTest:
"""Test suite to identify search/retrieval timestamp inconsistencies."""
def __init__(self):
self.storage = None
self.test_memories = []
async def setup(self):
"""Set up test environment."""
print("=== Setting up search/retrieval inconsistency test ===")
self.temp_db = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
self.temp_db.close()
self.storage = SqliteVecMemoryStorage(
db_path=self.temp_db.name,
embedding_model="all-MiniLM-L6-v2"
)
await self.storage.initialize()
print(f"✅ Storage initialized: {self.temp_db.name}")
async def cleanup(self):
"""Clean up test environment."""
self.storage = None
if hasattr(self, 'temp_db') and os.path.exists(self.temp_db.name):
os.unlink(self.temp_db.name)
print("✅ Test database cleaned up")
async def create_test_memories_with_specific_timestamps(self):
"""Create test memories with carefully controlled timestamps."""
print("\n🧪 Creating test memories with specific timestamps")
print("-" * 60)
# Calculate specific timestamps for testing
now = time.time()
yesterday_start = now - (24 * 60 * 60) # 24 hours ago
yesterday_middle = yesterday_start + (12 * 60 * 60) # 12 hours into yesterday
yesterday_end = yesterday_start + (23.5 * 60 * 60) # Near end of yesterday
test_cases = [
{
"name": "hook_style_memory_yesterday",
"content": "Hook-generated memory from yesterday's development session",
"timestamp": yesterday_middle,
"tags": ["claude-code-session", "session-consolidation", "yesterday-work"],
"metadata": {
"generated_by": "claude-code-session-end-hook",
"generated_at": datetime.fromtimestamp(yesterday_middle).isoformat() + "Z",
"session_analysis": {"topics": ["development", "testing"]}
},
"memory_type": "session-summary"
},
{
"name": "manual_memory_yesterday",
"content": "Manual note stored yesterday about project progress",
"timestamp": yesterday_end,
"tags": ["manual-note", "project-progress", "yesterday"],
"metadata": {
"created_by": "manual-storage",
"source": "user-input"
},
"memory_type": "note"
},
{
"name": "hook_style_memory_today",
"content": "Hook-generated memory from today's session",
"timestamp": now - (2 * 60 * 60), # 2 hours ago
"tags": ["claude-code-session", "session-consolidation", "today-work"],
"metadata": {
"generated_by": "claude-code-session-end-hook",
"generated_at": datetime.fromtimestamp(now - (2 * 60 * 60)).isoformat() + "Z"
},
"memory_type": "session-summary"
},
{
"name": "manual_memory_today",
"content": "Manual note stored today about urgent task",
"timestamp": now - (1 * 60 * 60), # 1 hour ago
"tags": ["manual-note", "urgent-task", "today"],
"metadata": {
"created_by": "manual-storage",
"source": "user-input"
},
"memory_type": "note"
}
]
stored_memories = []
for case in test_cases:
# Create memory with specific timestamp
memory = Memory(
content=case["content"],
content_hash=generate_content_hash(case["content"]),
tags=case["tags"],
memory_type=case["memory_type"],
metadata=case["metadata"],
created_at=case["timestamp"],
created_at_iso=datetime.fromtimestamp(case["timestamp"]).isoformat() + "Z"
)
# Store the memory
success, message = await self.storage.store(memory)
if success:
stored_memories.append({
"name": case["name"],
"memory": memory,
"expected_timestamp": case["timestamp"]
})
print(f"✅ Stored {case['name']}: {datetime.fromtimestamp(case['timestamp'])}")
else:
print(f"❌ Failed to store {case['name']}: {message}")
self.test_memories = stored_memories
return stored_memories
async def test_time_based_search_consistency(self):
"""Test if time-based searches find all expected memories."""
print("\n🧪 Test 1: Time-Based Search Consistency")
print("-" * 60)
# Test yesterday search
query = "yesterday"
cleaned_query, (start_ts, end_ts) = extract_time_expression(query)
print(f"🔍 Testing query: '{query}'")
print(f"📅 Search range: {datetime.fromtimestamp(start_ts)} to {datetime.fromtimestamp(end_ts)}")
# Check which memories should be found
expected_memories = []
for mem_info in self.test_memories:
if start_ts <= mem_info["expected_timestamp"] <= end_ts:
expected_memories.append(mem_info["name"])
print(f"📋 Expected to find memories: {expected_memories}")
# Perform the search using retrieve (general search)
search_results = await self.storage.retrieve(query, n_results=10)
print(f"🔍 General retrieve found: {len(search_results)} memories")
for result in search_results:
print(f" - {result.memory.content[:50]}...")
# Check if we found the expected memories
found_memories = []
for result in search_results:
for mem_info in self.test_memories:
if result.memory.content == mem_info["memory"].content:
found_memories.append(mem_info["name"])
break
print(f"📋 Actually found memories: {found_memories}")
# Analysis
missing_memories = set(expected_memories) - set(found_memories)
unexpected_memories = set(found_memories) - set(expected_memories)
search_analysis = {
"expected_count": len(expected_memories),
"found_count": len(found_memories),
"missing_memories": list(missing_memories),
"unexpected_memories": list(unexpected_memories),
"search_consistent": len(missing_memories) == 0 and len(unexpected_memories) == 0
}
if search_analysis["search_consistent"]:
print("✅ Time-based search is consistent")
else:
print("❌ Time-based search inconsistency detected!")
if missing_memories:
print(f" Missing: {missing_memories}")
if unexpected_memories:
print(f" Unexpected: {unexpected_memories}")
return search_analysis
async def test_direct_timestamp_queries(self):
"""Test direct timestamp-based queries to isolate the issue."""
print("\n🧪 Test 2: Direct Timestamp Query Analysis")
print("-" * 60)
# Get yesterday's timestamp range
yesterday_query = "yesterday"
cleaned_query, (start_ts, end_ts) = extract_time_expression(yesterday_query)
print(f"🕐 Yesterday range: {start_ts} to {end_ts}")
# Check each stored memory's timestamp against the range
timestamp_analysis = {
"memories_in_range": [],
"memories_out_of_range": [],
"timestamp_precision_issues": []
}
for mem_info in self.test_memories:
memory = mem_info["memory"]
expected_ts = mem_info["expected_timestamp"]
print(f"\n📝 Analyzing {mem_info['name']}:")
print(f" Expected timestamp: {expected_ts} ({datetime.fromtimestamp(expected_ts)})")
print(f" Memory created_at: {memory.created_at}")
print(f" Memory created_at_iso: {memory.created_at_iso}")
# Check if memory should be in yesterday's range
in_range = start_ts <= expected_ts <= end_ts
actually_in_range = start_ts <= (memory.created_at or 0) <= end_ts
if in_range:
timestamp_analysis["memories_in_range"].append(mem_info["name"])
if in_range != actually_in_range:
timestamp_analysis["timestamp_precision_issues"].append({
"memory": mem_info["name"],
"expected_in_range": in_range,
"actually_in_range": actually_in_range,
"expected_timestamp": expected_ts,
"stored_timestamp": memory.created_at
})
print(f" Should be in yesterday range: {in_range}")
print(f" Memory timestamp in range: {actually_in_range}")
print(f"\n📊 Timestamp Analysis Summary:")
print(f" Memories in yesterday range: {len(timestamp_analysis['memories_in_range'])}")
print(f" Timestamp precision issues: {len(timestamp_analysis['timestamp_precision_issues'])}")
return timestamp_analysis
async def test_memory_serialization_fields(self):
"""Test what timestamp fields are actually stored/retrieved."""
print("\n🧪 Test 3: Memory Serialization Fields Analysis")
print("-" * 60)
if not self.test_memories:
print("⚠️ No test memories available for analysis")
return {}
serialization_analysis = {
"memory_field_analysis": [],
"consistent_fields": True
}
for mem_info in self.test_memories:
memory = mem_info["memory"]
# Get the serialized dictionary representation
memory_dict = memory.to_dict()
timestamp_fields = {
"created_at": memory_dict.get("created_at"),
"created_at_iso": memory_dict.get("created_at_iso"),
"timestamp": memory_dict.get("timestamp"),
"timestamp_float": memory_dict.get("timestamp_float"),
"timestamp_str": memory_dict.get("timestamp_str"),
"updated_at": memory_dict.get("updated_at"),
"updated_at_iso": memory_dict.get("updated_at_iso")
}
print(f"\n📝 {mem_info['name']} serialization fields:")
for field, value in timestamp_fields.items():
if value is not None:
if isinstance(value, float):
dt_str = datetime.fromtimestamp(value).isoformat()
print(f" {field}: {value} ({dt_str})")
else:
print(f" {field}: {value}")
else:
print(f" {field}: None")
analysis_entry = {
"memory_name": mem_info["name"],
"timestamp_fields": timestamp_fields,
"has_all_required": all([
timestamp_fields.get("created_at") is not None,
timestamp_fields.get("created_at_iso") is not None,
timestamp_fields.get("timestamp") is not None
])
}
serialization_analysis["memory_field_analysis"].append(analysis_entry)
if not analysis_entry["has_all_required"]:
serialization_analysis["consistent_fields"] = False
return serialization_analysis
async def run_all_tests(self):
"""Run comprehensive search/retrieval inconsistency analysis."""
print("=" * 70)
print("MCP Memory Service: Search/Retrieval Inconsistency Root Cause Analysis")
print("Issue #99 - Final Investigation Phase")
print("=" * 70)
try:
await self.setup()
# Create test data
await self.create_test_memories_with_specific_timestamps()
# Run tests
search_test = await self.test_time_based_search_consistency()
timestamp_test = await self.test_direct_timestamp_queries()
serialization_test = await self.test_memory_serialization_fields()
# Final analysis
print("\n" + "=" * 70)
print("FINAL ROOT CAUSE ANALYSIS")
print("=" * 70)
tests_passed = 0
total_tests = 3
# Search consistency
if search_test.get("search_consistent", False):
print("✅ PASS: Time-based search is consistent")
tests_passed += 1
else:
print("❌ FAIL: Time-based search inconsistency confirmed")
print(f" Missing: {search_test.get('missing_memories', [])}")
print(f" Unexpected: {search_test.get('unexpected_memories', [])}")
# Timestamp precision
precision_issues = timestamp_test.get("timestamp_precision_issues", [])
if len(precision_issues) == 0:
print("✅ PASS: Timestamp precision is correct")
tests_passed += 1
else:
print("❌ FAIL: Timestamp precision issues detected")
for issue in precision_issues:
print(f" {issue['memory']}: expected={issue['expected_in_range']}, actual={issue['actually_in_range']}")
# Field serialization
if serialization_test.get("consistent_fields", False):
print("✅ PASS: Memory serialization fields consistent")
tests_passed += 1
else:
print("❌ FAIL: Memory serialization field issues")
print(f"\nOverall Result: {tests_passed}/{total_tests} tests passed")
# Root cause determination
print("\n🎯 DEFINITIVE ROOT CAUSE:")
if tests_passed == total_tests:
print("• Storage and serialization are working correctly")
print("• Issue #99 might be in a different storage backend or search implementation")
print("• The problem could be client-side or in specific edge cases")
else:
print("• CONFIRMED: Search/retrieval inconsistencies exist")
if not search_test.get("search_consistent", False):
print(" → Time-based search is not finding expected memories")
if precision_issues:
print(" → Timestamp precision/handling issues in queries")
if not serialization_test.get("consistent_fields", False):
print(" → Memory serialization field inconsistencies")
print("\n💡 RECOMMENDED FIXES:")
if not search_test.get("search_consistent", False):
print("• Review time-based search implementation in storage backends")
print("• Ensure timestamp field mapping is consistent between store and query")
if precision_issues:
print("• Fix timestamp precision handling in search queries")
if not serialization_test.get("consistent_fields", False):
print("• Standardize timestamp field serialization across all memories")
return tests_passed == total_tests
finally:
await self.cleanup()
async def main():
"""Main test execution."""
test_suite = SearchRetrievalInconsistencyTest()
success = await test_suite.run_all_tests()
return 0 if success else 1
if __name__ == "__main__":
exit_code = asyncio.run(main())
sys.exit(exit_code)
```
--------------------------------------------------------------------------------
/docs/natural-memory-triggers/performance-optimization.md:
--------------------------------------------------------------------------------
```markdown
# Natural Memory Triggers v7.1.3 - Performance Optimization Guide
This guide provides comprehensive strategies for optimizing Natural Memory Triggers performance to achieve the best balance of speed, accuracy, and resource usage for your specific workflow.
## Performance Overview
Natural Memory Triggers uses a sophisticated multi-tier architecture designed for optimal performance:
### Performance Tiers
| Tier | Target Latency | Processing | Accuracy | Use Case |
|------|---------------|------------|-----------|----------|
| **Instant** | < 50ms | Pattern matching, cache checks | 85% | Common memory-seeking patterns |
| **Fast** | < 150ms | Lightweight semantic analysis | 90% | Topic shifts, question patterns |
| **Intensive** | < 500ms | Deep semantic understanding | 95% | Complex context analysis |
### Real-World Benchmarks
**Production Performance Metrics:**
- ✅ **85%+ trigger accuracy** across all processing tiers
- ✅ **<50ms instant analysis** for cached and pattern-matched queries
- ✅ **<150ms fast analysis** for semantic topic detection
- ✅ **<5ms cache performance** with LRU management
- ✅ **Zero user-facing latency** with background processing
## Performance Profiles
Choose the right profile based on your current workflow needs:
### 🏃 Speed Focused Profile
Optimized for minimal latency with basic memory awareness.
```bash
node memory-mode-controller.js profile speed_focused
```
**Configuration:**
- **Max Latency**: 100ms
- **Enabled Tiers**: Instant only
- **Background Processing**: Disabled
- **Cache Aggressiveness**: High
**Best For:**
- Quick coding sessions
- Pair programming
- Time-sensitive development work
- Performance-critical environments
**Trade-offs:**
- Minimal memory awareness
- Only pattern-based detection
- No semantic analysis
- Reduced context accuracy
**Optimization Tips:**
```bash
# Increase cache size for better hit rates
node memory-mode-controller.js config set performance.cacheSize 100
# Reduce cooldown for faster triggers
node memory-mode-controller.js config set naturalTriggers.cooldownPeriod 15000
# Lower memory limit for faster responses
node memory-mode-controller.js config set naturalTriggers.maxMemoriesPerTrigger 3
```
### ⚖️ Balanced Profile (Recommended)
Optimal balance of speed and context awareness for general development.
```bash
node memory-mode-controller.js profile balanced
```
**Configuration:**
- **Max Latency**: 200ms
- **Enabled Tiers**: Instant + Fast
- **Background Processing**: Enabled
- **Degradation Threshold**: 400ms
**Best For:**
- Daily development work
- General coding sessions
- Code reviews and debugging
- Most productive for regular use
**Optimization Tips:**
```bash
# Fine-tune sensitivity for your preference
node memory-mode-controller.js sensitivity 0.6
# Monitor performance regularly
node memory-mode-controller.js metrics
# Adjust based on user satisfaction
node memory-mode-controller.js config set performance.autoAdjust true
```
### 🧠 Memory Aware Profile
Maximum context awareness with acceptable higher latency.
```bash
node memory-mode-controller.js profile memory_aware
```
**Configuration:**
- **Max Latency**: 500ms
- **Enabled Tiers**: All (Instant + Fast + Intensive)
- **Background Processing**: Enabled
- **Context Analysis**: Deep semantic understanding
**Best For:**
- Architectural decision sessions
- Complex problem solving
- Research and exploration work
- When context quality is paramount
**Optimization Tips:**
```bash
# Enable all analysis features
node memory-mode-controller.js config set performance.enableFullAnalysis true
# Increase memory retrieval for better context
node memory-mode-controller.js config set naturalTriggers.maxMemoriesPerTrigger 8
# Enable conversation context tracking
node memory-mode-controller.js config set performance.trackConversationContext true
```
### 🤖 Adaptive Profile
Machine learning-based optimization that learns your preferences.
```bash
node memory-mode-controller.js profile adaptive
```
**Configuration:**
- **Max Latency**: Auto-adjusting (100ms - 800ms)
- **Enabled Tiers**: Dynamic based on usage patterns
- **User Feedback**: Tracks satisfaction and adjusts
- **Learning Rate**: 0.05 (configurable)
**Optimization Process:**
1. **Learning Phase** (first 50 interactions): Collects usage data
2. **Adjustment Phase** (ongoing): Optimizes based on patterns
3. **Feedback Integration**: Incorporates user satisfaction signals
4. **Performance Tuning**: Adjusts tiers and thresholds automatically
**Monitoring Adaptive Learning:**
```bash
# Check learning progress
node memory-mode-controller.js metrics --learning
# View adaptation history
node memory-mode-controller.js config get performance.adaptationHistory
# Reset learning data if needed
node memory-mode-controller.js config set performance.resetLearning true
```
## Performance Monitoring
### Real-Time Metrics
Monitor system performance in real-time:
```bash
# Basic performance overview
node memory-mode-controller.js status
# Detailed performance metrics
node memory-mode-controller.js metrics
# Continuous monitoring (updates every 5 seconds)
watch -n 5 "node ~/.claude/hooks/memory-mode-controller.js metrics"
```
**Key Metrics to Monitor:**
#### Response Time Metrics
- **Average Latency**: Overall response time across all tiers
- **Tier-Specific Latency**: Performance breakdown by processing tier
- **Cache Hit Rate**: Percentage of requests served from cache
- **Memory Service Latency**: Backend response times
#### Accuracy Metrics
- **Trigger Accuracy**: Percentage of relevant memory retrievals
- **False Positive Rate**: Percentage of irrelevant triggers
- **User Satisfaction**: Adaptive feedback scoring
- **Success Rate**: Overall system effectiveness
#### Resource Usage Metrics
- **Cache Size**: Current semantic cache utilization
- **Memory Usage**: Node.js heap and memory consumption
- **CPU Usage**: Processing overhead (available with `--system` flag)
- **Network I/O**: Memory service communication overhead
### Performance Alerts
Set up automated performance monitoring:
```bash
# Create performance monitoring script
cat > ~/nmt-monitor.sh << 'EOF'
#!/bin/bash
METRICS=$(node ~/.claude/hooks/memory-mode-controller.js metrics --json)
AVG_LATENCY=$(echo $METRICS | jq '.performance.avgLatency')
if [ $AVG_LATENCY -gt 300 ]; then
echo "⚠️ High latency detected: ${AVG_LATENCY}ms"
# Could trigger notifications, logging, or automatic optimization
fi
EOF
chmod +x ~/nmt-monitor.sh
# Add to crontab for regular monitoring
(crontab -l ; echo "*/5 * * * * ~/nmt-monitor.sh") | crontab -
```
## Cache Optimization
The semantic cache is crucial for performance. Optimize it based on your usage patterns:
### Cache Configuration
```bash
# View current cache statistics
node memory-mode-controller.js cache stats
# Adjust cache size based on memory availability
node memory-mode-controller.js config set performance.cacheSize 75 # entries
# Configure cache cleanup behavior
node memory-mode-controller.js config set performance.cacheCleanupThreshold 0.8
```
### Cache Performance Analysis
```bash
# Analyze cache effectiveness
node memory-mode-controller.js cache analyze
# Example output:
Cache Performance Analysis:
Hit Rate: 42% (ideal: >30%)
Average Hit Time: 3.2ms
Average Miss Time: 147ms
Most Valuable Cached Patterns:
- "what did we decide": 15 hits, 180ms saved
- "how did we implement": 12 hits, 134ms saved
- "similar to what we": 8 hits, 98ms saved
```
### Cache Optimization Strategies
#### High Hit Rate Strategy
```bash
# Increase cache size for better retention
node memory-mode-controller.js config set performance.cacheSize 100
# Increase pattern retention time
node memory-mode-controller.js config set performance.cacheRetentionTime 3600000 # 1 hour
```
#### Memory-Conscious Strategy
```bash
# Reduce cache size for lower memory usage
node memory-mode-controller.js config set performance.cacheSize 25
# More aggressive cleanup
node memory-mode-controller.js config set performance.cacheCleanupThreshold 0.6
```
## Memory Service Optimization
Optimize communication with the MCP Memory Service:
### Connection Configuration
```bash
# Adjust timeout settings for your environment
node memory-mode-controller.js config set memoryService.timeout 5000
# Configure connection pooling (if available)
node memory-mode-controller.js config set memoryService.connectionPool.maxConnections 3
# Enable keep-alive for persistent connections
node memory-mode-controller.js config set memoryService.keepAlive true
```
### Backend-Specific Optimization
#### SQLite-vec Backend
```bash
# Optimize for local performance
node memory-mode-controller.js config set memoryService.localOptimizations true
node memory-mode-controller.js config set memoryService.timeout 3000
```
#### Cloudflare Backend
```bash
# Optimize for network latency
node memory-mode-controller.js config set memoryService.timeout 8000
node memory-mode-controller.js config set memoryService.retryAttempts 2
```
#### ChromaDB Backend
```bash
# Optimize for multi-client access
node memory-mode-controller.js config set memoryService.timeout 6000
node memory-mode-controller.js config set memoryService.batchRequests true
```
## Git Integration Optimization
Optimize Git-aware context analysis for better performance:
### Repository Analysis Configuration
```bash
# Limit commit analysis scope for performance
node memory-mode-controller.js config set gitAnalysis.commitLookback 7 # days
node memory-mode-controller.js config set gitAnalysis.maxCommits 10
# Cache git analysis results
node memory-mode-controller.js config set gitAnalysis.cacheResults true
node memory-mode-controller.js config set gitAnalysis.cacheExpiry 1800 # 30 minutes
```
### Large Repository Optimization
For repositories with extensive history:
```bash
# Reduce analysis depth
node memory-mode-controller.js config set gitAnalysis.maxCommits 5
node memory-mode-controller.js config set gitAnalysis.commitLookback 3
# Skip changelog parsing for performance
node memory-mode-controller.js config set gitAnalysis.includeChangelog false
# Use lightweight git operations
node memory-mode-controller.js config set gitAnalysis.lightweight true
```
## System Resource Optimization
### Memory Usage Optimization
Monitor and optimize Node.js memory usage:
```bash
# Check current memory usage
node --expose-gc -e "
const used = process.memoryUsage();
console.log('Memory usage:');
for (let key in used) {
console.log(\`\${key}: \${Math.round(used[key] / 1024 / 1024 * 100) / 100} MB\`);
}
"
# Configure garbage collection for better performance
export NODE_OPTIONS="--max-old-space-size=512 --gc-interval=100"
node memory-mode-controller.js status
```
### CPU Usage Optimization
#### Single-Core Optimization
```bash
# Disable background processing for CPU-constrained environments
node memory-mode-controller.js config set performance.backgroundProcessing false
# Reduce concurrent operations
node memory-mode-controller.js config set performance.maxConcurrentAnalysis 1
```
#### Multi-Core Optimization
```bash
# Enable parallel processing (if available)
node memory-mode-controller.js config set performance.enableParallelProcessing true
# Increase concurrent analysis threads
node memory-mode-controller.js config set performance.maxConcurrentAnalysis 3
```
## Performance Troubleshooting
### Common Performance Issues
#### High Latency
**Symptoms**: Response times consistently above target thresholds
**Diagnosis**:
```bash
# Identify bottlenecks
node memory-mode-controller.js metrics --breakdown
# Test memory service directly
curl -w "@curl-format.txt" -k https://localhost:8443/api/health
# Check system resources
top -p $(pgrep -f memory-mode-controller)
```
**Solutions**:
1. **Switch to faster profile**: `node memory-mode-controller.js profile speed_focused`
2. **Optimize cache**: Increase cache size and check hit rates
3. **Memory service optimization**: Check backend performance
4. **Reduce analysis depth**: Lower commit lookback and max commits
#### Cache Misses
**Symptoms**: Low cache hit rate (< 20%)
**Diagnosis**:
```bash
node memory-mode-controller.js cache analyze
```
**Solutions**:
1. **Increase cache size**: `node memory-mode-controller.js config set performance.cacheSize 100`
2. **Adjust cache retention**: Increase cache cleanup threshold
3. **Pattern analysis**: Review most common missed patterns
#### Memory Service Timeouts
**Symptoms**: Frequent timeout errors in metrics
**Diagnosis**:
```bash
# Test memory service responsiveness
time curl -k https://localhost:8443/api/health
# Check service logs
tail -f ~/Library/Logs/Claude/mcp-server-memory.log
```
**Solutions**:
1. **Increase timeout**: `node memory-mode-controller.js config set memoryService.timeout 10000`
2. **Check backend**: Switch to faster backend if available
3. **Network optimization**: Ensure local service deployment
### Performance Profiling
#### Detailed Timing Analysis
Enable detailed timing for performance analysis:
```bash
# Enable timing instrumentation
export CLAUDE_HOOKS_TIMING=true
node memory-mode-controller.js test "What did we decide about authentication?"
# Example output with timing:
🧪 Testing Natural Memory Triggers [TIMING ENABLED]
Query: "What did we decide about authentication?"
[0ms] Starting analysis
[2ms] Cache check: miss
[7ms] Pattern analysis complete
[45ms] Instant tier complete (confidence: 0.85)
[147ms] Fast tier complete (confidence: 0.78)
[389ms] Intensive tier complete (confidence: 0.92)
[421ms] Memory query generated
[567ms] Memory service response received
[572ms] Analysis complete
Total Time: 572ms
```
#### Memory Profiling
Profile memory usage patterns:
```bash
# Generate memory profile
node --inspect ~/.claude/hooks/memory-mode-controller.js status &
# Open Chrome DevTools to chrome://inspect for memory analysis
```
## Performance Best Practices
### Workflow-Specific Optimization
#### Development Sessions
```bash
# Morning setup for general development
node memory-mode-controller.js profile balanced
node memory-mode-controller.js sensitivity 0.6
```
#### Architecture Sessions
```bash
# Setup for architecture work
node memory-mode-controller.js profile memory_aware
node memory-mode-controller.js sensitivity 0.4
```
#### Quick Fixes/Debugging
```bash
# Setup for focused debugging
node memory-mode-controller.js profile speed_focused
node memory-mode-controller.js sensitivity 0.8
```
### Maintenance Routines
#### Daily Maintenance
```bash
# Check system health
node memory-mode-controller.js health
# Review performance metrics
node memory-mode-controller.js metrics
# Clear cache if hit rate is low
if [ $(node memory-mode-controller.js cache stats --json | jq '.hitRate < 0.2') ]; then
node memory-mode-controller.js cache clear
fi
```
#### Weekly Optimization
```bash
# Export performance data for analysis
node memory-mode-controller.js export metrics > weekly-metrics.json
# Review and adjust configuration based on usage patterns
node memory-mode-controller.js metrics --recommendations
# Update adaptive learning if needed
node memory-mode-controller.js config set performance.learningRate 0.1
```
## Advanced Performance Features
### Custom Performance Profiles
Create custom performance profiles for specific use cases:
```bash
# Create custom profile for code reviews
node memory-mode-controller.js config set performance.profiles.code_review '{
"maxLatency": 250,
"enabledTiers": ["instant", "fast"],
"backgroundProcessing": true,
"degradeThreshold": 500,
"description": "Optimized for code review sessions"
}'
# Activate custom profile
node memory-mode-controller.js profile code_review
```
### Performance Automation
Automate performance optimization based on context:
```bash
# Create context-aware performance script
cat > ~/nmt-auto-optimize.sh << 'EOF'
#!/bin/bash
# Check current time and adjust profile accordingly
HOUR=$(date +%H)
if [ $HOUR -ge 9 ] && [ $HOUR -le 11 ]; then
# Morning: architecture work
node ~/.claude/hooks/memory-mode-controller.js profile memory_aware
elif [ $HOUR -ge 14 ] && [ $HOUR -le 16 ]; then
# Afternoon: general development
node ~/.claude/hooks/memory-mode-controller.js profile balanced
else
# Other times: speed focused
node ~/.claude/hooks/memory-mode-controller.js profile speed_focused
fi
EOF
chmod +x ~/nmt-auto-optimize.sh
# Add to login scripts or IDE startup
```
---
**Natural Memory Triggers v7.1.3** provides extensive performance optimization capabilities to ensure optimal speed and accuracy for your specific development workflow! 🚀
```