Skip to content

Commit 9bd0141

Browse files
Copilotkerryjiang
andauthored
Refactor MCP to use SuperSocket Commands for shared HTTP/TCP handling (#810)
* Initial plan * Initial analysis and planning for MCP command refactoring Co-authored-by: kerryjiang <[email protected]> * Implement MCP command infrastructure with handler registry and base commands Co-authored-by: kerryjiang <[email protected]> * Refactor MCP servers to use shared handler registry and simplify command service extensions Co-authored-by: kerryjiang <[email protected]> * Complete MCP command refactoring implementation with documentation Co-authored-by: kerryjiang <[email protected]> * Refactor HTTP server to use command dispatcher, removing switch/case statements Co-authored-by: kerryjiang <[email protected]> * Refactor McpServer to also use command dispatcher, removing all remaining switch statements Co-authored-by: kerryjiang <[email protected]> * Refactor MCP to use SuperSocket native command system Co-authored-by: kerryjiang <[email protected]> * tried to fix the build --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: kerryjiang <[email protected]> Co-authored-by: Kerry Jiang <[email protected]>
1 parent ce0a878 commit 9bd0141

21 files changed

+3282
-494
lines changed

dotnet-install.sh

Lines changed: 1888 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System.Collections.Generic;
2+
3+
namespace SuperSocket.MCP.Abstractions
4+
{
5+
/// <summary>
6+
/// Registry for MCP handlers that can be shared across different transports
7+
/// </summary>
8+
public interface IMcpHandlerRegistry
9+
{
10+
/// <summary>
11+
/// Registers a tool handler
12+
/// </summary>
13+
/// <param name="name">Tool name</param>
14+
/// <param name="handler">Tool handler</param>
15+
void RegisterTool(string name, IMcpToolHandler handler);
16+
17+
/// <summary>
18+
/// Registers a resource handler
19+
/// </summary>
20+
/// <param name="uri">Resource URI</param>
21+
/// <param name="handler">Resource handler</param>
22+
void RegisterResource(string uri, IMcpResourceHandler handler);
23+
24+
/// <summary>
25+
/// Registers a prompt handler
26+
/// </summary>
27+
/// <param name="name">Prompt name</param>
28+
/// <param name="handler">Prompt handler</param>
29+
void RegisterPrompt(string name, IMcpPromptHandler handler);
30+
31+
/// <summary>
32+
/// Gets all registered tool handlers
33+
/// </summary>
34+
/// <returns>Dictionary of tool handlers</returns>
35+
IReadOnlyDictionary<string, IMcpToolHandler> GetToolHandlers();
36+
37+
/// <summary>
38+
/// Gets all registered resource handlers
39+
/// </summary>
40+
/// <returns>Dictionary of resource handlers</returns>
41+
IReadOnlyDictionary<string, IMcpResourceHandler> GetResourceHandlers();
42+
43+
/// <summary>
44+
/// Gets all registered prompt handlers
45+
/// </summary>
46+
/// <returns>Dictionary of prompt handlers</returns>
47+
IReadOnlyDictionary<string, IMcpPromptHandler> GetPromptHandlers();
48+
49+
/// <summary>
50+
/// Gets a specific tool handler
51+
/// </summary>
52+
/// <param name="name">Tool name</param>
53+
/// <returns>Tool handler if found, null otherwise</returns>
54+
IMcpToolHandler? GetToolHandler(string name);
55+
56+
/// <summary>
57+
/// Gets a specific resource handler
58+
/// </summary>
59+
/// <param name="uri">Resource URI</param>
60+
/// <returns>Resource handler if found, null otherwise</returns>
61+
IMcpResourceHandler? GetResourceHandler(string uri);
62+
63+
/// <summary>
64+
/// Gets a specific prompt handler
65+
/// </summary>
66+
/// <param name="name">Prompt name</param>
67+
/// <returns>Prompt handler if found, null otherwise</returns>
68+
IMcpPromptHandler? GetPromptHandler(string name);
69+
}
70+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text.Json;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Microsoft.Extensions.Logging;
7+
using SuperSocket.Command;
8+
using SuperSocket.MCP.Abstractions;
9+
using SuperSocket.MCP.Models;
10+
using SuperSocket.Server.Abstractions.Session;
11+
12+
namespace SuperSocket.MCP.Commands
13+
{
14+
/// <summary>
15+
/// Command to handle MCP tools/call requests
16+
/// </summary>
17+
[Command("tools/call")]
18+
public class CallToolCommand : McpCommandBase
19+
{
20+
/// <summary>
21+
/// Initializes a new instance of the CallToolCommand class
22+
/// </summary>
23+
/// <param name="logger">Logger instance</param>
24+
/// <param name="handlerRegistry">Handler registry</param>
25+
public CallToolCommand(ILogger<CallToolCommand> logger, IMcpHandlerRegistry handlerRegistry)
26+
: base(logger, handlerRegistry)
27+
{
28+
}
29+
30+
/// <inheritdoc />
31+
protected override async Task<McpMessage?> HandleAsync(IAppSession session, McpMessage message, CancellationToken cancellationToken)
32+
{
33+
try
34+
{
35+
var callParams = JsonSerializer.Deserialize<McpCallToolParams>(
36+
JsonSerializer.Serialize(message.Params),
37+
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
38+
39+
if (callParams == null || string.IsNullOrEmpty(callParams.Name))
40+
{
41+
return CreateErrorResponse(message.Id, McpErrorCodes.InvalidParams, "Invalid tool call parameters");
42+
}
43+
44+
var handler = _handlerRegistry.GetToolHandler(callParams.Name);
45+
if (handler == null)
46+
{
47+
return CreateErrorResponse(message.Id, McpErrorCodes.MethodNotFound, $"Tool '{callParams.Name}' not found");
48+
}
49+
50+
_logger.LogInformation("Executing tool: {ToolName}", callParams.Name);
51+
52+
var toolResult = await handler.ExecuteAsync(callParams.Arguments ?? new Dictionary<string, object>());
53+
54+
var result = new McpCallToolResult
55+
{
56+
Content = toolResult.Content,
57+
IsError = toolResult.IsError
58+
};
59+
60+
_logger.LogInformation("Tool {ToolName} executed successfully", callParams.Name);
61+
62+
return CreateSuccessResponse(message.Id, result);
63+
}
64+
catch (Exception ex)
65+
{
66+
_logger.LogError(ex, "Error executing tool");
67+
return CreateErrorResponse(message.Id, McpErrorCodes.InternalError, "Tool execution failed");
68+
}
69+
}
70+
}
71+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text.Json;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Microsoft.Extensions.Logging;
7+
using SuperSocket.Command;
8+
using SuperSocket.MCP.Abstractions;
9+
using SuperSocket.MCP.Models;
10+
using SuperSocket.Server.Abstractions.Session;
11+
12+
namespace SuperSocket.MCP.Commands
13+
{
14+
/// <summary>
15+
/// Command to handle MCP prompts/get requests
16+
/// </summary>
17+
[Command("prompts/get")]
18+
public class GetPromptCommand : McpCommandBase
19+
{
20+
/// <summary>
21+
/// Initializes a new instance of the GetPromptCommand class
22+
/// </summary>
23+
/// <param name="logger">Logger instance</param>
24+
/// <param name="handlerRegistry">Handler registry</param>
25+
public GetPromptCommand(ILogger<GetPromptCommand> logger, IMcpHandlerRegistry handlerRegistry)
26+
: base(logger, handlerRegistry)
27+
{
28+
}
29+
30+
/// <inheritdoc />
31+
protected override async Task<McpMessage?> HandleAsync(IAppSession session, McpMessage message, CancellationToken cancellationToken)
32+
{
33+
try
34+
{
35+
var promptParams = JsonSerializer.Deserialize<Dictionary<string, object>>(
36+
JsonSerializer.Serialize(message.Params),
37+
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
38+
39+
if (promptParams == null || !promptParams.TryGetValue("name", out var nameObj))
40+
{
41+
return CreateErrorResponse(message.Id, McpErrorCodes.InvalidParams, "Prompt name required");
42+
}
43+
44+
var name = nameObj?.ToString();
45+
if (string.IsNullOrEmpty(name))
46+
{
47+
return CreateErrorResponse(message.Id, McpErrorCodes.InvalidParams, "Prompt name cannot be empty");
48+
}
49+
50+
var handler = _handlerRegistry.GetPromptHandler(name);
51+
if (handler == null)
52+
{
53+
return CreateErrorResponse(message.Id, McpErrorCodes.MethodNotFound, $"Prompt '{name}' not found");
54+
}
55+
56+
_logger.LogInformation("Getting prompt: {PromptName}", name);
57+
58+
var args = promptParams.ContainsKey("arguments") ?
59+
JsonSerializer.Deserialize<Dictionary<string, object>>(
60+
JsonSerializer.Serialize(promptParams["arguments"])) : null;
61+
62+
var promptResult = await handler.GetAsync(name, args);
63+
64+
_logger.LogInformation("Prompt {PromptName} retrieved successfully", name);
65+
66+
return CreateSuccessResponse(message.Id, promptResult);
67+
}
68+
catch (Exception ex)
69+
{
70+
_logger.LogError(ex, "Error getting prompt");
71+
return CreateErrorResponse(message.Id, McpErrorCodes.InternalError, "Prompt retrieval failed");
72+
}
73+
}
74+
}
75+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System;
2+
using System.Linq;
3+
using System.Text.Json;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Microsoft.Extensions.Logging;
7+
using SuperSocket.Command;
8+
using SuperSocket.MCP.Abstractions;
9+
using SuperSocket.MCP.Models;
10+
using SuperSocket.Server.Abstractions.Session;
11+
12+
namespace SuperSocket.MCP.Commands
13+
{
14+
/// <summary>
15+
/// Command to handle MCP initialize requests
16+
/// </summary>
17+
[Command("initialize")]
18+
public class InitializeCommand : McpCommandBase
19+
{
20+
private readonly McpServerInfo _serverInfo;
21+
22+
/// <summary>
23+
/// Initializes a new instance of the InitializeCommand class
24+
/// </summary>
25+
/// <param name="logger">Logger instance</param>
26+
/// <param name="handlerRegistry">Handler registry</param>
27+
/// <param name="serverInfo">Server information</param>
28+
public InitializeCommand(ILogger<InitializeCommand> logger, IMcpHandlerRegistry handlerRegistry, McpServerInfo serverInfo)
29+
: base(logger, handlerRegistry)
30+
{
31+
_serverInfo = serverInfo ?? throw new ArgumentNullException(nameof(serverInfo));
32+
}
33+
34+
/// <inheritdoc />
35+
protected override async Task<McpMessage?> HandleAsync(IAppSession session, McpMessage message, CancellationToken cancellationToken)
36+
{
37+
try
38+
{
39+
var initParams = JsonSerializer.Deserialize<McpInitializeParams>(
40+
JsonSerializer.Serialize(message.Params),
41+
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
42+
43+
if (initParams == null)
44+
{
45+
return CreateErrorResponse(message.Id, McpErrorCodes.InvalidParams, "Invalid initialize parameters");
46+
}
47+
48+
_logger.LogInformation("Initializing MCP server for client: {ClientName} {ClientVersion}",
49+
initParams.ClientInfo.Name, initParams.ClientInfo.Version);
50+
51+
// Create server capabilities based on registered handlers
52+
var toolHandlers = _handlerRegistry.GetToolHandlers();
53+
var resourceHandlers = _handlerRegistry.GetResourceHandlers();
54+
var promptHandlers = _handlerRegistry.GetPromptHandlers();
55+
56+
var capabilities = new McpServerCapabilities
57+
{
58+
Tools = toolHandlers.Any() ? new McpToolsCapabilities { ListChanged = true } : null,
59+
Resources = resourceHandlers.Any() ? new McpResourcesCapabilities { ListChanged = true, Subscribe = true } : null,
60+
Prompts = promptHandlers.Any() ? new McpPromptsCapabilities { ListChanged = true } : null,
61+
Logging = new McpLoggingCapabilities()
62+
};
63+
64+
var result = new McpInitializeResult
65+
{
66+
ProtocolVersion = _serverInfo.ProtocolVersion,
67+
ServerInfo = _serverInfo,
68+
Capabilities = capabilities
69+
};
70+
71+
_logger.LogInformation("MCP server initialized successfully");
72+
73+
return CreateSuccessResponse(message.Id, result);
74+
}
75+
catch (Exception ex)
76+
{
77+
_logger.LogError(ex, "Error during MCP initialization");
78+
return CreateErrorResponse(message.Id, McpErrorCodes.InternalError, "Initialization failed");
79+
}
80+
}
81+
}
82+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System.Threading;
2+
using System.Threading.Tasks;
3+
using Microsoft.Extensions.Logging;
4+
using SuperSocket.Command;
5+
using SuperSocket.MCP.Abstractions;
6+
using SuperSocket.MCP.Models;
7+
using SuperSocket.Server.Abstractions.Session;
8+
9+
namespace SuperSocket.MCP.Commands
10+
{
11+
/// <summary>
12+
/// Command to handle MCP initialized notifications
13+
/// </summary>
14+
[Command("initialized")]
15+
public class InitializedCommand : McpCommandBase
16+
{
17+
/// <summary>
18+
/// Initializes a new instance of the InitializedCommand class
19+
/// </summary>
20+
/// <param name="logger">Logger instance</param>
21+
/// <param name="handlerRegistry">Handler registry</param>
22+
public InitializedCommand(ILogger<InitializedCommand> logger, IMcpHandlerRegistry handlerRegistry)
23+
: base(logger, handlerRegistry)
24+
{
25+
}
26+
27+
/// <inheritdoc />
28+
protected override async Task<McpMessage?> HandleAsync(IAppSession session, McpMessage message, CancellationToken cancellationToken)
29+
{
30+
await Task.Yield(); // Simulate async operation
31+
32+
_logger.LogInformation("MCP server initialization completed");
33+
34+
// Notifications don't send responses
35+
return null;
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)