# `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` | |