Skip to content

MCP: AI assistants can request crafts and set stock rules - #201

Merged
Maselkov merged 11 commits into
mainfrom
mcp-write-tools
Nov 4, 2026
Merged

Maselkov merged 11 commits into
mainfrom
mcp-write-tools

Conversation

@Maselkov

@Maselkov Maselkov commented Nov 4, 2026

Copy link
Copy Markdown
Owner

Last of the stack: merge #198 → #199 → #200 → this. Until those are in, this diff also shows their commits. The base is main on purpose.

An AI assistant can now act on the base as well as read it. It can only do what the signed-in user's role allows, through the same code the page uses.

New tools

Tool Who What
set_stock_alert, remove_stock_alert anyone signed in their own low-stock alerts, through stock.set_alert
set_stock_target, remove_stock_target operators, admins the base's keep-in-stock targets, through stock.set_target (craftable items only, refill above keep)
request_craft operators, admins asks AE2 for an amount of an item, through commands.queue_craft
cancel_craft operators, admins cancels a CPU's job, through commands.request_cancel
request_status anyone follows one of your requests or cancels to its outcome (accepted on CPU X, or failed and why), from the request history

Guards on request_craft

  • Plan first. It runs the same planner as the craft dialog. If ingredients are missing it queues nothing and returns the shortfall. Calling again with force: true starts it anyway; the description says to do that only when the user asks after seeing what's missing. With no pattern scan yet it queues the request and says the ingredients went unchecked, since AE2 still refuses what it can't cover.
  • No fixed amount cap, as decided: a sensible amount depends on the item (100k mB of a fluid is little). Instead it echoes back the item's name, the amount, the unit (items or mB) and the current stock, so a misread amount is obvious.
  • Confirmation. The game-changing tools are annotated destructiveHint: true, so clients ask the user before calling them. Their descriptions and the server's instructions also tell the model to state the item, amount or CPU and get the user's OK.
  • Same records as the page. A request lands in the user's history like one made on the page, and an accepted one pins its CPU for them.

Also

  • current_crafts gives each busy CPU's job a job_id. cancel_craft sends it back as the cancel's expected output, so a CPU that has moved on to another job isn't cancelled (the existing 409 check).
  • tools/list depends on the user: viewers aren't offered the operators' tools, and the tools also check the role when called.
  • plan_craft and request_craft share one plan helper.
  • Tool output drops Minecraft colour codes (§a16k§r ME Storage Component becomes 16k ME Storage Component), which showed up on dev.py.
  • The consent page says what an operator's app can change.
  • New store.requests.find() looks up one of the user's own requests or cancels.
  • Docs: a "What it can change" table and how request_craft guards a craft in docs/using.md, the README feature line, and a design note in CONTEXT.md.

Testing

  • New tests/test_mcp_write.py (15 tests): role filtering and refusals; annotations; alerts and targets with the page's own error messages; request_craft unchecked without patterns, held back when ingredients are missing, then forced; only craftable items; status through accepted and failed; cancel through job_id, refusing a CPU that moved on, and idle or unknown CPUs.
  • By hand on dev.py: request_craft for 4 Inconel-625 Gears was picked up by the fake game and accepted on CPU B01, and request_status showed it.
  • The full checklist passes: pytest (1002), node --test, e2e (55), node --check, Lua (on 5.4), ruff, luacheck and shellcheck.

🤖 Generated with Claude Code

Maselkov and others added 11 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>
claude.ai's connectors only sign in with OAuth, so the server is now its
own authorization server for /mcp:

- /mcp's 401 points at the protected resource metadata (RFC 9728),
  which names this server's authorization server metadata (RFC 8414).
- Clients register themselves (RFC 7591, public clients only, unused
  ones dropped after a day, capped per hour).
- /oauth/authorize is a consent page: sign in with your access token,
  see which app asks and where it sends you back, Allow or Cancel. Its
  own CSP lets the form's redirect reach the app's callback.
- /oauth/token trades the code (PKCE S256 only) for an hour's access
  token and a 30-day refresh token. Refresh tokens rotate; one used
  twice revokes the grant.
- Loopback redirect URIs match on any port, for Claude Code.
- Manage users lists each user's connected apps with Disconnect.
  Revoking the login token they consented with, New token or deleting
  the user disconnects them too.
- docs: using.md (claude.ai and Claude Code setup), server.md
  (PUBLIC_URL, proxy paths), CONTEXT.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seven more tools, each running the page route's own code and role check:

- set_stock_alert / remove_stock_alert: your own low-stock alerts.
- set_stock_target / remove_stock_target (operators): keep-in-stock
  targets.
- request_craft (operators): works out the crafting plan first and
  doesn't queue while ingredients are missing unless called again with
  force. No fixed amount cap: what's sensible depends on the item. It
  echoes back the name, amount, unit and stock.
- cancel_craft (operators): takes the job_id current_crafts now gives
  each busy CPU, so a CPU that moved on to another job isn't cancelled.
- request_status: whether a request or cancel went through.

The tools that change the game carry destructiveHint, and they and the
server's instructions tell the model to confirm with the user first.
Viewers aren't offered operators' tools. Names lose Minecraft's colour
codes. The consent page says what an operator's app can change.

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>
- The consent page only takes POSTs from its own page (Origin and
  Sec-Fetch-Site): another site could post a sign-in with its own token
  and sign the visitor in as someone else (login CSRF).
- A refresh token used again within 30 s of its first use gets tokens
  instead of revoking the grant: that's two requests refreshing at once
  or a retry after a lost answer, not a copied token.
- Registrations are capped per address (30 an hour) as well as in all
  (2000), so one script can't stop everyone else connecting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- request_status expires the queue before reading a request's row: a
  request the game never picks up is otherwise only marked failed when
  the game or the page next looks, and kept reading "waiting".
- cancel_craft requires the job_id when the CPU reports what it's
  making: without it, a CPU that moved on to another job since the user
  agreed would have that one cancelled.
- Stock rule amounts must be finite (on the page too): JSON's 1e400 is
  infinity, which a refill would then ask the game for.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Maselkov
Maselkov merged commit 3de5160 into main Nov 4, 2026
8 checks passed
@Maselkov
Maselkov deleted the mcp-write-tools branch November 4, 2026 21:44
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