coplay-mcp-server 1.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coplay_mcp_server/__init__.py +3 -0
- coplay_mcp_server/code_generator.py +370 -0
- coplay_mcp_server/generated_tools/.gitignore +4 -0
- coplay_mcp_server/generated_tools/__init__.py +4 -0
- coplay_mcp_server/generated_tools/agent_tool_tools.py +347 -0
- coplay_mcp_server/generated_tools/coplay_tool_tools.py +58 -0
- coplay_mcp_server/generated_tools/image_tool_tools.py +146 -0
- coplay_mcp_server/generated_tools/input_action_tool_tools.py +718 -0
- coplay_mcp_server/generated_tools/package_tool_tools.py +240 -0
- coplay_mcp_server/generated_tools/profiler_functions_tools.py +63 -0
- coplay_mcp_server/generated_tools/scene_view_functions_tools.py +58 -0
- coplay_mcp_server/generated_tools/screenshot_tool_tools.py +87 -0
- coplay_mcp_server/generated_tools/snapping_functions_tools.py +409 -0
- coplay_mcp_server/generated_tools/ui_functions_tools.py +419 -0
- coplay_mcp_server/generated_tools/unity_functions_tools.py +1643 -0
- coplay_mcp_server/image_utils.py +96 -0
- coplay_mcp_server/process_discovery.py +168 -0
- coplay_mcp_server/server.py +236 -0
- coplay_mcp_server/unity_client.py +342 -0
- coplay_mcp_server-1.4.1.dist-info/METADATA +70 -0
- coplay_mcp_server-1.4.1.dist-info/RECORD +24 -0
- coplay_mcp_server-1.4.1.dist-info/WHEEL +4 -0
- coplay_mcp_server-1.4.1.dist-info/entry_points.txt +3 -0
- coplay_mcp_server-1.4.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Utility functions for image processing in MCP server."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import logging
|
|
5
|
+
import mimetypes
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def convert_image_to_base64_url(image_path: str) -> Optional[str]:
|
|
13
|
+
"""Convert local image path to base64 data URL for MCP compatibility.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
image_path: Local file path to the image
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
Base64 data URL string (e.g., 'data:image/jpeg;base64,/9j/4AAQ...') or None if conversion fails
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
path = Path(image_path)
|
|
23
|
+
if not path.exists():
|
|
24
|
+
logger.warning(f"Image file not found: {image_path}")
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
if not path.is_file():
|
|
28
|
+
logger.warning(f"Path is not a file: {image_path}")
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
# Determine MIME type from file extension
|
|
32
|
+
mime_type, _ = mimetypes.guess_type(str(path))
|
|
33
|
+
if not mime_type or not mime_type.startswith("image/"):
|
|
34
|
+
# Default to JPEG for unknown image types
|
|
35
|
+
mime_type = "image/jpeg"
|
|
36
|
+
logger.debug(
|
|
37
|
+
f"Unknown MIME type for {image_path}, defaulting to {mime_type}"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Read and encode image to base64
|
|
41
|
+
with open(path, "rb") as f:
|
|
42
|
+
image_data = base64.b64encode(f.read()).decode("utf-8")
|
|
43
|
+
|
|
44
|
+
data_url = f"data:{mime_type};base64,{image_data}"
|
|
45
|
+
logger.debug(
|
|
46
|
+
f"Successfully converted {image_path} to base64 data URL ({len(data_url)} chars)"
|
|
47
|
+
)
|
|
48
|
+
return data_url
|
|
49
|
+
|
|
50
|
+
except Exception as e:
|
|
51
|
+
logger.error(f"Failed to convert image to base64: {e}")
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def is_image_file(file_path: str) -> bool:
|
|
56
|
+
"""Check if a file path points to an image file.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
file_path: Path to check
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
True if the file appears to be an image based on extension
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
mime_type, _ = mimetypes.guess_type(file_path)
|
|
66
|
+
return mime_type is not None and mime_type.startswith("image/")
|
|
67
|
+
except Exception:
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_image_info(image_path: str) -> Optional[dict]:
|
|
72
|
+
"""Get basic information about an image file.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
image_path: Path to the image file
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Dictionary with image info or None if file cannot be read
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
path = Path(image_path)
|
|
82
|
+
if not path.exists():
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
stat = path.stat()
|
|
86
|
+
mime_type, _ = mimetypes.guess_type(str(path))
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
"path": str(path),
|
|
90
|
+
"size_bytes": stat.st_size,
|
|
91
|
+
"mime_type": mime_type,
|
|
92
|
+
"is_image": is_image_file(str(path)),
|
|
93
|
+
}
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.error(f"Failed to get image info for {image_path}: {e}")
|
|
96
|
+
return None
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Unity process discovery utilities."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import platform
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
import psutil
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def find_unity_processes() -> List[psutil.Process]:
|
|
14
|
+
"""Find all running Unity Editor processes."""
|
|
15
|
+
unity_processes = []
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
|
|
19
|
+
try:
|
|
20
|
+
proc_info = proc.info
|
|
21
|
+
if not proc_info['name']:
|
|
22
|
+
continue
|
|
23
|
+
|
|
24
|
+
name = proc_info['name'].lower()
|
|
25
|
+
|
|
26
|
+
# Check for Unity process names across platforms
|
|
27
|
+
if any(unity_name in name for unity_name in [
|
|
28
|
+
'unity', 'unity.exe', 'unity editor', 'unity hub'
|
|
29
|
+
]):
|
|
30
|
+
# Skip Unity Hub, we only want Editor instances
|
|
31
|
+
if 'hub' not in name:
|
|
32
|
+
unity_processes.append(proc)
|
|
33
|
+
|
|
34
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
except Exception as e:
|
|
38
|
+
logger.error(f"Error finding Unity processes: {e}")
|
|
39
|
+
|
|
40
|
+
return unity_processes
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def extract_project_path_from_cmdline(cmdline: List[str]) -> Optional[str]:
|
|
44
|
+
"""Extract Unity project path from command line arguments."""
|
|
45
|
+
if not cmdline:
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
# Look for -projectPath argument
|
|
50
|
+
for i, arg in enumerate(cmdline):
|
|
51
|
+
if arg.lower() == "-projectpath" and i + 1 < len(cmdline):
|
|
52
|
+
project_path = cmdline[i + 1]
|
|
53
|
+
# Remove quotes if present
|
|
54
|
+
project_path = project_path.strip('"\'')
|
|
55
|
+
return project_path
|
|
56
|
+
|
|
57
|
+
except Exception as e:
|
|
58
|
+
logger.debug(f"Error extracting project path from cmdline: {e}")
|
|
59
|
+
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def verify_unity_project(project_path: str) -> bool:
|
|
64
|
+
"""Verify that a path is a valid Unity project."""
|
|
65
|
+
try:
|
|
66
|
+
project_dir = Path(project_path)
|
|
67
|
+
if not project_dir.exists() or not project_dir.is_dir():
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
# Check for Assets folder (required for Unity projects)
|
|
71
|
+
assets_dir = project_dir / "Assets"
|
|
72
|
+
if not assets_dir.exists() or not assets_dir.is_dir():
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
except Exception as e:
|
|
78
|
+
logger.debug(f"Error verifying Unity project at {project_path}: {e}")
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _discover_from_temp_dirs() -> List[str]:
|
|
83
|
+
"""Discover Unity projects from MCP request directories in temp locations."""
|
|
84
|
+
project_roots = []
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
# Common temp directory locations
|
|
88
|
+
temp_paths = []
|
|
89
|
+
|
|
90
|
+
if platform.system() == "Windows":
|
|
91
|
+
import tempfile
|
|
92
|
+
temp_paths.append(Path(tempfile.gettempdir()))
|
|
93
|
+
# Also check AppData/Local
|
|
94
|
+
try:
|
|
95
|
+
appdata_local = Path.home() / "AppData" / "Local"
|
|
96
|
+
if appdata_local.exists():
|
|
97
|
+
temp_paths.append(appdata_local)
|
|
98
|
+
except Exception:
|
|
99
|
+
pass
|
|
100
|
+
else:
|
|
101
|
+
# Unix-like systems
|
|
102
|
+
temp_paths.extend([
|
|
103
|
+
Path("/tmp"),
|
|
104
|
+
Path.home() / ".cache",
|
|
105
|
+
Path("/var/tmp")
|
|
106
|
+
])
|
|
107
|
+
|
|
108
|
+
for temp_path in temp_paths:
|
|
109
|
+
if not temp_path.exists():
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
# Look for Coplay MCP request directories
|
|
114
|
+
for path in temp_path.rglob("**/Temp/Coplay/MCPRequests"):
|
|
115
|
+
if path.is_dir():
|
|
116
|
+
# Try to infer project root from temp path structure
|
|
117
|
+
temp_index = None
|
|
118
|
+
parts = path.parts
|
|
119
|
+
|
|
120
|
+
for i, part in enumerate(parts):
|
|
121
|
+
if part.lower() == "temp":
|
|
122
|
+
temp_index = i
|
|
123
|
+
break
|
|
124
|
+
|
|
125
|
+
if temp_index and temp_index > 0:
|
|
126
|
+
project_path = str(Path(*parts[:temp_index]))
|
|
127
|
+
if verify_unity_project(project_path):
|
|
128
|
+
project_roots.append(project_path)
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.debug(f"Error searching temp path {temp_path}: {e}")
|
|
132
|
+
|
|
133
|
+
except Exception as e:
|
|
134
|
+
logger.debug(f"Error discovering from temp directories: {e}")
|
|
135
|
+
|
|
136
|
+
return project_roots
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def discover_unity_project_roots() -> List[str]:
|
|
140
|
+
"""Discover all Unity project roots from running processes."""
|
|
141
|
+
project_roots = []
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
unity_processes = find_unity_processes()
|
|
145
|
+
logger.info(f"Found {len(unity_processes)} Unity processes")
|
|
146
|
+
|
|
147
|
+
for proc in unity_processes:
|
|
148
|
+
try:
|
|
149
|
+
cmdline = proc.cmdline()
|
|
150
|
+
project_path = extract_project_path_from_cmdline(cmdline)
|
|
151
|
+
|
|
152
|
+
if project_path and verify_unity_project(project_path):
|
|
153
|
+
if project_path not in project_roots:
|
|
154
|
+
project_roots.append(project_path)
|
|
155
|
+
logger.info(f"Found Unity project: {project_path}")
|
|
156
|
+
|
|
157
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
|
158
|
+
continue
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.debug(f"Error processing Unity process {proc.pid}: {e}")
|
|
161
|
+
|
|
162
|
+
except Exception as e:
|
|
163
|
+
logger.error(f"Error discovering Unity project roots: {e}")
|
|
164
|
+
|
|
165
|
+
# Also check for MCP request directories in temp locations
|
|
166
|
+
project_roots.extend(_discover_from_temp_dirs())
|
|
167
|
+
|
|
168
|
+
return list(set(project_roots)) # Remove duplicates
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""Main entry point for Coplay MCP Server using FastMCP."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from typing import Any, Optional, Annotated
|
|
8
|
+
|
|
9
|
+
from pydantic import Field
|
|
10
|
+
from coplay_mcp_server.process_discovery import discover_unity_project_roots
|
|
11
|
+
from fastmcp import FastMCP
|
|
12
|
+
|
|
13
|
+
from coplay_mcp_server.unity_client import UnityRpcClient
|
|
14
|
+
from coplay_mcp_server.generated_tools import (
|
|
15
|
+
unity_functions_tools,
|
|
16
|
+
image_tool_tools,
|
|
17
|
+
coplay_tool_tools,
|
|
18
|
+
agent_tool_tools,
|
|
19
|
+
package_tool_tools,
|
|
20
|
+
input_action_tool_tools,
|
|
21
|
+
ui_functions_tools,
|
|
22
|
+
snapping_functions_tools,
|
|
23
|
+
scene_view_functions_tools,
|
|
24
|
+
profiler_functions_tools,
|
|
25
|
+
screenshot_tool_tools,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Set binary mode for stdin/stdout/stderr on Windows
|
|
29
|
+
# This is necessary for proper MCP protocol communication on Windows
|
|
30
|
+
if sys.platform == 'win32':
|
|
31
|
+
import msvcrt
|
|
32
|
+
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
|
|
33
|
+
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
|
|
34
|
+
msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def setup_logging() -> None:
|
|
38
|
+
"""Set up logging configuration with support for file logging and configurable log level via environment variables.
|
|
39
|
+
|
|
40
|
+
Environment variables:
|
|
41
|
+
- COPLAY_LOG_FILE: Path to log file (if set, logs will be written to this file)
|
|
42
|
+
- COPLAY_LOG_LEVEL: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL). Defaults to WARNING.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
# Get log level from environment variable, default to WARNING
|
|
46
|
+
log_level_str = os.getenv("COPLAY_LOG_LEVEL", "WARNING").upper()
|
|
47
|
+
log_level = getattr(logging, log_level_str, logging.WARNING)
|
|
48
|
+
|
|
49
|
+
# Get log file path from environment variable
|
|
50
|
+
log_file = os.getenv("COPLAY_LOG_FILE")
|
|
51
|
+
|
|
52
|
+
# Create handlers list
|
|
53
|
+
handlers = [
|
|
54
|
+
# Log to stderr (visible to MCP client)
|
|
55
|
+
logging.StreamHandler(sys.stderr),
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
# Add file handler if log file is specified
|
|
59
|
+
if log_file:
|
|
60
|
+
try:
|
|
61
|
+
# Create directory if it doesn't exist
|
|
62
|
+
log_dir = os.path.dirname(log_file)
|
|
63
|
+
if log_dir and not os.path.exists(log_dir):
|
|
64
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
65
|
+
|
|
66
|
+
file_handler = logging.FileHandler(log_file, mode='a', encoding='utf-8')
|
|
67
|
+
file_handler.setFormatter(
|
|
68
|
+
logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
|
69
|
+
)
|
|
70
|
+
handlers.append(file_handler)
|
|
71
|
+
|
|
72
|
+
# Log to stderr that file logging is enabled
|
|
73
|
+
sys.stderr.write(f"File logging enabled: {log_file}\n")
|
|
74
|
+
except Exception as e:
|
|
75
|
+
sys.stderr.write(f"Failed to set up file logging to {log_file}: {e}\n")
|
|
76
|
+
|
|
77
|
+
# Configure logging
|
|
78
|
+
logging.basicConfig(
|
|
79
|
+
level=log_level,
|
|
80
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
81
|
+
handlers=handlers,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Set specific log levels for noisy libraries
|
|
85
|
+
logging.getLogger("watchdog").setLevel(logging.WARNING)
|
|
86
|
+
logging.getLogger("asyncio").setLevel(logging.WARNING)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
setup_logging()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Initialize FastMCP server
|
|
93
|
+
mcp = FastMCP(name="coplay-mcp-server")
|
|
94
|
+
|
|
95
|
+
# Global Unity client instance
|
|
96
|
+
unity_client = UnityRpcClient()
|
|
97
|
+
|
|
98
|
+
logger = logging.getLogger(__name__)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@mcp.tool()
|
|
102
|
+
async def set_unity_project_root(
|
|
103
|
+
unity_project_root: str
|
|
104
|
+
) -> str:
|
|
105
|
+
"""Set the Unity project root path for the MCP server instance. This tool should be called before using any other Unity tools."""
|
|
106
|
+
try:
|
|
107
|
+
logger.info(f"Setting Unity project root to: {unity_project_root}")
|
|
108
|
+
|
|
109
|
+
if not unity_project_root or not unity_project_root.strip():
|
|
110
|
+
raise ValueError("Unity project root cannot be empty")
|
|
111
|
+
|
|
112
|
+
# Set the Unity project root in the RPC client
|
|
113
|
+
unity_client.set_unity_project_root(unity_project_root)
|
|
114
|
+
|
|
115
|
+
result = f"Unity project root set to: {unity_project_root}"
|
|
116
|
+
logger.info("Unity project root set successfully")
|
|
117
|
+
return result
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.error(f"Failed to set Unity project root: {e}")
|
|
121
|
+
raise
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@mcp.tool()
|
|
125
|
+
async def list_unity_project_roots() -> Any:
|
|
126
|
+
"""List all project roots of currently open Unity instances. This tool discovers all running Unity Editor instances and returns their project root directories."""
|
|
127
|
+
try:
|
|
128
|
+
logger.info("Discovering Unity project roots...")
|
|
129
|
+
|
|
130
|
+
project_roots = discover_unity_project_roots()
|
|
131
|
+
return {
|
|
132
|
+
"count": len(project_roots),
|
|
133
|
+
"projectRoots": [
|
|
134
|
+
{
|
|
135
|
+
"projectRoot": root,
|
|
136
|
+
"projectName": root.split("/")[-1]
|
|
137
|
+
if "/" in root
|
|
138
|
+
else root.split("\\")[-1],
|
|
139
|
+
}
|
|
140
|
+
for root in project_roots
|
|
141
|
+
],
|
|
142
|
+
}
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.error(f"Failed to list Unity project roots: {e}")
|
|
145
|
+
raise
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@mcp.tool()
|
|
149
|
+
async def create_coplay_task(
|
|
150
|
+
prompt: Annotated[
|
|
151
|
+
str,
|
|
152
|
+
Field(description="The task prompt to submit"),
|
|
153
|
+
],
|
|
154
|
+
file_paths: Annotated[
|
|
155
|
+
Optional[str],
|
|
156
|
+
Field(description="Optional comma-separated file paths to attach as context"),
|
|
157
|
+
] = None,
|
|
158
|
+
model: Annotated[
|
|
159
|
+
Optional[str],
|
|
160
|
+
Field(description="Optional AI model to use for this task"),
|
|
161
|
+
] = None,
|
|
162
|
+
) -> Any:
|
|
163
|
+
"""Creates a new task in the Unity Editor with the specified prompt and optional file attachments.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
prompt: The task prompt to submit
|
|
167
|
+
file_paths: Optional comma-separated file paths to attach as context
|
|
168
|
+
model: Optional AI model to use for this task
|
|
169
|
+
"""
|
|
170
|
+
try:
|
|
171
|
+
logger.info(f"Creating task with prompt: {prompt[:10000]}...")
|
|
172
|
+
|
|
173
|
+
params = {"prompt": prompt}
|
|
174
|
+
if file_paths:
|
|
175
|
+
params["file_paths"] = file_paths
|
|
176
|
+
if model:
|
|
177
|
+
params["model"] = model
|
|
178
|
+
|
|
179
|
+
# Always wait for completion
|
|
180
|
+
params["wait_for_completion"] = "true"
|
|
181
|
+
|
|
182
|
+
# Use a longer timeout (610 seconds) to accommodate Unity's default 600-second timeout
|
|
183
|
+
result = await unity_client.execute_request(
|
|
184
|
+
"create_task", params, timeout=610.0
|
|
185
|
+
)
|
|
186
|
+
return result
|
|
187
|
+
except Exception as e:
|
|
188
|
+
logger.error(f"Failed to create task: {e}")
|
|
189
|
+
raise
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def main():
|
|
193
|
+
"""Initialize MCP server with generated tools and start serving."""
|
|
194
|
+
try:
|
|
195
|
+
logger.info("Initializing Coplay MCP Server...")
|
|
196
|
+
|
|
197
|
+
# Register all generated tools
|
|
198
|
+
tool_modules = [
|
|
199
|
+
unity_functions_tools,
|
|
200
|
+
image_tool_tools,
|
|
201
|
+
coplay_tool_tools,
|
|
202
|
+
agent_tool_tools,
|
|
203
|
+
package_tool_tools,
|
|
204
|
+
input_action_tool_tools,
|
|
205
|
+
ui_functions_tools,
|
|
206
|
+
snapping_functions_tools,
|
|
207
|
+
scene_view_functions_tools,
|
|
208
|
+
profiler_functions_tools,
|
|
209
|
+
screenshot_tool_tools,
|
|
210
|
+
]
|
|
211
|
+
|
|
212
|
+
total_tools = 0
|
|
213
|
+
for module in tool_modules:
|
|
214
|
+
module.register_tools(mcp, unity_client)
|
|
215
|
+
# Count tools by checking for functions with @mcp.tool decorator
|
|
216
|
+
module_tools = [
|
|
217
|
+
name
|
|
218
|
+
for name in dir(module)
|
|
219
|
+
if not name.startswith("_") and name != "register_tools"
|
|
220
|
+
]
|
|
221
|
+
total_tools += len(module_tools)
|
|
222
|
+
logger.info(f"Registered tools from {module.__name__}")
|
|
223
|
+
|
|
224
|
+
logger.info(f"Total generated tools registered: {total_tools}")
|
|
225
|
+
logger.info("Coplay MCP Server initialized successfully")
|
|
226
|
+
|
|
227
|
+
# Start the MCP server
|
|
228
|
+
mcp.run()
|
|
229
|
+
|
|
230
|
+
except Exception as e:
|
|
231
|
+
logger.error(f"Failed to initialize MCP server: {e}")
|
|
232
|
+
raise
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
if __name__ == "__main__":
|
|
236
|
+
main()
|