fix(mcp): enforce recall limit, align server version, scope the client claim
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.
This commit is contained in:
parent
45c1a3c633
commit
0af516ae69
2 changed files with 30 additions and 3 deletions
16
README.md
16
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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}],
|
||||
|
|
|
|||
Loading…
Reference in a new issue