← Home

Godot In-game MCP Server

26 July, 2026 - Tags: Godot, AI, game dev, MCP

記得之前在我第一次瞭解到 MCP 的時候,我就想到這種讓 LLM 可以呼叫外部工具的標準,是不是可以用在遊戲上面,只要我把遊戲內的資料讀取與操作都包成 MCP 介面的話,遊戲 NPC 的核心邏輯是有機會被 LLM 取代的。雖然在目前的狀況這很顯然存在問題,包括雲端模型的隱私、成本以及延遲問題,或是本機模型的硬體需求等等。但我仍然覺得這會是有趣的嘗試,從各種 AI harness (e.g. OpenClaw) 的興起可以看出,當給出合適的工具時 AI 可以做出很不錯的成果。

在遊戲開發者的社群中也可以看到一些嘗試,像是 yayapi 寫了一個 Unity 的 MCP demo,示範如何透過 MCP 將遊戲執行時的遊戲資料暴露給 agent 並提供修改的工具,或是《天堂》之父宋在京做了《Open MMORPG》透過 MCP 讓遊戲內的 NPC 操作與玩家完全一樣,我認為這些方向都挺符合我想探索的可能性。

其實在 ChatGPT 剛推出不久的年代,我也有看過類似的想法,其中之一是 flecs 的作者 Sander Mertens 在 medium 上發布的 Why it is time to start thinking of games as databases,裡面提到了 NPC 可以如何跟遊戲中的資料互動,其中獲取資訊的方式背後就是 ECS query。但我覺得除了 ECS,對於熟悉 web 開發的人來說,資料庫本身也是個很適合的介面,只要我們能夠把遊戲中的資料存進去 SQL 的話,那麼獲取資料的方式就會變成 SQL query 了,要做到 relation 的話就是 JOIN 起來,或是 Graph Database 也會是很好的選項?

雖然我是覺得研究相關的做法如何應用在遊戲開發中很有趣,但實在是懶得動手,直到某天我突然想起 Godot 有內建的 TCPServer 能夠讓我直接在遊戲內開一個 TCP server,MCP 又支援用 HTTP 當 transport,所以我只要寫短短的 wrapper 去解析 TCP payload 就好,這樣就能不依賴外部套件實作出 MCP server,聽起來相當符合我的興趣。就這樣,經過了 Antigravity 幾天的努力後 in-game-mcp-godot 誕生了。

Code

MCP server 端的核心實作在 mcp_sse_transport.gd 這個檔案,總共只有不到 400 行,但足以滿足 MCP 大部分的 spec 了,因為在 HTTP 這邊需要實作的 route 很少,所以是直接寫死的,把所有條件列出來之後在底下判斷要進去哪個 handler:

var is_options = (client.method == "OPTIONS")
var is_get_sse = (client.method == "GET" and client.path.begins_with("/sse"))
var is_post_msg = (
    client.method == "POST" and
    (client.path.begins_with("/message") or client.path.begins_with("/sse")))

if is_options:
    pass # 處理 CORS preflight check
elif is_get_sse:
    pass # upgrade to SSE
elif is_post_msg:
    pass # 處理 SSE payload 裡面的 JSON RPC
else:                
    pass

後面處理 POST 跟 GET 的邏輯,因為是針對 json 格式的 payload 做處理,然後 GDScript 一樣有內建 JSON parsing,所以就變得很簡單了。而 handler 的定義,這邊實作了兩種註冊方式,一種是撰寫獨立的 .gd 檔案定義完整的 MCP Tool:

# my_tool.gd
extends MCPTool

func get_tool_name() -> String:
    return "player_get_health"

func get_description() -> String:
    return "Returns the health points of the main player character."

func get_input_schema() -> Dictionary:
    return {
        "type": "object",
        "properties": {
            "player_id": {
                "type": "string",
                "description": "Optional specific player ID"
            }
        }
    }

func execute(args: Dictionary) -> Dictionary:
    var player = get_tree().get_first_node_in_group("player")
    if not player:
        return {
            "isError": true,
            "content": [{"type": "text", "text": "Player not found."}]
        }
    return {
        "isError": false,
        "content": [{
            "type": "text",
            "text": "Player health is: %d" % player.health
        }]
    }

然後在 runtime 時手動註冊:

var health_tool = load("res://my_tool.gd").new()
add_child(health_tool)
MCPServer.register_tool(health_tool)

這個大致上是一開始 Antigravity 給我的版本,可以看到每個工具都需要一份獨立的 .gd(或至少一個獨立的 class,可以有多個存在於單一檔案內)。因為我覺得這樣有點麻煩,所以想說研究看看能否在 GDScript 中寫出類似 FastAPI 那樣用 decorator 註冊的形式,但因為語法上不支援,加上我也沒想到好的設計,最後是提供了可以用 lambda 註冊的 API,使用範例如下:

MCPServer.register_function(
    "heal_player",
    "Heals the player character by a specific amount.",
    {
        "type": "object",
        "properties": {
            "amount": {
              "type": "integer",
              "description": "HP points to restore",
              "default": 10
            }
        }
    },
    func(args: Dictionary) -> Dictionary:
        var amount = int(args.get("amount", 10))
        # Perform modification safely deferred (Execution Rule)
        call_deferred("_deferred_heal", amount)
        return {
            "isError": false,
            "content": [{"type": "text", "text": "Heal command queued."}]
        }
)

這樣的流程就比較簡單了,而且理想上 MCP tool 都會是遊戲核心邏輯的薄薄一層 wrapper,這樣的寫法應該足夠簡便,如果要更進一步或許可以自動在 runtime 去遍歷一個 object 的所有 method,透過某種方式來決定如何註冊到 MCP server 上。不過因為 GDScript 不像 Python 那樣可以抓 doc-string,沒辦法自動生成 tool description,這部份真的想做的話寫個 codegen 我想才比較可行,但我就沒有再深入研究了。

MCP Apps

除了基本的 MCP spec 支援,因為想起以前看過有人透過 MCP Apps 把 DOOM 塞進 Claude / ChatGPT 內,我覺得如果我能夠在遊戲中的 MCP server 提供 UI 給 client 好像還蠻酷的,就把它加上去了。基本用法長這樣:

MCPServer.register_dynamic_resource(
    "ui://demo/panel",
    "Godot Control Center",
    "text/html;profile=mcp-app",
    "Interactive control panel for game telemetry and manipulation.",
    _get_ui_html
)

其中 _get_ui_html 是個會回傳 HTML 字串的函式,這段程式碼會在 MCP server 上註冊一項資源,MCP client 去這邊把 HTML 抓下來就可以 render 了。但是 MCP client 並不會直接去抓 UI,它會認的是 tool 回傳的 _meta.ui.resourceUri 欄位,當 tool 回傳包含它時 client 才會 render 這份 HTML。因此我們還需要在程式碼中註冊一個 tool:

MCPServer.register_function(
    # ...參數省略,
    { "ui": { "resourceUri": "ui://demo/panel" } }
)

在 handler 後面的參數就是 tool 的 metadata,有這項資訊 client 就會顯示 UI 了,但因為在我 demo 的時候 Claude 跟 ChatGPT 桌面版都還不支援 Linux,最後我是用 VS Code Copilot Extension 來測試的,我不大確定其他 client 的支援如何,至少只有 terminal 的應該都不行,我本來以為他們會幫我打開瀏覽器的。

至於這功能實務上到底有什麼真實的 usecase,我其實也不大確定,後來有去問了 DK 它覺得好處是可以用 HTML + CSS 來排版做遊戲的 debug panel 會比遊戲內 UI 簡單,另外因為 UI render 的部份是獨立於遊戲外的 process (MCP client),推測對於遊戲本體的干擾會小一些。他有傳 Unity 的 Remote Runtime Inspector + Console 這個 asset 給我參考,看起來它也是提供獨立的網頁當 inspector。或許我可以嘗試看看哪天來自己實作一個類似的專案,看會不會有其他的 godot 開發者需要。

Demo

當初開發這個套件的初衷,就是我好奇現在的 agent 能不能在有適當工具的環境下不做微調直接玩遊戲,為此我寫了簡單的 demo 來驗證。一開始的版本只是 Antigravity 自己想的一個 2D top-down RPG,提供可以移動玩家位置跟生怪的 tool,雖然是可以看出 agent 不用多加說明也可以正常操作,但還是缺了點遊戲性。所以我決定還是要做更複雜一點的案例,本來是想說要不要做個本機多人的卡牌遊戲,讓 Antigravity 跟 Claude 陪我打牌,但後來跟同學討論過覺得做個世紀帝國類型的遊戲比較有趣。

遊戲內可以做的操作不多,就是蓋建築、訓練單位跟移動單位,除了這幾個 tool 以外還有一個是取得遊戲狀態。因為我懶得找美術素材,所有的圖片都是用 Godot 提供的 API 在遊戲內即時畫出來的,總之就是一堆幾何形狀的組合,似乎要讓 agent 自己動的話會蠻傾向這樣做的,我之前參加 kuso game jam 時 Antigravity 也是用 C# 去排遊戲內的所有 UI,但是蠻容易跑版的,我這 demo 可能因為元素比較單純所以我覺得效果還不錯。

做好之後我跟 Antigravity 還有 Claude 一起玩了一局,下的目標只有模糊的要求他們完成遊戲而已,他們有自己推敲出來目的是要拆掉別人主堡(雖然遊戲程式碼內根本沒有這個邏輯)。結果是 Antigravity 勝出,但我想有很大一部分是因為我是後面才開 Claude 下 prompt 的,而且我預設 setting 沒有自動 approve MCP 呼叫,是我在 session 內手動允許的,造成很大的時間差。

縱使結果不算標準,但只是 demo 而已我覺得效果挺好的了,後面還要繼續發展的話比起工具本身可能更多的是專案策略方面比較值得深入,像是怎樣的 tool 定義會讓 agent 可以有更好的表現,以及套件內是否可以提供一些預設的 tool 來滿足常見的遊戲開發需求等等,大概都是不錯的方向。除此之外,我覺得在 MCP Apps 的應用上也有很多值得研究的方向,除了上一段 DK 跟我提過的建構 debug panel 以外,探索如何讓 agent 動態生成 UI 應該也是個社群很感興趣的方向,像是我之前看過 AI Engineer 的一場演講 Beyond Components: Designing Generative UI for MCP Apps — Ruben Casas, Postman 就是 Postman 的工程師在分享讓 AI 動態產生 UI 的可能性,MCP-UI(MCP Apps 的前身)剛推出的時候也是類似的 demo。

Godot 開拓者線上分享會

這個專案我在完成後也有到 Godot 開拓交流互助公會每月固定舉辦的分享會向大家介紹,直播的錄影連結在這邊,我覺得跟之前講 CI/CD 的一樣大家看起來蠻有興趣的,不知道會後實際評估自己要不要導入的會有多少。蠻希望這些東西可以實際幫助到別的開發團隊的,感覺比起寫遊戲本身我還是對於搞定工具鏈比較有熱忱,但為了確保東西符合需求也還是要實際寫點遊戲啦,dogfooding 我想還是很重要的。