Net+AI智能体进阶3:MCP大模型的外挂商店
2025-10-18 12:28:24一、MCP 协议基础
1. 协议概述
MCP 是一个基于 JSON-RPC 2.0 的应用层协议,专为 AI 应用程序设计。它定义了:
- 标准化消息格式:统一的请求/响应/通知结构
- 能力协商机制:Client 和 Server 协商支持的功能
- 双向通信:支持请求-响应和异步通知
- 错误处理:标准化的错误代码和异常处理
MCP 协议栈
MCP 协议建立在 JSON-RPC 2.0 之上,提供了额外的语义和约定:
┌─────────────────────────────────────────┐
│ AI Application Layer │ ← 使用 MCP 的应用
├─────────────────────────────────────────┤
│ MCP Protocol Layer (Application) │ ← MCP 协议语义
│ (Tools, Resources, Prompts, etc.) │
├─────────────────────────────────────────┤
│ JSON-RPC 2.0 Layer │ ← 基础 RPC 协议
├─────────────────────────────────────────┤
│ Transport Layer (Stdio/HTTP) │ ← 传输机制
└─────────────────────────────────────────┘
核心设计原则
| 原则 | 说明 | 示例 |
|---|---|---|
| 标准化 | 统一的协议格式 | 所有工具调用使用 tools/call 方法 |
| 协商式 | 能力协商机制 | Client 和 Server 协商支持的功能 |
| 双向通信 | 支持请求和通知 | Server 可以主动发送日志通知 |
| 类型安全 | JSON Schema 验证 | 参数和返回值都有明确的类型定义 |
| 可扩展 | 支持自定义扩展 | 可以添加自定义 Capabilities |
MCP vs 其他协议
| 协议 | 用途 | MCP 的优势 |
|---|---|---|
| REST API | 通用 Web 服务 | MCP 专为 AI 上下文设计,支持工具发现和类型安全 |
| gRPC | 高性能 RPC | MCP 更简单,基于 JSON,易于调试和扩展 |
| WebSocket | 实时双向通信 | MCP 提供标准化的 AI 语义(工具、资源、提示) |
2. 消息类型
MCP 协议定义了三种核心消息类型,它们都基于 JSON-RPC 2.0。
Request 消息
消息结构:
{
"jsonrpc": "2.0",
"id": "1",
"method": "tools/call",
"params": {
"name": "GetCityWeather",
"arguments": {
"city": "北京"
}
}
}
关键字段:
- jsonrpc: 固定为 "2.0"
- id 请求的唯一标识符(用于匹配响应)
- method: 要调用的方法名称
- params: 方法参数(可选)
Response 消息
成功相应:
{
"jsonrpc": "2.0",
"id": "1",
"result": {
"content": [
{
"type": "text",
"text": "北京 的天气:晴朗,温度:22°C"
}
]
}
}
错误响应:
{
"jsonrpc": "2.0",
"id": "1",
"error": {
"code": -32602,
"message": "Invalid params",
"data": {
"details": "城市名称不能为空"
}
}
}
关键字段:
- id: 与原始请求的 id 相同
- result: 成功时返回的结果
- error: 失败时返回的错误信息
Notification (通知)消息
Notification 是不需要响应的单向消息,用于异步事件。
消息格式:
{
"jsonrpc": "2.0",
"method": "notifications/message",
"params": {
"level": "info",
"logger": "WeatherServer",
"data": {
"message": "天气查询服务已启动"
}
}
}
关键特征:
- 没有 id 字段(因为不需要响应)
- 可以由 Server 主动发送
- 用于日志、进度、事件等
典型用途
- notifications/initialized:握手完成通知
- notifications/progress:进度更新通知
- notifications/message:日志消息通知
3. 通信流程
初始化流程
- Client 和 Server 建立连接后,首先进行初始化和能力协商:
sequenceDiagram
participant C as MCP Client
participant S as MCP Server
C->>S: resources/list Request
S-->>C: resources/list Response<br/>[{uri, name, description}]
C->>S: resources/read Request<br/>{uri: "weather://beijing"}
Note over S: 读取资源内容
S-->>C: resources/read Response<br/>{contents: [...]}
- 工具调用请求:
{
"jsonrpc": "2.0",
"id": "3",
"method": "tools/call",
"params": {
"name": "GetCityWeather",
"arguments": {
"city": "北京"
}
}
}
- 工具调用响应
{
"jsonrpc": "2.0",
"id": "3",
"result": {
"content": [
{
"type": "text",
"text": "北京 的天气:晴朗,温度:22°C"
}
]
}
}
资源访问流程
- 资源(Resources)是 Server 提供的上下文数据
sequenceDiagram
participant C as MCP Client
participant S as MCP Server
Note over C: 用户发起查询<br/>"北京天气如何?"
C->>S: tools/call Request<br/>{name: "GetCityWeather", arguments: {city: "北京"}}
Note over S: 执行工具逻辑<br/>生成天气数据
S->>C: notifications/message<br/>(可选) 发送日志
S-->>C: tools/call Response<br/>{content: [{text: "天气信息"}]}
Note over C: 展示结果给用户
- 工具列表请求
{
"jsonrpc": "2.0",
"id": "2",
"method": "tools/list"
}
- 工具列表响应
{
"jsonrpc": "2.0",
"id": "2",
"result": {
"tools": [
{
"name": "GetCityWeather",
"description": "获取指定城市的随机天气信息",
"inputSchema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "要查询天气的城市名称"
}
},
"required": ["city"]
}
}
]
}
}
工具调用流程
- Client 调用 Server 提供的工具
sequenceDiagram
participant C as MCP Client
participant S as MCP Server
C->>S: tools/list Request
Note over S: Server 返回工具列表
S-->>C: tools/list Response<br/>[{name, description, inputSchema}]
Note over C: Client 解析工具信息
Note over C: 用户可以看到可用工具
- 初始化请求示例
{
"jsonrpc": "2.0",
"id": "1",
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"clientInfo": {
"name": "my-client",
"version": "1.0.0"
},
"capabilities": {
"roots": {
"listChanged": true
}
}
}
}
- 初始化响应示例
{
"jsonrpc": "2.0",
"id": "1",
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"clientInfo": {
"name": "my-client",
"version": "1.0.0"
},
"capabilities": {
"roots": {
"listChanged": true
}
}
}
}
工具发现流程
初始化完成后,Client 可以发现 Server 提供的工具
sequenceDiagram
participant C as MCP Client
participant S as MCP Server
Note over C,S: 阶段 1:建立连接
C->>S: 启动 Server 进程(Stdio)<br/>或 HTTP 连接
Note over C,S: 阶段 2:初始化握手
C->>S: initialize Request<br/>{clientInfo, capabilities}
Note over S: Server 处理初始化
S-->>C: initialize Response<br/>{serverInfo, capabilities}
Note over C,S: 阶段 3:确认初始化
C->>S: initialized Notification
Note over C,S: 连接就绪,可以开始工作
4. 能力协商(Capabilities)
Capabilities 是 MCP 协议的核心机制,用于 Client 和 Server 协商支持的功能
为什么需要能力协商?
| 问题 | 解决方案 |
|---|---|
| Client 如何知道 Server 支持哪些功能? | Server 在初始化时声明 Capabilities |
| Server 如何知道 Client 需要哪些功能? | Client 在初始化时声明 Capabilities |
| 协议升级时如何保持兼容性? | 通过 Capabilities 协商新功能的支持 |
Server Capabilities
Server 可以声明的能力:
public class ServerCapabilities
{
/// <summary>
/// Server 是否提供工具(Tools)
/// </summary>
public ToolsCapability? Tools { get; set; }
/// <summary>
/// Server 是否提供资源(Resources)
/// </summary>
public ResourcesCapability? Resources { get; set; }
/// <summary>
/// Server 是否提供提示(Prompts)
/// </summary>
public PromptsCapability? Prompts { get; set; }
/// <summary>
/// Server 是否支持日志(Logging)
/// </summary>
public LoggingCapability? Logging { get; set; }
/// <summary>
/// 实验性功能
/// </summary>
public Dictionary<string, object>? Experimental { get; set; }
}
示例:Server 声明能力
{
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"prompts": {
"listChanged": false
},
"logging": {}
}
}
能力说明:
- tools.listChanged: Server 会通知 Client 工具列表变化
- resources.subscribe: Client 可以订阅资源变化
- resources.listChanged: Server 会通知 Client 资源列表变化
- prompts.listChanged: 提示列表不会动态变化
Client Capabilities
Client 可以声明的能力
public class ClientCapabilities
{
/// <summary>
/// Client 是否支持 Roots(工作空间根目录)
/// </summary>
public RootsCapability? Roots { get; set; }
/// <summary>
/// Client 是否支持 Sampling(采样)
/// </summary>
public SamplingCapability? Sampling { get; set; }
/// <summary>
/// 实验性功能
/// </summary>
public Dictionary<string, object>? Experimental { get; set; }
}
示例:Client 生命能力
{
"capabilities": {
"roots": {
"listChanged": true
},
"sampling": {}
}
}
实践示例
场景:Client 需要订阅资源变化
// Client 声明能力
var clientOptions = new McpClientOptions
{
ClientInfo = new ClientInfo { Name = "my-client", Version = "1.0.0" },
Capabilities = new ClientCapabilities
{
Roots = new RootsCapability { ListChanged = true }
}
};
// 创建 Client
var client = await McpClientFactory.CreateAsync(transport, clientOptions);
// 检查 Server 能力
if (client.ServerCapabilities?.Resources?.Subscribe == true)
{
// Server 支持订阅,可以订阅资源
await client.SubscribeResourceAsync("weather://beijing");
}
else
{
// Server 不支持订阅,使用轮询
while (true)
{
var resource = await client.ReadResourceAsync("weather://beijing");
await Task.Delay(60000); // 每分钟轮询
}
}
sequenceDiagram
participant C as MCP Client
participant S as MCP Server
Note over C: Client 准备连接
Note over C: 决定需要的能力
C->>S: initialize<br/>{clientCapabilities}
Note over S: Server 检查 Client 能力
Note over S: 决定启用的功能
S-->>C: initialized<br/>{serverCapabilities}
Note over C: Client 检查 Server 能力
Note over C: 只使用 Server 支持的功能
Note over C,S: 能力协商完成
二、MCP 概述
1. MCP 核心架构组件
MCP Client(客户端)
职责:
- 发起连接,向 Server 请求能力
- 调用 Server 提供的工具(Tools)
- 读取 Server 暴露的资源(Resources)
- 使用 Server 的提示模板(Prompts)
典型实现:
- AI 应用程序(如 Claude Desktop、Copilot)
- AI Agent 框架(如 Semantic Kernel、LangChain)
- 企业业务系统的 AI 模块
MCP Server(服务器)
职责:
- 暴露能力(Tools、Resources、Prompts)
- 响应客户端请求,执行工具调用
- 提供数据资源和提示模板
- 发送通知(Logging、Progress 等)
典型实现:
- 数据库访问服务
- REST API 包装器
- 文件系统服务
- 业务工具服务(审批、通知等)
Transport(传输层)
职责:
- 在 Client 和 Server 之间传输 JSON-RPC 消息
- 支持多种传输协议
支持的传输方式:
| 传输方式 | 特点 | 适用场景 |
|---|---|---|
| Stdio | 标准输入输出 | 本地进程间通信 |
| HTTP/SSE | HTTP + 服务器推送事件 | 远程网络通信 |
| Custom | 自定义实现 | 特殊需求(如 WebSocket) |
形象比喻:
- Client 就像餐厅的顾客,提出需求
- Server 就像餐厅的厨房,提供服务
- Transport 就像服务员,负责传递信息
2. MCP 能力模型
MCP 定义了五种核心能力,Server 可以选择性地实现这些能力:
Tools(工具)
- 定义:可以被 AI 调用的函数或操作。
- 典型用例:
- 数据库查询:search_database(query: string)
- 文件操作:read_file(path: string)
- API 调用:send_email(to: string, subject: string, body: string)
- 业务操作:approve_request(requestId: string)
- 数据结构(源码参考):
public sealed class Tool
{
public required string Name { get; init; } // 工具名称
public string? Description { get; init; } // 工具描述
public JsonElement? InputSchema { get; init; } // 输入参数的 JSON Schema
}
Resources(资源)
定义:Server 暴露的数据资源,可以是文件、数据库记录、API 响应等。
特点:
只读访问(Read-only)
支持订阅变更通知
支持多种 MIME 类型(text/plain、application/json 等)
典型用例:
文档内容:file:///path/to/doc.txt
数据库记录:db://users/123
API 数据:api://weather/current
数据结构(源码参考):
public sealed class Resource
{
public required string Name { get; init; } // 资源名称
public required Uri Uri { get; init; } // 资源 URI
public string? Description { get; init; } // 资源描述
public string? MimeType { get; init; } // MIME 类型
}
Prompts(提示模板)
定义:预定义的、可参数化的提示模板。
典型用例:
代码审查提示:code_review(language: string, code: string)
翻译提示:translate(text: string, target_lang: string)
摘要提示:summarize(content: string, max_length: int)
数据结构(源码参考):
public sealed class Prompt
{
public required string Name { get; init; } // 提示名称
public string? Description { get; init; } // 提示描述
public IList<PromptArgument>? Arguments { get; init; } // 参数列表
}
Sampling(LLM 采样)
定义:Server 请求 Client 代为调用 LLM 进行推理。
使用场景:
Server 需要 LLM 能力但自己没有模型
需要利用 Client 的模型上下文
Logging(日志记录)
定义:Server 向 Client 发送日志消息。
日志级别:
Debug:调试信息
Info:一般信息
Warning:警告
Error:错误
Critical:严重错误
3. Initialize 握手流程与能力协商
MCP 通信的第一步是 Initialize 握手流程,这是 Client 和 Server 建立连接、协商能力的关键步骤。
sequenceDiagram
participant Client as MCP Client
participant Server as MCP Server
Note over Client,Server: 1. 初始化阶段
Client->>Server: Initialize Request<br/>{protocolVersion, clientInfo, capabilities}
Note over Server: 检查协议版本<br/>准备服务器能力
Server->>Client: Initialize Response<br/>{protocolVersion, serverInfo, capabilities, instructions}
Note over Client,Server: 2. 确认阶段
Client->>Server: Initialized Notification<br/>{}
Note over Server: 连接已建立<br/>可以开始通信
Note over Client,Server: 握手完成
Note over Client,Server: 3. 正常通信
Client->>Server: ListTools Request
Server->>Client: ListTools Response<br/>{tools: [...]}
Client->>Server: CallTool Request<br/>{name, arguments}
Server->>Client: CallTool Response<br/>{result}
三、MCP Client
1. 创建 MCP 客户端连接
MCP 支持两种主要的传输方式:
| 传输方式 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
| StdioClientTransport | 本地进程启动服务器 | 简单、安全、无需网络 | 仅限本地,不支持远程 |
| HttpClientTransport | 远程服务器连接 | 支持远程、易于扩展 | 需要网络,安全需额外配置 |
连接到中国农历日期 MCP Server,关键步骤:
- 创建 StdioClientTransport 实例,指定启动命令和参数
- 使用 McpClient.CreateAsync() 创建客户端(会自动启动服务器进程)
- 自动完成 Initialize 握手流程
// 创建 Stdio 传输实例,连接到中国农历日历服务器
var clientTransport = new StdioClientTransport(new()
{
Name = "Mcp.CN.Calendar",
Command = "dnx", // 使用 dnx 命令启动 NuGet 工具
Arguments = ["Mcp.CN.Calendar@", "--yes"] // @ 表示使用最新版本
});
// 创建 MCP 客户端(会自动启动服务器进程并完成 Initialize 握手)
// 注意:这里不使用 await using,因为我们需要在后续单元格中继续使用客户端
var mcpClient = await McpClient.CreateAsync(clientTransport);
Console.WriteLine("MCP 客户端已创建,服务器进程已启动并完成握手");
// 最佳实践:在完整应用中,应使用 await using 确保资源释放
// await using var mcpClient = await McpClient.CreateAsync(clientTransport);
关于 dnx 命令:
- dnx 是 .NET 10 上内置的命令行工具
- Mcp.CN.Calendar@ 表示运行 NuGet 包 Mcp.CN.Calendar,@ 后可指定版本(省略则使用最新版本)
- --yes 参数表示自动确认安装
- 服务器进程会在客户端释放(Dispose)时自动关闭
Mcp.CN.Calendar 是一个开源的中国农历日历 MCP 服务器,发布在 NuGet 上。详见:https://www.nuget.org/packages/Mcp.CN.Calendar
2. Initialize 握手流程详解
- 握手流程说明:MCP 客户端和服务器之间的连接建立需要经过 Initialize 握手流程:
sequenceDiagram
participant Client as MCP Client
participant Server as MCP Server
Client->>Server: initialize 请求
Note over Client,Server: 发送 ClientCapabilities
Server->>Client: initialize 响应
Note over Client,Server: 返回 ServerCapabilities
Client->>Server: initialized 通知
Note over Server: 握手完成,开始服务
ClientCapabilities 配置:客户端通过 ClientCapabilities 告诉服务器自己支持的功能:
Sampling:是否支持让服务器主动请求 AI 模型
Roots:是否支持根资源列表
Experimental:实验性功能支持
// McpClient.CreateAsync() 中的默认配置:
var clientCapabilities = new ClientCapabilities
{
Sampling = new {}, // 支持 Sampling
Roots = new { ListChanged = true } // 支持 Roots 变化通知
};
ServerCapabilities 读取:服务器通过 ServerCapabilities 告诉客户端自己支持的功能:
Tools:服务器提供的工具列表
Resources:服务器提供的资源列表
Prompts:服务器提供的提示模板
Logging:是否支持日志记录
提示:McpClient.CreateAsync() 已自动完成握手,我们可以直接查询服务器能力。
// 获取工具列表
var tools = await mcpClient.ListToolsAsync();
var firstTool = tools.FirstOrDefault();
if (firstTool != null)
{
new {
工具名称 = firstTool.Name,
描述 = firstTool.Description,
输入Schema = firstTool.JsonSchema
}.Display();
}
3. 手动调用 MCP 工具
调用 MCP 工具需要:
指定工具名称(toolName)
提供工具参数(arguments,符合工具的 InputSchema)
使用 CallToolAsync() 方法调用
查询今天的农历信息
try
{
Console.WriteLine("调用工具: get_holidays_by_date");
// 准备工具参数
var arguments = new Dictionary<string, object>
{
["date"] = DateTime.Now.ToString("yyyy-MM-dd")
};
// 调用工具
var result = await mcpClient.CallToolAsync("get_holidays_by_date", arguments);
Console.WriteLine("工具调用成功\n");
result.Display();
}
catch (Exception ex)
{
Console.WriteLine("工具调用失败:");
new {
错误类型 = ex.GetType().Name,
错误消息 = ex.Message
}.Display();
}
调用更多工具:我们继续尝试查询节假日信息
try
{
Console.WriteLine("调用工具: get_next_holiday");
var result = await mcpClient.CallToolAsync("get_next_holiday");
Console.WriteLine("工具调用成功\n");
result.Display();
}
catch (Exception ex)
{
Console.WriteLine("工具调用失败:");
new {
错误类型 = ex.GetType().Name,
错误消息 = ex.Message
}.Display();
}
4. 与 AI 模型集成
集成原理:MCP 的核心价值在于将工具无缝集成到 AI 模型的对话中:
sequenceDiagram
participant User as 用户
participant AI as AI 模型
participant MCP as MCP Client
participant Server as MCP Server
User->>AI: "今天是农历几月几日?"
AI->>AI: 决定调用工具
AI->>MCP: 请求调用 get-lunar-calendar
MCP->>Server: CallTool(get-lunar-calendar)
Server->>MCP: 返回农历信息
MCP->>AI: 返回工具结果
AI->>User: "今天是农历腊月..."
- 将 MCP 工具转换为 AI 工具
// 将 MCP 工具列表转换为 AI 工具
var aiTools = tools.Cast<AITool>();
- 配置 ChatOpions
// 配置 ChatOptions,启用 Function Invocation
var chatOptions = new ChatOptions
{
Tools = [..aiTools],
ToolMode = ChatToolMode.Auto // 自动决定是否调用工具
};
- 单轮对话测试
// 使用 FunctionInvocation 中间件自动处理工具调用
var aiClient = chatClient
.AsBuilder()
.UseFunctionInvocation()
.Build();
try
{
Console.WriteLine("用户问题: 今天是农历几月几日?\n");
var response = await aiClient.GetResponseAsync(
"今天是农历几月几日?",
chatOptions
);
Console.WriteLine("AI 回答:");
Console.WriteLine(response.Text);
}
catch (Exception ex)
{
Console.WriteLine("请求失败:");
new {
错误类型 = ex.GetType().Name,
错误消息 = ex.Message
}.Display();
}
5. 高级场景与最佳实践
- StdioClientTransport 的使用:对于本地 MCP 服务器,使用 stdio 传输更高效。适用场景:
- 本地开发和测试
- 单机部署的 AI 应用
- 需要启动子进程的场景
- 使用 NuGet 工具包形式发布的 MCP 服务器
// 示例1. 启动 .NET 项目作为 MCP 服务器
var clientTransport = new StdioClientTransport(new()
{
Name = "Demo Server",
Command = "dotnet",
Arguments = ["run", "--project", "path/to/server"]
});
// 示例 2:使用 NuGet 工具包(如本课程的日历服务器)
var clientTransport = new StdioClientTransport(new()
{
Name = "Mcp.CN.Calendar",
Command = "dnx", // dnx 是 dotnet tool 的便捷启动器
Arguments = ["Mcp.CN.Calendar@", "--yes"] // @ 后可指定版本号
});
// 示例 3:启动 Node.js MCP 服务器
var clientTransport = new StdioClientTransport(new()
{
Name = "Node Server",
Command = "node",
Arguments = ["path/to/server.js"]
});
- HttpClientTransport 的高级配置。适合场景:
- 远程 MCP 服务器
- 需要身份验证的场景
- 云端部署的服务
var clientTransport = new HttpClientTransport(new()
{
Endpoint = new Uri("https://api.example.com/mcp"),
Headers = new Dictionary<string, string>
{
["Authorization"] = "Bearer YOUR_TOKEN",
["X-Custom-Header"] = "value"
}
});
- 资源管理最佳实践
// 使用 using 确保资源正确释放
await using var mcpClient = await McpClient.CreateAsync(clientTransport);
// ... 使用客户端 ...
// 自动调用 Dispose,关闭连接
错误处理策略。常见错误类型:
HttpRequestException:网络连接问题
TaskCanceledException:请求超时
JsonException:JSON 序列化/反序列化错误
McpException:MCP 协议错误
// 推荐处理方式:
try
{
var result = await mcpClient.CallToolAsync("tool-name", arguments);
}
catch (HttpRequestException ex)
{
// 重试或提示网络错误
}
catch (TaskCanceledException ex)
{
// 提示超时
}
catch (Exception ex)
{
// 记录日志,返回友好错误消息
}
6. 性能优化建议
- 工具列表缓存
// 避免重复调用 ListToolsAsync
// 缓存结果,复用
var tools = await mcpClient.ListToolsAsync();
- 并发调用限制
// 使用 SemaphoreSlim 限制并发数
var semaphore = new SemaphoreSlim(5); // 最多 5 个并发
- 连接池管理:
- 对于 HTTP 传输,复用 HttpClient
- 避免频繁创建和销毁连接
四、MCP Server
1. 理解 MCP Server 的基本结构
核心组件
graph TB
Server[MCP Server] --> Tools[Tools 工具]
Server --> Resources[Resources 资源]
Server --> Prompts[Prompts 提示词]
Server --> Transport[Transport 传输层]
Tools --> Tool1[天气查询]
Tools --> Tool2[计算器]
Resources --> Res1[文本资源]
Resources --> Res2[文件资源]
Prompts --> Prompt1[简单提示]
Prompts --> Prompt2[参数化提示]
Transport --> Stdio[Stdio]
Transport --> HTTP[HTTP/SSE]
Tools(工具)
作用:提供可被 AI 调用的功能函数
示例:查询天气、执行计算、搜索数据库等
标记:使用 [McpServerTool] 特性
Resources(资源)
作用:提供静态或动态的数据资源
示例:文本文档、配置文件、数据库内容等
标记:使用 [McpServerResource] 特性
Prompts(提示词模板)
作用:提供可参数化的提示词模板
示例:代码审查模板、翻译模板等
标记:使用 [McpServerPrompt] 特性
Transport(传输层)
Stdio:标准输入/输出,适合本地进程通信
HTTP/SSE:适合远程服务和 Web 集成
2. 创建最简单的 MCP Server
核心要点:
- AddMcpServer() - 注册 MCP Server 服务
- WithStdioServerTransport() - 配置传输层(Stdio)
- WithTools
() - 注册工具类
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
// 创建主机构建器
var builder = Host.CreateApplicationBuilder(args);
// 添加 MCP Server 服务
builder.Services
.AddMcpServer() // 注册 MCP Server
.WithStdioServerTransport() // 使用 Stdio 传输
.WithTools<WeatherTools>(); // 注册 Tools 类
// 构建并运行
await builder.Build().RunAsync();
3. 实现 MCP Tools
Tools 是 MCP Server 最核心的功能,让我们实现一个完整的 WeatherTools 类。
- 定义 Tools 类,核心要点:
- 类级别特性:[McpServerToolType] 标记工具类
- 方法级别特性:[McpServerTool] 标记具体工具方法
- 描述特性:[Description] 为工具和参数添加说明
- 静态方法:工具方法通常定义为 static
/// <summary>
/// 简单的计算器工具类
/// 使用 [McpServerToolType] 标记这是一个 MCP 工具类
/// </summary>
[McpServerToolType]
public sealed class CalculatorTools
{
/// <summary>
/// 加法工具
/// </summary>
[McpServerTool]
[Description("Add two numbers together.")]
public static int Add([Description("First number")] int a, [Description("Second number")] int b)
{
return a + b;
}
/// <summary>
/// 乘法工具
/// </summary>
[McpServerTool]
[Description("Multiply two numbers.")]
public static int Multiply([Description("First number")] int a, [Description("Second number")] int b)
{
return a * b;
}
}
4. 实现复杂的 Tools(异步 + 依赖注入)
核心要点:
- 异步方法:返回 Task
支持异步操作 - 参数描述:使用 [Description] 帮助 AI 理解参数用途
- 返回格式:通常返回 JSON 字符串便于解析
/// <summary>
/// 天气查询工具(模拟版本)
/// 演示异步方法和参数使用
/// </summary>
[McpServerToolType]
public sealed class SimpleWeatherTools
{
/// <summary>
/// 获取指定城市的天气信息(模拟数据)
/// </summary>
[McpServerTool]
[Description("Get current weather for a city.")]
public static async Task<string> GetWeather([Description("City name (e.g., Beijing, Shanghai)")] string city)
{
// 模拟 API 调用延迟
await Task.Delay(100);
// 模拟返回天气数据
var weatherData = new
{
City = city,
Temperature = Random.Shared.Next(15, 35),
Condition = new[] { "Sunny", "Cloudy", "Rainy" }[Random.Shared.Next(3)],
Humidity = Random.Shared.Next(40, 90)
};
return JsonSerializer.Serialize(weatherData, new JsonSerializerOptions
{
WriteIndented = true
});
}
}
5. 实现 MCP Resources(资源)
简单的文本资源,核心要点:
- 类级别特性:[McpServerResourceType] 标记资源类
- 方法级别特性:[McpServerResource] 定义资源
- UriTemplate:资源的唯一标识(如 doc://welcome)
- MimeType:资源的内容类型(text/plain、application/json 等)
- 简单返回:直接返回字符串作为资源内容
/// <summary>
/// 简单的资源提供者
/// 使用 [McpServerResourceType] 标记这是一个 MCP 资源类
/// </summary>
[McpServerResourceType]
public sealed class SimpleResources
{
/// <summary>
/// 直接返回文本资源
/// </summary>
[McpServerResource(
UriTemplate = "doc://welcome",
Name = "Welcome Document",
MimeType = "text/plain")]
[Description("A welcome document")]
public static string WelcomeDocument()
{
return "Welcome to MCP Server! This is a simple text resource.";
}
/// <summary>
/// 返回 JSON 格式的配置资源
/// </summary>
[McpServerResource(
UriTemplate = "config://app",
Name = "App Configuration",
MimeType = "application/json")]
[Description("Application configuration")]
public static string AppConfig()
{
var config = new
{
AppName = "MCP Demo",
Version = "1.0.0",
Features = new[] { "Tools", "Resources", "Prompts" }
};
return JsonSerializer.Serialize(config, new JsonSerializerOptions
{
WriteIndented = true
});
}
}
模板化资源(带参数),核心要点
- URI 模板参数:使用 定义动态参数
- RequestContext:通过上下文访问请求信息
- ResourceContents:返回 TextResourceContents 或 BlobResourceContents
- 参数绑定:方法参数自动从 URI 中提取
URI 模板示例:
- user://profile/ → 匹配 user://profile/1、user://profile/123 等
- 参数 id 会自动传递给方法的 int id 参数
/// <summary>
/// 模板化资源提供者
/// 演示带参数的资源
/// </summary>
[McpServerResourceType]
public sealed class TemplateResources
{
/// <summary>
/// 使用 URI 模板参数的资源
/// </summary>
[McpServerResource(
UriTemplate = "user://profile/{id}",
Name = "User Profile",
MimeType = "application/json")]
[Description("Get user profile by ID")]
public static ResourceContents GetUserProfile(
RequestContext<ReadResourceRequestParams> requestContext,
int id)
{
// 模拟用户数据
var userData = new
{
Id = id,
Name = $"User{id}",
Email = $"user{id}@example.com",
Role = id % 2 == 0 ? "Admin" : "User"
};
var json = JsonSerializer.Serialize(userData, new JsonSerializerOptions
{
WriteIndented = true
});
return new TextResourceContents
{
Text = json,
MimeType = "application/json",
Uri = $"user://profile/{id}"
};
}
}
6. 实现 MCP Prompts(提示词模版)
简单的提示词模版,核心要点:
- 类级别特性:[McpServerPromptType] 标记提示词类
- 方法级别特性:[McpServerPrompt] 定义提示词
- Name 属性:提示词的唯一标识
- 简单返回:直接返回字符串作为提示词内容
/// <summary>
/// 简单的提示词模板
/// 使用 [McpServerPromptType] 标记这是一个 MCP 提示词类
/// </summary>
[McpServerPromptType]
public sealed class SimplePrompts
{
/// <summary>
/// 无参数的简单提示词
/// </summary>
[McpServerPrompt(Name = "welcome_prompt")]
[Description("A simple welcome prompt")]
public static string WelcomePrompt()
{
return "You are a helpful AI assistant. Please be friendly and professional.";
}
/// <summary>
/// 代码审查提示词
/// </summary>
[McpServerPrompt(Name = "code_review")]
[Description("A prompt for code review")]
public static string CodeReviewPrompt()
{
return @"You are an expert code reviewer. Please review the following code and provide:
1. Code quality assessment
2. Potential bugs or issues
3. Suggestions for improvement
4. Best practices recommendations";
}
}
参数化的提示词模板,核心要点:
- 方法参数:定义提示词的可配置参数
- 返回类型:返回 IEnumerable
集合 - ChatMessage:使用 Microsoft.Extensions.AI 的消息类型
- 默认参数:可以设置参数默认值(如 style = "functional")
参数化提示词的优势:
- 可复用:同一个模板支持多种场景
- 灵活性:通过参数控制提示词内容
- 标准化:统一管理常用提示词
/// <summary>
/// 参数化的提示词模板
/// </summary>
[McpServerPromptType]
public sealed class ParameterizedPrompts
{
/// <summary>
/// 翻译提示词(带参数)
/// </summary>
[McpServerPrompt(Name = "translate")]
[Description("A prompt for translation with language parameters")]
public static IEnumerable<ChatMessage> TranslatePrompt(
[Description("Source language")] string from,
[Description("Target language")] string to)
{
var systemMessage = $"You are a professional translator. Translate the following text from {from} to {to}.";
return
[
new ChatMessage(ChatRole.System, systemMessage),
new ChatMessage(ChatRole.User, "Please provide the text you want to translate.")
];
}
/// <summary>
/// 代码生成提示词(带参数)
/// </summary>
[McpServerPrompt(Name = "code_generation")]
[Description("A prompt for code generation with language and style parameters")]
public static IEnumerable<ChatMessage> CodeGenerationPrompt(
[Description("Programming language (e.g., C#, Python)")] string language,
[Description("Code style (e.g., functional, object-oriented)")] string style = "functional")
{
var systemMessage = $@"You are an expert {language} programmer.
Generate clean, well-documented {language} code following {style} programming paradigm.
Requirements:
- Write clean and readable code
- Include comments and documentation
- Follow best practices
- Handle errors appropriately";
return
[
new ChatMessage(ChatRole.System, systemMessage),
new ChatMessage(ChatRole.User, "Please describe what you want to implement.")
];
}
}
7. 完整的 MCP Server 示例
现在让我们将所有组件整合在一起,创建一个完整的 MCP Server。
var builder = Host.CreateApplicationBuilder(args);
// 配置日志输出到 stderr
builder.Logging.AddConsole(options =>
{
options.LogToStandardErrorThreshold = LogLevel.Trace;
});
// 添加 MCP Server 服务并注册所有组件
builder.Services
.AddMcpServer()
.WithStdioServerTransport() // 使用 Stdio 传输
.WithTools<CalculatorTools>() // 注册计算器工具
.WithTools<SimpleWeatherTools>() // 注册天气工具
.WithResources<SimpleResources>() // 注册简单资源
.WithResources<TemplateResources>() // 注册模板资源
.WithPrompts<SimplePrompts>() // 注册简单提示词
.WithPrompts<ParameterizedPrompts>(); // 注册参数化提示词
// 构建并运行服务器
await builder.Build().RunAsync();
也可以使用以下扩展方式批量注册程序集中的Tools、Resources 和 Prompts:
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly() // 自动注册当前程序集中的所有 Tools
.WithToolsFromAssembly(typeof(WeatherTools).Assembly) // 注册指定程序集中的 Tools
.WithResourcesFromAssembly(typeof(SimpleResources).Assembly) // 注册指定程序集中的 Resources
.WithPromptsFromAssembly(typeof(SimplePrompts).Assembly); // 注册指定程序集中的 Prompts
服务器架构说明
flowchart TB
Client[MCP Client] <--> Transport[Stdio Transport]
Transport <--> Server[MCP Server]
Server --> Tools[Tools 层]
Server --> Resources[Resources 层]
Server --> Prompts[Prompts 层]
Tools --> Tool1[CalculatorTools]
Tools --> Tool2[SimpleWeatherTools]
Resources --> Res1[SimpleResources]
Resources --> Res2[TemplateResources]
Prompts --> Prompt1[SimplePrompts]
Prompts --> Prompt2[ParameterizedPrompts]
Server --> DI[依赖注入容器]
DI --> Logger[Logger]
DI --> HttpClient[HttpClient]
推荐注册顺序:
builder.Services.AddMcpServer()
// 1.首先配置传输层
.WithStdioServerTransport()
// 2.然后注册 Tools(按功能分组)
.WithTools<CalculatorTools>()
.WithTools<WeatherTools>()
// 3.注册 Resources(按类型分组)
.WithResources<SimpleResources>()
.WithResources<TemplateResources>()
// 4.最后注册 Prompts(按用途分组)
.WithPrompts<SimplePrompts>()
.WithPrompts<ParameterizedPrompts>();
最佳实践建议:
- 分类组织:将相关的 Tools、Resources、Prompts 放在不同的类中
- 命名规范:使用清晰的命名(如 WeatherTools、UserResources)
- 日志配置:配置日志输出到 stderr,避免干扰 stdio 通信
- 依赖注入:通过 DI 注入 HttpClient、数据库上下文等依赖
8. 高级场景与最佳实践
- 错误处理
[McpServerTool]
[Description("Get weather data")]
public static async Task<string> GetWeather(HttpClient client, string city)
{
try
{
var response = await client.GetStringAsync($"/api/weather/{city}");
return response;
}
catch (HttpRequestException ex)
{
// 抛出 McpException 会被正确传递给客户端
throw new McpException($"Failed to fetch weather: {ex.Message}");
}
}
- 资源的 Mime Type 选择
| MimeType | 用途 | 示例 |
|---|---|---|
| text/plain | 纯文本 | 日志、配置文件 |
| application/json | JSON 数据 | API 响应、配置 |
| text/markdown | Markdown 文档 | 文档、帮助信息 |
| application/octet-stream | 二进制数据 | 图片、文件 |
性能优化推荐做法:
使用异步方法(async/await)
复用 HttpClient 实例(通过 DI 注入)
合理设置超时时间
缓存频繁访问的资源
避免
在方法内创建 HttpClient 实例
长时间阻塞的同步操作
未处理的异常
输入验证
[McpServerTool]
public static string ReadFile(string path)
{
// 验证路径,防止路径遍历攻击
if (path.Contains(".."))
{
throw new McpException("Invalid path");
}
// 限制访问范围
var basePath = "/safe/directory";
var fullPath = Path.Combine(basePath, path);
return File.ReadAllText(fullPath);
}
9. 构建企业级知识库服务器
场景说明:创建一个知识库管理服务器,提供:
- 文档搜索工具:搜索文档、获取统计信息
- 文档内容资源:文档内容、文档目录
- 问答提示词模板:问答模板、摘要模板
/// <summary>
/// 知识库工具类
/// </summary>
[McpServerToolType]
public sealed class KnowledgeBaseTools
{
private static readonly Dictionary<string, string> _documents = new()
{
["doc1"] = "MCP (Model Context Protocol) 是一个开放协议...",
["doc2"] = "C# 是一种类型安全的面向对象编程语言...",
["doc3"] = "依赖注入是一种设计模式..."
};
[McpServerTool]
[Description("Search documents in knowledge base")]
public static async Task<string> SearchDocuments(
[Description("Search keyword")] string keyword)
{
await Task.Delay(50); // 模拟搜索延迟
var results = _documents
.Where(d => d.Value.Contains(keyword, StringComparison.OrdinalIgnoreCase))
.Select(d => new { DocumentId = d.Key, Preview = d.Value[..Math.Min(50, d.Value.Length)] + "..." })
.ToList();
return JsonSerializer.Serialize(results, new JsonSerializerOptions
{
WriteIndented = true
});
}
[McpServerTool]
[Description("Get document statistics")]
public static string GetStatistics()
{
var stats = new
{
TotalDocuments = _documents.Count,
TotalCharacters = _documents.Values.Sum(v => v.Length),
AvgLength = _documents.Values.Average(v => v.Length)
};
return JsonSerializer.Serialize(stats, new JsonSerializerOptions
{
WriteIndented = true
});
}
}
/// <summary>
/// 知识库资源类
/// </summary>
[McpServerResourceType]
public sealed class KnowledgeBaseResources
{
private static readonly Dictionary<string, string> _documents = new()
{
["doc1"] = "MCP (Model Context Protocol) 是一个开放协议,用于连接 AI 应用与数据源。",
["doc2"] = "C# 是一种类型安全的面向对象编程语言,广泛应用于企业级应用开发。",
["doc3"] = "依赖注入是一种设计模式,用于实现控制反转(IoC)。"
};
[McpServerResource(
UriTemplate = "kb://document/{id}",
Name = "Knowledge Base Document",
MimeType = "text/plain")]
[Description("Get document content by ID")]
public static ResourceContents GetDocument(
RequestContext<ReadResourceRequestParams> requestContext,
string id)
{
if (!_documents.TryGetValue(id, out var content))
{
throw new McpException($"Document not found: {id}");
}
return new TextResourceContents
{
Text = content,
MimeType = "text/plain",
Uri = $"kb://document/{id}"
};
}
[McpServerResource(
UriTemplate = "kb://catalog",
Name = "Document Catalog",
MimeType = "application/json")]
[Description("Get list of all available documents")]
public static string GetCatalog()
{
var catalog = _documents.Keys.Select(id => new { DocumentId = id });
return JsonSerializer.Serialize(catalog, new JsonSerializerOptions
{
WriteIndented = true
});
}
}
/// <summary>
/// 知识库提示词类
/// </summary>
[McpServerPromptType]
public sealed class KnowledgeBasePrompts
{
[McpServerPrompt(Name = "qa_prompt")]
[Description("Question answering prompt using knowledge base")]
public static IEnumerable<ChatMessage> QuestionAnsweringPrompt(
[Description("Question to answer")] string question,
[Description("Document IDs to reference")] string documentIds = "doc1,doc2,doc3")
{
var systemMessage = $@"You are a helpful assistant that answers questions based on the knowledge base.
Available Documents: {documentIds}
Instructions:
1. Search for relevant information in the knowledge base
2. Provide accurate answers based on the documents
3. Cite the document ID when providing information
4. If information is not in the knowledge base, say so clearly";
return
[
new ChatMessage(ChatRole.System, systemMessage),
new ChatMessage(ChatRole.User, question)
];
}
[McpServerPrompt(Name = "summarize_prompt")]
[Description("Summarize document content")]
public static IEnumerable<ChatMessage> SummarizePrompt(
[Description("Document ID to summarize")] string documentId)
{
var systemMessage = @"You are a professional document summarizer.
Create a concise summary that captures the key points of the document.";
return
[
new ChatMessage(ChatRole.System, systemMessage),
new ChatMessage(ChatRole.User, $"Please summarize the content of document {documentId}.")
];
}
}
完整的知识库服务器配置,这个知识库服务器提供:
- 2 个工具:搜索文档、获取统计信息
- 2 个资源:文档内容、文档目录
- 2 个提示词:问答模板、摘要模板
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
var builder = Host.CreateApplicationBuilder(args);
// 配置日志
builder.Logging.AddConsole(options =>
{
options.LogToStandardErrorThreshold = LogLevel.Information;
});
// 配置 MCP Server
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithTools<KnowledgeBaseTools>()
.WithResources<KnowledgeBaseResources>()
.WithPrompts<KnowledgeBasePrompts>();
// 运行服务器
await builder.Build().RunAsync();
五、日志与采样
前面介绍了 Client 请求 Server 的单向通信模式(工具调用、资源访问等)。但在实际应用中,Server 也需要主动向 Client 发起请求,比如调用 LLM 能力或推送日志信息。接下来我们再来聊聊 MCP 的双向通信机制,包括:
- Sampling(采样):Server 调用 Client 的 LLM 能力进行智能处理
- Logging(日志):Server 向 Client 推送实时日志,支持动态级别控制
| 功能 | 方向 | 用途 | 示例 |
|---|---|---|---|
| Tools/Resources | Client → Server | 访问 Server 能力 | 调用天气查询工具 |
| Sampling | Server → Client | 调用 Client 的 LLM | Server 请求 AI 总结数据 |
| Logging | Server → Client | 推送日志信息 | Server 发送调试日志 |
1. Sampling 采样
Sampling 是 MCP 中 Server 调用 Client 的 LLM 能力的机制。通过 Sampling,Server 可以:
- 让 AI 处理工具执行结果(总结、翻译、格式化)
- 请求 AI 辅助决策
- 生成智能内容
Sampling 工作流程:
sequenceDiagram
participant Client as Client<br/>(有 LLM)
participant Server as Server<br/>(需要 AI)
Client->>Server: ① 调用工具
Server->>Server: ② 处理数据
Server->>Client: ③ 发送 Sampling 请求
Client->>Client: ④ 调用 LLM
Client->>Server: ⑤ 返回 AI 结果
Server->>Client: ⑥ 返回最终结果
Note over Client,Server: Server 可以在工具内部调用 Client 的 LLM
- 创建支持 Sampling 的工具。核心要点:
- McpServer.SampleAsync():Server 调用 Client 的 LLM 能力
- CreateMessageRequestParams:Sampling 请求参数
- Messages:对话消息列表
- SystemPrompt:系统提示词
- MaxTokens:最大生成 Token 数
- Temperature:生成温度(0-1,越高越随机)
- 返回结果:CreateMessageResul` 包含 AI 生成的内容
using System.Threading;
using System.ComponentModel;
/// <summary>
/// 智能天气工具 - 使用 Sampling 调用 Client 的 LLM
/// </summary>
[McpServerToolType]
public class SmartWeatherTool
{
[McpServerTool(Name = "smart_weather")]
[Description("查询天气并生成个性化建议")]
public static async Task<string> GetSmartWeather(
McpServer server,
[Description("城市名称")] string city,
CancellationToken cancellationToken)
{
// 1. 模拟查询天气数据
var weatherData = $"{city}:晴天,温度 22°C,湿度 60%,空气质量良好";
Console.WriteLine($"天气数据: {weatherData}");
// 2. 使用 Sampling 调用 Client 的 LLM 生成建议
Console.WriteLine("请求 AI 生成个性化建议...");
var samplingResult = await server.SampleAsync(
new CreateMessageRequestParams
{
Messages = [
new SamplingMessage
{
Role = Role.User,
Content = new TextContentBlock
{
Text = $"根据以下天气信息,生成简短的出行建议(50字以内):{weatherData}"
}
}
],
SystemPrompt = "你是一个友好的天气助手,提供实用的出行建议。",
MaxTokens = 100,
Temperature = 0.7f
},
cancellationToken
);
// 3. 提取 AI 生成的内容
var aiSuggestion = (samplingResult.Content as TextContentBlock)?.Text ?? "无建议";
Console.WriteLine($"AI 建议: {aiSuggestion}");
// 4. 返回组合结果
return $"【天气】{weatherData}\n\n【AI 建议】{aiSuggestion}";
}
}
配置 Server 和 Client(使用 InMemoryTransport),我们使用 InMemoryTransport 在同一进程中创建 Client 和 Server。关键配置:
Client 必须配置 Sampling 能力,Server 才能调用
Client 需要配置 ChatClient,用于处理 Sampling 请求
var chatClient = AIClientHelper.GetDefaultChatClient();
var mcpClientOptions = new McpClientOptions
{
ClientInfo = new Implementation { Name = "SamplingClient", Version = "1.0" },
Capabilities = new ClientCapabilities
{
Sampling = new SamplingCapability() // 启用 Sampling 能力
},
Handlers = new (){
SamplingHandler = chatClient.CreateSamplingHandler()
}
};
var tools = McpHelper.GetToolsForType<SmartWeatherTool>();
var (mcpClient, mcpServer) = await McpHelper.CreateInMemoryClientAndServerAsync(tools,
clientOptions: mcpClientOptions);
- 测试 Sampling 功能。现在调用工具,观察 Server 如何调用 Client 的 LLM。观察要点:
- 天气数据是 Server 本地生成的
- AI 建议是 Server 通过 Sampling 调用 Client 的 LLM 生成的
- 最终结果是两者的组合
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine(" 测试 Sampling 功能 ");
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
try
{
// 调用工具
var result = await mcpClient.CallToolAsync("smart_weather", new Dictionary<string, object?> { ["city"] = "北京" });
Console.WriteLine("\n工具调用成功!\n");
Console.WriteLine("完整结果:");
result.Display();
}
catch (Exception ex)
{
Console.WriteLine($"错误: {ex.Message}");
new { 错误类型 = ex.GetType().Name, 详细信息 = ex.Message }.Display();
}
执行流程分析:
sequenceDiagram
participant C as Client
participant S as Server
participant LLM as LLM (ChatClient)
C->>S: CallTool("smart_weather", {city:"北京"})
S->>S: 查询天气数据
S->>C: SampleAsync(请求 AI 建议)
C->>LLM: CompleteAsync(生成建议)
LLM->>C: 返回 AI 建议
C->>S: 返回 Sampling 结果
S->>C: 返回工具结果
Note over C,S: Server 在工具内部调用了 Client 的 LLM
2. Logging 日志
Logging 是 MCP 中 Server 向 Client 推送日志信息的机制。通过 Logging,Server 可以:
- 实时推送运行日志
- 支持动态日志级别控制
- 方便调试和监控
| 级别 | 名称 | 用途 | 示例 |
|---|---|---|---|
| 0 | Debug | 调试信息 | 变量值、执行步骤 |
| 1 | Info | 一般信息 | 工具调用、状态变化 |
| 2 | Notice | 重要提示 | 配置变更、关键节点 |
| 3 | Warning | 警告信息 | 性能问题、即将过期 |
| 4 | Error | 错误信息 | 工具失败、数据异常 |
| 5 | Critical | 严重错误 | 系统崩溃、数据丢失 |
| 6 | Alert | 紧急警报 | 安全问题、紧急维护 |
| 7 | Emergency | 紧急情况 | 系统不可用 |
- 创建支持 Logging 的工具
[McpServerToolType]
public class LoggingTools
{
[McpServerTool(Name = "LoggingTool"), Description("Demonstrates a tool that produces log messages")]
public static async Task<string> LoggingTool(
RequestContext<CallToolRequestParams> context)
{
var progressToken = context.Params?.ProgressToken;
// <snippet_LoggingConfiguration >
ILoggerProvider loggerProvider = context.Server.AsClientLoggerProvider();
ILogger logger = loggerProvider.CreateLogger("LoggingTools");
// </snippet_LoggingConfiguration>
try
{
logger.LogCritical("A critial log message");
logger.LogError("An error log message");
logger.LogWarning("A warning log message");
logger.LogInformation("An informational log message");
logger.LogDebug("A debug log message");
logger.LogTrace("A trace log message");
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred while logging messages");
}
return $"Long running tool completed. ";
}
}
- 配置新的 Server (支持日志级别控制)
实现 SetLoggingLevel 处理器,让 Client 可以动态控制日志级别。
private static LoggingLevel? _minimumLoggingLevel = null;
var mcpServerOptions = new McpServerOptions
{
Handlers = new ()
{
SetLoggingLevelHandler = async (context, cancellationToken) =>
{
if (context.Params?.Level is null)
{
throw new McpProtocolException("Missing required argument 'level'", McpErrorCode.InvalidParams);
}
_minimumLoggingLevel = context.Params.Level;
// 推送确认消息
await context.Server.SendNotificationAsync("notifications/message", new LoggingMessageNotificationParams
{
Level = LoggingLevel.Info,
Logger = "System",
Data = System.Text.Json.JsonSerializer.SerializeToElement($"日志级别已设置为: {_minimumLoggingLevel}"),
}, cancellationToken: cancellationToken);
return new EmptyResult();
}
}
};
配置 Client(接收日志通知)。Client 需要配置日志通知处理器,用于接收 Server 推送的日志。
一种是通过 McpClientOptions 指定 NotificationHandler。
另一种是通过 mcpClient.RegisterNotificationHandler 注册通知处理器。
下面采用第一种方式:
using System.Text.Json;
// 存储接收到的日志
var receivedLogs = new List<string>();
var mcpClientOptions = new McpClientOptions
{
ClientInfo = new Implementation { Name = "LoggingClient", Version = "1.0" },
Capabilities = new ClientCapabilities(),
Handlers = new ()
{
// 配置日志消息通知处理器
NotificationHandlers = [
new (NotificationMethods.LoggingMessageNotification, (notification, cancellationToken) =>
{
if (JsonSerializer.Deserialize<LoggingMessageNotificationParams>(notification.Params) is { } ln)
{
var logEntry = $"[{ln.Level}] {ln.Logger} {ln.Data}";
receivedLogs.Add(logEntry);
}
return default;
})
]
}
};
- 测试日志推送和级别管理
// 创建 Client 和 Server。
var loggingTool = McpHelper.GetToolsForType<LoggingTools>();
var (mcpClient, mcpServer) = await McpHelper.CreateInMemoryClientAndServerAsync(loggingTool,
serverOptions: mcpServerOptions,
clientOptions: mcpClientOptions);
// 设置日志级别
await mcpClient.SetLoggingLevel(LogLevel.Warning);
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine(" 测试: 日志级别设置为Warning ");
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
receivedLogs.Clear();
try
{
var result = await mcpClient.CallToolAsync("LoggingTool");
Console.WriteLine($"\n工具调用成功!");
result.Display();
Console.WriteLine($"收到日志数量: {receivedLogs.Count}");
receivedLogs.Display();
}
catch (Exception ex)
{
Console.WriteLine($"错误: {ex.Message}");
}
注意事项:本示例中的过滤逻辑在工具代码中未实现日志级别,需要在实际应用中添加级别判断。
