Skip to content

Refactor MCP to use SuperSocket Commands for shared HTTP/TCP handling #810

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 16, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,888 changes: 1,888 additions & 0 deletions dotnet-install.sh

Large diffs are not rendered by default.

70 changes: 70 additions & 0 deletions src/SuperSocket.MCP/Abstractions/IMcpHandlerRegistry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System.Collections.Generic;

namespace SuperSocket.MCP.Abstractions
{
/// <summary>
/// Registry for MCP handlers that can be shared across different transports
/// </summary>
public interface IMcpHandlerRegistry
{
/// <summary>
/// Registers a tool handler
/// </summary>
/// <param name="name">Tool name</param>
/// <param name="handler">Tool handler</param>
void RegisterTool(string name, IMcpToolHandler handler);

/// <summary>
/// Registers a resource handler
/// </summary>
/// <param name="uri">Resource URI</param>
/// <param name="handler">Resource handler</param>
void RegisterResource(string uri, IMcpResourceHandler handler);

/// <summary>
/// Registers a prompt handler
/// </summary>
/// <param name="name">Prompt name</param>
/// <param name="handler">Prompt handler</param>
void RegisterPrompt(string name, IMcpPromptHandler handler);

/// <summary>
/// Gets all registered tool handlers
/// </summary>
/// <returns>Dictionary of tool handlers</returns>
IReadOnlyDictionary<string, IMcpToolHandler> GetToolHandlers();

/// <summary>
/// Gets all registered resource handlers
/// </summary>
/// <returns>Dictionary of resource handlers</returns>
IReadOnlyDictionary<string, IMcpResourceHandler> GetResourceHandlers();

/// <summary>
/// Gets all registered prompt handlers
/// </summary>
/// <returns>Dictionary of prompt handlers</returns>
IReadOnlyDictionary<string, IMcpPromptHandler> GetPromptHandlers();

/// <summary>
/// Gets a specific tool handler
/// </summary>
/// <param name="name">Tool name</param>
/// <returns>Tool handler if found, null otherwise</returns>
IMcpToolHandler? GetToolHandler(string name);

/// <summary>
/// Gets a specific resource handler
/// </summary>
/// <param name="uri">Resource URI</param>
/// <returns>Resource handler if found, null otherwise</returns>
IMcpResourceHandler? GetResourceHandler(string uri);

/// <summary>
/// Gets a specific prompt handler
/// </summary>
/// <param name="name">Prompt name</param>
/// <returns>Prompt handler if found, null otherwise</returns>
IMcpPromptHandler? GetPromptHandler(string name);
}
}
71 changes: 71 additions & 0 deletions src/SuperSocket.MCP/Commands/CallToolCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SuperSocket.Command;
using SuperSocket.MCP.Abstractions;
using SuperSocket.MCP.Models;
using SuperSocket.Server.Abstractions.Session;

namespace SuperSocket.MCP.Commands
{
/// <summary>
/// Command to handle MCP tools/call requests
/// </summary>
[Command("tools/call")]
public class CallToolCommand : McpCommandBase
{
/// <summary>
/// Initializes a new instance of the CallToolCommand class
/// </summary>
/// <param name="logger">Logger instance</param>
/// <param name="handlerRegistry">Handler registry</param>
public CallToolCommand(ILogger<CallToolCommand> logger, IMcpHandlerRegistry handlerRegistry)
: base(logger, handlerRegistry)
{
}

/// <inheritdoc />
protected override async Task<McpMessage?> HandleAsync(IAppSession session, McpMessage message, CancellationToken cancellationToken)
{
try
{
var callParams = JsonSerializer.Deserialize<McpCallToolParams>(
JsonSerializer.Serialize(message.Params),
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });

if (callParams == null || string.IsNullOrEmpty(callParams.Name))
{
return CreateErrorResponse(message.Id, McpErrorCodes.InvalidParams, "Invalid tool call parameters");
}

var handler = _handlerRegistry.GetToolHandler(callParams.Name);
if (handler == null)
{
return CreateErrorResponse(message.Id, McpErrorCodes.MethodNotFound, $"Tool '{callParams.Name}' not found");
}

_logger.LogInformation("Executing tool: {ToolName}", callParams.Name);

var toolResult = await handler.ExecuteAsync(callParams.Arguments ?? new Dictionary<string, object>());

var result = new McpCallToolResult
{
Content = toolResult.Content,
IsError = toolResult.IsError
};

_logger.LogInformation("Tool {ToolName} executed successfully", callParams.Name);

return CreateSuccessResponse(message.Id, result);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error executing tool");
return CreateErrorResponse(message.Id, McpErrorCodes.InternalError, "Tool execution failed");
}
}
}
}
75 changes: 75 additions & 0 deletions src/SuperSocket.MCP/Commands/GetPromptCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SuperSocket.Command;
using SuperSocket.MCP.Abstractions;
using SuperSocket.MCP.Models;
using SuperSocket.Server.Abstractions.Session;

namespace SuperSocket.MCP.Commands
{
/// <summary>
/// Command to handle MCP prompts/get requests
/// </summary>
[Command("prompts/get")]
public class GetPromptCommand : McpCommandBase
{
/// <summary>
/// Initializes a new instance of the GetPromptCommand class
/// </summary>
/// <param name="logger">Logger instance</param>
/// <param name="handlerRegistry">Handler registry</param>
public GetPromptCommand(ILogger<GetPromptCommand> logger, IMcpHandlerRegistry handlerRegistry)
: base(logger, handlerRegistry)
{
}

/// <inheritdoc />
protected override async Task<McpMessage?> HandleAsync(IAppSession session, McpMessage message, CancellationToken cancellationToken)
{
try
{
var promptParams = JsonSerializer.Deserialize<Dictionary<string, object>>(
JsonSerializer.Serialize(message.Params),
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });

if (promptParams == null || !promptParams.TryGetValue("name", out var nameObj))
{
return CreateErrorResponse(message.Id, McpErrorCodes.InvalidParams, "Prompt name required");
}

var name = nameObj?.ToString();
if (string.IsNullOrEmpty(name))
{
return CreateErrorResponse(message.Id, McpErrorCodes.InvalidParams, "Prompt name cannot be empty");
}

var handler = _handlerRegistry.GetPromptHandler(name);
if (handler == null)
{
return CreateErrorResponse(message.Id, McpErrorCodes.MethodNotFound, $"Prompt '{name}' not found");
}

_logger.LogInformation("Getting prompt: {PromptName}", name);

var args = promptParams.ContainsKey("arguments") ?
JsonSerializer.Deserialize<Dictionary<string, object>>(
JsonSerializer.Serialize(promptParams["arguments"])) : null;

var promptResult = await handler.GetAsync(name, args);

_logger.LogInformation("Prompt {PromptName} retrieved successfully", name);

return CreateSuccessResponse(message.Id, promptResult);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting prompt");
return CreateErrorResponse(message.Id, McpErrorCodes.InternalError, "Prompt retrieval failed");
}
}
}
}
82 changes: 82 additions & 0 deletions src/SuperSocket.MCP/Commands/InitializeCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SuperSocket.Command;
using SuperSocket.MCP.Abstractions;
using SuperSocket.MCP.Models;
using SuperSocket.Server.Abstractions.Session;

namespace SuperSocket.MCP.Commands
{
/// <summary>
/// Command to handle MCP initialize requests
/// </summary>
[Command("initialize")]
public class InitializeCommand : McpCommandBase
{
private readonly McpServerInfo _serverInfo;

/// <summary>
/// Initializes a new instance of the InitializeCommand class
/// </summary>
/// <param name="logger">Logger instance</param>
/// <param name="handlerRegistry">Handler registry</param>
/// <param name="serverInfo">Server information</param>
public InitializeCommand(ILogger<InitializeCommand> logger, IMcpHandlerRegistry handlerRegistry, McpServerInfo serverInfo)
: base(logger, handlerRegistry)
{
_serverInfo = serverInfo ?? throw new ArgumentNullException(nameof(serverInfo));
}

/// <inheritdoc />
protected override async Task<McpMessage?> HandleAsync(IAppSession session, McpMessage message, CancellationToken cancellationToken)
{
try
{
var initParams = JsonSerializer.Deserialize<McpInitializeParams>(
JsonSerializer.Serialize(message.Params),
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });

if (initParams == null)
{
return CreateErrorResponse(message.Id, McpErrorCodes.InvalidParams, "Invalid initialize parameters");
}

_logger.LogInformation("Initializing MCP server for client: {ClientName} {ClientVersion}",
initParams.ClientInfo.Name, initParams.ClientInfo.Version);

// Create server capabilities based on registered handlers
var toolHandlers = _handlerRegistry.GetToolHandlers();
var resourceHandlers = _handlerRegistry.GetResourceHandlers();
var promptHandlers = _handlerRegistry.GetPromptHandlers();

var capabilities = new McpServerCapabilities
{
Tools = toolHandlers.Any() ? new McpToolsCapabilities { ListChanged = true } : null,
Resources = resourceHandlers.Any() ? new McpResourcesCapabilities { ListChanged = true, Subscribe = true } : null,
Prompts = promptHandlers.Any() ? new McpPromptsCapabilities { ListChanged = true } : null,
Logging = new McpLoggingCapabilities()
};

var result = new McpInitializeResult
{
ProtocolVersion = _serverInfo.ProtocolVersion,
ServerInfo = _serverInfo,
Capabilities = capabilities
};

_logger.LogInformation("MCP server initialized successfully");

return CreateSuccessResponse(message.Id, result);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during MCP initialization");
return CreateErrorResponse(message.Id, McpErrorCodes.InternalError, "Initialization failed");
}
}
}
}
63 changes: 63 additions & 0 deletions src/SuperSocket.MCP/Commands/ListPromptsCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SuperSocket.Command;
using SuperSocket.MCP.Abstractions;
using SuperSocket.MCP.Models;
using SuperSocket.Server.Abstractions.Session;

namespace SuperSocket.MCP.Commands
{
/// <summary>
/// Command to handle MCP prompts/list requests
/// </summary>
[Command("prompts/list")]
public class ListPromptsCommand : McpCommandBase
{
/// <summary>
/// Initializes a new instance of the ListPromptsCommand class
/// </summary>
/// <param name="logger">Logger instance</param>
/// <param name="handlerRegistry">Handler registry</param>
public ListPromptsCommand(ILogger<ListPromptsCommand> logger, IMcpHandlerRegistry handlerRegistry)
: base(logger, handlerRegistry)
{
}

/// <inheritdoc />
protected override async Task<McpMessage?> HandleAsync(IAppSession session, McpMessage message, CancellationToken cancellationToken)
{
try
{
var prompts = new List<McpPrompt>();
var promptHandlers = _handlerRegistry.GetPromptHandlers();

foreach (var handler in promptHandlers.Values)
{
try
{
var prompt = await handler.GetPromptDefinitionAsync();
prompts.Add(prompt);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting prompt definition");
}
}

var result = new { prompts };

_logger.LogDebug("Listed {PromptCount} prompts", prompts.Count);

return CreateSuccessResponse(message.Id, result);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error listing prompts");
return CreateErrorResponse(message.Id, McpErrorCodes.InternalError, "Failed to list prompts");
}
}
}
}
Loading