Skip to content

Commit ce0a878

Browse files
Copilotkerryjiang
andauthored
Add Model Context Protocol (MCP) Support to SuperSocket (#809)
* Initial plan * Initial setup and environment preparation Co-authored-by: kerryjiang <[email protected]> * Implement comprehensive MCP support for SuperSocket Co-authored-by: kerryjiang <[email protected]> * Complete MCP implementation with documentation and restore original .NET versions Co-authored-by: kerryjiang <[email protected]> * Add HTTP support to MCP with POST requests and SSE notifications Co-authored-by: kerryjiang <[email protected]> * Add HTTP MCP tests and complete implementation documentation Co-authored-by: kerryjiang <[email protected]> * fixed the build * fixed the build * fixed the unit tests --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: kerryjiang <[email protected]> Co-authored-by: Kerry Jiang <[email protected]>
1 parent af91d98 commit ce0a878

29 files changed

+3277
-0
lines changed

SuperSocket.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SuperSocket.Connection", "s
3939
EndProject
4040
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SuperSocket.Kestrel", "src\SuperSocket.Kestrel\SuperSocket.Kestrel.csproj", "{8C8507D6-903F-4786-8F18-ACA54257454B}"
4141
EndProject
42+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SuperSocket.MCP", "src\SuperSocket.MCP\SuperSocket.MCP.csproj", "{B1A8D345-2F3E-4B8D-9C7A-5E2F8A1B4C9D}"
43+
EndProject
4244
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
4345
ProjectSection(SolutionItems) = preProject
4446
.editorconfig = .editorconfig
@@ -246,6 +248,18 @@ Global
246248
{8C8507D6-903F-4786-8F18-ACA54257454B}.Release|x64.Build.0 = Release|Any CPU
247249
{8C8507D6-903F-4786-8F18-ACA54257454B}.Release|x86.ActiveCfg = Release|Any CPU
248250
{8C8507D6-903F-4786-8F18-ACA54257454B}.Release|x86.Build.0 = Release|Any CPU
251+
{B1A8D345-2F3E-4B8D-9C7A-5E2F8A1B4C9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
252+
{B1A8D345-2F3E-4B8D-9C7A-5E2F8A1B4C9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
253+
{B1A8D345-2F3E-4B8D-9C7A-5E2F8A1B4C9D}.Debug|x64.ActiveCfg = Debug|Any CPU
254+
{B1A8D345-2F3E-4B8D-9C7A-5E2F8A1B4C9D}.Debug|x64.Build.0 = Debug|Any CPU
255+
{B1A8D345-2F3E-4B8D-9C7A-5E2F8A1B4C9D}.Debug|x86.ActiveCfg = Debug|Any CPU
256+
{B1A8D345-2F3E-4B8D-9C7A-5E2F8A1B4C9D}.Debug|x86.Build.0 = Debug|Any CPU
257+
{B1A8D345-2F3E-4B8D-9C7A-5E2F8A1B4C9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
258+
{B1A8D345-2F3E-4B8D-9C7A-5E2F8A1B4C9D}.Release|Any CPU.Build.0 = Release|Any CPU
259+
{B1A8D345-2F3E-4B8D-9C7A-5E2F8A1B4C9D}.Release|x64.ActiveCfg = Release|Any CPU
260+
{B1A8D345-2F3E-4B8D-9C7A-5E2F8A1B4C9D}.Release|x64.Build.0 = Release|Any CPU
261+
{B1A8D345-2F3E-4B8D-9C7A-5E2F8A1B4C9D}.Release|x86.ActiveCfg = Release|Any CPU
262+
{B1A8D345-2F3E-4B8D-9C7A-5E2F8A1B4C9D}.Release|x86.Build.0 = Release|Any CPU
249263
EndGlobalSection
250264
GlobalSection(SolutionProperties) = preSolution
251265
HideSolutionNode = FALSE
@@ -267,6 +281,7 @@ Global
267281
{8454E8D5-777D-46CB-B050-76C5119B624B} = {DDA4741E-097F-40C3-A252-2E1E3476C1A7}
268282
{FC2B529F-4AF4-4C39-BC4F-A3836CC7B37C} = {DDA4741E-097F-40C3-A252-2E1E3476C1A7}
269283
{8C8507D6-903F-4786-8F18-ACA54257454B} = {DDA4741E-097F-40C3-A252-2E1E3476C1A7}
284+
{B1A8D345-2F3E-4B8D-9C7A-5E2F8A1B4C9D} = {DDA4741E-097F-40C3-A252-2E1E3476C1A7}
270285
EndGlobalSection
271286
GlobalSection(ExtensibilityGlobals) = postSolution
272287
SolutionGuid = {ADB30AA2-A848-4CB3-8A20-488C80F1BA9E}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<ProjectReference Include="../../src/SuperSocket.MCP/SuperSocket.MCP.csproj" />
12+
<ProjectReference Include="../../src/SuperSocket.Server.Host/SuperSocket.Server.Host.csproj" />
13+
</ItemGroup>
14+
15+
</Project>

samples/McpHttpServer/Program.cs

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.Hosting;
3+
using Microsoft.Extensions.Logging;
4+
using SuperSocket.Http;
5+
using SuperSocket.MCP;
6+
using SuperSocket.MCP.Abstractions;
7+
using SuperSocket.MCP.Extensions;
8+
using SuperSocket.MCP.Models;
9+
using SuperSocket.Server;
10+
using SuperSocket.Server.Abstractions;
11+
using SuperSocket.Server.Abstractions.Session;
12+
using SuperSocket.Server.Host;
13+
14+
// Create HTTP MCP server
15+
var host = SuperSocketHostBuilder.Create<McpHttpRequest, McpHttpPipelineFilter>(args)
16+
.UsePackageHandler(async (IAppSession session, McpHttpRequest request) =>
17+
{
18+
// Get services
19+
var logger = session.Server.ServiceProvider.GetService(typeof(ILogger<McpHttpServer>)) as ILogger<McpHttpServer>;
20+
var serverInfo = new McpServerInfo
21+
{
22+
Name = "SuperSocket HTTP MCP Server",
23+
Version = "1.0.0",
24+
ProtocolVersion = "2024-11-05"
25+
};
26+
27+
var mcpServer = new McpHttpServer(logger!, serverInfo);
28+
29+
// Register sample tools
30+
mcpServer.RegisterTool("echo", new EchoToolHandler());
31+
mcpServer.RegisterTool("math", new MathToolHandler());
32+
mcpServer.RegisterResource("file", new FileResourceHandler());
33+
34+
// Handle the HTTP request
35+
await mcpServer.HandleHttpRequestAsync(request, session);
36+
})
37+
.ConfigureSuperSocket(options =>
38+
{
39+
options.Name = "McpHttpServer";
40+
options.AddListener(new ListenOptions
41+
{
42+
Ip = "Any",
43+
Port = 8080
44+
});
45+
})
46+
.ConfigureLogging((hostCtx, loggingBuilder) =>
47+
{
48+
loggingBuilder.AddConsole();
49+
})
50+
.Build();
51+
52+
Console.WriteLine("HTTP MCP Server starting...");
53+
Console.WriteLine("Available endpoints:");
54+
Console.WriteLine(" POST /mcp - MCP JSON-RPC requests");
55+
Console.WriteLine(" GET /mcp/events - Server-Sent Events");
56+
Console.WriteLine(" GET /mcp/capabilities - Server capabilities");
57+
Console.WriteLine("Server listening on http://localhost:8080");
58+
59+
await host.RunAsync();
60+
61+
/// <summary>
62+
/// Simple echo tool that returns the input message
63+
/// </summary>
64+
public class EchoToolHandler : IMcpToolHandler
65+
{
66+
public Task<McpTool> GetToolDefinitionAsync()
67+
{
68+
return Task.FromResult(new McpTool
69+
{
70+
Name = "echo",
71+
Description = "Echo back the input message",
72+
InputSchema = new
73+
{
74+
type = "object",
75+
properties = new
76+
{
77+
message = new { type = "string", description = "Message to echo back" }
78+
},
79+
required = new[] { "message" }
80+
}
81+
});
82+
}
83+
84+
public Task<McpToolResult> ExecuteAsync(Dictionary<string, object> arguments)
85+
{
86+
var message = arguments.TryGetValue("message", out var msg) ? msg.ToString() : "Hello!";
87+
return Task.FromResult(new McpToolResult
88+
{
89+
Content = new List<McpContent>
90+
{
91+
new McpContent { Type = "text", Text = $"Echo: {message}" }
92+
}
93+
});
94+
}
95+
}
96+
97+
/// <summary>
98+
/// Math tool that performs basic arithmetic operations
99+
/// </summary>
100+
public class MathToolHandler : IMcpToolHandler
101+
{
102+
public Task<McpTool> GetToolDefinitionAsync()
103+
{
104+
return Task.FromResult(new McpTool
105+
{
106+
Name = "math",
107+
Description = "Perform basic math operations (add, subtract, multiply, divide)",
108+
InputSchema = new
109+
{
110+
type = "object",
111+
properties = new
112+
{
113+
operation = new { type = "string", description = "The operation to perform", @enum = new[] { "add", "subtract", "multiply", "divide" } },
114+
a = new { type = "number", description = "First number" },
115+
b = new { type = "number", description = "Second number" }
116+
},
117+
required = new[] { "operation", "a", "b" }
118+
}
119+
});
120+
}
121+
122+
public Task<McpToolResult> ExecuteAsync(Dictionary<string, object> arguments)
123+
{
124+
try
125+
{
126+
var operation = arguments.TryGetValue("operation", out var op) ? op.ToString() : "";
127+
var a = Convert.ToDouble(arguments.TryGetValue("a", out var aVal) ? aVal : 0);
128+
var b = Convert.ToDouble(arguments.TryGetValue("b", out var bVal) ? bVal : 0);
129+
130+
double result = operation switch
131+
{
132+
"add" => a + b,
133+
"subtract" => a - b,
134+
"multiply" => a * b,
135+
"divide" => b != 0 ? a / b : throw new DivideByZeroException("Cannot divide by zero"),
136+
_ => throw new ArgumentException($"Unknown operation: {operation}")
137+
};
138+
139+
return Task.FromResult(new McpToolResult
140+
{
141+
Content = new List<McpContent>
142+
{
143+
new McpContent { Type = "text", Text = $"Result: {a} {operation} {b} = {result}" }
144+
}
145+
});
146+
}
147+
catch (Exception ex)
148+
{
149+
return Task.FromResult(new McpToolResult
150+
{
151+
Content = new List<McpContent>
152+
{
153+
new McpContent { Type = "text", Text = $"Error: {ex.Message}" }
154+
},
155+
IsError = true
156+
});
157+
}
158+
}
159+
}
160+
161+
/// <summary>
162+
/// File resource handler that provides access to files
163+
/// </summary>
164+
public class FileResourceHandler : IMcpResourceHandler
165+
{
166+
public Task<McpResource> GetResourceDefinitionAsync()
167+
{
168+
return Task.FromResult(new McpResource
169+
{
170+
Uri = "file://",
171+
Name = "file",
172+
Description = "Access to file system resources",
173+
MimeType = "text/plain"
174+
});
175+
}
176+
177+
public Task<McpResourceContent> ReadAsync(string uri)
178+
{
179+
try
180+
{
181+
// For demo purposes, just return a simple text response
182+
var content = new McpResourceContent
183+
{
184+
Uri = uri,
185+
MimeType = "text/plain",
186+
Text = $"File content for: {uri}"
187+
};
188+
189+
return Task.FromResult(content);
190+
}
191+
catch (Exception ex)
192+
{
193+
return Task.FromResult(new McpResourceContent
194+
{
195+
Uri = uri,
196+
MimeType = "text/plain",
197+
Text = $"Error reading file: {ex.Message}"
198+
});
199+
}
200+
}
201+
}

samples/McpHttpServer/README.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# SuperSocket MCP HTTP Support
2+
3+
SuperSocket MCP now supports HTTP transport in addition to the original TCP transport with Content-Length headers. This enables easier integration with web applications and provides Server-Sent Events (SSE) support for real-time notifications.
4+
5+
## Features
6+
7+
- **HTTP POST**: Send MCP JSON-RPC requests via HTTP POST to `/mcp`
8+
- **Server-Sent Events**: Connect to `/mcp/events` for real-time notifications
9+
- **Capabilities Endpoint**: GET `/mcp/capabilities` to discover server capabilities
10+
- **Backward Compatibility**: Original TCP transport still works unchanged
11+
12+
## Usage
13+
14+
### HTTP POST Requests
15+
16+
Send MCP JSON-RPC requests to the `/mcp` endpoint:
17+
18+
```bash
19+
curl -X POST http://localhost:8080/mcp \
20+
-H "Content-Type: application/json" \
21+
-d '{
22+
"jsonrpc": "2.0",
23+
"id": 1,
24+
"method": "tools/call",
25+
"params": {
26+
"name": "echo",
27+
"arguments": {
28+
"message": "Hello from HTTP!"
29+
}
30+
}
31+
}'
32+
```
33+
34+
### Server-Sent Events
35+
36+
Connect to the SSE endpoint for real-time notifications:
37+
38+
```javascript
39+
const eventSource = new EventSource('http://localhost:8080/mcp/events');
40+
41+
eventSource.onmessage = function(event) {
42+
const data = JSON.parse(event.data);
43+
console.log('Received:', data);
44+
};
45+
46+
eventSource.addEventListener('notification', function(event) {
47+
const notification = JSON.parse(event.data);
48+
console.log('Notification:', notification);
49+
});
50+
```
51+
52+
### Capabilities
53+
54+
Check server capabilities:
55+
56+
```bash
57+
curl http://localhost:8080/mcp/capabilities
58+
```
59+
60+
## Example Server
61+
62+
```csharp
63+
using SuperSocket.MCP;
64+
using SuperSocket.Server.Host;
65+
66+
var host = SuperSocketHostBuilder.Create<McpHttpRequest, McpHttpPipelineFilter>(args)
67+
.UsePackageHandler(async (session, request) =>
68+
{
69+
var mcpServer = new McpHttpServer(logger, serverInfo);
70+
71+
// Register tools
72+
mcpServer.RegisterTool("echo", new EchoToolHandler());
73+
74+
// Handle HTTP request
75+
await mcpServer.HandleHttpRequestAsync(request, session);
76+
})
77+
.ConfigureSuperSocket(options =>
78+
{
79+
options.Name = "McpHttpServer";
80+
options.AddListener(new ListenOptions { Ip = "Any", Port = 8080 });
81+
})
82+
.Build();
83+
84+
await host.RunAsync();
85+
```
86+
87+
## API Endpoints
88+
89+
### POST /mcp
90+
- **Purpose**: Handle MCP JSON-RPC requests
91+
- **Content-Type**: application/json
92+
- **Body**: MCP JSON-RPC message
93+
- **Response**: JSON-RPC response
94+
95+
### GET /mcp/events
96+
- **Purpose**: Server-Sent Events stream
97+
- **Accept**: text/event-stream
98+
- **Response**: SSE stream with MCP notifications
99+
100+
### GET /mcp/capabilities
101+
- **Purpose**: Server capability discovery
102+
- **Response**: JSON object with server capabilities
103+
104+
## Benefits
105+
106+
1. **Web Integration**: Easy integration with web applications
107+
2. **Real-time Updates**: SSE support for live notifications
108+
3. **Standard HTTP**: Uses familiar HTTP protocols
109+
4. **Backward Compatible**: Original TCP transport unchanged
110+
5. **RESTful**: Standard HTTP methods and status codes
111+
112+
## Transport Comparison
113+
114+
| Feature | TCP (Original) | HTTP |
115+
|---------|----------------|------|
116+
| Protocol | Content-Length + JSON | HTTP + JSON |
117+
| Port | 3000 (default) | 8080 (default) |
118+
| Streaming | Bidirectional | SSE for notifications |
119+
| Web Compatible | No | Yes |
120+
| Firewall Friendly | No | Yes |
121+
| Load Balancer Support | Limited | Full |
122+
123+
Both transports support the same MCP features and can be used simultaneously.

samples/McpServer/McpServer.csproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
<ItemGroup>
9+
<PackageReference Include="Microsoft.Extensions.Hosting" />
10+
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
11+
</ItemGroup>
12+
<ItemGroup>
13+
<ProjectReference Include="../../src/SuperSocket.MCP/SuperSocket.MCP.csproj" />
14+
<ProjectReference Include="../../src/SuperSocket.Server/SuperSocket.Server.csproj" />
15+
</ItemGroup>
16+
</Project>

0 commit comments

Comments
 (0)