doc/unity/en/tutorial/mcp-server.md
com.puerts.mcp is a Unity Editor plugin (UPM package) that launches an MCP (Model Context Protocol) server inside the Unity Editor, enabling external AI Agents / LLM clients to remotely invoke Unity Editor capabilities through the standard MCP protocol.
Core Capability: Through the evalJsCode tool, an external AI can execute arbitrary JavaScript code inside the Unity Editor, leveraging PuerTS's C# bridging to access the full UnityEngine.* and UnityEditor.* APIs.
@modelcontextprotocol/sdkThis package requires the following UPM dependencies:
| Package | Description |
|---|---|
com.tencent.puerts.core | PuerTS core runtime |
com.tencent.puerts.nodejs | PuerTS Node.js backend |
From the Unity Editor menu bar, select:
PuerTS → MCP Server
This opens the MCP Server management window.
| Parameter | Default | Description |
|---|---|---|
| Resource Root | LLMAgent/editor-assistant | Resource root path for builtin modules |
| Port | 3100 | HTTP service listening port (range 1–65535) |
Settings are automatically saved to
EditorPrefsand restored on next open.
Click the Start Server button. Once started successfully:
http://127.0.0.1:3100/sseClick the Stop Server button to shut down the service.
Application.runInBackground = true is set automatically, ensuring the service stays alive when Unity loses focus.Note: Entering Play mode triggers Unity's Domain Reload, which will restart the service.
Once started, the MCP Server exposes the following HTTP endpoints (default listener 127.0.0.1:3100):
| Endpoint | Method | Description |
|---|---|---|
/sse | GET | SSE connection endpoint; clients establish MCP sessions here |
/messages?sessionId=xxx | POST | JSON-RPC message endpoint for sending MCP requests |
/health | GET | Health check endpoint; returns {"status": "ok"} |
All endpoints support CORS (Access-Control-Allow-Origin: *).
Add the following SSE-type server to your MCP client configuration file:
{
"mcpServers": {
"puerts-unity-editor-assistant": {
"url": "http://127.0.0.1:3100/sse"
}
}
}
You can use curl to verify the service is running:
# Health check
curl http://127.0.0.1:3100/health
# Establish SSE connection (will continuously output event stream)
curl -N http://127.0.0.1:3100/sse
evalJsCodeExecutes JavaScript code inside the PuerTS runtime environment within the Unity Editor.
Parameters:
| Name | Type | Description |
|---|---|---|
code | string | Must be an async function declaration named execute |
Code Format Requirements:
async function execute() {
// your logic here
return someValue;
}
Key Features:
CS global object, including CS.UnityEngine.* and CS.UnityEditor.*return to pass back result values; objects are serialized via JSON.stringify, primitives are converted to stringsconsole.log() for debug output (outputs to Unity Console)Response Format:
// Success
{ "success": true, "result": "string representation of the return value" }
// Failure
{ "success": false, "error": "error message", "stack": "stack trace" }
Examples:
// Get the position of Main Camera in the scene
async function execute() {
const go = CS.UnityEngine.GameObject.Find('Main Camera');
return go.transform.position.toString();
}
// Get the names of all GameObjects in the active scene
async function execute() {
const scene = CS.UnityEngine.SceneManagement.SceneManager.GetActiveScene();
const roots = scene.GetRootGameObjects();
const names = [];
for (let i = 0; i < roots.Length; i++) {
names.push(roots.get_Item(i).name);
}
return JSON.stringify(names);
}
// Create a new Cube
async function execute() {
const cube = CS.UnityEngine.GameObject.CreatePrimitive(CS.UnityEngine.PrimitiveType.Cube);
cube.name = 'MyCube';
cube.transform.position = new CS.UnityEngine.Vector3(0, 1, 0);
return `Created ${cube.name} at ${cube.transform.position}`;
}
The evalJsCode runtime environment comes with several preloaded helper modules that encapsulate common Unity Editor operations. Load them via ESM dynamic import().
Usage Pattern: On first use of a module, read its
.descriptionto learn the API; then call functions directly thereafter.
// First use — read description
async function execute() {
const mod = await import('LLMAgent/editor-assistant/builtins/scene-view.mjs');
return mod.description;
}
// After learning the API — call directly
async function execute() {
const sv = await import('LLMAgent/editor-assistant/builtins/scene-view.mjs');
sv.focusSceneViewOn('Main Camera');
return 'Done';
}
scene-view — Scene View Camera Control & Hierarchy OperationsPath: LLMAgent/editor-assistant/builtins/scene-view.mjs
| Function | Description |
|---|---|
sceneViewZoom(direction, amount?) | Zoom the scene view camera (like mouse scroll) |
sceneViewPan(direction, amount?) | Pan the scene view camera (like middle-click drag) |
sceneViewOrbit(direction, amount?) | Orbit the scene view camera (like right-click drag) |
getSceneViewState() | Get the current scene view camera state |
setSceneViewCamera(pivot?, rotation?, size?) | Set the scene view camera state directly |
focusSceneViewOn(gameObjectName) | Focus on a specified GameObject (like pressing F) |
getGameObjectHierarchy(name?, depth?) | Get the GameObject hierarchy tree |
selectGameObject(name) | Select a GameObject in the Editor |
saveScene() | Save the current scene |
screenshot — Screenshot CapturePath: LLMAgent/editor-assistant/builtins/screenshot.mjs
| Function | Description |
|---|---|
captureScreenshot(maxWidth?, maxHeight?) | Capture a Game view screenshot |
captureSceneView(maxWidth?, maxHeight?) | Capture a Scene view screenshot |
Screenshots are automatically returned to the AI as image content — no manual base64 handling required.
type-reflection — C# Type ReflectionPath: LLMAgent/editor-assistant/builtins/type-reflection.mjs
| Function | Description |
|---|---|
listNamespaces() | List all available C# namespaces |
listTypesInNamespace(namespaces) | List all public types in specified namespaces |
getTypeDetails(typeNames) | Get detailed type information (properties, methods, fields, etc.) |
unity-log — Unity Console LogsPath: LLMAgent/editor-assistant/builtins/unity-log.mjs
| Function | Description |
|---|---|
getUnityLogs(count?, logType?) | Get recent Unity console logs |
getUnityLogSummary() | Get a log summary (counts by type) |
┌─────────────────────────────────────────────────┐
│ External AI Client (Cursor / Claude Desktop) │
│ MCP Client via SSE Transport │
└──────────────────────┬──────────────────────────┘
│ HTTP (SSE + JSON-RPC)
▼
┌─────────────────────────────────────────────────┐
│ Unity Editor Process │
│ ┌───────────────────────────────────────────┐ │
│ │ McpServerWindow (Editor UI) │ │
│ │ - Start / Stop / Status │ │
│ └──────────────────┬────────────────────────┘ │
│ │ │
│ ┌──────────────────▼────────────────────────┐ │
│ │ McpScriptManager (C# Runtime) │ │
│ │ - Manages PuerTS ScriptEnv │ │
│ │ - EditorApplication.update ticking │ │
│ └──────────────────┬────────────────────────┘ │
│ │ │
│ ┌──────────────────▼────────────────────────┐ │
│ │ PuerTS Node.js VM (V8) │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ MCP Server (TypeScript/JS) │ │ │
│ │ │ - HTTP Server (node:http) │ │ │
│ │ │ - SSE Transport │ │ │
│ │ │ - evalJsCode Tool │ │ │
│ │ │ - Builtin Helper Modules │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
Yes. The port and resource root can only be changed while the server is stopped. Restart the server after making changes.
No. The MCP Server instance is a global singleton, independent of the window lifecycle. Reopen the window to see the current running status.
Unity performs a Domain Reload when entering Play mode, which destroys and rebuilds the JS engine. You will need to manually restart the server after entering Play mode.
using PuertsMcp;
var manager = new McpScriptManager();
manager.Initialize("LLMAgent/editor-assistant", 3100, () =>
{
Debug.Log("MCP Server is ready!");
});
// Shutdown
manager.Shutdown();
Yes. Each SSE connection creates an independent session. Multiple AI clients can connect and interact simultaneously.