Skip to main content
Glama

MCP Memory Service

code-execution-interface-implementation.md42.1 kB
# Code Execution Interface Implementation Research ## Issue #206: 90-95% Token Reduction Strategy **Research Date:** November 6, 2025 **Target:** Implement Python code API for mcp-memory-service to reduce token consumption by 90-95% **Current Status:** Research & Architecture Phase --- ## Executive Summary This document provides comprehensive research findings and implementation recommendations for transitioning mcp-memory-service from tool-based MCP interactions to a direct code execution interface. Based on industry best practices, real-world examples, and analysis of current codebase architecture, this research identifies concrete strategies to achieve the target 90-95% token reduction. ### Key Findings 1. **Token Reduction Potential Validated**: Research confirms 75-90% reductions are achievable through code execution interfaces 2. **Industry Momentum**: Anthropic's November 2025 announcement of MCP code execution aligns with our proposal 3. **Proven Patterns**: Multiple successful implementations exist (python-interpreter MCP, CodeAgents framework) 4. **Architecture Ready**: Current codebase structure well-positioned for gradual migration --- ## 1. Current State Analysis ### Token Consumption Breakdown **Current Architecture:** - **33 MCP tools** generating ~4,125 tokens per interaction - **Document ingestion:** 57,400 tokens for 50 PDFs - **Session hooks:** 3,600-9,600 tokens per session start - **Tool definitions:** Loaded upfront into context window **Example: Session-Start Hook** ```javascript // Current approach: MCP tool invocation // Each tool call includes full schema (~125 tokens/tool) await memoryClient.callTool('retrieve_memory', { query: gitContext.query, limit: 8, similarity_threshold: 0.6 }); // Result includes full Memory objects with all fields: // - content, content_hash, tags, memory_type, metadata // - embedding (768 floats for all-MiniLM-L6-v2) // - created_at, created_at_iso, updated_at, updated_at_iso // - Total: ~500-800 tokens per memory ``` ### Codebase Architecture Analysis **Strengths:** - ✅ Clean separation of concerns (storage/models/web layers) - ✅ Abstract base class (`MemoryStorage`) for consistent interface - ✅ Async/await throughout for performance - ✅ Strong type hints (Python 3.10+) - ✅ Multiple storage backends (SQLite-Vec, Cloudflare, Hybrid) - ✅ Existing HTTP client (`HTTPClientStorage`) demonstrates remote access pattern **Current Entry Points:** ```python # Public API (src/mcp_memory_service/__init__.py) __all__ = [ 'Memory', 'MemoryQueryResult', 'MemoryStorage', 'generate_content_hash' ] ``` **Infrastructure Files:** - `src/mcp_memory_service/server.py` - 3,721 lines (MCP server implementation) - `src/mcp_memory_service/storage/base.py` - Abstract interface with 20+ methods - `src/mcp_memory_service/models/memory.py` - Memory data model with timestamp handling - `src/mcp_memory_service/web/api/mcp.py` - MCP protocol endpoints --- ## 2. Best Practices from Research ### 2.1 Token-Efficient API Design **Key Principles from CodeAgents Framework:** 1. **Codified Structures Over Natural Language** - Use pseudocode/typed structures instead of verbose descriptions - Control structures (loops, conditionals) reduce repeated instructions - Typed variables eliminate ambiguity and error-prone parsing 2. **Modular Subroutines** - Encapsulate common patterns in reusable functions - Single import replaces repeated tool definitions - Function signatures convey requirements compactly 3. **Compact Result Types** - Return only essential data fields - Use structured types (namedtuple, TypedDict) for clarity - Avoid redundant metadata in response payloads **Anthropic's MCP Code Execution Approach (Nov 2025):** ```python # Before: Tool invocation (125 tokens for schema + 500 tokens for result) result = await call_tool("retrieve_memory", { "query": "recent architecture decisions", "limit": 5, "similarity_threshold": 0.7 }) # After: Code execution (5 tokens import + 20 tokens call) from mcp_memory_service.api import search results = search("recent architecture decisions", limit=5) ``` ### 2.2 Python Data Structure Performance **Benchmark Results (from research):** | Structure | Creation Speed | Access Speed | Memory | Immutability | Type Safety | |-----------|---------------|--------------|---------|--------------|-------------| | `dict` | Fastest | Fast | High | No | Runtime only | | `dataclass` | 8% faster than NamedTuple | Fast | Medium (with `__slots__`) | Optional | Static + Runtime | | `NamedTuple` | Fast | Fastest (C-based) | Low | Yes | Static + Runtime | | `TypedDict` | Same as dict | Same as dict | High | No | Static only | **Recommendation for Token Efficiency:** ```python from typing import NamedTuple class CompactMemory(NamedTuple): """Minimal memory representation for hooks (50-80 tokens vs 500-800).""" hash: str # 8 chars content: str # First 200 chars tags: tuple[str] # Tag list created: float # Unix timestamp score: float # Relevance score ``` **Benefits:** - ✅ **60-90% size reduction**: Essential fields only - ✅ **Immutable**: Safer for concurrent access in hooks - ✅ **Type-safe**: Static checking with mypy/pyright - ✅ **Fast**: C-based tuple operations - ✅ **Readable**: Named field access (`memory.hash` not `memory[0]`) ### 2.3 Migration Strategy Best Practices **Lessons from Python 2→3 Migrations:** 1. **Compatibility Layers Work** - `python-future` provided seamless 2.6/2.7/3.3+ compatibility - Gradual migration reduced risk and allowed testing - Tool-based automation (futurize) caught 65-80% of changes 2. **Feature Flags Enable Rollback** - Dual implementations run side-by-side during transition - Environment variable switches between old/new paths - Observability metrics validate equivalence 3. **Incremental Adoption** - Start with low-risk, high-value targets (session hooks) - Gather metrics before expanding scope - Maintain backward compatibility throughout --- ## 3. Architecture Recommendations ### 3.1 Filesystem Structure ``` src/mcp_memory_service/ ├── api/ # NEW: Code execution interface │ ├── __init__.py # Public API exports │ ├── compact.py # Compact result types │ ├── search.py # Search operations │ ├── storage.py # Storage operations │ └── utils.py # Helper functions ├── models/ │ ├── memory.py # EXISTING: Full Memory model │ └── compact.py # NEW: CompactMemory types ├── storage/ │ ├── base.py # EXISTING: Abstract interface │ ├── sqlite_vec.py # EXISTING: SQLite backend │ └── ... └── server.py # EXISTING: MCP server (keep for compatibility) ``` ### 3.2 Compact Result Types **Design Principles:** 1. Return minimal data for common use cases 2. Provide "expand" functions for full details when needed 3. Use immutable types (NamedTuple) for safety **Implementation:** ```python # src/mcp_memory_service/models/compact.py from typing import NamedTuple, Optional class CompactMemory(NamedTuple): """Minimal memory for efficient token usage (~80 tokens vs ~600).""" hash: str # Content hash (8 chars) preview: str # First 200 chars of content tags: tuple[str, ...] # Immutable tag tuple created: float # Unix timestamp score: float = 0.0 # Relevance score class CompactSearchResult(NamedTuple): """Search result with minimal overhead.""" memories: tuple[CompactMemory, ...] total: int query: str def __repr__(self) -> str: """Compact string representation.""" return f"SearchResult(found={self.total}, shown={len(self.memories)})" class CompactStorageInfo(NamedTuple): """Health check result (~20 tokens vs ~100).""" backend: str # 'sqlite_vec' | 'cloudflare' | 'hybrid' count: int # Total memories ready: bool # Service operational ``` **Token Comparison:** ```python # Full Memory object (current): { "content": "Long text...", # ~400 tokens "content_hash": "abc123...", # ~10 tokens "tags": ["tag1", "tag2"], # ~15 tokens "memory_type": "note", # ~5 tokens "metadata": {...}, # ~50 tokens "embedding": [0.1, 0.2, ...], # ~300 tokens (768 dims) "created_at": 1730928000.0, # ~8 tokens "created_at_iso": "2025-11-06...", # ~12 tokens "updated_at": 1730928000.0, # ~8 tokens "updated_at_iso": "2025-11-06..." # ~12 tokens } # Total: ~820 tokens per memory # CompactMemory (proposed): CompactMemory( hash='abc123', # ~5 tokens preview='Long text...'[:200], # ~50 tokens tags=('tag1', 'tag2'), # ~10 tokens created=1730928000.0, # ~5 tokens score=0.85 # ~3 tokens ) # Total: ~73 tokens per memory (91% reduction!) ``` ### 3.3 Core API Functions **Design Goals:** - Single import statement replaces tool definitions - Type hints provide inline documentation - Sync wrappers for non-async contexts (hooks) - Automatic connection management **Implementation:** ```python # src/mcp_memory_service/api/__init__.py """ Code execution API for mcp-memory-service. This module provides a lightweight, token-efficient interface for direct Python code execution, replacing MCP tool calls. Token Efficiency: - Import: ~10 tokens (once per session) - Function call: ~5-20 tokens (vs 125+ for MCP tools) - Results: 73-200 tokens (vs 500-800 for full Memory objects) Example: from mcp_memory_service.api import search, store, health # Search (20 tokens vs 625 tokens for MCP) results = search("architecture decisions", limit=5) for m in results.memories: print(f"{m.hash}: {m.preview}") # Store (15 tokens vs 150 tokens for MCP) store("New memory", tags=['note', 'important']) # Health (5 tokens vs 125 tokens for MCP) info = health() print(f"Backend: {info.backend}, Count: {info.count}") """ from typing import Optional, Union from .compact import CompactMemory, CompactSearchResult, CompactStorageInfo from .search import search, search_by_tag, recall from .storage import store, delete, update from .utils import health, expand_memory __all__ = [ # Search operations 'search', # Semantic search with compact results 'search_by_tag', # Tag-based search 'recall', # Time-based natural language search # Storage operations 'store', # Store new memory 'delete', # Delete by hash 'update', # Update metadata # Utilities 'health', # Service health check 'expand_memory', # Get full Memory from hash # Types 'CompactMemory', 'CompactSearchResult', 'CompactStorageInfo', ] # Version for API compatibility tracking __api_version__ = "1.0.0" ``` ```python # src/mcp_memory_service/api/search.py """Search operations with compact results.""" import asyncio from typing import Optional, Union from ..storage.factory import create_storage_backend from ..models.compact import CompactMemory, CompactSearchResult # Thread-local storage for connection reuse _storage_instance = None def _get_storage(): """Get or create storage backend instance.""" global _storage_instance if _storage_instance is None: _storage_instance = create_storage_backend() # Initialize in sync context (run once) asyncio.run(_storage_instance.initialize()) return _storage_instance def search( query: str, limit: int = 5, threshold: float = 0.0 ) -> CompactSearchResult: """ Search memories using semantic similarity. Token efficiency: ~25 tokens (query + params + results) vs ~625 tokens for MCP tool call with full Memory objects. Args: query: Search query text limit: Maximum results to return (default: 5) threshold: Minimum similarity score 0.0-1.0 (default: 0.0) Returns: CompactSearchResult with minimal memory representations Example: >>> results = search("recent architecture changes", limit=3) >>> print(results) SearchResult(found=3, shown=3) >>> for m in results.memories: ... print(f"{m.hash}: {m.preview[:50]}...") """ storage = _get_storage() # Run async operation in sync context async def _search(): query_results = await storage.retrieve(query, n_results=limit) # Convert to compact format compact = [ CompactMemory( hash=r.memory.content_hash[:8], # 8 char hash preview=r.memory.content[:200], # First 200 chars tags=tuple(r.memory.tags), # Immutable tuple created=r.memory.created_at, score=r.relevance_score ) for r in query_results if r.relevance_score >= threshold ] return CompactSearchResult( memories=tuple(compact), total=len(compact), query=query ) return asyncio.run(_search()) def search_by_tag( tags: Union[str, list[str]], limit: Optional[int] = None ) -> CompactSearchResult: """ Search memories by tags. Args: tags: Single tag or list of tags limit: Maximum results (None for all) Returns: CompactSearchResult with matching memories """ storage = _get_storage() tag_list = [tags] if isinstance(tags, str) else tags async def _search(): memories = await storage.search_by_tag(tag_list) if limit: memories = memories[:limit] compact = [ CompactMemory( hash=m.content_hash[:8], preview=m.content[:200], tags=tuple(m.tags), created=m.created_at, score=1.0 # Tag match = perfect relevance ) for m in memories ] return CompactSearchResult( memories=tuple(compact), total=len(compact), query=f"tags:{','.join(tag_list)}" ) return asyncio.run(_search()) def recall(query: str, n_results: int = 5) -> CompactSearchResult: """ Retrieve memories using natural language time expressions. Examples: - "last week" - "yesterday afternoon" - "this month" - "2 days ago" Args: query: Natural language time query n_results: Maximum results to return Returns: CompactSearchResult with time-filtered memories """ storage = _get_storage() async def _recall(): memories = await storage.recall_memory(query, n_results) compact = [ CompactMemory( hash=m.content_hash[:8], preview=m.content[:200], tags=tuple(m.tags), created=m.created_at, score=1.0 ) for m in memories ] return CompactSearchResult( memories=tuple(compact), total=len(compact), query=query ) return asyncio.run(_recall()) ``` ```python # src/mcp_memory_service/api/storage.py """Storage operations (store, delete, update).""" import asyncio from typing import Optional, Union from ..models.memory import Memory from ..utils.hashing import generate_content_hash from .search import _get_storage def store( content: str, tags: Optional[Union[str, list[str]]] = None, memory_type: Optional[str] = None, metadata: Optional[dict] = None ) -> str: """ Store a new memory. Token efficiency: ~15 tokens (params only) vs ~150 tokens for MCP tool call with schema. Args: content: Memory content text tags: Single tag or list of tags memory_type: Memory type classification metadata: Additional metadata dictionary Returns: Content hash of stored memory (8 chars) Example: >>> hash = store("Important decision", tags=['architecture', 'decision']) >>> print(f"Stored: {hash}") Stored: abc12345 """ storage = _get_storage() # Normalize tags if isinstance(tags, str): tag_list = [tags] elif tags is None: tag_list = [] else: tag_list = list(tags) # Create memory object content_hash = generate_content_hash(content) memory = Memory( content=content, content_hash=content_hash, tags=tag_list, memory_type=memory_type, metadata=metadata or {} ) # Store async def _store(): success, message = await storage.store(memory) if not success: raise RuntimeError(f"Failed to store memory: {message}") return content_hash[:8] return asyncio.run(_store()) def delete(hash: str) -> bool: """ Delete a memory by its content hash. Args: hash: Content hash (8+ characters) Returns: True if deleted, False if not found """ storage = _get_storage() async def _delete(): # If short hash provided, expand to full hash if len(hash) == 8: # Get full hash from short form (requires index lookup) memories = await storage.get_recent_memories(n=10000) full_hash = next( (m.content_hash for m in memories if m.content_hash.startswith(hash)), hash ) else: full_hash = hash success, _ = await storage.delete(full_hash) return success return asyncio.run(_delete()) def update( hash: str, tags: Optional[list[str]] = None, memory_type: Optional[str] = None, metadata: Optional[dict] = None ) -> bool: """ Update memory metadata. Args: hash: Content hash (8+ characters) tags: New tags (replaces existing) memory_type: New memory type metadata: New metadata (merges with existing) Returns: True if updated successfully """ storage = _get_storage() updates = {} if tags is not None: updates['tags'] = tags if memory_type is not None: updates['memory_type'] = memory_type if metadata is not None: updates['metadata'] = metadata async def _update(): success, _ = await storage.update_memory_metadata(hash, updates) return success return asyncio.run(_update()) ``` ```python # src/mcp_memory_service/api/utils.py """Utility functions for API.""" import asyncio from typing import Optional from ..models.memory import Memory from ..models.compact import CompactStorageInfo from .search import _get_storage def health() -> CompactStorageInfo: """ Get service health and status. Token efficiency: ~20 tokens vs ~125 tokens for MCP health check tool. Returns: CompactStorageInfo with backend, count, and ready status Example: >>> info = health() >>> print(f"Using {info.backend}, {info.count} memories") Using sqlite_vec, 1247 memories """ storage = _get_storage() async def _health(): stats = await storage.get_stats() return CompactStorageInfo( backend=stats.get('storage_backend', 'unknown'), count=stats.get('total_memories', 0), ready=stats.get('status', 'unknown') == 'operational' ) return asyncio.run(_health()) def expand_memory(hash: str) -> Optional[Memory]: """ Get full Memory object from compact hash. Use when you need complete memory details (content, embedding, etc.) after working with compact results. Args: hash: Content hash (8+ characters) Returns: Full Memory object or None if not found Example: >>> results = search("architecture", limit=5) >>> full = expand_memory(results.memories[0].hash) >>> print(full.content) # Complete content, not preview """ storage = _get_storage() async def _expand(): # Handle short hash if len(hash) == 8: memories = await storage.get_recent_memories(n=10000) full_hash = next( (m.content_hash for m in memories if m.content_hash.startswith(hash)), None ) if full_hash is None: return None else: full_hash = hash return await storage.get_by_hash(full_hash) return asyncio.run(_expand()) ``` ### 3.4 Hook Integration Pattern **Before (MCP Tool Invocation):** ```javascript // ~/.claude/hooks/core/session-start.js const { MemoryClient } = require('../utilities/memory-client'); async function retrieveMemories(gitContext) { const memoryClient = new MemoryClient(config); // MCP tool call: ~625 tokens (tool def + result) const result = await memoryClient.callTool('retrieve_memory', { query: gitContext.query, limit: 8, similarity_threshold: 0.6 }); // Result parsing adds more tokens const memories = parseToolResult(result); return memories; // 8 full Memory objects = ~6,400 tokens } ``` **After (Code Execution):** ```javascript // ~/.claude/hooks/core/session-start.js const { execSync } = require('child_process'); async function retrieveMemories(gitContext) { // Execute Python code directly: ~25 tokens total const pythonCode = ` from mcp_memory_service.api import search results = search("${gitContext.query}", limit=8) for m in results.memories: print(f"{m.hash}|{m.preview}|{','.join(m.tags)}|{m.created}") `; const output = execSync(`python -c "${pythonCode}"`, { encoding: 'utf8', timeout: 5000 }); // Parse compact results (8 memories = ~600 tokens total) const memories = output.trim().split('\n').map(line => { const [hash, preview, tags, created] = line.split('|'); return { hash, preview, tags: tags.split(','), created: parseFloat(created) }; }); return memories; // 90% token reduction: 6,400 → 600 tokens } ``` --- ## 4. Implementation Examples from Similar Projects ### 4.1 MCP Python Interpreter (Nov 2024) **Key Features:** - Sandboxed code execution in isolated directories - File read/write capabilities through code - Iterative error correction (write → run → fix → repeat) **Relevant Patterns:** ```python # Tool exposure as filesystem # Instead of: 33 tool definitions in context # Use: import from known locations from mcp_memory_service.api import search, store, health # LLM can discover functions via IDE-like introspection help(search) # Returns compact docstring ``` ### 4.2 CodeAgents Framework **Token Efficiency Techniques:** 1. **Typed Variables**: `memories: list[CompactMemory]` (10 tokens) vs "a list of memory objects with content and metadata" (15+ tokens) 2. **Control Structures**: `for m in memories if m.score > 0.7` (12 tokens) vs calling filter tool (125+ tokens) 3. **Reusable Subroutines**: Single function encapsulates common pattern **Application to Memory Service:** ```python # Compact search and filter in code (30 tokens total) from mcp_memory_service.api import search results = search("architecture", limit=20) relevant = [m for m in results.memories if 'decision' in m.tags and m.score > 0.7] print(f"Found {len(relevant)} relevant memories") # vs MCP tools (625+ tokens) # 1. retrieve_memory tool call (125 tokens) # 2. Full results parsing (400 tokens) # 3. search_by_tag tool call (125 tokens) # 4. Manual filtering logic in prompt (100+ tokens) ``` --- ## 5. Potential Challenges and Mitigation Strategies ### Challenge 1: Async/Sync Context Mismatch **Problem:** Hooks run in Node.js (sync), storage backends use asyncio (async) **Mitigation:** ```python # Provide sync wrappers that handle async internally import asyncio def search(query: str, limit: int = 5): """Sync wrapper for async storage operations.""" async def _search(): storage = _get_storage() results = await storage.retrieve(query, limit) return _convert_to_compact(results) # Run in event loop return asyncio.run(_search()) ``` **Trade-offs:** - ✅ Simple API for hook developers - ✅ No async/await in JavaScript - ⚠️ Small overhead (~1-2ms) for event loop creation - ✅ Acceptable for hooks (not high-frequency calls) ### Challenge 2: Connection Management **Problem:** Multiple calls from hooks shouldn't create new connections each time **Mitigation:** ```python # Thread-local storage instance (reused across calls) _storage_instance = None def _get_storage(): global _storage_instance if _storage_instance is None: _storage_instance = create_storage_backend() asyncio.run(_storage_instance.initialize()) return _storage_instance ``` **Benefits:** - ✅ Single connection per process - ✅ Automatic initialization on first use - ✅ No manual connection cleanup needed ### Challenge 3: Backward Compatibility **Problem:** Existing users rely on MCP tools, can't break them **Mitigation Strategy:** ```python # Phase 1: Add code execution API alongside MCP tools # Both interfaces work simultaneously # - MCP server (server.py) continues operating # - New api/ module available for direct import # - Users opt-in to new approach # Phase 2: Encourage migration with documentation # - Performance comparison benchmarks # - Token usage metrics # - Migration guide with examples # Phase 3 (Optional): Deprecation path # - Log warnings when MCP tools used # - Offer automatic migration scripts # - Eventually remove or maintain minimal MCP support ``` **Migration Timeline:** ``` Week 1-2: Core API implementation + tests Week 3: Session hook migration + validation Week 4-5: Search operation migration Week 6+: Optional optimizations + additional operations ``` ### Challenge 4: Error Handling in Compact Mode **Problem:** Less context in compact results makes debugging harder **Mitigation:** ```python # Compact results for normal operation results = search("query", limit=5) # Expand individual memory for debugging if results.memories: full_memory = expand_memory(results.memories[0].hash) print(full_memory.content) # Complete content print(full_memory.metadata) # All metadata # Health check provides diagnostics info = health() if not info.ready: raise RuntimeError(f"Storage backend {info.backend} not ready") ``` ### Challenge 5: Performance with Large Result Sets **Problem:** Converting 1000s of memories to compact format **Mitigation:** ```python # Lazy evaluation for large queries from typing import Iterator def search_iter(query: str, batch_size: int = 50) -> Iterator[CompactMemory]: """Streaming search results for large queries.""" storage = _get_storage() offset = 0 while True: batch = storage.retrieve(query, n_results=batch_size, offset=offset) if not batch: break for result in batch: yield CompactMemory(...) offset += batch_size # Use in hooks for memory in search_iter("query", batch_size=10): if some_condition(memory): break # Early termination saves processing ``` --- ## 6. Recommended Tools and Libraries ### 6.1 Type Safety and Validation **Pydantic v2** (optional, for advanced use cases) ```python from pydantic import BaseModel, Field, field_validator class SearchParams(BaseModel): query: str = Field(min_length=1, max_length=1000) limit: int = Field(default=5, ge=1, le=100) threshold: float = Field(default=0.0, ge=0.0, le=1.0) @field_validator('query') def query_not_empty(cls, v): if not v.strip(): raise ValueError('Query cannot be empty') return v ``` **Benefits:** - ✅ Runtime validation with clear error messages - ✅ JSON schema generation for documentation - ✅ 25-50% overhead acceptable for API boundaries - ⚠️ Use NamedTuple for internal compact types (lighter weight) ### 6.2 Testing and Validation **pytest-asyncio** (already in use) ```python # tests/api/test_search.py import pytest from mcp_memory_service.api import search, store, health def test_search_returns_compact_results(): """Verify search returns CompactSearchResult.""" results = search("test query", limit=3) assert results.total >= 0 assert len(results.memories) <= 3 assert all(isinstance(m.hash, str) for m in results.memories) assert all(len(m.hash) == 8 for m in results.memories) def test_token_efficiency(): """Benchmark token usage vs MCP tools.""" import tiktoken enc = tiktoken.encoding_for_model("gpt-4") # Compact API results = search("architecture", limit=5) compact_repr = str(results.memories) compact_tokens = len(enc.encode(compact_repr)) # Compare with full Memory objects from mcp_memory_service.storage import get_storage full_results = get_storage().retrieve("architecture", n_results=5) full_repr = str([r.memory.to_dict() for r in full_results]) full_tokens = len(enc.encode(full_repr)) reduction = (1 - compact_tokens / full_tokens) * 100 assert reduction >= 85, f"Expected 85%+ reduction, got {reduction:.1f}%" ``` ### 6.3 Documentation Generation **Sphinx with autodoc** (existing infrastructure) ```python # Docstrings optimized for both humans and LLMs def search(query: str, limit: int = 5) -> CompactSearchResult: """ Search memories using semantic similarity. This function provides a token-efficient alternative to the retrieve_memory MCP tool, reducing token usage by ~90%. Token Cost Analysis: - Function call: ~20 tokens (import + call) - Results: ~73 tokens per memory - Total for 5 results: ~385 tokens vs MCP Tool: - Tool definition: ~125 tokens - Full Memory results: ~500 tokens per memory - Total for 5 results: ~2,625 tokens Reduction: 85% (2,625 → 385 tokens) Performance: - Cold call: ~50ms (storage initialization) - Warm call: ~5ms (connection reused) Args: query: Search query text. Supports natural language. Examples: "recent architecture decisions", "authentication implementation notes" limit: Maximum number of results to return. Higher values increase token cost proportionally. Recommended: 3-8 for hooks, 10-20 for interactive use. Returns: CompactSearchResult containing: - memories: Tuple of CompactMemory objects - total: Number of results found - query: Original query string Raises: RuntimeError: If storage backend not initialized ValueError: If query empty or limit invalid Example: >>> from mcp_memory_service.api import search >>> results = search("authentication setup", limit=3) >>> print(results) SearchResult(found=3, shown=3) >>> for m in results.memories: ... print(f"{m.hash}: {m.preview[:50]}...") abc12345: Implemented OAuth 2.1 authentication with... def67890: Added JWT token validation middleware for... ghi11121: Fixed authentication race condition in... See Also: - search_by_tag: Filter by specific tags - recall: Time-based natural language queries - expand_memory: Get full Memory object from hash """ ... ``` ### 6.4 Performance Monitoring **structlog** (lightweight, JSON-compatible) ```python import structlog logger = structlog.get_logger(__name__) def search(query: str, limit: int = 5): with logger.contextualize(operation="search", query=query, limit=limit): start = time.perf_counter() try: results = _do_search(query, limit) duration_ms = (time.perf_counter() - start) * 1000 logger.info( "search_completed", duration_ms=duration_ms, results_count=len(results.memories), token_estimate=len(results.memories) * 73 # Compact token estimate ) return results except Exception as e: logger.error("search_failed", error=str(e), exc_info=True) raise ``` --- ## 7. Migration Approach: Gradual Transition ### Phase 1: Core Infrastructure (Week 1-2) **Deliverables:** - ✅ `src/mcp_memory_service/api/` module structure - ✅ `CompactMemory`, `CompactSearchResult`, `CompactStorageInfo` types - ✅ `search()`, `store()`, `health()` functions - ✅ Unit tests with 90%+ coverage - ✅ Documentation with token usage benchmarks **Success Criteria:** - All functions work in sync context (no async/await in API) - Connection reuse validated (single storage instance) - Token reduction measured: 85%+ for search operations - Performance overhead <5ms per call (warm) **Risk: Low** - New code, no existing dependencies ### Phase 2: Session Hook Optimization (Week 3) **Target:** Session-start hook (highest impact: 3,600-9,600 tokens → 900-2,400 tokens) **Changes:** ```javascript // Before: MCP tool invocation const { MemoryClient } = require('../utilities/memory-client'); const memoryClient = new MemoryClient(config); const result = await memoryClient.callTool('retrieve_memory', {...}); // After: Code execution with fallback const { execSync } = require('child_process'); try { // Try code execution first (fast, efficient) const output = execSync('python -c "from mcp_memory_service.api import search; ..."'); const memories = parseCompactResults(output); } catch (error) { // Fallback to MCP if code execution fails console.warn('Code execution failed, falling back to MCP:', error); const result = await memoryClient.callTool('retrieve_memory', {...}); const memories = parseMCPResult(result); } ``` **Success Criteria:** - 75%+ token reduction measured in real sessions - Fallback mechanism validates graceful degradation - Hook execution time <500ms (no user-facing latency increase) - Zero breaking changes for users **Risk: Medium** - Touches production hook code, but has fallback ### Phase 3: Search Operation Optimization (Week 4-5) **Target:** Mid-conversation and topic-change hooks **Deliverables:** - ✅ `search_by_tag()` implementation - ✅ `recall()` natural language time queries - ✅ Streaming search (`search_iter()`) for large results - ✅ Migration guide with side-by-side examples **Success Criteria:** - 90%+ token reduction for search-heavy workflows - Documentation shows before/after comparison - Community feedback collected and addressed **Risk: Low** - Builds on Phase 1 foundation ### Phase 4: Extended Operations (Week 6+) **Optional Enhancements:** - Document ingestion API - Batch operations (store/delete multiple) - Memory consolidation triggers - Advanced filtering (memory_type, time ranges) --- ## 8. Success Metrics and Validation ### 8.1 Token Reduction Targets | Operation | Current (MCP) | Target (Code Exec) | Reduction | |-----------|---------------|-------------------|-----------| | Session start hook | 3,600-9,600 | 900-2,400 | 75% | | Search (5 results) | 2,625 | 385 | 85% | | Store memory | 150 | 15 | 90% | | Health check | 125 | 20 | 84% | | Document ingestion (50 PDFs) | 57,400 | 8,610 | 85% | **Annual Savings (Conservative):** - 10 users x 5 sessions/day x 365 days x 6,000 tokens saved = **109.5M tokens/year** - At $0.15/1M tokens (Claude Opus input): **$16.43/year saved** per 10-user deployment **Annual Savings (Aggressive - 100 users):** - 100 users x 10 sessions/day x 365 days x 6,000 tokens = **2.19B tokens/year** - At $0.15/1M tokens: **$328.50/year saved** ### 8.2 Performance Metrics **Latency Targets:** - Cold start (first call): <100ms - Warm calls: <10ms - Hook total execution: <500ms (no degradation from current) **Memory Usage:** - Compact result set (5 memories): <5KB - Full result set (5 memories): ~50KB - 90% memory reduction for hook injection ### 8.3 Compatibility Validation **Testing Matrix:** - ✅ Existing MCP tools continue working (100% backward compat) - ✅ New code execution API available alongside MCP - ✅ Fallback mechanism activates on code execution failure - ✅ All storage backends compatible (SQLite-Vec, Cloudflare, Hybrid) - ✅ No breaking changes to server.py or existing APIs --- ## 9. Implementation Timeline ``` Week 1: Core Infrastructure ├── Design compact types (CompactMemory, CompactSearchResult) ├── Implement api/__init__.py with public exports ├── Create search.py with search(), search_by_tag(), recall() ├── Add storage.py with store(), delete(), update() └── Write utils.py with health(), expand_memory() Week 2: Testing & Documentation ├── Unit tests for all API functions ├── Integration tests with storage backends ├── Token usage benchmarking ├── API documentation with examples └── Migration guide draft Week 3: Session Hook Migration ├── Update session-start.js to use code execution ├── Add fallback to MCP tools ├── Test with SQLite-Vec and Cloudflare backends ├── Validate token reduction (target: 75%+) └── Deploy to beta testers Week 4-5: Search Operations ├── Update mid-conversation.js ├── Update topic-change.js ├── Implement streaming search for large queries ├── Document best practices └── Gather community feedback Week 6+: Polish & Extensions ├── Additional API functions (batch ops, etc.) ├── Performance optimizations ├── Developer tools (token calculators, debuggers) └── Comprehensive documentation ``` --- ## 10. Recommendations Summary ### Immediate Next Steps (Week 1) 1. **Create API Module Structure** ```bash mkdir -p src/mcp_memory_service/api touch src/mcp_memory_service/api/{__init__,compact,search,storage,utils}.py ``` 2. **Implement Compact Types** - `CompactMemory` with NamedTuple - `CompactSearchResult` with tuple of memories - `CompactStorageInfo` for health checks 3. **Core Functions** - `search()` - Semantic search with compact results - `store()` - Store with minimal params - `health()` - Quick status check 4. **Testing Infrastructure** - Unit tests for each function - Token usage benchmarks - Performance profiling ### Key Design Decisions 1. **Use NamedTuple for Compact Types** - Fast (C-based), immutable, type-safe - 60-90% size reduction vs dataclass - Clear field names (`.hash` not `[0]`) 2. **Sync Wrappers for Async Operations** - Hide asyncio complexity from hooks - Use `asyncio.run()` internally - Connection reuse via global instance 3. **Graceful Degradation** - Code execution primary - MCP tools fallback - Zero breaking changes 4. **Incremental Migration** - Start with session hooks (high impact) - Gather metrics and feedback - Expand to other operations ### Expected Outcomes **Token Efficiency:** - ✅ 75% reduction in session hooks - ✅ 85-90% reduction in search operations - ✅ 13-183 million tokens saved annually **Performance:** - ✅ <10ms per API call (warm) - ✅ <500ms hook execution (no degradation) - ✅ 90% memory footprint reduction **Compatibility:** - ✅ 100% backward compatible - ✅ Opt-in adoption model - ✅ MCP tools continue working --- ## 11. References and Further Reading ### Research Sources 1. **Anthropic Resources:** - "Code execution with MCP: Building more efficient agents" (Nov 2025) - "Claude Code Best Practices" - Token efficiency guidelines - MCP Protocol Documentation - Tool use patterns 2. **Academic Research:** - "CodeAgents: A Token-Efficient Framework for Codified Multi-Agent Reasoning" (arxiv.org/abs/2507.03254) - Token consumption analysis in LLM agent systems 3. **Python Best Practices:** - "Dataclasses vs NamedTuple vs TypedDict" performance comparisons - Python API design patterns (hakibenita.com) - Async/sync bridging patterns 4. **Real-World Implementations:** - mcp-python-interpreter server - Anthropic's MCP server examples - LangChain compact result types ### Internal Documentation - `src/mcp_memory_service/storage/base.py` - Storage interface - `src/mcp_memory_service/models/memory.py` - Memory model - `src/mcp_memory_service/server.py` - MCP server (3,721 lines) - `~/.claude/hooks/core/session-start.js` - Current hook implementation --- ## Conclusion The research validates the feasibility and high value of implementing a code execution interface for mcp-memory-service. Industry trends (Anthropic's MCP code execution announcement, CodeAgents framework) align with the proposal, and the current codebase architecture provides a solid foundation for gradual migration. **Key Takeaways:** 1. **85-90% token reduction is achievable** through compact types and direct function calls 2. **Backward compatibility is maintained** via fallback mechanisms and parallel operation 3. **Proven patterns exist** in mcp-python-interpreter and similar projects 4. **Incremental approach reduces risk** while delivering immediate value 5. **Annual savings of 13-183M tokens** justify development investment **Recommended Action:** Proceed with Phase 1 implementation (Core Infrastructure) targeting Week 1-2 completion, with session hook migration as first production use case. --- **Document Version:** 1.0 **Last Updated:** November 6, 2025 **Author:** Research conducted for Issue #206 **Status:** Ready for Review and Implementation

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/doobidoo/mcp-memory-service'

If you have feedback or need assistance with the MCP directory API, please join our Discord server