Toolset gating
A large flat tool surface degrades agent tool-selection and burns context. So the live surface stays small: core + inspection are exposed by default (24 tools), and 27 further toolsets are gated off until the agent enables them.
The enabled set
enable_toolset / disable_toolset write into a single server-global enabled set (issue #364). godot-mcp is a single-user, locally-run server — per-session isolation was removed because it was dead code on the sessionless MCP 2026-07-28 protocol (a fresh session_id per call made it useless) and violated the no-server-initiated-hook rule. Consequences, which are features:
- A grant issued on one connection persists to a fresh connection against the same server process — a reconnect between enable and mutate keeps the grant.
- All clients see the same surface. There is no per-session isolation to reason about.
- The set is seeded at startup from
GODOT_MCP_DEFAULT_TOOLSETS(allor a comma-separated list; unknown names are logged and ignored). The agent-facing protocol text derives its wording from the effective default so it never claims "everything is hidden" when the operator seeded more (#425).
tools/list_changed
Since 2026.09.17 (#491/#485), enable_toolset/disable_toolset emit notifications/tools/list_changed after mutating the set. Clients that cache the tool list at session start refresh on the notification. The send is fire-and-forget by design — the toggle already succeeded, so a delivery failure must not fail the tool call — but it is logged, never swallowed silently.
On the sessionless 2026-07-28 protocol the spec forbids unsolicited change notifications: they reach a client only on a subscriptions/listen stream it opens. Since #620 the server registers a ListenHandler (backed by an in-memory SubscriptionBus, the same wiring mcp's own MCPServer uses) on FastMCP's low-level server, so it both advertises tools.listChanged: true in server/discover and fans each toggle out to every open listen stream. Registering the method is what flips the advertised flag — the mcp SDK derives it from whether subscriptions/listen is served, not from FastMCP's legacy notification options.
Pull-based fallback (#621). Because many clients still ignore the notification, enable_toolset's result also carries the enabled tools' exposed names, descriptions, and input schemas — so an agent can call them immediately even when its registry never refreshes. The push that keeps the editor dock's Toolsets row honest is fire-and-forget (#624) and never stalls the toggle.
The typed tools remain authoritative either way: calling a gated tool whose toolset is disabled fails with a structured gating error, and _ensure_or_return_error-style client helpers re-enable idempotently.
Why the gate is worth its friction
- Tool selection sharpness. With 194 tools exposed, models confuse similar tools and waste calls. The default 24-tool surface fits in context with room to reason.
- Safety surface minimization. Destructive tools (
delete_node,reload_scene,close_scene, bus removers, file deleters) are invisible until the agent deliberately asks for a toolset containing them. - Version gating rides the same flag:
scene_edit,input_map,tilemap,scene_3drequire Godot 4.4+ and refuse to enable on older editors with a structured error naming the requirement.
The toolsets
| Domain | Toolsets |
|---|---|
| Scene building | scene_edit · composite (macro round-trips) · scene_3d · theme_ui · tilemap · particles · navigation · physics · animation · audio · shader · visual_shader |
| Code & data | scripts · resources_edit · project · project_scaffold |
| Run & verify | runtime · input · testing · debugger · profiling |
| Bulk & ship | batch · analysis · export · asset_import · editor |
Plus always-on core (diagnostics, toolset management, godot_undo, godot_debug_workflow) and inspection.
The full per-toolset table (with tool names and counts) lives in the toolsets reference; the authoritative per-tool spec is docs/tool-contracts.md.
Design guardrails
- New categories register gated off — added to
TOOLSETS, never toDEFAULT_ENABLED. Growth of the catalog does not grow the default surface. - Fewer, richer tools over many micro-tools. Create-with-config (
create_node(…, properties?, script?)) and batch setters replace one-tool-per-field. One tool per noun with a few clear verbs. - Never merge across safety classes. Folding
deleteinto a fatnodetool would bury theconfirmgate; the class split is deliberate. - Never a single
do(action, params)dispatcher — that discards the per-tool schemas and descriptions that guide the agent.