Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Server/src/services/tools/batch_execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Annotated, Any

from fastmcp import Context
from mcp.types import ToolAnnotations

from services.registry import mcp_for_unity_tool
from services.tools import get_unity_instance_from_context
Expand All @@ -17,7 +18,14 @@
name="batch_execute",
description=(
"Runs a list of MCP tool calls as one batch. Use it to send a full sequence of commands, "
"inspect the results, then submit the next batch for the following step."
"inspect the results, then submit the next batch for the following step. "
"Note: Safety characteristics depend on the tools contained in the batch—batches with only "
"read-only tools (e.g., find, get_info) are safe, while batches containing create/modify/delete "
"operations may be destructive."
),
annotations=ToolAnnotations(
title="Batch Execute",
destructiveHint=True,
),
)
async def batch_execute(
Expand Down
8 changes: 7 additions & 1 deletion Server/src/services/tools/debug_request_context.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
from typing import Any

from fastmcp import Context
from mcp.types import ToolAnnotations

from services.registry import mcp_for_unity_tool
from transport.unity_instance_middleware import get_unity_instance_middleware
from transport.plugin_hub import PluginHub


@mcp_for_unity_tool(
description="Return the current FastMCP request context details (client_id, session_id, and meta dump)."
description="Return the current FastMCP request context details (client_id, session_id, and meta dump).",
annotations=ToolAnnotations(
title="Debug Request Context",
readOnlyHint=True,
),
)
def debug_request_context(ctx: Context) -> dict[str, Any]:
# Check request_context properties
Expand Down
5 changes: 5 additions & 0 deletions Server/src/services/tools/execute_custom_tool.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from fastmcp import Context
from mcp.types import ToolAnnotations
from models.models import MCPResponse

from services.custom_tool_service import (
Expand All @@ -12,6 +13,10 @@
@mcp_for_unity_tool(
name="execute_custom_tool",
description="Execute a project-scoped custom tool registered by Unity.",
annotations=ToolAnnotations(
title="Execute Custom Tool",
destructiveHint=True,
),
)
async def execute_custom_tool(ctx: Context, tool_name: str, parameters: dict | None = None) -> MCPResponse:
unity_instance = get_unity_instance_from_context(ctx)
Expand Down
7 changes: 6 additions & 1 deletion Server/src/services/tools/execute_menu_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Annotated, Any

from fastmcp import Context
from mcp.types import ToolAnnotations

from models import MCPResponse
from services.registry import mcp_for_unity_tool
Expand All @@ -13,7 +14,11 @@


@mcp_for_unity_tool(
description="Execute a Unity menu item by path."
description="Execute a Unity menu item by path.",
annotations=ToolAnnotations(
title="Execute Menu Item",
destructiveHint=True,
),
)
async def execute_menu_item(
ctx: Context,
Expand Down
9 changes: 8 additions & 1 deletion Server/src/services/tools/find_in_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from urllib.parse import unquote, urlparse

from fastmcp import Context
from mcp.types import ToolAnnotations

from services.registry import mcp_for_unity_tool
from services.tools import get_unity_instance_from_context
Expand Down Expand Up @@ -64,7 +65,13 @@ def _split_uri(uri: str) -> tuple[str, str]:
return name, directory


@mcp_for_unity_tool(description="Searches a file with a regex pattern and returns line numbers and excerpts.")
@mcp_for_unity_tool(
description="Searches a file with a regex pattern and returns line numbers and excerpts.",
annotations=ToolAnnotations(
title="Find in File",
readOnlyHint=True,
),
)
async def find_in_file(
ctx: Context,
uri: Annotated[str, "The resource URI to search under Assets/ or file path form supported by read_resource"],
Expand Down
8 changes: 7 additions & 1 deletion Server/src/services/tools/manage_asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from typing import Annotated, Any, Literal

from fastmcp import Context
from mcp.types import ToolAnnotations

from services.registry import mcp_for_unity_tool
from services.tools import get_unity_instance_from_context
from services.tools.utils import parse_json_payload
Expand All @@ -15,7 +17,11 @@


@mcp_for_unity_tool(
description="Performs asset operations (import, create, modify, delete, etc.) in Unity."
description="Performs asset operations (import, create, modify, delete, etc.) in Unity. Read-only actions: search, get_info, get_components. Destructive actions: import, create, modify, delete, duplicate, move, rename, create_folder.",
annotations=ToolAnnotations(
title="Manage Asset",
destructiveHint=True,
),
)
async def manage_asset(
ctx: Context,
Expand Down
8 changes: 7 additions & 1 deletion Server/src/services/tools/manage_editor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from typing import Annotated, Any, Literal

from fastmcp import Context
from mcp.types import ToolAnnotations

from services.registry import mcp_for_unity_tool
from core.telemetry import is_telemetry_enabled, record_tool_usage
from services.tools import get_unity_instance_from_context
Expand All @@ -10,7 +12,11 @@


@mcp_for_unity_tool(
description="Controls and queries the Unity editor's state and settings. Tip: pass booleans as true/false; if your client only sends strings, 'true'/'false' are accepted."
description="Controls and queries the Unity editor's state and settings. Tip: pass booleans as true/false; if your client only sends strings, 'true'/'false' are accepted. Read-only actions: telemetry_status, telemetry_ping. Destructive actions: play, pause, stop, set_active_tool, add_tag, remove_tag, add_layer, remove_layer.",
annotations=ToolAnnotations(
title="Manage Editor",
destructiveHint=True,
),
)
async def manage_editor(
ctx: Context,
Expand Down
8 changes: 7 additions & 1 deletion Server/src/services/tools/manage_gameobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from typing import Annotated, Any, Literal, Union

from fastmcp import Context
from mcp.types import ToolAnnotations

from services.registry import mcp_for_unity_tool
from services.tools import get_unity_instance_from_context
from transport.unity_transport import send_with_unity_instance
Expand All @@ -11,7 +13,11 @@


@mcp_for_unity_tool(
description="Performs CRUD operations on GameObjects and components."
description="Performs CRUD operations on GameObjects and components. Read-only actions: find, get_components, get_component. Destructive actions: create, modify, delete, add_component, remove_component, set_component_property, duplicate, move_relative.",
annotations=ToolAnnotations(
title="Manage GameObject",
destructiveHint=True,
),
)
async def manage_gameobject(
ctx: Context,
Expand Down
8 changes: 7 additions & 1 deletion Server/src/services/tools/manage_material.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from typing import Annotated, Any, Literal, Union

from fastmcp import Context
from mcp.types import ToolAnnotations

from services.registry import mcp_for_unity_tool
from services.tools import get_unity_instance_from_context
from services.tools.utils import parse_json_payload
Expand All @@ -13,7 +15,11 @@


@mcp_for_unity_tool(
description="Manages Unity materials (set properties, colors, shaders, etc)."
description="Manages Unity materials (set properties, colors, shaders, etc). Read-only actions: ping, get_material_info. Destructive actions: create, set_material_shader_property, set_material_color, assign_material_to_renderer, set_renderer_color.",
annotations=ToolAnnotations(
title="Manage Material",
destructiveHint=True,
),
)
async def manage_material(
ctx: Context,
Expand Down
8 changes: 7 additions & 1 deletion Server/src/services/tools/manage_prefabs.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from typing import Annotated, Any, Literal

from fastmcp import Context
from mcp.types import ToolAnnotations

from services.registry import mcp_for_unity_tool
from services.tools import get_unity_instance_from_context
from transport.unity_transport import send_with_unity_instance
Expand All @@ -9,7 +11,11 @@


@mcp_for_unity_tool(
description="Performs prefab operations (open_stage, close_stage, save_open_stage, create_from_gameobject)."
description="Performs prefab operations (open_stage, close_stage, save_open_stage, create_from_gameobject).",
annotations=ToolAnnotations(
title="Manage Prefabs",
destructiveHint=True,
),
)
async def manage_prefabs(
ctx: Context,
Expand Down
8 changes: 7 additions & 1 deletion Server/src/services/tools/manage_scene.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
from typing import Annotated, Literal, Any

from fastmcp import Context
from mcp.types import ToolAnnotations

from services.registry import mcp_for_unity_tool
from services.tools import get_unity_instance_from_context
from transport.unity_transport import send_with_unity_instance
from transport.legacy.unity_connection import async_send_command_with_retry


@mcp_for_unity_tool(
description="Performs CRUD operations on Unity scenes."
description="Performs CRUD operations on Unity scenes. Read-only actions: get_hierarchy, get_active, get_build_settings, screenshot. Destructive actions: create, load, save.",
annotations=ToolAnnotations(
title="Manage Scene",
destructiveHint=True,
),
)
async def manage_scene(
ctx: Context,
Expand Down
65 changes: 54 additions & 11 deletions Server/src/services/tools/manage_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from urllib.parse import urlparse, unquote

from fastmcp import FastMCP, Context
from mcp.types import ToolAnnotations

from services.registry import mcp_for_unity_tool
from services.tools import get_unity_instance_from_context
Expand Down Expand Up @@ -63,8 +64,9 @@ def _split_uri(uri: str) -> tuple[str, str]:
return name, directory


@mcp_for_unity_tool(description=(
"""Apply small text edits to a C# script identified by URI.
@mcp_for_unity_tool(
description=(
"""Apply small text edits to a C# script identified by URI.
IMPORTANT: This tool replaces EXACT character positions. Always verify content at target lines/columns BEFORE editing!
RECOMMENDED WORKFLOW:
1. First call resources/read with start_line/line_count to verify exact content
Expand All @@ -76,7 +78,12 @@ def _split_uri(uri: str) -> tuple[str, str]:
- For pattern-based replacements, consider anchor operations in script_apply_edits
- Lines, columns are 1-indexed
- Tabs count as 1 column"""
))
),
annotations=ToolAnnotations(
title="Apply Text Edits",
destructiveHint=True,
),
)
async def apply_text_edits(
ctx: Context,
uri: Annotated[str, "URI of the script to edit under Assets/ directory, unity://path/Assets/... or file://... or Assets/..."],
Expand Down Expand Up @@ -367,7 +374,13 @@ async def _flip_async():
return {"success": False, "message": str(resp)}


@mcp_for_unity_tool(description=("Create a new C# script at the given project path."))
@mcp_for_unity_tool(
description="Create a new C# script at the given project path.",
annotations=ToolAnnotations(
title="Create Script",
destructiveHint=True,
),
)
async def create_script(
ctx: Context,
path: Annotated[str, "Path under Assets/ to create the script at, e.g., 'Assets/Scripts/My.cs'"],
Expand Down Expand Up @@ -412,7 +425,13 @@ async def create_script(
return resp if isinstance(resp, dict) else {"success": False, "message": str(resp)}


@mcp_for_unity_tool(description=("Delete a C# script by URI or Assets-relative path."))
@mcp_for_unity_tool(
description="Delete a C# script by URI or Assets-relative path.",
annotations=ToolAnnotations(
title="Delete Script",
destructiveHint=True,
),
)
async def delete_script(
ctx: Context,
uri: Annotated[str, "URI of the script to delete under Assets/ directory, unity://path/Assets/... or file://... or Assets/..."],
Expand All @@ -434,7 +453,13 @@ async def delete_script(
return resp if isinstance(resp, dict) else {"success": False, "message": str(resp)}


@mcp_for_unity_tool(description=("Validate a C# script and return diagnostics."))
@mcp_for_unity_tool(
description="Validate a C# script and return diagnostics.",
annotations=ToolAnnotations(
title="Validate Script",
readOnlyHint=True,
),
)
async def validate_script(
ctx: Context,
uri: Annotated[str, "URI of the script to validate under Assets/ directory, unity://path/Assets/... or file://... or Assets/..."],
Expand Down Expand Up @@ -475,7 +500,13 @@ async def validate_script(
return resp if isinstance(resp, dict) else {"success": False, "message": str(resp)}


@mcp_for_unity_tool(description=("Compatibility router for legacy script operations. Prefer apply_text_edits (ranges) or script_apply_edits (structured) for edits."))
@mcp_for_unity_tool(
description="Compatibility router for legacy script operations. Prefer apply_text_edits (ranges) or script_apply_edits (structured) for edits. Read-only action: read. Destructive actions: create, delete.",
annotations=ToolAnnotations(
title="Manage Script",
destructiveHint=True,
),
)
async def manage_script(
ctx: Context,
action: Annotated[Literal['create', 'read', 'delete'], "Perform CRUD operations on C# scripts."],
Expand Down Expand Up @@ -543,14 +574,20 @@ async def manage_script(
}


@mcp_for_unity_tool(description=(
"""Get manage_script capabilities (supported ops, limits, and guards).
@mcp_for_unity_tool(
description=(
"""Get manage_script capabilities (supported ops, limits, and guards).
Returns:
- ops: list of supported structured ops
- text_ops: list of supported text ops
- max_edit_payload_bytes: server edit payload cap
- guards: header/using guard enabled flag"""
))
),
annotations=ToolAnnotations(
title="Manage Script Capabilities",
readOnlyHint=True,
),
)
async def manage_script_capabilities(ctx: Context) -> dict[str, Any]:
await ctx.info("Processing manage_script_capabilities")
try:
Expand All @@ -575,7 +612,13 @@ async def manage_script_capabilities(ctx: Context) -> dict[str, Any]:
return {"success": False, "error": f"capabilities error: {e}"}


@mcp_for_unity_tool(description="Get SHA256 and basic metadata for a Unity C# script without returning file contents")
@mcp_for_unity_tool(
description="Get SHA256 and basic metadata for a Unity C# script without returning file contents",
annotations=ToolAnnotations(
title="Get SHA",
readOnlyHint=True,
),
)
async def get_sha(
ctx: Context,
uri: Annotated[str, "URI of the script to edit under Assets/ directory, unity://path/Assets/... or file://... or Assets/..."],
Expand Down
8 changes: 7 additions & 1 deletion Server/src/services/tools/manage_shader.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@
from typing import Annotated, Any, Literal

from fastmcp import Context
from mcp.types import ToolAnnotations

from services.registry import mcp_for_unity_tool
from services.tools import get_unity_instance_from_context
from transport.unity_transport import send_with_unity_instance
from transport.legacy.unity_connection import async_send_command_with_retry


@mcp_for_unity_tool(
description="Manages shader scripts in Unity (create, read, update, delete)."
description="Manages shader scripts in Unity (create, read, update, delete). Read-only action: read. Destructive actions: create, update, delete.",
annotations=ToolAnnotations(
title="Manage Shader",
destructiveHint=True,
),
)
async def manage_shader(
ctx: Context,
Expand Down
Loading