unity-mcp/MCPForUnity/Editor/Tools/CommandRegistry.cs

409 lines
14 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
using System.Threading.Tasks;
using MCPForUnity.Editor.Helpers;
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
using MCPForUnity.Editor.Resources;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace MCPForUnity.Editor.Tools
{
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
/// <summary>
/// Holds information about a registered command handler.
/// </summary>
class HandlerInfo
{
public string CommandName { get; }
public Func<JObject, object> SyncHandler { get; }
public Func<JObject, Task<object>> AsyncHandler { get; }
public bool IsAsync => AsyncHandler != null;
public HandlerInfo(string commandName, Func<JObject, object> syncHandler, Func<JObject, Task<object>> asyncHandler)
{
CommandName = commandName;
SyncHandler = syncHandler;
AsyncHandler = asyncHandler;
}
}
/// <summary>
/// Registry for all MCP command handlers via reflection.
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
/// Handles both MCP tools and resources.
/// </summary>
public static class CommandRegistry
{
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
private static readonly Dictionary<string, HandlerInfo> _handlers = new();
private static bool _initialized = false;
/// <summary>
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
/// Initialize and auto-discover all tools and resources marked with
/// [McpForUnityTool] or [McpForUnityResource]
/// </summary>
public static void Initialize()
{
if (_initialized) return;
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
AutoDiscoverCommands();
_initialized = true;
}
/// <summary>
/// Convert PascalCase or camelCase to snake_case
/// </summary>
private static string ToSnakeCase(string name)
{
if (string.IsNullOrEmpty(name)) return name;
// Insert underscore before uppercase letters (except first)
var s1 = Regex.Replace(name, "(.)([A-Z][a-z]+)", "$1_$2");
var s2 = Regex.Replace(s1, "([a-z0-9])([A-Z])", "$1_$2");
return s2.ToLower();
}
/// <summary>
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
/// Auto-discover all types with [McpForUnityTool] or [McpForUnityResource] attributes
/// </summary>
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
private static void AutoDiscoverCommands()
{
try
{
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
var allTypes = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !a.IsDynamic)
.SelectMany(a =>
{
try { return a.GetTypes(); }
catch { return new Type[0]; }
})
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
.ToList();
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
// Discover tools
var toolTypes = allTypes.Where(t => t.GetCustomAttribute<McpForUnityToolAttribute>() != null);
int toolCount = 0;
foreach (var type in toolTypes)
{
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
if (RegisterCommandType(type, isResource: false))
toolCount++;
}
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
// Discover resources
var resourceTypes = allTypes.Where(t => t.GetCustomAttribute<McpForUnityResourceAttribute>() != null);
int resourceCount = 0;
foreach (var type in resourceTypes)
{
if (RegisterCommandType(type, isResource: true))
resourceCount++;
}
McpLog.Info($"Auto-discovered {toolCount} tools and {resourceCount} resources ({_handlers.Count} total handlers)");
}
catch (Exception ex)
{
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
McpLog.Error($"Failed to auto-discover MCP commands: {ex.Message}");
}
}
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
/// <summary>
/// Register a command type (tool or resource) with the registry.
/// Returns true if successfully registered, false otherwise.
/// </summary>
private static bool RegisterCommandType(Type type, bool isResource)
{
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
string commandName;
string typeLabel = isResource ? "resource" : "tool";
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
// Get command name from appropriate attribute
if (isResource)
{
var resourceAttr = type.GetCustomAttribute<McpForUnityResourceAttribute>();
commandName = resourceAttr.ResourceName;
}
else
{
var toolAttr = type.GetCustomAttribute<McpForUnityToolAttribute>();
commandName = toolAttr.CommandName;
}
// Auto-generate command name if not explicitly provided
if (string.IsNullOrEmpty(commandName))
{
commandName = ToSnakeCase(type.Name);
}
// Check for duplicate command names
if (_handlers.ContainsKey(commandName))
{
McpLog.Warn(
$"Duplicate command name '{commandName}' detected. " +
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
$"{typeLabel} {type.Name} will override previously registered handler."
);
}
// Find HandleCommand method
var method = type.GetMethod(
"HandleCommand",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { typeof(JObject) },
null
);
if (method == null)
{
McpLog.Warn(
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
$"MCP {typeLabel} {type.Name} is marked with [McpForUnity{(isResource ? "Resource" : "Tool")}] " +
$"but has no public static HandleCommand(JObject) method"
);
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
return false;
}
try
{
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
HandlerInfo handlerInfo;
if (typeof(Task).IsAssignableFrom(method.ReturnType))
{
var asyncHandler = CreateAsyncHandlerDelegate(method, commandName);
handlerInfo = new HandlerInfo(commandName, null, asyncHandler);
}
else
{
var handler = (Func<JObject, object>)Delegate.CreateDelegate(
typeof(Func<JObject, object>),
method
);
handlerInfo = new HandlerInfo(commandName, handler, null);
}
_handlers[commandName] = handlerInfo;
return true;
}
catch (Exception ex)
{
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
McpLog.Error($"Failed to register {typeLabel} {type.Name}: {ex.Message}");
return false;
}
}
/// <summary>
/// Get a command handler by name
/// </summary>
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
private static HandlerInfo GetHandlerInfo(string commandName)
{
if (!_handlers.TryGetValue(commandName, out var handler))
{
throw new InvalidOperationException(
$"Unknown or unsupported command type: {commandName}"
);
}
return handler;
}
Add testing and move menu items to resources (#316) * deps: add tomli>=2.3.0 dependency to UnityMcpServer package * feat: dynamically fetch package version from pyproject.toml for telemetry * Add pydantic * feat: add resource registry for MCP resource auto-discovery * feat: add telemetry decorator for tracking MCP resource usage * feat: add auto-discovery and registration system for MCP resources * feat: add resource registration to MCP server initialization * feat: add MCPResponse model class for standardized API responses * refactor: replace Debug.Log calls with McpLog wrapper for consistent logging * feat: add test discovery endpoints for Unity Test Framework integration We haven't connected them as yet, still thinking about how to do this neatly * Fix server setup * refactor: reduce log verbosity by changing individual resource/tool registration logs to debug level * chore: bump mcp[cli] dependency from 1.15.0 to 1.17.0 * refactor: remove Context parameter and add uri keyword argument in resource decorator The Context parameter doesn't work on our version of FastMCP * chore: upgrade Python base image to 3.13 and simplify Dockerfile setup * fix: apply telemetry decorator before mcp.tool to ensure proper wrapping order * fix: swap order of telemetry and resource decorators to properly wrap handlers * fix: update log prefixes for consistency in logging methods * Fix compile errors * feat: extend command registry to support both tools and resources * Run get tests as a coroutine because it doesn't return results immediately This works but it spams logs like crazy, maybe there's a better/simpler way * refactor: migrate from coroutines to async/await for test retrieval and command execution * feat: add optional error field to MCPResponse model * Increased timeout because loading tests can take some time * Make message optional so error responses that only have success and error don't cause Pydantic errors * Set max_retries to 5 This connection module needs a lookover. The retries should be an exponential backoff and we could structure why it's failing so much * Use pydantic model to structure the error output * fix: initialize data field in GetTestsResponse to avoid potential errors * Don't return path parameter * feat: add Unity test runner execution with structured results and Python bindings * refactor: simplify GetTests by removing mode filtering and related parsing logic * refactor: move test runner functionality into dedicated service interface * feat: add resource retrieval telemetry tracking with new record type and helper function * fix: convert tool functions to async and await ctx.info calls * refactor: reorganize menu item functionality into separate execute and get commands An MCP resource for retrieval, and a simple command to execute. Because it's a resource, it's easier for the user to see what's in the menu items * refactor: rename manage_menu_item to execute_menu_item and update tool examples to use async/await We'll eventually put a section for resources * Revert "fix: convert tool functions to async and await ctx.info calls" This reverts commit 012ea6b7439bd1f2593864d98d03d9d95d7bdd03. * fix: replace tomllib with tomli for Python 3.10 compatibility in telemetry module * Remove confusing comment * refactor: improve error handling and simplify test retrieval logic in GetTests commands * No cache by default * docs: remove redundant comment for HandleCommand method in ExecuteMenuItem
2025-10-13 23:16:43 +08:00
/// <summary>
/// Get a synchronous command handler by name.
/// Throws if the command is asynchronous.
/// </summary>
/// <param name="commandName"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public static Func<JObject, object> GetHandler(string commandName)
{
var handlerInfo = GetHandlerInfo(commandName);
if (handlerInfo.IsAsync)
{
throw new InvalidOperationException(
$"Command '{commandName}' is asynchronous and must be executed via ExecuteCommand"
);
}
return handlerInfo.SyncHandler;
}
/// <summary>
/// Execute a command handler, supporting both synchronous and asynchronous (coroutine) handlers.
/// If the handler returns an IEnumerator, it will be executed as a coroutine.
/// </summary>
/// <param name="commandName">The command name to execute</param>
/// <param name="params">Command parameters</param>
/// <param name="tcs">TaskCompletionSource to complete when async operation finishes</param>
/// <returns>The result for synchronous commands, or null for async commands (TCS will be completed later)</returns>
public static object ExecuteCommand(string commandName, JObject @params, TaskCompletionSource<string> tcs)
{
var handlerInfo = GetHandlerInfo(commandName);
if (handlerInfo.IsAsync)
{
ExecuteAsyncHandler(handlerInfo, @params, commandName, tcs);
return null;
}
if (handlerInfo.SyncHandler == null)
{
throw new InvalidOperationException($"Handler for '{commandName}' does not provide a synchronous implementation");
}
return handlerInfo.SyncHandler(@params);
}
/// <summary>
/// Create a delegate for an async handler method that returns Task or Task<T>.
/// The delegate will invoke the method and await its completion, returning the result.
/// </summary>
/// <param name="method"></param>
/// <param name="commandName"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
private static Func<JObject, Task<object>> CreateAsyncHandlerDelegate(MethodInfo method, string commandName)
{
return async (JObject parameters) =>
{
object rawResult;
try
{
rawResult = method.Invoke(null, new object[] { parameters });
}
catch (TargetInvocationException ex)
{
throw ex.InnerException ?? ex;
}
if (rawResult == null)
{
return null;
}
if (rawResult is not Task task)
{
throw new InvalidOperationException(
$"Async handler '{commandName}' returned an object that is not a Task"
);
}
await task.ConfigureAwait(true);
var taskType = task.GetType();
if (taskType.IsGenericType)
{
var resultProperty = taskType.GetProperty("Result");
if (resultProperty != null)
{
return resultProperty.GetValue(task);
}
}
return null;
};
}
private static void ExecuteAsyncHandler(
HandlerInfo handlerInfo,
JObject parameters,
string commandName,
TaskCompletionSource<string> tcs)
{
if (handlerInfo.AsyncHandler == null)
{
throw new InvalidOperationException($"Async handler for '{commandName}' is not configured correctly");
}
Task<object> handlerTask;
try
{
handlerTask = handlerInfo.AsyncHandler(parameters);
}
catch (Exception ex)
{
ReportAsyncFailure(commandName, tcs, ex);
return;
}
if (handlerTask == null)
{
CompleteAsyncCommand(commandName, tcs, null);
return;
}
async void AwaitHandler()
{
try
{
var finalResult = await handlerTask.ConfigureAwait(true);
CompleteAsyncCommand(commandName, tcs, finalResult);
}
catch (Exception ex)
{
ReportAsyncFailure(commandName, tcs, ex);
}
}
AwaitHandler();
}
/// <summary>
/// Complete the TaskCompletionSource for an async command with a success result.
/// </summary>
/// <param name="commandName"></param>
/// <param name="tcs"></param>
/// <param name="result"></param>
private static void CompleteAsyncCommand(string commandName, TaskCompletionSource<string> tcs, object result)
{
try
{
var response = new { status = "success", result };
string json = JsonConvert.SerializeObject(response);
if (!tcs.TrySetResult(json))
{
McpLog.Warn($"TCS for async command '{commandName}' was already completed");
}
}
catch (Exception ex)
{
McpLog.Error($"Error completing async command '{commandName}': {ex.Message}\n{ex.StackTrace}");
ReportAsyncFailure(commandName, tcs, ex);
}
}
/// <summary>
/// Report an error that occurred during async command execution.
/// Completes the TaskCompletionSource with an error response.
/// </summary>
/// <param name="commandName"></param>
/// <param name="tcs"></param>
/// <param name="ex"></param>
private static void ReportAsyncFailure(string commandName, TaskCompletionSource<string> tcs, Exception ex)
{
McpLog.Error($"Error in async command '{commandName}': {ex.Message}\n{ex.StackTrace}");
var errorResponse = new
{
status = "error",
error = ex.Message,
command = commandName,
stackTrace = ex.StackTrace
};
string json;
try
{
json = JsonConvert.SerializeObject(errorResponse);
}
catch (Exception serializationEx)
{
McpLog.Error($"Failed to serialize error response for '{commandName}': {serializationEx.Message}");
json = "{\"status\":\"error\",\"error\":\"Failed to complete command\"}";
}
if (!tcs.TrySetResult(json))
{
McpLog.Warn($"TCS for async command '{commandName}' was already completed when trying to report error");
}
}
}
}