From 0af516ae697c07954b85dcbbbf1f3b9d5da50f76 Mon Sep 17 00:00:00 2001 From: RCLL Date: Mon, 24 Aug 2026 08:46:24 +0300 Subject: [PATCH] fix(mcp): enforce recall limit, align server version, scope the client claim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three things found by actually running the server over stdio against a live backend instead of describing it. 1. recall ignored 'limit'. The backend treats it as a retrieval hint and returns everything inside its own token budget — 113 facts, 43 KB of JSON, for a request that asked for 2. The tool's own description ('Max results') was therefore false, and every recall spent roughly 10k tokens of the caller's context. The MCP layer now enforces the requested limit and reports 'N of M' when it truncated: limit=2 goes from 43541 to 1000 chars. 2. serverInfo.version said 1.0.0 while package.json and server.json say 0.1.0. The MCP registry validates the resolved package against the manifest, so a version that disagrees with the tarball is a review failure. 3. The README claimed 'Claude Code, OpenClaw, Cursor, etc.' None of those was tested. Replaced with what was measured — MCP 2025-06-18, initialize/tools/list/tools/call round-trip, memory_recall returning real results over stdio — plus the Claude Code config we actually run. --- README.md | 16 +++++++++++++++- mcp-server/server.js | 17 +++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cdc2c0f2..e26f2232 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,16 @@ Dimension is detected automatically. ⚠️ Switching models changes the vector ## MCP Server -The `mcp-server/` directory contains a standalone [MCP](https://modelcontextprotocol.io) server. Any MCP-compatible client (Claude Code, OpenClaw, Cursor, etc.) connects and gets structured long-term memory. +The `mcp-server/` directory contains a standalone [MCP](https://modelcontextprotocol.io) server over stdio. + +**What is actually verified**, as of 2026-08-24, against a live backend: protocol version +`2025-06-18`; `initialize`, `tools/list` and `tools/call` all round-trip; `memory_recall` +returns real results. That is a protocol-level check run directly over stdio — not a +client-by-client compatibility matrix. + +Any client that speaks MCP over stdio should therefore work, but we have not sat in front +of each one. Listed below is the config we run ourselves (Claude Code) and no others. If +you get it working with a different client, a PR to this section is the useful kind. ### Tools @@ -139,6 +148,11 @@ The `mcp-server/` directory contains a standalone [MCP](https://modelcontextprot `memory_recall` is the only one of the five that never calls a model. `memory_reflect` is an agentic loop with repeated LLM calls — if you expose this server to anything untrusted, expose `memory_recall` alone. +One thing worth knowing before you budget context: the backend treats `limit` on recall as +a retrieval hint, not a result cap — it returns everything inside its own token budget, +around 110 facts. The MCP layer enforces your `limit` on the way out, so a `limit: 2` recall +costs about 1 KB instead of 43 KB. Recall spends no model call; it does spend context. + ### Setup ```bash diff --git a/mcp-server/server.js b/mcp-server/server.js index 8f4e7c6f..80abeef5 100644 --- a/mcp-server/server.js +++ b/mcp-server/server.js @@ -98,7 +98,10 @@ async function hindsightRequest(method, path, body = null) { const server = new McpServer({ name: 'rcll', - version: '1.0.0', + // Keep in step with package.json and server.json — the MCP registry validates + // the package it resolves against this manifest, and a version that disagrees + // with the tarball is a review failure, not a cosmetic drift. + version: '0.1.0', }); // Tool 1: memory_retain @@ -177,7 +180,7 @@ server.tool( const result = await hindsightRequest('POST', `/${bankId}/memories/recall`, body); - const memories = (result.results || []).map(r => ({ + const all = (result.results || []).map(r => ({ id: r.id || r.uuid || null, text: r.text, type: r.type, @@ -187,6 +190,13 @@ server.tool( hall: r.hall || null, })); + // The backend treats `limit` as a retrieval hint, not a result cap: it returns + // everything that fits its own token budget (~110 facts, ~40 KB of JSON) no + // matter what we ask for. Unenforced here, the tool's own description would be + // false and every recall would spend ~10k tokens of the caller's context. + const effectiveLimit = body.limit; + const memories = all.slice(0, effectiveLimit); + return { content: [{ type: 'text', @@ -194,6 +204,9 @@ server.tool( success: true, bank_id: bankId, count: memories.length, + ...(all.length > memories.length + ? { returned_of_available: `${memories.length} of ${all.length}` } + : {}), memories, }, null, 2), }],