This is page 8 of 47. Use http://codebase.md/doobidoo/mcp-memory-service?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .claude
│ ├── agents
│ │ ├── amp-bridge.md
│ │ ├── amp-pr-automator.md
│ │ ├── code-quality-guard.md
│ │ ├── gemini-pr-automator.md
│ │ └── github-release-manager.md
│ ├── settings.local.json.backup
│ └── settings.local.json.local
├── .commit-message
├── .dockerignore
├── .env.example
├── .env.sqlite.backup
├── .envnn#
├── .gitattributes
├── .github
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ ├── feature_request.yml
│ │ └── performance_issue.yml
│ ├── pull_request_template.md
│ └── workflows
│ ├── bridge-tests.yml
│ ├── CACHE_FIX.md
│ ├── claude-code-review.yml
│ ├── claude.yml
│ ├── cleanup-images.yml.disabled
│ ├── dev-setup-validation.yml
│ ├── docker-publish.yml
│ ├── LATEST_FIXES.md
│ ├── main-optimized.yml.disabled
│ ├── main.yml
│ ├── publish-and-test.yml
│ ├── README_OPTIMIZATION.md
│ ├── release-tag.yml.disabled
│ ├── release.yml
│ ├── roadmap-review-reminder.yml
│ ├── SECRET_CONDITIONAL_FIX.md
│ └── WORKFLOW_FIXES.md
├── .gitignore
├── .mcp.json.backup
├── .mcp.json.template
├── .pyscn
│ ├── .gitignore
│ └── reports
│ └── analyze_20251123_214224.html
├── AGENTS.md
├── archive
│ ├── deployment
│ │ ├── deploy_fastmcp_fixed.sh
│ │ ├── deploy_http_with_mcp.sh
│ │ └── deploy_mcp_v4.sh
│ ├── deployment-configs
│ │ ├── empty_config.yml
│ │ └── smithery.yaml
│ ├── development
│ │ └── test_fastmcp.py
│ ├── docs-removed-2025-08-23
│ │ ├── authentication.md
│ │ ├── claude_integration.md
│ │ ├── claude-code-compatibility.md
│ │ ├── claude-code-integration.md
│ │ ├── claude-code-quickstart.md
│ │ ├── claude-desktop-setup.md
│ │ ├── complete-setup-guide.md
│ │ ├── database-synchronization.md
│ │ ├── development
│ │ │ ├── autonomous-memory-consolidation.md
│ │ │ ├── CLEANUP_PLAN.md
│ │ │ ├── CLEANUP_README.md
│ │ │ ├── CLEANUP_SUMMARY.md
│ │ │ ├── dream-inspired-memory-consolidation.md
│ │ │ ├── hybrid-slm-memory-consolidation.md
│ │ │ ├── mcp-milestone.md
│ │ │ ├── multi-client-architecture.md
│ │ │ ├── test-results.md
│ │ │ └── TIMESTAMP_FIX_SUMMARY.md
│ │ ├── distributed-sync.md
│ │ ├── invocation_guide.md
│ │ ├── macos-intel.md
│ │ ├── master-guide.md
│ │ ├── mcp-client-configuration.md
│ │ ├── multi-client-server.md
│ │ ├── service-installation.md
│ │ ├── sessions
│ │ │ └── MCP_ENHANCEMENT_SESSION_MEMORY_v4.1.0.md
│ │ ├── UBUNTU_SETUP.md
│ │ ├── ubuntu.md
│ │ ├── windows-setup.md
│ │ └── windows.md
│ ├── docs-root-cleanup-2025-08-23
│ │ ├── AWESOME_LIST_SUBMISSION.md
│ │ ├── CLOUDFLARE_IMPLEMENTATION.md
│ │ ├── DOCUMENTATION_ANALYSIS.md
│ │ ├── DOCUMENTATION_CLEANUP_PLAN.md
│ │ ├── DOCUMENTATION_CONSOLIDATION_COMPLETE.md
│ │ ├── LITESTREAM_SETUP_GUIDE.md
│ │ ├── lm_studio_system_prompt.md
│ │ ├── PYTORCH_DOWNLOAD_FIX.md
│ │ └── README-ORIGINAL-BACKUP.md
│ ├── investigations
│ │ └── MACOS_HOOKS_INVESTIGATION.md
│ ├── litestream-configs-v6.3.0
│ │ ├── install_service.sh
│ │ ├── litestream_master_config_fixed.yml
│ │ ├── litestream_master_config.yml
│ │ ├── litestream_replica_config_fixed.yml
│ │ ├── litestream_replica_config.yml
│ │ ├── litestream_replica_simple.yml
│ │ ├── litestream-http.service
│ │ ├── litestream.service
│ │ └── requirements-cloudflare.txt
│ ├── release-notes
│ │ └── release-notes-v7.1.4.md
│ └── setup-development
│ ├── README.md
│ ├── setup_consolidation_mdns.sh
│ ├── STARTUP_SETUP_GUIDE.md
│ └── test_service.sh
├── CHANGELOG-HISTORIC.md
├── CHANGELOG.md
├── claude_commands
│ ├── memory-context.md
│ ├── memory-health.md
│ ├── memory-ingest-dir.md
│ ├── memory-ingest.md
│ ├── memory-recall.md
│ ├── memory-search.md
│ ├── memory-store.md
│ ├── README.md
│ └── session-start.md
├── claude-hooks
│ ├── config.json
│ ├── config.template.json
│ ├── CONFIGURATION.md
│ ├── core
│ │ ├── memory-retrieval.js
│ │ ├── mid-conversation.js
│ │ ├── session-end.js
│ │ ├── session-start.js
│ │ └── topic-change.js
│ ├── debug-pattern-test.js
│ ├── install_claude_hooks_windows.ps1
│ ├── install_hooks.py
│ ├── memory-mode-controller.js
│ ├── MIGRATION.md
│ ├── README-NATURAL-TRIGGERS.md
│ ├── README-phase2.md
│ ├── README.md
│ ├── simple-test.js
│ ├── statusline.sh
│ ├── test-adaptive-weights.js
│ ├── test-dual-protocol-hook.js
│ ├── test-mcp-hook.js
│ ├── test-natural-triggers.js
│ ├── test-recency-scoring.js
│ ├── tests
│ │ ├── integration-test.js
│ │ ├── phase2-integration-test.js
│ │ ├── test-code-execution.js
│ │ ├── test-cross-session.json
│ │ ├── test-session-tracking.json
│ │ └── test-threading.json
│ ├── utilities
│ │ ├── adaptive-pattern-detector.js
│ │ ├── context-formatter.js
│ │ ├── context-shift-detector.js
│ │ ├── conversation-analyzer.js
│ │ ├── dynamic-context-updater.js
│ │ ├── git-analyzer.js
│ │ ├── mcp-client.js
│ │ ├── memory-client.js
│ │ ├── memory-scorer.js
│ │ ├── performance-manager.js
│ │ ├── project-detector.js
│ │ ├── session-tracker.js
│ │ ├── tiered-conversation-monitor.js
│ │ └── version-checker.js
│ └── WINDOWS-SESSIONSTART-BUG.md
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Development-Sprint-November-2025.md
├── docs
│ ├── amp-cli-bridge.md
│ ├── api
│ │ ├── code-execution-interface.md
│ │ ├── memory-metadata-api.md
│ │ ├── PHASE1_IMPLEMENTATION_SUMMARY.md
│ │ ├── PHASE2_IMPLEMENTATION_SUMMARY.md
│ │ ├── PHASE2_REPORT.md
│ │ └── tag-standardization.md
│ ├── architecture
│ │ ├── search-enhancement-spec.md
│ │ └── search-examples.md
│ ├── architecture.md
│ ├── archive
│ │ └── obsolete-workflows
│ │ ├── load_memory_context.md
│ │ └── README.md
│ ├── assets
│ │ └── images
│ │ ├── dashboard-v3.3.0-preview.png
│ │ ├── memory-awareness-hooks-example.png
│ │ ├── project-infographic.svg
│ │ └── README.md
│ ├── CLAUDE_CODE_QUICK_REFERENCE.md
│ ├── cloudflare-setup.md
│ ├── deployment
│ │ ├── docker.md
│ │ ├── dual-service.md
│ │ ├── production-guide.md
│ │ └── systemd-service.md
│ ├── development
│ │ ├── ai-agent-instructions.md
│ │ ├── code-quality
│ │ │ ├── phase-2a-completion.md
│ │ │ ├── phase-2a-handle-get-prompt.md
│ │ │ ├── phase-2a-index.md
│ │ │ ├── phase-2a-install-package.md
│ │ │ └── phase-2b-session-summary.md
│ │ ├── code-quality-workflow.md
│ │ ├── dashboard-workflow.md
│ │ ├── issue-management.md
│ │ ├── pr-review-guide.md
│ │ ├── refactoring-notes.md
│ │ ├── release-checklist.md
│ │ └── todo-tracker.md
│ ├── docker-optimized-build.md
│ ├── document-ingestion.md
│ ├── DOCUMENTATION_AUDIT.md
│ ├── enhancement-roadmap-issue-14.md
│ ├── examples
│ │ ├── analysis-scripts.js
│ │ ├── maintenance-session-example.md
│ │ ├── memory-distribution-chart.jsx
│ │ └── tag-schema.json
│ ├── first-time-setup.md
│ ├── glama-deployment.md
│ ├── guides
│ │ ├── advanced-command-examples.md
│ │ ├── chromadb-migration.md
│ │ ├── commands-vs-mcp-server.md
│ │ ├── mcp-enhancements.md
│ │ ├── mdns-service-discovery.md
│ │ ├── memory-consolidation-guide.md
│ │ ├── migration.md
│ │ ├── scripts.md
│ │ └── STORAGE_BACKENDS.md
│ ├── HOOK_IMPROVEMENTS.md
│ ├── hooks
│ │ └── phase2-code-execution-migration.md
│ ├── http-server-management.md
│ ├── ide-compatability.md
│ ├── IMAGE_RETENTION_POLICY.md
│ ├── images
│ │ └── dashboard-placeholder.md
│ ├── implementation
│ │ ├── health_checks.md
│ │ └── performance.md
│ ├── IMPLEMENTATION_PLAN_HTTP_SSE.md
│ ├── integration
│ │ ├── homebrew.md
│ │ └── multi-client.md
│ ├── integrations
│ │ ├── gemini.md
│ │ ├── groq-bridge.md
│ │ ├── groq-integration-summary.md
│ │ └── groq-model-comparison.md
│ ├── integrations.md
│ ├── legacy
│ │ └── dual-protocol-hooks.md
│ ├── LM_STUDIO_COMPATIBILITY.md
│ ├── maintenance
│ │ └── memory-maintenance.md
│ ├── mastery
│ │ ├── api-reference.md
│ │ ├── architecture-overview.md
│ │ ├── configuration-guide.md
│ │ ├── local-setup-and-run.md
│ │ ├── testing-guide.md
│ │ └── troubleshooting.md
│ ├── migration
│ │ └── code-execution-api-quick-start.md
│ ├── natural-memory-triggers
│ │ ├── cli-reference.md
│ │ ├── installation-guide.md
│ │ └── performance-optimization.md
│ ├── oauth-setup.md
│ ├── pr-graphql-integration.md
│ ├── quick-setup-cloudflare-dual-environment.md
│ ├── README.md
│ ├── remote-configuration-wiki-section.md
│ ├── research
│ │ ├── code-execution-interface-implementation.md
│ │ └── code-execution-interface-summary.md
│ ├── ROADMAP.md
│ ├── sqlite-vec-backend.md
│ ├── statistics
│ │ ├── charts
│ │ │ ├── activity_patterns.png
│ │ │ ├── contributors.png
│ │ │ ├── growth_trajectory.png
│ │ │ ├── monthly_activity.png
│ │ │ └── october_sprint.png
│ │ ├── data
│ │ │ ├── activity_by_day.csv
│ │ │ ├── activity_by_hour.csv
│ │ │ ├── contributors.csv
│ │ │ └── monthly_activity.csv
│ │ ├── generate_charts.py
│ │ └── REPOSITORY_STATISTICS.md
│ ├── technical
│ │ ├── development.md
│ │ ├── memory-migration.md
│ │ ├── migration-log.md
│ │ ├── sqlite-vec-embedding-fixes.md
│ │ └── tag-storage.md
│ ├── testing
│ │ └── regression-tests.md
│ ├── testing-cloudflare-backend.md
│ ├── troubleshooting
│ │ ├── cloudflare-api-token-setup.md
│ │ ├── cloudflare-authentication.md
│ │ ├── general.md
│ │ ├── hooks-quick-reference.md
│ │ ├── pr162-schema-caching-issue.md
│ │ ├── session-end-hooks.md
│ │ └── sync-issues.md
│ └── tutorials
│ ├── advanced-techniques.md
│ ├── data-analysis.md
│ └── demo-session-walkthrough.md
├── examples
│ ├── claude_desktop_config_template.json
│ ├── claude_desktop_config_windows.json
│ ├── claude-desktop-http-config.json
│ ├── config
│ │ └── claude_desktop_config.json
│ ├── http-mcp-bridge.js
│ ├── memory_export_template.json
│ ├── README.md
│ ├── setup
│ │ └── setup_multi_client_complete.py
│ └── start_https_example.sh
├── install_service.py
├── install.py
├── LICENSE
├── NOTICE
├── pyproject.toml
├── pytest.ini
├── README.md
├── run_server.py
├── scripts
│ ├── .claude
│ │ └── settings.local.json
│ ├── archive
│ │ └── check_missing_timestamps.py
│ ├── backup
│ │ ├── backup_memories.py
│ │ ├── backup_sqlite_vec.sh
│ │ ├── export_distributable_memories.sh
│ │ └── restore_memories.py
│ ├── benchmarks
│ │ ├── benchmark_code_execution_api.py
│ │ ├── benchmark_hybrid_sync.py
│ │ └── benchmark_server_caching.py
│ ├── database
│ │ ├── analyze_sqlite_vec_db.py
│ │ ├── check_sqlite_vec_status.py
│ │ ├── db_health_check.py
│ │ └── simple_timestamp_check.py
│ ├── development
│ │ ├── debug_server_initialization.py
│ │ ├── find_orphaned_files.py
│ │ ├── fix_mdns.sh
│ │ ├── fix_sitecustomize.py
│ │ ├── remote_ingest.sh
│ │ ├── setup-git-merge-drivers.sh
│ │ ├── uv-lock-merge.sh
│ │ └── verify_hybrid_sync.py
│ ├── hooks
│ │ └── pre-commit
│ ├── installation
│ │ ├── install_linux_service.py
│ │ ├── install_macos_service.py
│ │ ├── install_uv.py
│ │ ├── install_windows_service.py
│ │ ├── install.py
│ │ ├── setup_backup_cron.sh
│ │ ├── setup_claude_mcp.sh
│ │ └── setup_cloudflare_resources.py
│ ├── linux
│ │ ├── service_status.sh
│ │ ├── start_service.sh
│ │ ├── stop_service.sh
│ │ ├── uninstall_service.sh
│ │ └── view_logs.sh
│ ├── maintenance
│ │ ├── assign_memory_types.py
│ │ ├── check_memory_types.py
│ │ ├── cleanup_corrupted_encoding.py
│ │ ├── cleanup_memories.py
│ │ ├── cleanup_organize.py
│ │ ├── consolidate_memory_types.py
│ │ ├── consolidation_mappings.json
│ │ ├── delete_orphaned_vectors_fixed.py
│ │ ├── fast_cleanup_duplicates_with_tracking.sh
│ │ ├── find_all_duplicates.py
│ │ ├── find_cloudflare_duplicates.py
│ │ ├── find_duplicates.py
│ │ ├── memory-types.md
│ │ ├── README.md
│ │ ├── recover_timestamps_from_cloudflare.py
│ │ ├── regenerate_embeddings.py
│ │ ├── repair_malformed_tags.py
│ │ ├── repair_memories.py
│ │ ├── repair_sqlite_vec_embeddings.py
│ │ ├── repair_zero_embeddings.py
│ │ ├── restore_from_json_export.py
│ │ └── scan_todos.sh
│ ├── migration
│ │ ├── cleanup_mcp_timestamps.py
│ │ ├── legacy
│ │ │ └── migrate_chroma_to_sqlite.py
│ │ ├── mcp-migration.py
│ │ ├── migrate_sqlite_vec_embeddings.py
│ │ ├── migrate_storage.py
│ │ ├── migrate_tags.py
│ │ ├── migrate_timestamps.py
│ │ ├── migrate_to_cloudflare.py
│ │ ├── migrate_to_sqlite_vec.py
│ │ ├── migrate_v5_enhanced.py
│ │ ├── TIMESTAMP_CLEANUP_README.md
│ │ └── verify_mcp_timestamps.py
│ ├── pr
│ │ ├── amp_collect_results.sh
│ │ ├── amp_detect_breaking_changes.sh
│ │ ├── amp_generate_tests.sh
│ │ ├── amp_pr_review.sh
│ │ ├── amp_quality_gate.sh
│ │ ├── amp_suggest_fixes.sh
│ │ ├── auto_review.sh
│ │ ├── detect_breaking_changes.sh
│ │ ├── generate_tests.sh
│ │ ├── lib
│ │ │ └── graphql_helpers.sh
│ │ ├── quality_gate.sh
│ │ ├── resolve_threads.sh
│ │ ├── run_pyscn_analysis.sh
│ │ ├── run_quality_checks.sh
│ │ ├── thread_status.sh
│ │ └── watch_reviews.sh
│ ├── quality
│ │ ├── fix_dead_code_install.sh
│ │ ├── phase1_dead_code_analysis.md
│ │ ├── phase2_complexity_analysis.md
│ │ ├── README_PHASE1.md
│ │ ├── README_PHASE2.md
│ │ ├── track_pyscn_metrics.sh
│ │ └── weekly_quality_review.sh
│ ├── README.md
│ ├── run
│ │ ├── run_mcp_memory.sh
│ │ ├── run-with-uv.sh
│ │ └── start_sqlite_vec.sh
│ ├── run_memory_server.py
│ ├── server
│ │ ├── check_http_server.py
│ │ ├── check_server_health.py
│ │ ├── memory_offline.py
│ │ ├── preload_models.py
│ │ ├── run_http_server.py
│ │ ├── run_memory_server.py
│ │ ├── start_http_server.bat
│ │ └── start_http_server.sh
│ ├── service
│ │ ├── deploy_dual_services.sh
│ │ ├── install_http_service.sh
│ │ ├── mcp-memory-http.service
│ │ ├── mcp-memory.service
│ │ ├── memory_service_manager.sh
│ │ ├── service_control.sh
│ │ ├── service_utils.py
│ │ └── update_service.sh
│ ├── sync
│ │ ├── check_drift.py
│ │ ├── claude_sync_commands.py
│ │ ├── export_memories.py
│ │ ├── import_memories.py
│ │ ├── litestream
│ │ │ ├── apply_local_changes.sh
│ │ │ ├── enhanced_memory_store.sh
│ │ │ ├── init_staging_db.sh
│ │ │ ├── io.litestream.replication.plist
│ │ │ ├── manual_sync.sh
│ │ │ ├── memory_sync.sh
│ │ │ ├── pull_remote_changes.sh
│ │ │ ├── push_to_remote.sh
│ │ │ ├── README.md
│ │ │ ├── resolve_conflicts.sh
│ │ │ ├── setup_local_litestream.sh
│ │ │ ├── setup_remote_litestream.sh
│ │ │ ├── staging_db_init.sql
│ │ │ ├── stash_local_changes.sh
│ │ │ ├── sync_from_remote_noconfig.sh
│ │ │ └── sync_from_remote.sh
│ │ ├── README.md
│ │ ├── safe_cloudflare_update.sh
│ │ ├── sync_memory_backends.py
│ │ └── sync_now.py
│ ├── testing
│ │ ├── run_complete_test.py
│ │ ├── run_memory_test.sh
│ │ ├── simple_test.py
│ │ ├── test_cleanup_logic.py
│ │ ├── test_cloudflare_backend.py
│ │ ├── test_docker_functionality.py
│ │ ├── test_installation.py
│ │ ├── test_mdns.py
│ │ ├── test_memory_api.py
│ │ ├── test_memory_simple.py
│ │ ├── test_migration.py
│ │ ├── test_search_api.py
│ │ ├── test_sqlite_vec_embeddings.py
│ │ ├── test_sse_events.py
│ │ ├── test-connection.py
│ │ └── test-hook.js
│ ├── utils
│ │ ├── claude_commands_utils.py
│ │ ├── generate_personalized_claude_md.sh
│ │ ├── groq
│ │ ├── groq_agent_bridge.py
│ │ ├── list-collections.py
│ │ ├── memory_wrapper_uv.py
│ │ ├── query_memories.py
│ │ ├── smithery_wrapper.py
│ │ ├── test_groq_bridge.sh
│ │ └── uv_wrapper.py
│ └── validation
│ ├── check_dev_setup.py
│ ├── check_documentation_links.py
│ ├── diagnose_backend_config.py
│ ├── validate_configuration_complete.py
│ ├── validate_memories.py
│ ├── validate_migration.py
│ ├── validate_timestamp_integrity.py
│ ├── verify_environment.py
│ ├── verify_pytorch_windows.py
│ └── verify_torch.py
├── SECURITY.md
├── selective_timestamp_recovery.py
├── SPONSORS.md
├── src
│ └── mcp_memory_service
│ ├── __init__.py
│ ├── api
│ │ ├── __init__.py
│ │ ├── client.py
│ │ ├── operations.py
│ │ ├── sync_wrapper.py
│ │ └── types.py
│ ├── backup
│ │ ├── __init__.py
│ │ └── scheduler.py
│ ├── cli
│ │ ├── __init__.py
│ │ ├── ingestion.py
│ │ ├── main.py
│ │ └── utils.py
│ ├── config.py
│ ├── consolidation
│ │ ├── __init__.py
│ │ ├── associations.py
│ │ ├── base.py
│ │ ├── clustering.py
│ │ ├── compression.py
│ │ ├── consolidator.py
│ │ ├── decay.py
│ │ ├── forgetting.py
│ │ ├── health.py
│ │ └── scheduler.py
│ ├── dependency_check.py
│ ├── discovery
│ │ ├── __init__.py
│ │ ├── client.py
│ │ └── mdns_service.py
│ ├── embeddings
│ │ ├── __init__.py
│ │ └── onnx_embeddings.py
│ ├── ingestion
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── chunker.py
│ │ ├── csv_loader.py
│ │ ├── json_loader.py
│ │ ├── pdf_loader.py
│ │ ├── registry.py
│ │ ├── semtools_loader.py
│ │ └── text_loader.py
│ ├── lm_studio_compat.py
│ ├── mcp_server.py
│ ├── models
│ │ ├── __init__.py
│ │ └── memory.py
│ ├── server.py
│ ├── services
│ │ ├── __init__.py
│ │ └── memory_service.py
│ ├── storage
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── cloudflare.py
│ │ ├── factory.py
│ │ ├── http_client.py
│ │ ├── hybrid.py
│ │ └── sqlite_vec.py
│ ├── sync
│ │ ├── __init__.py
│ │ ├── exporter.py
│ │ ├── importer.py
│ │ └── litestream_config.py
│ ├── utils
│ │ ├── __init__.py
│ │ ├── cache_manager.py
│ │ ├── content_splitter.py
│ │ ├── db_utils.py
│ │ ├── debug.py
│ │ ├── document_processing.py
│ │ ├── gpu_detection.py
│ │ ├── hashing.py
│ │ ├── http_server_manager.py
│ │ ├── port_detection.py
│ │ ├── system_detection.py
│ │ └── time_parser.py
│ └── web
│ ├── __init__.py
│ ├── api
│ │ ├── __init__.py
│ │ ├── analytics.py
│ │ ├── backup.py
│ │ ├── consolidation.py
│ │ ├── documents.py
│ │ ├── events.py
│ │ ├── health.py
│ │ ├── manage.py
│ │ ├── mcp.py
│ │ ├── memories.py
│ │ ├── search.py
│ │ └── sync.py
│ ├── app.py
│ ├── dependencies.py
│ ├── oauth
│ │ ├── __init__.py
│ │ ├── authorization.py
│ │ ├── discovery.py
│ │ ├── middleware.py
│ │ ├── models.py
│ │ ├── registration.py
│ │ └── storage.py
│ ├── sse.py
│ └── static
│ ├── app.js
│ ├── index.html
│ ├── README.md
│ ├── sse_test.html
│ └── style.css
├── start_http_debug.bat
├── start_http_server.sh
├── test_document.txt
├── test_version_checker.js
├── tests
│ ├── __init__.py
│ ├── api
│ │ ├── __init__.py
│ │ ├── test_compact_types.py
│ │ └── test_operations.py
│ ├── bridge
│ │ ├── mock_responses.js
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ └── test_http_mcp_bridge.js
│ ├── conftest.py
│ ├── consolidation
│ │ ├── __init__.py
│ │ ├── conftest.py
│ │ ├── test_associations.py
│ │ ├── test_clustering.py
│ │ ├── test_compression.py
│ │ ├── test_consolidator.py
│ │ ├── test_decay.py
│ │ └── test_forgetting.py
│ ├── contracts
│ │ └── api-specification.yml
│ ├── integration
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── test_api_key_fallback.py
│ │ ├── test_api_memories_chronological.py
│ │ ├── test_api_tag_time_search.py
│ │ ├── test_api_with_memory_service.py
│ │ ├── test_bridge_integration.js
│ │ ├── test_cli_interfaces.py
│ │ ├── test_cloudflare_connection.py
│ │ ├── test_concurrent_clients.py
│ │ ├── test_data_serialization_consistency.py
│ │ ├── test_http_server_startup.py
│ │ ├── test_mcp_memory.py
│ │ ├── test_mdns_integration.py
│ │ ├── test_oauth_basic_auth.py
│ │ ├── test_oauth_flow.py
│ │ ├── test_server_handlers.py
│ │ └── test_store_memory.py
│ ├── performance
│ │ ├── test_background_sync.py
│ │ └── test_hybrid_live.py
│ ├── README.md
│ ├── smithery
│ │ └── test_smithery.py
│ ├── sqlite
│ │ └── simple_sqlite_vec_test.py
│ ├── test_client.py
│ ├── test_content_splitting.py
│ ├── test_database.py
│ ├── test_hybrid_cloudflare_limits.py
│ ├── test_hybrid_storage.py
│ ├── test_memory_ops.py
│ ├── test_semantic_search.py
│ ├── test_sqlite_vec_storage.py
│ ├── test_time_parser.py
│ ├── test_timestamp_preservation.py
│ ├── timestamp
│ │ ├── test_hook_vs_manual_storage.py
│ │ ├── test_issue99_final_validation.py
│ │ ├── test_search_retrieval_inconsistency.py
│ │ ├── test_timestamp_issue.py
│ │ └── test_timestamp_simple.py
│ └── unit
│ ├── conftest.py
│ ├── test_cloudflare_storage.py
│ ├── test_csv_loader.py
│ ├── test_fastapi_dependencies.py
│ ├── test_import.py
│ ├── test_json_loader.py
│ ├── test_mdns_simple.py
│ ├── test_mdns.py
│ ├── test_memory_service.py
│ ├── test_memory.py
│ ├── test_semtools_loader.py
│ ├── test_storage_interface_compatibility.py
│ └── test_tag_time_filtering.py
├── tools
│ ├── docker
│ │ ├── DEPRECATED.md
│ │ ├── docker-compose.http.yml
│ │ ├── docker-compose.pythonpath.yml
│ │ ├── docker-compose.standalone.yml
│ │ ├── docker-compose.uv.yml
│ │ ├── docker-compose.yml
│ │ ├── docker-entrypoint-persistent.sh
│ │ ├── docker-entrypoint-unified.sh
│ │ ├── docker-entrypoint.sh
│ │ ├── Dockerfile
│ │ ├── Dockerfile.glama
│ │ ├── Dockerfile.slim
│ │ ├── README.md
│ │ └── test-docker-modes.sh
│ └── README.md
└── uv.lock
```
# Files
--------------------------------------------------------------------------------
/src/mcp_memory_service/web/api/backup.py:
--------------------------------------------------------------------------------
```python
1 | # Copyright 2024 Heinrich Krupp
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | """
16 | Backup management endpoints for MCP Memory Service.
17 |
18 | Provides status monitoring, manual backup triggering, and backup listing.
19 | """
20 |
21 | from typing import Dict, Any, List, Optional, TYPE_CHECKING
22 | from datetime import datetime, timezone
23 |
24 | from fastapi import APIRouter, HTTPException, Depends
25 | from pydantic import BaseModel
26 |
27 | from ...config import OAUTH_ENABLED
28 | from ...backup.scheduler import get_backup_service, get_backup_scheduler
29 |
30 | # OAuth authentication imports (conditional)
31 | if OAUTH_ENABLED or TYPE_CHECKING:
32 | from ..oauth.middleware import require_read_access, require_write_access, AuthenticationResult
33 | else:
34 | # Provide type stubs when OAuth is disabled
35 | AuthenticationResult = None
36 | require_read_access = None
37 | require_write_access = None
38 |
39 | router = APIRouter()
40 |
41 |
42 | class BackupStatusResponse(BaseModel):
43 | """Backup status response model."""
44 | enabled: bool
45 | interval: str
46 | retention_days: int
47 | max_count: int
48 | backup_count: int
49 | total_size_bytes: int
50 | last_backup_time: Optional[float]
51 | time_since_last_seconds: Optional[float]
52 | next_backup_at: Optional[str]
53 | scheduler_running: bool
54 |
55 |
56 | class BackupCreateResponse(BaseModel):
57 | """Backup creation response model."""
58 | success: bool
59 | filename: Optional[str] = None
60 | size_bytes: Optional[int] = None
61 | created_at: Optional[str] = None
62 | duration_seconds: Optional[float] = None
63 | error: Optional[str] = None
64 |
65 |
66 | class BackupInfo(BaseModel):
67 | """Backup information model."""
68 | filename: str
69 | size_bytes: int
70 | created_at: str
71 | age_days: int
72 |
73 |
74 | class BackupListResponse(BaseModel):
75 | """Backup list response model."""
76 | backups: List[BackupInfo]
77 | total_count: int
78 | total_size_bytes: int
79 |
80 |
81 | @router.get("/backup/status", response_model=BackupStatusResponse)
82 | async def get_backup_status(
83 | user: AuthenticationResult = Depends(require_read_access) if OAUTH_ENABLED else None
84 | ):
85 | """
86 | Get current backup service status.
87 |
88 | Returns backup configuration, last backup time, and next scheduled backup.
89 | """
90 | try:
91 | scheduler = get_backup_scheduler()
92 | status = scheduler.get_status()
93 |
94 | return BackupStatusResponse(
95 | enabled=status.get('enabled', False),
96 | interval=status.get('interval', 'daily'),
97 | retention_days=status.get('retention_days', 7),
98 | max_count=status.get('max_count', 10),
99 | backup_count=status.get('backup_count', 0),
100 | total_size_bytes=status.get('total_size_bytes', 0),
101 | last_backup_time=status.get('last_backup_time'),
102 | time_since_last_seconds=status.get('time_since_last_seconds'),
103 | next_backup_at=status.get('next_backup_at'),
104 | scheduler_running=status.get('scheduler_running', False)
105 | )
106 |
107 | except Exception as e:
108 | raise HTTPException(status_code=500, detail=f"Failed to get backup status: {str(e)}")
109 |
110 |
111 | @router.post("/backup/now", response_model=BackupCreateResponse)
112 | async def trigger_backup(
113 | user: AuthenticationResult = Depends(require_write_access) if OAUTH_ENABLED else None
114 | ):
115 | """
116 | Manually trigger an immediate backup.
117 |
118 | Creates a new backup of the database regardless of the schedule.
119 | """
120 | try:
121 | backup_service = get_backup_service()
122 | result = await backup_service.create_backup(description="Manual backup from dashboard")
123 |
124 | if result.get('success'):
125 | return BackupCreateResponse(
126 | success=True,
127 | filename=result.get('filename'),
128 | size_bytes=result.get('size_bytes'),
129 | created_at=result.get('created_at'),
130 | duration_seconds=result.get('duration_seconds')
131 | )
132 | else:
133 | return BackupCreateResponse(
134 | success=False,
135 | error=result.get('error', 'Unknown error')
136 | )
137 |
138 | except Exception as e:
139 | raise HTTPException(status_code=500, detail=f"Failed to create backup: {str(e)}")
140 |
141 |
142 | @router.get("/backup/list", response_model=BackupListResponse)
143 | async def list_backups(
144 | user: AuthenticationResult = Depends(require_read_access) if OAUTH_ENABLED else None
145 | ):
146 | """
147 | List all available backups.
148 |
149 | Returns list of backups sorted by date (newest first).
150 | """
151 | try:
152 | backup_service = get_backup_service()
153 | backups = backup_service.list_backups()
154 |
155 | backup_infos = [
156 | BackupInfo(
157 | filename=b['filename'],
158 | size_bytes=b['size_bytes'],
159 | created_at=b['created_at'],
160 | age_days=b['age_days']
161 | )
162 | for b in backups
163 | ]
164 |
165 | total_size = sum(b['size_bytes'] for b in backups)
166 |
167 | return BackupListResponse(
168 | backups=backup_infos,
169 | total_count=len(backup_infos),
170 | total_size_bytes=total_size
171 | )
172 |
173 | except Exception as e:
174 | raise HTTPException(status_code=500, detail=f"Failed to list backups: {str(e)}")
175 |
```
--------------------------------------------------------------------------------
/scripts/utils/smithery_wrapper.py:
--------------------------------------------------------------------------------
```python
1 | #!/usr/bin/env python3
2 | """
3 | Smithery wrapper for MCP Memory Service
4 | This wrapper is specifically designed for Smithery installations.
5 | It doesn't rely on UV and works with the installed package.
6 | """
7 | import os
8 | import sys
9 | import subprocess
10 | import traceback
11 | import importlib.util
12 |
13 | def print_info(text):
14 | """Print formatted info text."""
15 | print(f"[INFO] {text}", file=sys.stderr, flush=True)
16 |
17 | def print_error(text):
18 | """Print formatted error text."""
19 | print(f"[ERROR] {text}", file=sys.stderr, flush=True)
20 |
21 | def print_success(text):
22 | """Print formatted success text."""
23 | print(f"[SUCCESS] {text}", file=sys.stderr, flush=True)
24 |
25 | def print_warning(text):
26 | """Print formatted warning text."""
27 | print(f"[WARNING] {text}", file=sys.stderr, flush=True)
28 |
29 | def setup_environment():
30 | """Set up the environment for proper MCP Memory Service operation."""
31 | # Set environment variables for better cross-platform compatibility
32 | os.environ.setdefault("PYTORCH_ENABLE_MPS_FALLBACK", "1")
33 |
34 | # For systems with limited GPU memory, use smaller chunks
35 | os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "max_split_size_mb:128")
36 |
37 | # Ensure proper Python path
38 | script_dir = os.path.dirname(os.path.abspath(__file__))
39 | src_dir = os.path.join(script_dir, "src")
40 | if os.path.exists(src_dir) and src_dir not in sys.path:
41 | sys.path.insert(0, src_dir)
42 |
43 | def check_dependencies():
44 | """Check if required dependencies are available."""
45 | required_packages = ["mcp", "chromadb", "sentence_transformers"]
46 | missing_packages = []
47 |
48 | for package in required_packages:
49 | try:
50 | __import__(package)
51 | print_info(f"✓ {package} is available")
52 | except ImportError:
53 | missing_packages.append(package)
54 | print_warning(f"✗ {package} is missing")
55 |
56 | return missing_packages
57 |
58 | def install_missing_packages(packages):
59 | """Try to install missing packages."""
60 | if not packages:
61 | return True
62 |
63 | print_warning("Missing packages detected. For Smithery installations, dependencies should be pre-installed.")
64 | print_info("Attempting to install missing packages with --break-system-packages flag...")
65 |
66 | for package in packages:
67 | try:
68 | # Try user installation first
69 | subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--user', package])
70 | print_success(f"Successfully installed {package}")
71 | except subprocess.SubprocessError:
72 | try:
73 | # Try with --break-system-packages for externally managed environments
74 | subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--break-system-packages', package])
75 | print_success(f"Successfully installed {package}")
76 | except subprocess.SubprocessError as e:
77 | print_error(f"Failed to install {package}: {e}")
78 | print_warning("Continuing anyway - dependencies might be available in different location")
79 | continue
80 |
81 | return True
82 |
83 | def run_memory_service():
84 | """Run the memory service."""
85 | print_info("Starting MCP Memory Service...")
86 |
87 | # Display environment configuration
88 | if "MCP_MEMORY_CHROMA_PATH" in os.environ:
89 | print_info(f"Using ChromaDB path: {os.environ['MCP_MEMORY_CHROMA_PATH']}")
90 |
91 | if "MCP_MEMORY_BACKUPS_PATH" in os.environ:
92 | print_info(f"Using backups path: {os.environ['MCP_MEMORY_BACKUPS_PATH']}")
93 |
94 | try:
95 | # Try to import and run the server directly
96 | from mcp_memory_service.server import main
97 | print_success("Successfully imported memory service")
98 | main()
99 | except ImportError as e:
100 | print_warning(f"Failed to import from installed package: {e}")
101 |
102 | # Fallback to source directory import
103 | script_dir = os.path.dirname(os.path.abspath(__file__))
104 | src_dir = os.path.join(script_dir, "src")
105 |
106 | if os.path.exists(src_dir):
107 | print_info("Trying to import from source directory...")
108 | sys.path.insert(0, src_dir)
109 | try:
110 | from mcp_memory_service.server import main
111 | print_success("Successfully imported from source directory")
112 | main()
113 | except ImportError as import_error:
114 | print_error(f"Failed to import from source directory: {import_error}")
115 | sys.exit(1)
116 | else:
117 | print_error("Could not find memory service source code")
118 | sys.exit(1)
119 | except Exception as e:
120 | print_error(f"Error running memory service: {e}")
121 | traceback.print_exc(file=sys.stderr)
122 | sys.exit(1)
123 |
124 | def main():
125 | """Main entry point for Smithery wrapper."""
126 | print_info("MCP Memory Service - Smithery Wrapper")
127 |
128 | try:
129 | # Set up environment
130 | setup_environment()
131 |
132 | # Check dependencies (informational only)
133 | missing_packages = check_dependencies()
134 |
135 | if missing_packages:
136 | print_warning(f"Some packages appear missing: {', '.join(missing_packages)}")
137 | print_info("Attempting to proceed anyway - packages might be available in different location")
138 |
139 | # Run the memory service
140 | run_memory_service()
141 |
142 | except KeyboardInterrupt:
143 | print_info("Shutting down gracefully...")
144 | sys.exit(0)
145 | except Exception as e:
146 | print_error(f"Unhandled exception: {e}")
147 | traceback.print_exc(file=sys.stderr)
148 | sys.exit(1)
149 |
150 | if __name__ == "__main__":
151 | main()
```
--------------------------------------------------------------------------------
/docs/development/dashboard-workflow.md:
--------------------------------------------------------------------------------
```markdown
1 | # Dashboard Development Workflow
2 |
3 | This guide documents the essential workflow for developing the interactive dashboard UI to prevent repetitive trial-and-error cycles.
4 |
5 | ## Critical Workflow Requirements
6 |
7 | ### 1. Server Restart After Static File Changes ⚠️
8 |
9 | **Problem**: FastAPI/uvicorn caches static files (CSS, JS, HTML) in memory. Changes to these files won't appear in the browser until the server is restarted.
10 |
11 | **Symptoms of Forgetting**:
12 | - Modified JavaScript still shows old console.log statements
13 | - CSS changes don't appear in browser
14 | - File modification time is recent but browser serves old version
15 |
16 | **Solution**:
17 | ```bash
18 | # Restart HTTP server
19 | systemctl --user restart mcp-memory-http.service
20 |
21 | # Then hard refresh browser to clear cache
22 | # Ctrl+Shift+R (Linux/Windows) or Cmd+Shift+R (macOS)
23 | ```
24 |
25 | ### 2. Automated Hooks (Claude Code) ✅
26 |
27 | To eliminate manual restarts, configure automation hooks in `.claude/settings.local.json`:
28 |
29 | ```json
30 | {
31 | "hooks": {
32 | "PostToolUse": [
33 | {
34 | "matchers": [
35 | "Write(file_path:**/web/static/*.css)",
36 | "Edit(file_path:**/web/static/*.css)",
37 | "Write(file_path:**/web/static/*.js)",
38 | "Edit(file_path:**/web/static/*.js)",
39 | "Write(file_path:**/web/static/*.html)",
40 | "Edit(file_path:**/web/static/*.html)"
41 | ],
42 | "hooks": [
43 | {
44 | "type": "command",
45 | "command": "bash",
46 | "args": [
47 | "-c",
48 | "systemctl --user restart mcp-memory-http.service && echo '\n⚠️ REMINDER: Hard refresh browser (Ctrl+Shift+R) to clear cache!'"
49 | ]
50 | }
51 | ]
52 | },
53 | {
54 | "matchers": [
55 | "Write(file_path:**/web/static/*.css)",
56 | "Edit(file_path:**/web/static/*.css)"
57 | ],
58 | "hooks": [
59 | {
60 | "type": "command",
61 | "command": "bash",
62 | "args": [
63 | "-c",
64 | "if grep -E 'background.*:.*white|background.*:.*#fff|color.*:.*white|color.*:.*#fff' /home/hkr/repositories/mcp-memory-service/src/mcp_memory_service/web/static/style.css | grep -v 'dark-mode'; then echo '\n⚠️ WARNING: Found hardcoded light colors in CSS. Check if body.dark-mode overrides are needed!'; fi"
65 | ]
66 | }
67 | ]
68 | }
69 | ]
70 | }
71 | }
72 | ```
73 |
74 | **What This Automates**:
75 | - ✅ Auto-restart HTTP server when CSS/JS/HTML files are modified
76 | - ✅ Display reminder to hard refresh browser
77 | - ✅ Check for hardcoded light colors that need dark mode overrides
78 | - ✅ Prevent the exact issue we had with chunk backgrounds
79 |
80 | ### 3. Dark Mode Compatibility Checklist
81 |
82 | When adding new UI components, always verify dark mode compatibility:
83 |
84 | **Common Issues**:
85 | - Hardcoded `background: white` or `color: white`
86 | - Hardcoded hex colors like `#fff` or `#000`
87 | - Missing `body.dark-mode` overrides for new elements
88 |
89 | **Example Fix** (from PR #164):
90 | ```css
91 | /* BAD: Hardcoded light background */
92 | .chunk-content {
93 | background: white;
94 | color: #333;
95 | }
96 |
97 | /* GOOD: Dark mode override */
98 | body.dark-mode .chunk-content {
99 | background: #111827 !important;
100 | color: #d1d5db !important;
101 | }
102 | ```
103 |
104 | **Automation Hook**: The CSS hook automatically scans for hardcoded colors and warns if dark mode overrides might be needed.
105 |
106 | ### 4. Browser Cache Management
107 |
108 | **Cache-Busting Techniques**:
109 |
110 | 1. **Hard Refresh**: Ctrl+Shift+R (Linux/Windows) or Cmd+Shift+R (macOS)
111 | 2. **URL Parameter**: Add `?nocache=timestamp` to force reload
112 | 3. **DevTools**: Keep DevTools open with "Disable cache" enabled during development
113 |
114 | **Why This Matters**: Even after server restart, browsers aggressively cache static files. You must force a cache clear to see changes.
115 |
116 | ## Development Checklist
117 |
118 | Before testing dashboard changes:
119 |
120 | - [ ] Modified CSS/JS/HTML files
121 | - [ ] Restarted HTTP server (`systemctl --user restart mcp-memory-http.service`)
122 | - [ ] Hard refreshed browser (Ctrl+Shift+R)
123 | - [ ] Checked console for JavaScript errors
124 | - [ ] Verified dark mode compatibility (if CSS changes)
125 | - [ ] Tested both light and dark mode
126 |
127 | ## Performance Benchmarks
128 |
129 | Dashboard performance targets (validated v7.2.2):
130 |
131 | | Component | Target | Typical |
132 | |-----------|--------|---------|
133 | | Page Load | <2s | ~25ms |
134 | | Memory Operations | <1s | ~26ms |
135 | | Tag Search | <500ms | <100ms |
136 |
137 | If performance degrades:
138 | 1. Check browser DevTools Network tab for slow requests
139 | 2. Verify server logs for backend delays
140 | 3. Profile JavaScript execution in DevTools
141 |
142 | ## Testing with browser-mcp
143 |
144 | For UI investigation and debugging:
145 |
146 | ```bash
147 | # Navigate to dashboard
148 | mcp__browsermcp__browser_navigate http://127.0.0.1:8888/
149 |
150 | # Take screenshot
151 | mcp__browsermcp__browser_screenshot
152 |
153 | # Get console logs
154 | mcp__browsermcp__browser_get_console_logs
155 |
156 | # Click elements (requires ref from snapshot)
157 | mcp__browsermcp__browser_click
158 | ```
159 |
160 | ## Common Pitfalls
161 |
162 | 1. **Forgetting server restart** → Use automation hooks!
163 | 2. **Missing browser cache clear** → Always hard refresh
164 | 3. **Dark mode not tested** → Check both themes for every UI change
165 | 4. **Console errors ignored** → Always check browser console
166 | 5. **Mobile responsiveness** → Test at 768px and 1024px breakpoints
167 |
168 | ## Related Documentation
169 |
170 | - **Interactive Dashboard**: See `CLAUDE.md` section "Interactive Dashboard (v7.2.2+)"
171 | - **Performance**: `docs/implementation/performance.md`
172 | - **API Endpoints**: `CLAUDE.md` section "Key Endpoints"
173 | - **Troubleshooting**: Wiki troubleshooting guide
174 |
175 | ---
176 |
177 | **Note**: These automation hooks eliminate 95% of repetitive trial-and-error during dashboard development. Always verify hooks are configured in your local `.claude/settings.local.json`.
178 |
```
--------------------------------------------------------------------------------
/archive/docs-root-cleanup-2025-08-23/AWESOME_LIST_SUBMISSION.md:
--------------------------------------------------------------------------------
```markdown
1 | # Awesome List Submission Guide
2 |
3 | ## MCP Memory Service - Universal Memory Service for AI Applications
4 |
5 | > **Ready-to-use submission templates for awesome lists and community directories**
6 |
7 | This guide provides optimized submission content for promoting MCP Memory Service across various awesome lists and community platforms. Each template is tailored for specific communities while highlighting our unique value propositions.
8 |
9 | ### One-Line Description
10 | **[MCP Memory Service](https://github.com/doobidoo/mcp-memory-service)** - Universal MCP memory service with semantic search, multi-client support, and autonomous consolidation for Claude Desktop, VS Code, and 13+ AI applications.
11 |
12 | ### Detailed Description
13 | A production-ready Model Context Protocol server that provides intelligent semantic memory, persistent storage, and autonomous memory consolidation for AI assistants and development environments. Features universal compatibility with 13+ AI clients including Claude Desktop, VS Code, Cursor, Continue, WindSurf, LM Studio, and Zed.
14 |
15 | ### Key Features for Awesome Lists
16 |
17 | #### For Awesome MCP:
18 | - ✅ **Full MCP Protocol Compliance** - Complete implementation with resources, prompts, and tools
19 | - 🧠 **Semantic Memory Search** - Vector database with sentence transformers for intelligent retrieval
20 | - 🔄 **Autonomous Memory Consolidation** - Dream-inspired system for automatic memory organization
21 | - 🌐 **Multi-Client Support** - Works with 13+ AI applications simultaneously
22 | - 🗄️ **Multiple Storage Backends** - SQLite-vec (default) and ChromaDB support
23 | - 🚀 **Production Ready** - Deployed at scale with Docker, HTTPS, and service installation
24 |
25 | #### For Awesome Claude:
26 | - 🎯 **Native Claude Desktop Integration** - Seamless MCP server configuration
27 | - 💬 **Conversational Memory Commands** - Optional Claude Code commands for direct memory operations
28 | - 🔗 **Multi-Client Coordination** - Use memories across Claude Desktop and other AI tools
29 | - 📊 **Advanced MCP Features** - URI-based resources, guided prompts, progress tracking
30 | - ⚡ **High Performance** - 10x faster startup with SQLite-vec backend
31 | - 🛠️ **Developer Friendly** - Comprehensive documentation and troubleshooting
32 |
33 | #### For Awesome Developer Tools:
34 | - 🛠️ **Universal AI Tool Integration** - Works with VS Code, Continue, Cursor, and other IDEs
35 | - 📝 **Persistent Development Context** - Remember project decisions, architectural choices, and solutions
36 | - 🔍 **Intelligent Search** - Natural language queries for finding past development insights
37 | - 🏗️ **Cross-Project Memory** - Share knowledge across different codebases and teams
38 | - 📈 **Productivity Enhancement** - Reduces context switching and information re-discovery
39 | - 🐳 **Easy Deployment** - Docker, pip, or service installation options
40 |
41 | ### Technical Specifications
42 | - **Language**: Python 3.10+
43 | - **Protocol**: Model Context Protocol (MCP)
44 | - **Storage**: SQLite-vec, ChromaDB
45 | - **ML/AI**: Sentence Transformers, PyTorch
46 | - **API**: FastAPI, Server-Sent Events
47 | - **Platforms**: Windows, macOS, Linux (including Apple Silicon)
48 | - **Deployment**: Local, Remote Server, Docker, System Service
49 |
50 | ### Links for Submission
51 | - **Repository**: https://github.com/doobidoo/mcp-memory-service
52 | - **Documentation**: Complete README with installation guides
53 | - **Demo**: Production deployment on glama.ai
54 | - **Package**: Available via pip, Docker Hub, and Smithery
55 | - **License**: Apache 2.0
56 |
57 | ### Submission Categories
58 |
59 | #### Awesome MCP
60 | ```markdown
61 | - [MCP Memory Service](https://github.com/doobidoo/mcp-memory-service) - Universal memory service with semantic search, autonomous consolidation, and 13+ client support. Features production deployment, multi-client coordination, and dream-inspired memory organization.
62 | ```
63 |
64 | #### Awesome Claude
65 | ```markdown
66 | - [MCP Memory Service](https://github.com/doobidoo/mcp-memory-service) - Intelligent semantic memory service for Claude Desktop with multi-client support, autonomous consolidation, and optional conversational commands. Production-ready with Docker and service deployment.
67 | ```
68 |
69 | #### Awesome Developer Tools
70 | ```markdown
71 | - [MCP Memory Service](https://github.com/doobidoo/mcp-memory-service) - Universal memory service for AI-powered development workflows. Integrates with VS Code, Continue, Cursor, and 13+ AI tools to provide persistent context and intelligent search across projects.
72 | ```
73 |
74 | #### Awesome AI Tools
75 | ```markdown
76 | - [MCP Memory Service](https://github.com/doobidoo/mcp-memory-service) - Production-ready memory service for AI assistants with semantic search, vector storage, and autonomous consolidation. Works with Claude Desktop, LM Studio, and 13+ AI applications.
77 | ```
78 |
79 | ### Community Engagement Strategy
80 |
81 | 1. **Submit to Awesome Lists** (in order of priority):
82 | - Awesome MCP (if exists)
83 | - Awesome Claude
84 | - Awesome AI Tools
85 | - Awesome Developer Tools
86 | - Awesome FastAPI
87 | - Awesome Python
88 |
89 | 2. **Platform Submissions**:
90 | - Submit to Smithery (already done)
91 | - Submit to MseeP (already done)
92 | - Consider submission to Product Hunt
93 | - Submit to relevant Reddit communities (r/MachineLearning, r/Python, r/programming)
94 |
95 | 3. **Documentation & Tutorials**:
96 | - Create video walkthrough
97 | - Write blog post about MCP integration
98 | - Submit to dev.to or Medium
99 |
100 | ### SEO-Optimized Tags for GitHub Topics
101 | ```
102 | model-context-protocol, mcp-server, claude-desktop, semantic-memory,
103 | vector-database, ai-memory, sqlite-vec, fastapi, multi-client,
104 | cross-platform, docker, semantic-search, memory-consolidation,
105 | ai-productivity, vs-code, cursor, continue, developer-tools,
106 | production-ready, autonomous-memory
107 | ```
```
--------------------------------------------------------------------------------
/scripts/testing/test_cloudflare_backend.py:
--------------------------------------------------------------------------------
```python
1 | #!/usr/bin/env python3
2 | """
3 | Test script for Cloudflare backend integration.
4 | Run this after setting up your Cloudflare resources.
5 | """
6 |
7 | import os
8 | import asyncio
9 | import logging
10 | from datetime import datetime
11 | from src.mcp_memory_service.storage.cloudflare import CloudflareStorage
12 | from src.mcp_memory_service.models.memory import Memory
13 |
14 | # Configure logging
15 | logging.basicConfig(level=logging.INFO)
16 | logger = logging.getLogger(__name__)
17 |
18 | async def test_cloudflare_backend():
19 | """Test all Cloudflare backend functionality."""
20 |
21 | # Check environment variables
22 | required_vars = [
23 | 'CLOUDFLARE_API_TOKEN',
24 | 'CLOUDFLARE_ACCOUNT_ID',
25 | 'CLOUDFLARE_VECTORIZE_INDEX',
26 | 'CLOUDFLARE_D1_DATABASE_ID'
27 | ]
28 |
29 | missing_vars = [var for var in required_vars if not os.getenv(var)]
30 | if missing_vars:
31 | logger.error(f"Missing environment variables: {missing_vars}")
32 | return False
33 |
34 | try:
35 | # Initialize storage
36 | logger.info("🔧 Initializing Cloudflare storage...")
37 | storage = CloudflareStorage(
38 | api_token=os.getenv('CLOUDFLARE_API_TOKEN'),
39 | account_id=os.getenv('CLOUDFLARE_ACCOUNT_ID'),
40 | vectorize_index=os.getenv('CLOUDFLARE_VECTORIZE_INDEX'),
41 | d1_database_id=os.getenv('CLOUDFLARE_D1_DATABASE_ID'),
42 | r2_bucket=os.getenv('CLOUDFLARE_R2_BUCKET') # Optional
43 | )
44 |
45 | # Test initialization
46 | logger.info("🚀 Testing storage initialization...")
47 | await storage.initialize()
48 | logger.info("✅ Storage initialized successfully")
49 |
50 | # Test storing a memory
51 | logger.info("💾 Testing memory storage...")
52 | test_memory = Memory(
53 | content="This is a test memory for Cloudflare backend integration.",
54 | tags=["test", "cloudflare", "integration"],
55 | memory_type="test",
56 | metadata={"test_run": datetime.now().isoformat()}
57 | )
58 |
59 | success, message = await storage.store(test_memory)
60 | if success:
61 | logger.info(f"✅ Memory stored: {message}")
62 | else:
63 | logger.error(f"❌ Failed to store memory: {message}")
64 | return False
65 |
66 | # Test retrieval
67 | logger.info("🔍 Testing memory retrieval...")
68 | results = await storage.retrieve("test memory cloudflare", n_results=5)
69 | if results:
70 | logger.info(f"✅ Retrieved {len(results)} memories")
71 | for i, result in enumerate(results):
72 | logger.info(f" {i+1}. Score: {result.similarity_score:.3f} - {result.memory.content[:50]}...")
73 | else:
74 | logger.warning("⚠️ No memories retrieved")
75 |
76 | # Test tag search
77 | logger.info("🏷️ Testing tag search...")
78 | tag_results = await storage.search_by_tag(["test"])
79 | if tag_results:
80 | logger.info(f"✅ Found {len(tag_results)} memories with 'test' tag")
81 | else:
82 | logger.warning("⚠️ No memories found with 'test' tag")
83 |
84 | # Test statistics
85 | logger.info("📊 Testing statistics...")
86 | stats = await storage.get_stats()
87 | logger.info(f"✅ Stats: {stats['total_memories']} memories, {stats['status']} status")
88 |
89 | # Test cleanup (optional - uncomment to clean up test data)
90 | # logger.info("🧹 Cleaning up test data...")
91 | # deleted_count, delete_message = await storage.delete_by_tag("test")
92 | # logger.info(f"✅ Cleaned up: {delete_message}")
93 |
94 | logger.info("🎉 All tests passed! Cloudflare backend is working correctly.")
95 | return True
96 |
97 | except Exception as e:
98 | logger.error(f"❌ Test failed: {e}")
99 | return False
100 |
101 | finally:
102 | if 'storage' in locals():
103 | await storage.close()
104 | logger.info("🔒 Storage connection closed")
105 |
106 | def print_setup_instructions():
107 | """Print setup instructions if environment is not configured."""
108 | print("\n" + "="*60)
109 | print("🔧 CLOUDFLARE BACKEND SETUP REQUIRED")
110 | print("="*60)
111 | print()
112 | print("Please complete these steps:")
113 | print()
114 | print("1. Create API token with these permissions:")
115 | print(" - Vectorize:Edit")
116 | print(" - D1:Edit")
117 | print(" - Workers AI:Edit")
118 | print(" - R2:Edit (optional)")
119 | print()
120 | print("2. Create Cloudflare resources:")
121 | print(" wrangler vectorize create mcp-memory-index --dimensions=768 --metric=cosine")
122 | print(" wrangler d1 create mcp-memory-db")
123 | print(" wrangler r2 bucket create mcp-memory-content # optional")
124 | print()
125 | print("3. Set environment variables:")
126 | print(" export CLOUDFLARE_API_TOKEN='your-token'")
127 | print(" export CLOUDFLARE_ACCOUNT_ID='be0e35a26715043ef8df90253268c33f'")
128 | print(" export CLOUDFLARE_VECTORIZE_INDEX='mcp-memory-index'")
129 | print(" export CLOUDFLARE_D1_DATABASE_ID='your-d1-id'")
130 | print(" export CLOUDFLARE_R2_BUCKET='mcp-memory-content' # optional")
131 | print()
132 | print("4. Run this test again:")
133 | print(" python test_cloudflare_backend.py")
134 | print()
135 | print("See docs/cloudflare-setup.md for detailed instructions.")
136 | print("="*60)
137 |
138 | if __name__ == "__main__":
139 | # Check if basic environment is set up
140 | if not all(os.getenv(var) for var in ['CLOUDFLARE_API_TOKEN', 'CLOUDFLARE_ACCOUNT_ID']):
141 | print_setup_instructions()
142 | else:
143 | success = asyncio.run(test_cloudflare_backend())
144 | if success:
145 | print("\n🎉 Cloudflare backend is ready for production use!")
146 | else:
147 | print("\n❌ Tests failed. Check the logs above for details.")
```
--------------------------------------------------------------------------------
/scripts/testing/test_docker_functionality.py:
--------------------------------------------------------------------------------
```python
1 | #!/usr/bin/env python3
2 | """
3 | Test script to verify Docker container functionality after cleanup.
4 | Tests basic memory operations and timestamp handling.
5 | """
6 |
7 | import subprocess
8 | import time
9 | import json
10 | import sys
11 | from pathlib import Path
12 |
13 | def run_command(cmd, capture_output=True, timeout=30):
14 | """Run a command and return the result."""
15 | try:
16 | result = subprocess.run(
17 | cmd,
18 | shell=True,
19 | capture_output=capture_output,
20 | text=True,
21 | timeout=timeout
22 | )
23 | return result.returncode, result.stdout, result.stderr
24 | except subprocess.TimeoutExpired:
25 | return -1, "", "Command timed out"
26 |
27 | def test_docker_build():
28 | """Test Docker image build."""
29 | print("🔨 Testing Docker build...")
30 |
31 | # Build the Docker image
32 | cmd = "docker build -f tools/docker/Dockerfile -t mcp-memory-service:test ."
33 | returncode, stdout, stderr = run_command(cmd, timeout=300)
34 |
35 | if returncode != 0:
36 | print(f"❌ Docker build failed:")
37 | print(f"STDOUT: {stdout}")
38 | print(f"STDERR: {stderr}")
39 | return False
40 |
41 | print("✅ Docker build successful")
42 | return True
43 |
44 | def test_docker_import():
45 | """Test that the server can import without errors."""
46 | print("🧪 Testing Python imports in container...")
47 |
48 | # Test import using python directly instead of the entrypoint
49 | cmd = '''docker run --rm --entrypoint python mcp-memory-service:test -c "
50 | import sys
51 | sys.path.append('/app/src')
52 | from mcp_memory_service.server import main
53 | from mcp_memory_service.models.memory import Memory
54 | from datetime import datetime
55 | print('✅ All imports successful')
56 | print('✅ Memory model available')
57 | print('✅ Server main function available')
58 | "'''
59 |
60 | returncode, stdout, stderr = run_command(cmd, timeout=60)
61 |
62 | if returncode != 0:
63 | print(f"❌ Import test failed:")
64 | print(f"STDOUT: {stdout}")
65 | print(f"STDERR: {stderr}")
66 | return False
67 |
68 | print(stdout.strip())
69 | return True
70 |
71 | def test_memory_model():
72 | """Test Memory model and timestamp functionality."""
73 | print("📝 Testing Memory model and timestamps...")
74 |
75 | cmd = '''docker run --rm --entrypoint python mcp-memory-service:test -c "
76 | import sys
77 | sys.path.append('/app/src')
78 | from mcp_memory_service.models.memory import Memory
79 | from datetime import datetime
80 | import json
81 |
82 | # Test Memory creation
83 | memory = Memory(
84 | content='Test memory content',
85 | content_hash='testhash123',
86 | tags=['test', 'docker'],
87 | metadata={'source': 'test_script'}
88 | )
89 |
90 | print(f'✅ Memory created successfully')
91 | print(f'✅ Content: {memory.content}')
92 | print(f'✅ Tags: {memory.tags}')
93 | print(f'✅ Timestamp type: {type(memory.timestamp).__name__}')
94 | print(f'✅ Timestamp value: {memory.timestamp}')
95 |
96 | # Test that timestamp is already datetime (no conversion needed)
97 | if isinstance(memory.timestamp, datetime):
98 | print('✅ Timestamp is correctly a datetime object')
99 | else:
100 | print('❌ Timestamp is not a datetime object')
101 | sys.exit(1)
102 | "'''
103 |
104 | returncode, stdout, stderr = run_command(cmd, timeout=60)
105 |
106 | if returncode != 0:
107 | print(f"❌ Memory model test failed:")
108 | print(f"STDOUT: {stdout}")
109 | print(f"STDERR: {stderr}")
110 | return False
111 |
112 | print(stdout.strip())
113 | return True
114 |
115 | def test_server_startup():
116 | """Test that server can start without crashing immediately."""
117 | print("🚀 Testing server startup...")
118 |
119 | # Start server in background and check if it runs for a few seconds
120 | # Test server startup by running it briefly
121 | cmd = '''timeout 5s docker run --rm mcp-memory-service:test 2>/dev/null || echo "✅ Server startup test completed (timeout expected)"'''
122 |
123 | returncode, stdout, stderr = run_command(cmd, timeout=15)
124 |
125 | # We expect a timeout or success message
126 | if "Server started successfully" in stdout or "Server startup test completed" in stdout:
127 | print("✅ Server can start without immediate crashes")
128 | return True
129 | else:
130 | print(f"❌ Server startup test unclear:")
131 | print(f"STDOUT: {stdout}")
132 | print(f"STDERR: {stderr}")
133 | return False
134 |
135 | def cleanup_docker():
136 | """Clean up test Docker images."""
137 | print("🧹 Cleaning up test images...")
138 | run_command("docker rmi mcp-memory-service:test", capture_output=False)
139 |
140 | def main():
141 | """Run all tests."""
142 | print("🔍 DOCKER FUNCTIONALITY TEST SUITE")
143 | print("=" * 50)
144 |
145 | tests = [
146 | ("Docker Build", test_docker_build),
147 | ("Python Imports", test_docker_import),
148 | ("Memory Model", test_memory_model),
149 | ("Server Startup", test_server_startup),
150 | ]
151 |
152 | passed = 0
153 | failed = 0
154 |
155 | for test_name, test_func in tests:
156 | print(f"\n📋 Running: {test_name}")
157 | print("-" * 30)
158 |
159 | try:
160 | if test_func():
161 | passed += 1
162 | print(f"✅ {test_name} PASSED")
163 | else:
164 | failed += 1
165 | print(f"❌ {test_name} FAILED")
166 | except Exception as e:
167 | failed += 1
168 | print(f"❌ {test_name} ERROR: {e}")
169 |
170 | print("\n" + "=" * 50)
171 | print(f"📊 TEST SUMMARY:")
172 | print(f"✅ Passed: {passed}")
173 | print(f"❌ Failed: {failed}")
174 | print(f"📈 Success Rate: {passed/(passed+failed)*100:.1f}%")
175 |
176 | if failed == 0:
177 | print("\n🎉 ALL TESTS PASSED! Docker functionality is working correctly.")
178 | cleanup_docker()
179 | return 0
180 | else:
181 | print(f"\n⚠️ {failed} test(s) failed. Please review the issues above.")
182 | cleanup_docker()
183 | return 1
184 |
185 | if __name__ == "__main__":
186 | sys.exit(main())
```
--------------------------------------------------------------------------------
/archive/docs-root-cleanup-2025-08-23/CLOUDFLARE_IMPLEMENTATION.md:
--------------------------------------------------------------------------------
```markdown
1 | # Cloudflare Native Integration Implementation Log
2 |
3 | ## Project Overview
4 | Adding Cloudflare as a native backend option to MCP Memory Service while maintaining full compatibility with existing deployments.
5 |
6 | ## Implementation Timeline
7 | - **Start Date:** 2025-08-16
8 | - **Target Completion:** 4 weeks
9 | - **Current Phase:** Phase 1 - Foundation Setup
10 |
11 | ## Phase 1: Core Backend Implementation (Weeks 1-2)
12 |
13 | ### Week 1 Progress
14 |
15 | #### Day 1 (2025-08-16)
16 | - ✅ Created implementation tracking infrastructure
17 | - ✅ Analyzed current MCP Memory Service architecture
18 | - ✅ Researched Cloudflare Vectorize, D1, and R2 APIs
19 | - ✅ Designed overall architecture approach
20 | - ✅ Set up feature branch and task files
21 | - ✅ **COMPLETED:** Core CloudflareStorage backend implementation
22 |
23 | #### Foundation Setup Tasks ✅
24 | - ✅ Create feature branch: `feature/cloudflare-native-backend`
25 | - ✅ Set up task tracking files in `tasks/` directory
26 | - ✅ Store initial plan in memory service
27 | - ✅ Document Cloudflare API requirements and limits
28 |
29 | #### CloudflareStorage Backend Tasks ✅
30 | - ✅ Implement base CloudflareStorage class extending MemoryStorage
31 | - ✅ Add Vectorize vector operations (store, query, delete)
32 | - ✅ Implement D1 metadata operations (tags, timestamps, content hashes)
33 | - ✅ Add R2 content storage for large objects (>1MB)
34 | - ✅ Implement comprehensive error handling and retry logic
35 | - ✅ Add logging and performance metrics
36 | - ✅ Update config.py for Cloudflare backend support
37 | - ✅ Update server.py for Cloudflare backend initialization
38 | - ✅ Create comprehensive unit tests
39 |
40 | #### Configuration Updates ✅
41 | - ✅ Add `cloudflare` to SUPPORTED_BACKENDS
42 | - ✅ Implement Cloudflare-specific environment variables
43 | - ✅ Add Workers AI embedding model configuration
44 | - ✅ Update validation logic for Cloudflare backend
45 | - ✅ Add server initialization code
46 |
47 | #### Implementation Highlights
48 | - **Full Interface Compliance**: All MemoryStorage methods implemented
49 | - **Robust Error Handling**: Exponential backoff, retry logic, circuit breaker patterns
50 | - **Performance Optimizations**: Embedding caching, connection pooling, async operations
51 | - **Smart Content Strategy**: Small content in D1, large content in R2
52 | - **Comprehensive Testing**: 15 unit tests covering all major functionality
53 |
54 | #### Files Created/Modified
55 | - ✅ `src/mcp_memory_service/storage/cloudflare.py` - Core implementation (740 lines)
56 | - ✅ `src/mcp_memory_service/config.py` - Configuration updates
57 | - ✅ `src/mcp_memory_service/server.py` - Backend initialization
58 | - ✅ `tests/unit/test_cloudflare_storage.py` - Comprehensive test suite
59 | - ✅ `requirements-cloudflare.txt` - Additional dependencies
60 | - ✅ `tasks/cloudflare-api-requirements.md` - API documentation
61 |
62 | ### Architecture Decisions Made
63 |
64 | #### Storage Strategy
65 | - **Vectors:** Cloudflare Vectorize for semantic embeddings
66 | - **Metadata:** D1 SQLite for tags, timestamps, relationships, content hashes
67 | - **Content:** Inline for small content (<1MB), R2 for larger content
68 | - **Embeddings:** Workers AI `@cf/baai/bge-base-en-v1.5` with local fallback
69 |
70 | #### Configuration Approach
71 | - Environment variable: `MCP_MEMORY_BACKEND=cloudflare`
72 | - Required: `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID`
73 | - Services: `CLOUDFLARE_VECTORIZE_INDEX`, `CLOUDFLARE_D1_DATABASE_ID`
74 | - Optional: `CLOUDFLARE_R2_BUCKET` for large content storage
75 |
76 | ## Phase 2: Workers Deployment Support (Week 3)
77 | - [ ] Worker entry point implementation
78 | - [ ] Deployment configuration (wrangler.toml)
79 | - [ ] Build system updates
80 | - [ ] CI/CD pipeline integration
81 |
82 | ## Phase 3: Migration & Testing (Week 4)
83 | - [ ] Data migration tools
84 | - [ ] Comprehensive testing suite
85 | - [ ] Performance benchmarking
86 | - [ ] Documentation completion
87 |
88 | ## Phase 1 Status: ✅ COMPLETE
89 |
90 | ### Final Deliverables ✅
91 | - ✅ **Core Implementation**: CloudflareStorage backend (740 lines) with full interface compliance
92 | - ✅ **Configuration**: Complete environment variable setup and validation
93 | - ✅ **Server Integration**: Seamless backend initialization in server.py
94 | - ✅ **Testing**: Comprehensive test suite with 15 unit tests covering all functionality
95 | - ✅ **Documentation**: Complete setup guide, API documentation, and troubleshooting
96 | - ✅ **Migration Tools**: Universal migration script supporting SQLite-vec and ChromaDB
97 | - ✅ **README Updates**: Integration with main project documentation
98 |
99 | ### Performance Achievements
100 | - **Memory Efficiency**: Minimal local footprint with cloud-based storage
101 | - **Global Performance**: <100ms latency from most global locations
102 | - **Smart Caching**: 1000-entry embedding cache with LRU eviction
103 | - **Error Resilience**: Exponential backoff, retry logic, circuit breaker patterns
104 | - **Async Operations**: Full async/await implementation for optimal performance
105 |
106 | ### Architecture Success
107 | - **Vectorize Integration**: Semantic search with Workers AI embeddings
108 | - **D1 Database**: Relational metadata storage with ACID compliance
109 | - **R2 Storage**: Smart content strategy for large objects (>1MB)
110 | - **Connection Pooling**: HTTP client optimization for API efficiency
111 | - **Batch Processing**: Bulk operations for improved throughput
112 |
113 | ## Current Blockers
114 | - None - Phase 1 complete and ready for production use
115 |
116 | ## Next Steps - Phase 2: Workers Deployment
117 | 1. **Worker Entry Point**: Create cloudflare/worker.js for Workers runtime
118 | 2. **Deployment Configuration**: Complete wrangler.toml setup
119 | 3. **Build System**: Workers-compatible bundling and optimization
120 | 4. **CI/CD Pipeline**: Automated deployment workflows
121 | 5. **Testing**: Integration tests with real Cloudflare Workers environment
122 |
123 | ## Technical Notes
124 | - Maintaining full backward compatibility with existing storage backends
125 | - Zero breaking changes to current deployments
126 | - Gradual migration capability for existing users
```
--------------------------------------------------------------------------------
/claude-hooks/test-adaptive-weights.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env node
2 | /**
3 | * Test Adaptive Memory Weight Adjustment
4 | * Tests the new dynamic weight adjustment based on memory age distribution
5 | */
6 |
7 | const { analyzeMemoryAgeDistribution, calculateAdaptiveGitWeight } = require('./utilities/memory-scorer');
8 |
9 | console.log('=== ADAPTIVE WEIGHT ADJUSTMENT TEST ===\n');
10 |
11 | // Scenario 1: Stale memory set (your actual problem)
12 | console.log('📊 Scenario 1: Stale Memory Set (Median > 30 days)');
13 | console.log('─'.repeat(80));
14 |
15 | const staleMemories = [
16 | { created_at_iso: new Date(Date.now() - 60 * 24 * 60 * 60 * 1000).toISOString(), content: 'Old README work' },
17 | { created_at_iso: new Date(Date.now() - 54 * 24 * 60 * 60 * 1000).toISOString(), content: 'Old wiki docs' },
18 | { created_at_iso: new Date(Date.now() - 24 * 24 * 60 * 60 * 1000).toISOString(), content: 'Contributing guide' },
19 | { created_at_iso: new Date(Date.now() - 57 * 24 * 60 * 60 * 1000).toISOString(), content: 'GitHub issue work' },
20 | { created_at_iso: new Date(Date.now() - 52 * 24 * 60 * 60 * 1000).toISOString(), content: 'Old PR merge' },
21 | ];
22 |
23 | const ageAnalysis1 = analyzeMemoryAgeDistribution(staleMemories, { verbose: true });
24 |
25 | console.log('\n🔍 Analysis Results:');
26 | console.log(` Median Age: ${Math.round(ageAnalysis1.medianAge)} days`);
27 | console.log(` Average Age: ${Math.round(ageAnalysis1.avgAge)} days`);
28 | console.log(` Recent Count: ${ageAnalysis1.recentCount}/${ageAnalysis1.totalCount} (${Math.round(ageAnalysis1.recentCount/ageAnalysis1.totalCount*100)}%)`);
29 | console.log(` Is Stale: ${ageAnalysis1.isStale ? '✅ YES' : '❌ NO'}`);
30 |
31 | if (ageAnalysis1.recommendedAdjustments.reason) {
32 | console.log('\n💡 Recommended Adjustments:');
33 | console.log(` Reason: ${ageAnalysis1.recommendedAdjustments.reason}`);
34 | console.log(` Time Decay Weight: 0.25 → ${ageAnalysis1.recommendedAdjustments.timeDecay}`);
35 | console.log(` Tag Relevance Weight: 0.35 → ${ageAnalysis1.recommendedAdjustments.tagRelevance}`);
36 | }
37 |
38 | // Test adaptive git weight with recent commits but stale memories
39 | const gitContext1 = {
40 | recentCommits: [
41 | { date: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(), message: 'chore: bump version to v8.5.0' },
42 | { date: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(), message: 'fix: sync script import path' },
43 | ]
44 | };
45 |
46 | const gitWeightResult1 = calculateAdaptiveGitWeight(gitContext1, ageAnalysis1, 1.8, { verbose: true });
47 |
48 | console.log('\n⚙️ Adaptive Git Weight:');
49 | console.log(` Configured Weight: 1.8x`);
50 | console.log(` Adaptive Weight: ${gitWeightResult1.weight.toFixed(1)}x`);
51 | console.log(` Adjusted: ${gitWeightResult1.adjusted ? '✅ YES' : '❌ NO'}`);
52 | console.log(` Reason: ${gitWeightResult1.reason}`);
53 |
54 | // Scenario 2: Recent memory set
55 | console.log('\n\n📊 Scenario 2: Recent Memory Set (All memories < 14 days)');
56 | console.log('─'.repeat(80));
57 |
58 | const recentMemories = [
59 | { created_at_iso: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(), content: 'Recent HTTP fix' },
60 | { created_at_iso: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(), content: 'Dark mode feature' },
61 | { created_at_iso: new Date(Date.now() - 4 * 24 * 60 * 60 * 1000).toISOString(), content: 'ChromaDB removal' },
62 | { created_at_iso: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), content: 'Memory optimization' },
63 | { created_at_iso: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), content: 'Token savings' },
64 | ];
65 |
66 | const ageAnalysis2 = analyzeMemoryAgeDistribution(recentMemories, { verbose: true });
67 |
68 | console.log('\n🔍 Analysis Results:');
69 | console.log(` Median Age: ${Math.round(ageAnalysis2.medianAge)} days`);
70 | console.log(` Average Age: ${Math.round(ageAnalysis2.avgAge)} days`);
71 | console.log(` Recent Count: ${ageAnalysis2.recentCount}/${ageAnalysis2.totalCount} (${Math.round(ageAnalysis2.recentCount/ageAnalysis2.totalCount*100)}%)`);
72 | console.log(` Is Stale: ${ageAnalysis2.isStale ? '✅ YES' : '❌ NO'}`);
73 |
74 | const gitContext2 = {
75 | recentCommits: [
76 | { date: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(), message: 'chore: bump version' },
77 | ]
78 | };
79 |
80 | const gitWeightResult2 = calculateAdaptiveGitWeight(gitContext2, ageAnalysis2, 1.8, { verbose: true });
81 |
82 | console.log('\n⚙️ Adaptive Git Weight:');
83 | console.log(` Configured Weight: 1.8x`);
84 | console.log(` Adaptive Weight: ${gitWeightResult2.weight.toFixed(1)}x`);
85 | console.log(` Adjusted: ${gitWeightResult2.adjusted ? '✅ YES' : '❌ NO'}`);
86 | console.log(` Reason: ${gitWeightResult2.reason}`);
87 |
88 | // Summary
89 | console.log('\n\n✅ Test Summary:');
90 | console.log('─'.repeat(80));
91 | console.log('Expected Behavior:');
92 | console.log(' 1. Stale memories (median > 30d) should trigger auto-calibration');
93 | console.log(' → Increase time decay weight, reduce tag relevance weight');
94 | console.log(' 2. Recent commits + stale memories should reduce git weight');
95 | console.log(' → Prevents old git memories from dominating');
96 | console.log(' 3. Recent commits + recent memories should keep git weight');
97 | console.log(' → Git context is relevant and aligned');
98 | console.log('\nActual Results:');
99 | console.log(` ✅ Scenario 1: ${ageAnalysis1.isStale ? 'Auto-calibrated weights' : 'ERROR: Should calibrate'}`);
100 | console.log(` ✅ Scenario 1 Git: ${gitWeightResult1.adjusted ? 'Reduced git weight from 1.8 to ' + gitWeightResult1.weight.toFixed(1) : 'ERROR: Should adjust'}`);
101 | console.log(` ✅ Scenario 2: ${!ageAnalysis2.isStale ? 'No calibration needed' : 'ERROR: Should not calibrate'}`);
102 | console.log(` ✅ Scenario 2 Git: ${!gitWeightResult2.adjusted ? 'Kept git weight at 1.8' : 'ERROR: Should not adjust'}`);
103 | console.log('\n🎉 Dynamic weight adjustment is working as expected!');
104 |
```
--------------------------------------------------------------------------------
/docs/troubleshooting/general.md:
--------------------------------------------------------------------------------
```markdown
1 | # MCP Memory Service Troubleshooting Guide
2 |
3 | This guide covers common issues and their solutions when working with the MCP Memory Service.
4 |
5 | ## First-Time Setup Warnings (Normal Behavior)
6 |
7 | ### Expected Warnings on First Run
8 |
9 | The following warnings are **completely normal** during first-time setup:
10 |
11 | #### "No snapshots directory" Warning
12 | ```
13 | WARNING:mcp_memory_service.storage.sqlite_vec:Failed to load from cache: No snapshots directory
14 | ```
15 | - **Status:** ✅ Normal - Service is checking for cached models
16 | - **Action:** None required - Model will download automatically
17 | - **Duration:** Appears only on first run
18 |
19 | #### "TRANSFORMERS_CACHE deprecated" Warning
20 | ```
21 | WARNING: Using TRANSFORMERS_CACHE is deprecated
22 | ```
23 | - **Status:** ✅ Normal - Informational warning from Hugging Face
24 | - **Action:** None required - Doesn't affect functionality
25 | - **Duration:** May appear on each run (can be ignored)
26 |
27 | #### Model Download Messages
28 | ```
29 | Downloading model 'all-MiniLM-L6-v2'...
30 | ```
31 | - **Status:** ✅ Normal - One-time model download (~25MB)
32 | - **Action:** Wait 1-2 minutes for download to complete
33 | - **Duration:** First run only
34 |
35 | For detailed information, see the [First-Time Setup Guide](../first-time-setup.md).
36 |
37 | ## Python 3.13 sqlite-vec Issues
38 |
39 | ### Problem: sqlite-vec Installation Fails on Python 3.13
40 | **Error:** `Failed to install SQLite-vec: Command ... returned non-zero exit status 1`
41 |
42 | **Cause:** sqlite-vec doesn't have pre-built wheels for Python 3.13 yet, and no source distribution is available on PyPI.
43 |
44 | **Solutions:**
45 |
46 | 1. **Automatic Fallback (v6.13.2+)**
47 | - The installer now automatically tries multiple installation methods
48 | - It will attempt: uv pip, standard pip, source build, and GitHub installation
49 | - If all fail, you'll be prompted to switch to ChromaDB
50 |
51 | 2. **Use Python 3.12 (Recommended)**
52 | ```bash
53 | # macOS
54 | brew install [email protected]
55 | python3.12 -m venv .venv
56 | source .venv/bin/activate
57 | python install.py
58 | ```
59 |
60 | 3. **Switch to ChromaDB Backend**
61 | ```bash
62 | python install.py --storage-backend chromadb
63 | ```
64 |
65 | 4. **Manual Installation Attempts**
66 | ```bash
67 | # Force source build
68 | pip install --no-binary :all: sqlite-vec
69 |
70 | # Install from GitHub
71 | pip install git+https://github.com/asg017/sqlite-vec.git#subdirectory=python
72 |
73 | # Alternative: pysqlite3-binary
74 | pip install pysqlite3-binary
75 | ```
76 |
77 | 5. **Report Issue**
78 | - Check for updates: https://github.com/asg017/sqlite-vec/issues
79 | - sqlite-vec may add Python 3.13 support in future releases
80 |
81 | ## macOS SQLite Extension Issues
82 |
83 | ### Problem: `AttributeError: 'sqlite3.Connection' object has no attribute 'enable_load_extension'`
84 | **Error:** Python 3.12 (and other versions) on macOS failing with sqlite-vec backend
85 |
86 | **Cause:** Python on macOS is not compiled with `--enable-loadable-sqlite-extensions` by default. The system SQLite library doesn't support extensions.
87 |
88 | **Platform:** Affects macOS (all versions), particularly with system Python
89 |
90 | **Solutions:**
91 |
92 | 1. **Use Homebrew Python (Recommended)**
93 | ```bash
94 | # Install Homebrew Python (includes extension support)
95 | brew install python
96 | hash -r # Refresh shell command cache
97 | python3 --version # Verify Homebrew version
98 |
99 | # Reinstall MCP Memory Service
100 | python3 install.py
101 | ```
102 |
103 | 2. **Use pyenv with Extension Support**
104 | ```bash
105 | # Install pyenv
106 | brew install pyenv
107 |
108 | # Install Python with extension support
109 | PYTHON_CONFIGURE_OPTS="--enable-loadable-sqlite-extensions" \
110 | LDFLAGS="-L$(brew --prefix sqlite)/lib" \
111 | CPPFLAGS="-I$(brew --prefix sqlite)/include" \
112 | pyenv install 3.12.0
113 |
114 | pyenv local 3.12.0
115 | python install.py
116 | ```
117 |
118 | 3. **Switch to ChromaDB Backend**
119 | ```bash
120 | # ChromaDB doesn't require SQLite extensions
121 | export MCP_MEMORY_STORAGE_BACKEND=chromadb
122 | python install.py --storage-backend chromadb
123 | ```
124 |
125 | 4. **Verify Extension Support**
126 | ```bash
127 | python3 -c "
128 | import sqlite3
129 | conn = sqlite3.connect(':memory:')
130 | if hasattr(conn, 'enable_load_extension'):
131 | try:
132 | conn.enable_load_extension(True)
133 | print('✅ Extension support working')
134 | except Exception as e:
135 | print(f'❌ Extension support disabled: {e}')
136 | else:
137 | print('❌ No enable_load_extension attribute')
138 | "
139 | ```
140 |
141 | **Why this happens:**
142 | - Security: Extension loading disabled by default
143 | - Compilation: System Python not built with extension support
144 | - Library: macOS bundled SQLite lacks extension loading capability
145 |
146 | **Detection:** The installer now automatically detects this issue and provides guidance.
147 |
148 | ## Common Installation Issues
149 |
150 | [Content from installation.md's troubleshooting section - already well documented]
151 |
152 | ## MCP Protocol Issues
153 |
154 | ### Method Not Found Errors
155 |
156 | If you're seeing "Method not found" errors or JSON error popups in Claude Desktop:
157 |
158 | #### Symptoms
159 | - "Method not found" errors in logs
160 | - JSON error popups in Claude Desktop
161 | - Connection issues between Claude Desktop and the memory service
162 |
163 | #### Solution
164 | 1. Ensure you have the latest version of the MCP Memory Service
165 | 2. Verify your server implements all required MCP protocol methods:
166 | - resources/list
167 | - resources/read
168 | - resource_templates/list
169 | 3. Update your Claude Desktop configuration using the provided template
170 |
171 | [Additional content from MCP_PROTOCOL_FIX.md]
172 |
173 | ## Windows-Specific Issues
174 |
175 | [Content from WINDOWS_JSON_FIX.md and windows-specific sections]
176 |
177 | ## Performance Optimization
178 |
179 | ### Memory Issues
180 | [Content from installation.md's performance section]
181 |
182 | ### Acceleration Issues
183 | [Content from installation.md's acceleration section]
184 |
185 | ## Debugging Tools
186 |
187 | [Content from installation.md's debugging section]
188 |
189 | ## Getting Help
190 |
191 | [Content from installation.md's help section]
192 |
```
--------------------------------------------------------------------------------
/docs/remote-configuration-wiki-section.md:
--------------------------------------------------------------------------------
```markdown
1 | # Remote Server Configuration (Wiki Section)
2 |
3 | This content can be added to the **03 Integration Guide** wiki page under the "1. Claude Desktop Integration" section.
4 |
5 | ---
6 |
7 | ## Remote Server Configuration
8 |
9 | For users who want to connect Claude Desktop or Cursor to a remote MCP Memory Service instance (running on a VPS, server, or different machine), use the HTTP-to-MCP bridge included in the repository.
10 |
11 | ### Quick Setup
12 |
13 | The MCP Memory Service includes a Node.js bridge that translates HTTP API calls to MCP protocol messages, allowing remote connections.
14 |
15 | **Configuration for Claude Desktop:**
16 |
17 | ```json
18 | {
19 | "mcpServers": {
20 | "memory": {
21 | "command": "node",
22 | "args": ["/path/to/mcp-memory-service/examples/http-mcp-bridge.js"],
23 | "env": {
24 | "MCP_HTTP_ENDPOINT": "https://your-server:8000/api",
25 | "MCP_MEMORY_API_KEY": "your-secure-api-key"
26 | }
27 | }
28 | }
29 | }
30 | ```
31 |
32 | ### Configuration Options
33 |
34 | #### Manual Endpoint Configuration (Recommended for Remote Servers)
35 | ```json
36 | {
37 | "mcpServers": {
38 | "memory": {
39 | "command": "node",
40 | "args": ["/path/to/mcp-memory-service/examples/http-mcp-bridge.js"],
41 | "env": {
42 | "MCP_HTTP_ENDPOINT": "https://your-server:8000/api",
43 | "MCP_MEMORY_API_KEY": "your-secure-api-key",
44 | "MCP_MEMORY_AUTO_DISCOVER": "false",
45 | "MCP_MEMORY_PREFER_HTTPS": "true"
46 | }
47 | }
48 | }
49 | }
50 | ```
51 |
52 | #### Auto-Discovery (For Local Network)
53 | ```json
54 | {
55 | "mcpServers": {
56 | "memory": {
57 | "command": "node",
58 | "args": ["/path/to/mcp-memory-service/examples/http-mcp-bridge.js"],
59 | "env": {
60 | "MCP_MEMORY_AUTO_DISCOVER": "true",
61 | "MCP_MEMORY_PREFER_HTTPS": "true",
62 | "MCP_MEMORY_API_KEY": "your-api-key"
63 | }
64 | }
65 | }
66 | }
67 | ```
68 |
69 | ### Step-by-Step Setup
70 |
71 | 1. **Download the HTTP Bridge**
72 | - Copy [`examples/http-mcp-bridge.js`](https://github.com/doobidoo/mcp-memory-service/blob/main/examples/http-mcp-bridge.js) to your local machine
73 |
74 | 2. **Update Configuration**
75 | - Open your Claude Desktop configuration file:
76 | - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
77 | - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
78 | - Add the remote server configuration (see examples above)
79 | - Replace `/path/to/mcp-memory-service/examples/http-mcp-bridge.js` with the actual path
80 | - Replace `https://your-server:8000/api` with your server's endpoint
81 | - Replace `your-secure-api-key` with your actual API key
82 |
83 | 3. **Verify Connection**
84 | - Restart Claude Desktop
85 | - Test the connection with a simple memory operation
86 | - Check the bridge logs for any connection issues
87 |
88 | ### Bridge Features
89 |
90 | The HTTP-to-MCP bridge supports:
91 |
92 | - ✅ **Manual endpoint configuration** - Direct connection to your remote server
93 | - ✅ **API key authentication** - Secure access to your memory service
94 | - ✅ **HTTPS with self-signed certificates** - Works with development SSL certificates
95 | - ✅ **Automatic service discovery via mDNS** - Auto-detects local network services
96 | - ✅ **Retry logic and error handling** - Robust connection management
97 | - ✅ **Comprehensive logging** - Detailed logs for troubleshooting
98 |
99 | ### Environment Variables Reference
100 |
101 | | Variable | Description | Default | Example |
102 | |----------|-------------|---------|---------|
103 | | `MCP_HTTP_ENDPOINT` | Remote server API endpoint (Streamable HTTP lives at `/mcp`; REST at `/api`) | `http://localhost:8000/api` | `https://myserver.com:8000/api` |
104 | | `MCP_MEMORY_API_KEY` | Authentication token for client bridge (server uses `MCP_API_KEY`) | None | `abc123xyz789` |
105 | | `MCP_MEMORY_AUTO_DISCOVER` | Enable mDNS service discovery | `false` | `true` |
106 | | `MCP_MEMORY_PREFER_HTTPS` | Prefer HTTPS over HTTP when discovering | `true` | `false` |
107 |
108 | ### Troubleshooting Remote Connections
109 |
110 | #### Connection Refused
111 | - **Issue**: Bridge can't connect to the remote server
112 | - **Solutions**:
113 | - Verify the server is running and accessible
114 | - Check firewall rules allow connections on port 8000
115 | - Confirm the endpoint URL is correct
116 | - Test with curl: `curl https://your-server:8000/api/health`
117 |
118 | #### SSL Certificate Issues
119 | - **Issue**: HTTPS connections fail with SSL errors
120 | - **Solutions**:
121 | - The bridge automatically accepts self-signed certificates
122 | - Ensure your server is running with HTTPS enabled
123 | - Check server logs for SSL configuration issues
124 |
125 | #### API Key Authentication Failed
126 | - **Issue**: Server returns 401 Unauthorized
127 | - **Solutions**:
128 | - Verify the API key is correctly set on the server
129 | - Check the key is properly configured in the bridge environment
130 | - Ensure no extra whitespace in the API key value
131 |
132 | #### Service Discovery Not Working
133 | - **Issue**: Auto-discovery can't find the service
134 | - **Solutions**:
135 | - Use manual endpoint configuration instead
136 | - Ensure both devices are on the same network
137 | - Check if mDNS/Bonjour is enabled on your network
138 |
139 | #### Bridge Logs Not Appearing
140 | - **Issue**: Can't see bridge connection logs
141 | - **Solutions**:
142 | - Bridge logs appear in Claude Desktop's console/stderr
143 | - On macOS, use Console.app to view logs
144 | - On Windows, check Event Viewer or run Claude Desktop from command line
145 |
146 | ### Complete Example Files
147 |
148 | For complete working examples, see:
149 | - [`examples/claude-desktop-http-config.json`](https://github.com/doobidoo/mcp-memory-service/blob/main/examples/claude-desktop-http-config.json) - Complete configuration template
150 | - [`examples/http-mcp-bridge.js`](https://github.com/doobidoo/mcp-memory-service/blob/main/examples/http-mcp-bridge.js) - Full bridge implementation with documentation
151 |
152 | ---
153 |
154 | *This section should be added to the existing "1. Claude Desktop Integration" section of the 03 Integration Guide wiki page, positioned after the basic local configuration examples.*
155 |
```
--------------------------------------------------------------------------------
/scripts/maintenance/cleanup_corrupted_encoding.py:
--------------------------------------------------------------------------------
```python
1 | #!/usr/bin/env python3
2 | """
3 | Clean up memories with corrupted emoji encoding from the database.
4 | This script identifies and removes entries where emojis were incorrectly encoded.
5 | """
6 |
7 | import sqlite3
8 | import json
9 | import re
10 | import sys
11 | from pathlib import Path
12 |
13 | def detect_corrupted_encoding(text):
14 | """
15 | Detect if text contains corrupted emoji encoding patterns.
16 | Common patterns include:
17 | - üöÄ (corrupted 🚀)
18 | - ‚ö° (corrupted ⚡)
19 | - üéØ (corrupted 🎯)
20 | - ‚úÖ (corrupted ✅)
21 | - ‚û°Ô∏è (corrupted ➡️)
22 | - and other mangled Unicode sequences
23 | """
24 | # Pattern for corrupted emojis - looking for specific corrupted sequences
25 | corrupted_patterns = [
26 | r'[üöÄ]{2,}', # Multiple Germanic umlauts together
27 | r'‚[öûú][°ÖØ]', # Specific corrupted emoji patterns
28 | r'️', # Part of corrupted arrow emoji
29 | r'\uf8ff', # Apple logo character that shouldn't be there
30 | r'ü[öéì][ÄØÖ]', # Common corrupted emoji starts
31 | r'‚Ä[窆]', # Another corruption pattern
32 | ]
33 |
34 | for pattern in corrupted_patterns:
35 | if re.search(pattern, text):
36 | return True
37 |
38 | # Also check for suspicious character combinations
39 | # Real emojis are typically in ranges U+1F300-U+1F9FF, U+2600-U+27BF
40 | # Corrupted text often has unusual combinations of Latin extended characters
41 | suspicious_chars = ['ü', 'ö', 'Ä', '‚', 'Ô', '∏', 'è', '°', 'Ö', 'Ø', 'û', 'ú', 'ì', '†', 'ª', 'ç']
42 | char_count = sum(text.count(char) for char in suspicious_chars)
43 |
44 | # If we have multiple suspicious characters in a short span, likely corrupted
45 | if char_count > 3 and len(text) < 200:
46 | return True
47 |
48 | return False
49 |
50 | def cleanup_corrupted_memories(db_path, dry_run=True):
51 | """
52 | Clean up memories with corrupted encoding.
53 |
54 | Args:
55 | db_path: Path to the SQLite database
56 | dry_run: If True, only show what would be deleted without actually deleting
57 | """
58 | conn = sqlite3.connect(db_path)
59 | cursor = conn.cursor()
60 |
61 | print(f"{'DRY RUN - ' if dry_run else ''}Scanning for memories with corrupted encoding...")
62 |
63 | # Get all memories with potential corruption
64 | cursor.execute("""
65 | SELECT content_hash, content, tags, created_at
66 | FROM memories
67 | WHERE tags LIKE '%readme%' OR tags LIKE '%documentation%'
68 | ORDER BY created_at DESC
69 | """)
70 |
71 | all_memories = cursor.fetchall()
72 | corrupted_memories = []
73 |
74 | for content_hash, content, tags_json, created_at in all_memories:
75 | if detect_corrupted_encoding(content):
76 | try:
77 | tags = json.loads(tags_json) if tags_json else []
78 | except:
79 | tags = []
80 |
81 | # Skip if already marked as UTF8-fixed (these are the corrected versions)
82 | if 'utf8-fixed' in tags:
83 | continue
84 |
85 | corrupted_memories.append({
86 | 'hash': content_hash,
87 | 'content_preview': content[:200],
88 | 'tags': tags,
89 | 'created_at': created_at
90 | })
91 |
92 | print(f"\nFound {len(corrupted_memories)} memories with corrupted encoding")
93 |
94 | if corrupted_memories:
95 | print("\nCorrupted memories to be deleted:")
96 | print("-" * 80)
97 |
98 | for i, mem in enumerate(corrupted_memories[:10], 1): # Show first 10
99 | print(f"\n{i}. Hash: {mem['hash'][:20]}...")
100 | print(f" Created: {mem['created_at']}")
101 | print(f" Tags: {', '.join(mem['tags'][:5])}")
102 | print(f" Content preview: {mem['content_preview'][:100]}...")
103 |
104 | if len(corrupted_memories) > 10:
105 | print(f"\n... and {len(corrupted_memories) - 10} more")
106 |
107 | if not dry_run:
108 | print("\n" + "="*80)
109 | print("DELETING CORRUPTED MEMORIES...")
110 |
111 | # Delete from memories table
112 | for mem in corrupted_memories:
113 | cursor.execute("DELETE FROM memories WHERE content_hash = ?", (mem['hash'],))
114 |
115 | # Also delete from embeddings table if it exists
116 | try:
117 | cursor.execute("DELETE FROM memory_embeddings WHERE rowid = ?", (mem['hash'],))
118 | except:
119 | pass # Embeddings table might use different structure
120 |
121 | conn.commit()
122 | print(f"✅ Deleted {len(corrupted_memories)} corrupted memories")
123 |
124 | # Verify deletion
125 | cursor.execute("SELECT COUNT(*) FROM memories")
126 | remaining = cursor.fetchone()[0]
127 | print(f"📊 Remaining memories in database: {remaining}")
128 | else:
129 | print("\n" + "="*80)
130 | print("DRY RUN COMPLETE - No changes made")
131 | print(f"To actually delete these {len(corrupted_memories)} memories, run with --execute flag")
132 | else:
133 | print("✅ No corrupted memories found!")
134 |
135 | conn.close()
136 |
137 | def main():
138 | """Main entry point."""
139 | import argparse
140 |
141 | parser = argparse.ArgumentParser(description='Clean up memories with corrupted emoji encoding')
142 | parser.add_argument('--db-path', type=str,
143 | default='/home/hkr/.local/share/mcp-memory/sqlite_vec.db',
144 | help='Path to SQLite database')
145 | parser.add_argument('--execute', action='store_true',
146 | help='Actually delete the corrupted memories (default is dry run)')
147 |
148 | args = parser.parse_args()
149 |
150 | if not Path(args.db_path).exists():
151 | print(f"❌ Database not found: {args.db_path}")
152 | sys.exit(1)
153 |
154 | cleanup_corrupted_memories(args.db_path, dry_run=not args.execute)
155 |
156 | if __name__ == "__main__":
157 | main()
```
--------------------------------------------------------------------------------
/scripts/benchmarks/benchmark_server_caching.py:
--------------------------------------------------------------------------------
```python
1 | #!/usr/bin/env python3
2 | """
3 | Benchmark production MCP server (server.py) caching performance.
4 |
5 | Tests global caching implementation to measure performance improvement
6 | from baseline ~1,810ms to target <400ms on cache hits.
7 |
8 | Usage:
9 | python scripts/benchmarks/benchmark_server_caching.py
10 | """
11 |
12 | import asyncio
13 | import time
14 | import sys
15 | from pathlib import Path
16 |
17 | # Add src to path
18 | sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
19 |
20 | from mcp_memory_service.server import MemoryServer
21 | from mcp_memory_service import config
22 |
23 |
24 | async def benchmark_server_caching():
25 | """Benchmark production MCP server caching performance."""
26 |
27 | print("=" * 80)
28 | print("PRODUCTION MCP SERVER CACHING PERFORMANCE BENCHMARK")
29 | print("=" * 80)
30 | print(f"Storage Backend: {config.STORAGE_BACKEND}")
31 | print(f"Database Path: {config.SQLITE_VEC_PATH}")
32 | print()
33 |
34 | # Create server instance
35 | server = MemoryServer()
36 |
37 | results = []
38 | num_calls = 10
39 |
40 | print(f"Running {num_calls} consecutive storage initialization calls...\n")
41 |
42 | for i in range(num_calls):
43 | # Reset storage flag to simulate fresh initialization check
44 | # (but cache will persist between calls)
45 | if i > 0:
46 | server._storage_initialized = False
47 |
48 | start = time.time()
49 |
50 | # Call the lazy initialization method
51 | await server._ensure_storage_initialized()
52 |
53 | duration_ms = (time.time() - start) * 1000
54 | results.append(duration_ms)
55 |
56 | call_type = "CACHE MISS" if i == 0 else "CACHE HIT"
57 | print(f"Call #{i+1:2d}: {duration_ms:7.2f}ms ({call_type})")
58 |
59 | # Import cache stats from server module
60 | from mcp_memory_service import server as server_module
61 | cache_stats = server_module._CACHE_STATS
62 |
63 | # Calculate statistics
64 | first_call = results[0] # Cache miss
65 | cached_calls = results[1:] # Cache hits
66 | avg_cached = sum(cached_calls) / len(cached_calls) if cached_calls else 0
67 | min_cached = min(cached_calls) if cached_calls else 0
68 | max_cached = max(cached_calls) if cached_calls else 0
69 |
70 | print()
71 | print("=" * 80)
72 | print("RESULTS")
73 | print("=" * 80)
74 | print(f"First Call (Cache Miss): {first_call:7.2f}ms")
75 | print(f"Cached Calls Average: {avg_cached:7.2f}ms")
76 | print(f"Cached Calls Min: {min_cached:7.2f}ms")
77 | print(f"Cached Calls Max: {max_cached:7.2f}ms")
78 | print()
79 |
80 | # Calculate improvement
81 | if avg_cached > 0:
82 | improvement = ((first_call - avg_cached) / first_call) * 100
83 | speedup = first_call / avg_cached
84 | print(f"Performance Improvement: {improvement:.1f}%")
85 | print(f"Speedup Factor: {speedup:.2f}x faster")
86 |
87 | print()
88 | print("=" * 80)
89 | print("CACHE STATISTICS")
90 | print("=" * 80)
91 | print(f"Total Initialization Calls: {cache_stats['total_calls']}")
92 | print(f"Storage Cache Hits: {cache_stats['storage_hits']}")
93 | print(f"Storage Cache Misses: {cache_stats['storage_misses']}")
94 | print(f"Service Cache Hits: {cache_stats['service_hits']}")
95 | print(f"Service Cache Misses: {cache_stats['service_misses']}")
96 |
97 | storage_cache = server_module._STORAGE_CACHE
98 | service_cache = server_module._MEMORY_SERVICE_CACHE
99 | print(f"Storage Cache Size: {len(storage_cache)} instances")
100 | print(f"Service Cache Size: {len(service_cache)} instances")
101 |
102 | total_checks = cache_stats['total_calls'] * 2
103 | total_hits = cache_stats['storage_hits'] + cache_stats['service_hits']
104 | hit_rate = (total_hits / total_checks * 100) if total_checks > 0 else 0
105 | print(f"Overall Cache Hit Rate: {hit_rate:.1f}%")
106 |
107 | print()
108 | print("=" * 80)
109 | print("COMPARISON TO BASELINE")
110 | print("=" * 80)
111 | print("Baseline (no caching): 1,810ms per call")
112 | print(f"Optimized (cache miss): {first_call:7.2f}ms")
113 | print(f"Optimized (cache hit): {avg_cached:7.2f}ms")
114 | print()
115 |
116 | # Determine success
117 | target_cached_time = 400 # ms
118 | if avg_cached < target_cached_time:
119 | print(f"✅ SUCCESS: Cache hit average ({avg_cached:.2f}ms) is under target ({target_cached_time}ms)")
120 | success = True
121 | else:
122 | print(f"⚠️ PARTIAL: Cache hit average ({avg_cached:.2f}ms) exceeds target ({target_cached_time}ms)")
123 | print(f" Note: Still a significant improvement over baseline!")
124 | success = avg_cached < 1000 # Consider <1s a success
125 |
126 | print()
127 |
128 | # Test get_cache_stats MCP tool
129 | print("=" * 80)
130 | print("TESTING get_cache_stats MCP TOOL")
131 | print("=" * 80)
132 |
133 | try:
134 | # Call with empty arguments dict (tool takes no parameters)
135 | result = await server.handle_get_cache_stats({})
136 | # Extract the actual stats from MCP response format (safely parse JSON)
137 | import json
138 | stats_result = json.loads(result[0].text) if result else {}
139 | print("✅ get_cache_stats tool works!")
140 | print(f" Hit Rate: {stats_result.get('hit_rate', 'N/A')}%")
141 | print(f" Message: {stats_result.get('message', 'N/A')}")
142 | except Exception as e:
143 | print(f"❌ get_cache_stats tool failed: {e}")
144 |
145 | print()
146 |
147 | return {
148 | "success": success,
149 | "first_call_ms": first_call,
150 | "avg_cached_ms": avg_cached,
151 | "min_cached_ms": min_cached,
152 | "max_cached_ms": max_cached,
153 | "improvement_pct": improvement if avg_cached > 0 else 0,
154 | "cache_hit_rate": hit_rate
155 | }
156 |
157 |
158 | if __name__ == "__main__":
159 | try:
160 | results = asyncio.run(benchmark_server_caching())
161 |
162 | # Exit code based on success
163 | sys.exit(0 if results["success"] else 1)
164 |
165 | except Exception as e:
166 | print(f"\n❌ Benchmark failed with error: {e}")
167 | import traceback
168 | traceback.print_exc()
169 | sys.exit(2)
170 |
```
--------------------------------------------------------------------------------
/archive/docs-removed-2025-08-23/development/CLEANUP_PLAN.md:
--------------------------------------------------------------------------------
```markdown
1 | # Cleanup Plan for MCP-MEMORY-SERVICE
2 |
3 | ## 1. Test Files Organization
4 |
5 | ### Current Test Files
6 | - `test_chromadb.py` - Tests ChromaDB initialization with new API pattern
7 | - `test_health_check_fixes.py` - Tests health check fixes and validation
8 | - `test_issue_5_fix.py` - Tests tag deletion functionality
9 | - `test_performance_optimizations.py` - Tests performance improvements
10 |
11 | ### Recommended Organization
12 | 1. **Create a structured tests directory:**
13 | ```
14 | tests/
15 | ├── integration/ # Integration tests between components
16 | │ ├── test_server.py # Server integration tests
17 | │ └── test_storage.py # Storage integration tests
18 | ├── unit/ # Unit tests for individual components
19 | │ ├── test_chroma.py # ChromaDB-specific tests
20 | │ ├── test_config.py # Configuration tests
21 | │ └── test_utils.py # Utility function tests
22 | └── performance/ # Performance benchmarks
23 | ├── test_caching.py # Cache performance tests
24 | └── test_queries.py # Query performance tests
25 | ```
26 |
27 | 2. **Move existing test files to appropriate directories:**
28 | - `test_chromadb.py` → `tests/unit/test_chroma.py`
29 | - `test_health_check_fixes.py` → `tests/integration/test_storage.py`
30 | - `test_issue_5_fix.py` → `tests/unit/test_tags.py`
31 | - `test_performance_optimizations.py` → `tests/performance/test_caching.py`
32 |
33 | 3. **Create a proper test runner:**
34 | - Add `pytest.ini` configuration
35 | - Add `conftest.py` with common fixtures
36 | - Create a `.coveragerc` file for coverage reporting
37 |
38 | ## 2. Documentation Organization
39 |
40 | ### Current Documentation
41 | - `CHANGELOG.md` - Release history and changes
42 | - `CLAUDE.md` - Claude-specific documentation
43 | - `CLEANUP_SUMMARY.md` - Cleanup summary
44 | - `HEALTH_CHECK_FIXES_SUMMARY.md` - Health check fixes documentation
45 | - `PERFORMANCE_OPTIMIZATION_SUMMARY.md` - Performance optimization documentation
46 | - `README.md` - Main project documentation
47 |
48 | ### Recommended Organization
49 | 1. **Consolidate implementation documentation:**
50 | ```
51 | docs/
52 | ├── guides/ # User guides
53 | │ ├── getting_started.md # Quick start guide
54 | │ └── configuration.md # Configuration options
55 | ├── implementation/ # Implementation details
56 | │ ├── health_checks.md # Health check documentation
57 | │ ├── performance.md # Performance optimization details
58 | │ └── tags.md # Tag functionality documentation
59 | ├── api/ # API documentation
60 | │ ├── server.md # Server API documentation
61 | │ └── storage.md # Storage API documentation
62 | └── examples/ # Example code
63 | ├── basic_usage.md # Basic usage examples
64 | └── advanced.md # Advanced usage examples
65 | ```
66 |
67 | 2. **Move existing documentation:**
68 | - `HEALTH_CHECK_FIXES_SUMMARY.md` → `docs/implementation/health_checks.md`
69 | - `PERFORMANCE_OPTIMIZATION_SUMMARY.md` → `docs/implementation/performance.md`
70 | - Keep `CHANGELOG.md` in the root directory
71 | - Move `CLAUDE.md` to `docs/guides/claude_integration.md`
72 |
73 | ## 3. Backup and Archive Files
74 |
75 | ### Files to Archive
76 | - `backup_performance_optimization/` - Archive this directory
77 | - Any development artifacts that are no longer needed
78 |
79 | ### Recommended Action
80 | 1. **Create an archive directory:**
81 | ```
82 | archive/
83 | ├── 2025-06-24/ # Archive by date
84 | │ ├── tests/ # Old test files
85 | │ └── docs/ # Old documentation
86 | ```
87 |
88 | 2. **Move backup files to the archive:**
89 | - Move `backup_performance_optimization/` to `archive/2025-06-24/`
90 | - Create a README in the archive directory explaining what's stored there
91 |
92 | ## 4. Git Cleanup Actions
93 |
94 | ### Recommended Git Actions
95 | 1. **Create a new branch for changes:**
96 | ```bash
97 | git checkout -b feature/cleanup-and-organization
98 | ```
99 |
100 | 2. **Add and organize files:**
101 | ```bash
102 | # Create new directories
103 | mkdir -p tests/integration tests/unit tests/performance
104 | mkdir -p docs/guides docs/implementation docs/api docs/examples
105 | mkdir -p archive/2025-06-24
106 |
107 | # Move test files
108 | git mv test_chromadb.py tests/unit/test_chroma.py
109 | git mv test_health_check_fixes.py tests/integration/test_storage.py
110 | git mv test_issue_5_fix.py tests/unit/test_tags.py
111 | git mv test_performance_optimizations.py tests/performance/test_caching.py
112 |
113 | # Move documentation
114 | git mv HEALTH_CHECK_FIXES_SUMMARY.md docs/implementation/health_checks.md
115 | git mv PERFORMANCE_OPTIMIZATION_SUMMARY.md docs/implementation/performance.md
116 | git mv CLAUDE.md docs/guides/claude_integration.md
117 |
118 | # Archive backup files
119 | git mv backup_performance_optimization archive/2025-06-24/
120 | ```
121 |
122 | 3. **Update CHANGELOG.md:**
123 | ```bash
124 | git mv CHANGELOG.md.new CHANGELOG.md
125 | ```
126 |
127 | 4. **Commit changes:**
128 | ```bash
129 | git add .
130 | git commit -m "Organize tests, documentation, and archive old files"
131 | ```
132 |
133 | 5. **Create new branch for hardware testing:**
134 | ```bash
135 | git checkout -b test/hardware-validation
136 | ```
137 |
138 | ## 5. Final Verification Steps
139 |
140 | 1. **Run tests to ensure everything still works:**
141 | ```bash
142 | cd tests
143 | pytest
144 | ```
145 |
146 | 2. **Verify documentation links are updated:**
147 | - Check README.md for any links to moved files
148 | - Update any cross-references in documentation
149 |
150 | 3. **Ensure CHANGELOG is complete:**
151 | - Verify all changes are documented
152 | - Check version numbers and dates
153 |
154 | 4. **Track changes in memory:**
155 | ```bash
156 | # Store the changes in memory
157 | memory store_memory --content "Reorganized MCP-MEMORY-SERVICE project structure on June 24, 2025. Created proper test directory structure, consolidated documentation in docs/ directory, and archived old backup files. Changes are in the feature/cleanup-and-organization branch, with hardware testing in test/hardware-validation branch." --tags "mcp-memory-service,cleanup,reorganization,memory-driven"
158 | ```
```
--------------------------------------------------------------------------------
/scripts/pr/thread_status.sh:
--------------------------------------------------------------------------------
```bash
1 | #!/bin/bash
2 | # scripts/pr/thread_status.sh - Display PR review thread status
3 | #
4 | # Shows comprehensive status of all review threads on a PR with filtering options.
5 | # Uses GitHub GraphQL API to access review thread data.
6 | #
7 | # Usage: bash scripts/pr/thread_status.sh <PR_NUMBER> [--unresolved|--resolved|--outdated]
8 | # Example: bash scripts/pr/thread_status.sh 212 --unresolved
9 | #
10 | # Flags:
11 | # --unresolved: Show only unresolved threads
12 | # --resolved: Show only resolved threads
13 | # --outdated: Show only outdated threads
14 | # (no flag): Show all threads with summary
15 |
16 | set -e
17 |
18 | # Get script directory for sourcing helpers
19 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
20 |
21 | # Source GraphQL helpers
22 | if [ -f "$SCRIPT_DIR/lib/graphql_helpers.sh" ]; then
23 | source "$SCRIPT_DIR/lib/graphql_helpers.sh"
24 | else
25 | echo "Error: GraphQL helpers not found at $SCRIPT_DIR/lib/graphql_helpers.sh"
26 | exit 1
27 | fi
28 |
29 | # Parse arguments
30 | PR_NUMBER=$1
31 | FILTER=${2:-all}
32 |
33 | if [ -z "$PR_NUMBER" ]; then
34 | echo "Usage: $0 <PR_NUMBER> [--unresolved|--resolved|--outdated]"
35 | echo "Example: $0 212 --unresolved"
36 | exit 1
37 | fi
38 |
39 | # Verify gh CLI supports GraphQL
40 | if ! check_graphql_support; then
41 | exit 1
42 | fi
43 |
44 | # Color codes for output
45 | RED='\033[0;31m'
46 | GREEN='\033[0;32m'
47 | YELLOW='\033[1;33m'
48 | BLUE='\033[0;34m'
49 | GRAY='\033[0;90m'
50 | NC='\033[0m' # No Color
51 |
52 | echo "========================================"
53 | echo " PR Review Thread Status"
54 | echo "========================================"
55 | echo "PR Number: #$PR_NUMBER"
56 | echo "Filter: ${FILTER/--/}"
57 | echo ""
58 |
59 | # Get all review threads
60 | echo "Fetching review threads..."
61 | threads_json=$(get_review_threads "$PR_NUMBER")
62 |
63 | # Get thread statistics
64 | stats=$(get_thread_stats "$PR_NUMBER")
65 |
66 | total=$(echo "$stats" | jq -r '.total')
67 | resolved=$(echo "$stats" | jq -r '.resolved')
68 | unresolved=$(echo "$stats" | jq -r '.unresolved')
69 | outdated=$(echo "$stats" | jq -r '.outdated')
70 |
71 | # Display summary
72 | echo "========================================"
73 | echo " Summary"
74 | echo "========================================"
75 | echo -e "Total Threads: $total"
76 | echo -e "${GREEN}Resolved:${NC} $resolved"
77 | echo -e "${RED}Unresolved:${NC} $unresolved"
78 | echo -e "${YELLOW}Outdated:${NC} $outdated"
79 | echo ""
80 |
81 | if [ "$total" -eq 0 ]; then
82 | echo "✅ No review threads found for PR #$PR_NUMBER"
83 | exit 0
84 | fi
85 |
86 | # Display detailed thread list
87 | echo "========================================"
88 | echo " Thread Details"
89 | echo "========================================"
90 |
91 | # Determine jq filter based on flag
92 | case "$FILTER" in
93 | --unresolved)
94 | jq_filter='select(.isResolved == false)'
95 | ;;
96 | --resolved)
97 | jq_filter='select(.isResolved == true)'
98 | ;;
99 | --outdated)
100 | jq_filter='select(.isOutdated == true)'
101 | ;;
102 | *)
103 | jq_filter='.'
104 | ;;
105 | esac
106 |
107 | # Process and display threads
108 | thread_count=0
109 |
110 | echo "$threads_json" | jq -r ".data.repository.pullRequest.reviewThreads.nodes[] | $jq_filter | @json" | while IFS= read -r thread_json; do
111 | thread_count=$((thread_count + 1))
112 |
113 | thread_id=$(echo "$thread_json" | jq -r '.id')
114 | path=$(echo "$thread_json" | jq -r '.path // "unknown"')
115 | line=$(echo "$thread_json" | jq -r '.line // 0')
116 | original_line=$(echo "$thread_json" | jq -r '.originalLine // 0')
117 | diff_side=$(echo "$thread_json" | jq -r '.diffSide // "unknown"')
118 | is_resolved=$(echo "$thread_json" | jq -r '.isResolved')
119 | is_outdated=$(echo "$thread_json" | jq -r '.isOutdated')
120 |
121 | # Get first comment details
122 | author=$(echo "$thread_json" | jq -r '.comments.nodes[0].author.login // "unknown"')
123 | comment_body=$(echo "$thread_json" | jq -r '.comments.nodes[0].body // "No comment"')
124 | created_at=$(echo "$thread_json" | jq -r '.comments.nodes[0].createdAt // "unknown"')
125 | comment_count=$(echo "$thread_json" | jq -r '.comments.nodes | length')
126 |
127 | # Truncate comment to 150 chars for display
128 | comment_preview=$(echo "$comment_body" | head -c 150 | tr '\n' ' ')
129 | if [ ${#comment_body} -gt 150 ]; then
130 | comment_preview="${comment_preview}..."
131 | fi
132 |
133 | # Format status indicators
134 | if [ "$is_resolved" = "true" ]; then
135 | status_icon="${GREEN}✓${NC}"
136 | status_text="${GREEN}RESOLVED${NC}"
137 | else
138 | status_icon="${RED}○${NC}"
139 | status_text="${RED}UNRESOLVED${NC}"
140 | fi
141 |
142 | if [ "$is_outdated" = "true" ]; then
143 | outdated_icon="${YELLOW}⚠${NC}"
144 | outdated_text="${YELLOW}OUTDATED${NC}"
145 | else
146 | outdated_icon=" "
147 | outdated_text="${GRAY}current${NC}"
148 | fi
149 |
150 | # Display thread
151 | echo ""
152 | echo -e "$status_icon Thread #$thread_count"
153 | echo -e " Status: $status_text | $outdated_text"
154 | echo -e " File: ${BLUE}$path${NC}:$line (original: $original_line)"
155 | echo -e " Side: $diff_side"
156 | echo -e " Author: $author"
157 | echo -e " Created: $created_at"
158 | echo -e " Comments: $comment_count"
159 | echo -e " ${GRAY}\"${comment_preview}\"${NC}"
160 |
161 | # Show thread ID for reference (can be used with resolve_threads.sh)
162 | echo -e " ${GRAY}Thread ID: ${thread_id:0:20}...${NC}"
163 | done
164 |
165 | echo ""
166 | echo "========================================"
167 |
168 | # Provide actionable next steps
169 | if [ "$unresolved" -gt 0 ]; then
170 | echo ""
171 | echo "📝 Next Steps:"
172 | echo ""
173 | echo " 1. Review unresolved threads:"
174 | echo " gh pr view $PR_NUMBER --web"
175 | echo ""
176 | echo " 2. After fixing issues and pushing commits, resolve threads:"
177 | echo " bash scripts/pr/resolve_threads.sh $PR_NUMBER HEAD --auto"
178 | echo ""
179 | echo " 3. Manually resolve specific threads via GitHub web interface"
180 | echo ""
181 | echo " 4. Trigger new Gemini review after fixes:"
182 | echo " gh pr comment $PR_NUMBER --body '/gemini review'"
183 | echo ""
184 | fi
185 |
186 | # Exit with status indicating unresolved threads
187 | if [ "$unresolved" -gt 0 ]; then
188 | exit 1
189 | else
190 | echo "✅ All review threads resolved!"
191 | exit 0
192 | fi
193 |
```
--------------------------------------------------------------------------------
/scripts/pr/watch_reviews.sh:
--------------------------------------------------------------------------------
```bash
1 | #!/bin/bash
2 | # scripts/pr/watch_reviews.sh - Watch for Gemini reviews and auto-respond
3 | #
4 | # Usage: bash scripts/pr/watch_reviews.sh <PR_NUMBER> [CHECK_INTERVAL_SECONDS]
5 | # Example: bash scripts/pr/watch_reviews.sh 212 180
6 | #
7 | # Press Ctrl+C to stop watching
8 |
9 | set -e
10 |
11 | # Get script directory for sourcing helpers
12 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13 |
14 | # Source GraphQL helpers for thread resolution
15 | if [ -f "$SCRIPT_DIR/lib/graphql_helpers.sh" ]; then
16 | source "$SCRIPT_DIR/lib/graphql_helpers.sh"
17 | GRAPHQL_AVAILABLE=true
18 | else
19 | echo "Warning: GraphQL helpers not available, thread status disabled"
20 | GRAPHQL_AVAILABLE=false
21 | fi
22 |
23 | PR_NUMBER=$1
24 | CHECK_INTERVAL=${2:-180} # Default: 3 minutes
25 |
26 | if [ -z "$PR_NUMBER" ]; then
27 | echo "Usage: $0 <PR_NUMBER> [CHECK_INTERVAL_SECONDS]"
28 | echo "Example: $0 212 180"
29 | exit 1
30 | fi
31 |
32 | echo "========================================"
33 | echo " Gemini PR Review Watch Mode"
34 | echo "========================================"
35 | echo "PR Number: #$PR_NUMBER"
36 | echo "Check Interval: ${CHECK_INTERVAL}s"
37 | echo "GraphQL Thread Tracking: $([ "$GRAPHQL_AVAILABLE" = true ] && echo "Enabled" || echo "Disabled")"
38 | echo "Press Ctrl+C to stop"
39 | echo ""
40 |
41 | # Get repository from git remote (portable across forks)
42 | REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || echo "doobidoo/mcp-memory-service")
43 |
44 | # Track last review timestamp to detect new reviews
45 | last_review_time=""
46 |
47 | while true; do
48 | echo "[$(date '+%H:%M:%S')] Checking for new reviews..."
49 |
50 | # Get latest Gemini review timestamp
51 | current_review_time=$(gh api "repos/$REPO/pulls/$PR_NUMBER/reviews" 2>/dev/null | \
52 | jq -r '[.[] | select(.user.login == "gemini-code-assist[bot]")] | last | .submitted_at' 2>/dev/null || echo "")
53 |
54 | # Get review state
55 | review_state=$(gh pr view $PR_NUMBER --json reviews --jq '[.reviews[] | select(.author.login == "gemini-code-assist[bot]")] | last | .state' 2>/dev/null || echo "")
56 |
57 | # Get inline comments count (from latest review)
58 | comments_count=$(gh api "repos/$REPO/pulls/$PR_NUMBER/comments" 2>/dev/null | \
59 | jq '[.[] | select(.user.login == "gemini-code-assist[bot]")] | length' 2>/dev/null || echo "0")
60 |
61 | echo " Review State: ${review_state:-none}"
62 | echo " Inline Comments: $comments_count"
63 | echo " Last Review: ${current_review_time:-never}"
64 |
65 | # Display thread status if GraphQL available
66 | if [ "$GRAPHQL_AVAILABLE" = true ]; then
67 | thread_stats=$(get_thread_stats "$PR_NUMBER" 2>/dev/null || echo '{"total":0,"resolved":0,"unresolved":0}')
68 | total_threads=$(echo "$thread_stats" | jq -r '.total // 0')
69 | resolved_threads=$(echo "$thread_stats" | jq -r '.resolved // 0')
70 | unresolved_threads=$(echo "$thread_stats" | jq -r '.unresolved // 0')
71 | echo " Review Threads: $total_threads total, $resolved_threads resolved, $unresolved_threads unresolved"
72 | fi
73 |
74 | # Check if there's a new review
75 | if [ -n "$current_review_time" ] && [ "$current_review_time" != "$last_review_time" ]; then
76 | echo ""
77 | echo "🔔 NEW REVIEW DETECTED!"
78 | echo " Timestamp: $current_review_time"
79 | echo " State: $review_state"
80 | echo ""
81 |
82 | last_review_time="$current_review_time"
83 |
84 | # Check if approved
85 | if [ "$review_state" = "APPROVED" ]; then
86 | echo "✅ PR APPROVED by Gemini!"
87 | echo " No further action needed"
88 | echo ""
89 | echo "You can now merge the PR:"
90 | echo " gh pr merge $PR_NUMBER --squash"
91 | echo ""
92 | echo "Watch mode will continue monitoring..."
93 |
94 | elif [ "$review_state" = "CHANGES_REQUESTED" ] || [ "$comments_count" -gt 0 ]; then
95 | echo "📝 Review feedback received ($comments_count inline comments)"
96 | echo ""
97 |
98 | # Display detailed thread status if GraphQL available
99 | if [ "$GRAPHQL_AVAILABLE" = true ] && [ "$unresolved_threads" -gt 0 ]; then
100 | echo "Thread Status:"
101 | bash "$SCRIPT_DIR/thread_status.sh" "$PR_NUMBER" --unresolved 2>/dev/null || true
102 | echo ""
103 | fi
104 |
105 | echo "Options:"
106 | echo " 1. View detailed thread status:"
107 | echo " bash scripts/pr/thread_status.sh $PR_NUMBER"
108 | echo ""
109 | echo " 2. View inline comments on GitHub:"
110 | echo " gh pr view $PR_NUMBER --web"
111 | echo ""
112 | echo " 3. Run auto-review to fix issues automatically:"
113 | echo " bash scripts/pr/auto_review.sh $PR_NUMBER 5 true"
114 | echo ""
115 | echo " 4. Fix manually, push, and resolve threads:"
116 | echo " # After pushing fixes:"
117 | echo " bash scripts/pr/resolve_threads.sh $PR_NUMBER HEAD --auto"
118 | echo " gh pr comment $PR_NUMBER --body '/gemini review'"
119 | echo ""
120 |
121 | # Optionally auto-trigger review cycle
122 | read -t 30 -p "Auto-run review cycle? (y/N): " response || response="n"
123 | echo ""
124 |
125 | if [[ "$response" =~ ^[Yy]$ ]]; then
126 | echo "🤖 Starting automated review cycle..."
127 | bash scripts/pr/auto_review.sh $PR_NUMBER 3 true
128 | echo ""
129 | echo "✅ Auto-review cycle completed"
130 | echo " Watch mode resuming..."
131 | else
132 | echo "⏭️ Skipped auto-review"
133 | echo " Manual fixes expected"
134 | fi
135 |
136 | elif [ "$review_state" = "COMMENTED" ]; then
137 | echo "💬 General comments received (no changes requested)"
138 | echo " Review: $review_state"
139 |
140 | else
141 | echo "ℹ️ Review state: ${review_state:-unknown}"
142 | fi
143 |
144 | echo ""
145 | echo "----------------------------------------"
146 | fi
147 |
148 | echo " Next check in ${CHECK_INTERVAL}s..."
149 | echo ""
150 | sleep $CHECK_INTERVAL
151 | done
152 |
```
--------------------------------------------------------------------------------
/src/mcp_memory_service/cli/main.py:
--------------------------------------------------------------------------------
```python
1 | # Copyright 2024 Heinrich Krupp
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | """
16 | Main CLI entry point for MCP Memory Service.
17 | """
18 |
19 | import click
20 | import sys
21 | import os
22 |
23 | from .. import __version__
24 | from .ingestion import add_ingestion_commands
25 |
26 |
27 | @click.group(invoke_without_command=True)
28 | @click.version_option(version=__version__, prog_name="MCP Memory Service")
29 | @click.pass_context
30 | def cli(ctx):
31 | """
32 | MCP Memory Service - A semantic memory service using the Model Context Protocol.
33 |
34 | Provides document ingestion, memory management, and MCP server functionality.
35 | """
36 | ctx.ensure_object(dict)
37 |
38 | # Backward compatibility: if no subcommand provided, default to server
39 | if ctx.invoked_subcommand is None:
40 | import warnings
41 | warnings.warn(
42 | "Running 'memory' without a subcommand is deprecated. "
43 | "Please use 'memory server' explicitly. "
44 | "This backward compatibility will be removed in a future version.",
45 | DeprecationWarning,
46 | stacklevel=2
47 | )
48 | # Default to server command with default options for backward compatibility
49 | ctx.invoke(server, debug=False, storage_backend=None)
50 |
51 |
52 | @cli.command()
53 | @click.option('--debug', is_flag=True, help='Enable debug logging')
54 | @click.option('--storage-backend', '-s', default=None,
55 | type=click.Choice(['sqlite_vec', 'sqlite-vec', 'cloudflare', 'hybrid']), help='Storage backend to use (defaults to environment or sqlite_vec)')
56 | def server(debug, storage_backend):
57 | """
58 | Start the MCP Memory Service server.
59 |
60 | This starts the Model Context Protocol server that can be used by
61 | Claude Desktop, VS Code extensions, and other MCP-compatible clients.
62 | """
63 | # Set environment variables if explicitly provided
64 | if storage_backend is not None:
65 | os.environ['MCP_MEMORY_STORAGE_BACKEND'] = storage_backend
66 |
67 | # Import and run the server main function
68 | from ..server import main as server_main
69 |
70 | # Set debug flag
71 | if debug:
72 | import logging
73 | logging.basicConfig(level=logging.DEBUG)
74 |
75 | # Start the server
76 | server_main()
77 |
78 |
79 | @cli.command()
80 | @click.option('--storage-backend', '-s', default='sqlite_vec',
81 | type=click.Choice(['sqlite_vec', 'sqlite-vec', 'cloudflare', 'hybrid']), help='Storage backend to use')
82 | def status():
83 | """
84 | Show memory service status and statistics.
85 | """
86 | import asyncio
87 |
88 | async def show_status():
89 | try:
90 | from .utils import get_storage
91 |
92 | storage = await get_storage(storage_backend)
93 | stats = await storage.get_stats() if hasattr(storage, 'get_stats') else {}
94 |
95 | click.echo("📊 MCP Memory Service Status\n")
96 | click.echo(f" Version: {__version__}")
97 | click.echo(f" Backend: {storage.__class__.__name__}")
98 |
99 | if stats:
100 | click.echo(f" Memories: {stats.get('total_memories', 'Unknown')}")
101 | click.echo(f" Database size: {stats.get('database_size_mb', 'Unknown')} MB")
102 | click.echo(f" Unique tags: {stats.get('unique_tags', 'Unknown')}")
103 |
104 | click.echo("\n✅ Service is healthy")
105 |
106 | await storage.close()
107 |
108 | except Exception as e:
109 | click.echo(f"❌ Error connecting to storage: {str(e)}", err=True)
110 | sys.exit(1)
111 |
112 | asyncio.run(show_status())
113 |
114 |
115 | # Add ingestion commands to the CLI group
116 | add_ingestion_commands(cli)
117 |
118 |
119 | def memory_server_main():
120 | """
121 | Compatibility entry point for memory-server command.
122 |
123 | This function provides backward compatibility for the old memory-server
124 | entry point by parsing argparse-style arguments and routing them to
125 | the Click-based CLI.
126 | """
127 | import argparse
128 | import warnings
129 |
130 | # Issue deprecation warning
131 | warnings.warn(
132 | "The 'memory-server' command is deprecated. Please use 'memory server' instead. "
133 | "This compatibility wrapper will be removed in a future version.",
134 | DeprecationWarning,
135 | stacklevel=2
136 | )
137 |
138 | # Parse arguments using the same structure as the old argparse CLI
139 | parser = argparse.ArgumentParser(
140 | description="MCP Memory Service - A semantic memory service using the Model Context Protocol"
141 | )
142 | parser.add_argument(
143 | "--version",
144 | action="version",
145 | version=f"MCP Memory Service {__version__}",
146 | help="Show version information"
147 | )
148 | parser.add_argument(
149 | "--debug",
150 | action="store_true",
151 | help="Enable debug logging"
152 | )
153 | args = parser.parse_args()
154 |
155 | # Convert to Click CLI arguments and call server command
156 | click_args = ['server']
157 | if args.debug:
158 | click_args.append('--debug')
159 |
160 | # Call the Click CLI with the converted arguments
161 | try:
162 | # Temporarily replace sys.argv to pass arguments to Click
163 | original_argv = sys.argv
164 | sys.argv = ['memory'] + click_args
165 | cli()
166 | finally:
167 | sys.argv = original_argv
168 |
169 |
170 | def main():
171 | """Main entry point for the CLI."""
172 | try:
173 | cli()
174 | except KeyboardInterrupt:
175 | click.echo("\n⚠️ Operation cancelled by user")
176 | sys.exit(130)
177 | except Exception as e:
178 | click.echo(f"❌ Unexpected error: {str(e)}", err=True)
179 | sys.exit(1)
180 |
181 |
182 | if __name__ == '__main__':
183 | main()
```
--------------------------------------------------------------------------------
/archive/docs-removed-2025-08-23/UBUNTU_SETUP.md:
--------------------------------------------------------------------------------
```markdown
1 | # Ubuntu Setup Guide for MCP Memory Service with SQLite-vec
2 |
3 | ## 🎯 Overview
4 |
5 | This guide shows how to set up the MCP Memory Service with SQLite-vec backend on Ubuntu for integration with Claude Code and VS Code.
6 |
7 | ## ✅ Prerequisites Met
8 |
9 | You have successfully completed:
10 | - ✅ SQLite-vec installation and testing
11 | - ✅ Basic dependencies (sentence-transformers, torch, mcp)
12 | - ✅ Environment configuration
13 |
14 | ## 🔧 Current Setup Status
15 |
16 | Your Ubuntu machine now has:
17 |
18 | ```bash
19 | # Virtual environment active
20 | source venv/bin/activate
21 |
22 | # SQLite-vec backend configured
23 | export MCP_MEMORY_STORAGE_BACKEND=sqlite_vec
24 |
25 | # Key packages installed:
26 | - sqlite-vec (0.1.6)
27 | - sentence-transformers (5.0.0)
28 | - torch (2.7.1+cpu)
29 | - mcp (1.11.0)
30 | ```
31 |
32 | ## Claude Code Integration
33 |
34 | ### 1. Install Claude Code CLI
35 |
36 | If not already installed:
37 | ```bash
38 | # Install Claude Code CLI
39 | curl -fsSL https://claude.ai/install.sh | sh
40 | ```
41 |
42 | ### 2. Configure MCP Memory Service with Claude Code
43 |
44 | #### Option A: Automatic Configuration (Recommended)
45 | ```bash
46 | # Run installer with Claude Code auto-configuration
47 | python install.py --configure-claude-code
48 | ```
49 |
50 | This automatically:
51 | - Detects Claude Code installation
52 | - Creates personalized .mcp.json from template
53 | - Replaces placeholder paths with your system paths
54 | - Adds optimized environment variables
55 | - Adds .mcp.json to .gitignore (protects personal info)
56 | - Verifies the configuration works
57 |
58 | #### Option B: Manual Configuration
59 | ```bash
60 | # Navigate to project directory
61 | cd /home/hkr/repositories/mcp-memory-service
62 |
63 | # Add memory service with optimized settings
64 | claude mcp add memory-service --scope project \
65 | -e MCP_MEMORY_CHROMA_PATH=$HOME/.mcp_memory_chroma \
66 | -e LOG_LEVEL=INFO \
67 | -e MCP_TIMEOUT=30000 \
68 | -- python scripts/run_memory_server.py
69 |
70 | # Verify configuration
71 | claude mcp list
72 | ```
73 |
74 | ### 3. Secure Configuration Management
75 |
76 | The system uses a template-based approach to protect personal information:
77 |
78 | - **Template**: .mcp.json.template (shared, no personal data)
79 | - **Generated**: .mcp.json (personalized, automatically added to .gitignore)
80 | - **Placeholders**: {{USER_HOME}} replaced with your actual home directory
81 |
82 | ### 2. Database Location
83 |
84 | Your SQLite-vec database will be created at:
85 | ```
86 | /home/hkr/.local/share/mcp-memory/sqlite_vec.db
87 | ```
88 |
89 | This single file contains all your memories and can be easily backed up.
90 |
91 | ### 3. Claude Code Usage
92 |
93 | With the MCP Memory Service running, Claude Code can:
94 |
95 | - **Store memories**: "Remember that I prefer using Ubuntu for development"
96 | - **Retrieve memories**: "What did I tell you about my development preferences?"
97 | - **Search by tags**: Find memories with specific topics
98 | - **Time-based recall**: "What did we discuss yesterday about databases?"
99 |
100 | ### 4. Performance Benefits
101 |
102 | SQLite-vec backend provides:
103 | - **75% less memory usage** vs ChromaDB
104 | - **Faster startup times** (2-3x faster)
105 | - **Single file database** (easy backup/share)
106 | - **Better for <100K memories**
107 |
108 | ## 💻 VS Code Integration Options
109 |
110 | ### Option 1: Claude Code in VS Code Terminal
111 | ```bash
112 | # Open VS Code in your project
113 | code /home/hkr/repositories/mcp-memory-service
114 |
115 | # Use integrated terminal to run Claude Code with memory support
116 | # The memory service will automatically use sqlite-vec backend
117 | ```
118 |
119 | ### Option 2: MCP Extension (if available)
120 | ```bash
121 | # Install VS Code MCP extension when available
122 | # Configure to use local MCP Memory Service
123 | ```
124 |
125 | ### Option 3: Development Workflow
126 | ```bash
127 | # 1. Keep MCP Memory Service running in background
128 | python -m src.mcp_memory_service.server &
129 |
130 | # 2. Use Claude Code normally - it will connect to your local service
131 | # 3. All memories stored in local sqlite-vec database
132 | ```
133 |
134 | ## 🔄 Migration from ChromaDB (if needed)
135 |
136 | If you have existing ChromaDB data to migrate:
137 |
138 | ```bash
139 | # Simple migration
140 | python migrate_to_sqlite_vec.py
141 |
142 | # Or with custom paths
143 | python scripts/migrate_storage.py \
144 | --from chroma \
145 | --to sqlite_vec \
146 | --backup \
147 | --backup-path backup.json
148 | ```
149 |
150 | ## 🧪 Testing the Setup
151 |
152 | ### Quick Test
153 | ```bash
154 | # Test that everything works
155 | source venv/bin/activate
156 | export MCP_MEMORY_STORAGE_BACKEND=sqlite_vec
157 | python simple_sqlite_vec_test.py
158 | ```
159 |
160 | ### Full Test (when server is ready)
161 | ```bash
162 | # Test MCP server startup
163 | python -c "
164 | import os
165 | os.environ['MCP_MEMORY_STORAGE_BACKEND'] = 'sqlite_vec'
166 | from src.mcp_memory_service.server import main
167 | print('✅ Server can start with sqlite-vec backend')
168 | "
169 | ```
170 |
171 | ## 🛠️ Troubleshooting
172 |
173 | ### Common Issues
174 |
175 | 1. **Module Import Errors**
176 | ```bash
177 | # Make sure you're in the virtual environment
178 | source venv/bin/activate
179 |
180 | # Check installed packages
181 | pip list | grep -E "(sqlite-vec|sentence|torch|mcp)"
182 | ```
183 |
184 | 2. **Permission Errors**
185 | ```bash
186 | # Ensure database directory is writable
187 | mkdir -p ~/.local/share/mcp-memory
188 | chmod 755 ~/.local/share/mcp-memory
189 | ```
190 |
191 | 3. **Memory/Performance Issues**
192 | ```bash
193 | # SQLite-vec uses much less memory than ChromaDB
194 | # Monitor with: htop or free -h
195 | ```
196 |
197 | ### Environment Variables
198 |
199 | Add to your `~/.bashrc` for permanent configuration:
200 | ```bash
201 | echo 'export MCP_MEMORY_STORAGE_BACKEND=sqlite_vec' >> ~/.bashrc
202 | source ~/.bashrc
203 | ```
204 |
205 | ## 📊 Performance Comparison
206 |
207 | | Metric | ChromaDB | SQLite-vec | Improvement |
208 | |--------|----------|------------|-------------|
209 | | Memory Usage (1K memories) | ~200MB | ~50MB | 75% less |
210 | | Startup Time | ~5-10s | ~2-3s | 2-3x faster |
211 | | Disk Usage | ~50MB | ~35MB | 30% less |
212 | | Database Files | Multiple | Single | Simpler |
213 |
214 | ## 🎉 Next Steps
215 |
216 | 1. **Start using the memory service** with Claude Code
217 | 2. **Store development notes** and project information
218 | 3. **Build up your memory database** over time
219 | 4. **Enjoy faster, lighter memory operations**
220 |
221 | ## 📞 Support
222 |
223 | If you encounter issues:
224 | 1. Check the troubleshooting section above
225 | 2. Review the [SQLite-vec Backend Guide](../sqlite-vec-backend.md)
226 | 3. Test with `simple_sqlite_vec_test.py`
227 |
228 | Your Ubuntu setup is ready for high-performance memory operations with Claude Code! 🚀
```
--------------------------------------------------------------------------------
/claude-hooks/utilities/version-checker.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * Version Checker Utility
3 | * Reads local version from __init__.py and checks PyPI for latest published version
4 | */
5 |
6 | const fs = require('fs').promises;
7 | const path = require('path');
8 | const https = require('https');
9 |
10 | /**
11 | * Read version from __init__.py
12 | * @param {string} projectRoot - Path to project root directory
13 | * @returns {Promise<string|null>} Version string or null if not found
14 | */
15 | async function readLocalVersion(projectRoot) {
16 | try {
17 | const initPath = path.join(projectRoot, 'src', 'mcp_memory_service', '__init__.py');
18 | const content = await fs.readFile(initPath, 'utf8');
19 |
20 | // Match __version__ = "X.Y.Z" or __version__ = 'X.Y.Z'
21 | const versionMatch = content.match(/__version__\s*=\s*['"]([\d.]+)['"]/);
22 |
23 | if (versionMatch && versionMatch[1]) {
24 | return versionMatch[1];
25 | }
26 |
27 | return null;
28 | } catch (error) {
29 | return null;
30 | }
31 | }
32 |
33 | /**
34 | * Fetch latest version from PyPI
35 | * @param {string} packageName - Name of the package on PyPI
36 | * @param {number} timeout - Request timeout in ms (default: 2000)
37 | * @returns {Promise<string|null>} Latest version string or null if error
38 | */
39 | async function fetchPyPIVersion(packageName = 'mcp-memory-service', timeout = 2000) {
40 | return new Promise((resolve) => {
41 | const url = `https://pypi.org/pypi/${packageName}/json`;
42 |
43 | const timeoutId = setTimeout(() => {
44 | resolve(null);
45 | }, timeout);
46 |
47 | https.get(url, {
48 | headers: {
49 | 'User-Agent': 'mcp-memory-service-hook'
50 | }
51 | }, (res) => {
52 | let data = '';
53 |
54 | res.on('data', (chunk) => {
55 | data += chunk;
56 | });
57 |
58 | res.on('end', () => {
59 | clearTimeout(timeoutId);
60 | try {
61 | const parsed = JSON.parse(data);
62 | const latestVersion = parsed?.info?.version;
63 | resolve(latestVersion || null);
64 | } catch (error) {
65 | resolve(null);
66 | }
67 | });
68 | }).on('error', () => {
69 | clearTimeout(timeoutId);
70 | resolve(null);
71 | });
72 | });
73 | }
74 |
75 | /**
76 | * Compare two semantic versions
77 | * @param {string} local - Local version (e.g., "8.39.1")
78 | * @param {string} pypi - PyPI version (e.g., "8.38.0")
79 | * @returns {number} -1 if local < pypi, 0 if equal, 1 if local > pypi
80 | */
81 | function compareVersions(local, pypi) {
82 | const localParts = local.split('.').map(Number);
83 | const pypiParts = pypi.split('.').map(Number);
84 |
85 | for (let i = 0; i < Math.max(localParts.length, pypiParts.length); i++) {
86 | const localPart = localParts[i] || 0;
87 | const pypiPart = pypiParts[i] || 0;
88 |
89 | if (localPart < pypiPart) return -1;
90 | if (localPart > pypiPart) return 1;
91 | }
92 |
93 | return 0;
94 | }
95 |
96 | /**
97 | * Get version information with local and PyPI comparison
98 | * @param {string} projectRoot - Path to project root directory
99 | * @param {Object} options - Options for version check
100 | * @param {boolean} options.checkPyPI - Whether to check PyPI (default: true)
101 | * @param {number} options.timeout - PyPI request timeout in ms (default: 2000)
102 | * @returns {Promise<Object>} Version info object
103 | */
104 | async function getVersionInfo(projectRoot, options = {}) {
105 | const { checkPyPI = true, timeout = 2000 } = options;
106 |
107 | const localVersion = await readLocalVersion(projectRoot);
108 |
109 | const result = {
110 | local: localVersion,
111 | pypi: null,
112 | comparison: null,
113 | status: 'unknown'
114 | };
115 |
116 | if (!localVersion) {
117 | result.status = 'error';
118 | return result;
119 | }
120 |
121 | if (checkPyPI) {
122 | const pypiVersion = await fetchPyPIVersion('mcp-memory-service', timeout);
123 | result.pypi = pypiVersion;
124 |
125 | if (pypiVersion) {
126 | const comparison = compareVersions(localVersion, pypiVersion);
127 | result.comparison = comparison;
128 |
129 | if (comparison === 0) {
130 | result.status = 'published';
131 | } else if (comparison > 0) {
132 | result.status = 'development';
133 | } else {
134 | result.status = 'outdated';
135 | }
136 | } else {
137 | result.status = 'local-only';
138 | }
139 | } else {
140 | result.status = 'local-only';
141 | }
142 |
143 | return result;
144 | }
145 |
146 | /**
147 | * Format version information for display
148 | * @param {Object} versionInfo - Version info from getVersionInfo()
149 | * @param {Object} colors - Console color codes
150 | * @returns {string} Formatted version string
151 | */
152 | function formatVersionDisplay(versionInfo, colors) {
153 | const { local, pypi, status } = versionInfo;
154 |
155 | if (!local) {
156 | return `${colors.CYAN}📦 Version${colors.RESET} ${colors.DIM}→${colors.RESET} ${colors.GRAY}Unable to read version${colors.RESET}`;
157 | }
158 |
159 | let statusLabel = '';
160 | let pypiDisplay = '';
161 |
162 | switch (status) {
163 | case 'published':
164 | statusLabel = `${colors.GRAY}(published)${colors.RESET}`;
165 | break;
166 | case 'development':
167 | statusLabel = `${colors.GRAY}(local)${colors.RESET}`;
168 | pypiDisplay = pypi ? ` ${colors.DIM}•${colors.RESET} PyPI: ${colors.YELLOW}${pypi}${colors.RESET}` : '';
169 | break;
170 | case 'outdated':
171 | statusLabel = `${colors.RED}(outdated)${colors.RESET}`;
172 | pypiDisplay = pypi ? ` ${colors.DIM}•${colors.RESET} PyPI: ${colors.GREEN}${pypi}${colors.RESET}` : '';
173 | break;
174 | case 'local-only':
175 | statusLabel = `${colors.GRAY}(local)${colors.RESET}`;
176 | break;
177 | default:
178 | statusLabel = `${colors.GRAY}(unknown)${colors.RESET}`;
179 | }
180 |
181 | return `${colors.CYAN}📦 Version${colors.RESET} ${colors.DIM}→${colors.RESET} ${colors.BRIGHT}${local}${colors.RESET} ${statusLabel}${pypiDisplay}`;
182 | }
183 |
184 | module.exports = {
185 | readLocalVersion,
186 | fetchPyPIVersion,
187 | compareVersions,
188 | getVersionInfo,
189 | formatVersionDisplay
190 | };
191 |
```
--------------------------------------------------------------------------------
/scripts/testing/test_mdns.py:
--------------------------------------------------------------------------------
```python
1 | #!/usr/bin/env python3
2 | # Copyright 2024 Heinrich Krupp
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | """
17 | Test script for mDNS functionality.
18 |
19 | This script runs the mDNS unit and integration tests to verify that
20 | the service discovery functionality works correctly.
21 | """
22 |
23 | import os
24 | import sys
25 | import subprocess
26 | import argparse
27 |
28 | # Add the src directory to the Python path
29 | sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'src'))
30 |
31 | def run_unit_tests():
32 | """Run unit tests for mDNS functionality."""
33 | print("🧪 Running mDNS unit tests...")
34 |
35 | # Try pytest first, fall back to simple test
36 | test_file_pytest = os.path.join(
37 | os.path.dirname(os.path.dirname(__file__)),
38 | 'tests', 'unit', 'test_mdns.py'
39 | )
40 |
41 | test_file_simple = os.path.join(
42 | os.path.dirname(os.path.dirname(__file__)),
43 | 'tests', 'unit', 'test_mdns_simple.py'
44 | )
45 |
46 | # Try pytest first
47 | try:
48 | result = subprocess.run([
49 | sys.executable, '-m', 'pytest', test_file_pytest, '-v'
50 | ], check=True, capture_output=True, text=True)
51 |
52 | print("✅ Unit tests passed (pytest)!")
53 | print(result.stdout)
54 | return True
55 |
56 | except (subprocess.CalledProcessError, FileNotFoundError):
57 | # Fall back to simple test
58 | try:
59 | result = subprocess.run([
60 | sys.executable, test_file_simple
61 | ], check=True, capture_output=True, text=True)
62 |
63 | print("✅ Unit tests passed (simple)!")
64 | print(result.stdout)
65 | return True
66 |
67 | except subprocess.CalledProcessError as e:
68 | print("❌ Unit tests failed!")
69 | print(e.stdout)
70 | print(e.stderr)
71 | return False
72 |
73 | def run_integration_tests():
74 | """Run integration tests for mDNS functionality."""
75 | print("🌐 Running mDNS integration tests...")
76 |
77 | test_file = os.path.join(
78 | os.path.dirname(os.path.dirname(__file__)),
79 | 'tests', 'integration', 'test_mdns_integration.py'
80 | )
81 |
82 | try:
83 | result = subprocess.run([
84 | sys.executable, '-m', 'pytest', test_file, '-v', '-m', 'integration'
85 | ], check=True, capture_output=True, text=True)
86 |
87 | print("✅ Integration tests passed!")
88 | print(result.stdout)
89 | return True
90 |
91 | except subprocess.CalledProcessError as e:
92 | print("⚠️ Integration tests had issues (may be expected in CI):")
93 | print(e.stdout)
94 | print(e.stderr)
95 | # Integration tests may fail in CI environments, so don't fail the script
96 | return True
97 |
98 | def check_dependencies():
99 | """Check if required dependencies are available."""
100 | print("🔍 Checking mDNS test dependencies...")
101 |
102 | pytest_available = True
103 | try:
104 | import pytest
105 | print("✅ pytest available")
106 | except ImportError:
107 | print("⚠️ pytest not available - will use simple tests")
108 | pytest_available = False
109 |
110 | try:
111 | import zeroconf
112 | print("✅ zeroconf available")
113 | except ImportError:
114 | print("❌ zeroconf not available - this should have been installed with the package")
115 | return False
116 |
117 | try:
118 | import aiohttp
119 | print("✅ aiohttp available")
120 | except ImportError:
121 | print("❌ aiohttp not available - install with: pip install aiohttp")
122 | return False
123 |
124 | return True
125 |
126 | def test_basic_imports():
127 | """Test that mDNS modules can be imported."""
128 | print("📦 Testing mDNS module imports...")
129 |
130 | try:
131 | from mcp_memory_service.discovery.mdns_service import ServiceAdvertiser, ServiceDiscovery
132 | print("✅ mDNS service modules imported successfully")
133 |
134 | from mcp_memory_service.discovery.client import DiscoveryClient
135 | print("✅ Discovery client imported successfully")
136 |
137 | return True
138 |
139 | except ImportError as e:
140 | print(f"❌ Import failed: {e}")
141 | return False
142 |
143 | def main():
144 | """Main test function."""
145 | parser = argparse.ArgumentParser(description="Test mDNS functionality")
146 | parser.add_argument(
147 | "--unit-only",
148 | action="store_true",
149 | help="Run only unit tests (skip integration tests)"
150 | )
151 | parser.add_argument(
152 | "--integration-only",
153 | action="store_true",
154 | help="Run only integration tests (skip unit tests)"
155 | )
156 | parser.add_argument(
157 | "--no-integration",
158 | action="store_true",
159 | help="Skip integration tests (same as --unit-only)"
160 | )
161 |
162 | args = parser.parse_args()
163 |
164 | print("🔧 MCP Memory Service - mDNS Functionality Test")
165 | print("=" * 50)
166 |
167 | # Check dependencies
168 | if not check_dependencies():
169 | print("\n❌ Dependency check failed!")
170 | return 1
171 |
172 | # Test imports
173 | if not test_basic_imports():
174 | print("\n❌ Import test failed!")
175 | return 1
176 |
177 | success = True
178 |
179 | # Run unit tests
180 | if not args.integration_only:
181 | if not run_unit_tests():
182 | success = False
183 |
184 | # Run integration tests
185 | if not (args.unit_only or args.no_integration):
186 | if not run_integration_tests():
187 | success = False
188 |
189 | print("\n" + "=" * 50)
190 | if success:
191 | print("🎉 All mDNS tests completed successfully!")
192 | return 0
193 | else:
194 | print("❌ Some tests failed!")
195 | return 1
196 |
197 | if __name__ == "__main__":
198 | sys.exit(main())
```
--------------------------------------------------------------------------------
/tests/test_client.py:
--------------------------------------------------------------------------------
```python
1 | # Copyright 2024 Heinrich Krupp
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | """
16 | MCP Memory Service Test Client
17 | Copyright (c) 2024 Heinrich Krupp
18 | Licensed under the MIT License. See LICENSE file in the project root for full license text.
19 | """
20 | import json
21 | import logging
22 | import sys
23 | import os
24 | from typing import Dict, Any
25 | import threading
26 | import queue
27 | import time
28 |
29 | # Configure logging
30 | logging.basicConfig(
31 | level=logging.DEBUG,
32 | format='%(asctime)s - %(levelname)s - %(message)s',
33 | stream=sys.stderr
34 | )
35 | logger = logging.getLogger(__name__)
36 |
37 | class MCPTestClient:
38 | def __init__(self):
39 | self.message_id = 0
40 | self.client_name = "test_client"
41 | self.client_version = "0.1.0"
42 | self.protocol_version = "0.1.0"
43 | self.response_queue = queue.Queue()
44 | self._setup_io()
45 |
46 | def _setup_io(self):
47 | """Set up binary mode for Windows."""
48 | if os.name == 'nt':
49 | import msvcrt
50 | msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
51 | msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
52 | sys.stdin.reconfigure(encoding='utf-8')
53 | sys.stdout.reconfigure(encoding='utf-8')
54 |
55 | def get_message_id(self) -> str:
56 | """Generate a unique message ID."""
57 | self.message_id += 1
58 | return f"msg_{self.message_id}"
59 |
60 | def send_message(self, message: Dict[str, Any], timeout: float = 30.0) -> Dict[str, Any]:
61 | """Send a message and wait for response."""
62 | try:
63 | message_str = json.dumps(message) + '\n'
64 | logger.debug(f"Sending message: {message_str.strip()}")
65 |
66 | # Write message to stdout
67 | sys.stdout.write(message_str)
68 | sys.stdout.flush()
69 |
70 | # Read response from stdin with timeout
71 | start_time = time.time()
72 | while True:
73 | if time.time() - start_time > timeout:
74 | raise TimeoutError(f"No response received within {timeout} seconds")
75 |
76 | try:
77 | response = sys.stdin.readline()
78 | if response:
79 | logger.debug(f"Received response: {response.strip()}")
80 | return json.loads(response)
81 | except Exception as e:
82 | logger.error(f"Error reading response: {str(e)}")
83 | raise
84 |
85 | time.sleep(0.1) # Small delay to prevent busy waiting
86 |
87 | except Exception as e:
88 | logger.error(f"Error in communication: {str(e)}")
89 | raise
90 |
91 | def test_memory_operations(self):
92 | """Run through a series of test operations."""
93 | try:
94 | # Initialize connection
95 | logger.info("Initializing connection...")
96 | init_message = {
97 | "jsonrpc": "2.0",
98 | "method": "initialize",
99 | "params": {
100 | "client_name": self.client_name,
101 | "client_version": self.client_version,
102 | "protocol_version": self.protocol_version
103 | },
104 | "id": self.get_message_id()
105 | }
106 | init_response = self.send_message(init_message)
107 | logger.info(f"Initialization response: {json.dumps(init_response, indent=2)}")
108 |
109 | # List available tools
110 | logger.info("\nListing available tools...")
111 | tools_message = {
112 | "jsonrpc": "2.0",
113 | "method": "list_tools",
114 | "params": {},
115 | "id": self.get_message_id()
116 | }
117 | tools_response = self.send_message(tools_message)
118 | logger.info(f"Available tools: {json.dumps(tools_response, indent=2)}")
119 |
120 | # Store test memories
121 | test_memories = [
122 | {
123 | "content": "Remember to update documentation for API changes",
124 | "metadata": {
125 | "tags": ["todo", "documentation", "api"],
126 | "type": "task"
127 | }
128 | },
129 | {
130 | "content": "Team meeting notes: Discussed new feature rollout plan",
131 | "metadata": {
132 | "tags": ["meeting", "notes", "features"],
133 | "type": "note"
134 | }
135 | }
136 | ]
137 |
138 | logger.info("\nStoring test memories...")
139 | for memory in test_memories:
140 | store_message = {
141 | "jsonrpc": "2.0",
142 | "method": "call_tool",
143 | "params": {
144 | "name": "store_memory",
145 | "arguments": memory
146 | },
147 | "id": self.get_message_id()
148 | }
149 | store_response = self.send_message(store_message)
150 | logger.info(f"Store response: {json.dumps(store_response, indent=2)}")
151 |
152 | except TimeoutError as e:
153 | logger.error(f"Operation timed out: {str(e)}")
154 | except Exception as e:
155 | logger.error(f"An error occurred: {str(e)}")
156 | raise
157 |
158 | def main():
159 | client = MCPTestClient()
160 | client.test_memory_operations()
161 |
162 | if __name__ == "__main__":
163 | try:
164 | main()
165 | except KeyboardInterrupt:
166 | logger.info("Test client stopped by user")
167 | except Exception as e:
168 | logger.error(f"Test client failed: {str(e)}")
```
--------------------------------------------------------------------------------
/scripts/maintenance/regenerate_embeddings.py:
--------------------------------------------------------------------------------
```python
1 | #!/usr/bin/env python3
2 | """
3 | Regenerate embeddings for all memories after cosine distance migration.
4 |
5 | This script regenerates embeddings for all existing memories in the database.
6 | Useful after migrations that drop the embeddings table but preserve memories.
7 |
8 | Usage:
9 | python scripts/maintenance/regenerate_embeddings.py
10 | """
11 |
12 | import asyncio
13 | import sys
14 | import logging
15 | from pathlib import Path
16 |
17 | # Add parent directory to path for imports
18 | sys.path.insert(0, str(Path(__file__).parent.parent.parent))
19 |
20 | from src.mcp_memory_service.storage.factory import create_storage_instance
21 | from src.mcp_memory_service.config import SQLITE_VEC_PATH
22 |
23 | logging.basicConfig(
24 | level=logging.INFO,
25 | format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
26 | )
27 | logger = logging.getLogger(__name__)
28 |
29 |
30 | async def regenerate_embeddings():
31 | """Regenerate embeddings for all memories."""
32 |
33 | database_path = SQLITE_VEC_PATH
34 | logger.info(f"Using database: {database_path}")
35 |
36 | # Create storage instance
37 | logger.info("Initializing storage backend...")
38 | storage = await create_storage_instance(database_path)
39 |
40 | try:
41 | # Get all memories (this accesses the memories table, not embeddings)
42 | logger.info("Fetching all memories from database...")
43 |
44 | # Access the primary storage directly for hybrid backend
45 | if hasattr(storage, 'primary'):
46 | actual_storage = storage.primary
47 | else:
48 | actual_storage = storage
49 |
50 | # Get count first
51 | if hasattr(actual_storage, 'conn'):
52 | cursor = actual_storage.conn.execute('SELECT COUNT(*) FROM memories')
53 | total_count = cursor.fetchone()[0]
54 | logger.info(f"Found {total_count} memories to process")
55 |
56 | # Get all memories
57 | cursor = actual_storage.conn.execute('''
58 | SELECT content_hash, content, tags, memory_type, metadata,
59 | created_at, updated_at, created_at_iso, updated_at_iso
60 | FROM memories
61 | ''')
62 |
63 | memories = []
64 | for row in cursor.fetchall():
65 | content_hash, content, tags_str, memory_type, metadata_str = row[:5]
66 | created_at, updated_at, created_at_iso, updated_at_iso = row[5:]
67 |
68 | # Parse tags
69 | tags = [tag.strip() for tag in tags_str.split(",") if tag.strip()] if tags_str else []
70 |
71 | # Parse metadata
72 | import json
73 | metadata = json.loads(metadata_str) if metadata_str else {}
74 |
75 | memories.append({
76 | 'content_hash': content_hash,
77 | 'content': content,
78 | 'tags': tags,
79 | 'memory_type': memory_type,
80 | 'metadata': metadata,
81 | 'created_at': created_at,
82 | 'updated_at': updated_at,
83 | 'created_at_iso': created_at_iso,
84 | 'updated_at_iso': updated_at_iso
85 | })
86 |
87 | logger.info(f"Loaded {len(memories)} memories")
88 |
89 | # Regenerate embeddings
90 | logger.info("Regenerating embeddings...")
91 | success_count = 0
92 | error_count = 0
93 |
94 | for i, mem in enumerate(memories, 1):
95 | try:
96 | # Generate embedding
97 | embedding = actual_storage._generate_embedding(mem['content'])
98 |
99 | # Get the rowid for this memory
100 | cursor = actual_storage.conn.execute(
101 | 'SELECT id FROM memories WHERE content_hash = ?',
102 | (mem['content_hash'],)
103 | )
104 | result = cursor.fetchone()
105 | if not result:
106 | logger.warning(f"Memory {mem['content_hash'][:8]} not found, skipping")
107 | error_count += 1
108 | continue
109 |
110 | memory_id = result[0]
111 |
112 | # Insert embedding
113 | from src.mcp_memory_service.storage.sqlite_vec import serialize_float32
114 | actual_storage.conn.execute(
115 | 'INSERT OR REPLACE INTO memory_embeddings(rowid, content_embedding) VALUES (?, ?)',
116 | (memory_id, serialize_float32(embedding))
117 | )
118 |
119 | success_count += 1
120 |
121 | if i % 10 == 0:
122 | logger.info(f"Progress: {i}/{len(memories)} ({(i/len(memories)*100):.1f}%)")
123 | actual_storage.conn.commit()
124 |
125 | except Exception as e:
126 | logger.error(f"Error processing memory {mem['content_hash'][:8]}: {e}")
127 | error_count += 1
128 | continue
129 |
130 | # Final commit
131 | actual_storage.conn.commit()
132 |
133 | logger.info(f"\n{'='*60}")
134 | logger.info(f"Regeneration complete!")
135 | logger.info(f" ✅ Success: {success_count} embeddings")
136 | logger.info(f" ❌ Errors: {error_count}")
137 | logger.info(f" 📊 Total: {len(memories)} memories")
138 | logger.info(f"{'='*60}\n")
139 |
140 | # Verify
141 | cursor = actual_storage.conn.execute('SELECT COUNT(*) FROM memory_embeddings')
142 | embedding_count = cursor.fetchone()[0]
143 | logger.info(f"Verification: {embedding_count} embeddings in database")
144 |
145 | else:
146 | logger.error("Storage backend doesn't support direct database access")
147 | return False
148 |
149 | return True
150 |
151 | finally:
152 | # Cleanup
153 | if hasattr(storage, 'close'):
154 | await storage.close()
155 |
156 |
157 | if __name__ == '__main__':
158 | try:
159 | result = asyncio.run(regenerate_embeddings())
160 | sys.exit(0 if result else 1)
161 | except KeyboardInterrupt:
162 | logger.info("\nOperation cancelled by user")
163 | sys.exit(1)
164 | except Exception as e:
165 | logger.error(f"Fatal error: {e}", exc_info=True)
166 | sys.exit(1)
167 |
```
--------------------------------------------------------------------------------
/scripts/validation/check_documentation_links.py:
--------------------------------------------------------------------------------
```python
1 | #!/usr/bin/env python3
2 | """
3 | Script to check for broken internal links in markdown files.
4 | Checks relative links to files within the repository.
5 |
6 | Usage:
7 | python scripts/check_documentation_links.py
8 | python scripts/check_documentation_links.py --verbose
9 | python scripts/check_documentation_links.py --fix-suggestions
10 | """
11 |
12 | import os
13 | import re
14 | import argparse
15 | from pathlib import Path
16 | from typing import List, Tuple, Dict
17 |
18 | def find_markdown_files(root_dir: str) -> List[Path]:
19 | """Find all markdown files in the repository."""
20 | root = Path(root_dir)
21 | md_files = []
22 |
23 | for path in root.rglob("*.md"):
24 | # Skip venv and node_modules
25 | if ".venv" in path.parts or "venv" in path.parts or "node_modules" in path.parts:
26 | continue
27 | md_files.append(path)
28 |
29 | return md_files
30 |
31 | def extract_links(content: str) -> List[Tuple[str, str]]:
32 | """Extract markdown links from content with their text."""
33 | # Pattern for markdown links: [text](url)
34 | link_pattern = r'\[([^\]]*)\]\(([^)]+)\)'
35 | links = re.findall(link_pattern, content)
36 | return links # Return (text, url) tuples
37 |
38 | def is_internal_link(link: str) -> bool:
39 | """Check if a link is internal (relative path)."""
40 | # Skip external URLs, anchors, and mailto links
41 | if (link.startswith('http://') or
42 | link.startswith('https://') or
43 | link.startswith('mailto:') or
44 | link.startswith('#')):
45 | return False
46 | return True
47 |
48 | def resolve_link_path(md_file_path: Path, link: str) -> Path:
49 | """Resolve relative link path from markdown file location."""
50 | # Remove any anchor fragments
51 | link_path = link.split('#')[0]
52 |
53 | # Resolve relative to the markdown file's directory
54 | return (md_file_path.parent / link_path).resolve()
55 |
56 | def suggest_fixes(broken_link: str, repo_root: Path) -> List[str]:
57 | """Suggest possible fixes for broken links."""
58 | suggestions = []
59 |
60 | # Extract filename from the broken link
61 | filename = Path(broken_link).name
62 |
63 | # Search for files with similar names
64 | for md_file in find_markdown_files(str(repo_root)):
65 | if md_file.name.lower() == filename.lower():
66 | suggestions.append(str(md_file.relative_to(repo_root)))
67 | elif filename.lower() in md_file.name.lower():
68 | suggestions.append(str(md_file.relative_to(repo_root)))
69 |
70 | return suggestions[:3] # Return top 3 suggestions
71 |
72 | def check_links_in_file(md_file: Path, repo_root: Path) -> List[Tuple[str, str, str, bool]]:
73 | """Check all internal links in a markdown file."""
74 | try:
75 | with open(md_file, 'r', encoding='utf-8') as f:
76 | content = f.read()
77 | except Exception as e:
78 | print(f"Error reading {md_file}: {e}")
79 | return []
80 |
81 | links = extract_links(content)
82 | internal_links = [(text, link) for text, link in links if is_internal_link(link)]
83 |
84 | results = []
85 | for link_text, link in internal_links:
86 | try:
87 | target_path = resolve_link_path(md_file, link)
88 | exists = target_path.exists()
89 | results.append((link_text, link, str(target_path), exists))
90 | except Exception as e:
91 | results.append((link_text, link, f"Error resolving: {e}", False))
92 |
93 | return results
94 |
95 | def main():
96 | parser = argparse.ArgumentParser(description='Check for broken internal links in markdown documentation')
97 | parser.add_argument('--verbose', '-v', action='store_true', help='Show all links, not just broken ones')
98 | parser.add_argument('--fix-suggestions', '-s', action='store_true', help='Suggest fixes for broken links')
99 | parser.add_argument('--format', choices=['text', 'markdown', 'json'], default='text', help='Output format')
100 |
101 | args = parser.parse_args()
102 |
103 | repo_root = Path(__file__).parent.parent
104 | md_files = find_markdown_files(str(repo_root))
105 |
106 | print(f"Checking {len(md_files)} markdown files for broken links...\n")
107 |
108 | broken_links = []
109 | total_links = 0
110 | file_results = {}
111 |
112 | for md_file in sorted(md_files):
113 | rel_path = md_file.relative_to(repo_root)
114 | link_results = check_links_in_file(md_file, repo_root)
115 |
116 | if link_results:
117 | file_results[str(rel_path)] = link_results
118 |
119 | if args.verbose or any(not exists for _, _, _, exists in link_results):
120 | print(f"\n[FILE] {rel_path}")
121 |
122 | for link_text, link, target, exists in link_results:
123 | total_links += 1
124 | status = "[OK]" if exists else "[ERROR]"
125 |
126 | if args.verbose or not exists:
127 | print(f" {status} [{link_text}]({link})")
128 | if not exists:
129 | print(f" -> Target: {target}")
130 | broken_links.append((str(rel_path), link_text, link, target))
131 |
132 | # Summary
133 | print(f"\n" + "="*60)
134 | print(f"SUMMARY:")
135 | print(f"Total internal links checked: {total_links}")
136 | print(f"Broken links found: {len(broken_links)}")
137 |
138 | if broken_links:
139 | print(f"\n❌ BROKEN LINKS:")
140 | for file_path, link_text, link, target in broken_links:
141 | print(f"\n 📄 {file_path}")
142 | print(f" Text: {link_text}")
143 | print(f" Link: {link}")
144 | print(f" Target: {target}")
145 |
146 | if args.fix_suggestions:
147 | suggestions = suggest_fixes(link, repo_root)
148 | if suggestions:
149 | print(f" 💡 Suggestions:")
150 | for suggestion in suggestions:
151 | print(f" - {suggestion}")
152 |
153 | # Exit with error code if broken links found
154 | exit_code = 1 if broken_links else 0
155 |
156 | if broken_links:
157 | print(f"\n⚠️ Found {len(broken_links)} broken links. Use --fix-suggestions for repair ideas.")
158 | else:
159 | print(f"\n✅ All documentation links are working correctly!")
160 |
161 | return exit_code
162 |
163 | if __name__ == "__main__":
164 | exit(main())
```
--------------------------------------------------------------------------------
/scripts/backup/restore_memories.py:
--------------------------------------------------------------------------------
```python
1 | #!/usr/bin/env python3
2 | # Copyright 2024 Heinrich Krupp
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | """
17 | Restoration script to import memories from a backup JSON file into the database.
18 | This can be used to restore memories after a database issue or migration problem.
19 | """
20 | import sys
21 | import os
22 | import json
23 | import asyncio
24 | import logging
25 | import argparse
26 | from pathlib import Path
27 |
28 | # Add parent directory to path so we can import from the src directory
29 | sys.path.insert(0, str(Path(__file__).parent.parent))
30 |
31 | from src.mcp_memory_service.storage.chroma import ChromaMemoryStorage
32 | from src.mcp_memory_service.config import CHROMA_PATH, BACKUPS_PATH
33 |
34 | # Configure logging
35 | logging.basicConfig(
36 | level=logging.INFO,
37 | format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
38 | )
39 | logger = logging.getLogger("memory_restore")
40 |
41 | def parse_args():
42 | """Parse command line arguments."""
43 | parser = argparse.ArgumentParser(description="Restore memories from backup file")
44 | parser.add_argument("backup_file", help="Path to backup JSON file", type=str)
45 | parser.add_argument("--reset", action="store_true", help="Reset database before restoration")
46 | return parser.parse_args()
47 |
48 | async def restore_memories(backup_file, reset_db=False):
49 | """
50 | Import memories from a backup JSON file into the database.
51 |
52 | Args:
53 | backup_file: Path to the backup JSON file
54 | reset_db: If True, reset the database before restoration
55 | """
56 | logger.info(f"Initializing ChromaDB storage at {CHROMA_PATH}")
57 | storage = ChromaMemoryStorage(CHROMA_PATH)
58 |
59 | # Check if backup file exists
60 | if not os.path.exists(backup_file):
61 | # Check if it's a filename in the backups directory
62 | potential_path = os.path.join(BACKUPS_PATH, backup_file)
63 | if os.path.exists(potential_path):
64 | backup_file = potential_path
65 | else:
66 | raise FileNotFoundError(f"Backup file not found: {backup_file}")
67 |
68 | logger.info(f"Loading backup from {backup_file}")
69 |
70 | try:
71 | # Load backup data
72 | with open(backup_file, 'r', encoding='utf-8') as f:
73 | backup_data = json.load(f)
74 |
75 | total_memories = backup_data.get("total_memories", 0)
76 | memories = backup_data.get("memories", [])
77 |
78 | if not memories:
79 | logger.warning("No memories found in backup file")
80 | return
81 |
82 | logger.info(f"Found {len(memories)} memories in backup file")
83 |
84 | # Reset database if requested
85 | if reset_db:
86 | logger.warning("Resetting database before restoration")
87 | try:
88 | storage.client.delete_collection("memory_collection")
89 | logger.info("Deleted existing collection")
90 | except Exception as e:
91 | logger.error(f"Error deleting collection: {str(e)}")
92 |
93 | # Reinitialize collection
94 | storage.collection = storage.client.create_collection(
95 | name="memory_collection",
96 | metadata={"hnsw:space": "cosine"},
97 | embedding_function=storage.embedding_function
98 | )
99 | logger.info("Created new collection")
100 |
101 | # Process memories in batches
102 | batch_size = 50
103 | success_count = 0
104 | error_count = 0
105 |
106 | for i in range(0, len(memories), batch_size):
107 | batch = memories[i:i+batch_size]
108 | logger.info(f"Processing batch {i//batch_size + 1}/{(len(memories)-1)//batch_size + 1}")
109 |
110 | # Prepare batch data
111 | batch_ids = []
112 | batch_documents = []
113 | batch_metadatas = []
114 | batch_embeddings = []
115 |
116 | for memory in batch:
117 | batch_ids.append(memory["id"])
118 | batch_documents.append(memory["document"])
119 | batch_metadatas.append(memory["metadata"])
120 | if memory.get("embedding") is not None:
121 | batch_embeddings.append(memory["embedding"])
122 |
123 | try:
124 | # Use upsert to avoid duplicates
125 | if batch_embeddings and len(batch_embeddings) > 0 and len(batch_embeddings) == len(batch_ids):
126 | storage.collection.upsert(
127 | ids=batch_ids,
128 | documents=batch_documents,
129 | metadatas=batch_metadatas,
130 | embeddings=batch_embeddings
131 | )
132 | else:
133 | storage.collection.upsert(
134 | ids=batch_ids,
135 | documents=batch_documents,
136 | metadatas=batch_metadatas
137 | )
138 | success_count += len(batch)
139 | except Exception as e:
140 | logger.error(f"Error restoring batch: {str(e)}")
141 | error_count += len(batch)
142 |
143 | logger.info(f"Restoration completed: {success_count} memories restored, {error_count} errors")
144 |
145 | except Exception as e:
146 | logger.error(f"Error restoring backup: {str(e)}")
147 | raise
148 |
149 | async def main():
150 | """Main function to run the restoration."""
151 | args = parse_args()
152 |
153 | logger.info("=== Starting memory restoration ===")
154 |
155 | try:
156 | await restore_memories(args.backup_file, args.reset)
157 | logger.info("=== Restoration completed successfully ===")
158 | except Exception as e:
159 | logger.error(f"Restoration failed: {str(e)}")
160 | sys.exit(1)
161 |
162 | if __name__ == "__main__":
163 | asyncio.run(main())
```