/** * Example: Generic MCP-Memory Bridge (Darwin → any MCP-compliant memory server) * * Wraps the `FeedbackStore` interface from closed-loop-feedback.ts with a * thin JSON-RPC 2.0 client for MCP. Works with any MCP server that exposes * a write tool (e.g. `memory_learn`) and a read tool (e.g. `memory_search`). * * Default wiring targets `@studiomeyer/local-memory-mcp` (zero-config, lives * in a single SQLite file under the OS data dir, no cloud, no API keys). * Drop in Mem0 / Zep / Letta / Cognee / your own self-hosted MCP server by * overriding `writeTool` / `readTool` and providing schema mappers. * * Why raw JSON-RPC and not @modelcontextprotocol/sdk? * - Darwin keeps a "zero hard deps" policy (peerDependencies only). * - MCP wire protocol is three messages: initialize, tools/list, tools/call. * - Keeps the bridge testable without a mock SDK. * * Why a single bridge instead of per-provider clients? * - The wire is the same. Only tool names and arg/result shapes vary. * - One reconnect / lifecycle path, one place to harden timeouts. * * Run demo (requires `@studiomeyer/local-memory-mcp` installed): * npm install -g @studiomeyer/local-memory-mcp * npx tsx examples/mcp-memory-bridge.ts */ import type { FeedbackRecord, FeedbackStore } from './closed-loop-feedback.js'; /** A lesson retrieved from the memory store — what gets injected into the next prompt. */ export interface Lesson { /** Free-form text the agent should consider next run. */ content: string; /** Tags surfaced by the store (may be empty). */ tags: string[]; /** Optional ranking score (semantics depend on the backing store). */ score?: number; } /** Options bag for `fetchRelevant()`. Object-shaped for future-compat. */ export interface FetchRelevantOptions { query: string; /** Max number of lessons. Default: 5. */ limit?: number; /** Optional tag filter — passed through to the read tool if it honours it. */ tags?: string[]; /** * Per-call timeout override in ms. Falls back to `requestTimeoutMs` from * the bridge config (default 10 000 ms). Useful for slow embedding-backed * stores or when a particular query is known to be expensive. */ timeoutMs?: number; } /** Options bag for `save()`. Mirrors the per-call timeout knob on read. */ export interface SaveOptions { /** * Per-call timeout override in ms. Falls back to `requestTimeoutMs` from * the bridge config (default 10 000 ms). */ timeoutMs?: number; } /** Darwin-side store contract extended with retrieval + lifecycle. */ export interface RetrievableFeedbackStore extends FeedbackStore { /** * Retrieve lessons relevant to a query. * Backward-compatible: accepts either a plain string (legacy) or an * options object. The bag form is preferred for v0.4.7+. */ fetchRelevant(queryOrOpts: string | FetchRelevantOptions, limit?: number): Promise; close(): Promise; } /** Transport-agnostic configuration. */ export interface McpMemoryConfig { transport: 'stdio' | 'http'; /** * - stdio: [command, ...args] for `spawn`. Example: ['npx', '-y', '@studiomeyer/local-memory-mcp']. * - http: full URL of the MCP endpoint. Example: 'https://memory.example.com/mcp'. */ endpoint: string | string[]; /** Optional Authorization header value for http transport (e.g. 'Bearer …'). */ authHeader?: string; /** Name of the tool that writes a memory. Default: 'memory_learn'. */ writeTool?: string; /** Name of the tool that searches memories. Default: 'memory_search'. */ readTool?: string; /** Translate a FeedbackRecord to the writeTool's input arguments. */ mapWriteArgs?: (rec: FeedbackRecord) => Record; /** Translate a raw tools/call result into Lesson[]. */ mapReadResult?: (toolResult: unknown) => Lesson[]; /** Per-RPC timeout in ms (default: 10_000). */ requestTimeoutMs?: number; /** Number of automatic respawn attempts after a stdio EPIPE/exit (default: 1). */ maxRespawn?: number; /** * HTTP retry policy. Number of retries for 5xx + transient network errors * (ECONNRESET / ETIMEDOUT / abort). Default: 2. Exponential backoff * starting at 250 ms. */ httpMaxRetries?: number; /** Protocol version handed to the server during initialize. Default: '2025-11-25'. */ protocolVersion?: string; /** Logger for diagnostics. Defaults to console.warn on errors only. */ logger?: { warn: (msg: string) => void; debug?: (msg: string) => void; }; } /** * Base class for bridge errors. Discriminates protocol-level (server-side * JSON-RPC error responses) from transport-level (local timeouts, network * resets, EPIPE, child process exits). Callers can branch on `kind` to * decide retry-vs-fail-loud without parsing the message text. * * Mirrors the split MCP TypeScript SDK v2 uses internally (`ProtocolError` * vs `SdkError`). We keep our own classes to preserve the bridge's * zero-hard-dep policy. */ export declare class McpBridgeError extends Error { /** 'protocol' = JSON-RPC error from the server, 'transport' = local. */ readonly kind: 'protocol' | 'transport'; /** JSON-RPC error code for protocol errors, or a stable string for transport errors. */ readonly code: number | string; /** Which transport produced the error. */ readonly transport: 'stdio' | 'http'; constructor(opts: { message: string; kind: 'protocol' | 'transport'; code: number | string; transport: 'stdio' | 'http'; cause?: unknown; }); } /** JSON-RPC error from the server. `code` is the numeric JSON-RPC code. */ export declare class McpBridgeProtocolError extends McpBridgeError { constructor(opts: { code: number; serverMessage: string; transport: 'stdio' | 'http'; }); } /** * Local transport-layer error (timeout, EPIPE, network reset, child exit, * abort, DNS). `code` is a short stable string for branching: * 'timeout' | 'closed' | 'transient' | 'child_exit' | 'spawn_failed' | 'http_status' */ export declare class McpBridgeTransportError extends McpBridgeError { constructor(opts: { message: string; code: 'timeout' | 'closed' | 'transient' | 'child_exit' | 'spawn_failed' | 'http_status'; transport: 'stdio' | 'http'; cause?: unknown; }); } /** Default mapping for `memory_learn` shape (local-memory-mcp, mcp-nex). */ export declare function defaultMapWriteArgs(rec: FeedbackRecord): Record; /** * Default mapping for `memory_search` shape. * Accepts both the raw MCP CallToolResult shape `{content:[{text:JSON}]}` * and a pre-parsed object — that keeps the parser tolerant when a custom * server returns structured content directly. */ export declare function defaultMapReadResult(raw: unknown): Lesson[]; /** * Generic MCP-Memory bridge. * * Lifecycle: * const m = openLocalMemory(); // or openRemoteMemory(...) * await m.save({...}); // FeedbackStore.save * const lessons = await m.fetchRelevant({ query: 'topic', limit: 5 }); * await m.close(); * * Errors in save/fetch are swallowed at the FeedbackStore level (the * closed-loop callsite logs them) — the bridge surfaces them as thrown * Errors so the caller decides whether to fail-loud or fail-quiet. */ export declare class McpMemoryBridge implements RetrievableFeedbackStore { private readonly config; private child; private nextId; private pending; private stdoutBuffer; private respawnCount; private initialized; private initInFlight; private closed; constructor(config: McpMemoryConfig); /** * Persist a feedback record. Accepts an optional second arg for per-call * overrides (currently `timeoutMs`). The signature stays a structural * super-type of `FeedbackStore.save(record)`, so callers using the base * interface keep working unchanged. */ save(record: FeedbackRecord, opts?: SaveOptions): Promise; fetchRelevant(queryOrOpts: string | FetchRelevantOptions, legacyLimit?: number): Promise; close(): Promise; private ensureReady; private initialize; private spawnStdio; private onChildExit; private onStdoutData; private handleIncomingLine; private sendStdio; /** * Send one JSON-RPC request and await the matching response. * Used for both initialize and tools/call. * F1: no inline respawn — ensureReady/initialize is the only path that * spawns. This avoids the double-spawn race where rpc() and ensureReady() * both spawn children competing for `this.child`. * * `timeoutMs` overrides the bridge-level `requestTimeoutMs` for this call. */ private rpc; private rpcStdio; /** * HTTP transport with bounded retries on 5xx + transient network errors. * Per-attempt timeout via AbortController. Honors `httpMaxRetries`. * * Sends the `MCP-Protocol-Version` HTTP header per MCP spec 2025-11-25 * §"HTTP Protocol Versioning". Strict servers MAY return 400 if it is * missing or unsupported. */ private rpcHttp; /** * Invoke a tool. Accepts a per-call `opts.timeoutMs` override that takes * precedence over the bridge-level `requestTimeoutMs` config. */ private callTool; } /** * Parse an HTTP body that may be either plain JSON or Server-Sent Events. * F2: SSE framing is `data: …\n\n` per event with possible multi-line data * payloads. We take the last well-formed event so a streamed in-progress * frame doesn't override the final result. */ export declare function parseHttpRpcBody(text: string): { result?: unknown; error?: unknown; id?: unknown; jsonrpc?: unknown; }; /** * Default zero-config wiring: spawn `@studiomeyer/local-memory-mcp` via npx. * * Requires the package to be reachable (either installed globally or via * `npx -y @studiomeyer/local-memory-mcp` which fetches on first run). */ export declare function localMemory(overrides?: Partial): McpMemoryBridge; /** * Connect to a remote MCP server over HTTP. * * Example: connect to your hosted memory.studiomeyer.io endpoint * remoteMemory('https://memory.studiomeyer.io/mcp', { authHeader: `Bearer ${KEY}` }) * * Example: Mem0 with tool-name + arg aliasing — see `mem0Preset()` below * for a one-line spread that handles all of this for you. */ export declare function remoteMemory(url: string, overrides?: Partial): McpMemoryBridge; /** * Mem0 MCP server preset — drop-in overrides that match the exact tool * names + arg shapes the `mem0ai/mem0-mcp` server expects. * * Reference: github.com/mem0ai/mem0-mcp — tools are `add_memory` (NOT * `mem0_add` or `add_memories`) and `search_memories` (NOT `search_memory`). * Add-args require one of `text`/`messages` + at least one of * `user_id`/`agent_id`/`run_id`. Search-result rows expose the lesson * text under `memory` (not `content`/`body`/`text`). * * Usage with the hosted Mem0 platform: * \`\`\`ts * const memory = remoteMemory('https://api.mem0.ai/mcp', { * authHeader: `Bearer ${process.env.MEM0_KEY}`, * ...mem0Preset({ userId: 'darwin-agent' }), * }); * \`\`\` * * Usage with a locally spawned mem0-mcp server (stdio): * \`\`\`ts * const memory = new McpMemoryBridge({ * transport: 'stdio', * endpoint: ['uvx', 'mem0-mcp'], * ...mem0Preset({ userId: 'darwin-agent' }), * }); * \`\`\` * * Spread order matters: put `...mem0Preset(...)` AFTER other overrides if * you want the preset to win, or BEFORE if you want to override the * preset's defaults yourself. */ export declare function mem0Preset(opts?: { /** * Mem0 scope identifier. Required because `add_memory` rejects writes * without at least one of user_id / agent_id / run_id. */ userId?: string; agentId?: string; runId?: string; /** Extra metadata merged into every write under the `metadata` key. */ defaultMetadata?: Record; }): Partial; //# sourceMappingURL=mcp-memory-bridge.d.ts.map