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,347 @@
|
|
|
1
|
+
"""Generated MCP tools from agent_tool_schema.json"""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Annotated, Optional, Any, Dict, Literal
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
from fastmcp import FastMCP
|
|
7
|
+
from ..unity_client import UnityRpcClient
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
# Global references to be set by register_tools
|
|
12
|
+
_mcp: Optional[FastMCP] = None
|
|
13
|
+
_unity_client: Optional[UnityRpcClient] = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def get_unity_logs(
|
|
17
|
+
skip_newest_n_logs: Annotated[
|
|
18
|
+
int | None,
|
|
19
|
+
Field(
|
|
20
|
+
description="""Number of the *most recent* log entries to skip before returning results. Default is 0 (start with the newest)."""
|
|
21
|
+
),
|
|
22
|
+
] = None,
|
|
23
|
+
limit: Annotated[
|
|
24
|
+
int | None,
|
|
25
|
+
Field(
|
|
26
|
+
description="""The maximum number of log entries to return after applying the offset. Default is 100."""
|
|
27
|
+
),
|
|
28
|
+
] = None,
|
|
29
|
+
show_logs: Annotated[
|
|
30
|
+
bool | None,
|
|
31
|
+
Field(
|
|
32
|
+
description="""Include INFO level logs. Defaults to true if not specified."""
|
|
33
|
+
),
|
|
34
|
+
] = None,
|
|
35
|
+
show_warnings: Annotated[
|
|
36
|
+
bool | None,
|
|
37
|
+
Field(
|
|
38
|
+
description="""Include WARNING level logs. Defaults to true if not specified."""
|
|
39
|
+
),
|
|
40
|
+
] = None,
|
|
41
|
+
show_errors: Annotated[
|
|
42
|
+
bool | None,
|
|
43
|
+
Field(
|
|
44
|
+
description="""Include ERROR and EXCEPTION level logs. Defaults to true if not specified."""
|
|
45
|
+
),
|
|
46
|
+
] = None,
|
|
47
|
+
show_stack_traces: Annotated[
|
|
48
|
+
bool | None,
|
|
49
|
+
Field(
|
|
50
|
+
description="""Include stack traces. Defaults to true if not specified."""
|
|
51
|
+
),
|
|
52
|
+
] = None,
|
|
53
|
+
search_term: Annotated[
|
|
54
|
+
str | None,
|
|
55
|
+
Field(
|
|
56
|
+
description="""Only include logs containing this text (case-insensitive). Defaults to empty (no search filter)."""
|
|
57
|
+
),
|
|
58
|
+
] = None,
|
|
59
|
+
) -> Any:
|
|
60
|
+
"""Get logs from the Unity Editor console buffer, ordered chronologically (oldest first). Allows filtering by type and content, and pagination based on the most recent logs."""
|
|
61
|
+
try:
|
|
62
|
+
logger.debug(f"Executing get_unity_logs with parameters: {locals()}")
|
|
63
|
+
|
|
64
|
+
# Prepare parameters for Unity RPC call
|
|
65
|
+
params = {}
|
|
66
|
+
if skip_newest_n_logs is not None:
|
|
67
|
+
params['skip_newest_n_logs'] = str(skip_newest_n_logs)
|
|
68
|
+
if limit is not None:
|
|
69
|
+
params['limit'] = str(limit)
|
|
70
|
+
if show_logs is not None:
|
|
71
|
+
params['show_logs'] = str(show_logs)
|
|
72
|
+
if show_warnings is not None:
|
|
73
|
+
params['show_warnings'] = str(show_warnings)
|
|
74
|
+
if show_errors is not None:
|
|
75
|
+
params['show_errors'] = str(show_errors)
|
|
76
|
+
if show_stack_traces is not None:
|
|
77
|
+
params['show_stack_traces'] = str(show_stack_traces)
|
|
78
|
+
if search_term is not None:
|
|
79
|
+
params['search_term'] = str(search_term)
|
|
80
|
+
|
|
81
|
+
# Execute Unity RPC call
|
|
82
|
+
result = await _unity_client.execute_request('get_unity_logs', params)
|
|
83
|
+
logger.debug(f"get_unity_logs completed successfully")
|
|
84
|
+
return result
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.error(f"Failed to execute get_unity_logs: {e}")
|
|
88
|
+
raise RuntimeError(f"Tool execution failed for get_unity_logs: {e}")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
async def get_unity_editor_state(
|
|
92
|
+
) -> Any:
|
|
93
|
+
"""Retrieve the current state of the Unity Editor, excluding scene hierarchy."""
|
|
94
|
+
try:
|
|
95
|
+
logger.debug(f"Executing get_unity_editor_state with parameters: {locals()}")
|
|
96
|
+
|
|
97
|
+
# Prepare parameters for Unity RPC call
|
|
98
|
+
params = {}
|
|
99
|
+
|
|
100
|
+
# Execute Unity RPC call
|
|
101
|
+
result = await _unity_client.execute_request('get_unity_editor_state', params)
|
|
102
|
+
logger.debug(f"get_unity_editor_state completed successfully")
|
|
103
|
+
return result
|
|
104
|
+
|
|
105
|
+
except Exception as e:
|
|
106
|
+
logger.error(f"Failed to execute get_unity_editor_state: {e}")
|
|
107
|
+
raise RuntimeError(f"Tool execution failed for get_unity_editor_state: {e}")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
async def list_game_objects_in_hierarchy(
|
|
111
|
+
referenceObjectPath: Annotated[
|
|
112
|
+
str | None,
|
|
113
|
+
Field(
|
|
114
|
+
description="""Optional path of a reference game object to search relative to (e.g. 'Root/Parent/Child'). If specified, the search will be performed relative to this object instead of the entire hierarchy. This is not the file path."""
|
|
115
|
+
),
|
|
116
|
+
] = None,
|
|
117
|
+
nameFilter: Annotated[
|
|
118
|
+
str | None,
|
|
119
|
+
Field(
|
|
120
|
+
description="""Optional filter to match game objects by name (case-insensitive substring match)"""
|
|
121
|
+
),
|
|
122
|
+
] = None,
|
|
123
|
+
tagFilter: Annotated[
|
|
124
|
+
str | None,
|
|
125
|
+
Field(
|
|
126
|
+
description="""Optional filter to match game objects by tag"""
|
|
127
|
+
),
|
|
128
|
+
] = None,
|
|
129
|
+
componentFilter: Annotated[
|
|
130
|
+
str | None,
|
|
131
|
+
Field(
|
|
132
|
+
description="""Optional filter to match game objects by component type (e.g. 'Transform', 'MeshRenderer')"""
|
|
133
|
+
),
|
|
134
|
+
] = None,
|
|
135
|
+
includeInactive: Annotated[
|
|
136
|
+
bool | None,
|
|
137
|
+
Field(
|
|
138
|
+
description="""Optional flag to include inactive game objects in the results. Defaults to false if not specified."""
|
|
139
|
+
),
|
|
140
|
+
] = None,
|
|
141
|
+
limit: Annotated[
|
|
142
|
+
int | None,
|
|
143
|
+
Field(
|
|
144
|
+
description="""Optional maximum number of objects to return. Defaults to 200 if not specified."""
|
|
145
|
+
),
|
|
146
|
+
] = None,
|
|
147
|
+
skip: Annotated[
|
|
148
|
+
int | None,
|
|
149
|
+
Field(
|
|
150
|
+
description="""Optional number of objects to skip (for pagination). Defaults to 0 if not specified."""
|
|
151
|
+
),
|
|
152
|
+
] = None,
|
|
153
|
+
onlyPaths: Annotated[
|
|
154
|
+
bool | None,
|
|
155
|
+
Field(
|
|
156
|
+
description="""Optional flag to return only the paths of the game objects. Defaults to true if not specified. If false, the components and children will also be returned."""
|
|
157
|
+
),
|
|
158
|
+
] = None,
|
|
159
|
+
) -> Any:
|
|
160
|
+
"""List game objects in the hierarchy with optional filtering capabilities. Uses breadth-first traversal to prioritize objects closer to the root. Results are truncated if they exceed the limit, with a message indicating the truncation. Only works for the active scene or prefab."""
|
|
161
|
+
try:
|
|
162
|
+
logger.debug(f"Executing list_game_objects_in_hierarchy with parameters: {locals()}")
|
|
163
|
+
|
|
164
|
+
# Prepare parameters for Unity RPC call
|
|
165
|
+
params = {}
|
|
166
|
+
if referenceObjectPath is not None:
|
|
167
|
+
params['referenceObjectPath'] = str(referenceObjectPath)
|
|
168
|
+
if nameFilter is not None:
|
|
169
|
+
params['nameFilter'] = str(nameFilter)
|
|
170
|
+
if tagFilter is not None:
|
|
171
|
+
params['tagFilter'] = str(tagFilter)
|
|
172
|
+
if componentFilter is not None:
|
|
173
|
+
params['componentFilter'] = str(componentFilter)
|
|
174
|
+
if includeInactive is not None:
|
|
175
|
+
params['includeInactive'] = str(includeInactive)
|
|
176
|
+
if limit is not None:
|
|
177
|
+
params['limit'] = str(limit)
|
|
178
|
+
if skip is not None:
|
|
179
|
+
params['skip'] = str(skip)
|
|
180
|
+
if onlyPaths is not None:
|
|
181
|
+
params['onlyPaths'] = str(onlyPaths)
|
|
182
|
+
|
|
183
|
+
# Execute Unity RPC call
|
|
184
|
+
result = await _unity_client.execute_request('list_game_objects_in_hierarchy', params)
|
|
185
|
+
logger.debug(f"list_game_objects_in_hierarchy completed successfully")
|
|
186
|
+
return result
|
|
187
|
+
|
|
188
|
+
except Exception as e:
|
|
189
|
+
logger.error(f"Failed to execute list_game_objects_in_hierarchy: {e}")
|
|
190
|
+
raise RuntimeError(f"Tool execution failed for list_game_objects_in_hierarchy: {e}")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
async def execute_script(
|
|
194
|
+
filePath: Annotated[
|
|
195
|
+
str,
|
|
196
|
+
Field(
|
|
197
|
+
description="""The path to the C# file to execute. Can be absolute or relative to the Unity project root."""
|
|
198
|
+
),
|
|
199
|
+
],
|
|
200
|
+
methodName: Annotated[
|
|
201
|
+
str | None,
|
|
202
|
+
Field(
|
|
203
|
+
description="""Optional name of the public static method to execute as the entry point. Defaults to 'Execute' if not specified."""
|
|
204
|
+
),
|
|
205
|
+
] = None,
|
|
206
|
+
arguments: Annotated[
|
|
207
|
+
str | None,
|
|
208
|
+
Field(
|
|
209
|
+
description="""Optional JSON string containing arguments to pass to the method. Must be a valid JSON object that can be parsed into a JObject."""
|
|
210
|
+
),
|
|
211
|
+
] = None,
|
|
212
|
+
) -> Any:
|
|
213
|
+
"""Executes arbitrary C# code within the Unity Editor environment. The provided code must define a class that includes a public static method, which serves as the entry point for execution. The code can access any classes and APIs available in Unity's editor mode."""
|
|
214
|
+
try:
|
|
215
|
+
logger.debug(f"Executing execute_script with parameters: {locals()}")
|
|
216
|
+
|
|
217
|
+
# Prepare parameters for Unity RPC call
|
|
218
|
+
params = {}
|
|
219
|
+
if filePath is not None:
|
|
220
|
+
params['filePath'] = str(filePath)
|
|
221
|
+
if methodName is not None:
|
|
222
|
+
params['methodName'] = str(methodName)
|
|
223
|
+
if arguments is not None:
|
|
224
|
+
params['arguments'] = str(arguments)
|
|
225
|
+
|
|
226
|
+
# Execute Unity RPC call
|
|
227
|
+
result = await _unity_client.execute_request('execute_script', params)
|
|
228
|
+
logger.debug(f"execute_script completed successfully")
|
|
229
|
+
return result
|
|
230
|
+
|
|
231
|
+
except Exception as e:
|
|
232
|
+
logger.error(f"Failed to execute execute_script: {e}")
|
|
233
|
+
raise RuntimeError(f"Tool execution failed for execute_script: {e}")
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
async def invoke_mcp_tool(
|
|
237
|
+
mcp_server: Annotated[
|
|
238
|
+
str,
|
|
239
|
+
Field(
|
|
240
|
+
description="""The name of the MCP server to execute the command on."""
|
|
241
|
+
),
|
|
242
|
+
],
|
|
243
|
+
tool: Annotated[
|
|
244
|
+
str,
|
|
245
|
+
Field(
|
|
246
|
+
description="""The name of the tool to invoke."""
|
|
247
|
+
),
|
|
248
|
+
],
|
|
249
|
+
args: Annotated[
|
|
250
|
+
str,
|
|
251
|
+
Field(
|
|
252
|
+
description="""The arguments to pass to the tool as a JSON encoded string. The json object must match the args schema of the tool."""
|
|
253
|
+
),
|
|
254
|
+
],
|
|
255
|
+
) -> Any:
|
|
256
|
+
"""Invoke a tool on an MCP server. The tool must be valid for the server."""
|
|
257
|
+
try:
|
|
258
|
+
logger.debug(f"Executing invoke_mcp_tool with parameters: {locals()}")
|
|
259
|
+
|
|
260
|
+
# Prepare parameters for Unity RPC call
|
|
261
|
+
params = {}
|
|
262
|
+
if mcp_server is not None:
|
|
263
|
+
params['mcp_server'] = str(mcp_server)
|
|
264
|
+
if tool is not None:
|
|
265
|
+
params['tool'] = str(tool)
|
|
266
|
+
if args is not None:
|
|
267
|
+
params['args'] = str(args)
|
|
268
|
+
|
|
269
|
+
# Execute Unity RPC call
|
|
270
|
+
result = await _unity_client.execute_request('invoke_mcp_tool', params)
|
|
271
|
+
logger.debug(f"invoke_mcp_tool completed successfully")
|
|
272
|
+
return result
|
|
273
|
+
|
|
274
|
+
except Exception as e:
|
|
275
|
+
logger.error(f"Failed to execute invoke_mcp_tool: {e}")
|
|
276
|
+
raise RuntimeError(f"Tool execution failed for invoke_mcp_tool: {e}")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
async def get_game_object_info(
|
|
280
|
+
gameObjectPath: Annotated[
|
|
281
|
+
str,
|
|
282
|
+
Field(
|
|
283
|
+
description="""Path of the game object in the hierarchy (e.g. 'Root/Parent/Child')."""
|
|
284
|
+
),
|
|
285
|
+
],
|
|
286
|
+
componentFilter: Annotated[
|
|
287
|
+
str | None,
|
|
288
|
+
Field(
|
|
289
|
+
description="""Optional filter for a specific component type in the returned information (e.g. 'Transform', 'MeshRenderer')"""
|
|
290
|
+
),
|
|
291
|
+
] = None,
|
|
292
|
+
includeInactive: Annotated[
|
|
293
|
+
bool | None,
|
|
294
|
+
Field(
|
|
295
|
+
description="""Optional flag to include inactive game objects in the results. Defaults to false if not specified."""
|
|
296
|
+
),
|
|
297
|
+
] = None,
|
|
298
|
+
prefabPath: Annotated[
|
|
299
|
+
str | None,
|
|
300
|
+
Field(
|
|
301
|
+
description="""Optional path to a prefab asset. If provided, the gameObjectPath will be resolved within this prefab. Required when seeking game object details within a prefab."""
|
|
302
|
+
),
|
|
303
|
+
] = None,
|
|
304
|
+
) -> Any:
|
|
305
|
+
"""Get information about a game object with optional filtering capabilities. Results include the AABB information of the game object. Use this tool to list all the details of a game object."""
|
|
306
|
+
try:
|
|
307
|
+
logger.debug(f"Executing get_game_object_info with parameters: {locals()}")
|
|
308
|
+
|
|
309
|
+
# Prepare parameters for Unity RPC call
|
|
310
|
+
params = {}
|
|
311
|
+
if gameObjectPath is not None:
|
|
312
|
+
params['gameObjectPath'] = str(gameObjectPath)
|
|
313
|
+
if componentFilter is not None:
|
|
314
|
+
params['componentFilter'] = str(componentFilter)
|
|
315
|
+
if includeInactive is not None:
|
|
316
|
+
params['includeInactive'] = str(includeInactive)
|
|
317
|
+
if prefabPath is not None:
|
|
318
|
+
params['prefabPath'] = str(prefabPath)
|
|
319
|
+
|
|
320
|
+
# Execute Unity RPC call
|
|
321
|
+
result = await _unity_client.execute_request('get_game_object_info', params)
|
|
322
|
+
logger.debug(f"get_game_object_info completed successfully")
|
|
323
|
+
return result
|
|
324
|
+
|
|
325
|
+
except Exception as e:
|
|
326
|
+
logger.error(f"Failed to execute get_game_object_info: {e}")
|
|
327
|
+
raise RuntimeError(f"Tool execution failed for get_game_object_info: {e}")
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def register_tools(mcp: FastMCP, unity_client: UnityRpcClient) -> None:
|
|
331
|
+
"""Register all tools from agent_tool_schema with the MCP server."""
|
|
332
|
+
global _mcp, _unity_client
|
|
333
|
+
_mcp = mcp
|
|
334
|
+
_unity_client = unity_client
|
|
335
|
+
|
|
336
|
+
# Register get_unity_logs
|
|
337
|
+
mcp.tool()(get_unity_logs)
|
|
338
|
+
# Register get_unity_editor_state
|
|
339
|
+
mcp.tool()(get_unity_editor_state)
|
|
340
|
+
# Register list_game_objects_in_hierarchy
|
|
341
|
+
mcp.tool()(list_game_objects_in_hierarchy)
|
|
342
|
+
# Register execute_script
|
|
343
|
+
mcp.tool()(execute_script)
|
|
344
|
+
# Register invoke_mcp_tool
|
|
345
|
+
mcp.tool()(invoke_mcp_tool)
|
|
346
|
+
# Register get_game_object_info
|
|
347
|
+
mcp.tool()(get_game_object_info)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Generated MCP tools from coplay_tool_schema.json"""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Annotated, Optional, Any, Dict, Literal
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
from fastmcp import FastMCP
|
|
7
|
+
from ..unity_client import UnityRpcClient
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
# Global references to be set by register_tools
|
|
12
|
+
_mcp: Optional[FastMCP] = None
|
|
13
|
+
_unity_client: Optional[UnityRpcClient] = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def generate_scene(
|
|
17
|
+
description: Annotated[
|
|
18
|
+
str,
|
|
19
|
+
Field(
|
|
20
|
+
description="""A natural language description of the scene to generate. Be specific about objects, their positions, colors, and relationships. Use the original user prompt, or an enhanced version of it."""
|
|
21
|
+
),
|
|
22
|
+
],
|
|
23
|
+
num_iterations: Annotated[
|
|
24
|
+
int | None,
|
|
25
|
+
Field(
|
|
26
|
+
description="""Optional. Controls the number of iterations of scene generation. Higher values (e.g., 5) generate larger and more detailed scenes. Default is 2."""
|
|
27
|
+
),
|
|
28
|
+
] = None,
|
|
29
|
+
) -> Any:
|
|
30
|
+
"""Generates a 3D scene in Unity based on a text description using AI. This function is useful for generating game levels, environments, and other 3D scenes."""
|
|
31
|
+
try:
|
|
32
|
+
logger.debug(f"Executing generate_scene with parameters: {locals()}")
|
|
33
|
+
|
|
34
|
+
# Prepare parameters for Unity RPC call
|
|
35
|
+
params = {}
|
|
36
|
+
if description is not None:
|
|
37
|
+
params['description'] = str(description)
|
|
38
|
+
if num_iterations is not None:
|
|
39
|
+
params['num_iterations'] = str(num_iterations)
|
|
40
|
+
|
|
41
|
+
# Execute Unity RPC call
|
|
42
|
+
result = await _unity_client.execute_request('generate_scene', params)
|
|
43
|
+
logger.debug(f"generate_scene completed successfully")
|
|
44
|
+
return result
|
|
45
|
+
|
|
46
|
+
except Exception as e:
|
|
47
|
+
logger.error(f"Failed to execute generate_scene: {e}")
|
|
48
|
+
raise RuntimeError(f"Tool execution failed for generate_scene: {e}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def register_tools(mcp: FastMCP, unity_client: UnityRpcClient) -> None:
|
|
52
|
+
"""Register all tools from coplay_tool_schema with the MCP server."""
|
|
53
|
+
global _mcp, _unity_client
|
|
54
|
+
_mcp = mcp
|
|
55
|
+
_unity_client = unity_client
|
|
56
|
+
|
|
57
|
+
# Register generate_scene
|
|
58
|
+
mcp.tool()(generate_scene)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Generated MCP tools from image_tool_schema.json"""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Annotated, Optional, Any, Dict, Literal
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
from fastmcp import FastMCP
|
|
7
|
+
from ..unity_client import UnityRpcClient
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
# Global references to be set by register_tools
|
|
12
|
+
_mcp: Optional[FastMCP] = None
|
|
13
|
+
_unity_client: Optional[UnityRpcClient] = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def image_functions(
|
|
17
|
+
is_edit: Annotated[
|
|
18
|
+
bool,
|
|
19
|
+
Field(
|
|
20
|
+
description="""True for editing existing images (requires image_path), false for generating new images from scratch."""
|
|
21
|
+
),
|
|
22
|
+
],
|
|
23
|
+
prompt: Annotated[
|
|
24
|
+
str,
|
|
25
|
+
Field(
|
|
26
|
+
description="""Text prompt describing the image to generate or the edits to apply to existing images. Be specific and detailed."""
|
|
27
|
+
),
|
|
28
|
+
],
|
|
29
|
+
save_path: Annotated[
|
|
30
|
+
str | None,
|
|
31
|
+
Field(
|
|
32
|
+
description="""Optional file path to save the generated image. Should be relative to the Unity project Assets folder (e.g., 'Images/generated_image.png'). Defaults to Assets/ folder."""
|
|
33
|
+
),
|
|
34
|
+
],
|
|
35
|
+
image_path: Annotated[
|
|
36
|
+
str | None,
|
|
37
|
+
Field(
|
|
38
|
+
description="""Path to the image file to edit. Required when is_edit is true. Should be a file path to an existing image file in the project."""
|
|
39
|
+
),
|
|
40
|
+
] = None,
|
|
41
|
+
provider: Annotated[
|
|
42
|
+
Literal['gpt_image_1', 'gemini'] | None,
|
|
43
|
+
Field(
|
|
44
|
+
description="""AI model provider for image generation. 'gpt_image_1' provides streaming partial previews, 'gemini' provides high-quality final results with no preview. Defaults to gpt_image_1."""
|
|
45
|
+
),
|
|
46
|
+
] = None,
|
|
47
|
+
size: Annotated[
|
|
48
|
+
Literal['1K', '2K', '4K', 'auto'] | None,
|
|
49
|
+
Field(
|
|
50
|
+
description="""Target image resolution. Default is 'auto'."""
|
|
51
|
+
),
|
|
52
|
+
] = None,
|
|
53
|
+
quality: Annotated[
|
|
54
|
+
Literal['low', 'medium', 'high', 'auto'] | None,
|
|
55
|
+
Field(
|
|
56
|
+
description="""Rendering quality. Higher quality takes longer but produces better results. 'auto' lets the model choose. Defaults to auto."""
|
|
57
|
+
),
|
|
58
|
+
] = None,
|
|
59
|
+
format: Annotated[
|
|
60
|
+
Literal['png', 'jpeg', 'webp'] | None,
|
|
61
|
+
Field(
|
|
62
|
+
description="""Output image format. PNG supports transparency, JPEG is faster, WebP offers good compression. Defaults to png."""
|
|
63
|
+
),
|
|
64
|
+
] = None,
|
|
65
|
+
compression: Annotated[
|
|
66
|
+
int | None,
|
|
67
|
+
Field(
|
|
68
|
+
description="""Compression level for JPEG and WebP formats (0-100%). Higher values mean less compression and larger file sizes. Defaults to 100."""
|
|
69
|
+
),
|
|
70
|
+
] = None,
|
|
71
|
+
transparent_background: Annotated[
|
|
72
|
+
bool | None,
|
|
73
|
+
Field(
|
|
74
|
+
description="""Enable transparent background. Only supported with PNG and WebP formats. Works best with medium or high quality. Defaults to false."""
|
|
75
|
+
),
|
|
76
|
+
] = None,
|
|
77
|
+
object_size: Annotated[
|
|
78
|
+
str | None,
|
|
79
|
+
Field(
|
|
80
|
+
description="""Optional size of the generated object (opaque area of the image), specified in pixels as a comma-separated width,height vector (e.g., '512,512'). Defaults to null (no object size)."""
|
|
81
|
+
),
|
|
82
|
+
] = None,
|
|
83
|
+
scale_mode: Annotated[
|
|
84
|
+
Literal['fit_canvas', 'trim', 'crop'] | None,
|
|
85
|
+
Field(
|
|
86
|
+
description="""How to scale the image to object_size. 'fit_canvas' (default) scales and centers the image on a transparent canvas of exact object_size. 'trim' scales the image to fit within object_size bounds without adding padding. 'crop' scales the image to fully cover object_size and crops overflow to exact dimensions. Only applies when object_size is specified. Defaults to fit_canvas."""
|
|
87
|
+
),
|
|
88
|
+
] = None,
|
|
89
|
+
aspect: Annotated[
|
|
90
|
+
Literal['1:1', '2:3', '3:2', '3:4', '4:3', '4:5', '5:4', '9:16', '16:9', '21:9', 'auto'] | None,
|
|
91
|
+
Field(
|
|
92
|
+
description="""Aspect ratio for the generated image. Options include common ratios like 1:1 (square), 16:9 (widescreen), 9:16 (portrait), etc. 'auto' lets the model choose. Defaults to auto."""
|
|
93
|
+
),
|
|
94
|
+
] = None,
|
|
95
|
+
) -> Any:
|
|
96
|
+
"""Generate new images from text prompts or edit existing images."""
|
|
97
|
+
try:
|
|
98
|
+
logger.debug(f"Executing image_functions with parameters: {locals()}")
|
|
99
|
+
|
|
100
|
+
# Prepare parameters for Unity RPC call
|
|
101
|
+
params = {}
|
|
102
|
+
if is_edit is not None:
|
|
103
|
+
params['is_edit'] = str(is_edit)
|
|
104
|
+
if image_path is not None:
|
|
105
|
+
params['image_path'] = str(image_path)
|
|
106
|
+
if prompt is not None:
|
|
107
|
+
params['prompt'] = str(prompt)
|
|
108
|
+
if provider is not None:
|
|
109
|
+
params['provider'] = str(provider)
|
|
110
|
+
if size is not None:
|
|
111
|
+
params['size'] = str(size)
|
|
112
|
+
if quality is not None:
|
|
113
|
+
params['quality'] = str(quality)
|
|
114
|
+
if format is not None:
|
|
115
|
+
params['format'] = str(format)
|
|
116
|
+
if compression is not None:
|
|
117
|
+
params['compression'] = str(compression)
|
|
118
|
+
if transparent_background is not None:
|
|
119
|
+
params['transparent_background'] = str(transparent_background)
|
|
120
|
+
if save_path is not None:
|
|
121
|
+
params['save_path'] = str(save_path)
|
|
122
|
+
if object_size is not None:
|
|
123
|
+
params['object_size'] = str(object_size)
|
|
124
|
+
if scale_mode is not None:
|
|
125
|
+
params['scale_mode'] = str(scale_mode)
|
|
126
|
+
if aspect is not None:
|
|
127
|
+
params['aspect'] = str(aspect)
|
|
128
|
+
|
|
129
|
+
# Execute Unity RPC call
|
|
130
|
+
result = await _unity_client.execute_request('image_functions', params)
|
|
131
|
+
logger.debug(f"image_functions completed successfully")
|
|
132
|
+
return result
|
|
133
|
+
|
|
134
|
+
except Exception as e:
|
|
135
|
+
logger.error(f"Failed to execute image_functions: {e}")
|
|
136
|
+
raise RuntimeError(f"Tool execution failed for image_functions: {e}")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def register_tools(mcp: FastMCP, unity_client: UnityRpcClient) -> None:
|
|
140
|
+
"""Register all tools from image_tool_schema with the MCP server."""
|
|
141
|
+
global _mcp, _unity_client
|
|
142
|
+
_mcp = mcp
|
|
143
|
+
_unity_client = unity_client
|
|
144
|
+
|
|
145
|
+
# Register image_functions
|
|
146
|
+
mcp.tool()(image_functions)
|