Skip to content

fix: legacy bearer tokens respect GBRAIN_SOURCE env for sourceId#1648

Open
JavanC wants to merge 2 commits into
garrytan:masterfrom
JavanC:fix/legacy-bearer-source-id
Open

fix: legacy bearer tokens respect GBRAIN_SOURCE env for sourceId#1648
JavanC wants to merge 2 commits into
garrytan:masterfrom
JavanC:fix/legacy-bearer-source-id

Conversation

@JavanC

@JavanC JavanC commented May 30, 2026

Copy link
Copy Markdown

Summary

Fixes #1336

  • Legacy bearer tokens (access_tokens transport) hard-coded sourceId to 'default', causing all MCP queries to return empty results for brains using a non-default source via GBRAIN_SOURCE
  • Adds fallback chain process.env.GBRAIN_SOURCE || 'default' at all three auth resolution sites
  • Matches existing behavior of stdio MCP server (server.ts:42) which already reads GBRAIN_SOURCE
  • OAuth clients are unaffected — their sourceId comes from oauth_clients.source_id (migration v60)

Reproduction

  1. Set up a multi-source brain with gbrain sources add my-source --path /path
  2. Run gbrain serve --http with GBRAIN_SOURCE=my-source
  3. Query via bearer token → returns empty []
  4. Same query via CLI (gbrain call) → returns correct results

Files changed

  • src/core/oauth-provider.ts — primary verifyAccessToken path
  • src/mcp/http-transport.ts — v0.22.7 transport path
  • src/commands/serve-http.tstokenSourceId fallback chain

Test plan

  • Verified list_pages returns data via HTTP bearer token with GBRAIN_SOURCE=javan-brain
  • Verified query semantic search returns results through cloudflared tunnel
  • Confirmed OAuth clients unaffected (source_id from DB, not env)

🤖 Generated with Claude Code

Javan and others added 2 commits May 30, 2026 18:06
Legacy bearer tokens (access_tokens transport) hard-coded sourceId to
'default', making all MCP queries return empty results for brains using
a non-default source (e.g. multi-source setups with GBRAIN_SOURCE set).

The fix adds a fallback chain: process.env.GBRAIN_SOURCE || 'default'
at all three sites where legacy bearer token auth resolves sourceId:

- oauth-provider.ts verifyAccessToken (primary auth path)
- http-transport.ts verifyAccessToken (v0.22.7 transport path)
- serve-http.ts tokenSourceId fallback

This matches the existing behavior of the stdio MCP server (server.ts:42)
which already reads GBRAIN_SOURCE. OAuth clients are unaffected — their
sourceId comes from oauth_clients.source_id (migration v60).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…auto_timeline

JavanC fork patch. Upstream put_page intentionally skips auto_link and
auto_timeline for remote (MCP) callers because untrusted webhook input
could plant arbitrary outbound links via bare-slug regex matching.

For single-user trusted deployments (e.g. Mac mini canonical host with
bearer-token-only MCP access behind Cloudflare WAF), every remote call
is already from an internal agent. The skip then becomes pure cost —
agents must run a separate batch extract to backfill links.

Add GBRAIN_TRUSTED_REMOTE=1 env opt-in. Default behavior unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@jacklau1993

Copy link
Copy Markdown

Hey — sharing a related use case from a downstream user running gbrain as a multi-agent shared brain via Zeabur, in case it helps future readers.

Same bug class, different transport. We hit the silent default-to-default symptom on a multi-source brain (canonical source nikke-memory, federated setup) but for the stdio MCP transport rather than HTTP bearer. Our v0.42.8.0 src/mcp/server.ts:42 already reads process.env.GBRAIN_SOURCE, so the stdio side is technically covered — the underlying issue for us was that the env var was simply not being set in the subprocess at all (openab spawns agents with inherit_env = [], and the [mcp_servers.gbrain].environment block in our ~/.grok/config.toml wasn't being honored by the Grok MCP client). Same blind-spot shape, different layer.

3-layer defense that worked for us (all on the calling side, no gbrain patch needed):

  1. Wrapper that hard-codes the env/home/agent/bin/gbrain:

    #!/bin/sh
    export HOME=/home/agent
    export PATH="/home/agent/.bun/bin:$PATH"
    : "${GBRAIN_SOURCE:=nikke-memory}"
    export GBRAIN_SOURCE
    exec /home/agent/.bun/bin/gbrain "$@"

    POSIX default-substitution := means even a clean parent env (env -i) gets the right value. Defense in depth: doesn't rely on the MCP client correctly threading [mcp_servers.gbrain].environment to subprocess.

  2. Grok config.toml explicit env block~/.grok/config.toml:

    [mcp_servers.gbrain]
    command = "/home/agent/bin/gbrain"
    args = ["serve"]
    enabled = true
    
    [mcp_servers.gbrain.environment]
    GBRAIN_SOURCE = "nikke-memory"
  3. ~/.gbrain-source = nikke-memory dotfile — CLI fallback for sanity, since the MCP stdio transport in our version (v0.42.8.0 src/mcp/server.ts:42) does NOT consult the dotfile the way the CLI 6-tier core/source-resolver.ts does. This is the only real gap on the stdio side, and it's the analog of your fix: legacy bearer tokens respect GBRAIN_SOURCE env for sourceId #1648 fix for the stdio path. A 2-line patch calling resolveSourceId(engine, explicit=null, cwd=process.cwd()) from server.ts:42 would close it; we'd love a follow-up for it.

End-to-end verification (the gold-standard proof for routing): the real Grok MCP call returned an error whose body contained the string (source=nikke-memory) — printed by src/core/operations.ts:549 from ctx.sourceId, which mcp/server.ts:42 set from process.env.GBRAIN_SOURCE. The error message itself is the proof that the env plumbing through the entire Grok → openab → wrapper → gbrain chain is correct.

Two adjacent observations for v0.42.8.0 (independent of your PR, FYI):

  1. The new content-sanity gate (v0.42.8.0 src/core/import-file.ts:469 assessContentSanity) hard-blocks minimal test slugs (e.g. ops/test/...) and surfaces as Page not found: <slug> (source=...) from _upsertChunksOnce (the page INSERT never durably commits, so the chunk SELECT returns 0 rows). Both CLI and MCP fail identically. It looks like a routing bug at first glance but isn't — the actual cause is the gate. Surfacing the gate verdict explicitly (e.g. ContentSanityBlocked with the assessContentSanity reason) would save a lot of debug time for downstream users.

  2. Your second commit (feat(put_page): add GBRAIN_TRUSTED_REMOTE env override for auto_link/auto_timeline) is directly relevant for our case. We have a single-user trusted deployment behind Zeabur internal DNS; every MCP call is internal, so the security-skip on auto_link/auto_timeline is pure cost for us. Will pick up the opt-in once the PR lands.

Reference: I documented our full 3-layer pattern in ops/diagnostics/sakura-gbrain-mcp-source-routing-hardening-2026-06-03 (will be ingested into our gbrain via the team sync worker). Cross-linking to PR #1648 from there.

Thanks for the fix — looking forward to it landing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Legacy access_tokens ignore permissions.source_id; MCP reads silently return empty in non-default brains

2 participants