# 自定义工具 — Godot · AI Game Developer Wiki

用 C# 编写你自己的 Godot MCP 工具，在主线程执行 Godot API 调用，并从运行中的 Godot 游戏里暴露工具、提示和资源。

本页由英文机器翻译而成。[阅读英文原文](https://ai-game.dev/docs/wiki/godot/custom-tools)。

你可以在项目内用纯 C# 为 Godot MCP 添加自己的 MCP 工具。自定义工具就是一个 AI agent 可以调用的方法，并带有描述，告诉模型它做什么。本页介绍编辑器工具、主线程规则，以及让 agent 与运行中的游戏通信的可选运行时。

## 编写编辑器工具

一个工具族就是一个标记了 `[AiToolType]` 的 `partial class`。每个工具就是一个标记了 `[AiTool("tool-name")]` 的方法，方法本身和每个参数上都要有 `[Description]`，这样模型才知道何时以及如何调用它。

```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.";
        });
    }
}
```

构建 C# 项目（编辑器的 Build 按钮、`godot-cli build`，或让 agent 运行 `script-build`）。Godot 会重新加载程序集，插件会重新连接，新工具就会出现在 agent 的工具列表和停靠面板的 Tools 窗口中。

### 让工具保持可靠的规则

- **Godot 对象只能在主线程访问。** 工具方法是在主线程之外被调用的。对 `Node`、`Resource`、`SceneTree` 或 `EditorInterface` 的任何调用都必须通过 `MainThread.Instance.Run(...)`。从后台线程访问它们可能导致编辑器崩溃。
- **返回数据，而不是格式化文本。** 返回一个带属性的类（它会为模型序列化），或者对只有副作用的工具返回 `void`。凡是 agent 需要解析的内容，都不要用手工拼接的字符串。
- **标记可选参数。** 使用带默认值的可空类型，例如 `string? filter = null`，模型就会把该参数视为可选。
- **为一切写描述。** 模型读的就是这些描述。说明工具做什么、何时使用，以及每个参数的含义。
- **仅限编辑器的代码放在 `#if TOOLS` 之后。** 使用编辑器 API 的代码绝不能编译进导出的游戏。

### 提示与目标

`[AiTool]` 接受可选提示，例如 `ReadOnlyHint = true` 和 `IdempotentHint = true`，它们告诉客户端该工具不改变任何东西，或者可以安全地重复调用。`Target` 决定工具注册到哪里：`ToolTarget.Editor`（仅编辑器）、`ToolTarget.Runtime`（仅游戏）或默认值，两者都注册。

### 让 agent 来编写工具

系统工具 `godot-skill-create` 会把新的 C# 工具文件写入你的项目；该工具在下次构建后即可调用。在编写新工具之前，先确认 RQL 是否已经能完成这件事：大多数编辑器数据访问（节点、资源、文件、项目设置）只需一次 `rql-query` 或 `rql-mutate`。参见 [工具](https://ai-game.dev/zh-hans/docs/wiki/godot/tools#rql)。

## 运行中游戏里的工具

Godot MCP 也可以运行在运行中或已导出的游戏内，无论是调试版还是发布版。这使 agent 能够读取并驱动实时游戏状态，例如一个由 LLM 提供机器人走法的国际象棋游戏。有两点是刻意设计的：

- **它绝不会自行连接。** 由你的代码决定是否以及何时调用 `Connect()`。
- **默认不暴露任何东西。** 每个工具、提示和资源都由你自己注册。插件的编辑器工具不会被编译进游戏构建。

在 `SceneTree` 存在之后再启动它，例如在某个 autoload 的 `_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();
    }
}
```

用 `[AiToolType(Target = ToolTarget.Runtime)]`（来自 `com.IvanMurzak.McpPlugin.Common`）标记仅游戏用的工具类，这样编辑器就不会注册它们，即使编辑器已连接，agent 也能访问到游戏。

### Builder 选项

| 调用 | 作用 |
| --- | --- |
| `WithConfig(...)` | 在代码中设置 `Host`、`Token`、`ConnectionMode` 和 `AuthOption`。 |
| `WithToolsFromAssembly(...)` / `WithTools(...)` | 从程序集注册 `[AiToolType]` 类，或注册指定类型。 |
| `WithPromptsFromAssembly(...)` / `WithPrompts(...)` | 注册 `[AiPromptType]` 提示类。 |
| `WithResourcesFromAssembly(...)` / `WithResources(...)` | 注册 `[AiResourceType]` 资源类。 |
| `WithRuntimeErrorCapture()` | 捕获游戏内错误，并注册 `runtime-errors-get` / `runtime-errors-clear`。默认关闭。 |
| `WithScriptExecute()` | 在游戏中注册 `script-execute`。默认关闭；需要在 `.csproj` 中选择启用。 |
| `ConnectWhenRunFromEditor()` | 当从编辑器用 Play 启动游戏时也进行连接。默认关闭。 |
| `WithoutMainThreadDispatcher()` | 跳过自动的主线程调度器（仅在你安装了自己的调度器时使用）。 |

Host 和 token 也可以来自 `GODOT_MCP_*` 环境变量或 `.env` 文件，它们的优先级高于代码。游戏永远不会读取编辑器保存的设置。

### 提示与资源

除了工具之外，游戏还可以暴露 MCP prompts（具名指令模板，`[AiPromptType]` 搭配 `[AiPrompt]` 方法）和 resources（通过 URI 获取的只读内容，`[AiResourceType]` 搭配 `[AiResource]` 方法）。这两种类型都是可选的，注册方式与工具相同。

### 在游戏内运行 C#

要让 agent 在游戏中运行 C#，需要在游戏的 `.csproj` 中选择启用，并调用 `WithScriptExecute()`：

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

此功能仅适用于桌面平台的 CoreCLR 构建，不适用于 NativeAOT 或 iOS。

**警告**

游戏内的 `script-execute` 等同于任意代码执行，而运行时错误捕获会转发完整消息和堆栈跟踪，其中可能包含路径、用户名或密钥。两者都只应在开发构建中使用，并运行在带真实 token 的回环主机上，切勿在公开发布版本中启用它们。

## 游戏构建的安全检查清单

- 优先在 `localhost` 或 `127.0.0.1` 上使用 Custom 模式，并配合 `AuthOption.token` 和真实 token。
- 只注册你需要的工具；攻击面正是你所注册的那些工具。
- 在发布构建中，不要把连接暴露在公网网络接口上，除非你已为此做了专门的设计与安全加固。

完整工具列表请参见 [工具](https://ai-game.dev/zh-hans/docs/wiki/godot/tools)；打包好的工具集请参见 [扩展](https://ai-game.dev/zh-hans/docs/wiki/godot/extensions)。

---

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

Machine-translated (zh-Hans) from the English page: https://ai-game.dev/docs/wiki/godot/custom-tools
