大家好,我是Edison。
近日被MCP刷屏了,刚好看到张队发了一篇文章提到MCP的官方C# SDK发布了预览版,于是手痒痒尝了一下鲜,写了一个DEMO分享给大家。
MCP是什么鬼?
ModelContextProtocol: 0.1.0-preview.2using ModelContextProtocol.Server;using System.ComponentModel;namespace EDT.McpServer.Tools.ConsoleHost;[]public static class TimeTool{[]public static string GetCurrentTime(string city) =>$"It is {DateTime.Now.Hour}:{DateTime.Now.Minute} in {city}.";}
using Microsoft.Extensions.Hosting;using ModelContextProtocol;using EDT.McpServer.Tools.ConsoleHost;try{Console.WriteLine("Starting MCP Server...");var builder = Host.CreateEmptyApplicationBuilder(settings: );builder.Services.AddMcpServer().WithStdioServerTransport().WithToolsFromAssembly();await builder.Build().RunAsync();return 0;}catch (Exception ex){Console.WriteLine($"Host terminated unexpectedly : {ex.Message}");return 1;}
ModelContextProtocol: 0.1.0-preview.2await using var mcpClient = await McpClientFactory.CreateAsync(new(){Id = "time",Name = "Time MCP Server",TransportType = TransportTypes.StdIo,TransportOptions = new(){["command"] = @"..\..\..\..\EDT.McpServer\bin\Debug\net8.0\EDT.McpServer.exe"}});
var tools = await mcpClient.ListToolsAsync();foreach (var tool in tools){Console.WriteLine($"{tool.Name} ({tool.Description})");}
var result = await mcpClient.CallToolAsync("GetCurrentTime",new Dictionary<string, object?>() { ["city"] = "Chengdu" },CancellationToken.None);Console.WriteLine(result.Content.First(c => c.Type == "text").Text);
var apiKeyCredential = new ApiKeyCredential(config["LLM:ApiKey"]);var aiClientOptions = new OpenAIClientOptions();aiClientOptions.Endpoint = new Uri(config["LLM:EndPoint"]);var aiClient = new OpenAIClient(apiKeyCredential, aiClientOptions).AsChatClient(config["LLM:ModelId"]);var chatClient = new ChatClientBuilder(aiClient).UseFunctionInvocation().Build();IList<ChatMessage> chatHistory =[new(ChatRole.System, """You are a helpful assistant delivering time in one sentencein a short format, like 'It is 10:08 in Paris, France.'"""),];// Core Part: Get AI Tools from MCP Servervar mcpTools = await mcpClient.ListToolsAsync();var chatOptions = new ChatOptions(){Tools = [..mcpTools]};// Prompt the user for a question.Console.ForegroundColor = ConsoleColor.Green;Console.WriteLine($"Assistant> How can I assist you today?");while (true){// Read the user question.Console.ForegroundColor = ConsoleColor.White;Console.Write("User> ");var question = Console.ReadLine();// Exit the application if the user didn't type anything.if (!string.IsOrWhiteSpace(question) && question.ToUpper() == "EXIT")break;chatHistory.Add(new ChatMessage(ChatRole.User, question));Console.ForegroundColor = ConsoleColor.Green;var response = await chatClient.GetResponseAsync(chatHistory, chatOptions);var content = response.ToString();Console.WriteLine($"Assistant> {content}");chatHistory.Add(new ChatMessage(ChatRole.Assistant, content));Console.WriteLine();}
ModelContextProtocol: 0.1.0-preview.2public static class McpEndpointRouteBuilderExtensions{public static IEndpointConventionBuilder MapMcpSse(this IEndpointRouteBuilder endpoints){SseResponseStreamTransport? transport = ;var loggerFactory = endpoints.ServiceProvider.GetRequiredService<ILoggerFactory>();var mcpServerOptions = endpoints.ServiceProvider.GetRequiredService<IOptions<McpServerOptions>>();var routeGroup = endpoints.MapGroup("");routeGroup.MapGet("/sse", async (HttpResponse response, CancellationToken requestAborted) =>{response.Headers.ContentType = "text/event-stream";response.Headers.CacheControl = "no-cache";await using var localTransport = transport = new SseResponseStreamTransport(response.Body);await using var server = McpServerFactory.Create(transport, mcpServerOptions.Value, loggerFactory, endpoints.ServiceProvider);try{var transportTask = transport.RunAsync(cancellationToken: requestAborted);await server.StartAsync(cancellationToken: requestAborted);await transportTask;}catch (OperationCanceledException) when (requestAborted.IsCancellationRequested){// RequestAborted always triggers when the client disconnects before a complete response body is written,// but this is how SSE connections are typically closed.}});routeGroup.MapPost("/message", async context =>{if (transport is ){await Results.BadRequest("Connect to the /sse endpoint before sending messages.").ExecuteAsync(context);return;}var message = await context.Request.ReadFromJsonAsync<IJsonRpcMessage>(McpJsonUtilities.DefaultOptions, context.RequestAborted);if (message is ){await Results.BadRequest("No message in request body.").ExecuteAsync(context);return;}await transport.OnMessageReceivedAsync(message, context.RequestAborted);context.Response.StatusCode = StatusCodes.Status202Accepted;await context.Response.WriteAsync("Accepted");});return routeGroup;}}
using EDT.McpServer.WebHost.Extensions;using EDT.McpServer.WebHost.Tools;using ModelContextProtocol;try{Console.WriteLine("Starting MCP Server...");var builder = WebApplication.CreateBuilder(args);builder.Services.AddMcpServer().WithToolsFromAssembly();builder.Services.AddWeatherToolService();var app = builder.Build();app.UseHttpsRedirection();app.MapGet("/", () => "Hello MCP Server!");app.MapMcpSse();app.Run();return 0;}catch (Exception ex){Console.WriteLine($"Host terminated unexpectedly : {ex.Message}");return 1;}
await using var mcpClient = await McpClientFactory.CreateAsync(new(){Id = "time",Name = "Time MCP Server",TransportType = TransportTypes.Sse,Location = "https://localhost:8443/sse"});
