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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);

Check warning on line 54 in src/SuperSocket.MCP/Abstractions/IMcpHandlerRegistry.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 54 in src/SuperSocket.MCP/Abstractions/IMcpHandlerRegistry.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 54 in src/SuperSocket.MCP/Abstractions/IMcpHandlerRegistry.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 54 in src/SuperSocket.MCP/Abstractions/IMcpHandlerRegistry.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 54 in src/SuperSocket.MCP/Abstractions/IMcpHandlerRegistry.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 54 in src/SuperSocket.MCP/Abstractions/IMcpHandlerRegistry.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

/// <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);

Check warning on line 61 in src/SuperSocket.MCP/Abstractions/IMcpHandlerRegistry.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 61 in src/SuperSocket.MCP/Abstractions/IMcpHandlerRegistry.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 61 in src/SuperSocket.MCP/Abstractions/IMcpHandlerRegistry.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 61 in src/SuperSocket.MCP/Abstractions/IMcpHandlerRegistry.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 61 in src/SuperSocket.MCP/Abstractions/IMcpHandlerRegistry.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 61 in src/SuperSocket.MCP/Abstractions/IMcpHandlerRegistry.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

/// <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);

Check warning on line 68 in src/SuperSocket.MCP/Abstractions/IMcpHandlerRegistry.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 68 in src/SuperSocket.MCP/Abstractions/IMcpHandlerRegistry.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 68 in src/SuperSocket.MCP/Abstractions/IMcpHandlerRegistry.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 68 in src/SuperSocket.MCP/Abstractions/IMcpHandlerRegistry.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 68 in src/SuperSocket.MCP/Abstractions/IMcpHandlerRegistry.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 68 in src/SuperSocket.MCP/Abstractions/IMcpHandlerRegistry.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
}
}
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");
}
}
}
}
38 changes: 38 additions & 0 deletions src/SuperSocket.MCP/Commands/InitializedCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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 initialized notifications
/// </summary>
[Command("initialized")]
public class InitializedCommand : McpCommandBase
{
/// <summary>
/// Initializes a new instance of the InitializedCommand class
/// </summary>
/// <param name="logger">Logger instance</param>
/// <param name="handlerRegistry">Handler registry</param>
public InitializedCommand(ILogger<InitializedCommand> logger, IMcpHandlerRegistry handlerRegistry)
: base(logger, handlerRegistry)
{
}

/// <inheritdoc />
protected override async Task<McpMessage?> HandleAsync(IAppSession session, McpMessage message, CancellationToken cancellationToken)
{
await Task.Yield(); // Simulate async operation

_logger.LogInformation("MCP server initialization completed");

// Notifications don't send responses
return null;
}
}
}
Loading
Loading