用C#构建简易MCP服务器与客户端
什么是MCP?
模型上下文协议(Model Context Protocol,简称MCP)是一种标准化方式,用于让AI系统访问和与各种外部资源(如文件系统、数据库、API及其他服务)进行交互。MCP提供了一个统一的接口,任何符合该协议的系统都可以使用,从而避免了为每个集成编写独特连接器的需求。
通过使用MCP,开发者能够使AI代理具备发现、检索和处理信息的能力,而无需关心底层技术的具体实现。这种抽象化使得开发者可以更专注于系统的功能实现,而非数据连接的技术细节。
MCP核心基础组件
MCP围绕三个核心概念构建:
1. 资源(Resources)
这些代表AI代理可能需要访问的任何外部数据,例如文档、图像、文件、数据库等。
- 统一访问:代理通过统一的接口请求任何资源。
- 实时更新:系统确保代理能够获取最新的信息。
- 复杂性抽象:开发者无需为每个数据源编写自定义代码。
2. 工具(Tools)
工具代表AI代理可以调用以执行特定任务的功能或操作,例如获取数据、编辑文件、处理内容等。
- 面向操作:代理不仅能读取数据,还能修改数据或触发工作流。
- 动态选择:代理可根据上下文动态发现并选择合适的工具。
3. 提示词(Prompts)
提示词是结构化的模板或指令,用于定义AI代理如何与工具和资源进行交互。
- 引导式交互:提示词确保所有请求都以正确的格式发出。
- 上下文管理:代理在多个操作之间保持连贯的交互逻辑。
- 明确性:标准化的提示词减少了歧义和误解。
这三个基础组件共同作用,使代理能够在模块化系统中智能且自主地运行。
MCP架构:客户端-服务器模型
MCP采用客户端-服务器模型设计:
- 客户端(通常是AI代理)发送结构化请求,其中包含提示词(prompts)和资源标识符。
- MCP服务器负责处理这些请求,维护上下文,并返回结果。
这种分离设计确保了系统在各种应用场景下具备良好的可扩展性、安全性和灵活性。
C# MCP SDK
为支持 .NET 开发者,我们已在 GitHub 上发布了 C# SDK:
👉 MCP C# SDK
该 SDK 简化了将 AI 代理与 MCP 服务器集成的过程,自动处理序列化、请求/响应格式以及上下文管理。
示例解决方案:简易 MCP 服务器与客户端
为了演示 MCP 在实际中的工作方式,我创建了一个极简的 C# MCP 解决方案:
📁 GitHub 仓库:petersaktor/mcp
该解决方案包含服务器端和客户端两部分,展示了如何实现以下功能:
- 在服务器端定义并暴露工具(Tools)
- 发送提示词(Prompts)并与资源进行交互
- 记录并处理来自 MCP 服务器的返回结果
MCP Server
Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
Console.WriteLine("Hello, MCP Simple server!");
var builder = Host.CreateApplicationBuilder(args);
builder.Logging.AddConsole(consoleLogOptions =>
{
// Configure all logs to go to stderr
consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
});
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithPromptsFromAssembly()
.WithToolsFromAssembly()
.WithResourcesFromAssembly();
await builder.Build()
.RunAsync();
SimplePrompt.cs
using Microsoft.Extensions.AI;
using ModelContextProtocol.Server;
using System.ComponentModel;
namespace SimpleServer.Prompts;
[McpServerPromptType]
public static class SimplePrompt
{
[McpServerPrompt, Description("Creates a prompt to summarize the provided message.")]
public static ChatMessage Summarize(string content) =>
new(ChatRole.User, $"Please summarize this content into a single sentence: {content}");
}
SimpleTool.cs
using ModelContextProtocol.Server;
using System.ComponentModel;
namespace SimpleServer.Prompts;
[McpServerToolType]
public static class SimpleTool
{
[McpServerTool, Description("Returns the current time for the specified time zone.")]
public static string GetCurrentTime(string timeZone)
{
try
{
var tz = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
var now = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz);
return now.ToString("o"); // ISO 8601 format
}
catch (TimeZoneNotFoundException)
{
return $"Time zone '{timeZone}' not found.";
}
catch (InvalidTimeZoneException)
{
return $"Time zone '{timeZone}' is invalid.";
}
}
}
SimpleResource.cs
using ModelContextProtocol.Server;
using System.ComponentModel;
namespace SimpleServer.Prompts;
[McpServerResourceType]
public static class SimpleResource
{
[McpServerResource, Description("Returns the Bing endpoint URL.")]
public static string GetBingEndpoint() => "https://bing.com";
}
MCP Client
Program.cs
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
Console.WriteLine($"Hello, MCP Simple client!");
// Create the stdio transport (using current process's stdin/stdout)
var transport = new StdioClientTransport(new StdioClientTransportOptions
{
Name = "MCP Simple Client",
Command = "..\\..\\..\\..\\SimpleServer\\bin\\Debug\\net8.0\\SimpleServer.exe",
// The command to start the MCP server process
// If the server is already running, you can use the same command as the server
// If you want to use a different command, specify it here
});
try
{
// Create the MCP client
var client = await McpClientFactory.CreateAsync(cancellationToken: default, clientTransport: transport);
// List available prompts from the server
foreach (var pr in await client.ListPromptsAsync())
{
Console.WriteLine($"Available prompt: {pr.Name} - {pr.Description}");
}
// Specify the prompt you want to use
var promptName = "Summarize";
// Create a dictionary of arguments for the prompt
IReadOnlyDictionary<string, object> promptArguments = new Dictionary<string, object>()
{
{ "content", "ModelContextProtocol enables structured communication." }
};
// Get the prompt from the server using the specified name and arguments
var prompt = await client.GetPromptAsync(promptName, promptArguments!);
if (prompt == null)
{
Console.WriteLine($"Prompt '{promptName}' not found.");
return;
}
// Print the prompt details
foreach (var message in prompt.Messages)
{
Console.WriteLine($"Message Role: {message.Role}, Content: {message.Content?.Text}");
}
// List available tools from the server
foreach (var to in await client.ListToolsAsync())
{
Console.WriteLine($"Available tool: {to.Name} - {to.Description}");
}
// Specify the tool you want to use
var toolName = "GetCurrentTime";
// Create a dictionary of arguments for the tool
IReadOnlyDictionary<string, object> toolArguments = new Dictionary<string, object>()
{
{ "timeZone", "Pacific Standard Time" } // Example time zone
};
// Call tool from the server using the specified name and arguments
var result = await client.CallToolAsync(toolName, toolArguments!);
if (result == null)
{
Console.WriteLine($"Tool '{toolName}' not found.");
return;
}
// Print the tool result
foreach (var res in result.Content)
{
Console.WriteLine($"Tool Result: {res.Text}");
}
// List available resources from the server
foreach (var res in await client.ListResourcesAsync())
{
Console.WriteLine($"Available resource: {res.Name} - {res.Description}");
}
// Specify the resource you want to use
var resourceName = "resource://GetBingEndpoint";
// Get the resource from the server using the specified name
var resource = await client.ReadResourceAsync(resourceName);
if (resource == null)
{
Console.WriteLine($"Resource '{resourceName}' not found.");
return;
}
// Print the resource details
foreach (var res in resource.Contents)
{
Console.WriteLine($"Resource Content: {((TextResourceContents)res)?.Text}");
}
}
catch (Exception ex)
{
Console.WriteLine("Error creating MCP client: " + ex.Message);
return;
}
总结
MCP 为 AI 集成带来了更高水平的一致性和模块化能力。通过使用资源(Resources)、提示词(Prompts)和工具(Tools)这一简洁而强大的抽象机制,结合清晰的客户端-服务器模型,开发者可以构建出能够与外部世界无缝交互的智能代理。
了解更多:
- C# SDK
- 示例解决方案
