skill catalog reports every tool as unavailable over stdio MCP, and tells the agent not to call them
#3,635 opened on Jul 30, 2026
Repository metrics
- Stars
- (21,368 stars)
- PR merge metrics
- (Avg merge 18d 10h) (162 merged PRs in 30d)
Description
Version: 0.42.67.0 (checkout at c6dc0adf, clean)
Transport: stdio MCP, registered exactly as docs/mcp/CLAUDE_CODE.md:11 prescribes — claude mcp add gbrain -- gbrain serve
Symptom
On the stdio transport, list_skills returns every skill with usable_tools: [],
every declared tool in unavailable_tools, and instructions.available_brain_tools: [].
The same payload then instructs the agent (src/core/operations-descriptions.ts:208,224):
"Only call tools in this skill's
usable_tools; tools inunavailable_toolsare not callable by you on this server." "Do not invent tools — only the tools inusable_toolsare callable by you."
An agent that obeys the catalog concludes it cannot use the brain at all.
It is not true
Same server, same session, same minute:
whoami→{"transport":"stdio","scopes":[]}search,get_health,list_skills→ all succeedput_page— awrite-scope op the catalog listed as unavailable — succeeded and wrote through to disk:{"status":"created_or_updated","chunks":1,"write_through":{"written":true,"path":"..."}}
For contrast, the CLI path on the same skill reports the truth:
gbrain skill brain-ops → usable_tools: [search, query, get_page, put_page, add_link, add_timeline_entry, get_backlinks, sync_brain], unavailable_tools: [].
Mechanism
src/core/skill-catalog.ts:295-299
function opCallableByCaller(op: Operation, ctx: OperationContext): boolean {
if (ctx.remote === false) return true; // local CLI
if (op.localOnly) return false;
return hasScope(ctx.auth?.scopes ?? [], op.scope ?? 'read');
}
The stdio pipe is deliberately remote: true (src/core/operations.ts:339-344) and carries
no auth, so ctx.auth?.scopes ?? [] is empty and hasScope([], 'read') is false for every op.
The catalog is the only place that consults scopes on this transport. hasScope has three
call sites in the whole repo: OAuth grant issuance, request-time enforcement in
src/commands/serve-http.ts:1893 (HTTP only), and skill-catalog.ts:298. There is no
scope enforcement in the stdio dispatch path, which is why every op executes regardless.
So the advertisement applies an HTTP authorization model to a transport that has no authorization, and reports the exact inverse of reality.
Suggested fix
Treat the auth-less stdio transport as trusted for the purposes of this advertisement — it already is for dispatch. Something like:
if (ctx.remote === false || ctx.transport === 'stdio') return true;
(OperationContext already carries the 'stdio' transport marker per the note at
src/core/operations.ts:339-344.)
Why it matters
This is upstream's own zero-config path — docs/mcp/DEPLOY.md:18-25 and
docs/mcp/CLAUDE_CODE.md:11-15, "No server, no tunnel, no token needed". Any agent that
follows the catalog's instructions on that path silently loses the entire brain surface.