Skip to content

MCP endpoint: AI assistants can read the base - #199

Merged
Maselkov merged 3 commits into
mainfrom
mcp-endpoint
Nov 4, 2026
Merged

Maselkov merged 3 commits into
mainfrom
mcp-endpoint

Conversation

@Maselkov

@Maselkov Maselkov commented Nov 4, 2026

Copy link
Copy Markdown
Owner

Stacked on #198. Merge #198 first. Until then this diff also shows #198's commit. The base is main on purpose, so this can't end up merged into #198's branch.

Adds /mcp, a Model Context Protocol endpoint, so an AI assistant can answer questions about the base, such as "what's draining fastest this week?", "why is M00 slow?" or "what's missing for 64 of these?". Everything is read-only. Signing in from claude.ai (OAuth) and the write tools come in the next PRs.

What it adds

  • 11 tools in gcm/mcp/tools.py: search_items, get_item, item_history, inventory_changes, current_crafts, craft_job, craft_history, craft_stats, plan_craft, stock_rules, power. They call the same code as the page's routes (the planner, stock.rules, inventory.history, bottlenecks.steps…).
    • Item tools take the item key that search_items returns, never a name, so a request can't land on the wrong one of several items with the same name.
    • Output is compacted for the model's context: no icons, capped lists, at most 48 history points, ISO times, numbers rounded.
  • Protocol (gcm/mcp/protocol.py): hand-written JSON-RPC over Streamable HTTP. The MCP Python SDK is ASGI and this server is WSGI, and without sessions or streams the protocol is small. Every request gets one JSON answer.
    • It serves both protocol eras on one URL. 2026-07-28, the current spec: no handshake, the version goes in _meta and the MCP-Protocol-Version header must match it, Mcp-Method/Mcp-Name are checked against the body (-32020 on a mismatch), server/discover is supported, unsupported versions get -32022. 2025-03-26 to 2025-11-25: initialize picks the version, then each request is served by its header.
    • GET/DELETE get 405, as the spec says for a server without sessions or streams.
  • Auth: a new auth.bearer_required policy reads the user's personal access token from Authorization: Bearer, through the new store.users.token_user. A session cookie never works on /mcp, and a request from another site's Origin gets 403. The tools apply the user's role.
  • Docs: an "AI assistants" section in docs/using.md (setup in Claude Code, a table of the tools), plus README, server.md (the proxy must pass /mcp), how-it-works.md and CONTEXT.md. dev.py prints the MCP address.

Step 0: what the plan needed checking

  • MCP 2026-07-28 (released July 2026) dropped the initialize handshake and sessions, which is why the endpoint serves both eras.
  • claude.ai connectors take DCR or CIMD (CIMD only when the server's metadata advertises client_id_metadata_document_supported and the none auth method), PKCE S256, and the callback https://claude.ai/api/mcp/auth_callback. Claude Code uses loopback redirects on any port. All of this is for the OAuth PR.

Testing

  • New tests/test_mcp.py (35 tests): auth (missing, wrong, revoked or cookie-only credentials; Origin; no-store), both eras' handshakes and errors, header-mismatch cases including base64 Mcp-Name, and every tool against the real app.
  • The full checklist passes: pytest (947), node --test, e2e, node --check, Lua (on 5.4), ruff, luacheck and shellcheck.
  • By hand against dev.py: each tool called over HTTP in both eras.
  • Not tested: a third-party MCP client. Running the Inspector CLI meant downloading it from npm, which was refused in my session. To check it with Claude Code: claude mcp add --transport http gtnh-dev http://127.0.0.1:8421/mcp --header "Authorization: Bearer <the token dev.py prints>".

🤖 Generated with Claude Code

Maselkov and others added 2 commits November 4, 2026 21:48
The MCP endpoint coming next must check what the page's routes check,
so the logic moves into functions both can call:

- commands.request_cancel(): the busy-CPU and same-job checks. The job
  identity helpers (output_identity, same_output) move with it from
  tracking, which imports commands, not the other way round.
- commands.craft_request_error(): the craft request's input checks.
- stock.item_from(), set_alert(), set_target() and rules(): stock rule
  validation, the craftable check and the rules listing.
- planner.rules_for_user(): the stock rules a plan warns about.

No change to what the routes accept or answer, except that a craft
request's amount can no longer be true (which counted as 1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/mcp is a Model Context Protocol endpoint, so Claude Code or another MCP
client can answer questions about the base. Eleven read-only tools:
item search, item details and history, inventory changes, current
crafts, a job's held-up steps, craft history and stats, the crafting
plan, stock rules and power.

- Hand-written JSON-RPC over Streamable HTTP (gcm/mcp/protocol.py): one
  JSON answer per POST, no sessions. It serves both protocol eras: the
  2026-07-28 stateless requests (version in _meta, mirrored headers
  checked) and initialize-based 2025-03-26..2025-11-25 clients.
- Signed in with the user's personal access token as a bearer token
  (auth.bearer_required), never a session cookie; another site's Origin
  is refused.
- Tools take item keys from search_items, never names, and compact their
  output for a model's context.
- docs: an AI assistants section in using.md, plus README, server.md,
  how-it-works.md and CONTEXT.md. dev.py prints the MCP address.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A tool that raises anything but ToolError answers as a tool error
  (and is logged) instead of a 500 the client can't read, and a KeyError
  inside a tool no longer reads as "Unknown tool".
- Numbers in arguments must be finite: JSON's 1e400 is infinity to
  Python, which int() turned into a 500.
- inventory_changes builds the snapshot's lookup once instead of
  scanning it for every changed item.
- search_items ranks by the name without Minecraft's colour codes, and
  tool output drops them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Maselkov
Maselkov merged commit f6d66a6 into main Nov 4, 2026
8 checks passed
Maselkov added a commit that referenced this pull request Nov 4, 2026
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.

1 participant