# `tecs.mcp` The MCP debug server, its tool registry, and the tools every build exposes. ## Serving a session `listen` binds a loopback Streamable HTTP endpoint at `http://127.0.0.1:/mcp`. An application configured with `mcpPort` owns the listener and `tecs.host` polls it once per host turn, outside world updates, including after a crash or while gameplay is suspended. For a standalone server, call `poll` outside the world update scope and `destroy` at teardown. Each poll answers at most one tool request; handlers must answer promptly. While a lifecycle operation is parked, `ping`, `context` and `get_logs` remain available. Other tools report that the operation must complete first. A custom tool may opt in with `whenParked = true` only if it never touches the in-progress world. `readOnly` alone grants no such permission. ```nupp local server = assert(tecs.mcp.listen(7100)) tecs.mcp.bind(app.world, app) -- once per frame tecs.mcp.poll(server) ``` ## Registering a tool ```nupp local placed = world:newQuery({include = {tecs.Transform2D}}) mcp.register({ name = "placed_count", description = "Count entities that carry a Transform2D", inputSchema = {type = "object", properties = {}}, readOnly = true, handler = function(_arguments: {[string]: any}): {[string]: any}? local total = 0 for _, length in placed:iter() do total = total + (length as integer) end return {count = total} end }) ``` Registration is what publishes the listing, so mutating a registered table in place does not change what a connected agent is told. ## Inspecting a world Start with `components_info`, `query` and `info`. Use `modify` for a partial component update and `set` for a complete value or a missing component. `systems_list` and `states_info` report what the world is running. Prefer these structured tools to `run_lua`; the Lua sandbox limits accidents but does not create a security boundary. ## After a crash `setCrashed` records a guarded gameplay failure. The server keeps polling after one: `ping`, `context`, `get_logs`, `send_event` and `clear_crash` remain available, and every tool that touches the world reports the stored traceback instead of running. ## Subsystem tools Archetypes, resources, materials, camera, layers, render configuration and physics have structured inspection tools. `snapshot_save` and `snapshot_load` round-trip committed worlds, including binary physics state. `audio` and `reload_sound` use the world's installed mixer. `profile_start` and `profile_stop` use Nupp's process sampler; stop writes collapsed stacks under the writable root. `help`, `describe` and `capabilities` enumerate the live registry. They and the sampler remain available while parked or crashed. The server does not provide GPU readback, shader or image reload, watcher orchestration, or physics debug overlay tools. Snapshot files use TECS framing over Nupp's binary codec and refuse unsupported framing or physics encodings. ## Types ### `Request` _record_ ```nupp record Request name: string arguments: string end ``` One tool call taken off a connection and waiting for the frame. #### Fields ##### `name` ```nupp name: string ``` Read-only. Names the registered tool the agent called. ##### `arguments` ```nupp arguments: string ``` Read-only. Carries the call's arguments as the JSON object text the agent sent, which the frame decodes. ### `Server` _record_ ```nupp record Server port: integer end ``` A listening MCP endpoint, which answers only while something calls `poll`. #### Fields ##### `port` ```nupp port: integer ``` Read-only. Reports the bound TCP port, which is the one `listen` was given unless it was given zero. ### `Tool` _type_ ```nupp type Tool = { name: string, description: string?, inputSchema: {[string]: any}?, outputSchema: {[string]: any}?, handler: ToolHandler, readOnly: boolean?, destructive: boolean?, whenCrashed: boolean?, --- Allows a tool to run during a parked lifecycle operation without touching the --- world. whenParked: boolean? } ``` One registered tool and its handler. ### `ToolHandler` _type_ ```nupp type ToolHandler = function(arguments: {[string]: any}): {[string]: any}? ``` The decoded arguments a tool receives and the structured content it returns. ## Functions ### `bind` _function_ ```nupp function bind(world: ecs.World?, app: bindings.Application?): nil ``` Names the world and application the built-in tools act on. One of each. The MCP protocol has nowhere to carry them, so they are held in module-level slots and every tool that needs one fails with a message saying so until this has run. #### Arguments | Name | Type | Description | | --- | --- | --- | | `world` | `ecs.World?` | the world the world tools read and write, or nil to unbind | | `app` | `bindings.Application?` | the application the lifecycle tools act on, or nil to unbind | #### Returns | Type | Description | | --- | --- | | `nil` | | ### `bindLogFile` _function_ ```nupp function bindLogFile(path: string?): nil ``` Names the file `get_logs` reads. Nupp's logging writes to a sink rather than to a path it can be asked for, so the host that configured a file sink is the only thing that knows where it went. Until something calls this, `get_logs` reports no path and no lines rather than guessing at one. #### Arguments | Name | Type | Description | | --- | --- | --- | | `path` | `string?` | the log file path, or nil to report that there is none | #### Returns | Type | Description | | --- | --- | | `nil` | | ### `crashed` _function_ ```nupp function crashed(): string? ``` Returns the recorded crash text. #### Returns | Type | Description | | --- | --- | | `string?` | the traceback, or nil when nothing has recorded a crash; nothing here polls the game to confirm that it remains healthy | ### `destroy` _function_ ```nupp function destroy(exclusive self: Server): nil ``` Releases the server. Safe to call more than once. One way: the port is given up and the server cannot be made to listen again. The tool registry is process-wide and is left untouched, so a later `listen` answers the same tools. #### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive self` | `Server` | the server to stop | #### Returns | Type | Description | | --- | --- | | `nil` | | ### `dispatch` _function_ ```nupp function dispatch(text: string): string ``` Runs one JSON-RPC request in process and returns the response text. The transport is not involved, so this is what a test drives and what a host embedding the protocol on a channel of its own can call. A tool handler runs on the calling thread, inside this call. #### Arguments | Name | Type | Description | | --- | --- | --- | | `text` | `string` | one JSON-RPC request object, which may not be a batch | #### Returns | Type | Description | | --- | --- | | `string` | the response as JSON text, which reports bad input in band rather than by raising | ### `hasBuiltInTools` _function_ ```nupp function hasBuiltInTools(): boolean ``` Reports whether the built-in tools were registered when this module loaded. #### Returns | Type | Description | | --- | --- | | `boolean` | true, because requiring the module registers them | ### `listen` _function_ ```nupp function listen(port: integer?): Server?, string? ``` Starts the server on `port`. #### Arguments | Name | Type | Description | | --- | --- | --- | | `port` | `integer?` | the TCP port to bind, 7100 when nil, or zero to let the platform choose one and report it as `Server.port` | #### Returns | Type | Description | | --- | --- | | `Server?` | the listening server, which answers nothing until something calls `poll`, or nil when the port could not be bound | | `string?` | why it could not bind, when unsuccessful | ### `listTools` _function_ ```nupp function listTools(): {Tool} ``` Returns every registered tool in registration order. #### Returns | Type | Description | | --- | --- | | `{Tool}` | a fresh list the caller owns, holding the registered declarations themselves rather than copies | ### `poll` _function_ ```nupp function poll(exclusive self: Server, parked: boolean?): boolean ``` Answers at most one tool call. Call once per host turn outside world updates. Protocol-only traffic, `initialize` and `tools/list` included, is answered without running a tool and therefore does not make this report true. #### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive self` | `Server` | the running server | | `parked` | `boolean?` | whether a lifecycle operation is suspended; only `whenParked` tools may run then. | #### Returns | Type | Description | | --- | --- | | `boolean` | whether one tool request was answered. | ### `register` _function_ ```nupp function register(tool: Tool): nil ``` Registers a tool, replacing any tool already holding its name. A replacement keeps the name's original position, so the order a client sees is first-registration order rather than last. #### Arguments | Name | Type | Description | | --- | --- | --- | | `tool` | `Tool` | the declaration, stored by reference and never copied, so a later mutation changes what the server dispatches but not what a connected agent has been told | #### Returns | Type | Description | | --- | --- | | `nil` | | #### Raises - when the declaration carries no name or no handler ### `resetSandbox` _function_ ```nupp function resetSandbox(): nil ``` Forgets everything a previous `run_lua` call stashed. The next `run_lua` compiles into a fresh environment. A chunk already compiled keeps the old one, so a reset makes a clean slate for new code rather than for code already in flight. #### Returns | Type | Description | | --- | --- | | `nil` | | ### `setCrashed` _function_ ```nupp function setCrashed(traceback: string?): nil ``` Records a crash, after which every world-touching tool reports it. The server outliving the game is the point. An agent debugging something up to the moment it broke should get the reason rather than a refused connection, and the tools that do not touch the world keep working so it can still read the log and ask what the build was. #### Arguments | Name | Type | Description | | --- | --- | --- | | `traceback` | `string?` | the failure text, handed back verbatim by `crashed` and sent as the whole body of the error a blocked tool answers with, or nil to clear the crashed state | #### Returns | Type | Description | | --- | --- | | `nil` | |