# Custom tools

Write your own Godot MCP tools in C#, run Godot API calls on the main thread, and expose tools, prompts and resources from a running Godot game.

_Last updated: 2026-11-04_

You can add your own MCP tools to Godot MCP in plain C#, inside your project. A custom tool is a method the AI agent can call, with a description that tells the model what it does. This page covers editor tools, the main-thread rule, and the opt-in runtime that lets an agent talk to a running game.

## Write an editor tool

A tool family is a `partial class` marked `[AiToolType]`. Each tool is a method marked `[AiTool("tool-name")]`, with a `[Description]` on the method and on every parameter so the model knows when and how to call it.

```csharp
using System.ComponentModel;
using com.IvanMurzak.McpPlugin;           // [AiToolType], [AiTool]
using com.IvanMurzak.ReflectorNet.Utils;  // MainThread

[AiToolType]
public partial class Tool_MyFeature
{
    [AiTool("my-custom-task", Title = "Do a custom task")]
    [Description("Explain to the LLM what this does and when to call it.")]
    public string CustomTask
    (
        [Description("Explain to the LLM what this parameter is.")]
        string inputData
    )
    {
        // Work that does not touch the Godot API can run here, on a background thread.

        return MainThread.Instance.Run(() =>
        {
            // Touch EditorInterface, Node or Resource here, on the main thread.
            return "[Success] Operation completed.";
        });
    }
}
```

Build the C# project (the editor's Build button, `godot-cli build`, or ask the agent to run `script-build`). Godot reloads the assembly, the addon reconnects, and the new tool appears in the agent's tool list and in the dock's Tools window.

### Rules that keep tools reliable

- **Main thread only for Godot objects.** Tool methods are called off the main thread. Any call on `Node`, `Resource`, `SceneTree` or `EditorInterface` must go through `MainThread.Instance.Run(...)`. Touching them from the background thread can crash the editor.
- **Return data, not formatted text.** Return a class with properties (it is serialized for the model), or `void` for side-effect-only tools. Avoid hand-built strings for anything the agent needs to parse.
- **Mark optional parameters.** Use a nullable type with a default, for example `string? filter = null`, and the model treats the parameter as optional.
- **Describe everything.** The descriptions are what the model reads. Say what the tool does, when to use it and what each parameter means.
- **Editor-only code goes behind `#if TOOLS`.** Code that uses editor APIs must not compile into an exported game.

### Hints and targets

`[AiTool]` takes optional hints such as `ReadOnlyHint = true` and `IdempotentHint = true`, which tell clients the tool does not change anything or can safely be repeated. A `Target` decides where a tool is registered: `ToolTarget.Editor` (editor only), `ToolTarget.Runtime` (game only) or the default, both.

### Let the agent write the tool

The system tool `godot-skill-create` writes a new C# tool file into your project; the tool becomes callable after the next build. Before writing a new tool, check whether RQL already covers the job: most editor data access (nodes, resources, files, project settings) is one `rql-query` or `rql-mutate` away. See [Tools](https://ai-game.dev/docs/wiki/godot/tools#rql).

## Tools in a running game

Godot MCP can also run inside a running or exported game, debug or release. This lets an agent read and drive live game state, for example a chess game whose bot moves come from an LLM. Two things are deliberate:

- **It never connects on its own.** Your code decides if and when to call `Connect()`.
- **It exposes nothing by default.** You register every tool, prompt and resource yourself. The addon's editor tools are not compiled into a game build.

Start it once a `SceneTree` exists, for example from an autoload's `_Ready()`:

```csharp
using System.Reflection;
using com.IvanMurzak.Godot.MCP.Connection;
using com.IvanMurzak.Godot.MCP.Runtime;
using McpServerConsts = com.IvanMurzak.McpPlugin.Common.Consts.MCP.Server;
using Godot;

public partial class GameMcp : Node
{
    private GodotMcpRuntimeHandle? _mcp;

    public override async void _Ready()
    {
        _mcp = GodotMcpRuntime.Initialize(builder =>
        {
            builder.WithConfig(config =>
            {
                config.ConnectionMode = GodotMcpConnectionMode.Custom;
                config.Host = "http://localhost:8080";
                config.AuthOption = McpServerConsts.AuthOption.token;
                config.Token = "your-secret-token";
            });
            builder.WithToolsFromAssembly(Assembly.GetExecutingAssembly());
        }).Build();

        await _mcp.Connect();
    }

    public override async void _ExitTree()
    {
        if (_mcp is not null)
            await _mcp.Disconnect();
    }
}
```

Mark game-only tool classes with `[AiToolType(Target = ToolTarget.Runtime)]` (from `com.IvanMurzak.McpPlugin.Common`), so the editor does not register them and the agent reaches the game even while the editor is connected.

### Builder options

| Call | What it does |
| --- | --- |
| `WithConfig(...)` | Set `Host`, `Token`, `ConnectionMode` and `AuthOption` in code. |
| `WithToolsFromAssembly(...)` / `WithTools(...)` | Register `[AiToolType]` classes from an assembly, or specific types. |
| `WithPromptsFromAssembly(...)` / `WithPrompts(...)` | Register `[AiPromptType]` prompt classes. |
| `WithResourcesFromAssembly(...)` / `WithResources(...)` | Register `[AiResourceType]` resource classes. |
| `WithRuntimeErrorCapture()` | Capture in-game errors and register `runtime-errors-get` / `runtime-errors-clear`. Off by default. |
| `WithScriptExecute()` | Register `script-execute` in the game. Off by default; needs a `.csproj` opt-in. |
| `ConnectWhenRunFromEditor()` | Also connect when the game is started with Play from the editor. Off by default. |
| `WithoutMainThreadDispatcher()` | Skip the automatic main-thread dispatcher (only if you install your own). |

Host and token can also come from `GODOT_MCP_*` environment variables or a `.env` file, which win over code. A game never reads the editor's saved settings.

### Prompts and resources

Besides tools, a game can expose MCP prompts (named instruction templates, `[AiPromptType]` with `[AiPrompt]` methods) and resources (read-only content fetched by URI, `[AiResourceType]` with `[AiResource]` methods). Each kind is optional and registered the same way as tools.

### Running C# inside the game

To let the agent run C# in the game, opt in in the game's `.csproj` and call `WithScriptExecute()`:

```xml
<PropertyGroup>
  <GodotMcpCodeExecInGame>true</GodotMcpCodeExecInGame>
  <DefineConstants>$(DefineConstants);GODOT_MCP_CODE_EXEC_IN_GAME</DefineConstants>
</PropertyGroup>
```

This works on desktop CoreCLR builds only, not NativeAOT or iOS.

**Warning**

In-game `script-execute` is arbitrary code execution, and runtime error capture forwards full messages and stack traces that can contain paths, user names or secrets. Use both only in development builds, on a loopback host with a real token, and never ship them enabled in a public release.

## Security checklist for game builds

- Prefer Custom mode on `localhost` or `127.0.0.1` with `AuthOption.token` and a real token.
- Register only the tools you need; the attack surface is exactly the tools you register.
- Do not expose the connection on a public network interface in a release build unless you designed and secured it for that.

For the full tool list, see [Tools](https://ai-game.dev/docs/wiki/godot/tools); for packaged tool sets, see [Extensions](https://ai-game.dev/docs/wiki/godot/extensions).

---

Source (HTML): https://ai-game.dev/docs/wiki/godot/custom-tools
