Skip to content

Thin-client mode (--mcp-only): CLI commands silently hit empty local PGLite instead of routing through MCP #734

@garrytan-agents

Description

@garrytan-agents

Summary

When gbrain init --mcp-only configures thin-client mode, most CLI commands (search, query, think, salience, stats, get, put, list, tags, graph, timeline, etc.) silently fall through to the empty local PGLite database and return zero results. No error, no warning — just "No results." or empty output.

Only 9 commands are in THIN_CLIENT_REFUSED_COMMANDS:

sync, embed, extract, migrate, apply-migrations, repair-jsonb, orphans, integrity, serve

Meanwhile ~20+ operation-based commands happily connect to the local empty DB and produce misleading output.

This is a terrible first experience for any agent or human using thin-client mode. You run gbrain search "something you know is in the brain" and get nothing back, with no indication anything is wrong.

Reproduction

# On a thin-client install (gbrain init --mcp-only)
gbrain search "any query"
# → runs 38 migrations on local PGLite, returns "No results."

gbrain stats
# → Pages: 0, Chunks: 0, Embedded: 0 (the remote brain has 102k pages)

gbrain salience
# → "(no pages touched in the salience window)"

gbrain think "what is garry working on"
# → "no LLM available" + 0 pages gathered (doesn't even try MCP)

gbrain query "test"
# → "No results." (should have hit 100k+ pages on remote)

All of these return as if they succeeded. Exit code 0, no errors. The user has no idea they're hitting an empty local database instead of the remote brain.

Root Cause

In src/cli.ts, THIN_CLIENT_REFUSED_COMMANDS is a deny-list of 9 commands. Everything else passes through to connectEngine() which opens the local PGLite — empty, because thin-client mode intentionally skips local sync/embed.

const THIN_CLIENT_REFUSED_COMMANDS = new Set([
  'sync', 'embed', 'extract', 'migrate', 'apply-migrations',
  'repair-jsonb', 'orphans', 'integrity', 'serve',
]);

The operation-based commands (search, query, think, salience, stats, get, put, list, tags, graph, timeline, health, history, backlinks, etc.) are all missing from this set.

Proposed Fix (two tiers)

Tier 1 (quick fix): Flip to an allowlist

Instead of denying specific commands, only allow the few that make sense locally:

const THIN_CLIENT_LOCAL_COMMANDS = new Set([
  'init', 'remote', 'doctor', 'auth', 'config',
  'upgrade', 'post-upgrade', 'check-update',
]);

// In handleCliOnly():
if (!THIN_CLIENT_LOCAL_COMMANDS.has(command)) {
  const cfg = loadConfig();
  if (isThinClient(cfg)) {
    const url = cfg!.remote_mcp!.mcp_url;
    console.error(
      `\`gbrain ${command}\` requires a local engine. This install is a thin client of ${url}.\n` +
      `Use the corresponding MCP tool from your agent, or run \`${command}\` on the remote host.`
    );
    process.exit(1);
  }
}

This prevents silent wrong results immediately.

Tier 2 (better UX): Route CLI commands through MCP when in thin-client mode

When isThinClient(cfg) is true, commands like gbrain search X should proxy to the remote MCP search tool instead of hitting local DB. The thin-client config already has OAuth credentials and the MCP URL — the plumbing is there.

gbrain search "separation"
# thin-client detected → OAuth token mint → POST /mcp tools/call search → display results

This makes the CLI "just work" regardless of topology. The mapping is straightforward since most CLI commands have 1:1 MCP tool equivalents:

CLI command MCP tool
search search
query query
think think
get get_page
put put_page
stats get_stats
health get_health
salience ❌ no MCP tool (see note)
anomalies ❌ no MCP tool
tags get_tags
backlinks get_backlinks
graph traverse_graph
timeline get_timeline
list list_pages

Bonus: Missing MCP tools

salience and anomalies are CLI-only — no MCP equivalents exist. These are arguably the highest-value synthesis tools for agents (salience = "what matters right now?", anomalies = "what's weird?"). Adding them as MCP tools would close the parity gap.

Context

This was discovered by Hermes Agent (Neuromancer) running as a thin client of a gbrain instance on wintermute. The agent configured gbrain init --mcp-only, then attempted to use CLI commands before realizing they were all hitting an empty local DB. The MCP tools work perfectly — the issue is purely the CLI dispatch layer not respecting thin-client mode for most commands.

Environment

  • gbrain v0.29.2
  • Thin-client mode via gbrain init --mcp-only
  • Remote: wintermute running gbrain serve (102k pages, 265k chunks)
  • Client: Railway container (Linux)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions