1+ using System . Text ;
2+ using System . Text . Json ;
3+ using Microsoft . Extensions . DependencyInjection ;
4+ using Microsoft . Extensions . Hosting ;
5+ using Microsoft . Extensions . Logging ;
6+ using SuperSocket . MCP ;
7+ using SuperSocket . MCP . Abstractions ;
8+ using SuperSocket . MCP . Commands ;
9+ using SuperSocket . MCP . Extensions ;
10+ using SuperSocket . MCP . Models ;
11+ using SuperSocket . Server ;
12+ using SuperSocket . Server . Abstractions . Session ;
13+ using SuperSocket . Server . Host ;
14+ using SuperSocket . Command ;
15+
16+ namespace McpStdioServer
17+ {
18+ /// <summary>
19+ /// MCP Server implementation that communicates over stdio (stdin/stdout)
20+ /// This is the standard way MCP servers are used - they communicate via stdio
21+ /// and are typically spawned by MCP clients as subprocess
22+ /// </summary>
23+ class Program
24+ {
25+ static async Task Main ( string [ ] args )
26+ {
27+ // Disable console buffering for immediate I/O
28+ Console . SetOut ( new StreamWriter ( Console . OpenStandardOutput ( ) ) { AutoFlush = true } ) ;
29+
30+ var host = SuperSocketHostBuilder . Create < McpMessage , McpPipelineFilter > ( )
31+ . UseConsole ( ) // Use stdio instead of TCP - this is the key change!
32+ . UseCommand < string , McpMessage > ( commandOptions =>
33+ {
34+ // Register MCP commands
35+ commandOptions . AddCommand < InitializeCommand > ( ) ;
36+ commandOptions . AddCommand < InitializedCommand > ( ) ;
37+ commandOptions . AddCommand < ListToolsCommand > ( ) ;
38+ commandOptions . AddCommand < CallToolCommand > ( ) ;
39+ commandOptions . AddCommand < ListResourcesCommand > ( ) ;
40+ commandOptions . AddCommand < ListPromptsCommand > ( ) ;
41+ } )
42+ . ConfigureServices ( ( hostCtx , services ) =>
43+ {
44+ // Register MCP services
45+ services . AddSingleton < IMcpHandlerRegistry , McpHandlerRegistry > ( ) ;
46+ services . AddSingleton < McpServerInfo > ( new McpServerInfo
47+ {
48+ Name = "SuperSocket MCP Stdio Server" ,
49+ Version = "1.0.0" ,
50+ ProtocolVersion = "2024-11-05"
51+ } ) ;
52+
53+ // Register sample tools
54+ services . AddSingleton < IMcpToolHandler , EchoToolHandler > ( ) ;
55+ services . AddSingleton < IMcpToolHandler , MathToolHandler > ( ) ;
56+ services . AddSingleton < IMcpToolHandler , FileReadToolHandler > ( ) ;
57+ } )
58+ . ConfigureLogging ( ( hostCtx , loggingBuilder ) =>
59+ {
60+ // Minimal logging to stderr to avoid interfering with MCP protocol on stdout
61+ loggingBuilder . AddConsole ( options =>
62+ {
63+ options . LogToStandardErrorThreshold = LogLevel . Warning ;
64+ } ) ;
65+ loggingBuilder . SetMinimumLevel ( LogLevel . Warning ) ;
66+ } )
67+ . Build ( ) ;
68+
69+ await host . RunAsync ( ) ;
70+ }
71+ }
72+
73+ /// <summary>
74+ /// Simple echo tool that returns the input message
75+ /// </summary>
76+ public class EchoToolHandler : IMcpToolHandler
77+ {
78+ public Task < McpTool > GetToolDefinitionAsync ( )
79+ {
80+ return Task . FromResult ( new McpTool
81+ {
82+ Name = "echo" ,
83+ Description = "Echo back the input message" ,
84+ InputSchema = new
85+ {
86+ type = "object" ,
87+ properties = new
88+ {
89+ message = new { type = "string" , description = "Message to echo back" }
90+ } ,
91+ required = new [ ] { "message" }
92+ }
93+ } ) ;
94+ }
95+
96+ public Task < McpToolResult > ExecuteAsync ( Dictionary < string , object > arguments )
97+ {
98+ var message = arguments . TryGetValue ( "message" , out var msg ) ? msg . ToString ( ) : "Hello!" ;
99+ return Task . FromResult ( new McpToolResult
100+ {
101+ Content = new List < McpContent >
102+ {
103+ new McpContent { Type = "text" , Text = $ "Echo: { message } " }
104+ }
105+ } ) ;
106+ }
107+ }
108+
109+ /// <summary>
110+ /// Math tool that performs basic arithmetic operations
111+ /// </summary>
112+ public class MathToolHandler : IMcpToolHandler
113+ {
114+ public Task < McpTool > GetToolDefinitionAsync ( )
115+ {
116+ return Task . FromResult ( new McpTool
117+ {
118+ Name = "math" ,
119+ Description = "Perform basic math operations (add, subtract, multiply, divide)" ,
120+ InputSchema = new
121+ {
122+ type = "object" ,
123+ properties = new
124+ {
125+ operation = new { type = "string" , description = "The operation to perform" , @enum = new [ ] { "add" , "subtract" , "multiply" , "divide" } } ,
126+ a = new { type = "number" , description = "First number" } ,
127+ b = new { type = "number" , description = "Second number" }
128+ } ,
129+ required = new [ ] { "operation" , "a" , "b" }
130+ }
131+ } ) ;
132+ }
133+
134+ public Task < McpToolResult > ExecuteAsync ( Dictionary < string , object > arguments )
135+ {
136+ try
137+ {
138+ var operation = arguments . TryGetValue ( "operation" , out var op ) ? op . ToString ( ) : "" ;
139+ var a = Convert . ToDouble ( arguments . TryGetValue ( "a" , out var aVal ) ? aVal : 0 ) ;
140+ var b = Convert . ToDouble ( arguments . TryGetValue ( "b" , out var bVal ) ? bVal : 0 ) ;
141+
142+ double result = operation switch
143+ {
144+ "add" => a + b ,
145+ "subtract" => a - b ,
146+ "multiply" => a * b ,
147+ "divide" => b != 0 ? a / b : throw new DivideByZeroException ( "Cannot divide by zero" ) ,
148+ _ => throw new ArgumentException ( $ "Unknown operation: { operation } ")
149+ } ;
150+
151+ return Task . FromResult ( new McpToolResult
152+ {
153+ Content = new List < McpContent >
154+ {
155+ new McpContent { Type = "text" , Text = $ "Result: { a } { operation } { b } = { result } " }
156+ }
157+ } ) ;
158+ }
159+ catch ( Exception ex )
160+ {
161+ return Task . FromResult ( new McpToolResult
162+ {
163+ Content = new List < McpContent >
164+ {
165+ new McpContent { Type = "text" , Text = $ "Error: { ex . Message } " }
166+ } ,
167+ IsError = true
168+ } ) ;
169+ }
170+ }
171+ }
172+
173+ /// <summary>
174+ /// File reading tool for accessing local files
175+ /// </summary>
176+ public class FileReadToolHandler : IMcpToolHandler
177+ {
178+ public Task < McpTool > GetToolDefinitionAsync ( )
179+ {
180+ return Task . FromResult ( new McpTool
181+ {
182+ Name = "read_file" ,
183+ Description = "Read the contents of a text file" ,
184+ InputSchema = new
185+ {
186+ type = "object" ,
187+ properties = new
188+ {
189+ path = new { type = "string" , description = "Path to the file to read" }
190+ } ,
191+ required = new [ ] { "path" }
192+ }
193+ } ) ;
194+ }
195+
196+ public async Task < McpToolResult > ExecuteAsync ( Dictionary < string , object > arguments )
197+ {
198+ try
199+ {
200+ var path = arguments . TryGetValue ( "path" , out var p ) ? p . ToString ( ) : "" ;
201+
202+ if ( string . IsNullOrEmpty ( path ) )
203+ {
204+ throw new ArgumentException ( "File path is required" ) ;
205+ }
206+
207+ if ( ! File . Exists ( path ) )
208+ {
209+ throw new FileNotFoundException ( $ "File not found: { path } ") ;
210+ }
211+
212+ var content = await File . ReadAllTextAsync ( path ) ;
213+
214+ return new McpToolResult
215+ {
216+ Content = new List < McpContent >
217+ {
218+ new McpContent { Type = "text" , Text = content }
219+ }
220+ } ;
221+ }
222+ catch ( Exception ex )
223+ {
224+ return new McpToolResult
225+ {
226+ Content = new List < McpContent >
227+ {
228+ new McpContent { Type = "text" , Text = $ "Error reading file: { ex . Message } " }
229+ } ,
230+ IsError = true
231+ } ;
232+ }
233+ }
234+ }
235+ }
0 commit comments