#
tokens: 48048/50000 16/625 files (page 12/47)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 12 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

--------------------------------------------------------------------------------
/docs/deployment/systemd-service.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Systemd Service Setup for Linux
  2 | 
  3 | This guide explains how to set up the MCP Memory HTTP server as a systemd service on Linux for automatic startup and management.
  4 | 
  5 | ## Overview
  6 | 
  7 | The systemd service provides:
  8 | - ✅ **Automatic startup** on user login
  9 | - ✅ **Persistent operation** even when logged out (with linger enabled)
 10 | - ✅ **Automatic restarts** on failure
 11 | - ✅ **Centralized logging** via journald
 12 | - ✅ **Easy management** via systemctl commands
 13 | 
 14 | ## Installation
 15 | 
 16 | ### Quick Install
 17 | 
 18 | ```bash
 19 | # Run the installation script
 20 | cd /path/to/mcp-memory-service
 21 | bash scripts/service/install_http_service.sh
 22 | ```
 23 | 
 24 | The script will:
 25 | 1. Check prerequisites (.env file, venv)
 26 | 2. Ask whether to install as user or system service
 27 | 3. Copy service file to appropriate location
 28 | 4. Reload systemd configuration
 29 | 5. Show next steps
 30 | 
 31 | ### Manual Installation
 32 | 
 33 | If you prefer manual installation:
 34 | 
 35 | **1. User Service (Recommended - No sudo required):**
 36 | 
 37 | ```bash
 38 | # Create directory
 39 | mkdir -p ~/.config/systemd/user
 40 | 
 41 | # Copy service file
 42 | cp scripts/service/mcp-memory-http.service ~/.config/systemd/user/
 43 | 
 44 | # Reload systemd
 45 | systemctl --user daemon-reload
 46 | 
 47 | # Start service
 48 | systemctl --user start mcp-memory-http.service
 49 | 
 50 | # Enable auto-start
 51 | systemctl --user enable mcp-memory-http.service
 52 | 
 53 | # Enable linger (runs even when logged out)
 54 | loginctl enable-linger $USER
 55 | ```
 56 | 
 57 | **2. System Service (Requires sudo):**
 58 | 
 59 | ```bash
 60 | # Copy service file
 61 | sudo cp scripts/service/mcp-memory-http.service /etc/systemd/system/
 62 | 
 63 | # Edit to ensure paths are correct
 64 | sudo nano /etc/systemd/system/mcp-memory-http.service
 65 | 
 66 | # Reload systemd
 67 | sudo systemctl daemon-reload
 68 | 
 69 | # Start service
 70 | sudo systemctl start mcp-memory-http.service
 71 | 
 72 | # Enable auto-start
 73 | sudo systemctl enable mcp-memory-http.service
 74 | ```
 75 | 
 76 | ## Service Management
 77 | 
 78 | ### Basic Commands
 79 | 
 80 | ```bash
 81 | # Start service
 82 | systemctl --user start mcp-memory-http.service
 83 | 
 84 | # Stop service
 85 | systemctl --user stop mcp-memory-http.service
 86 | 
 87 | # Restart service
 88 | systemctl --user restart mcp-memory-http.service
 89 | 
 90 | # Check status
 91 | systemctl --user status mcp-memory-http.service
 92 | 
 93 | # Enable auto-start on login
 94 | systemctl --user enable mcp-memory-http.service
 95 | 
 96 | # Disable auto-start
 97 | systemctl --user disable mcp-memory-http.service
 98 | ```
 99 | 
100 | ### Viewing Logs
101 | 
102 | ```bash
103 | # Live logs (follow mode)
104 | journalctl --user -u mcp-memory-http.service -f
105 | 
106 | # Last 50 lines
107 | journalctl --user -u mcp-memory-http.service -n 50
108 | 
109 | # Logs since boot
110 | journalctl --user -u mcp-memory-http.service -b
111 | 
112 | # Logs for specific time range
113 | journalctl --user -u mcp-memory-http.service --since "2 hours ago"
114 | 
115 | # Logs with priority filter (only errors and above)
116 | journalctl --user -u mcp-memory-http.service -p err
117 | ```
118 | 
119 | ## Configuration
120 | 
121 | The service file is located at:
122 | - User service: `~/.config/systemd/user/mcp-memory-http.service`
123 | - System service: `/etc/systemd/system/mcp-memory-http.service`
124 | 
125 | ### Service File Structure
126 | 
127 | ```ini
128 | [Unit]
129 | Description=MCP Memory Service HTTP Server (Hybrid Backend)
130 | Documentation=https://github.com/doobidoo/mcp-memory-service
131 | After=network.target network-online.target
132 | Wants=network-online.target
133 | 
134 | [Service]
135 | Type=simple
136 | WorkingDirectory=/home/hkr/repositories/mcp-memory-service
137 | Environment=PATH=/home/hkr/repositories/mcp-memory-service/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
138 | Environment=PYTHONPATH=/home/hkr/repositories/mcp-memory-service/src
139 | EnvironmentFile=/home/hkr/repositories/mcp-memory-service/.env
140 | ExecStart=/home/hkr/repositories/mcp-memory-service/venv/bin/python /home/hkr/repositories/mcp-memory-service/scripts/server/run_http_server.py
141 | Restart=always
142 | RestartSec=10
143 | StandardOutput=journal
144 | StandardError=journal
145 | SyslogIdentifier=mcp-memory-http
146 | 
147 | # Security hardening
148 | NoNewPrivileges=true
149 | PrivateTmp=true
150 | 
151 | [Install]
152 | WantedBy=default.target
153 | ```
154 | 
155 | ### Important Configuration Points
156 | 
157 | 1. **User Service vs System Service:**
158 |    - User services run as your user (recommended)
159 |    - System services run at boot (before user login)
160 |    - User services can't have `User=` and `Group=` directives
161 |    - User services use `WantedBy=default.target` not `multi-user.target`
162 | 
163 | 2. **Environment Loading:**
164 |    - Service loads `.env` file via `EnvironmentFile` directive
165 |    - All environment variables are available to the service
166 |    - Changes to `.env` require service restart
167 | 
168 | 3. **Working Directory:**
169 |    - Service runs from project root
170 |    - Relative paths in code work correctly
171 |    - Database paths should be absolute or relative to working directory
172 | 
173 | ## Troubleshooting
174 | 
175 | ### Service Won't Start
176 | 
177 | **Check status for errors:**
178 | ```bash
179 | systemctl --user status mcp-memory-http.service
180 | ```
181 | 
182 | **Common Issues:**
183 | 
184 | 1. **GROUP error (status=216/GROUP):**
185 |    - Remove `User=` and `Group=` directives from user service file
186 |    - These are only for system services
187 | 
188 | 2. **Permission denied:**
189 |    - Check that `.env` file is readable by your user
190 |    - Check that venv and scripts are accessible
191 |    - For system services, ensure files are owned by service user
192 | 
193 | 3. **Port already in use:**
194 |    ```bash
195 |    lsof -i :8000
196 |    # Kill existing process or change port in .env
197 |    ```
198 | 
199 | 4. **Missing dependencies:**
200 |    ```bash
201 |    # Verify venv is set up
202 |    ls -la venv/bin/python
203 | 
204 |    # Reinstall if needed
205 |    python -m venv venv
206 |    source venv/bin/activate
207 |    pip install -e .
208 |    ```
209 | 
210 | ### Service Fails to Enable
211 | 
212 | **Error:** "Unit is added as a dependency to a non-existent unit"
213 | 
214 | **Solution:** For user services, change `WantedBy=` target:
215 | ```bash
216 | # Edit service file
217 | nano ~/.config/systemd/user/mcp-memory-http.service
218 | 
219 | # Change this:
220 | [Install]
221 | WantedBy=multi-user.target
222 | 
223 | # To this:
224 | [Install]
225 | WantedBy=default.target
226 | 
227 | # Reload and reenable
228 | systemctl --user daemon-reload
229 | systemctl --user reenable mcp-memory-http.service
230 | ```
231 | 
232 | ### Logs Show Configuration Errors
233 | 
234 | **Check environment loading:**
235 | ```bash
236 | # View effective environment
237 | systemctl --user show-environment
238 | 
239 | # Test service startup manually
240 | cd /path/to/mcp-memory-service
241 | source .env
242 | venv/bin/python scripts/server/run_http_server.py
243 | ```
244 | 
245 | ### Service Stops After Logout
246 | 
247 | **Enable linger to keep user services running:**
248 | ```bash
249 | loginctl enable-linger $USER
250 | 
251 | # Verify
252 | loginctl show-user $USER | grep Linger
253 | # Should show: Linger=yes
254 | ```
255 | 
256 | ## Performance Monitoring
257 | 
258 | ```bash
259 | # Check memory usage
260 | systemctl --user status mcp-memory-http.service | grep Memory
261 | 
262 | # Check CPU usage
263 | systemctl --user status mcp-memory-http.service | grep CPU
264 | 
265 | # Monitor in real-time
266 | watch -n 2 'systemctl --user status mcp-memory-http.service | grep -E "Memory|CPU"'
267 | 
268 | # Detailed resource usage
269 | systemd-cgtop --user
270 | ```
271 | 
272 | ## Security Considerations
273 | 
274 | The service includes basic security hardening:
275 | - `NoNewPrivileges=true` - Prevents privilege escalation
276 | - `PrivateTmp=true` - Isolated /tmp directory
277 | - User services run with user permissions (no root access)
278 | 
279 | For system services, consider additional hardening:
280 | - `ProtectSystem=strict` - Read-only access to system directories
281 | - `ProtectHome=read-only` - Limited home directory access
282 | - `ReadWritePaths=` - Explicitly allow write access to database paths
283 | 
284 | **Note:** Some security directives may conflict with application requirements. Test thoroughly when adding restrictions.
285 | 
286 | ## Uninstallation
287 | 
288 | ```bash
289 | # Stop and disable service
290 | systemctl --user stop mcp-memory-http.service
291 | systemctl --user disable mcp-memory-http.service
292 | 
293 | # Remove service file
294 | rm ~/.config/systemd/user/mcp-memory-http.service
295 | 
296 | # Reload systemd
297 | systemctl --user daemon-reload
298 | 
299 | # Optional: Disable linger if no other user services needed
300 | loginctl disable-linger $USER
301 | ```
302 | 
303 | ## See Also
304 | 
305 | - [HTTP Server Management](../http-server-management.md) - General server management
306 | - [Troubleshooting Guide](https://github.com/doobidoo/mcp-memory-service/wiki/07-TROUBLESHOOTING) - Common issues
307 | - [Claude Code Hooks Configuration](../../CLAUDE.md#claude-code-hooks-configuration-) - Hooks setup
308 | - [systemd.service(5)](https://www.freedesktop.org/software/systemd/man/systemd.service.html) - systemd documentation
309 | 
310 | ---
311 | 
312 | **Last Updated**: 2025-10-13
313 | **Version**: 8.5.4
314 | **Tested On**: Ubuntu 22.04, Debian 12, Fedora 38
315 | 
```

--------------------------------------------------------------------------------
/src/mcp_memory_service/ingestion/semtools_loader.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 | Semtools document loader for enhanced text extraction using Rust-based parser.
 17 | 
 18 | Uses semtools CLI (https://github.com/run-llama/semtools) for superior document
 19 | parsing with LlamaParse API integration. Supports PDF, DOCX, PPTX and other formats.
 20 | """
 21 | 
 22 | import logging
 23 | import asyncio
 24 | import os
 25 | from pathlib import Path
 26 | from typing import AsyncGenerator, Dict, Any, Optional
 27 | import shutil
 28 | 
 29 | from .base import DocumentLoader, DocumentChunk
 30 | from .chunker import TextChunker, ChunkingStrategy
 31 | 
 32 | logger = logging.getLogger(__name__)
 33 | 
 34 | 
 35 | class SemtoolsLoader(DocumentLoader):
 36 |     """
 37 |     Document loader using semtools for superior text extraction.
 38 | 
 39 |     Leverages semtools' Rust-based parser with LlamaParse API for:
 40 |     - Advanced OCR capabilities
 41 |     - Table extraction
 42 |     - Multi-format support (PDF, DOCX, PPTX, etc.)
 43 | 
 44 |     Falls back gracefully when semtools is not available.
 45 |     """
 46 | 
 47 |     def __init__(self, chunk_size: int = 1000, chunk_overlap: int = 200):
 48 |         """
 49 |         Initialize Semtools loader.
 50 | 
 51 |         Args:
 52 |             chunk_size: Target size for text chunks in characters
 53 |             chunk_overlap: Number of characters to overlap between chunks
 54 |         """
 55 |         super().__init__(chunk_size, chunk_overlap)
 56 |         self.supported_extensions = ['pdf', 'docx', 'doc', 'pptx', 'xlsx']
 57 |         self.chunker = TextChunker(ChunkingStrategy(
 58 |             chunk_size=chunk_size,
 59 |             chunk_overlap=chunk_overlap,
 60 |             respect_paragraph_boundaries=True
 61 |         ))
 62 | 
 63 |         # Check semtools availability
 64 |         self._semtools_available = self._check_semtools_availability()
 65 | 
 66 |         # Get API key from environment
 67 |         self.api_key = os.getenv('LLAMAPARSE_API_KEY')
 68 |         if self._semtools_available and not self.api_key:
 69 |             logger.warning(
 70 |                 "Semtools is available but LLAMAPARSE_API_KEY not set. "
 71 |                 "Document parsing quality may be limited."
 72 |             )
 73 | 
 74 |     def _check_semtools_availability(self) -> bool:
 75 |         """
 76 |         Check if semtools is installed and available.
 77 | 
 78 |         Returns:
 79 |             True if semtools CLI is available
 80 |         """
 81 |         semtools_path = shutil.which('semtools')
 82 |         if semtools_path:
 83 |             logger.info(f"Semtools found at: {semtools_path}")
 84 |             return True
 85 |         else:
 86 |             logger.debug(
 87 |                 "Semtools not available. Install with: npm i -g @llamaindex/semtools "
 88 |                 "or cargo install semtools"
 89 |             )
 90 |             return False
 91 | 
 92 |     def can_handle(self, file_path: Path) -> bool:
 93 |         """
 94 |         Check if this loader can handle the file.
 95 | 
 96 |         Args:
 97 |             file_path: Path to the file to check
 98 | 
 99 |         Returns:
100 |             True if semtools is available and file format is supported
101 |         """
102 |         if not self._semtools_available:
103 |             return False
104 | 
105 |         return (file_path.suffix.lower().lstrip('.') in self.supported_extensions and
106 |                 file_path.exists() and
107 |                 file_path.is_file())
108 | 
109 |     async def extract_chunks(self, file_path: Path, **kwargs) -> AsyncGenerator[DocumentChunk, None]:
110 |         """
111 |         Extract text chunks from a document using semtools.
112 | 
113 |         Args:
114 |             file_path: Path to the document file
115 |             **kwargs: Additional options (currently unused)
116 | 
117 |         Yields:
118 |             DocumentChunk objects containing parsed content
119 | 
120 |         Raises:
121 |             FileNotFoundError: If the file doesn't exist
122 |             ValueError: If semtools is not available or parsing fails
123 |         """
124 |         await self.validate_file(file_path)
125 | 
126 |         if not self._semtools_available:
127 |             raise ValueError(
128 |                 "Semtools is not available. Install with: npm i -g @llamaindex/semtools"
129 |             )
130 | 
131 |         logger.info(f"Extracting chunks from {file_path} using semtools")
132 | 
133 |         try:
134 |             # Parse document to markdown using semtools
135 |             markdown_content = await self._parse_with_semtools(file_path)
136 | 
137 |             # Get base metadata
138 |             base_metadata = self.get_base_metadata(file_path)
139 |             base_metadata.update({
140 |                 'extraction_method': 'semtools',
141 |                 'parser_backend': 'llamaparse',
142 |                 'content_type': 'markdown',
143 |                 'has_api_key': bool(self.api_key)
144 |             })
145 | 
146 |             # Chunk the markdown content
147 |             chunks = self.chunker.chunk_text(markdown_content, base_metadata)
148 | 
149 |             chunk_index = 0
150 |             for chunk_text, chunk_metadata in chunks:
151 |                 yield DocumentChunk(
152 |                     content=chunk_text,
153 |                     metadata=chunk_metadata,
154 |                     chunk_index=chunk_index,
155 |                     source_file=file_path
156 |                 )
157 |                 chunk_index += 1
158 | 
159 |         except Exception as e:
160 |             logger.error(f"Error processing {file_path} with semtools: {e}")
161 |             raise ValueError(f"Failed to parse document: {str(e)}") from e
162 | 
163 |     async def _parse_with_semtools(self, file_path: Path) -> str:
164 |         """
165 |         Parse document using semtools CLI.
166 | 
167 |         Args:
168 |             file_path: Path to document to parse
169 | 
170 |         Returns:
171 |             Markdown content extracted from document
172 | 
173 |         Raises:
174 |             RuntimeError: If semtools command fails
175 |         """
176 |         # Prepare semtools command
177 |         cmd = ['semtools', 'parse', str(file_path)]
178 | 
179 |         # Set up environment with API key if available
180 |         env = os.environ.copy()
181 |         if self.api_key:
182 |             env['LLAMAPARSE_API_KEY'] = self.api_key
183 | 
184 |         try:
185 |             # Run semtools parse command
186 |             process = await asyncio.create_subprocess_exec(
187 |                 *cmd,
188 |                 stdout=asyncio.subprocess.PIPE,
189 |                 stderr=asyncio.subprocess.PIPE,
190 |                 env=env
191 |             )
192 | 
193 |             # Wait for completion with timeout
194 |             stdout, stderr = await asyncio.wait_for(
195 |                 process.communicate(),
196 |                 timeout=300  # 5 minute timeout for large documents
197 |             )
198 | 
199 |             if process.returncode != 0:
200 |                 error_msg = stderr.decode('utf-8', errors='replace')
201 |                 logger.error(f"Semtools parsing failed: {error_msg}")
202 |                 raise RuntimeError(f"Semtools returned error: {error_msg}")
203 | 
204 |             # Parse markdown output
205 |             markdown_content = stdout.decode('utf-8', errors='replace')
206 | 
207 |             if not markdown_content.strip():
208 |                 logger.warning(f"Semtools returned empty content for {file_path}")
209 |                 raise RuntimeError("Semtools returned empty content")
210 | 
211 |             logger.debug(f"Successfully parsed {file_path}, extracted {len(markdown_content)} characters")
212 |             return markdown_content
213 | 
214 |         except asyncio.TimeoutError:
215 |             logger.error(f"Semtools parsing timed out for {file_path}")
216 |             raise RuntimeError("Document parsing timed out after 5 minutes")
217 |         except Exception as e:
218 |             logger.error(f"Error running semtools: {e}")
219 |             raise
220 | 
221 | 
222 | # Register the semtools loader
223 | def _register_semtools_loader():
224 |     """Register semtools loader with the registry."""
225 |     try:
226 |         from .registry import register_loader
227 |         register_loader(SemtoolsLoader, ['pdf', 'docx', 'doc', 'pptx', 'xlsx'])
228 |         logger.debug("Semtools loader registered successfully")
229 |     except ImportError:
230 |         logger.debug("Registry not available during import")
231 | 
232 | 
233 | # Auto-register when module is imported
234 | _register_semtools_loader()
235 | 
```

--------------------------------------------------------------------------------
/archive/docs-root-cleanup-2025-08-23/lm_studio_system_prompt.md:
--------------------------------------------------------------------------------

```markdown
  1 | # LM Studio System Prompt for MCP Tools
  2 | 
  3 | You are an AI assistant with access to various tools through the Model Context Protocol (MCP). You have access to memory storage, database operations, and other utility functions.
  4 | 
  5 | ## Why This System Prompt Exists
  6 | 
  7 | **Normally, MCP servers provide tool schemas through the `tools/list` endpoint** - the client shouldn't need explicit instructions. However, this system prompt exists because:
  8 | 
  9 | 1. **LM Studio Implementation Gap**: Some MCP clients struggle with complex JSON schema interpretation
 10 | 2. **Model Training Limitation**: The openai/gpt-oss-20b model was failing to generate proper tool calls despite receiving correct schemas
 11 | 3. **Legacy Server Compatibility**: This connects to the legacy MCP Memory Service server with specific parameter expectations
 12 | 
 13 | **This prompt supplements, not replaces, the official MCP tool schemas.** It provides concrete examples when schema interpretation fails.
 14 | 
 15 | ## Available Tool Categories:
 16 | 
 17 | ### Memory Tools (MCP Memory Service):
 18 | - `check_database_health` - Check database status and performance
 19 | - `store_memory` - Store information with tags and metadata
 20 | - `retrieve_memory` - Search and retrieve stored memories
 21 | - `recall_memory` - Time-based memory retrieval with natural language
 22 | - `search_by_tag` - Find memories by specific tags
 23 | - `delete_memory` - Remove specific memories
 24 | - `delete_by_tag` - Bulk delete memories by tags
 25 | - `optimize_db` - Optimize database performance
 26 | 
 27 | ### Other Available Tools:
 28 | - File operations, web search, code analysis, etc. (varies by MCP setup)
 29 | 
 30 | ## Tool Usage Guidelines:
 31 | 
 32 | ### 1. When to Use Tools:
 33 | - **Always use tools** when the user explicitly mentions operations like:
 34 |   - "check database health", "db health", "database status"
 35 |   - "store this information", "remember this", "save to memory"
 36 |   - "search for", "find", "recall", "retrieve"
 37 |   - "delete", "remove", "clear"
 38 | - **Use tools** for data operations, file access, external queries
 39 | - **Respond directly** for general questions, explanations, or conversations
 40 | 
 41 | ### 2. Tool Call Format - CRITICAL:
 42 | When calling a tool, use this EXACT JSON structure:
 43 | 
 44 | **For store_memory (most common):**
 45 | ```json
 46 | {"name": "store_memory", "arguments": {"content": "your text here", "metadata": {"tags": ["tag1", "tag2"], "type": "fact"}}}
 47 | ```
 48 | 
 49 | **IMPORTANT: Parameter Rules for store_memory:**
 50 | - `content` (REQUIRED): String containing the information to store
 51 | - `metadata` (OPTIONAL): Object containing:
 52 |   - `tags` (OPTIONAL): Array of strings - e.g., ["database", "health", "check"] 
 53 |   - `type` (OPTIONAL): String - "note", "fact", "reminder", "decision", etc.
 54 | 
 55 | **NOTE: The MCP server expects tags INSIDE the metadata object, not as a separate parameter!**
 56 | 
 57 | **Other common tool calls:**
 58 | - Database health: `{"name": "check_database_health", "arguments": {}}`
 59 | - Retrieve: `{"name": "retrieve_memory", "arguments": {"query": "search terms"}}`
 60 | - Recall: `{"name": "recall_memory", "arguments": {"query": "last week"}}`
 61 | - Delete: `{"name": "delete_memory", "arguments": {"memory_id": "12345"}}`
 62 | 
 63 | **CRITICAL: JSON Formatting Rules:**
 64 | 1. `tags` must be an ARRAY: `["tag1", "tag2"]` NOT a string `"tag1,tag2"`
 65 | 2. All strings must be properly escaped (use `\"` for quotes inside strings)
 66 | 3. `content` parameter is ALWAYS required for store_memory
 67 | 4. No trailing commas in JSON objects
 68 | 
 69 | ### 3. Interpreting User Requests:
 70 | - "check db health" → use `check_database_health`
 71 | - "remember that X happened" → use `store_memory` with content="X happened"
 72 | - "what do you know about Y" → use `retrieve_memory` with query="Y"
 73 | - "find memories from last week" → use `recall_memory` with query="last week"
 74 | - "delete memories about Z" → use `search_by_tag` first, then `delete_memory`
 75 | 
 76 | ### 3.1. EXACT Examples for Common Requests:
 77 | 
 78 | **"Memorize the database health results":**
 79 | ```json
 80 | {"name": "store_memory", "arguments": {"content": "Database health check completed successfully. SQLite-vec backend is healthy with 439 memories stored (2.36 MB).", "metadata": {"tags": ["database", "health", "status"], "type": "reference"}}}
 81 | ```
 82 | 
 83 | **"Remember that we got Memory MCP running in LMStudio":**
 84 | ```json
 85 | {"name": "store_memory", "arguments": {"content": "Successfully got Memory MCP running in LMStudio. The integration is working properly.", "metadata": {"tags": ["lmstudio", "mcp", "integration", "success"], "type": "fact"}}}
 86 | ```
 87 | 
 88 | **"Store this configuration":**
 89 | ```json
 90 | {"name": "store_memory", "arguments": {"content": "Configuration details: [insert config here]", "metadata": {"tags": ["configuration", "setup"], "type": "note"}}}
 91 | ```
 92 | 
 93 | ### 4. Response Format:
 94 | After calling a tool:
 95 | 1. **Briefly summarize** what you did
 96 | 2. **Present the results** in a clear, user-friendly format
 97 | 3. **Offer follow-up actions** if relevant
 98 | 
 99 | Example response flow:
100 | ```
101 | I'll check the database health for you.
102 | 
103 | {"name": "check_database_health", "arguments": {}}
104 | 
105 | The database is healthy with 439 memories stored (2.36 MB). The SQLite-vec backend is working properly with the all-MiniLM-L6-v2 embedding model.
106 | 
107 | Would you like me to run any other database operations?
108 | ```
109 | 
110 | ### 5. Common Patterns:
111 | - For storage: Always include relevant tags like ["date", "project", "category"]
112 | - For retrieval: Start with broad searches, then narrow down
113 | - For health checks: Run without arguments first, then investigate specific issues
114 | - For deletion: Always search first to confirm what will be deleted
115 | 
116 | ### 6. Error Handling:
117 | - If a tool call fails, explain what went wrong and suggest alternatives
118 | - For missing information, ask the user for clarification
119 | - If unsure which tool to use, describe your options and ask the user
120 | 
121 | ### 7. Common JSON Parsing Errors - AVOID THESE:
122 | 
123 | **❌ WRONG: String instead of array for tags**
124 | ```json
125 | {"name": "store_memory", "arguments": {"content": "text", "metadata": {"tags": "database,health"}}}
126 | ```
127 | 
128 | **✅ CORRECT: Array for tags (inside metadata)**
129 | ```json
130 | {"name": "store_memory", "arguments": {"content": "text", "metadata": {"tags": ["database", "health"]}}}
131 | ```
132 | 
133 | **❌ WRONG: Missing content parameter**
134 | ```json
135 | {"name": "store_memory", "arguments": {"metadata": {"tags": ["database"], "type": "fact"}}}
136 | ```
137 | 
138 | **✅ CORRECT: Content parameter included**
139 | ```json
140 | {"name": "store_memory", "arguments": {"content": "Actual information to store", "metadata": {"tags": ["database"]}}}
141 | ```
142 | 
143 | **❌ WRONG: Tags as separate parameter (wrong for legacy server)**
144 | ```json
145 | {"name": "store_memory", "arguments": {"content": "text", "tags": ["tag1"], "memory_type": "fact"}}
146 | ```
147 | 
148 | **✅ CORRECT: Tags inside metadata object (legacy server format)**
149 | ```json
150 | {"name": "store_memory", "arguments": {"content": "text", "metadata": {"tags": ["tag1"], "type": "fact"}}}
151 | ```
152 | 
153 | ### 8. Debugging Tool Calls:
154 | If a tool call fails with "params requires property 'content'":
155 | 1. Ensure `content` is present and is a string
156 | 2. Check that `tags` is an array of strings, not a string
157 | 3. Verify JSON syntax (no trailing commas, proper escaping)
158 | 4. Use the exact examples above as templates
159 | 
160 | ### 9. COMPLETE WORKING EXAMPLE:
161 | For the request "Memorize the result and the fact that we got the Memory MCP running in LMStudio":
162 | 
163 | **Step 1:** Call check_database_health (if needed)
164 | ```json
165 | {"name": "check_database_health", "arguments": {}}
166 | ```
167 | 
168 | **Step 2:** Store the memory with CORRECT syntax:
169 | ```json
170 | {"name": "store_memory", "arguments": {"content": "Memory MCP is successfully running in LMStudio. Database health check shows SQLite-vec backend is healthy with 439 memories stored (2.36 MB). Integration confirmed working.", "metadata": {"tags": ["lmstudio", "mcp", "integration", "success", "database"], "type": "fact"}}}
171 | ```
172 | 
173 | **✅ This format will work because:**
174 | - `content` is present and contains the actual information
175 | - `metadata.tags` is an array of strings (not a separate parameter)
176 | - `metadata.type` is a string inside the metadata object
177 | - All JSON syntax is correct
178 | - Matches the legacy MCP server schema that LM Studio connects to
179 | 
180 | Remember: **Be proactive with tool use**. When users mention operations that tools can handle, use them immediately rather than just describing what you could do.
```

--------------------------------------------------------------------------------
/scripts/validation/verify_pytorch_windows.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 | Verification script for PyTorch installation on Windows.
 18 | This script checks if PyTorch is properly installed and configured for Windows.
 19 | """
 20 | import os
 21 | import sys
 22 | import platform
 23 | import subprocess
 24 | import importlib.util
 25 | 
 26 | def print_header(text):
 27 |     """Print a formatted header."""
 28 |     print("\n" + "=" * 80)
 29 |     print(f" {text}")
 30 |     print("=" * 80)
 31 | 
 32 | def print_info(text):
 33 |     """Print formatted info text."""
 34 |     print(f"  → {text}")
 35 | 
 36 | def print_success(text):
 37 |     """Print formatted success text."""
 38 |     print(f"  ✅ {text}")
 39 | 
 40 | def print_error(text):
 41 |     """Print formatted error text."""
 42 |     print(f"  ❌ ERROR: {text}")
 43 | 
 44 | def print_warning(text):
 45 |     """Print formatted warning text."""
 46 |     print(f"  ⚠️  {text}")
 47 | 
 48 | def check_system():
 49 |     """Check if running on Windows."""
 50 |     system = platform.system().lower()
 51 |     if system != "windows":
 52 |         print_warning(f"This script is designed for Windows, but you're running on {system.capitalize()}")
 53 |     else:
 54 |         print_info(f"Running on {platform.system()} {platform.release()}")
 55 |     
 56 |     print_info(f"Python version: {platform.python_version()}")
 57 |     print_info(f"Architecture: {platform.machine()}")
 58 |     
 59 |     return system == "windows"
 60 | 
 61 | def check_pytorch_installation():
 62 |     """Check if PyTorch is installed and properly configured."""
 63 |     try:
 64 |         import torch
 65 |         print_success(f"PyTorch is installed (version {torch.__version__})")
 66 |         
 67 |         # Check if PyTorch was installed from the correct index URL
 68 |         if hasattr(torch, '_C'):
 69 |             print_success("PyTorch C extensions are available")
 70 |         else:
 71 |             print_warning("PyTorch C extensions might not be properly installed")
 72 |         
 73 |         # Check CUDA availability
 74 |         if torch.cuda.is_available():
 75 |             print_success(f"CUDA is available (version {torch.version.cuda})")
 76 |             print_info(f"GPU: {torch.cuda.get_device_name(0)}")
 77 |             print_info(f"GPU Memory: {torch.cuda.get_device_properties(0).total_memory / (1024**3):.2f} GB")
 78 |         else:
 79 |             print_info("CUDA is not available, using CPU only")
 80 |             
 81 |             # Check if DirectML is available
 82 |             try:
 83 |                 import torch_directml
 84 |                 print_success(f"DirectML is available (version {torch_directml.__version__})")
 85 |                 
 86 |                 # Check for Intel ARC GPU
 87 |                 try:
 88 |                     ps_cmd = "Get-WmiObject Win32_VideoController | Select-Object Name | Format-List"
 89 |                     gpu_output = subprocess.check_output(['powershell', '-Command', ps_cmd],
 90 |                                                     stderr=subprocess.DEVNULL,
 91 |                                                     universal_newlines=True)
 92 |                     
 93 |                     if 'Intel(R) Arc(TM)' in gpu_output or 'Intel ARC' in gpu_output:
 94 |                         print_success("Intel ARC GPU detected, DirectML support is available")
 95 |                     elif 'Intel' in gpu_output:
 96 |                         print_success("Intel GPU detected, DirectML support is available")
 97 |                     elif 'AMD' in gpu_output or 'Radeon' in gpu_output:
 98 |                         print_success("AMD GPU detected, DirectML support is available")
 99 |                 except (subprocess.SubprocessError, FileNotFoundError):
100 |                     pass
101 |                 
102 |                 # Test a simple DirectML tensor operation
103 |                 try:
104 |                     dml = torch_directml.device()
105 |                     x_dml = torch.rand(5, 3, device=dml)
106 |                     y_dml = torch.rand(5, 3, device=dml)
107 |                     z_dml = x_dml + y_dml
108 |                     print_success("DirectML tensor operations work correctly")
109 |                 except Exception as e:
110 |                     print_warning(f"DirectML tensor operations failed: {e}")
111 |             except ImportError:
112 |                 print_info("DirectML is not available")
113 |                 
114 |                 # Check for Intel/AMD GPUs that could benefit from DirectML
115 |                 try:
116 |                     ps_cmd = "Get-WmiObject Win32_VideoController | Select-Object Name | Format-List"
117 |                     gpu_output = subprocess.check_output(['powershell', '-Command', ps_cmd],
118 |                                                     stderr=subprocess.DEVNULL,
119 |                                                     universal_newlines=True)
120 |                     
121 |                     if 'Intel(R) Arc(TM)' in gpu_output or 'Intel ARC' in gpu_output:
122 |                         print_warning("Intel ARC GPU detected, but DirectML is not installed")
123 |                         print_info("Consider installing torch-directml for better performance")
124 |                     elif 'Intel' in gpu_output or 'AMD' in gpu_output or 'Radeon' in gpu_output:
125 |                         print_warning("Intel/AMD GPU detected, but DirectML is not installed")
126 |                         print_info("Consider installing torch-directml for better performance")
127 |                 except (subprocess.SubprocessError, FileNotFoundError):
128 |                     pass
129 |         
130 |         # Test a simple tensor operation
131 |         try:
132 |             x = torch.rand(5, 3)
133 |             y = torch.rand(5, 3)
134 |             z = x + y
135 |             print_success("Basic tensor operations work correctly")
136 |         except Exception as e:
137 |             print_error(f"Failed to perform basic tensor operations: {e}")
138 |             return False
139 |         
140 |         return True
141 |     except ImportError:
142 |         print_error("PyTorch is not installed")
143 |         return False
144 |     except Exception as e:
145 |         print_error(f"Error checking PyTorch installation: {e}")
146 |         return False
147 | 
148 | def suggest_installation():
149 |     """Suggest PyTorch installation commands."""
150 |     print_header("Installation Suggestions")
151 |     print_info("To install PyTorch for Windows, use one of the following commands:")
152 |     print_info("\nFor CUDA support (NVIDIA GPUs):")
153 |     print("pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118")
154 |     
155 |     print_info("\nFor CPU-only:")
156 |     print("pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu")
157 |     
158 |     print_info("\nFor DirectML support (AMD/Intel GPUs):")
159 |     print("pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu")
160 |     print("pip install torch-directml>=0.2.0")
161 |     
162 |     print_info("\nFor Intel ARC Pro Graphics:")
163 |     print("pip install torch==2.2.0 torchvision==2.2.0 torchaudio==2.2.0 --index-url https://download.pytorch.org/whl/cpu")
164 |     print("pip install torch-directml>=0.2.0")
165 |     
166 |     print_info("\nFor dual GPU setups (NVIDIA + Intel):")
167 |     print("pip install torch==2.2.0 torchvision==2.2.0 torchaudio==2.2.0 --index-url https://download.pytorch.org/whl/cu118")
168 |     print("pip install torch-directml>=0.2.0")
169 |     
170 |     print_info("\nAfter installing PyTorch, run this script again to verify the installation.")
171 | 
172 | def main():
173 |     """Main function."""
174 |     print_header("PyTorch Windows Installation Verification")
175 |     
176 |     is_windows = check_system()
177 |     if not is_windows:
178 |         print_warning("This script is designed for Windows, but may still provide useful information")
179 |     
180 |     pytorch_installed = check_pytorch_installation()
181 |     
182 |     if not pytorch_installed:
183 |         suggest_installation()
184 |         return 1
185 |     
186 |     print_header("Verification Complete")
187 |     print_success("PyTorch is properly installed and configured for Windows")
188 |     return 0
189 | 
190 | if __name__ == "__main__":
191 |     sys.exit(main())
```

--------------------------------------------------------------------------------
/scripts/quality/README_PHASE2.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Phase 2 Complexity Reduction - Quick Reference
  2 | 
  3 | ## Overview
  4 | 
  5 | This guide provides a quick reference for implementing Phase 2 complexity reductions identified in `phase2_complexity_analysis.md`.
  6 | 
  7 | ## Quick Stats
  8 | 
  9 | | Metric | Current | Target | Improvement |
 10 | |--------|---------|--------|-------------|
 11 | | **Complexity Score** | 40/100 | 50-55/100 | +10-15 points |
 12 | | **Overall Health** | 63/100 | 66-68/100 | +3 points |
 13 | | **Functions Analyzed** | 10 | - | - |
 14 | | **Total Time Estimate** | - | 12-15 hours | - |
 15 | | **Complexity Reduction** | - | -39 points | - |
 16 | 
 17 | ## Priority Matrix
 18 | 
 19 | ### High Priority (Week 1) - 7 hours
 20 | Critical path functions that need careful attention:
 21 | 
 22 | 1. **install.py::configure_paths()** (15 → 5, -10 points, 4h)
 23 |    - Extract platform detection
 24 |    - Extract storage setup
 25 |    - Extract Claude config update
 26 | 
 27 | 2. **cloudflare.py::_search_by_tags_internal()** (13 → 8, -5 points, 1.75h)
 28 |    - Extract tag normalization
 29 |    - Extract SQL query builder
 30 | 
 31 | 3. **consolidator.py::consolidate()** (12 → 8, -4 points, 1.25h)
 32 |    - Extract sync context manager
 33 |    - Extract phase guards
 34 | 
 35 | ### Medium Priority (Week 2) - 2.75 hours
 36 | Analytics functions (non-critical):
 37 | 
 38 | 4. **analytics.py::get_memory_growth()** (11 → 6, -5 points, 1.75h)
 39 |    - Extract period configuration
 40 |    - Extract interval aggregation
 41 | 
 42 | 5. **analytics.py::get_tag_usage_analytics()** (10 → 6, -4 points, 1h)
 43 |    - Extract storage stats retrieval
 44 |    - Extract tag stats calculation
 45 | 
 46 | ### Low Priority (Weeks 2-3) - 4.25 hours
 47 | Quick wins with minimal risk:
 48 | 
 49 | 6. **install.py::detect_gpu()** (10 → 7, -3 points, 1h)
 50 | 7. **cloudflare.py::get_memory_timestamps()** (9 → 7, -2 points, 45m)
 51 | 8. **consolidator.py::_get_memories_for_horizon()** (10 → 8, -2 points, 45m)
 52 | 9. **analytics.py::get_activity_breakdown()** (9 → 7, -2 points, 1h)
 53 | 10. **analytics.py::get_memory_type_distribution()** (9 → 7, -2 points, 45m)
 54 | 
 55 | ## Refactoring Patterns Cheat Sheet
 56 | 
 57 | ### Pattern 1: Extract Method
 58 | **When to use:** Function > 50 lines, nested logic, repeated code
 59 | 
 60 | **Example:**
 61 | ```python
 62 | # Before
 63 | def complex_function():
 64 |     # 20 lines of platform detection
 65 |     # 30 lines of setup logic
 66 |     # 15 lines of validation
 67 | 
 68 | # After
 69 | def detect_platform(): ...
 70 | def setup_system(): ...
 71 | def validate_config(): ...
 72 | 
 73 | def complex_function():
 74 |     platform = detect_platform()
 75 |     setup_system(platform)
 76 |     validate_config()
 77 | ```
 78 | 
 79 | ### Pattern 2: Dict Lookup
 80 | **When to use:** if/elif/else chains with similar structure
 81 | 
 82 | **Example:**
 83 | ```python
 84 | # Before
 85 | if period == "week":
 86 |     days = 7
 87 | elif period == "month":
 88 |     days = 30
 89 | elif period == "year":
 90 |     days = 365
 91 | 
 92 | # After
 93 | PERIOD_DAYS = {"week": 7, "month": 30, "year": 365}
 94 | days = PERIOD_DAYS[period]
 95 | ```
 96 | 
 97 | ### Pattern 3: Guard Clause
 98 | **When to use:** Nested if statements, early validation
 99 | 
100 | **Example:**
101 | ```python
102 | # Before
103 | def process(data):
104 |     if data is not None:
105 |         if data.valid():
106 |             if data.ready():
107 |                 return process_data(data)
108 |     return None
109 | 
110 | # After
111 | def process(data):
112 |     if data is None:
113 |         return None
114 |     if not data.valid():
115 |         return None
116 |     if not data.ready():
117 |         return None
118 |     return process_data(data)
119 | ```
120 | 
121 | ### Pattern 4: Context Manager
122 | **When to use:** Resource management, setup/teardown logic
123 | 
124 | **Example:**
125 | ```python
126 | # Before
127 | def process():
128 |     resource = acquire()
129 |     try:
130 |         do_work(resource)
131 |     finally:
132 |         release(resource)
133 | 
134 | # After
135 | class ResourceManager:
136 |     async def __aenter__(self): ...
137 |     async def __aexit__(self, *args): ...
138 | 
139 | async def process():
140 |     async with ResourceManager() as resource:
141 |         do_work(resource)
142 | ```
143 | 
144 | ### Pattern 5: Configuration Object
145 | **When to use:** Related configuration values, multiple parameters
146 | 
147 | **Example:**
148 | ```python
149 | # Before
150 | def analyze(period, days, interval, format):
151 |     ...
152 | 
153 | # After
154 | @dataclass
155 | class AnalysisConfig:
156 |     period: str
157 |     days: int
158 |     interval: int
159 |     format: str
160 | 
161 | def analyze(config: AnalysisConfig):
162 |     ...
163 | ```
164 | 
165 | ## Testing Checklist
166 | 
167 | For each refactored function:
168 | 
169 | - [ ] **Unit tests pass** - Run `pytest tests/test_<module>.py`
170 | - [ ] **Integration tests pass** - Run `pytest tests/integration/`
171 | - [ ] **No performance regression** - Benchmark before/after
172 | - [ ] **API contracts unchanged** - Check response formats
173 | - [ ] **Edge cases tested** - Null inputs, empty lists, errors
174 | - [ ] **Documentation updated** - Docstrings, comments
175 | 
176 | ## Implementation Order
177 | 
178 | ### Sequential (Single Developer)
179 | 1. Week 1: High priority functions (7h)
180 | 2. Week 2: Medium priority functions (2.75h)
181 | 3. Week 3: Low priority quick wins (4.25h)
182 | 
183 | **Total:** 14 hours over 3 weeks
184 | 
185 | ### Parallel (Multiple Developers)
186 | 1. **Developer A:** configure_paths, detect_gpu (5h)
187 | 2. **Developer B:** cloudflare functions (2.5h)
188 | 3. **Developer C:** consolidator functions (2h)
189 | 4. **Developer D:** analytics functions (4.75h)
190 | 
191 | **Total:** ~7 hours (with coordination overhead: 9-10 hours)
192 | 
193 | ### Prioritized (Critical Path Only)
194 | Focus on high-priority functions only:
195 | 1. configure_paths (4h)
196 | 2. _search_by_tags_internal (1.75h)
197 | 3. consolidate (1.25h)
198 | 
199 | **Total:** 7 hours for core improvements
200 | 
201 | ## Risk Mitigation
202 | 
203 | ### Critical Path Functions
204 | **Extra caution required:**
205 | - _search_by_tags_internal (core search)
206 | - consolidate (memory consolidation)
207 | - _get_memories_for_horizon (consolidation)
208 | 
209 | **Safety measures:**
210 | - Create feature branch for each
211 | - Comprehensive integration tests
212 | - Performance benchmarking
213 | - Staged rollout (dev → staging → production)
214 | 
215 | ### Low-Risk Functions
216 | **Can be batched:**
217 | - All analytics endpoints (read-only)
218 | - Setup functions (non-critical path)
219 | 
220 | **Safety measures:**
221 | - Standard unit testing
222 | - Manual smoke testing
223 | - Can be rolled back easily
224 | 
225 | ## Success Metrics
226 | 
227 | ### Quantitative Goals
228 | - [ ] Complexity score: 40 → 50+ (+10 points minimum)
229 | - [ ] Overall health: 63 → 66+ (+3 points minimum)
230 | - [ ] All 10 functions refactored successfully
231 | - [ ] Zero breaking changes
232 | - [ ] All tests passing
233 | 
234 | ### Qualitative Goals
235 | - [ ] Code easier to understand (peer review)
236 | - [ ] Functions are testable in isolation
237 | - [ ] Better separation of concerns
238 | - [ ] Improved maintainability
239 | 
240 | ## Common Pitfalls to Avoid
241 | 
242 | ### 1. Over-Extraction
243 | **Problem:** Creating too many tiny functions
244 | **Solution:** Extract only when it improves clarity (10+ lines minimum)
245 | 
246 | ### 2. Breaking API Contracts
247 | **Problem:** Changing function signatures
248 | **Solution:** Keep public APIs unchanged, refactor internals only
249 | 
250 | ### 3. Performance Regression
251 | **Problem:** Excessive function calls overhead
252 | **Solution:** Benchmark before/after, inline hot paths if needed
253 | 
254 | ### 4. Incomplete Testing
255 | **Problem:** Missing edge cases
256 | **Solution:** Test error paths, null inputs, boundary conditions
257 | 
258 | ### 5. Rushing Critical Functions
259 | **Problem:** Breaking core functionality
260 | **Solution:** Extra time for testing critical path functions
261 | 
262 | ## Command Reference
263 | 
264 | ### Run Quality Analysis
265 | ```bash
266 | # Run pyscn baseline report
267 | python -m pyscn baseline --output scripts/quality/baseline_report.txt
268 | 
269 | # Check specific function complexity
270 | python -m radon cc src/mcp_memory_service/storage/cloudflare.py -a
271 | 
272 | # Check cyclomatic complexity for all files
273 | python -m radon cc src/ -a
274 | ```
275 | 
276 | ### Run Tests
277 | ```bash
278 | # All tests
279 | pytest tests/
280 | 
281 | # Specific module
282 | pytest tests/test_storage.py
283 | 
284 | # Integration tests only
285 | pytest tests/integration/
286 | 
287 | # With coverage
288 | pytest tests/ --cov=mcp_memory_service --cov-report=html
289 | ```
290 | 
291 | ### Benchmark Performance
292 | ```bash
293 | # Before refactoring
294 | python scripts/benchmarks/run_benchmarks.py --baseline
295 | 
296 | # After refactoring
297 | python scripts/benchmarks/run_benchmarks.py --compare
298 | ```
299 | 
300 | ## Getting Help
301 | 
302 | ### Resources
303 | - **Phase 2 Analysis:** `scripts/quality/phase2_complexity_analysis.md` (detailed proposals)
304 | - **Phase 1 Results:** `scripts/quality/phase1_dead_code_analysis.md` (lessons learned)
305 | - **Complexity Guide:** `scripts/quality/complexity_scoring_guide.md` (understanding metrics)
306 | 
307 | ### Questions?
308 | - Review the detailed analysis for each function
309 | - Check the refactoring pattern examples
310 | - Test incrementally after each change
311 | - Ask for peer review on critical functions
312 | 
313 | ---
314 | 
315 | **Last Updated:** 2024-11-24
316 | **Next Review:** After Phase 2 completion
317 | 
```

--------------------------------------------------------------------------------
/scripts/maintenance/restore_from_json_export.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python3
  2 | """
  3 | Restore Timestamps from Clean JSON Export
  4 | 
  5 | Recovers corrupted timestamps using the clean export from the other MacBook
  6 | (v8.26, before the hybrid sync bug). Matches memories by content_hash and
  7 | restores their original creation timestamps.
  8 | 
  9 | This script:
 10 | - Reads clean timestamp mapping (content_hash → ISO timestamp)
 11 | - Matches memories in current database by content_hash
 12 | - Updates created_at and created_at_iso with original timestamps
 13 | - Preserves memories not in mapping (created after the clean export)
 14 | 
 15 | Usage:
 16 |     python scripts/maintenance/restore_from_json_export.py [--dry-run|--apply]
 17 | """
 18 | 
 19 | import json
 20 | import sqlite3
 21 | import sys
 22 | from datetime import datetime
 23 | from pathlib import Path
 24 | 
 25 | # Add src to path
 26 | sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
 27 | 
 28 | from mcp_memory_service import config
 29 | 
 30 | 
 31 | def restore_from_json(db_path: str, mapping_file: str, dry_run: bool = True):
 32 |     """
 33 |     Restore timestamps from JSON export mapping.
 34 | 
 35 |     Args:
 36 |         db_path: Path to SQLite database
 37 |         mapping_file: Path to JSON file with content_hash → timestamp mapping
 38 |         dry_run: If True, only show what would be changed
 39 |     """
 40 |     print("=" * 80)
 41 |     print("TIMESTAMP RESTORATION FROM CLEAN JSON EXPORT")
 42 |     print("=" * 80)
 43 |     print(f"Database: {db_path}")
 44 |     print(f"Mapping:  {mapping_file}")
 45 |     print(f"Mode: {'DRY RUN (no changes)' if dry_run else 'LIVE (applying changes)'}")
 46 |     print()
 47 | 
 48 |     # Load clean timestamp mapping
 49 |     print("Loading clean timestamp mapping...")
 50 |     with open(mapping_file, 'r') as f:
 51 |         clean_mapping = json.load(f)
 52 | 
 53 |     print(f"✅ Loaded {len(clean_mapping)} clean timestamps")
 54 |     print()
 55 | 
 56 |     # Connect to database
 57 |     conn = sqlite3.connect(db_path, timeout=30.0)
 58 |     conn.execute('PRAGMA busy_timeout = 30000')
 59 |     cursor = conn.cursor()
 60 | 
 61 |     # Get all memories from current database
 62 |     print("Analyzing current database...")
 63 |     cursor.execute('''
 64 |         SELECT content_hash, created_at, created_at_iso, substr(content, 1, 60)
 65 |         FROM memories
 66 |     ''')
 67 | 
 68 |     current_memories = cursor.fetchall()
 69 |     print(f"✅ Found {len(current_memories)} memories in database")
 70 |     print()
 71 | 
 72 |     # Match and analyze
 73 |     print("=" * 80)
 74 |     print("MATCHING ANALYSIS:")
 75 |     print("=" * 80)
 76 | 
 77 |     matched = []
 78 |     unmatched = []
 79 |     already_correct = []
 80 | 
 81 |     for content_hash, created_at, created_at_iso, content_preview in current_memories:
 82 |         if content_hash in clean_mapping:
 83 |             clean_timestamp = clean_mapping[content_hash]
 84 | 
 85 |             # Check if already correct
 86 |             if created_at_iso == clean_timestamp:
 87 |                 already_correct.append(content_hash)
 88 |             else:
 89 |                 matched.append({
 90 |                     'hash': content_hash,
 91 |                     'current_iso': created_at_iso,
 92 |                     'clean_iso': clean_timestamp,
 93 |                     'content': content_preview
 94 |                 })
 95 |         else:
 96 |             unmatched.append({
 97 |                 'hash': content_hash,
 98 |                 'created_iso': created_at_iso,
 99 |                 'content': content_preview
100 |             })
101 | 
102 |     print(f"✅ Matched (will restore): {len(matched)}")
103 |     print(f"✅ Already correct: {len(already_correct)}")
104 |     print(f"⏭️  Unmatched (keep as-is): {len(unmatched)}")
105 |     print()
106 | 
107 |     # Show samples
108 |     print("=" * 80)
109 |     print("SAMPLE RESTORATIONS (first 10):")
110 |     print("=" * 80)
111 |     for i, mem in enumerate(matched[:10], 1):
112 |         print(f"{i}. Hash: {mem['hash'][:16]}...")
113 |         print(f"   CURRENT: {mem['current_iso']}")
114 |         print(f"   RESTORE: {mem['clean_iso']}")
115 |         print(f"   Content: {mem['content']}...")
116 |         print()
117 | 
118 |     if len(matched) > 10:
119 |         print(f"   ... and {len(matched) - 10} more")
120 |         print()
121 | 
122 |     # Show unmatched samples (new memories)
123 |     if unmatched:
124 |         print("=" * 80)
125 |         print("UNMATCHED MEMORIES (will keep current timestamps):")
126 |         print("=" * 80)
127 |         print(f"Total: {len(unmatched)} memories")
128 |         print("\nSample (first 5):")
129 |         for i, mem in enumerate(unmatched[:5], 1):
130 |             print(f"{i}. Hash: {mem['hash'][:16]}...")
131 |             print(f"   Created: {mem['created_iso']}")
132 |             print(f"   Content: {mem['content']}...")
133 |             print()
134 | 
135 |     if dry_run:
136 |         print("=" * 80)
137 |         print("DRY RUN COMPLETE - No changes made")
138 |         print("=" * 80)
139 |         print(f"Would restore {len(matched)} timestamps")
140 |         print(f"Would preserve {len(unmatched)} new memories")
141 |         print("\nTo apply changes, run with --apply flag")
142 |         conn.close()
143 |         return
144 | 
145 |     # Confirm before proceeding
146 |     print("=" * 80)
147 |     print(f"⚠️  ABOUT TO RESTORE {len(matched)} TIMESTAMPS")
148 |     print("=" * 80)
149 |     response = input("Continue with restoration? [y/N]: ")
150 | 
151 |     if response.lower() != 'y':
152 |         print("Restoration cancelled")
153 |         conn.close()
154 |         return
155 | 
156 |     # Apply restorations
157 |     print("\nRestoring timestamps...")
158 |     restored_count = 0
159 |     failed_count = 0
160 | 
161 |     for mem in matched:
162 |         try:
163 |             content_hash = mem['hash']
164 |             clean_iso = mem['clean_iso']
165 | 
166 |             # Convert ISO to Unix timestamp
167 |             dt = datetime.fromisoformat(clean_iso.replace('Z', '+00:00'))
168 |             clean_unix = dt.timestamp()
169 | 
170 |             # Update database
171 |             cursor.execute('''
172 |                 UPDATE memories
173 |                 SET created_at = ?, created_at_iso = ?
174 |                 WHERE content_hash = ?
175 |             ''', (clean_unix, clean_iso, content_hash))
176 | 
177 |             restored_count += 1
178 | 
179 |             if restored_count % 100 == 0:
180 |                 print(f"  Progress: {restored_count}/{len(matched)} restored...")
181 |                 conn.commit()  # Commit in batches
182 | 
183 |         except Exception as e:
184 |             print(f"  Error restoring {content_hash[:16]}: {e}")
185 |             failed_count += 1
186 | 
187 |     # Final commit
188 |     conn.commit()
189 | 
190 |     # Verify results
191 |     cursor.execute('''
192 |         SELECT created_at_iso, COUNT(*) as count
193 |         FROM memories
194 |         GROUP BY DATE(created_at_iso)
195 |         ORDER BY DATE(created_at_iso) DESC
196 |         LIMIT 20
197 |     ''')
198 | 
199 |     print()
200 |     print("=" * 80)
201 |     print("RESTORATION COMPLETE")
202 |     print("=" * 80)
203 |     print(f"✅ Successfully restored: {restored_count}")
204 |     print(f"❌ Failed to restore: {failed_count}")
205 |     print(f"⏭️  Preserved (new memories): {len(unmatched)}")
206 |     print()
207 | 
208 |     # Show date distribution
209 |     print("=" * 80)
210 |     print("TIMESTAMP DISTRIBUTION (After Restoration):")
211 |     print("=" * 80)
212 | 
213 |     from collections import Counter
214 |     cursor.execute('SELECT created_at_iso FROM memories')
215 |     dates = Counter()
216 |     for row in cursor.fetchall():
217 |         date_str = row[0][:10] if row[0] else 'Unknown'
218 |         dates[date_str] += 1
219 | 
220 |     for date, count in dates.most_common(15):
221 |         print(f"  {date}: {count:4} memories")
222 | 
223 |     # Check corruption remaining
224 |     corruption_dates = {'2025-11-16', '2025-11-17', '2025-11-18'}
225 |     corrupted_remaining = sum(count for date, count in dates.items() if date in corruption_dates)
226 | 
227 |     print()
228 |     print(f"Corrupted dates remaining: {corrupted_remaining}")
229 |     print(f"Expected: ~250-400 (legitimately created Nov 16-18)")
230 | 
231 |     conn.close()
232 | 
233 |     if failed_count == 0 and corrupted_remaining < 500:
234 |         print("\n🎉 SUCCESS: Timestamps restored successfully!")
235 |     else:
236 |         print(f"\n⚠️  WARNING: Some issues occurred during restoration")
237 | 
238 | 
239 | if __name__ == "__main__":
240 |     dry_run = '--apply' not in sys.argv
241 | 
242 |     db_path = config.SQLITE_VEC_PATH
243 |     mapping_file = Path(__file__).parent.parent.parent / "clean_timestamp_mapping.json"
244 | 
245 |     if not mapping_file.exists():
246 |         print(f"❌ ERROR: Mapping file not found: {mapping_file}")
247 |         print("Run Phase 1 first to extract the clean timestamp mapping")
248 |         sys.exit(1)
249 | 
250 |     try:
251 |         restore_from_json(str(db_path), str(mapping_file), dry_run=dry_run)
252 |     except KeyboardInterrupt:
253 |         print("\n\nRestoration cancelled by user")
254 |         sys.exit(1)
255 |     except Exception as e:
256 |         print(f"\n❌ Restoration failed: {e}")
257 |         import traceback
258 |         traceback.print_exc()
259 |         sys.exit(1)
260 | 
```

--------------------------------------------------------------------------------
/scripts/migration/mcp-migration.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 | Enhanced migration script for MCP Memory Service.
 18 | This script handles migration of memories between different ChromaDB instances,
 19 | with support for both local and remote migrations.
 20 | """
 21 | import sys
 22 | import os
 23 | from dotenv import load_dotenv
 24 | from pathlib import Path
 25 | import chromadb
 26 | from chromadb import HttpClient, Settings
 27 | import json
 28 | import time
 29 | from chromadb.utils import embedding_functions
 30 | 
 31 | # Import our environment verifier
 32 | from verify_environment import EnvironmentVerifier
 33 | 
 34 | def verify_environment():
 35 |     """Verify the environment before proceeding with migration"""
 36 |     verifier = EnvironmentVerifier()
 37 |     verifier.run_verifications()
 38 |     if not verifier.print_results():
 39 |         print("\n⚠️  Environment verification failed! Migration cannot proceed.")
 40 |         sys.exit(1)
 41 |     print("\n✓ Environment verification passed! Proceeding with migration.")
 42 | 
 43 | # Load environment variables
 44 | load_dotenv()
 45 | 
 46 | def get_claude_desktop_chroma_path():
 47 |     """Get ChromaDB path from Claude Desktop config"""
 48 |     base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 49 |     config_path = os.path.join(base_path, 'claude_config', 'mcp-memory', 'chroma_db')
 50 |     print(f"Using ChromaDB path: {config_path}")
 51 |     return config_path
 52 | 
 53 | def migrate_memories(source_type, source_config, target_type, target_config):
 54 |     """
 55 |     Migrate memories between ChromaDB instances.
 56 |     
 57 |     Args:
 58 |         source_type: 'local' or 'remote'
 59 |         source_config: For local: path to ChromaDB, for remote: {'host': host, 'port': port}
 60 |         target_type: 'local' or 'remote'
 61 |         target_config: For local: path to ChromaDB, for remote: {'host': host, 'port': port}
 62 |     """
 63 |     print(f"Starting migration from {source_type} to {target_type}")
 64 |     
 65 |     try:
 66 |         # Set up embedding function
 67 |         embedding_function = embedding_functions.SentenceTransformerEmbeddingFunction(
 68 |             model_name='all-MiniLM-L6-v2'
 69 |         )
 70 |         
 71 |         # Connect to target ChromaDB
 72 |         if target_type == 'remote':
 73 |             target_client = HttpClient(
 74 |                 host=target_config['host'],
 75 |                 port=target_config['port']
 76 |             )
 77 |             print(f"Connected to remote ChromaDB at {target_config['host']}:{target_config['port']}")
 78 |         else:
 79 |             settings = Settings(
 80 |                 anonymized_telemetry=False,
 81 |                 allow_reset=True,
 82 |                 is_persistent=True,
 83 |                 persist_directory=target_config
 84 |             )
 85 |             target_client = chromadb.Client(settings)
 86 |             print(f"Connected to local ChromaDB at {target_config}")
 87 |         
 88 |         # Get or create collection for imported memories
 89 |         try:
 90 |             target_collection = target_client.get_collection(
 91 |                 name="mcp_imported_memories",
 92 |                 embedding_function=embedding_function
 93 |             )
 94 |             print("Found existing collection 'mcp_imported_memories' on target")
 95 |         except Exception:
 96 |             target_collection = target_client.create_collection(
 97 |                 name="mcp_imported_memories",
 98 |                 metadata={"hnsw:space": "cosine"},
 99 |                 embedding_function=embedding_function
100 |             )
101 |             print("Created new collection 'mcp_imported_memories' on target")
102 |         
103 |         # Connect to source ChromaDB
104 |         if source_type == 'remote':
105 |             source_client = HttpClient(
106 |                 host=source_config['host'],
107 |                 port=source_config['port']
108 |             )
109 |             print(f"Connected to remote ChromaDB at {source_config['host']}:{source_config['port']}")
110 |         else:
111 |             settings = Settings(
112 |                 anonymized_telemetry=False,
113 |                 allow_reset=True,
114 |                 is_persistent=True,
115 |                 persist_directory=source_config
116 |             )
117 |             source_client = chromadb.Client(settings)
118 |             print(f"Connected to local ChromaDB at {source_config}")
119 |         
120 |         # List collections
121 |         collections = source_client.list_collections()
122 |         print(f"Found {len(collections)} collections in source")
123 |         for coll in collections:
124 |             print(f"- {coll.name}")
125 |         
126 |         # Try to get the memory collection
127 |         try:
128 |             source_collection = source_client.get_collection(
129 |                 name="memory_collection",
130 |                 embedding_function=embedding_function
131 |             )
132 |             print("Found source memory collection")
133 |         except ValueError as e:
134 |             print(f"Error accessing source collection: {str(e)}")
135 |             return
136 |             
137 |         # Get all memories from source
138 |         print("Fetching source memories...")
139 |         results = source_collection.get()
140 |         
141 |         if not results["ids"]:
142 |             print("No memories found in source collection")
143 |             return
144 |             
145 |         print(f"Found {len(results['ids'])} memories to migrate")
146 |         
147 |         # Check for existing memories in target to avoid duplicates
148 |         target_existing = target_collection.get()
149 |         existing_ids = set(target_existing["ids"])
150 |         
151 |         # Filter out already migrated memories
152 |         new_memories = {
153 |             "ids": [],
154 |             "documents": [],
155 |             "metadatas": []
156 |         }
157 |         
158 |         for i, memory_id in enumerate(results["ids"]):
159 |             if memory_id not in existing_ids:
160 |                 new_memories["ids"].append(memory_id)
161 |                 new_memories["documents"].append(results["documents"][i])
162 |                 new_memories["metadatas"].append(results["metadatas"][i])
163 |         
164 |         if not new_memories["ids"]:
165 |             print("All memories are already migrated!")
166 |             return
167 |             
168 |         print(f"Found {len(new_memories['ids'])} new memories to migrate")
169 |         
170 |         # Import in batches of 10
171 |         batch_size = 10
172 |         for i in range(0, len(new_memories['ids']), batch_size):
173 |             batch_end = min(i + batch_size, len(new_memories['ids']))
174 |             
175 |             batch_ids = new_memories['ids'][i:batch_end]
176 |             batch_documents = new_memories['documents'][i:batch_end]
177 |             batch_metadatas = new_memories['metadatas'][i:batch_end]
178 |             
179 |             print(f"Migrating batch {i//batch_size + 1} ({len(batch_ids)} memories)...")
180 |             
181 |             target_collection.add(
182 |                 documents=batch_documents,
183 |                 metadatas=batch_metadatas,
184 |                 ids=batch_ids
185 |             )
186 |             
187 |             # Small delay between batches
188 |             time.sleep(1)
189 |         
190 |         print("\nMigration complete!")
191 |         
192 |         # Verify migration
193 |         target_results = target_collection.get()
194 |         print(f"Verification: {len(target_results['ids'])} total memories in target collection")
195 |         
196 |     except Exception as e:
197 |         print(f"Error during migration: {str(e)}")
198 |         print("Please ensure both ChromaDB instances are running and accessible")
199 | 
200 | if __name__ == "__main__":
201 |     # First verify the environment
202 |     verify_environment()
203 |     
204 |     # Example usage:
205 |     # Local to remote migration
206 |     migrate_memories(
207 |         source_type='local',
208 |         source_config=get_claude_desktop_chroma_path(),
209 |         target_type='remote',
210 |         target_config={'host': '16.171.169.46', 'port': 8000}
211 |     )
212 |     
213 |     # Remote to local migration
214 |     # migrate_memories(
215 |     #     source_type='remote',
216 |     #     source_config={'host': '16.171.169.46', 'port': 8000},
217 |     #     target_type='local',
218 |     #     target_config=get_claude_desktop_chroma_path()
219 |     # )
220 | 
```

--------------------------------------------------------------------------------
/scripts/quality/weekly_quality_review.sh:
--------------------------------------------------------------------------------

```bash
  1 | #!/bin/bash
  2 | # scripts/quality/weekly_quality_review.sh - Weekly code quality review
  3 | #
  4 | # Usage: bash scripts/quality/weekly_quality_review.sh [--create-issue]
  5 | #
  6 | # Features:
  7 | # - Run pyscn analysis
  8 | # - Compare to last week's metrics
  9 | # - Generate markdown trend report
 10 | # - Optionally create GitHub issue if health score dropped >5%
 11 | 
 12 | set -e
 13 | 
 14 | # Colors for output
 15 | RED='\033[0;31m'
 16 | YELLOW='\033[1;33m'
 17 | GREEN='\033[0;32m'
 18 | BLUE='\033[0;34m'
 19 | NC='\033[0m' # No Color
 20 | 
 21 | # Parse arguments
 22 | CREATE_ISSUE=false
 23 | if [ "$1" = "--create-issue" ]; then
 24 |     CREATE_ISSUE=true
 25 | fi
 26 | 
 27 | echo -e "${BLUE}=== Weekly Quality Review ===${NC}"
 28 | echo ""
 29 | 
 30 | # Run metrics tracking
 31 | echo "Running pyscn metrics tracking..."
 32 | if bash scripts/quality/track_pyscn_metrics.sh > /tmp/weekly_review.log 2>&1; then
 33 |     echo -e "${GREEN}✓${NC} Metrics tracking complete"
 34 | else
 35 |     echo -e "${RED}❌ Metrics tracking failed${NC}"
 36 |     cat /tmp/weekly_review.log
 37 |     exit 1
 38 | fi
 39 | 
 40 | # Extract current and previous metrics
 41 | CSV_FILE=".pyscn/history/metrics.csv"
 42 | 
 43 | if [ ! -f "$CSV_FILE" ] || [ $(wc -l < "$CSV_FILE") -lt 2 ]; then
 44 |     echo -e "${YELLOW}⚠️  Insufficient data for weekly review (need at least 1 previous run)${NC}"
 45 |     exit 0
 46 | fi
 47 | 
 48 | # Get current (last line) and previous (second to last) metrics
 49 | CURRENT_LINE=$(tail -1 "$CSV_FILE")
 50 | CURRENT_HEALTH=$(echo "$CURRENT_LINE" | cut -d',' -f3)
 51 | CURRENT_DATE=$(echo "$CURRENT_LINE" | cut -d',' -f2)
 52 | CURRENT_COMPLEXITY=$(echo "$CURRENT_LINE" | cut -d',' -f4)
 53 | CURRENT_DUPLICATION=$(echo "$CURRENT_LINE" | cut -d',' -f6)
 54 | 
 55 | # Find last week's metrics (7+ days ago)
 56 | SEVEN_DAYS_AGO=$(date -v-7d +%Y%m%d 2>/dev/null || date -d "7 days ago" +%Y%m%d)
 57 | PREV_LINE=$(awk -F',' -v cutoff="$SEVEN_DAYS_AGO" '$1 < cutoff {last=$0} END {print last}' "$CSV_FILE")
 58 | 
 59 | if [ -z "$PREV_LINE" ]; then
 60 |     # Fallback to most recent previous entry if no 7-day-old entry exists
 61 |     PREV_LINE=$(tail -2 "$CSV_FILE" | head -1)
 62 | fi
 63 | 
 64 | PREV_HEALTH=$(echo "$PREV_LINE" | cut -d',' -f3)
 65 | PREV_DATE=$(echo "$PREV_LINE" | cut -d',' -f2)
 66 | PREV_COMPLEXITY=$(echo "$PREV_LINE" | cut -d',' -f4)
 67 | PREV_DUPLICATION=$(echo "$PREV_LINE" | cut -d',' -f6)
 68 | 
 69 | # Calculate deltas
 70 | HEALTH_DELTA=$((CURRENT_HEALTH - PREV_HEALTH))
 71 | COMPLEXITY_DELTA=$((CURRENT_COMPLEXITY - PREV_COMPLEXITY))
 72 | DUPLICATION_DELTA=$((CURRENT_DUPLICATION - PREV_DUPLICATION))
 73 | 
 74 | echo ""
 75 | echo -e "${BLUE}=== Weekly Comparison ===${NC}"
 76 | echo "Period: $(echo "$PREV_DATE" | cut -d' ' -f1) → $(echo "$CURRENT_DATE" | cut -d' ' -f1)"
 77 | echo ""
 78 | echo "Health Score:"
 79 | echo "  Previous: $PREV_HEALTH/100"
 80 | echo "  Current:  $CURRENT_HEALTH/100"
 81 | echo "  Change:   $([ $HEALTH_DELTA -ge 0 ] && echo "+")$HEALTH_DELTA points"
 82 | echo ""
 83 | 
 84 | # Determine overall trend
 85 | TREND_EMOJI="➡️"
 86 | TREND_TEXT="Stable"
 87 | 
 88 | if [ $HEALTH_DELTA -gt 5 ]; then
 89 |     TREND_EMOJI="📈"
 90 |     TREND_TEXT="Improving"
 91 | elif [ $HEALTH_DELTA -lt -5 ]; then
 92 |     TREND_EMOJI="📉"
 93 |     TREND_TEXT="Declining"
 94 | fi
 95 | 
 96 | echo -e "${TREND_EMOJI} Trend: ${TREND_TEXT}"
 97 | echo ""
 98 | 
 99 | # Generate markdown report
100 | REPORT_FILE="docs/development/quality-review-$(date +%Y%m%d).md"
101 | mkdir -p docs/development
102 | 
103 | cat > "$REPORT_FILE" <<EOF
104 | # Weekly Quality Review - $(date +"%B %d, %Y")
105 | 
106 | ## Summary
107 | 
108 | **Overall Trend:** ${TREND_EMOJI} ${TREND_TEXT}
109 | 
110 | | Metric | Previous | Current | Change |
111 | |--------|----------|---------|--------|
112 | | Health Score | $PREV_HEALTH/100 | $CURRENT_HEALTH/100 | $([ $HEALTH_DELTA -ge 0 ] && echo "+")$HEALTH_DELTA |
113 | | Complexity | $PREV_COMPLEXITY/100 | $CURRENT_COMPLEXITY/100 | $([ $COMPLEXITY_DELTA -ge 0 ] && echo "+")$COMPLEXITY_DELTA |
114 | | Duplication | $PREV_DUPLICATION/100 | $CURRENT_DUPLICATION/100 | $([ $DUPLICATION_DELTA -ge 0 ] && echo "+")$DUPLICATION_DELTA |
115 | 
116 | ## Analysis Period
117 | 
118 | - **Start**: $(echo "$PREV_DATE" | cut -d' ' -f1)
119 | - **End**: $(echo "$CURRENT_DATE" | cut -d' ' -f1)
120 | - **Duration**: ~7 days
121 | 
122 | ## Status
123 | 
124 | EOF
125 | 
126 | if [ $CURRENT_HEALTH -lt 50 ]; then
127 |     cat >> "$REPORT_FILE" <<EOF
128 | ### 🔴 Critical - Release Blocker
129 | 
130 | Health score below 50 requires immediate action:
131 | - Cannot merge PRs until resolved
132 | - Focus on refactoring high-complexity functions
133 | - Remove dead code
134 | - Address duplication
135 | 
136 | **Action Items:**
137 | 1. Review full pyscn report: \`.pyscn/reports/analyze_*.html\`
138 | 2. Create refactoring tasks for complexity >10 functions
139 | 3. Schedule refactoring sprint (target: 2 weeks)
140 | 4. Track progress in issue #240
141 | 
142 | EOF
143 | elif [ $CURRENT_HEALTH -lt 70 ]; then
144 |     cat >> "$REPORT_FILE" <<EOF
145 | ### ⚠️  Action Required
146 | 
147 | Health score 50-69 indicates technical debt accumulation:
148 | - Plan refactoring sprint within 2 weeks
149 | - Review high-complexity functions
150 | - Track improvement progress
151 | 
152 | **Recommended Actions:**
153 | 1. Identify top 5 complexity hotspots
154 | 2. Create project board for tracking
155 | 3. Allocate 20% of sprint capacity to quality improvements
156 | 
157 | EOF
158 | else
159 |     cat >> "$REPORT_FILE" <<EOF
160 | ### ✅ Acceptable
161 | 
162 | Health score ≥70 indicates good code quality:
163 | - Continue current development practices
164 | - Monitor trends for regressions
165 | - Address new issues proactively
166 | 
167 | **Maintenance:**
168 | - Monthly quality reviews
169 | - Track complexity trends
170 | - Keep health score above 70
171 | 
172 | EOF
173 | fi
174 | 
175 | # Add trend observations
176 | cat >> "$REPORT_FILE" <<EOF
177 | ## Observations
178 | 
179 | EOF
180 | 
181 | if [ $HEALTH_DELTA -gt 5 ]; then
182 |     cat >> "$REPORT_FILE" <<EOF
183 | - ✅ **Health score improved by $HEALTH_DELTA points** - Great progress on code quality
184 | EOF
185 | elif [ $HEALTH_DELTA -lt -5 ]; then
186 |     cat >> "$REPORT_FILE" <<EOF
187 | - ⚠️  **Health score declined by ${HEALTH_DELTA#-} points** - Quality regression detected
188 | EOF
189 | fi
190 | 
191 | if [ $COMPLEXITY_DELTA -gt 0 ]; then
192 |     cat >> "$REPORT_FILE" <<EOF
193 | - ⚠️  Complexity score decreased - New complex code introduced
194 | EOF
195 | elif [ $COMPLEXITY_DELTA -lt 0 ]; then
196 |     cat >> "$REPORT_FILE" <<EOF
197 | - ✅ Complexity score improved - Refactoring efforts paying off
198 | EOF
199 | fi
200 | 
201 | if [ $DUPLICATION_DELTA -lt 0 ]; then
202 |     cat >> "$REPORT_FILE" <<EOF
203 | - ⚠️  Code duplication increased - Review for consolidation opportunities
204 | EOF
205 | elif [ $DUPLICATION_DELTA -gt 0 ]; then
206 |     cat >> "$REPORT_FILE" <<EOF
207 | - ✅ Code duplication reduced - Good refactoring work
208 | EOF
209 | fi
210 | 
211 | cat >> "$REPORT_FILE" <<EOF
212 | 
213 | ## Next Steps
214 | 
215 | 1. Review detailed pyscn report for specific issues
216 | 2. Update project board with quality improvement tasks
217 | 3. Schedule next weekly review for $(date -v+7d +"%B %d, %Y" 2>/dev/null || date -d "7 days" +"%B %d, %Y")
218 | 
219 | ## Resources
220 | 
221 | - [Full pyscn Report](.pyscn/reports/)
222 | - [Metrics History](.pyscn/history/metrics.csv)
223 | - [Code Quality Workflow](docs/development/code-quality-workflow.md)
224 | - [Issue #240](https://github.com/doobidoo/mcp-memory-service/issues/240) - Quality improvements tracking
225 | 
226 | EOF
227 | 
228 | echo -e "${GREEN}✓${NC} Report generated: $REPORT_FILE"
229 | echo ""
230 | 
231 | # Create GitHub issue if significant regression and flag enabled
232 | if [ "$CREATE_ISSUE" = true ] && [ $HEALTH_DELTA -lt -5 ]; then
233 |     if command -v gh &> /dev/null; then
234 |         echo -e "${YELLOW}Creating GitHub issue for quality regression...${NC}"
235 | 
236 |         ISSUE_BODY="## Quality Regression Detected
237 | 
238 | Weekly quality review detected a significant health score decline:
239 | 
240 | **Health Score Change:** $PREV_HEALTH → $CURRENT_HEALTH (${HEALTH_DELTA} points)
241 | 
242 | ### Details
243 | 
244 | $(cat "$REPORT_FILE" | sed -n '/## Summary/,/## Next Steps/p' | head -n -1)
245 | 
246 | ### Action Required
247 | 
248 | 1. Review full weekly report: [\`$REPORT_FILE\`]($REPORT_FILE)
249 | 2. Investigate recent changes: \`git log --since='$PREV_DATE'\`
250 | 3. Prioritize quality improvements in next sprint
251 | 
252 | ### Related
253 | 
254 | - Issue #240 - Code Quality Improvements
255 | - [pyscn Report](.pyscn/reports/)
256 | "
257 | 
258 |         gh issue create \
259 |             --title "Weekly Quality Review: Health Score Regression (${HEALTH_DELTA} points)" \
260 |             --body "$ISSUE_BODY" \
261 |             --label "technical-debt,quality"
262 | 
263 |         echo -e "${GREEN}✓${NC} GitHub issue created"
264 |     else
265 |         echo -e "${YELLOW}⚠️  gh CLI not found, skipping issue creation${NC}"
266 |     fi
267 | fi
268 | 
269 | echo ""
270 | echo -e "${BLUE}=== Summary ===${NC}"
271 | echo "Review Period: $(echo "$PREV_DATE" | cut -d' ' -f1) → $(echo "$CURRENT_DATE" | cut -d' ' -f1)"
272 | echo "Health Score: $PREV_HEALTH → $CURRENT_HEALTH ($([ $HEALTH_DELTA -ge 0 ] && echo "+")$HEALTH_DELTA)"
273 | echo "Trend: ${TREND_EMOJI} ${TREND_TEXT}"
274 | echo ""
275 | echo "Report: $REPORT_FILE"
276 | echo ""
277 | echo -e "${GREEN}✓${NC} Weekly review complete"
278 | exit 0
279 | 
```

--------------------------------------------------------------------------------
/src/mcp_memory_service/web/api/health.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 | Health check endpoints for the HTTP interface.
 17 | """
 18 | 
 19 | import time
 20 | import platform
 21 | import psutil
 22 | from datetime import datetime, timezone
 23 | from typing import Dict, Any, TYPE_CHECKING
 24 | 
 25 | from fastapi import APIRouter, Depends
 26 | from pydantic import BaseModel
 27 | 
 28 | from ...storage.base import MemoryStorage
 29 | from ..dependencies import get_storage
 30 | from ... import __version__
 31 | from ...config import OAUTH_ENABLED
 32 | 
 33 | # OAuth authentication imports (conditional)
 34 | if OAUTH_ENABLED or TYPE_CHECKING:
 35 |     from ..oauth.middleware import require_read_access, AuthenticationResult
 36 | else:
 37 |     # Provide type stubs when OAuth is disabled
 38 |     AuthenticationResult = None
 39 |     require_read_access = None
 40 | 
 41 | router = APIRouter()
 42 | 
 43 | 
 44 | class HealthResponse(BaseModel):
 45 |     """Basic health check response."""
 46 |     status: str
 47 |     version: str
 48 |     timestamp: str
 49 |     uptime_seconds: float
 50 | 
 51 | 
 52 | class DetailedHealthResponse(BaseModel):
 53 |     """Detailed health check response."""
 54 |     status: str
 55 |     version: str
 56 |     timestamp: str
 57 |     uptime_seconds: float
 58 |     storage: Dict[str, Any]
 59 |     system: Dict[str, Any]
 60 |     performance: Dict[str, Any]
 61 |     statistics: Dict[str, Any] = None
 62 | 
 63 | 
 64 | # Track startup time for uptime calculation
 65 | _startup_time = time.time()
 66 | 
 67 | 
 68 | @router.get("/health", response_model=HealthResponse)
 69 | async def health_check():
 70 |     """Basic health check endpoint."""
 71 |     return HealthResponse(
 72 |         status="healthy",
 73 |         version=__version__,
 74 |         timestamp=datetime.now(timezone.utc).isoformat(),
 75 |         uptime_seconds=time.time() - _startup_time
 76 |     )
 77 | 
 78 | 
 79 | @router.get("/health/detailed", response_model=DetailedHealthResponse)
 80 | async def detailed_health_check(
 81 |     storage: MemoryStorage = Depends(get_storage),
 82 |     user: AuthenticationResult = Depends(require_read_access) if OAUTH_ENABLED else None
 83 | ):
 84 |     """Detailed health check with system and storage information."""
 85 |     
 86 |     # Get system information
 87 |     memory_info = psutil.virtual_memory()
 88 |     disk_info = psutil.disk_usage('/')
 89 |     
 90 |     system_info = {
 91 |         "platform": platform.system(),
 92 |         "platform_version": platform.version(),
 93 |         "python_version": platform.python_version(),
 94 |         "cpu_count": psutil.cpu_count(),
 95 |         "memory_total_gb": round(memory_info.total / (1024**3), 2),
 96 |         "memory_available_gb": round(memory_info.available / (1024**3), 2),
 97 |         "memory_percent": memory_info.percent,
 98 |         "disk_total_gb": round(disk_info.total / (1024**3), 2),
 99 |         "disk_free_gb": round(disk_info.free / (1024**3), 2),
100 |         "disk_percent": round((disk_info.used / disk_info.total) * 100, 2)
101 |     }
102 |     
103 |     # Get storage information (support all storage backends)
104 |     try:
105 |         # Get statistics from storage using universal get_stats() method
106 |         if hasattr(storage, 'get_stats') and callable(getattr(storage, 'get_stats')):
107 |             # All storage backends now have async get_stats()
108 |             stats = await storage.get_stats()
109 |         else:
110 |             stats = {"error": "Storage backend doesn't support statistics"}
111 | 
112 |         if "error" not in stats:
113 |             # Detect backend type from storage class or stats
114 |             backend_name = stats.get("storage_backend", storage.__class__.__name__)
115 |             if "sqlite" in backend_name.lower():
116 |                 backend_type = "sqlite-vec"
117 |             elif "cloudflare" in backend_name.lower():
118 |                 backend_type = "cloudflare"
119 |             elif "hybrid" in backend_name.lower():
120 |                 backend_type = "hybrid"
121 |             else:
122 |                 backend_type = backend_name
123 | 
124 |             storage_info = {
125 |                 "backend": backend_type,
126 |                 "status": "connected",
127 |                 "accessible": True
128 |             }
129 | 
130 |             # Add backend-specific information if available
131 |             if hasattr(storage, 'db_path'):
132 |                 storage_info["database_path"] = storage.db_path
133 |             if hasattr(storage, 'embedding_model_name'):
134 |                 storage_info["embedding_model"] = storage.embedding_model_name
135 | 
136 |             # Add sync status for hybrid backend
137 |             if backend_type == "hybrid" and hasattr(storage, 'get_sync_status'):
138 |                 try:
139 |                     sync_status = await storage.get_sync_status()
140 |                     storage_info["sync_status"] = {
141 |                         "is_running": sync_status.get('is_running', False),
142 |                         "last_sync_time": sync_status.get('last_sync_time', 0),
143 |                         "pending_operations": sync_status.get('pending_operations', 0),
144 |                         "operations_processed": sync_status.get('operations_processed', 0),
145 |                         "operations_failed": sync_status.get('operations_failed', 0),
146 |                         "time_since_last_sync": time.time() - sync_status.get('last_sync_time', 0) if sync_status.get('last_sync_time', 0) > 0 else 0
147 |                     }
148 |                 except Exception as sync_err:
149 |                     storage_info["sync_status"] = {"error": str(sync_err)}
150 | 
151 |             # Merge all stats
152 |             storage_info.update(stats)
153 |         else:
154 |             storage_info = {
155 |                 "backend": storage.__class__.__name__,
156 |                 "status": "error",
157 |                 "accessible": False,
158 |                 "error": stats["error"]
159 |             }
160 | 
161 |     except Exception as e:
162 |         storage_info = {
163 |             "backend": storage.__class__.__name__ if hasattr(storage, '__class__') else "unknown",
164 |             "status": "error",
165 |             "error": str(e)
166 |         }
167 |     
168 |     # Performance metrics (basic for now)
169 |     performance_info = {
170 |         "uptime_seconds": time.time() - _startup_time,
171 |         "uptime_formatted": format_uptime(time.time() - _startup_time)
172 |     }
173 |     
174 |     # Extract statistics for separate field if available
175 |     statistics = {
176 |         "total_memories": storage_info.get("total_memories", 0),
177 |         "unique_tags": storage_info.get("unique_tags", 0),
178 |         "memories_this_week": storage_info.get("memories_this_week", 0),
179 |         "database_size_mb": storage_info.get("database_size_mb", 0),
180 |         "backend": storage_info.get("backend", "sqlite-vec")
181 |     }
182 |     
183 |     return DetailedHealthResponse(
184 |         status="healthy",
185 |         version=__version__,
186 |         timestamp=datetime.now(timezone.utc).isoformat(),
187 |         uptime_seconds=time.time() - _startup_time,
188 |         storage=storage_info,
189 |         system=system_info,
190 |         performance=performance_info,
191 |         statistics=statistics
192 |     )
193 | 
194 | 
195 | @router.get("/health/sync-status")
196 | async def sync_status(
197 |     storage: MemoryStorage = Depends(get_storage),
198 |     user: AuthenticationResult = Depends(require_read_access) if OAUTH_ENABLED else None
199 | ):
200 |     """Get current initial sync status for hybrid storage."""
201 | 
202 |     # Check if this is a hybrid storage that supports sync status
203 |     if hasattr(storage, 'get_initial_sync_status'):
204 |         sync_status = storage.get_initial_sync_status()
205 |         return {
206 |             "sync_supported": True,
207 |             "status": sync_status
208 |         }
209 |     else:
210 |         return {
211 |             "sync_supported": False,
212 |             "status": {
213 |                 "in_progress": False,
214 |                 "total": 0,
215 |                 "completed": 0,
216 |                 "finished": True,
217 |                 "progress_percentage": 100
218 |             }
219 |         }
220 | 
221 | 
222 | def format_uptime(seconds: float) -> str:
223 |     """Format uptime in human-readable format."""
224 |     if seconds < 60:
225 |         return f"{seconds:.1f} seconds"
226 |     elif seconds < 3600:
227 |         return f"{seconds/60:.1f} minutes"
228 |     elif seconds < 86400:
229 |         return f"{seconds/3600:.1f} hours"
230 |     else:
231 |         return f"{seconds/86400:.1f} days"
```

--------------------------------------------------------------------------------
/scripts/migration/migrate_tags.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 | # scripts/migrate_tags.py
 16 | # python scripts/validate_memories.py --db-path /path/to/your/chroma_db
 17 | 
 18 | import asyncio
 19 | import json
 20 | import logging
 21 | from datetime import datetime
 22 | from pathlib import Path
 23 | from mcp_memory_service.storage.chroma import ChromaMemoryStorage
 24 | import argparse 
 25 | 
 26 | logger = logging.getLogger(__name__)
 27 | 
 28 | async def analyze_tag_formats(metadatas):
 29 |     """Analyze the current tag formats in the database"""
 30 |     formats = {
 31 |         "json_string": 0,
 32 |         "raw_list": 0,
 33 |         "comma_string": 0,
 34 |         "empty": 0,
 35 |         "invalid": 0
 36 |     }
 37 |     
 38 |     for meta in metadatas:
 39 |         tags = meta.get("tags")
 40 |         if tags is None:
 41 |             formats["empty"] += 1
 42 |             continue
 43 |             
 44 |         if isinstance(tags, list):
 45 |             formats["raw_list"] += 1
 46 |         elif isinstance(tags, str):
 47 |             try:
 48 |                 parsed = json.loads(tags)
 49 |                 if isinstance(parsed, list):
 50 |                     formats["json_string"] += 1
 51 |                 else:
 52 |                     formats["invalid"] += 1
 53 |             except json.JSONDecodeError:
 54 |                 if "," in tags:
 55 |                     formats["comma_string"] += 1
 56 |                 else:
 57 |                     formats["invalid"] += 1
 58 |         else:
 59 |             formats["invalid"] += 1
 60 |             
 61 |     return formats
 62 | 
 63 | async def find_invalid_tags(metadatas):
 64 |     """Find any invalid tag formats"""
 65 |     invalid_entries = []
 66 |     
 67 |     for i, meta in enumerate(metadatas):
 68 |         tags = meta.get("tags")
 69 |         if tags is None:
 70 |             continue
 71 |             
 72 |         try:
 73 |             if isinstance(tags, str):
 74 |                 json.loads(tags)
 75 |         except json.JSONDecodeError:
 76 |             invalid_entries.append({
 77 |                 "memory_id": meta.get("content_hash", f"index_{i}"),
 78 |                 "tags": tags
 79 |             })
 80 |             
 81 |     return invalid_entries
 82 | 
 83 | async def backup_memories(storage):
 84 |     """Create a backup of all memories"""
 85 |     results = storage.collection.get(include=["metadatas", "documents"])
 86 |     
 87 |     backup_data = {
 88 |         "timestamp": datetime.now().isoformat(),
 89 |         "memories": [{
 90 |             "id": results["ids"][i],
 91 |             "content": results["documents"][i],
 92 |             "metadata": results["metadatas"][i]
 93 |         } for i in range(len(results["ids"]))]
 94 |     }
 95 |     
 96 |     backup_path = Path("backups")
 97 |     backup_path.mkdir(exist_ok=True)
 98 |     
 99 |     backup_file = backup_path / f"memory_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
100 |     with open(backup_file, 'w') as f:
101 |         json.dump(backup_data, f)
102 |     
103 |     return backup_file
104 | 
105 | async def validate_current_state(storage):
106 |     """Validate the current state of the database"""
107 |     results = storage.collection.get(include=["metadatas"])
108 |     return {
109 |         "total_memories": len(results["ids"]),
110 |         "tag_formats": await analyze_tag_formats(results["metadatas"]),
111 |         "invalid_tags": await find_invalid_tags(results["metadatas"])
112 |     }
113 | 
114 | async def migrate_tags(storage):
115 |     """Perform the tag migration"""
116 |     results = storage.collection.get(include=["metadatas", "documents"])
117 |     
118 |     migrated_count = 0
119 |     error_count = 0
120 |     
121 |     for i, meta in enumerate(results["metadatas"]):
122 |         try:
123 |             # Extract current tags
124 |             current_tags = meta.get("tags", "[]")
125 |             
126 |             # Normalize to list format
127 |             if isinstance(current_tags, str):
128 |                 try:
129 |                     # Try parsing as JSON first
130 |                     tags = json.loads(current_tags)
131 |                     if isinstance(tags, str):
132 |                         tags = [t.strip() for t in tags.split(",")]
133 |                     elif isinstance(tags, list):
134 |                         tags = [str(t).strip() for t in tags]
135 |                     else:
136 |                         tags = []
137 |                 except json.JSONDecodeError:
138 |                     # Handle as comma-separated string
139 |                     tags = [t.strip() for t in current_tags.split(",")]
140 |             elif isinstance(current_tags, list):
141 |                 tags = [str(t).strip() for t in current_tags]
142 |             else:
143 |                 tags = []
144 |             
145 |             # Update with normalized format
146 |             new_meta = meta.copy()
147 |             new_meta["tags"] = json.dumps(tags)
148 |             
149 |             # Update memory
150 |             storage.collection.update(
151 |                 ids=[results["ids"][i]],
152 |                 metadatas=[new_meta]
153 |             )
154 |             
155 |             migrated_count += 1
156 |             
157 |         except Exception as e:
158 |             error_count += 1
159 |             logger.error(f"Error migrating memory {results['ids'][i]}: {str(e)}")
160 |             
161 |     return migrated_count, error_count
162 | 
163 | async def verify_migration(storage):
164 |     """Verify the migration was successful"""
165 |     results = storage.collection.get(include=["metadatas"])
166 |     
167 |     verification = {
168 |         "total_memories": len(results["ids"]),
169 |         "tag_formats": await analyze_tag_formats(results["metadatas"]),
170 |         "invalid_tags": await find_invalid_tags(results["metadatas"])
171 |     }
172 |     
173 |     return verification
174 | 
175 | async def rollback_migration(storage, backup_file):
176 |     """Rollback to the backup if needed"""
177 |     with open(backup_file, 'r') as f:
178 |         backup = json.load(f)
179 |         
180 |     for memory in backup["memories"]:
181 |         storage.collection.update(
182 |             ids=[memory["id"]],
183 |             metadatas=[memory["metadata"]],
184 |             documents=[memory["content"]]
185 |         )
186 | 
187 | async def main():
188 |     # Configure logging
189 |     log_level = os.getenv('LOG_LEVEL', 'ERROR').upper()
190 |     logging.basicConfig(
191 |         level=getattr(logging, log_level, logging.ERROR),
192 |         format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
193 |         stream=sys.stderr
194 |     )
195 |     
196 |     # Initialize storage
197 |     # storage = ChromaMemoryStorage("path/to/your/db")
198 |     
199 |     # Parse command line arguments
200 |     parser = argparse.ArgumentParser(description='Validate memory data tags')
201 |     parser.add_argument('--db-path', required=True, help='Path to ChromaDB database')
202 |     args = parser.parse_args()
203 |     
204 |     # Initialize storage with provided path
205 |     logger.info(f"Connecting to database at: {args.db_path}")
206 |     storage = ChromaMemoryStorage(args.db_path)
207 | 
208 | 
209 |     # 1. Create backup
210 |     logger.info("Creating backup...")
211 |     backup_file = await backup_memories(storage)
212 |     logger.info(f"Backup created at: {backup_file}")
213 |     
214 |     # 2. Validate current state
215 |     logger.info("Validating current state...")
216 |     current_state = await validate_current_state(storage)
217 |     logger.info("\nCurrent state:")
218 |     logger.info(json.dumps(current_state, indent=2))
219 |     
220 |     # 3. Confirm migration
221 |     proceed = input("\nProceed with migration? (yes/no): ")
222 |     if proceed.lower() == 'yes':
223 |         # 4. Run migration
224 |         logger.info("Running migration...")
225 |         migrated_count, error_count = await migrate_tags(storage)
226 |         logger.info(f"Migration completed. Migrated: {migrated_count}, Errors: {error_count}")
227 |         
228 |         # 5. Verify migration
229 |         logger.info("Verifying migration...")
230 |         verification = await verify_migration(storage)
231 |         logger.info("\nMigration verification:")
232 |         logger.info(json.dumps(verification, indent=2))
233 |         
234 |         # 6. Check if rollback needed
235 |         if error_count > 0:
236 |             rollback = input("\nErrors detected. Rollback to backup? (yes/no): ")
237 |             if rollback.lower() == 'yes':
238 |                 logger.info("Rolling back...")
239 |                 await rollback_migration(storage, backup_file)
240 |                 logger.info("Rollback completed")
241 |     else:
242 |         logger.info("Migration cancelled")
243 | 
244 | if __name__ == "__main__":
245 |     asyncio.run(main())
246 | 
```

--------------------------------------------------------------------------------
/tests/integration/test_oauth_flow.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python3
  2 | """
  3 | OAuth 2.1 Dynamic Client Registration integration test.
  4 | 
  5 | Tests the OAuth endpoints for full flow functionality from client registration
  6 | through token acquisition and API access.
  7 | """
  8 | 
  9 | import asyncio
 10 | import json
 11 | import sys
 12 | from typing import Optional
 13 | 
 14 | import httpx
 15 | 
 16 | 
 17 | async def test_oauth_endpoints(base_url: str = "http://localhost:8000") -> bool:
 18 |     """
 19 |     Test OAuth 2.1 endpoints for basic functionality.
 20 | 
 21 |     Returns:
 22 |         True if all tests pass, False otherwise
 23 |     """
 24 |     print(f"Testing OAuth endpoints at {base_url}")
 25 |     print("=" * 50)
 26 | 
 27 |     async with httpx.AsyncClient() as client:
 28 |         try:
 29 |             # Test 1: OAuth Authorization Server Metadata
 30 |             print("1. Testing OAuth Authorization Server Metadata...")
 31 |             response = await client.get(f"{base_url}/.well-known/oauth-authorization-server/mcp")
 32 | 
 33 |             if response.status_code != 200:
 34 |                 print(f"   ❌ Failed: {response.status_code}")
 35 |                 return False
 36 | 
 37 |             metadata = response.json()
 38 |             required_fields = ["issuer", "authorization_endpoint", "token_endpoint", "registration_endpoint"]
 39 | 
 40 |             for field in required_fields:
 41 |                 if field not in metadata:
 42 |                     print(f"   ❌ Missing required field: {field}")
 43 |                     return False
 44 | 
 45 |             print(f"   ✅ Metadata endpoint working")
 46 |             print(f"   📋 Issuer: {metadata.get('issuer')}")
 47 | 
 48 |             # Test 2: Client Registration
 49 |             print("\n2. Testing Dynamic Client Registration...")
 50 | 
 51 |             registration_data = {
 52 |                 "client_name": "Test Client",
 53 |                 "redirect_uris": ["https://example.com/callback"],
 54 |                 "grant_types": ["authorization_code"],
 55 |                 "response_types": ["code"]
 56 |             }
 57 | 
 58 |             response = await client.post(
 59 |                 f"{base_url}/oauth/register",
 60 |                 json=registration_data
 61 |             )
 62 | 
 63 |             if response.status_code != 201:
 64 |                 print(f"   ❌ Registration failed: {response.status_code}")
 65 |                 print(f"   Response: {response.text}")
 66 |                 return False
 67 | 
 68 |             client_info = response.json()
 69 |             client_id = client_info.get("client_id")
 70 |             client_secret = client_info.get("client_secret")
 71 | 
 72 |             if not client_id or not client_secret:
 73 |                 print(f"   ❌ Missing client credentials in response")
 74 |                 return False
 75 | 
 76 |             print(f"   ✅ Client registration successful")
 77 |             print(f"   📋 Client ID: {client_id}")
 78 | 
 79 |             # Test 3: Authorization Endpoint (expect redirect)
 80 |             print("\n3. Testing Authorization Endpoint...")
 81 | 
 82 |             auth_url = f"{base_url}/oauth/authorize"
 83 |             auth_params = {
 84 |                 "response_type": "code",
 85 |                 "client_id": client_id,
 86 |                 "redirect_uri": "https://example.com/callback",
 87 |                 "state": "test_state_123"
 88 |             }
 89 | 
 90 |             response = await client.get(auth_url, params=auth_params, follow_redirects=False)
 91 | 
 92 |             if response.status_code not in [302, 307]:
 93 |                 print(f"   ❌ Authorization failed: {response.status_code}")
 94 |                 print(f"   Response: {response.text}")
 95 |                 return False
 96 | 
 97 |             location = response.headers.get("location", "")
 98 |             if "code=" not in location or "state=test_state_123" not in location:
 99 |                 print(f"   ❌ Invalid redirect: {location}")
100 |                 return False
101 | 
102 |             print(f"   ✅ Authorization endpoint working")
103 |             print(f"   📋 Redirect URL: {location[:100]}...")
104 | 
105 |             # Extract authorization code from redirect
106 |             auth_code = None
107 |             for param in location.split("?")[1].split("&"):
108 |                 if param.startswith("code="):
109 |                     auth_code = param.split("=")[1]
110 |                     break
111 | 
112 |             if not auth_code:
113 |                 print(f"   ❌ No authorization code in redirect")
114 |                 return False
115 | 
116 |             # Test 4: Token Endpoint
117 |             print("\n4. Testing Token Endpoint...")
118 | 
119 |             token_data = {
120 |                 "grant_type": "authorization_code",
121 |                 "code": auth_code,
122 |                 "redirect_uri": "https://example.com/callback",
123 |                 "client_id": client_id,
124 |                 "client_secret": client_secret
125 |             }
126 | 
127 |             response = await client.post(
128 |                 f"{base_url}/oauth/token",
129 |                 data=token_data,
130 |                 headers={"Content-Type": "application/x-www-form-urlencoded"}
131 |             )
132 | 
133 |             if response.status_code != 200:
134 |                 print(f"   ❌ Token request failed: {response.status_code}")
135 |                 print(f"   Response: {response.text}")
136 |                 return False
137 | 
138 |             token_response = response.json()
139 |             access_token = token_response.get("access_token")
140 | 
141 |             if not access_token:
142 |                 print(f"   ❌ No access token in response")
143 |                 return False
144 | 
145 |             print(f"   ✅ Token endpoint working")
146 |             print(f"   📋 Token type: {token_response.get('token_type')}")
147 |             print(f"   📋 Expires in: {token_response.get('expires_in')} seconds")
148 | 
149 |             # Test 5: Protected Resource Access
150 |             print("\n5. Testing Protected API Endpoints...")
151 | 
152 |             headers = {"Authorization": f"Bearer {access_token}"}
153 | 
154 |             # Test health endpoint (should be public, no auth required)
155 |             response = await client.get(f"{base_url}/api/health")
156 |             if response.status_code == 200:
157 |                 print(f"   ✅ Public health endpoint accessible")
158 |             else:
159 |                 print(f"   ❌ Health endpoint failed: {response.status_code}")
160 | 
161 |             # Test protected memories endpoint (requires read access)
162 |             response = await client.get(f"{base_url}/api/memories", headers=headers)
163 |             if response.status_code == 200:
164 |                 print(f"   ✅ Protected memories endpoint accessible with Bearer token")
165 |             else:
166 |                 print(f"   ❌ Protected memories endpoint failed: {response.status_code}")
167 | 
168 |             # Test protected search endpoint (requires read access)
169 |             search_data = {"query": "test search", "n_results": 5}
170 |             response = await client.post(f"{base_url}/api/search", json=search_data, headers=headers)
171 |             if response.status_code in [200, 404]:  # 404 is OK if no memories exist
172 |                 print(f"   ✅ Protected search endpoint accessible with Bearer token")
173 |             else:
174 |                 print(f"   ❌ Protected search endpoint failed: {response.status_code}")
175 | 
176 |             # Test accessing protected endpoint without token (should fail)
177 |             response = await client.get(f"{base_url}/api/memories")
178 |             if response.status_code == 401:
179 |                 print(f"   ✅ Protected endpoint correctly rejects unauthenticated requests")
180 |             else:
181 |                 print(f"   ⚠️  Protected endpoint security test inconclusive: {response.status_code}")
182 | 
183 |             print("\n" + "=" * 50)
184 |             print("🎉 All OAuth 2.1 tests passed!")
185 |             print("✅ Ready for Claude Code HTTP transport integration")
186 |             print("✅ API endpoints properly protected with OAuth authentication")
187 |             return True
188 | 
189 |         except Exception as e:
190 |             print(f"\n❌ Test failed with exception: {e}")
191 |             return False
192 | 
193 | 
194 | async def main():
195 |     """Main test function."""
196 |     if len(sys.argv) > 1:
197 |         base_url = sys.argv[1]
198 |     else:
199 |         base_url = "http://localhost:8000"
200 | 
201 |     print("OAuth 2.1 Dynamic Client Registration Test")
202 |     print("==========================================")
203 |     print(f"Target: {base_url}")
204 |     print()
205 |     print("Make sure the MCP Memory Service is running with OAuth enabled:")
206 |     print("  export MCP_OAUTH_ENABLED=true")
207 |     print("  uv run memory server --http")
208 |     print()
209 | 
210 |     success = await test_oauth_endpoints(base_url)
211 | 
212 |     if success:
213 |         print("\n🚀 OAuth implementation is ready!")
214 |         sys.exit(0)
215 |     else:
216 |         print("\n💥 OAuth tests failed - check implementation")
217 |         sys.exit(1)
218 | 
219 | 
220 | if __name__ == "__main__":
221 |     asyncio.run(main())
```

--------------------------------------------------------------------------------
/docs/api/memory-metadata-api.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Memory Metadata Enhancement API
  2 | 
  3 | ## Overview
  4 | 
  5 | The Memory Metadata Enhancement API provides efficient memory metadata updates without requiring complete memory recreation. This addresses the core limitation identified in Issue #10 where updating memory metadata required deleting and recreating entire memory entries.
  6 | 
  7 | ## API Method
  8 | 
  9 | ### `update_memory_metadata`
 10 | 
 11 | Updates memory metadata while preserving the original memory content, embeddings, and optionally timestamps.
 12 | 
 13 | **Signature:**
 14 | ```python
 15 | async def update_memory_metadata(
 16 |     content_hash: str, 
 17 |     updates: Dict[str, Any], 
 18 |     preserve_timestamps: bool = True
 19 | ) -> Tuple[bool, str]
 20 | ```
 21 | 
 22 | **Parameters:**
 23 | - `content_hash` (string, required): The content hash of the memory to update
 24 | - `updates` (object, required): Dictionary of metadata fields to update
 25 | - `preserve_timestamps` (boolean, optional): Whether to preserve original created_at timestamp (default: true)
 26 | 
 27 | **Returns:**
 28 | - `success` (boolean): Whether the update was successful
 29 | - `message` (string): Summary of updated fields or error message
 30 | 
 31 | ## Supported Update Fields
 32 | 
 33 | ### Core Metadata Fields
 34 | 
 35 | 1. **tags** (array of strings)
 36 |    - Replaces existing tags completely
 37 |    - Example: `"tags": ["important", "reference", "new-tag"]`
 38 | 
 39 | 2. **memory_type** (string)
 40 |    - Updates the memory type classification
 41 |    - Example: `"memory_type": "reminder"`
 42 | 
 43 | 3. **metadata** (object)
 44 |    - Merges with existing custom metadata
 45 |    - Example: `"metadata": {"priority": "high", "due_date": "2024-01-15"}`
 46 | 
 47 | ### Custom Fields
 48 | 
 49 | Any other fields not in the protected list can be updated directly:
 50 | - `"priority": "urgent"`
 51 | - `"status": "active"`
 52 | - `"category": "work"`
 53 | - Custom application-specific fields
 54 | 
 55 | ### Protected Fields
 56 | 
 57 | These fields cannot be modified through this API:
 58 | - `content` - Memory content is immutable
 59 | - `content_hash` - Content hash is immutable  
 60 | - `embedding` - Embeddings are preserved automatically
 61 | - `created_at` / `created_at_iso` - Preserved unless `preserve_timestamps=false`
 62 | - Internal timestamp fields (`timestamp`, `timestamp_float`, `timestamp_str`)
 63 | 
 64 | ## Usage Examples
 65 | 
 66 | ### Example 1: Add Tags to Memory
 67 | 
 68 | ```json
 69 | {
 70 |   "content_hash": "abc123def456...",
 71 |   "updates": {
 72 |     "tags": ["important", "reference", "project-alpha"]
 73 |   }
 74 | }
 75 | ```
 76 | 
 77 | ### Example 2: Update Memory Type and Custom Metadata
 78 | 
 79 | ```json
 80 | {
 81 |   "content_hash": "abc123def456...",
 82 |   "updates": {
 83 |     "memory_type": "reminder",
 84 |     "metadata": {
 85 |       "priority": "high",
 86 |       "due_date": "2024-01-15",
 87 |       "assignee": "[email protected]"
 88 |     }
 89 |   }
 90 | }
 91 | ```
 92 | 
 93 | ### Example 3: Update Custom Fields Directly
 94 | 
 95 | ```json
 96 | {
 97 |   "content_hash": "abc123def456...",
 98 |   "updates": {
 99 |     "priority": "urgent",
100 |     "status": "active",
101 |     "category": "work",
102 |     "last_reviewed": "2024-01-10"
103 |   }
104 | }
105 | ```
106 | 
107 | ### Example 4: Update with Timestamp Reset
108 | 
109 | ```json
110 | {
111 |   "content_hash": "abc123def456...",
112 |   "updates": {
113 |     "tags": ["archived", "completed"]
114 |   },
115 |   "preserve_timestamps": false
116 | }
117 | ```
118 | 
119 | ## Timestamp Behavior
120 | 
121 | ### Default Behavior (preserve_timestamps=true)
122 | 
123 | - `created_at` and `created_at_iso` are preserved from original memory
124 | - `updated_at` and `updated_at_iso` are set to current time
125 | - Legacy timestamp fields are updated for backward compatibility
126 | 
127 | ### Reset Behavior (preserve_timestamps=false)
128 | 
129 | - All timestamp fields are set to current time
130 | - Useful for marking memories as "refreshed" or "re-activated"
131 | 
132 | ## Implementation Details
133 | 
134 | ### Storage Layer
135 | 
136 | The API is implemented in the storage abstraction layer:
137 | 
138 | 1. **Base Storage Interface** (`storage/base.py`)
139 |    - Abstract method definition
140 |    - Consistent interface across storage backends
141 | 
142 | 2. **ChromaDB Implementation** (`storage/chroma.py`)
143 |    - Efficient upsert operation preserving embeddings
144 |    - Metadata merging with validation
145 |    - Timestamp synchronization
146 | 
147 | 3. **Future Storage Backends**
148 |    - sqlite-vec implementation will follow same interface
149 |    - Other storage backends can implement consistently
150 | 
151 | ### MCP Protocol Integration
152 | 
153 | The API is exposed via the MCP protocol:
154 | 
155 | 1. **Tool Registration** - Available as `update_memory_metadata` tool
156 | 2. **Input Validation** - Comprehensive parameter validation
157 | 3. **Error Handling** - Clear error messages for debugging
158 | 4. **Logging** - Detailed operation logging for monitoring
159 | 
160 | ## Performance Benefits
161 | 
162 | ### Efficiency Gains
163 | 
164 | 1. **No Content Re-processing**
165 |    - Original content remains unchanged
166 |    - No need to regenerate embeddings
167 |    - Preserves vector database relationships
168 | 
169 | 2. **Minimal Network Transfer**
170 |    - Only metadata changes are transmitted
171 |    - Reduced bandwidth usage
172 |    - Faster operation completion
173 | 
174 | 3. **Database Optimization**
175 |    - Single update operation vs delete+insert
176 |    - Maintains database indices and relationships
177 |    - Reduces transaction overhead
178 | 
179 | ### Resource Savings
180 | 
181 | - **Memory Usage**: No need to load full memory content
182 | - **CPU Usage**: No embedding regeneration required
183 | - **Storage I/O**: Minimal database operations
184 | - **Network**: Reduced data transfer
185 | 
186 | ## Error Handling
187 | 
188 | ### Common Error Scenarios
189 | 
190 | 1. **Memory Not Found**
191 |    ```
192 |    Error: Memory with hash abc123... not found
193 |    ```
194 | 
195 | 2. **Invalid Updates Format**
196 |    ```
197 |    Error: updates must be a dictionary
198 |    ```
199 | 
200 | 3. **Invalid Tags Format**
201 |    ```
202 |    Error: Tags must be provided as a list of strings
203 |    ```
204 | 
205 | 4. **Storage Not Initialized**
206 |    ```
207 |    Error: Collection not initialized, cannot update memory metadata
208 |    ```
209 | 
210 | ### Error Recovery
211 | 
212 | - Detailed error messages for debugging
213 | - Transaction rollback on failures
214 | - Original memory remains unchanged on errors
215 | - Logging for troubleshooting
216 | 
217 | ## Migration and Compatibility
218 | 
219 | ### Backward Compatibility
220 | 
221 | - Existing memories work without modification
222 | - Legacy timestamp fields are maintained
223 | - No breaking changes to existing APIs
224 | 
225 | ### Migration Strategy
226 | 
227 | 1. **Immediate Availability** - API available immediately after deployment
228 | 2. **Gradual Adoption** - Can be adopted incrementally
229 | 3. **Fallback Support** - Original store/delete pattern still works
230 | 4. **Validation** - Comprehensive testing before production use
231 | 
232 | ## Use Cases
233 | 
234 | ### Memory Organization
235 | 
236 | 1. **Tag Management**
237 |    - Add organizational tags over time
238 |    - Categorize memories as understanding improves
239 |    - Apply bulk tagging for organization
240 | 
241 | 2. **Priority Updates**
242 |    - Mark memories as high/low priority
243 |    - Update urgency as contexts change
244 |    - Implement memory lifecycle management
245 | 
246 | 3. **Status Tracking**
247 |    - Track memory processing status
248 |    - Mark memories as reviewed/processed
249 |    - Implement workflow states
250 | 
251 | ### Advanced Features
252 | 
253 | 1. **Memory Linking**
254 |    - Add relationship metadata
255 |    - Create memory hierarchies
256 |    - Implement reference systems
257 | 
258 | 2. **Time-to-Live Management**
259 |    - Add expiration metadata
260 |    - Implement memory aging
261 |    - Schedule automatic cleanup
262 | 
263 | 3. **Access Control**
264 |    - Add ownership metadata
265 |    - Implement sharing controls
266 |    - Track access permissions
267 | 
268 | ## Testing and Validation
269 | 
270 | ### Unit Tests
271 | 
272 | - Comprehensive test coverage for all update scenarios
273 | - Error condition testing
274 | - Timestamp behavior validation
275 | - Metadata merging verification
276 | 
277 | ### Integration Tests
278 | 
279 | - End-to-end MCP protocol testing
280 | - Storage backend compatibility testing
281 | - Performance benchmarking
282 | - Cross-platform validation
283 | 
284 | ### Performance Testing
285 | 
286 | - Large dataset updates
287 | - Concurrent update operations
288 | - Memory usage monitoring
289 | - Response time measurement
290 | 
291 | ## Future Enhancements
292 | 
293 | ### Planned Improvements
294 | 
295 | 1. **Batch Updates** - Update multiple memories in single operation
296 | 2. **Conditional Updates** - Update only if conditions are met  
297 | 3. **Metadata Validation** - Schema validation for metadata fields
298 | 4. **Update History** - Track metadata change history
299 | 5. **Selective Updates** - Update only specific metadata fields
300 | 
301 | ### Storage Backend Support
302 | 
303 | - sqlite-vec implementation (Issue #40)
304 | - Other vector database backends
305 | - Consistent API across all backends
306 | - Performance optimization per backend
307 | 
308 | ## Conclusion
309 | 
310 | The Memory Metadata Enhancement API provides a robust, efficient solution for memory metadata management. It enables sophisticated memory organization features while maintaining excellent performance and backward compatibility.
311 | 
312 | This implementation forms the foundation for advanced memory management features like re-tagging systems (Issue #45) and memory consolidation (Issue #11).
```

--------------------------------------------------------------------------------
/scripts/installation/setup_cloudflare_resources.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python3
  2 | """
  3 | Automated Cloudflare resource setup for MCP Memory Service.
  4 | This script creates the required Cloudflare resources using the HTTP API.
  5 | """
  6 | 
  7 | import os
  8 | import sys
  9 | import asyncio
 10 | import json
 11 | import logging
 12 | from typing import Dict, Any, Optional
 13 | import httpx
 14 | 
 15 | logging.basicConfig(level=logging.INFO)
 16 | logger = logging.getLogger(__name__)
 17 | 
 18 | class CloudflareSetup:
 19 |     def __init__(self, api_token: str, account_id: str):
 20 |         self.api_token = api_token
 21 |         self.account_id = account_id
 22 |         self.base_url = f"https://api.cloudflare.com/client/v4/accounts/{account_id}"
 23 |         self.client = None
 24 |     
 25 |     async def _get_client(self) -> httpx.AsyncClient:
 26 |         if self.client is None:
 27 |             headers = {
 28 |                 "Authorization": f"Bearer {self.api_token}",
 29 |                 "Content-Type": "application/json"
 30 |             }
 31 |             self.client = httpx.AsyncClient(headers=headers, timeout=30.0)
 32 |         return self.client
 33 |     
 34 |     async def _make_request(self, method: str, url: str, **kwargs) -> Dict[str, Any]:
 35 |         """Make authenticated request to Cloudflare API."""
 36 |         client = await self._get_client()
 37 |         response = await client.request(method, url, **kwargs)
 38 |         
 39 |         if response.status_code not in [200, 201]:
 40 |             logger.error(f"API request failed: {response.status_code} {response.text}")
 41 |             response.raise_for_status()
 42 |         
 43 |         return response.json()
 44 |     
 45 |     async def create_vectorize_index(self, name: str = "mcp-memory-index") -> str:
 46 |         """Create Vectorize index and return its ID."""
 47 |         logger.info(f"Creating Vectorize index: {name}")
 48 |         
 49 |         # Check if index already exists
 50 |         try:
 51 |             url = f"{self.base_url}/vectorize/indexes/{name}"
 52 |             result = await self._make_request("GET", url)
 53 |             if result.get("success"):
 54 |                 logger.info(f"Vectorize index {name} already exists")
 55 |                 return name
 56 |         except httpx.HTTPStatusError as e:
 57 |             if e.response.status_code != 404:
 58 |                 raise
 59 |         
 60 |         # Create new index
 61 |         url = f"{self.base_url}/vectorize/indexes"
 62 |         payload = {
 63 |             "name": name,
 64 |             "config": {
 65 |                 "dimensions": 768,
 66 |                 "metric": "cosine"
 67 |             }
 68 |         }
 69 |         
 70 |         result = await self._make_request("POST", url, json=payload)
 71 |         if result.get("success"):
 72 |             logger.info(f"✅ Created Vectorize index: {name}")
 73 |             return name
 74 |         else:
 75 |             raise ValueError(f"Failed to create Vectorize index: {result}")
 76 |     
 77 |     async def create_d1_database(self, name: str = "mcp-memory-db") -> str:
 78 |         """Create D1 database and return its ID."""
 79 |         logger.info(f"Creating D1 database: {name}")
 80 |         
 81 |         # List existing databases to check if it exists
 82 |         url = f"{self.base_url}/d1/database"
 83 |         result = await self._make_request("GET", url)
 84 |         
 85 |         if result.get("success"):
 86 |             for db in result.get("result", []):
 87 |                 if db.get("name") == name:
 88 |                     db_id = db.get("uuid")
 89 |                     logger.info(f"D1 database {name} already exists with ID: {db_id}")
 90 |                     return db_id
 91 |         
 92 |         # Create new database
 93 |         payload = {"name": name}
 94 |         result = await self._make_request("POST", url, json=payload)
 95 |         
 96 |         if result.get("success"):
 97 |             db_id = result["result"]["uuid"]
 98 |             logger.info(f"✅ Created D1 database: {name} (ID: {db_id})")
 99 |             return db_id
100 |         else:
101 |             raise ValueError(f"Failed to create D1 database: {result}")
102 |     
103 |     async def create_r2_bucket(self, name: str = "mcp-memory-content") -> str:
104 |         """Create R2 bucket and return its name."""
105 |         logger.info(f"Creating R2 bucket: {name}")
106 |         
107 |         # Check if bucket already exists
108 |         try:
109 |             url = f"{self.base_url}/r2/buckets/{name}"
110 |             result = await self._make_request("GET", url)
111 |             if result.get("success"):
112 |                 logger.info(f"R2 bucket {name} already exists")
113 |                 return name
114 |         except httpx.HTTPStatusError as e:
115 |             if e.response.status_code != 404:
116 |                 raise
117 |         
118 |         # Create new bucket
119 |         url = f"{self.base_url}/r2/buckets"
120 |         payload = {"name": name}
121 |         
122 |         result = await self._make_request("POST", url, json=payload)
123 |         if result.get("success"):
124 |             logger.info(f"✅ Created R2 bucket: {name}")
125 |             return name
126 |         else:
127 |             raise ValueError(f"Failed to create R2 bucket: {result}")
128 |     
129 |     async def verify_workers_ai_access(self) -> bool:
130 |         """Verify Workers AI access and embedding model."""
131 |         logger.info("Verifying Workers AI access...")
132 |         
133 |         # Test embedding generation
134 |         url = f"{self.base_url}/ai/run/@cf/baai/bge-base-en-v1.5"
135 |         payload = {"text": ["test embedding"]}
136 |         
137 |         try:
138 |             result = await self._make_request("POST", url, json=payload)
139 |             if result.get("success"):
140 |                 logger.info("✅ Workers AI access verified")
141 |                 return True
142 |             else:
143 |                 logger.warning(f"Workers AI test failed: {result}")
144 |                 return False
145 |         except Exception as e:
146 |             logger.warning(f"Workers AI verification failed: {e}")
147 |             return False
148 |     
149 |     async def close(self):
150 |         """Close HTTP client."""
151 |         if self.client:
152 |             await self.client.aclose()
153 | 
154 | async def main():
155 |     """Main setup routine."""
156 |     print("🚀 Cloudflare Backend Setup for MCP Memory Service")
157 |     print("=" * 55)
158 |     
159 |     # Check for required environment variables
160 |     api_token = os.getenv("CLOUDFLARE_API_TOKEN")
161 |     account_id = os.getenv("CLOUDFLARE_ACCOUNT_ID")
162 |     
163 |     if not api_token:
164 |         print("❌ CLOUDFLARE_API_TOKEN environment variable not set")
165 |         print("Please create an API token at: https://dash.cloudflare.com/profile/api-tokens")
166 |         print("Required permissions: Vectorize:Edit, D1:Edit, Workers AI:Edit, R2:Edit")
167 |         return False
168 |     
169 |     if not account_id:
170 |         print("❌ CLOUDFLARE_ACCOUNT_ID environment variable not set")
171 |         print("You can find your account ID in the Cloudflare dashboard sidebar")
172 |         return False
173 |     
174 |     setup = CloudflareSetup(api_token, account_id)
175 |     
176 |     try:
177 |         # Create resources
178 |         vectorize_index = await setup.create_vectorize_index()
179 |         d1_database_id = await setup.create_d1_database()
180 |         
181 |         # R2 bucket is optional
182 |         r2_bucket = None
183 |         create_r2 = input("\n🪣 Create R2 bucket for large content storage? (y/N): ").lower().strip()
184 |         if create_r2 in ['y', 'yes']:
185 |             try:
186 |                 r2_bucket = await setup.create_r2_bucket()
187 |             except Exception as e:
188 |                 logger.warning(f"Failed to create R2 bucket: {e}")
189 |                 logger.warning("Continuing without R2 storage...")
190 |         
191 |         # Verify Workers AI
192 |         ai_available = await setup.verify_workers_ai_access()
193 |         
194 |         print("\n🎉 Setup Complete!")
195 |         print("=" * 20)
196 |         print(f"Vectorize Index: {vectorize_index}")
197 |         print(f"D1 Database ID: {d1_database_id}")
198 |         print(f"R2 Bucket: {r2_bucket or 'Not configured'}")
199 |         print(f"Workers AI: {'Available' if ai_available else 'Limited access'}")
200 |         
201 |         print("\n📝 Environment Variables:")
202 |         print("=" * 25)
203 |         print(f"export CLOUDFLARE_API_TOKEN=\"{api_token[:10]}...\"")
204 |         print(f"export CLOUDFLARE_ACCOUNT_ID=\"{account_id}\"")
205 |         print(f"export CLOUDFLARE_VECTORIZE_INDEX=\"{vectorize_index}\"")
206 |         print(f"export CLOUDFLARE_D1_DATABASE_ID=\"{d1_database_id}\"")
207 |         if r2_bucket:
208 |             print(f"export CLOUDFLARE_R2_BUCKET=\"{r2_bucket}\"")
209 |         print("export MCP_MEMORY_STORAGE_BACKEND=\"cloudflare\"")
210 |         
211 |         print("\n🧪 Test the setup:")
212 |         print("python test_cloudflare_backend.py")
213 |         
214 |         return True
215 |         
216 |     except Exception as e:
217 |         logger.error(f"Setup failed: {e}")
218 |         return False
219 |     
220 |     finally:
221 |         await setup.close()
222 | 
223 | if __name__ == "__main__":
224 |     success = asyncio.run(main())
225 |     sys.exit(0 if success else 1)
```

--------------------------------------------------------------------------------
/docs/assets/images/project-infographic.svg:
--------------------------------------------------------------------------------

```
  1 | <svg width="800" height="1200" viewBox="0 0 800 1200" xmlns="http://www.w3.org/2000/svg">
  2 |   <!-- Background -->
  3 |   <rect width="800" height="1200" fill="#f8f9fa"/>
  4 |   
  5 |   <!-- Header -->
  6 |   <rect width="800" height="120" fill="#1a1a1a"/>
  7 |   <text x="400" y="60" font-family="Arial, sans-serif" font-size="36" font-weight="bold" fill="white" text-anchor="middle">MCP Memory Service</text>
  8 |   <text x="400" y="90" font-family="Arial, sans-serif" font-size="18" fill="#888" text-anchor="middle">Production-Ready Knowledge Management Platform</text>
  9 |   
 10 |   <!-- Performance Metrics Section -->
 11 |   <g transform="translate(0, 140)">
 12 |     <text x="400" y="30" font-family="Arial, sans-serif" font-size="24" font-weight="bold" fill="#333" text-anchor="middle">Performance Metrics</text>
 13 |     
 14 |     <!-- Metric Cards -->
 15 |     <g transform="translate(50, 60)">
 16 |       <!-- Card 1 -->
 17 |       <rect x="0" y="0" width="160" height="100" rx="10" fill="#e3f2fd" stroke="#2196f3" stroke-width="2"/>
 18 |       <text x="80" y="35" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="#1976d2" text-anchor="middle">319+</text>
 19 |       <text x="80" y="60" font-family="Arial, sans-serif" font-size="14" fill="#555" text-anchor="middle">Memories</text>
 20 |       <text x="80" y="80" font-family="Arial, sans-serif" font-size="14" fill="#555" text-anchor="middle">Managed</text>
 21 |       
 22 |       <!-- Card 2 -->
 23 |       <rect x="190" y="0" width="160" height="100" rx="10" fill="#e8f5e9" stroke="#4caf50" stroke-width="2"/>
 24 |       <text x="270" y="35" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="#388e3c" text-anchor="middle">828ms</text>
 25 |       <text x="270" y="60" font-family="Arial, sans-serif" font-size="14" fill="#555" text-anchor="middle">Avg Query</text>
 26 |       <text x="270" y="80" font-family="Arial, sans-serif" font-size="14" fill="#555" text-anchor="middle">Time</text>
 27 |       
 28 |       <!-- Card 3 -->
 29 |       <rect x="380" y="0" width="160" height="100" rx="10" fill="#fff3e0" stroke="#ff9800" stroke-width="2"/>
 30 |       <text x="460" y="35" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="#f57c00" text-anchor="middle">100%</text>
 31 |       <text x="460" y="60" font-family="Arial, sans-serif" font-size="14" fill="#555" text-anchor="middle">Cache Hit</text>
 32 |       <text x="460" y="80" font-family="Arial, sans-serif" font-size="14" fill="#555" text-anchor="middle">Ratio</text>
 33 |       
 34 |       <!-- Card 4 -->
 35 |       <rect x="570" y="0" width="160" height="100" rx="10" fill="#fce4ec" stroke="#e91e63" stroke-width="2"/>
 36 |       <text x="650" y="35" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="#c2185b" text-anchor="middle">20MB</text>
 37 |       <text x="650" y="60" font-family="Arial, sans-serif" font-size="14" fill="#555" text-anchor="middle">Efficient</text>
 38 |       <text x="650" y="80" font-family="Arial, sans-serif" font-size="14" fill="#555" text-anchor="middle">Storage</text>
 39 |     </g>
 40 |   </g>
 41 |   
 42 |   <!-- Features Section -->
 43 |   <g transform="translate(0, 380)">
 44 |     <text x="400" y="30" font-family="Arial, sans-serif" font-size="24" font-weight="bold" fill="#333" text-anchor="middle">16 Comprehensive Operations</text>
 45 |     
 46 |     <!-- Feature Categories -->
 47 |     <g transform="translate(50, 60)">
 48 |       <!-- Memory Operations -->
 49 |       <rect x="0" y="0" width="220" height="180" rx="10" fill="#f5f5f5" stroke="#999" stroke-width="1"/>
 50 |       <text x="110" y="25" font-family="Arial, sans-serif" font-size="16" font-weight="bold" fill="#333" text-anchor="middle">Memory Operations</text>
 51 |       <text x="15" y="50" font-family="Arial, sans-serif" font-size="14" fill="#555">• store_memory</text>
 52 |       <text x="15" y="70" font-family="Arial, sans-serif" font-size="14" fill="#555">• retrieve_memory</text>
 53 |       <text x="15" y="90" font-family="Arial, sans-serif" font-size="14" fill="#555">• search_by_tag</text>
 54 |       <text x="15" y="110" font-family="Arial, sans-serif" font-size="14" fill="#555">• delete_memory</text>
 55 |       <text x="15" y="130" font-family="Arial, sans-serif" font-size="14" fill="#555">• update_metadata</text>
 56 |       <text x="15" y="150" font-family="Arial, sans-serif" font-size="14" fill="#555">• exact_match_retrieve</text>
 57 |       
 58 |       <!-- Database Management -->
 59 |       <rect x="250" y="0" width="220" height="180" rx="10" fill="#f5f5f5" stroke="#999" stroke-width="1"/>
 60 |       <text x="360" y="25" font-family="Arial, sans-serif" font-size="16" font-weight="bold" fill="#333" text-anchor="middle">Database Management</text>
 61 |       <text x="265" y="50" font-family="Arial, sans-serif" font-size="14" fill="#555">• create_backup</text>
 62 |       <text x="265" y="70" font-family="Arial, sans-serif" font-size="14" fill="#555">• optimize_db</text>
 63 |       <text x="265" y="90" font-family="Arial, sans-serif" font-size="14" fill="#555">• check_health</text>
 64 |       <text x="265" y="110" font-family="Arial, sans-serif" font-size="14" fill="#555">• get_stats</text>
 65 |       <text x="265" y="130" font-family="Arial, sans-serif" font-size="14" fill="#555">• cleanup_duplicates</text>
 66 |       
 67 |       <!-- Advanced Features -->
 68 |       <rect x="500" y="0" width="200" height="180" rx="10" fill="#f5f5f5" stroke="#999" stroke-width="1"/>
 69 |       <text x="600" y="25" font-family="Arial, sans-serif" font-size="16" font-weight="bold" fill="#333" text-anchor="middle">Advanced Features</text>
 70 |       <text x="515" y="50" font-family="Arial, sans-serif" font-size="14" fill="#555">• debug_retrieve</text>
 71 |       <text x="515" y="70" font-family="Arial, sans-serif" font-size="14" fill="#555">• recall_memory</text>
 72 |       <text x="515" y="90" font-family="Arial, sans-serif" font-size="14" fill="#555">• delete_by_timeframe</text>
 73 |       <text x="515" y="110" font-family="Arial, sans-serif" font-size="14" fill="#555">• check_embedding</text>
 74 |     </g>
 75 |   </g>
 76 |   
 77 |   <!-- Architecture -->
 78 |   <g transform="translate(0, 650)">
 79 |     <text x="400" y="30" font-family="Arial, sans-serif" font-size="24" font-weight="bold" fill="#333" text-anchor="middle">Architecture Stack</text>
 80 |     
 81 |     <g transform="translate(150, 60)">
 82 |       <!-- Stack layers -->
 83 |       <rect x="0" y="0" width="500" height="50" rx="5" fill="#4a90e2" stroke="#357abd" stroke-width="2"/>
 84 |       <text x="250" y="30" font-family="Arial, sans-serif" font-size="16" font-weight="bold" fill="white" text-anchor="middle">React Dashboard + Real-time Statistics</text>
 85 |       
 86 |       <rect x="0" y="60" width="500" height="50" rx="5" fill="#5cb85c" stroke="#449d44" stroke-width="2"/>
 87 |       <text x="250" y="90" font-family="Arial, sans-serif" font-size="16" font-weight="bold" fill="white" text-anchor="middle">MCP Protocol (stdin/stdout)</text>
 88 |       
 89 |       <rect x="0" y="120" width="500" height="50" rx="5" fill="#f0ad4e" stroke="#ec971f" stroke-width="2"/>
 90 |       <text x="250" y="150" font-family="Arial, sans-serif" font-size="16" font-weight="bold" fill="white" text-anchor="middle">Python Server + Sentence Transformers</text>
 91 |       
 92 |       <rect x="0" y="180" width="500" height="50" rx="5" fill="#d9534f" stroke="#c9302c" stroke-width="2"/>
 93 |       <text x="250" y="210" font-family="Arial, sans-serif" font-size="16" font-weight="bold" fill="white" text-anchor="middle">ChromaDB Vector Storage</text>
 94 |     </g>
 95 |   </g>
 96 |   
 97 |   <!-- Sponsorship CTA -->
 98 |   <g transform="translate(0, 950)">
 99 |     <rect x="50" y="0" width="700" height="200" rx="15" fill="#1a1a1a"/>
100 |     <text x="400" y="40" font-family="Arial, sans-serif" font-size="28" font-weight="bold" fill="white" text-anchor="middle">Support Open Source Development</text>
101 |     
102 |     <text x="400" y="80" font-family="Arial, sans-serif" font-size="16" fill="#ccc" text-anchor="middle">Your sponsorship enables:</text>
103 |     <text x="200" y="110" font-family="Arial, sans-serif" font-size="14" fill="#aaa">✓ New feature development</text>
104 |     <text x="200" y="135" font-family="Arial, sans-serif" font-size="14" fill="#aaa">✓ Bug fixes &amp; maintenance</text>
105 |     <text x="450" y="110" font-family="Arial, sans-serif" font-size="14" fill="#aaa">✓ Documentation improvements</text>
106 |     <text x="450" y="135" font-family="Arial, sans-serif" font-size="14" fill="#aaa">✓ Community support</text>
107 |     
108 |     <rect x="300" y="155" width="200" height="35" rx="20" fill="#ea4aaa" stroke="none"/>
109 |     <text x="400" y="178" font-family="Arial, sans-serif" font-size="16" font-weight="bold" fill="white" text-anchor="middle">Become a Sponsor</text>
110 |   </g>
111 | </svg>
```

--------------------------------------------------------------------------------
/scripts/development/verify_hybrid_sync.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python3
  2 | """
  3 | Comprehensive verification of hybrid storage background sync functionality.
  4 | """
  5 | 
  6 | import asyncio
  7 | import sys
  8 | import tempfile
  9 | import os
 10 | import time
 11 | from unittest.mock import patch
 12 | 
 13 | sys.path.insert(0, 'src')
 14 | 
 15 | from mcp_memory_service.storage.hybrid import HybridMemoryStorage
 16 | from mcp_memory_service.models.memory import Memory
 17 | import hashlib
 18 | 
 19 | 
 20 | class DetailedMockCloudflare:
 21 |     """Detailed mock for tracking sync operations."""
 22 | 
 23 |     def __init__(self, **kwargs):
 24 |         self.memories = {}
 25 |         self.operation_log = []
 26 |         self.initialized = False
 27 |         self.delay = 0.01  # Simulate network delay
 28 | 
 29 |     async def initialize(self):
 30 |         self.initialized = True
 31 |         self.operation_log.append(('init', time.time()))
 32 | 
 33 |     async def store(self, memory):
 34 |         await asyncio.sleep(self.delay)  # Simulate network
 35 |         self.memories[memory.content_hash] = memory
 36 |         self.operation_log.append(('store', memory.content_hash, time.time()))
 37 |         return True, "Stored"
 38 | 
 39 |     async def delete(self, content_hash):
 40 |         await asyncio.sleep(self.delay)
 41 |         if content_hash in self.memories:
 42 |             del self.memories[content_hash]
 43 |         self.operation_log.append(('delete', content_hash, time.time()))
 44 |         return True, "Deleted"
 45 | 
 46 |     async def update_memory_metadata(self, content_hash, updates, preserve_timestamps=True):
 47 |         await asyncio.sleep(self.delay)
 48 |         self.operation_log.append(('update', content_hash, time.time()))
 49 |         return True, "Updated"
 50 | 
 51 |     async def get_stats(self):
 52 |         return {"total": len(self.memories)}
 53 | 
 54 |     async def close(self):
 55 |         self.operation_log.append(('close', time.time()))
 56 | 
 57 | 
 58 | async def verify_sync():
 59 |     print("🔍 HYBRID STORAGE BACKGROUND SYNC VERIFICATION")
 60 |     print("=" * 60)
 61 | 
 62 |     with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as tmp:
 63 |         db_path = tmp.name
 64 | 
 65 |     try:
 66 |         config = {
 67 |             'api_token': 'test',
 68 |             'account_id': 'test',
 69 |             'vectorize_index': 'test',
 70 |             'd1_database_id': 'test'
 71 |         }
 72 | 
 73 |         with patch('mcp_memory_service.storage.hybrid.CloudflareStorage', DetailedMockCloudflare):
 74 |             # Initialize with short sync interval
 75 |             storage = HybridMemoryStorage(
 76 |                 sqlite_db_path=db_path,
 77 |                 cloudflare_config=config,
 78 |                 sync_interval=0.5,  # 500ms for quick testing
 79 |                 batch_size=2
 80 |             )
 81 | 
 82 |             await storage.initialize()
 83 |             print("✅ Hybrid storage initialized with background sync")
 84 |             print(f"  • Primary: SQLite-vec (local)")
 85 |             print(f"  • Secondary: Mock Cloudflare (simulated)")
 86 |             print(f"  • Sync interval: 0.5 seconds")
 87 |             print(f"  • Batch size: 2 operations")
 88 |             print()
 89 | 
 90 |             # TEST 1: Store operations are queued
 91 |             print("📝 TEST 1: Store Operations Queuing")
 92 |             print("-" * 40)
 93 | 
 94 |             memories = []
 95 |             for i in range(4):
 96 |                 content = f"Sync test memory #{i+1} at {time.time()}"
 97 |                 memory = Memory(
 98 |                     content=content,
 99 |                     content_hash=hashlib.sha256(content.encode()).hexdigest(),
100 |                     tags=['sync-verify'],
101 |                     memory_type='test'
102 |                 )
103 |                 memories.append(memory)
104 | 
105 |                 start = time.time()
106 |                 success, msg = await storage.store(memory)
107 |                 elapsed = (time.time() - start) * 1000
108 |                 print(f"  Memory #{i+1}: ✅ stored in {elapsed:.1f}ms (local)")
109 | 
110 |             # Check initial queue
111 |             status = await storage.sync_service.get_sync_status()
112 |             print(f"\n  📊 Queue status after stores:")
113 |             print(f"     • Queued operations: {status['queue_size']}")
114 |             print(f"     • Processed: {status['stats']['operations_processed']}")
115 | 
116 |             # TEST 2: Wait for automatic background sync
117 |             print("\n⏳ TEST 2: Automatic Background Sync")
118 |             print("-" * 40)
119 |             print("  Waiting 1.5 seconds for automatic sync...")
120 |             await asyncio.sleep(1.5)
121 | 
122 |             status = await storage.sync_service.get_sync_status()
123 |             mock_log = storage.secondary.operation_log
124 | 
125 |             print(f"\n  📊 After automatic sync:")
126 |             print(f"     • Queue remaining: {status['queue_size']}")
127 |             print(f"     • Operations processed: {status['stats']['operations_processed']}")
128 |             print(f"     • Mock Cloudflare received: {len([op for op in mock_log if op[0] == 'store'])} stores")
129 | 
130 |             # TEST 3: Delete operation
131 |             print("\n🗑️ TEST 3: Delete Operation Sync")
132 |             print("-" * 40)
133 | 
134 |             delete_hash = memories[0].content_hash
135 |             success, msg = await storage.delete(delete_hash)
136 |             print(f"  Delete operation: ✅ (local)")
137 | 
138 |             await asyncio.sleep(1)  # Wait for sync
139 | 
140 |             delete_ops = [op for op in mock_log if op[0] == 'delete']
141 |             print(f"  Mock Cloudflare received: {len(delete_ops)} delete operation(s)")
142 | 
143 |             # TEST 4: Force sync
144 |             print("\n🔄 TEST 4: Force Sync")
145 |             print("-" * 40)
146 | 
147 |             # Add more memories
148 |             for i in range(2):
149 |                 content = f"Force sync test #{i+1}"
150 |                 memory = Memory(
151 |                     content=content,
152 |                     content_hash=hashlib.sha256(content.encode()).hexdigest(),
153 |                     tags=['force-sync'],
154 |                     memory_type='test'
155 |                 )
156 |                 await storage.store(memory)
157 | 
158 |             print(f"  Added 2 more memories")
159 | 
160 |             # Force sync
161 |             result = await storage.force_sync()
162 |             print(f"\n  Force sync result:")
163 |             print(f"     • Status: {result['status']}")
164 |             print(f"     • Primary memories: {result['primary_memories']}")
165 |             print(f"     • Synced to secondary: {result['synced_to_secondary']}")
166 |             print(f"     • Duration: {result.get('duration', 0):.3f}s")
167 | 
168 |             # Final verification
169 |             print("\n✅ FINAL VERIFICATION")
170 |             print("-" * 40)
171 | 
172 |             final_status = await storage.sync_service.get_sync_status()
173 |             final_mock_ops = storage.secondary.operation_log
174 | 
175 |             print(f"  Sync service statistics:")
176 |             print(f"     • Total operations processed: {final_status['stats']['operations_processed']}")
177 |             print(f"     • Failed operations: {final_status['stats'].get('operations_failed', 0)}")
178 |             print(f"     • Cloudflare available: {final_status['cloudflare_available']}")
179 | 
180 |             print(f"\n  Mock Cloudflare operations log:")
181 |             store_count = len([op for op in final_mock_ops if op[0] == 'store'])
182 |             delete_count = len([op for op in final_mock_ops if op[0] == 'delete'])
183 |             update_count = len([op for op in final_mock_ops if op[0] == 'update'])
184 | 
185 |             print(f"     • Store operations: {store_count}")
186 |             print(f"     • Delete operations: {delete_count}")
187 |             print(f"     • Update operations: {update_count}")
188 |             print(f"     • Total operations: {len(final_mock_ops) - 2}")  # Exclude init and close
189 | 
190 |             # Verify memory counts match
191 |             primary_count = len(await storage.primary.get_all_memories())
192 |             secondary_count = len(storage.secondary.memories)
193 | 
194 |             print(f"\n  Memory count verification:")
195 |             print(f"     • Primary (SQLite-vec): {primary_count}")
196 |             print(f"     • Secondary (Mock CF): {secondary_count}")
197 |             print(f"     • Match: {'✅ YES' if primary_count == secondary_count else '❌ NO'}")
198 | 
199 |             await storage.close()
200 | 
201 |             print("\n" + "=" * 60)
202 |             print("🎉 BACKGROUND SYNC VERIFICATION COMPLETE")
203 |             print("\nSummary: The hybrid storage backend is working correctly!")
204 |             print("  ✅ Store operations are queued for background sync")
205 |             print("  ✅ Automatic sync processes operations in batches")
206 |             print("  ✅ Delete operations are synced to secondary")
207 |             print("  ✅ Force sync ensures complete synchronization")
208 |             print("  ✅ Both backends maintain consistency")
209 | 
210 |     finally:
211 |         if os.path.exists(db_path):
212 |             os.unlink(db_path)
213 | 
214 | 
215 | if __name__ == "__main__":
216 |     asyncio.run(verify_sync())
```

--------------------------------------------------------------------------------
/tests/integration/test_server_handlers.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Integration tests for MCP handler methods in server.py.
  3 | 
  4 | These tests verify that the MCP handlers correctly transform MemoryService
  5 | responses to MCP TextContent format, particularly after the fix for issue #198.
  6 | """
  7 | 
  8 | import pytest
  9 | from mcp import types
 10 | from mcp_memory_service.server import MemoryServer
 11 | 
 12 | 
 13 | class TestHandleStoreMemory:
 14 |     """Test suite for handle_store_memory MCP handler."""
 15 | 
 16 |     @pytest.mark.asyncio
 17 |     async def test_store_memory_success(self):
 18 |         """Test storing a valid memory returns success message with hash."""
 19 |         server = MemoryServer()
 20 | 
 21 |         result = await server.handle_store_memory({
 22 |             "content": "Test memory content for integration test",
 23 |             "metadata": {
 24 |                 "tags": ["test", "integration"],
 25 |                 "type": "note"
 26 |             }
 27 |         })
 28 | 
 29 |         # Verify result structure
 30 |         assert isinstance(result, list)
 31 |         assert len(result) == 1
 32 |         assert isinstance(result[0], types.TextContent)
 33 | 
 34 |         # Verify success message
 35 |         text = result[0].text
 36 |         assert "successfully" in text.lower()
 37 |         assert "hash:" in text.lower()
 38 |         assert "..." in text  # Hash should be truncated
 39 | 
 40 |     @pytest.mark.asyncio
 41 |     async def test_store_memory_chunked(self):
 42 |         """Test storing long content creates multiple chunks."""
 43 |         server = MemoryServer()
 44 | 
 45 |         # Create content that will be auto-split (> 1500 chars)
 46 |         long_content = "This is a very long memory content. " * 100
 47 | 
 48 |         result = await server.handle_store_memory({
 49 |             "content": long_content,
 50 |             "metadata": {"tags": ["test"], "type": "note"}
 51 |         })
 52 | 
 53 |         # Verify result structure
 54 |         assert isinstance(result, list)
 55 |         assert len(result) == 1
 56 |         assert isinstance(result[0], types.TextContent)
 57 | 
 58 |         # Verify chunked message
 59 |         text = result[0].text
 60 |         assert "chunk" in text.lower()
 61 |         assert "successfully" in text.lower()
 62 | 
 63 |     @pytest.mark.asyncio
 64 |     async def test_store_memory_empty_content(self):
 65 |         """Test storing empty content returns error."""
 66 |         server = MemoryServer()
 67 | 
 68 |         result = await server.handle_store_memory({
 69 |             "content": "",
 70 |             "metadata": {}
 71 |         })
 72 | 
 73 |         # Verify error message
 74 |         assert isinstance(result, list)
 75 |         assert len(result) == 1
 76 |         text = result[0].text
 77 |         assert "error" in text.lower()
 78 |         assert "required" in text.lower()
 79 | 
 80 |     @pytest.mark.asyncio
 81 |     async def test_store_memory_missing_content(self):
 82 |         """Test storing without content parameter returns error."""
 83 |         server = MemoryServer()
 84 | 
 85 |         result = await server.handle_store_memory({
 86 |             "metadata": {"tags": ["test"]}
 87 |         })
 88 | 
 89 |         # Verify error message
 90 |         assert isinstance(result, list)
 91 |         assert len(result) == 1
 92 |         text = result[0].text
 93 |         assert "error" in text.lower()
 94 | 
 95 |     @pytest.mark.asyncio
 96 |     async def test_store_memory_with_tags_string(self):
 97 |         """Test storing memory with tags as string (not array)."""
 98 |         server = MemoryServer()
 99 | 
100 |         result = await server.handle_store_memory({
101 |             "content": "Test with string tags",
102 |             "metadata": {
103 |                 "tags": "test,integration,string-tags",
104 |                 "type": "note"
105 |             }
106 |         })
107 | 
108 |         # Should succeed - MemoryService handles string tags
109 |         assert isinstance(result, list)
110 |         assert len(result) == 1
111 |         text = result[0].text
112 |         assert "successfully" in text.lower()
113 | 
114 |     @pytest.mark.asyncio
115 |     async def test_store_memory_default_type(self):
116 |         """Test storing memory without explicit type uses default."""
117 |         server = MemoryServer()
118 | 
119 |         result = await server.handle_store_memory({
120 |             "content": "Memory without explicit type",
121 |             "metadata": {"tags": ["test"]}
122 |         })
123 | 
124 |         # Should succeed with default type
125 |         assert isinstance(result, list)
126 |         assert len(result) == 1
127 |         text = result[0].text
128 |         assert "successfully" in text.lower()
129 | 
130 | 
131 | class TestHandleRetrieveMemory:
132 |     """Test suite for handle_retrieve_memory MCP handler."""
133 | 
134 |     @pytest.mark.asyncio
135 |     async def test_retrieve_memory_success(self):
136 |         """Test retrieving memories with valid query."""
137 |         server = MemoryServer()
138 | 
139 |         # First store a memory
140 |         await server.handle_store_memory({
141 |             "content": "Searchable test memory for retrieval",
142 |             "metadata": {"tags": ["retrieval-test"], "type": "note"}
143 |         })
144 | 
145 |         # Now retrieve it
146 |         result = await server.handle_retrieve_memory({
147 |             "query": "searchable test memory",
148 |             "n_results": 5
149 |         })
150 | 
151 |         # Verify result structure
152 |         assert isinstance(result, list)
153 |         assert len(result) == 1
154 |         assert isinstance(result[0], types.TextContent)
155 | 
156 |         # Should contain memory data (JSON format)
157 |         text = result[0].text
158 |         assert "searchable test memory" in text.lower() or "retrieval-test" in text.lower()
159 | 
160 |     @pytest.mark.asyncio
161 |     async def test_retrieve_memory_missing_query(self):
162 |         """Test retrieving without query parameter returns error."""
163 |         server = MemoryServer()
164 | 
165 |         result = await server.handle_retrieve_memory({
166 |             "n_results": 5
167 |         })
168 | 
169 |         # Verify error message
170 |         assert isinstance(result, list)
171 |         assert len(result) == 1
172 |         text = result[0].text
173 |         assert "error" in text.lower()
174 |         assert "query" in text.lower()
175 | 
176 | 
177 | class TestHandleSearchByTag:
178 |     """Test suite for handle_search_by_tag MCP handler."""
179 | 
180 |     @pytest.mark.asyncio
181 |     async def test_search_by_tag_success(self):
182 |         """Test searching by tag returns matching memories."""
183 |         server = MemoryServer()
184 | 
185 |         # Store a memory with specific tag
186 |         await server.handle_store_memory({
187 |             "content": "Memory with unique tag for search",
188 |             "metadata": {"tags": ["unique-search-tag"], "type": "note"}
189 |         })
190 | 
191 |         # Search by tag
192 |         result = await server.handle_search_by_tag({
193 |             "tags": ["unique-search-tag"]
194 |         })
195 | 
196 |         # Verify result structure
197 |         assert isinstance(result, list)
198 |         assert len(result) == 1
199 |         assert isinstance(result[0], types.TextContent)
200 | 
201 |         # Should contain memory data
202 |         text = result[0].text
203 |         assert "unique-search-tag" in text.lower() or "memory with unique tag" in text.lower()
204 | 
205 |     @pytest.mark.asyncio
206 |     async def test_search_by_tag_missing_tags(self):
207 |         """Test searching without tags parameter returns error."""
208 |         server = MemoryServer()
209 | 
210 |         result = await server.handle_search_by_tag({})
211 | 
212 |         # Verify error message
213 |         assert isinstance(result, list)
214 |         assert len(result) == 1
215 |         text = result[0].text
216 |         assert "error" in text.lower()
217 |         assert "tags" in text.lower()
218 | 
219 | 
220 | # Regression test for issue #198
221 | class TestIssue198Regression:
222 |     """Regression tests specifically for issue #198 - Response format bug."""
223 | 
224 |     @pytest.mark.asyncio
225 |     async def test_no_keyerror_on_store_success(self):
226 |         """Verify fix for issue #198: No KeyError on successful store."""
227 |         server = MemoryServer()
228 | 
229 |         # This would previously raise KeyError: 'message'
230 |         result = await server.handle_store_memory({
231 |             "content": "Test for issue 198 regression",
232 |             "metadata": {"tags": ["issue-198"], "type": "test"}
233 |         })
234 | 
235 |         # Should return success message without KeyError
236 |         assert isinstance(result, list)
237 |         assert len(result) == 1
238 |         assert "successfully" in result[0].text.lower()
239 |         # Should NOT contain the string "message" (old buggy behavior)
240 |         assert result[0].text != "Error storing memory: 'message'"
241 | 
242 |     @pytest.mark.asyncio
243 |     async def test_error_handling_without_keyerror(self):
244 |         """Verify fix for issue #198: Errors handled without KeyError."""
245 |         server = MemoryServer()
246 | 
247 |         # Store with empty content (triggers error path)
248 |         result = await server.handle_store_memory({
249 |             "content": "",
250 |             "metadata": {}
251 |         })
252 | 
253 |         # Should return error message without KeyError
254 |         assert isinstance(result, list)
255 |         assert len(result) == 1
256 |         assert "error" in result[0].text.lower()
257 |         # Should NOT be KeyError message
258 |         assert "'message'" not in result[0].text
259 | 
```
Page 12/47FirstPrevNextLast