Skip to content

Commit 5acf107

Browse files
authored
Standardize how we define MCP tools (#292)
* refactor: migrate command routing to use CommandRegistry lookup instead of switch statement * style: improve code formatting and indentation consistency * refactor: clean up imports and type hints across tool modules * Revert "feat: Implement Asset Store Compliance for Unity MCP Bridge" This reverts commit 2fca7fc. * Revert "feat(asset-store): implement post-installation prompt system for Asset Store compliance" This reverts commit ab25a71. * chore: upgrade mcp[cli] dependency from 1.4.1 to 1.15.0 * style: fix formatting and whitespace in Python server files * Remove description, probably a Python versionn change * feat: add type hints and parameter descriptions to Unity MCP tools * docs: improve shader management tool parameter descriptions and types * refactor: add type annotations and improve documentation for script management tools * refactor: improve type annotations and documentation in manage_scene tool * refactor: add type annotations and improve parameter descriptions across MCP tools * feat: add explicit name parameters to all MCP tool decorators * refactor: remove unused Unity connection instance in manage_asset_tools * chore: update type hints in manage_editor function parameters for better clarity * feat: make name and path parameters optional for scene management operations * refactor: remove unused get_unity_connection import from manage_asset.py * chore: rename Operation parameter annotation to Operations for consistency * feat: add logging to MCP clients for tool actions across MCP server components * chore: add FastMCP type hint to register_all_tools parameter * style: reformat docstring in apply_text_edits tool to use multiline string syntax * refactor: update type hints from Dict/List/Tuple/Optional to modern Python syntax * refactor: clean up imports and add type annotations to script editing tools * refactor: update type hints to use | None syntax for optional parameters * Minor fixes * docs: improve tool descriptions with clearer action explanations * refactor: remove legacy update action migration code from manage_script.py * style: replace em dashes with regular hyphens in tool descriptions [skip ci] * refactor: convert manage_script_capabilities docstring to multiline format [skip ci]
1 parent af4ddf1 commit 5acf107

26 files changed

+1173
-1191
lines changed

UnityMcpBridge/Editor/MCPForUnityBridge.cs

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,27 +1040,7 @@ private static string ExecuteCommand(Command command)
10401040

10411041
// Use JObject for parameters as the new handlers likely expect this
10421042
JObject paramsObject = command.@params ?? new JObject();
1043-
1044-
// Route command based on the new tool structure from the refactor plan
1045-
object result = command.type switch
1046-
{
1047-
// Maps the command type (tool name) to the corresponding handler's static HandleCommand method
1048-
// Assumes each handler class has a static method named 'HandleCommand' that takes JObject parameters
1049-
"manage_script" => ManageScript.HandleCommand(paramsObject),
1050-
// Run scene operations on the main thread to avoid deadlocks/hangs (with diagnostics under debug flag)
1051-
"manage_scene" => HandleManageScene(paramsObject)
1052-
?? throw new TimeoutException($"manage_scene timed out after {FrameIOTimeoutMs} ms on main thread"),
1053-
"manage_editor" => ManageEditor.HandleCommand(paramsObject),
1054-
"manage_gameobject" => ManageGameObject.HandleCommand(paramsObject),
1055-
"manage_asset" => ManageAsset.HandleCommand(paramsObject),
1056-
"manage_shader" => ManageShader.HandleCommand(paramsObject),
1057-
"read_console" => ReadConsole.HandleCommand(paramsObject),
1058-
"manage_menu_item" => ManageMenuItem.HandleCommand(paramsObject),
1059-
"manage_prefabs" => ManagePrefabs.HandleCommand(paramsObject),
1060-
_ => throw new ArgumentException(
1061-
$"Unknown or unsupported command type: {command.type}"
1062-
),
1063-
};
1043+
object result = CommandRegistry.GetHandler(command.type)(paramsObject);
10641044

10651045
// Standard success response format
10661046
var response = new { status = "success", result };

UnityMcpBridge/Editor/Tools/CommandRegistry.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using Newtonsoft.Json.Linq;
44
using MCPForUnity.Editor.Tools.MenuItems;
5+
using MCPForUnity.Editor.Tools.Prefabs;
56

67
namespace MCPForUnity.Editor.Tools
78
{
@@ -22,6 +23,7 @@ public static class CommandRegistry
2223
{ "read_console", ReadConsole.HandleCommand },
2324
{ "manage_menu_item", ManageMenuItem.HandleCommand },
2425
{ "manage_shader", ManageShader.HandleCommand},
26+
{ "manage_prefabs", ManagePrefabs.HandleCommand},
2527
};
2628

2729
/// <summary>

UnityMcpBridge/Editor/Tools/ManageScript.cs

Lines changed: 295 additions & 293 deletions
Large diffs are not rendered by default.

UnityMcpBridge/UnityMcpServer~/src/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ RUN uv pip install --system -e .
2424

2525

2626
# Command to run the server
27-
CMD ["uv", "run", "server.py"]
27+
CMD ["uv", "run", "server.py"]
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""
22
MCP for Unity Server package.
3-
"""
3+
"""

UnityMcpBridge/UnityMcpServer~/src/config.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,30 @@
55

66
from dataclasses import dataclass
77

8+
89
@dataclass
910
class ServerConfig:
1011
"""Main configuration class for the MCP server."""
11-
12+
1213
# Network settings
1314
unity_host: str = "localhost"
1415
unity_port: int = 6400
1516
mcp_port: int = 6500
16-
17+
1718
# Connection settings
18-
connection_timeout: float = 1.0 # short initial timeout; retries use shorter timeouts
19+
# short initial timeout; retries use shorter timeouts
20+
connection_timeout: float = 1.0
1921
buffer_size: int = 16 * 1024 * 1024 # 16MB buffer
2022
# Framed receive behavior
21-
framed_receive_timeout: float = 2.0 # max seconds to wait while consuming heartbeats only
22-
max_heartbeat_frames: int = 16 # cap heartbeat frames consumed before giving up
23-
23+
# max seconds to wait while consuming heartbeats only
24+
framed_receive_timeout: float = 2.0
25+
# cap heartbeat frames consumed before giving up
26+
max_heartbeat_frames: int = 16
27+
2428
# Logging settings
2529
log_level: str = "INFO"
2630
log_format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
27-
31+
2832
# Server settings
2933
max_retries: int = 10
3034
retry_delay: float = 0.25
@@ -33,11 +37,12 @@ class ServerConfig:
3337
# Number of polite retries when Unity reports reloading
3438
# 40 × 250ms ≈ 10s default window
3539
reload_max_retries: int = 40
36-
40+
3741
# Telemetry settings
3842
telemetry_enabled: bool = True
3943
# Align with telemetry.py default Cloud Run endpoint
4044
telemetry_endpoint: str = "https://api-prod.coplay.dev/telemetry/events"
4145

46+
4247
# Create a global config instance
43-
config = ServerConfig()
48+
config = ServerConfig()

UnityMcpBridge/UnityMcpServer~/src/port_discovery.py

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,31 @@
1111
(quick socket connect + ping) before choosing it.
1212
"""
1313

14+
import glob
1415
import json
15-
import os
1616
import logging
1717
from pathlib import Path
18-
from typing import Optional, List
19-
import glob
2018
import socket
19+
from typing import Optional, List
2120

2221
logger = logging.getLogger("mcp-for-unity-server")
2322

23+
2424
class PortDiscovery:
2525
"""Handles port discovery from Unity Bridge registry"""
2626
REGISTRY_FILE = "unity-mcp-port.json" # legacy single-project file
2727
DEFAULT_PORT = 6400
2828
CONNECT_TIMEOUT = 0.3 # seconds, keep this snappy during discovery
29-
29+
3030
@staticmethod
3131
def get_registry_path() -> Path:
3232
"""Get the path to the port registry file"""
3333
return Path.home() / ".unity-mcp" / PortDiscovery.REGISTRY_FILE
34-
34+
3535
@staticmethod
3636
def get_registry_dir() -> Path:
3737
return Path.home() / ".unity-mcp"
38-
38+
3939
@staticmethod
4040
def list_candidate_files() -> List[Path]:
4141
"""Return candidate registry files, newest first.
@@ -52,7 +52,7 @@ def list_candidate_files() -> List[Path]:
5252
# Put legacy at the end so hashed, per-project files win
5353
hashed.append(legacy)
5454
return hashed
55-
55+
5656
@staticmethod
5757
def _try_probe_unity_mcp(port: int) -> bool:
5858
"""Quickly check if a MCP for Unity listener is on this port.
@@ -78,7 +78,8 @@ def _read_latest_status() -> Optional[dict]:
7878
try:
7979
base = PortDiscovery.get_registry_dir()
8080
status_files = sorted(
81-
(Path(p) for p in glob.glob(str(base / "unity-mcp-status-*.json"))),
81+
(Path(p)
82+
for p in glob.glob(str(base / "unity-mcp-status-*.json"))),
8283
key=lambda p: p.stat().st_mtime,
8384
reverse=True,
8485
)
@@ -88,14 +89,14 @@ def _read_latest_status() -> Optional[dict]:
8889
return json.load(f)
8990
except Exception:
9091
return None
91-
92+
9293
@staticmethod
9394
def discover_unity_port() -> int:
9495
"""
9596
Discover Unity port by scanning per-project and legacy registry files.
9697
Prefer the newest file whose port responds; fall back to first parsed
9798
value; finally default to 6400.
98-
99+
99100
Returns:
100101
Port number to connect to
101102
"""
@@ -120,26 +121,29 @@ def discover_unity_port() -> int:
120121
if first_seen_port is None:
121122
first_seen_port = unity_port
122123
if PortDiscovery._try_probe_unity_mcp(unity_port):
123-
logger.info(f"Using Unity port from {path.name}: {unity_port}")
124+
logger.info(
125+
f"Using Unity port from {path.name}: {unity_port}")
124126
return unity_port
125127
except Exception as e:
126128
logger.warning(f"Could not read port registry {path}: {e}")
127129

128130
if first_seen_port is not None:
129-
logger.info(f"No responsive port found; using first seen value {first_seen_port}")
131+
logger.info(
132+
f"No responsive port found; using first seen value {first_seen_port}")
130133
return first_seen_port
131134

132135
# Fallback to default port
133-
logger.info(f"No port registry found; using default port {PortDiscovery.DEFAULT_PORT}")
136+
logger.info(
137+
f"No port registry found; using default port {PortDiscovery.DEFAULT_PORT}")
134138
return PortDiscovery.DEFAULT_PORT
135-
139+
136140
@staticmethod
137141
def get_port_config() -> Optional[dict]:
138142
"""
139143
Get the most relevant port configuration from registry.
140144
Returns the most recent hashed file's config if present,
141145
otherwise the legacy file's config. Returns None if nothing exists.
142-
146+
143147
Returns:
144148
Port configuration dict or None if not found
145149
"""
@@ -151,5 +155,6 @@ def get_port_config() -> Optional[dict]:
151155
with open(path, 'r') as f:
152156
return json.load(f)
153157
except Exception as e:
154-
logger.warning(f"Could not read port configuration {path}: {e}")
155-
return None
158+
logger.warning(
159+
f"Could not read port configuration {path}: {e}")
160+
return None

UnityMcpBridge/UnityMcpServer~/src/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ version = "4.0.0"
44
description = "MCP for Unity Server: A Unity package for Unity Editor integration via the Model Context Protocol (MCP)."
55
readme = "README.md"
66
requires-python = ">=3.10"
7-
dependencies = ["httpx>=0.27.2", "mcp[cli]>=1.4.1"]
7+
dependencies = ["httpx>=0.27.2", "mcp[cli]>=1.15.0"]
88

99
[build-system]
1010
requires = ["setuptools>=64.0.0", "wheel"]

UnityMcpBridge/UnityMcpServer~/src/reload_sentinel.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
All functions are no-ops to prevent accidental external writes.
55
"""
66

7+
78
def flip_reload_sentinel(*args, **kwargs) -> str:
89
return "reload_sentinel.py is deprecated; use execute_menu_item → 'MCP/Flip Reload Sentinel'"

UnityMcpBridge/UnityMcpServer~/src/server.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
from mcp.server.fastmcp import FastMCP, Context, Image
1+
from mcp.server.fastmcp import FastMCP
22
import logging
33
from logging.handlers import RotatingFileHandler
44
import os
5-
from dataclasses import dataclass
65
from contextlib import asynccontextmanager
7-
from typing import AsyncIterator, Dict, Any, List
6+
from typing import AsyncIterator, Dict, Any
87
from config import config
98
from tools import register_all_tools
109
from unity_connection import get_unity_connection, UnityConnection
@@ -150,8 +149,7 @@ def _emit_startup():
150149

151150
# Initialize MCP server
152151
mcp = FastMCP(
153-
"mcp-for-unity-server",
154-
description="Unity Editor integration via Model Context Protocol",
152+
name="mcp-for-unity-server",
155153
lifespan=server_lifespan
156154
)
157155

0 commit comments

Comments
 (0)