当 AI Agent 遇上 MCP:微软 Agent Framework 的“瑞士军刀“式扩展之道
引言:AI Agent 的"能力焦虑"
想象一下,你精心打造了一个 AI Agent,它能够流畅对话、逻辑清晰,但当用户问它"帮我查一下 GitHub 上 semantic-kernel 仓库最近的提交记录"时,它却只能尴尬地回答"抱歉,我无法访问外部系统"。这就像给一位博学多才的学者配了一副眼罩——再聪明的大脑,没有感知外界的能力,也只能是"纸上谈兵"。
这正是当今 AI Agent 开发面临的核心挑战:如何让 Agent 优雅地连接到各种外部工具和数据源,而不是为每个集成都重新造轮子?
微软的 Agent Framework 给出了一个令人眼前一亮的答案:**Model Context Protocol (MCP)**。这不是又一个"重复发明的轮子",而是一把真正的"瑞士军刀"——一个开放的标准化协议,让 AI Agent 能够像插拔 USB 设备一样,轻松接入各种工具和服务。
本文将深入剖析 MCP 在微软 Agent Framework 中的实际应用场景,从技术原理到代码实践,带你领略这个"协议级创新"如何重新定义 AI Agent 的能力边界。
第一章:MCP 不是"又一个协议"——它是 AI Agent 的"通用语言"
1.1 MCP 的本质:标准化的上下文提供机制
Model Context Protocol(模型上下文协议)是一个开放协议,旨在标准化应用程序向大语言模型(LLM)提供上下文的方式。听起来很学术?让我们用一个类比来理解:
-
传统方式:每个 AI Agent 想要调用 GitHub API、访问数据库、读取文件系统,都需要编写专门的集成代码。这就像每个电器都有自己独特的插头,你需要为每个设备准备不同的转换器。
-
MCP 方式:定义了一套统一的"插座标准"。无论是 GitHub 服务、天气 API 还是本地文件系统,只要它们实现了 MCP Server,任何支持 MCP 的 Agent 都能直接使用,无需额外适配。
这种标准化带来的价值是革命性的:
-
解耦性:Agent 的核心逻辑与外部工具完全分离
-
可复用性:一个 MCP Server 可以被无数个 Agent 共享使用
-
可扩展性:新增工具不需要修改 Agent 代码
-
互操作性:不同厂商的 Agent 和工具可以无缝协作
1.2 MCP 的架构:Client-Server 的优雅设计
MCP 采用经典的 Client-Server 架构,但在 AI Agent 场景下有其独特的设计哲学:
┌─────────────────────────────────────────────────────────┐
│ AI Agent (MCP Client) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Agent Core Logic (LLM + Reasoning) │ │
│ └──────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ MCP Client SDK (Tool Discovery & Invocation) │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘↓ MCP Protocol
┌─────────────────────────────────────────────────────────┐
│ MCP Server │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Tool Registry (ListTools, GetToolSchema) │ │
│ └──────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Tool Implementations (GitHub, Weather, etc.) │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
关键设计要点:
-
MCP Client:集成在 Agent 中,负责发现和调用 MCP Server 提供的工具
-
MCP Server:独立运行的服务,暴露一组工具(Tools)供 Agent 使用
-
通信协议:支持多种传输方式(Stdio、HTTP/SSE、WebSocket)
-
工具描述:使用标准化的 JSON Schema 描述工具的输入输出
1.3 为什么微软 Agent Framework 选择 MCP?
微软在设计 Agent Framework 时,面临一个关键决策:是自己定义一套工具集成标准,还是拥抱开放生态?
他们选择了后者,原因显而易见:
-
生态优势:MCP 是由 Anthropic 主导的开放协议,已有大量社区贡献的 MCP Server(GitHub、Slack、Google Drive 等)
-
互操作性:支持 MCP 意味着微软的 Agent 可以与其他支持 MCP 的系统(如 Claude Desktop)共享工具
-
未来兼容性:作为开放标准,MCP 的演进由社区驱动,不会被单一厂商锁定
-
开发效率:开发者可以直接使用现成的 MCP Server,而不是从零开始构建集成
这个决策体现了微软在 AI 时代的战略智慧:不是构建封闭的帝国,而是成为开放生态的积极参与者。
第二章:实战解析——MCP 在 Agent Framework 中的三大应用场景
2.1 场景一:无缝集成第三方服务(以 GitHub 为例)
业务场景
假设你正在构建一个开发助手 Agent,需要帮助用户查询 GitHub 仓库信息、分析提交历史、管理 Issue 等。传统做法需要:
-
学习 GitHub REST API 文档
-
处理认证和授权
-
编写 HTTP 请求代码
-
解析响应数据
-
将功能封装为 Agent 可调用的函数
这个过程繁琐且容易出错。而使用 MCP,整个流程被简化为三行核心代码:
// 创建 MCP Client 连接到 GitHub Server
await using var mcpClient = await McpClient.CreateAsync(new StdioClientTransport(new()
{Name = "MCPServer",Command = "npx",Arguments = ["-y", "--verbose", "@modelcontextprotocol/server-github"],
}));// 获取 GitHub Server 提供的所有工具
var mcpTools = await mcpClient.ListToolsAsync().ConfigureAwait(false);// 创建 Agent 并注入工具
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).CreateAIAgent(instructions: "You answer questions related to GitHub repositories only.", tools: [.. mcpTools.Cast<AITool>()]);// 直接使用!
Console.WriteLine(await agent.RunAsync("Summarize the last four commits to the microsoft/semantic-kernel repository?"
));
技术深度解析
让我们逐步拆解这段代码的魔法:
步骤 1:建立 MCP 连接
await using var mcpClient = await McpClient.CreateAsync(new StdioClientTransport(new(){Name = "MCPServer",Command = "npx",Arguments = ["-y", "--verbose", "@modelcontextprotocol/server-github"],})
);
这里使用了 Stdio Transport(标准输入输出传输),这是 MCP 支持的三种传输方式之一:
-
Stdio:通过标准输入输出与子进程通信(适合本地工具)
-
HTTP/SSE:通过 HTTP 和 Server-Sent Events 通信(适合远程服务)
-
WebSocket:双向实时通信(适合需要推送的场景)
在这个例子中,npx 命令会自动下载并运行 @modelcontextprotocol/server-github 这个 npm 包,它是一个预构建的 GitHub MCP Server。
步骤 2:工具发现(Tool Discovery)
var mcpTools = await mcpClient.ListToolsAsync().ConfigureAwait(false);
这一行代码触发了 MCP 的核心机制:工具发现。MCP Client 向 Server 发送 tools/list 请求,Server 返回它支持的所有工具的元数据,例如:
{"tools": [{"name": "get_repository","description": "Get details about a GitHub repository","inputSchema": {"type": "object","properties": {"owner": { "type": "string", "description": "Repository owner" },"repo": { "type": "string", "description": "Repository name" }},"required": ["owner", "repo"]}},{"name": "list_commits","description": "List commits in a repository","inputSchema": { /* ... */ }}// ... 更多工具]
}
步骤 3:工具转换为 AIFunction
tools: [.. mcpTools.Cast<AITool>()]
这里发生了一个关键的类型转换:MCP 工具被转换为 AITool(Agent Framework 的统一工具抽象)。这个转换过程包括:
-
将 MCP 的 JSON Schema 转换为 .NET 类型系统
-
创建工具调用的代理(Proxy),当 Agent 调用工具时,实际会通过 MCP Client 发送
tools/call请求到 Server -
处理工具执行结果的序列化和反序列化
步骤 4:Agent 的智能调用
当用户提问"Summarize the last four commits..."时,Agent 的执行流程如下:
User Query↓
LLM 推理:需要调用 list_commits 工具↓
Agent Framework 拦截函数调用↓
MCP Client 发送 tools/call 请求↓
GitHub MCP Server 执行 API 调用↓
返回提交历史数据↓
LLM 基于数据生成摘要↓
返回最终答案给用户
整个过程对开发者完全透明,你只需要关注 Agent 的业务逻辑,而不用操心底层的工具集成细节。
2.2 场景二:OAuth 保护的企业级 MCP 服务
业务场景
在企业环境中,工具往往需要严格的身份认证和授权。例如,一个天气预报 MCP Server 可能只允许付费用户访问,或者一个内部文档系统需要验证员工身份。
微软 Agent Framework 提供了完整的 OAuth 2.0 集成方案,让 Agent 能够安全地访问受保护的 MCP 服务。
代码实现
// 创建支持 OAuth 的 HTTP Transport
var transport = new HttpClientTransport(new()
{Endpoint = new Uri("http://localhost:7071/"),Name = "Secure Weather Client",OAuth = new(){ClientId = "ProtectedMcpClient",RedirectUri = new Uri("http://localhost:1179/callback"),AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,}
}, httpClient, loggerFactory);// 创建 MCP Client
await using var mcpClient = await McpClient.CreateAsync(transport, loggerFactory: consoleLoggerFactory);// 获取工具并创建 Agent(与之前相同)
var mcpTools = await mcpClient.ListToolsAsync().ConfigureAwait(false);
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).CreateAIAgent(instructions: "You answer questions related to the weather.", tools: [.. mcpTools]);// 使用受保护的工具
Console.WriteLine(await agent.RunAsync("Get current weather alerts for New York?"));
OAuth 流程深度解析
这个场景的核心是 HandleAuthorizationUrlAsync 回调函数,它实现了完整的 OAuth 2.0 授权码流程:
static async Task<string?> HandleAuthorizationUrlAsync(Uri authorizationUrl, Uri redirectUri, CancellationToken cancellationToken)
{Console.WriteLine($"Opening browser to: {authorizationUrl}");// 1. 启动本地 HTTP 监听器接收回调using var listener = new HttpListener();listener.Prefixes.Add(redirectUri.GetLeftPart(UriPartial.Authority) + "/");listener.Start();// 2. 打开浏览器让用户授权OpenBrowser(authorizationUrl);// 3. 等待 OAuth 服务器重定向回来var context = await listener.GetContextAsync();var query = HttpUtility.ParseQueryString(context.Request.Url?.Query ?? string.Empty);var code = query["code"];// 4. 返回授权码给 MCP Client// MCP Client 会自动用授权码换取 Access Tokenreturn code;
}
OAuth 流程时序图:
┌──────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ User │ │ Agent │ │ MCP │ │ OAuth │
│ │ │ │ │ Server │ │ Server │
└──┬───┘ └────┬─────┘ └────┬─────┘ └────┬─────┘│ │ │ ││ Query Agent │ │ │├──────────────────>│ │ ││ │ MCP Request │ ││ │ (no token) │ ││ ├────────────────────>│ ││ │ 401 Unauthorized │ ││ │<────────────────────┤ ││ │ │ ││ │ Get OAuth Metadata │ ││ ├────────────────────>│ ││ │ Returns Auth URL │ ││ │<────────────────────┤ ││ │ │ ││ │ Get Authorization Server Metadata ││ ├─────────────────────────────────────────>││ │ Returns Token Endpoint, etc. ││ │<─────────────────────────────────────────┤│ │ │ ││ Browser Opens │ │ ││<──────────────────┤ │ ││ │ │ ││ User Authorizes │ │ │├───────────────────────────────────────────────────────────>││ Redirect with Code ││<──────────────────────────────────────────────────────────────┤│ │ │ ││ Authorization Code │ │├──────────────────>│ │ ││ │ Exchange Code for Token ││ ├─────────────────────────────────────────>││ │ Access Token ││ │<─────────────────────────────────────────┤│ │ │ ││ │ MCP Request │ ││ │ (with token) │ ││ ├────────────────────>│ ││ │ Success Response │ ││ │<────────────────────┤ ││ Agent Response │ │ ││<──────────────────┤ │ │
企业级安全考量
这个实现展示了几个关键的安全最佳实践:
-
授权码流程(Authorization Code Flow):比隐式流程更安全,Access Token 不会暴露在浏览器 URL 中
-
本地回调服务器:使用
HttpListener在本地启动临时服务器接收回调,避免将敏感信息发送到远程服务器 -
Token 自动管理:MCP Client SDK 自动处理 Token 的存储、刷新和过期管理
-
HTTPS 支持:生产环境应使用 HTTPS 保护通信(示例中使用 HTTP 仅为演示)
实际应用价值
这个场景在企业 AI 应用中极为常见:
-
内部知识库访问:Agent 需要通过 SSO 认证才能访问公司文档
-
CRM 系统集成:需要用户授权才能读取客户数据
-
云服务 API:如 Azure、AWS 的 API 都需要 OAuth 认证
通过 MCP + OAuth 的组合,Agent Framework 让这些复杂的集成变得标准化和可复用。
2.3 场景三:Azure AI Foundry 的托管 MCP 服务
业务场景
前两个场景都是在客户端运行 MCP Client,但在生产环境中,你可能希望:
-
服务端托管:MCP Server 运行在云端,由专业团队维护
-
统一管理:集中管理工具的访问权限、配额和监控
-
高可用性:利用云平台的负载均衡和容错能力
Azure AI Foundry 提供了托管 MCP 服务,让 Agent 可以直接使用云端的 MCP 工具,无需在本地运行 MCP Server。
代码实现
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT");
var model = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_MODEL_ID") ?? "gpt-4.1-mini";// 获取 Azure Foundry 的持久化 Agent 客户端
var persistentAgentsClient = new PersistentAgentsClient(endpoint, new AzureCliCredential());// 定义 MCP 工具(指向云端 MCP Server)
var mcpTool = new MCPToolDefinition(serverLabel: "microsoft_learn",serverUrl: "https://learn.microsoft.com/api/mcp");
mcpTool.AllowedTools.Add("microsoft_docs_search");// 在云端创建 Agent(包含 MCP 工具)
var agentMetadata = await persistentAgentsClient.Administration.CreateAgentAsync(model: model,name: "MicrosoftLearnAgent",instructions: "You answer questions by searching the Microsoft Learn content only.",tools: [mcpTool]);// 获取 Agent 实例
AIAgent agent = await persistentAgentsClient.GetAIAgentAsync(agentMetadata.Value.Id);// 配置 MCP 工具资源
var runOptions = new ChatClientAgentRunOptions()
{ChatOptions = new(){RawRepresentationFactory = (_) => new ThreadAndRunOptions(){ToolResources = new MCPToolResource(serverLabel: "microsoft_learn"){RequireApproval = new MCPApproval("never"),}.ToToolResources()}}
};// 使用云端 Agent 和 MCP 工具
AgentThread thread = agent.GetNewThread();
var response = await agent.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", thread, runOptions
);
Console.WriteLine(response);// 清理资源
await persistentAgentsClient.Administration.DeleteAgentAsync(agent.Id);
架构对比:本地 vs 云端 MCP
本地 MCP 架构:
┌─────────────────────────────────┐
│ Your Application │
│ ┌───────────────────────────┐ │
│ │ Agent + MCP Client │ │
│ └───────────────────────────┘ │
│ ↓ Stdio │
│ ┌───────────────────────────┐ │
│ │ MCP Server (subprocess) │ │
│ └───────────────────────────┘ │
└─────────────────────────────────┘
云端 MCP 架构:
┌─────────────────────────────────┐
│ Your Application │
│ ┌───────────────────────────┐ │
│ │ Agent Proxy │ │
│ └───────────────────────────┘ │
└─────────────────────────────────┘↓ HTTPS
┌─────────────────────────────────┐
│ Azure AI Foundry │
│ ┌───────────────────────────┐ │
│ │ Hosted Agent │ │
│ └───────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────┐ │
│ │ Managed MCP Client │ │
│ └───────────────────────────┘ │
│ ↓ HTTPS │
│ ┌───────────────────────────┐ │
│ │ MCP Server Pool │ │
│ └───────────────────────────┘ │
└─────────────────────────────────┘
云端托管的优势
-
零运维:不需要在本地安装和管理 MCP Server 进程
-
弹性扩展:Azure 自动处理负载均衡和扩容
-
企业级安全:利用 Azure 的身份认证、网络隔离、审计日志等功能
-
成本优化:按需付费,避免闲置资源浪费
-
版本管理:云端统一升级 MCP Server,无需客户端更新
工具审批机制
注意代码中的这一行:
RequireApproval = new MCPApproval("never")
Azure AI Foundry 提供了三种工具审批策略:
-
never:自动执行所有工具调用(适合可信工具)
-
always:每次调用都需要人工审批(适合敏感操作)
-
conditional:基于规则决定是否需要审批(如:写操作需要审批,读操作自动执行)
这个机制在企业场景中至关重要,例如:
-
查询客户信息:自动执行
-
修改订单状态:需要审批
-
发送营销邮件:需要审批
通过细粒度的审批控制,Agent 既能保持高效,又能确保关键操作的安全性。
第三章:技术深度剖析——MCP 在 Agent Framework 中的实现机制
3.1 AIAgent 抽象层:统一的 Agent 接口
微软 Agent Framework 的核心设计哲学是抽象与解耦。无论底层使用的是 OpenAI、Azure OpenAI、Copilot Studio 还是其他 AI 服务,开发者都通过统一的 AIAgent 抽象类进行交互。
AIAgent 的核心接口
public abstract class AIAgent
{// 唯一标识符public virtual string Id { get; }// 人类可读的名称public virtual string? Name { get; }// Agent 的描述public virtual string? Description { get; }// 创建新的对话线程public abstract AgentThread GetNewThread();// 核心执行方法(非流式)public abstract Task<AgentRunResponse> RunAsync(IEnumerable<ChatMessage> messages,AgentThread? thread = null,AgentRunOptions? options = null,CancellationToken cancellationToken = default);// 核心执行方法(流式)public abstract IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(IEnumerable<ChatMessage> messages,AgentThread? thread = null,AgentRunOptions? options = null,CancellationToken cancellationToken = default);
}
为什么这个抽象如此重要?
-
提供者无关性:同一段 Agent 代码可以无缝切换底层 AI 服务
-
工具集成点:
RunAsync方法是工具调用的拦截点,MCP 工具在这里被注入和执行 -
对话管理:
AgentThread抽象了对话历史的存储和管理 -
流式支持:原生支持流式响应,提升用户体验
3.2 AIAgentBuilder:管道式的 Agent 构建
Agent Framework 使用 Builder 模式 和 管道模式 来构建 Agent,这使得功能扩展变得极为灵活。
Builder 模式的威力
var agent = new AzureOpenAIClient(endpoint, credential).GetChatClient(deploymentName).CreateAIAgent(name: "MyAgent",instructions: "You are a helpful assistant.",tools: [.. mcpTools] // MCP 工具在这里注入);
这段看似简单的代码,实际上构建了一个复杂的处理管道:
User Input↓
┌─────────────────────────────────────┐
│ OpenTelemetryAgent (可选) │ ← 遥测和监控
│ ┌───────────────────────────────┐ │
│ │ FunctionInvocationAgent │ │ ← 函数调用拦截
│ │ ┌─────────────────────────┐ │ │
│ │ │ ChatClientAgent │ │ │ ← 核心 LLM 交互
│ │ │ ┌─────────────────────┐│ │ │
│ │ │ │ MCP Tool Executor ││ │ │ ← MCP 工具执行
│ │ │ └─────────────────────┘│ │ │
│ │ └─────────────────────────┘ │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘↓
Agent Response
中间件机制:Use() 方法的魔法
Agent Framework 提供了强大的中间件机制,允许在 Agent 执行管道中插入自定义逻辑:
var builder = new AIAgentBuilder(innerAgent).Use((messages, thread, options, next, cancellationToken) =>{// 前置处理:记录请求Console.WriteLine($"[Pre] Received {messages.Count()} messages");// 调用下一个中间件var task = next(messages, thread, options, cancellationToken);// 后置处理:记录响应Console.WriteLine("[Post] Request completed");return task;}).UseOpenTelemetry() // 添加遥测.Build();
这种设计让 MCP 工具的集成变得非常自然:MCP 工具只是管道中的一个环节,可以与其他功能(如日志、缓存、限流)无缝组合。
3.3 工具调用的完整生命周期
让我们深入追踪一次 MCP 工具调用的完整流程:
阶段 1:工具注册
// 1. 从 MCP Server 获取工具列表
var mcpTools = await mcpClient.ListToolsAsync();// 2. 转换为 AITool(Agent Framework 的统一工具抽象)
var aiTools = mcpTools.Cast<AITool>().ToArray();// 3. 注入到 Agent
var agent = chatClient.CreateAIAgent(tools: aiTools);
在这个过程中,每个 MCP 工具被包装成一个 AIFunction,它包含:
-
名称:工具的唯一标识符
-
描述:告诉 LLM 这个工具的用途
-
参数 Schema:定义工具接受的输入参数
-
执行委托:实际调用 MCP Server 的代理函数
阶段 2:LLM 决策
当用户发送消息时,Agent 将对话历史和可用工具列表一起发送给 LLM:
{"messages": [{"role": "user", "content": "查询 semantic-kernel 仓库的最新提交"}],"tools": [{"type": "function","function": {"name": "list_commits","description": "List commits in a GitHub repository","parameters": {"type": "object","properties": {"owner": {"type": "string"},"repo": {"type": "string"},"limit": {"type": "integer"}},"required": ["owner", "repo"]}}}]
}
LLM 分析后决定调用 list_commits 工具,并返回:
{"role": "assistant","content": null,"tool_calls": [{"id": "call_abc123","type": "function","function": {"name": "list_commits","arguments": "{\"owner\":\"microsoft\",\"repo\":\"semantic-kernel\",\"limit\":4}"}}]
}
阶段 3:工具执行
Agent Framework 拦截这个工具调用请求,并通过 MCP Client 执行:
// Agent Framework 内部逻辑(简化版)
foreach (var toolCall in response.ToolCalls)
{// 1. 查找对应的 MCP 工具var mcpTool = mcpTools.First(t => t.Name == toolCall.Function.Name);// 2. 解析参数var arguments = JsonSerializer.Deserialize<Dictionary<string, object>>(toolCall.Function.Arguments);// 3. 通过 MCP Client 调用工具var result = await mcpClient.CallToolAsync(toolCall.Function.Name,arguments,cancellationToken);// 4. 将结果包装为 ChatMessagevar toolResultMessage = new ChatMessage(ChatRole.Tool,JsonSerializer.Serialize(result),toolCallId: toolCall.Id);// 5. 添加到对话历史messages.Add(toolResultMessage);
}
阶段 4:结果合成
工具执行结果被添加到对话历史后,Agent 再次调用 LLM,让它基于工具返回的数据生成最终答案:
{"messages": [{"role": "user", "content": "查询 semantic-kernel 仓库的最新提交"},{"role": "assistant", "tool_calls": [...]},{"role": "tool","tool_call_id": "call_abc123","content": "[{\"sha\":\"abc123\",\"message\":\"Fix bug\",\"author\":\"John\"}, ...]"}]
}
LLM 最终返回:
根据查询结果,semantic-kernel 仓库最近的 4 次提交是:
1. abc123 - Fix bug (作者:John)
2. def456 - Add feature (作者:Jane)
...
完整时序图
┌──────┐ ┌───────┐ ┌──────────┐ ┌─────┐ ┌──────────┐
│ User │ │ Agent │ │ LLM │ │ MCP │ │ GitHub │
│ │ │ │ │ │ │ Srv │ │ API │
└──┬───┘ └───┬───┘ └────┬─────┘ └──┬──┘ └────┬─────┘│ │ │ │ ││ Query │ │ │ │├───────────>│ │ │ ││ │ Messages + │ │ ││ │ Tools │ │ ││ ├────────────>│ │ ││ │ Tool Call │ │ ││ │ Decision │ │ ││ │<────────────┤ │ ││ │ │ │ ││ │ CallTool(list_commits) │ ││ ├────────────────────────────>│ ││ │ │ │ GET /repos││ │ │ ├───────────>││ │ │ │ Commits ││ │ │ │<───────────┤│ │ Tool Result │ ││ │<────────────────────────────┤ ││ │ │ │ ││ │ Messages + │ │ ││ │ Tool Result│ │ ││ ├────────────>│ │ ││ │ Final │ │ ││ │ Answer │ │ ││ │<────────────┤ │ ││ Response │ │ │ ││<───────────┤ │ │ │
3.4 错误处理与容错机制
在实际应用中,MCP 工具调用可能会遇到各种错误:
-
网络超时
-
MCP Server 崩溃
-
工具参数验证失败
-
API 限流
Agent Framework 提供了多层次的错误处理:
1. MCP Client 层面
try
{var result = await mcpClient.CallToolAsync(toolName, arguments, cancellationToken);
}
catch (McpException ex) when (ex.ErrorCode == "TOOL_NOT_FOUND")
{// 工具不存在,返回友好错误信息给 LLMreturn new ToolResult { Error = "The requested tool is not available." };
}
catch (TimeoutException)
{// 超时,可以重试或降级return new ToolResult { Error = "Tool execution timed out. Please try again." };
}
2. Agent 层面
Agent Framework 会将工具执行错误包装为特殊的 ChatMessage,让 LLM 能够理解错误并采取补救措施:
var errorMessage = new ChatMessage(ChatRole.Tool,JsonSerializer.Serialize(new { error = "Tool execution failed" }),toolCallId: toolCall.Id
);
LLM 收到错误后,可能会:
-
尝试使用其他工具
-
向用户请求更多信息
-
基于已有信息给出部分答案
3. 应用层面
开发者可以通过中间件捕获和处理错误:
builder.Use(async (messages, thread, options, next, cancellationToken) =>
{try{return await next(messages, thread, options, cancellationToken);}catch (Exception ex){// 记录错误logger.LogError(ex, "Agent execution failed");// 返回降级响应return new AgentRunResponse{Messages = [new ChatMessage(ChatRole.Assistant,"I'm sorry, I encountered an error. Please try again later.")]};}
});
这种多层次的错误处理确保了 Agent 在面对异常时仍能保持稳定和用户友好。
第四章:最佳实践与设计模式
4.1 MCP 工具的设计原则
在为 Agent 设计 MCP 工具时,遵循以下原则可以显著提升用户体验和系统可靠性:
原则 1:单一职责(Single Responsibility)
每个工具应该只做一件事,并把它做好。
❌ 不好的设计:
{"name": "github_operations","description": "Perform various GitHub operations","parameters": {"operation": {"type": "string", "enum": ["list_repos", "get_commits", "create_issue"]},"params": {"type": "object"}}
}
✅ 好的设计:
[{"name": "list_repositories","description": "List repositories for a user or organization","parameters": {"owner": {"type": "string"}}},{"name": "list_commits","description": "List commits in a repository","parameters": {"owner": {"type": "string"},"repo": {"type": "string"}}},{"name": "create_issue","description": "Create a new issue in a repository","parameters": {"owner": {"type": "string"},"repo": {"type": "string"},"title": {"type": "string"},"body": {"type": "string"}}}
]
为什么?
-
LLM 更容易理解:清晰的工具名称和描述让 LLM 能准确选择合适的工具
-
参数验证更简单:每个工具的参数结构固定,易于验证
-
错误隔离:一个工具的问题不会影响其他工具
原则 2:描述性命名(Descriptive Naming)
工具名称和描述应该清晰地传达其功能和用途。
❌ 不好的命名:
{"name": "get_data","description": "Gets data"
}
✅ 好的命名:
{"name": "get_weather_forecast","description": "Get the weather forecast for a specific location and date range. Returns temperature, precipitation, wind speed, and humidity."
}
技巧:
-
使用动词开头(get, list, create, update, delete)
-
包含领域信息(weather, github, database)
-
在描述中说明返回值的结构
原则 3:参数验证(Parameter Validation)
在 MCP Server 端进行严格的参数验证,而不是依赖 LLM 提供正确的参数。
// MCP Server 端的参数验证示例
public async Task<ToolResult> GetWeatherForecast(string location, DateTime date)
{// 验证 locationif (string.IsNullOrWhiteSpace(location)){return ToolResult.Error("Location parameter is required and cannot be empty.");}// 验证 dateif (date < DateTime.UtcNow.Date){return ToolResult.Error("Date cannot be in the past.");}if (date > DateTime.UtcNow.AddDays(14)){return ToolResult.Error("Weather forecast is only available for the next 14 days.");}// 执行实际逻辑var forecast = await weatherService.GetForecastAsync(location, date);return ToolResult.Success(forecast);
}
原则 4:幂等性(Idempotency)
对于写操作,尽可能设计为幂等的,避免重复执行导致的副作用。
// 非幂等:每次调用都创建新记录
public async Task<ToolResult> CreateOrder(string customerId, string productId)
{var order = new Order { CustomerId = customerId, ProductId = productId };await database.Orders.AddAsync(order);return ToolResult.Success(order.Id);
}// 幂等:使用唯一标识符避免重复创建
public async Task<ToolResult> CreateOrder(string orderId, string customerId, string productId)
{// 检查订单是否已存在var existing = await database.Orders.FindAsync(orderId);if (existing != null){return ToolResult.Success(existing.Id, message: "Order already exists.");}var order = new Order { Id = orderId, CustomerId = customerId, ProductId = productId };await database.Orders.AddAsync(order);return ToolResult.Success(order.Id);
}
4.2 多 MCP Server 的编排策略
在复杂的企业应用中,Agent 可能需要同时使用多个 MCP Server。如何优雅地管理这些连接?
策略 1:服务注册模式
使用依赖注入容器管理多个 MCP Client:
public class McpServiceRegistry
{private readonly Dictionary<string, McpClient> _clients = new();public async Task RegisterAsync(string name, McpClientTransport transport){var client = await McpClient.CreateAsync(transport);_clients[name] = client;}public async Task<IEnumerable<AITool>> GetAllToolsAsync(){var allTools = new List<AITool>();foreach (var (serverName, client) in _clients){var tools = await client.ListToolsAsync();// 为每个工具添加服务器前缀,避免命名冲突var prefixedTools = tools.Select(tool => new AITool{Name = $"{serverName}_{tool.Name}",Description = $"[{serverName}] {tool.Description}",Parameters = tool.Parameters,Executor = tool.Executor});allTools.AddRange(prefixedTools);}return allTools;}
}// 使用示例
var registry = new McpServiceRegistry();await registry.RegisterAsync("github", new StdioClientTransport(new()
{Command = "npx",Arguments = ["-y", "@modelcontextprotocol/server-github"]
}));await registry.RegisterAsync("weather", new HttpClientTransport(new()
{Endpoint = new Uri("https://weather-mcp.example.com")
}));var allTools = await registry.GetAllToolsAsync();
var agent = chatClient.CreateAIAgent(tools: allTools.ToArray());
策略 2:工具路由模式
根据用户意图动态选择合适的 MCP Server:
public class McpRouter
{private readonly Dictionary<string, McpClient> _clients;public async Task<ToolResult> RouteAndExecuteAsync(string toolName, Dictionary<string, object> arguments){// 从工具名称中提取服务器标识var parts = toolName.Split('_', 2);if (parts.Length != 2){return ToolResult.Error("Invalid tool name format.");}var serverName = parts[0];var actualToolName = parts[1];if (!_clients.TryGetValue(serverName, out var client)){return ToolResult.Error($"MCP server '{serverName}' not found.");}// 路由到对应的 MCP Serverreturn await client.CallToolAsync(actualToolName, arguments);}
}
策略 3:工具聚合模式
将多个 MCP 工具组合成高级功能:
public class AggregatedMcpTools
{private readonly McpClient _githubClient;private readonly McpClient _slackClient;// 高级工具:监控仓库并发送通知public async Task<ToolResult> MonitorRepositoryAndNotify(string owner, string repo, string slackChannel){// 1. 从 GitHub 获取最新提交var commits = await _githubClient.CallToolAsync("list_commits", new(){["owner"] = owner,["repo"] = repo,["limit"] = 5});// 2. 格式化消息var message = FormatCommitsMessage(commits);// 3. 发送到 Slackvar result = await _slackClient.CallToolAsync("send_message", new(){["channel"] = slackChannel,["text"] = message});return result;}
}
4.3 性能优化技巧
技巧 1:工具列表缓存
避免每次创建 Agent 都重新获取工具列表:
public class CachedMcpClient
{private readonly McpClient _client;private IEnumerable<AITool>? _cachedTools;private DateTime _cacheExpiry;public async Task<IEnumerable<AITool>> GetToolsAsync(){if (_cachedTools == null || DateTime.UtcNow > _cacheExpiry){_cachedTools = await _client.ListToolsAsync();_cacheExpiry = DateTime.UtcNow.AddMinutes(5);}return _cachedTools;}
}
技巧 2:并行工具调用
当 Agent 需要调用多个独立的工具时,使用并行执行:
// Agent Framework 内部已支持并行工具调用
// 当 LLM 返回多个 tool_calls 时,自动并行执行// 如果需要手动控制,可以这样做:
var tasks = toolCalls.Select(async toolCall =>
{return await mcpClient.CallToolAsync(toolCall.Function.Name,ParseArguments(toolCall.Function.Arguments));
});var results = await Task.WhenAll(tasks);
技巧 3:流式工具结果
对于返回大量数据的工具,使用流式传输:
// MCP Server 端
public async IAsyncEnumerable<string> StreamLargeDataset(string query)
{await foreach (var chunk in database.QueryStreamAsync(query)){yield return JsonSerializer.Serialize(chunk);}
}// Agent 端
await foreach (var chunk in mcpClient.CallToolStreamingAsync("query_data", arguments))
{// 逐步处理数据,而不是等待全部加载ProcessChunk(chunk);
}
4.4 安全性最佳实践
实践 1:最小权限原则
为每个 MCP Server 配置最小必要的权限:
var mcpTool = new MCPToolDefinition(serverLabel: "github",serverUrl: "https://github-mcp.example.com");// 只允许读操作,禁止写操作
mcpTool.AllowedTools.Add("list_repositories");
mcpTool.AllowedTools.Add("get_repository");
mcpTool.AllowedTools.Add("list_commits");
// 不添加 create_issue, delete_repository 等危险操作
实践 2:敏感数据脱敏
在工具返回结果中自动脱敏敏感信息:
public class SensitiveDataFilter
{public ToolResult FilterSensitiveData(ToolResult result){var json = JsonSerializer.Serialize(result.Data);// 脱敏邮箱json = Regex.Replace(json, @"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", "***@***.***");// 脱敏电话号码json = Regex.Replace(json, @"\b\d{3}-\d{3}-\d{4}\b", "***-***-****");// 脱敏信用卡号json = Regex.Replace(json, @"\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b", "****-****-****-****");return new ToolResult { Data = JsonSerializer.Deserialize(json) };}
}
实践 3:审计日志
记录所有 MCP 工具调用,用于安全审计和问题排查:
public class AuditedMcpClient
{private readonly McpClient _client;private readonly IAuditLogger _auditLogger;public async Task<ToolResult> CallToolAsync(string toolName, Dictionary<string, object> arguments,string userId){var auditEntry = new AuditEntry{Timestamp = DateTime.UtcNow,UserId = userId,ToolName = toolName,Arguments = JsonSerializer.Serialize(arguments)};try{var result = await _client.CallToolAsync(toolName, arguments);auditEntry.Success = true;auditEntry.ResultSummary = GetResultSummary(result);return result;}catch (Exception ex){auditEntry.Success = false;auditEntry.Error = ex.Message;throw;}finally{await _auditLogger.LogAsync(auditEntry);}}
}
4.5 测试策略
单元测试:Mock MCP Client
public class MockMcpClient : IMcpClient
{private readonly Dictionary<string, Func<Dictionary<string, object>, ToolResult>> _mockTools;public Task<IEnumerable<AITool>> ListToolsAsync(){var tools = _mockTools.Keys.Select(name => new AITool{Name = name,Description = $"Mock tool: {name}",Executor = async (args) => _mockTools[name](args)});return Task.FromResult(tools);}
}// 测试用例
[Fact]
public async Task Agent_Should_Call_GitHub_Tool()
{// Arrangevar mockClient = new MockMcpClient();mockClient.RegisterTool("list_commits", args =>{Assert.Equal("microsoft", args["owner"]);Assert.Equal("semantic-kernel", args["repo"]);return ToolResult.Success(new[]{new { sha = "abc123", message = "Test commit" }});});var tools = await mockClient.ListToolsAsync();var agent = chatClient.CreateAIAgent(tools: tools.ToArray());// Actvar response = await agent.RunAsync("List commits in microsoft/semantic-kernel");// AssertAssert.Contains("Test commit", response.ToString());
}
集成测试:使用真实 MCP Server
[Fact]
public async Task Integration_Test_With_Real_MCP_Server()
{// 启动测试用的 MCP Serverusing var mcpServer = await TestMcpServer.StartAsync(port: 5000);// 创建 Agentvar transport = new HttpClientTransport(new(){Endpoint = new Uri("http://localhost:5000")});await using var mcpClient = await McpClient.CreateAsync(transport);var tools = await mcpClient.ListToolsAsync();var agent = chatClient.CreateAIAgent(tools: tools.ToArray());// 执行测试var response = await agent.RunAsync("Get weather for Seattle");Assert.NotNull(response);Assert.Contains("Seattle", response.ToString());
}
第五章:实战案例——构建一个企业级客服 Agent
让我们通过一个完整的实战案例,展示如何使用 MCP 构建一个功能强大的企业客服 Agent。
5.1 业务需求分析
场景:某电商公司需要一个智能客服 Agent,能够处理以下任务:
-
订单查询:查询订单状态、物流信息
-
退换货处理:根据公司政策判断是否可以退换货
-
库存查询:查询商品库存和价格
-
工单创建:对于无法自动处理的问题,创建工单转人工
技术要求:
-
支持多轮对话
-
需要访问多个后端系统(订单系统、库存系统、CRM)
-
敏感操作需要人工审批
-
完整的审计日志
-
高可用性和容错能力
5.2 架构设计
┌─────────────────────────────────────────────────────────────┐
│ Customer Service Agent │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Agent Core (Azure OpenAI GPT-4) │ │
│ └───────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Middleware Pipeline │ │
│ │ • OpenTelemetry (监控) │ │
│ │ • Audit Logger (审计) │ │
│ │ • Rate Limiter (限流) │ │
│ │ • Error Handler (容错) │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘↓ MCP Protocol
┌─────────────────────────────────────────────────────────────┐
│ MCP Servers │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Order Server │ │ Inventory │ │ CRM Server │ │
│ │ │ │ Server │ │ │ │
│ │ • get_order │ │ • check_stock│ │ • create_ │ │
│ │ • track_ │ │ • get_price │ │ ticket │ │
│ │ shipment │ │ │ │ • get_policy │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘↓
┌─────────────────────────────────────────────────────────────┐
│ Backend Systems │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Order DB │ │ Inventory DB │ │ CRM System │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
5.3 代码实现
步骤 1:定义 MCP Servers
Order MCP Server (简化版):
public class OrderMcpServer
{private readonly IOrderService _orderService;[McpTool("get_order", "Get order details by order ID")]public async Task<OrderDetails> GetOrder([McpParameter("order_id", "The order ID")] string orderId){var order = await _orderService.GetOrderAsync(orderId);if (order == null){throw new McpException("ORDER_NOT_FOUND", $"Order {orderId} not found");}return new OrderDetails{OrderId = order.Id,Status = order.Status,Items = order.Items.Select(i => new OrderItem{ProductId = i.ProductId,ProductName = i.ProductName,Quantity = i.Quantity,Price = i.Price}).ToList(),TotalAmount = order.TotalAmount,CreatedAt = order.CreatedAt};}[McpTool("track_shipment", "Track shipment status")]public async Task<ShipmentInfo> TrackShipment([McpParameter("order_id", "The order ID")] string orderId){var shipment = await _orderService.GetShipmentAsync(orderId);return new ShipmentInfo{TrackingNumber = shipment.TrackingNumber,Carrier = shipment.Carrier,Status = shipment.Status,EstimatedDelivery = shipment.EstimatedDelivery,TrackingHistory = shipment.History};}
}
Inventory MCP Server:
public class InventoryMcpServer
{private readonly IInventoryService _inventoryService;[McpTool("check_stock", "Check product stock availability")]public async Task<StockInfo> CheckStock([McpParameter("product_id", "The product ID")] string productId,[McpParameter("quantity", "Desired quantity")] int quantity = 1){var stock = await _inventoryService.GetStockAsync(productId);return new StockInfo{ProductId = productId,ProductName = stock.ProductName,AvailableQuantity = stock.Quantity,IsAvailable = stock.Quantity >= quantity,Price = stock.Price,EstimatedRestockDate = stock.Quantity < quantity ? stock.EstimatedRestockDate : null};}[McpTool("get_price", "Get current price for a product")]public async Task<PriceInfo> GetPrice([McpParameter("product_id", "The product ID")] string productId){var price = await _inventoryService.GetPriceAsync(productId);return new PriceInfo{ProductId = productId,CurrentPrice = price.Amount,OriginalPrice = price.OriginalAmount,DiscountPercentage = price.DiscountPercentage,ValidUntil = price.ValidUntil};}
}
CRM MCP Server:
public class CrmMcpServer
{private readonly ICrmService _crmService;[McpTool("create_ticket", "Create a customer service ticket")][RequiresApproval] // 需要人工审批public async Task<TicketInfo> CreateTicket([McpParameter("customer_id", "Customer ID")] string customerId,[McpParameter("subject", "Ticket subject")] string subject,[McpParameter("description", "Detailed description")] string description,[McpParameter("priority", "Priority level")] string priority = "normal"){var ticket = await _crmService.CreateTicketAsync(new CreateTicketRequest{CustomerId = customerId,Subject = subject,Description = description,Priority = priority,Source = "AI Agent"});return new TicketInfo{TicketId = ticket.Id,Status = "Open",AssignedTo = ticket.AssignedAgent,CreatedAt = ticket.CreatedAt};}[McpTool("get_return_policy", "Get return and refund policy")]public async Task<PolicyInfo> GetReturnPolicy([McpParameter("product_category", "Product category")] string category){var policy = await _crmService.GetPolicyAsync("return", category);return new PolicyInfo{PolicyName = policy.Name,Description = policy.Description,ReturnWindow = policy.ReturnWindowDays,Conditions = policy.Conditions,RefundMethod = policy.RefundMethod};}
}
步骤 2:构建 Agent
public class CustomerServiceAgentBuilder
{private readonly IConfiguration _configuration;private readonly ILogger<CustomerServiceAgentBuilder> _logger;public async Task<AIAgent> BuildAsync(){// 1. 创建 MCP Clientsvar mcpRegistry = new McpServiceRegistry();await mcpRegistry.RegisterAsync("order", new HttpClientTransport(new(){Endpoint = new Uri(_configuration["MCP:OrderServerUrl"]),Name = "Order Service"}));await mcpRegistry.RegisterAsync("inventory", new HttpClientTransport(new(){Endpoint = new Uri(_configuration["MCP:InventoryServerUrl"]),Name = "Inventory Service"}));await mcpRegistry.RegisterAsync("crm", new HttpClientTransport(new(){Endpoint = new Uri(_configuration["MCP:CrmServerUrl"]),Name = "CRM Service",OAuth = new(){ClientId = _configuration["MCP:CrmClientId"],ClientSecret = _configuration["MCP:CrmClientSecret"],TokenEndpoint = new Uri(_configuration["MCP:CrmTokenEndpoint"])}}));// 2. 获取所有工具var allTools = await mcpRegistry.GetAllToolsAsync();// 3. 创建 Azure OpenAI Clientvar endpoint = _configuration["AzureOpenAI:Endpoint"];var deploymentName = _configuration["AzureOpenAI:DeploymentName"];var chatClient = new AzureOpenAIClient(new Uri(endpoint),new AzureCliCredential()).GetChatClient(deploymentName);// 4. 构建 Agent Pipelinevar agentBuilder = new AIAgentBuilder(chatClient.CreateAIAgent(name: "CustomerServiceAgent",instructions: GetSystemInstructions(),tools: allTools.ToArray()));// 5. 添加中间件agentBuilder.UseOpenTelemetry(sourceName: "CustomerService").Use(AuditMiddleware).Use(RateLimitMiddleware).Use(ErrorHandlingMiddleware).Use(ApprovalMiddleware);return agentBuilder.Build();}private string GetSystemInstructions(){return @"
You are a helpful customer service agent for an e-commerce company.Your responsibilities:
1. Help customers check their order status and track shipments
2. Answer questions about product availability and pricing
3. Assist with return and refund requests according to company policy
4. Create support tickets for complex issues that require human interventionGuidelines:
- Always be polite and professional
- Verify order information before providing details
- Follow company policies strictly for returns and refunds
- If you're unsure about something, create a ticket for human review
- Protect customer privacy - never share personal informationAvailable tools:
- order_get_order: Get order details
- order_track_shipment: Track shipment status
- inventory_check_stock: Check product availability
- inventory_get_price: Get product pricing
- crm_create_ticket: Create support ticket (requires approval)
- crm_get_return_policy: Get return policy information
";}// 审计中间件private async Task AuditMiddleware(IEnumerable<ChatMessage> messages,AgentThread? thread,AgentRunOptions? options,Func<IEnumerable<ChatMessage>, AgentThread?, AgentRunOptions?, CancellationToken, Task> next,CancellationToken cancellationToken){var sessionId = thread?.Id ?? Guid.NewGuid().ToString();var userId = options?.GetUserId() ?? "anonymous";_logger.LogInformation("Agent session started: SessionId={SessionId}, UserId={UserId}, MessageCount={MessageCount}",sessionId, userId, messages.Count());var stopwatch = Stopwatch.StartNew();try{await next(messages, thread, options, cancellationToken);_logger.LogInformation("Agent session completed: SessionId={SessionId}, Duration={Duration}ms",sessionId, stopwatch.ElapsedMilliseconds);}catch (Exception ex){_logger.LogError(ex,"Agent session failed: SessionId={SessionId}, Duration={Duration}ms",sessionId, stopwatch.ElapsedMilliseconds);throw;}}// 限流中间件private async Task RateLimitMiddleware(IEnumerable<ChatMessage> messages,AgentThread? thread,AgentRunOptions? options,Func<IEnumerable<ChatMessage>, AgentThread?, AgentRunOptions?, CancellationToken, Task> next,CancellationToken cancellationToken){var userId = options?.GetUserId() ?? "anonymous";// 检查用户是否超过速率限制(例如:每分钟 10 次请求)if (!await _rateLimiter.TryAcquireAsync(userId)){throw new RateLimitExceededException("You have exceeded the rate limit. Please try again later.");}await next(messages, thread, options, cancellationToken);}// 错误处理中间件private async Task ErrorHandlingMiddleware(IEnumerable<ChatMessage> messages,AgentThread? thread,AgentRunOptions? options,Func<IEnumerable<ChatMessage>, AgentThread?, AgentRunOptions?, CancellationToken, Task> next,CancellationToken cancellationToken){try{await next(messages, thread, options, cancellationToken);}catch (McpException ex) when (ex.ErrorCode == "ORDER_NOT_FOUND"){// 友好的错误消息var errorMessage = new ChatMessage(ChatRole.Assistant,"I couldn't find that order. Please double-check the order number and try again.");await thread?.MessagesReceivedAsync([errorMessage], cancellationToken);}catch (RateLimitExceededException ex){var errorMessage = new ChatMessage(ChatRole.Assistant,"I'm receiving too many requests right now. Please wait a moment and try again.");await thread?.MessagesReceivedAsync([errorMessage], cancellationToken);}catch (Exception ex){_logger.LogError(ex, "Unexpected error in agent execution");var errorMessage = new ChatMessage(ChatRole.Assistant,"I'm sorry, I encountered an unexpected error. A support ticket has been created and our team will look into this.");await thread?.MessagesReceivedAsync([errorMessage], cancellationToken);// 自动创建工单await CreateErrorTicketAsync(ex, messages, thread);}}// 审批中间件private async Task ApprovalMiddleware(IEnumerable<ChatMessage> messages,AgentThread? thread,AgentRunOptions? options,Func<IEnumerable<ChatMessage>, AgentThread?, AgentRunOptions?, CancellationToken, Task> next,CancellationToken cancellationToken){// 拦截需要审批的工具调用var approvalHandler = new ApprovalHandler();options ??= new AgentRunOptions();options.ToolCallApprovalCallback = async (toolCall) =>{// 检查工具是否需要审批if (toolCall.Function.Name.Contains("create_ticket")){_logger.LogInformation("Tool call requires approval: {ToolName}",toolCall.Function.Name);// 请求人工审批var approved = await approvalHandler.RequestApprovalAsync(toolCall,timeout: TimeSpan.FromMinutes(5));if (!approved){_logger.LogWarning("Tool call rejected: {ToolName}",toolCall.Function.Name);return false;}}return true;};await next(messages, thread, options, cancellationToken);}
}
步骤 3:使用 Agent
public class CustomerServiceController : ControllerBase
{private readonly AIAgent _agent;private readonly IAgentThreadStore _threadStore;[HttpPost("chat")]public async Task<IActionResult> Chat([FromBody] ChatRequest request){// 获取或创建对话线程var thread = await _threadStore.GetOrCreateThreadAsync(request.SessionId);// 执行 Agentvar response = await _agent.RunAsync(request.Message,thread,new AgentRunOptions{UserId = request.UserId,Metadata = new Dictionary<string, object>{["ip_address"] = HttpContext.Connection.RemoteIpAddress?.ToString(),["user_agent"] = Request.Headers["User-Agent"].ToString()}});// 保存线程await _threadStore.SaveThreadAsync(thread);return Ok(new ChatResponse{Message = response.ToString(),SessionId = request.SessionId});}[HttpPost("chat/stream")]public async Task StreamChat([FromBody] ChatRequest request){Response.ContentType = "text/event-stream";var thread = await _threadStore.GetOrCreateThreadAsync(request.SessionId);await foreach (var update in _agent.RunStreamingAsync(request.Message,thread,new AgentRunOptions { UserId = request.UserId })){var json = JsonSerializer.Serialize(new { content = update.ToString() });await Response.WriteAsync($"data: {json}\n\n");await Response.Body.FlushAsync();}await _threadStore.SaveThreadAsync(thread);}
}
5.4 实际对话示例
场景 1:查询订单
User: 你好,我想查询订单 ORD-12345 的状态Agent: [调用 order_get_order 工具]Agent: 您好!我已经查到您的订单信息:订单号:ORD-12345
状态:已发货
商品:- iPhone 15 Pro × 1 - ¥7999- AirPods Pro × 1 - ¥1999
总金额:¥9998
下单时间:2025-10-25 14:30您的订单已经发货,需要我帮您查询物流信息吗?User: 好的,帮我查一下物流Agent: [调用 order_track_shipment 工具]Agent: 物流信息如下:快递公司:顺丰速运
运单号:SF1234567890
当前状态:运输中
预计送达:2025-10-30最新物流记录:- 2025-10-28 09:15 [北京分拨中心] 已发出- 2025-10-27 18:30 [北京分拨中心] 已到达- 2025-10-27 10:00 [深圳转运中心] 已发出您的包裹预计明天就能送达,请保持电话畅通哦!
场景 2:退货咨询
User: 我买的手机不太满意,可以退货吗?Agent: [调用 crm_get_return_policy 工具]Agent: 关于手机类商品的退货政策:退货期限:自收货之日起 7 天内
退货条件:1. 商品及包装保持完好,未激活使用2. 配件齐全(充电器、数据线、说明书等)3. 提供购买凭证(订单号)退款方式:原路退回(3-5 个工作日到账)请问您的手机是否符合以上条件?如果符合,我可以帮您创建退货工单。User: 符合的,我还没拆封Agent: [准备调用 crm_create_ticket 工具]
Agent: [等待人工审批...][审批通过]Agent: [调用 crm_create_ticket 工具]Agent: 好的,我已经为您创建了退货工单:工单号:TKT-98765
状态:待处理
负责客服:张小姐我们的客服会在 2 小时内联系您,安排上门取件。请保持电话畅通。退货流程:
1. 客服联系确认
2. 快递上门取件
3. 仓库验货
4. 退款处理(3-5 个工作日)还有其他需要帮助的吗?
5.5 监控与运维
使用 OpenTelemetry 收集的遥测数据可以在 Azure Monitor 或 Grafana 中可视化:
关键指标:
-
请求量:每分钟处理的对话数
-
响应时间:P50、P95、P99 延迟
-
工具调用统计:各个 MCP 工具的调用频率和成功率
-
错误率:按错误类型分类的失败率
-
用户满意度:基于对话结果的评分
告警规则:
-
错误率超过 5% → 发送告警
-
P95 延迟超过 3 秒 → 发送告警
-
MCP Server 连接失败 → 立即告警
-
审批队列积压超过 10 个 → 通知管理员
更多AIGC文章
RAG技术全解:从原理到实战的简明指南
更多VibeCoding文章

