Status: Accepted Date: 2026-03-03 Authors: RuVector Team Deciders: ruv Supersedes: N/A Related: ADR-059 (Shared Brain Google Cloud Deployment), ADR-060 (Shared Brain Capabilities), ADR-064 (Pi Brain Infrastructure)
v0.2.7 shipped proxy-aware fetch and new brain API types/routes in the Rust server (types.rs, store.rs, routes.rs), but the Cloud Run service at pi.ruv.io was serving a stale pre-built binary — the Dockerfile copies a pre-compiled mcp-brain-server ELF from the repo root rather than building from source. The binary at ./mcp-brain-server predated the v0.2.7 Rust changes, so scored search, paginated list, POST /v1/verify, and enhanced transfer all returned old formats or 404.
Deep review uncovered six bugs across CLI, MCP, and deployment:
The crates/mcp-brain-server/Dockerfile does COPY mcp-brain-server /usr/local/bin/mcp-brain-server — it copies a pre-built binary from the Docker build context, not from a Cargo build step. The binary at the repo root was compiled before the v0.2.7 Rust changes (ScoredBrainMemory, ListResponse, /v1/verify), so Cloud Run was running old code despite the source being updated.
proxyFetch() (cli.js line ~174) provides a curl-based fallback when Node's fetch() cannot reach the proxy. The fallback constructs a fake Response object with status: 200 and headers: new Map() regardless of actual HTTP status. This means:
- The 204 guard in
brainFetch()(resp.status === 204) never triggers resp.headers.get('content-length')returnsundefined(Map, not Headers)- DELETE operations returning 204 with empty body crash on
JSON.parse('') - Non-2xx errors silently appear as success
brainFetch() unconditionally called resp.json(). While the 204 guard was added in the initial v0.2.8 pass, it was insufficient alone because of the proxyFetch fallback (1.2 above).
The AGI subcommands (brain agi status, brain agi sona, etc.) use a separate fetchBrainEndpoint() function (line ~8276) that also unconditionally calls resp.json() without a 204 guard.
The MCP tool schema for brain_list only declared category and limit, but the handler reads args.offset, args.sort, and args.tags. Claude (or any MCP client) could not discover or send these parameters.
The sync handler at MCP line ~3419 hardcoded url = ${brainUrl}/v1/lora/latest without appending the direction query parameter from args.direction. The pull/push/both parameter was silently dropped.
The shared brain tool handler (MCP line ~3426) does const result = await resp.json() unconditionally. DELETE returning 204 crashes the same way as the CLI.
Compile mcp-brain-server from source (cargo build --release in crates/mcp-brain-server/), copy the fresh binary to the repo root, and redeploy via Cloud Build + Cloud Run. This activates:
ScoredBrainMemorywithscore: f64in search resultsListResponse { memories, total_count, offset }paginated envelopePOST /v1/verifyendpoint for witness chain verification- Enhanced transfer warnings with domain-level safety checks
Capture actual HTTP status from curl via -w '\n%{http_code}', parse the status code from the last line, and construct the Response object with correct ok, status, and safe json() that returns {} for empty bodies:
const args = ['-sS', '-L', '--max-time', '30', '-w', '\n%{http_code}'];
// ...
const lines = stdout.trimEnd().split('\n');
const statusCode = parseInt(lines.pop(), 10) || 200;
const body = lines.join('\n').trim();
const ok = statusCode >= 200 && statusCode < 300;
return {
ok,
status: statusCode,
statusText: ok ? 'OK' : `HTTP ${statusCode}`,
text: async () => body,
json: async () => body ? JSON.parse(body) : {},
headers: new Map(),
};Both functions now check for 204 or empty content-length before calling resp.json():
if (resp.status === 204 || resp.headers.get('content-length') === '0') return {};
return resp.json();Added .option('--json', 'Output as JSON') and the standard JSON gate to brain share, brain vote, brain delete, and brain sync.
Added offset, sort, and tags properties to the brain_list tool inputSchema, matching the handler's usage.
Changed the sync handler to append ?direction=... from args.direction:
case 'sync': {
const p = new URLSearchParams();
if (args.direction) p.set('direction', args.direction);
url = `${brainUrl}/v1/lora/latest${p.toString() ? '?' + p : ''}`;
break;
}Changed const result = await resp.json() to:
const result = (resp.status === 204 || resp.headers.get('content-length') === '0') ? {} : await resp.json();The Rust server had POST /v1/pages and GET /v1/pages/:id but no GET /v1/pages to list all pages. The CLI brain page list command tried to call this endpoint and got 405 Method Not Allowed.
Added:
PageSummaryandListPagesResponsetypes intypes.rslist_pages()store method instore.rslist_pagesroute handler inroutes.rswith pagination (limit,offset),statusfilter, and sort byupdated_atdescending- Registered route:
.route("/v1/pages", get(list_pages).post(create_page))
The brain page and brain node CLI commands (Brainpedia ADR-062, WASM Nodes ADR-063) were only available via the Rust SSE MCP server, not in the Node.js stdio MCP server. This meant Claude Desktop (stdio transport) could not access page or node operations.
Added 9 new MCP tool definitions and handlers to mcp-server.js:
| Tool | Method | Endpoint |
|---|---|---|
brain_page_list |
GET | /v1/pages |
brain_page_get |
GET | /v1/pages/:id |
brain_page_create |
POST | /v1/pages |
brain_page_update |
PUT | /v1/pages/:id |
brain_page_delete |
DELETE | /v1/pages/:id |
brain_node_list |
GET | /v1/nodes |
brain_node_get |
GET | /v1/nodes/:id |
brain_node_publish |
POST | /v1/nodes |
brain_node_revoke |
POST | /v1/nodes/:id/revoke |
All handlers include the 204 guard pattern and use proxyFetch for proxy-aware connectivity.
brain deleteJSON output: Changed--json/ non-TTY output from bare{}to{ "deleted": true, "id": "<id>" }— meaningful for piped consumersbrain page getdisplay: Unwrap.memorywrapper fromPageDetailResponsefor human-readable output — shows title, status, category, quality score, tags, delta/evidence counts, and content instead of raw JSON dumpbrain page listdisplay: Enhanced formatting with quality scores, status badges, and total count header
- 0.2.7 → 0.2.8: Initial bug fixes (proxyFetch, 204 guards, --json flags)
- 0.2.8 → 0.2.9: GET /v1/pages route, 9 new MCP tools, fresh binary deploy
- 0.2.9 → 0.2.10: Cosmetic fixes for delete JSON output and page display
Updated in:
npm/packages/ruvector/package.jsonnpm/packages/ruvector/bin/mcp-server.js(2 occurrences)
| File | Changes |
|---|---|
npm/packages/ruvector/bin/cli.js |
Fix proxyFetch() curl fallback to capture real HTTP status; fix brainFetch() and fetchBrainEndpoint() 204 guards; add --json to 4 brain commands |
npm/packages/ruvector/bin/mcp-server.js |
Add offset/sort/tags to brain_list schema; fix brain_sync direction passthrough; add 204 guard to brain handler; add 9 page/node MCP tools; version bump x2 |
npm/packages/ruvector/package.json |
Version 0.2.7 → 0.2.9 |
npm/packages/ruvector/test/integration.js |
MCP tool count threshold updated from 103 to 112 |
crates/mcp-brain-server/src/types.rs |
Add PageSummary, ListPagesResponse types |
crates/mcp-brain-server/src/store.rs |
Add list_pages() method |
crates/mcp-brain-server/src/routes.rs |
Add list_pages handler, register GET /v1/pages route |
mcp-brain-server (binary) |
Rebuilt from source with ScoredBrainMemory, ListResponse, /v1/verify, GET /v1/pages |
- Server-side features live: Scored search, paginated list, verify endpoint, GET /v1/pages, and enhanced transfer are now served from a binary compiled from the current source.
- CLI robustness:
brain deleteandbrain voteno longer crash. The proxy fallback correctly reports non-2xx errors instead of silently swallowing them. - MCP completeness: 112 total MCP tools.
brain_listschema exposes pagination/sort/tags.brain_syncdirection parameter reaches the server. 9 new page/node tools available in stdio transport (previously SSE-only). DELETE operations return clean{}. - API consistency: All 19 brain CLI commands + 6 AGI commands now support
--json. - Full parity: Every brain CLI command now has a corresponding Node.js MCP tool — no more SSE-only gaps.
- The Dockerfile still uses a pre-built binary strategy. A future improvement would add a Cargo build stage to ensure the deployed binary always matches the source.
| CLI Command | Server Route | MCP Tool | --json | Notes |
|---|---|---|---|---|
brain search <query> |
GET /v1/memories/search |
brain_search |
Yes | Score field now present |
brain share <title> |
POST /v1/memories |
brain_share |
Yes | Fixed in v0.2.8 |
brain get <id> |
GET /v1/memories/:id |
brain_get |
Yes | |
brain vote <id> <dir> |
POST /v1/memories/:id/vote |
brain_vote |
Yes | Fixed in v0.2.8 |
brain list |
GET /v1/memories/list |
brain_list |
Yes | Paginated envelope now live |
brain delete <id> |
DELETE /v1/memories/:id |
brain_delete |
Yes | Fixed 204 crash |
brain status |
GET /v1/status |
brain_status |
Yes | |
brain drift |
GET /v1/drift |
brain_drift |
Yes | |
brain partition |
GET /v1/partition |
brain_partition |
Yes | |
brain transfer <s> <t> |
POST /v1/transfer |
brain_transfer |
Yes | |
brain sync [dir] |
GET /v1/lora/latest |
brain_sync |
Yes | Fixed direction passthrough |
brain page list |
GET /v1/pages |
brain_page_list |
Yes | Added in v0.2.9 |
brain page get <id> |
GET /v1/pages/:id |
brain_page_get |
Yes | Added in v0.2.9 |
brain page create |
POST /v1/pages |
brain_page_create |
Yes | Added in v0.2.9 |
brain page update <id> |
PUT /v1/pages/:id |
brain_page_update |
Yes | Added in v0.2.9 |
brain page delete <id> |
DELETE /v1/pages/:id |
brain_page_delete |
Yes | Added in v0.2.9 |
brain node list |
GET /v1/nodes |
brain_node_list |
Yes | Added in v0.2.9 |
brain node get <id> |
GET /v1/nodes/:id |
brain_node_get |
Yes | Added in v0.2.9 |
brain node publish |
POST /v1/nodes |
brain_node_publish |
Yes | Added in v0.2.9 |
brain node revoke <id> |
POST /v1/nodes/:id/revoke |
brain_node_revoke |
Yes | Added in v0.2.9 |
brain agi status |
GET /v1/status |
brain_agi_status |
Yes | AGI field extraction |
brain agi sona |
GET /v1/sona/stats |
brain_sona_stats |
Yes | |
brain agi temporal |
GET /v1/temporal |
brain_temporal |
Yes | |
brain agi explore |
GET /v1/explore |
brain_explore |
Yes | |
brain agi midstream |
GET /v1/midstream |
brain_midstream |
Yes | |
brain agi flags |
GET /v1/status |
brain_flags |
Yes | Flag field extraction |
| Route | Description | Status |
|---|---|---|
GET /v1/health |
Health check | Used internally by midstream tools |
GET /v1/challenge |
Nonce for replay protection | Used by SSE MCP, not needed in CLI |
POST /v1/verify |
Witness chain verification | New in v0.2.8 — no CLI command yet |
POST /v1/lora/submit |
Submit LoRA weights | No CLI command |
GET /v1/training/preferences |
Training prefs | No CLI command |
GET /v1/pages/:id/deltas |
List page deltas | Accessible via brain page but not granular MCP tool |
POST /v1/pages/:id/evidence |
Add evidence | Not exposed as separate command |
POST /v1/pages/:id/promote |
Promote page | Not exposed as separate command |
GET /v1/nodes/:id/wasm |
Download WASM binary | Not exposed |
node -c bin/cli.js && node -c bin/mcp-server.js— syntax check passesnpm test— 69 tests passcargo build --releaseincrates/mcp-brain-server/— compiles withScoredBrainMemory,ListResponse- Cloud Build + Cloud Run redeploy with fresh binary
brain status --json— confirms API respondsbrain search <query> --json— confirmsscorefield present in resultsbrain list --sort quality --limit 5 --json— confirms{ memories, total_count, offset }envelopebrain delete <id> --json— returns{}without crash
GET /v1/pages?limit=2&status=canonical— returns{ pages, total_count, offset, limit }envelopenpm test— 69 tests pass, MCP tool count 112- 9 new MCP tools in
mcp-server.js—brain_page_list/get/create/update/delete,brain_node_list/get/publish/revoke - Cloud Build + Cloud Run redeploy (revision
ruvbrain-00075-m7w) - Published
ruvector@0.2.9to npm