On this page
tecs.io.mcp
MCP debug server and tool registry.
Server lifecycle
Set Application.Config.mcpPort for the built-in server. A custom host can call listen, call Server:poll once per frame, and call Server:destroy at teardown. The listener accepts loopback connections only. Each call to poll runs at most one queued handler. The handler sees a committed world, but it must return quickly because the frame cannot end until it does.
Custom tools
local placed <const> = world:newQuery({include = {tecs.Transform2D}})
tecs.io.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}
return {count = placed:count()}
end,
})The registry produces both the advertised tool list and dispatch table. Tool handlers can use the world captured when the game registers them.
World inspection
Start with components_info, query, and info. Use modify for a partial component update and set for a complete value or a missing component. modify skips entities without the component and marks changed columns dirty. Prefer these structured tools to run_lua; the Lua sandbox limits accidents but does not create a security boundary.
Crash access
The server keeps polling after an uncaught gameplay error. ping, context, get_logs, and send_event remain available; tools that touch the world report the stored traceback instead.
Module contents
Types
| Type | Kind | Description |
|---|---|---|
Server |
record | Represents a listening MCP endpoint. |
Tool |
record | Describes one registered tool and its handler. |
ToolHandler |
type | Defines the decoded arguments a tool receives and the structured content it returns. |
Functions
| Function | Kind | Description |
|---|---|---|
crashed |
Static | Returns the crash traceback, or nil while the game is healthy. |
dispatch |
Static | Runs one JSON-RPC request and returns the response text. |
listen |
Static | Starts the server on port. |
register |
Static | Registers a tool. |
setCrashed |
Static | Records a crash. |
tools |
Static | Returns every registered tool in registration order. |
Types
tecs.io.mcp.Server record
Represents a listening MCP endpoint. It answers only while something calls poll.
tecs.io.mcp.Server.port field
Read-only. This is the bound TCP port, which is 7100 when listen was given none.
tecs.io.mcp.Server:destroy Instance
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 module-wide and is left untouched, so a later listen answers the same tools.
function tecs.io.mcp.Server.destroy(self)Arguments
| Name | Type | Description |
|---|---|---|
self |
Server |
Returns
None.
tecs.io.mcp.Server:poll Instance
Answers at most one tool call. Call once per frame.
function tecs.io.mcp.Server.poll(self): booleanArguments
| Name | Type | Description |
|---|---|---|
self |
Server |
Returns
| Type | Description |
|---|---|
boolean |
True when one tool handler ran. Protocol-only traffic does not count. False when no tool call is waiting or after destruction. |
tecs.io.mcp.Tool record
Describes one registered tool and its handler.
record tecs.io.mcp.Tool
name: string
description: string
inputSchema: {string: any}
handler: ToolHandler
readOnly: boolean
destructive: boolean
whenCrashed: boolean
endtecs.io.mcp.Tool.name field
Caller-writable. This is the name an agent calls and the registry key. Established MCP tool names follow the protocol ecosystem's spelling, including run_lua and send_event, so they use snake_case where the rest of this tree uses camelCase. This Do not rename this compatibility surface. Required.
tecs.io.mcp.Tool.description field
Caller-writable. This is one line shown to the agent alongside the name. Optional; nil is sent as an empty string rather than omitted.
tecs.io.mcp.Tool.description: stringtecs.io.mcp.Tool.inputSchema field
Caller-writable. This JSON Schema describes the arguments and is sent verbatim in the tool list.
tecs.io.mcp.Tool.inputSchema: {string: any}tecs.io.mcp.Tool.handler field
Caller-writable. This handler runs synchronously inside poll. Required; its absence raises at registration rather than at call time.
tecs.io.mcp.Tool.handler: ToolHandlertecs.io.mcp.Tool.readOnly field
Caller-writable. This declares whether the tool only reads state so an agent can judge a call before making it.
tecs.io.mcp.Tool.destructive field
Caller-writable. This declares whether a call changes state that a caller would not want changed without asking. Optional, false when nil. Advisory, like readOnly: the server reports both values to the agent and enforces neither, so a tool declared read-only may still write.
tecs.io.mcp.Tool.destructive: booleantecs.io.mcp.Tool.whenCrashed field
Caller-writable. This allows the tool to remain callable after the game has crashed. False by default, because a crashed world cannot answer anything about itself, and a tool that read it would return nonsense rather than an error.
tecs.io.mcp.Tool.whenCrashed: booleantecs.io.mcp.ToolHandler type
Defines the decoded arguments a tool receives and the structured content it returns.
type tecs.io.mcp.ToolHandler = function({string: any}): {string: any}Functions
tecs.io.mcp.crashed Static
Returns the crash traceback, or nil while the game is healthy.
function tecs.io.mcp.crashed(): stringArguments
None.
Returns
| Type | Description |
|---|---|
string |
nil means no code has recorded a crash. Nothing here polls the game to confirm that it remains healthy. |
tecs.io.mcp.dispatch Static
Runs one JSON-RPC request and returns the response text.
Lets tests exercise the protocol without a socket, which covers most of the failure surface.
The three methods answered, initialize, tools/list and tools/call, use the names from the MCP specification, which this tree must not rename. Anything else is a method-not-found error.
When a handler raises, the server sets isError for the agent and continues. A handler may return nil, which produces an empty structuredContent.
function tecs.io.mcp.dispatch(text: string): stringArguments
| Name | Type | Description |
|---|---|---|
text |
string |
One JSON-RPC request object. The server does not support a batch, which decodes as a JSON array with no method and produces an invalid-request error rather than dispatching each element. |
Returns
| Type | Description |
|---|---|
string |
Always a response as JSON text, never nil, and never a raise, since the response reports bad input in-band. The server still answers a request with no id, omitting the id field from the response. |
tecs.io.mcp.listen Static
Starts the server on port.
function tecs.io.mcp.listen(port: integer): mcp.ServerArguments
| Name | Type | Description |
|---|---|---|
port |
integer |
Defaults to 7100. A port already in use raises rather than selecting another port, since a debugger could not find a moved server. |
Returns
| Type | Description |
|---|---|
mcp.Server |
Listening, but answering nothing until something calls poll. |
tecs.io.mcp.register Static
Registers a tool. Re-registering a name replaces it.
Replacing keeps the name's original position in the list, so the order a client sees is first-registration order rather than last.
function tecs.io.mcp.register(tool: mcp.Tool)Arguments
| Name | Type | Description |
|---|---|---|
tool |
mcp.Tool |
Stored by reference and never copied, so mutating the table afterwards changes what the server dispatches and reports. name and The call requires name and handler and raises when either is absent; the rest may be nil. |
Returns
None.
tecs.io.mcp.setCrashed Static
Records a crash. Every subsequent world-touching call reports it.
function tecs.io.mcp.setCrashed(traceback: string)Arguments
| Name | Type | Description |
|---|---|---|
traceback |
string |
Stored as given and handed back verbatim by crashed, and sent as the whole body of the error a blocked tool answers with. Passing nil clears the crashed state and lets every tool run again. |
Returns
None.
tecs.io.mcp.tools Static
Returns every registered tool in registration order.
function tecs.io.mcp.tools(): {mcp.Tool}Arguments
None.
Returns
| Type | Description |
|---|---|
{mcp.Tool} |
A fresh list each call, so the caller may keep it. The Tool values in it are the registered tables themselves rather than copies, so writing to one writes to the registry. |