本文介绍如何使用 .NET 生成模型上下文协议 (MCP) 代理。 在此示例中,MCP 客户端(用 C#/.NET 编写)连接到 MCP 服务器(用 TypeScript 编写)来管理待办事项列表。 客户端从服务器找到可用的工具,并将其发送到 Azure OpenAI 模型。 然后,用户可以使用日常语言与待办事项系统通信。
访问代码
查看 OpenAI MCP 代理构建基块 AI 模板。 此示例演示如何生成使用 MCP 客户端来使用现有 MCP 服务器的 OpenAI 代理。
跳转到 代码演练部分 ,了解此示例的工作原理。
体系结构概述
- MCP 客户端:连接到 MCP 服务器并查找可用的工具
- 聊天客户端:使用 Azure OpenAI 了解自然语言
- Blazor UI:提供用户可以聊天的 Web 界面
- 传输层:使用 Server-Sent 事件(SSE)实时发送消息
- 身份验证:使用 JWT 令牌保护连接安全
MCP 服务器在 Azure 容器应用(ACA)上作为容器化应用运行。 它使用 TypeScript 后端通过模型上下文协议向 MCP 客户端提供工具。 所有工具都使用后端 SQLite 数据库。
注释
访问 使用 Azure 容器应用生成 TypeScript MCP 服务器 ,查看本文中使用的 TypeScript MCP 服务器的代码演练。
成本
为了降低成本,此示例对大多数资源使用基本或消耗定价层。 根据需要调整层,并在完成后删除资源以避免费用。
先决条件
- Visual Studio Code - 支持 MCP 服务器开发的最新版本。
- .NET 9 SDK
- 适用于 Visual Studio Code 的 C# 开发工具包 Visual Studio Code 扩展
- GitHub Copilot Visual Studio Code 扩展
- GitHub Copilot Chat Visual Studio Code 扩展
- Azure 开发人员 CLI (azd)
- AI Foundry 扩展 Visual Studio Code 扩展
- 已部署的 AI Foundry gpt-5 微型模型
开发容器包含本文所需的所有依赖项。 可以在 GitHub Codespaces(在浏览器中)或使用 Visual Studio Code 在本地运行它。
若要遵循本文,请确保满足以下先决条件:
使用 AI Foundry VS Code 扩展部署 AI Foundry gpt-5 微型模型
gpt-5-mini使用 Visual Studio Code 中的 AI Foundry 扩展部署模型,请执行以下步骤:
创建 AI Foundry 项目并部署模型
- 若要创建 AI Foundry 项目并部署
gpt-5-mini模型,请按照使用适用于 Visual Studio Code 的 Azure AI Foundry 扩展(预览版)一文中的“入门”说明进行作。
创建 OpenAI 模型连接字符串
gpt-5-mini部署模型后,右键单击 AI Foundry 扩展中的模型,然后选择“复制 API 密钥”将模型的 API 密钥复制到剪贴板。接下来,在 AI Foundry 扩展中右键单击已
gpt-5-mini部署的模型,然后选择 “复制终结点 ”将模型的终结点复制到剪贴板,如以下屏幕截图所示:最后,使用以下格式为复制的终结点和 API 密钥为已
gpt-5-mini部署的模型创建连接字符串:Endpoint=<AZURE_OPENAI_ENDPOINT>;Key=<AZURE_OPENAI_API_KEY>本文稍后需要此连接字符串。
- Azure 订阅 - 免费创建一个
- Azure 帐户权限 – Azure 帐户必须具有
Microsoft.Authorization/roleAssignments/write权限,例如 基于角色的访问控制管理员、 用户访问管理员或 所有者。 如果没有订阅级权限,则必须为现有资源组授予 RBAC 并部署到该组。- Azure 帐户还需要
Microsoft.Resources/deployments/write订阅级别的权限。
- Azure 帐户还需要
- GitHub 帐户
开放开发环境
按照以下步骤设置具有所有必需依赖项的预配置开发环境。
GitHub Codespaces 以 Visual Studio Code for Web 作为界面运行 GitHub 托管的开发容器。 使用 GitHub Codespaces 进行最简单的设置,因为它附带了本文预安装的必需工具和依赖项。
重要
所有 GitHub 帐户每月最多可使用 Codespaces 60 小时,其中包含两个核心实例。 有关详细信息,请参阅 GitHub Codespaces 每月包含的存储和核心小时数。
使用以下步骤在 GitHub 存储库的main分支上Azure-Samples/openai-mcp-agent-dotnet创建新的 GitHub Codespace。
右键单击以下按钮,然后选择 新窗口中的“打开”链接。 此作使你能够并行打开开发环境和文档。
在 “创建代码空间 ”页上,查看并选择“ 创建新代码空间”。
等待代码空间开始。 可能需要几分钟时间。
确保已部署的模型名称为
gpt-5-mini. 如果部署的模型不同,请使用正确的部署名称进行更新src/McpTodo.ClientApp/appsettings.json。{ "OpenAI": { // Make sure this is the right deployment name. "DeploymentName": "gpt-5-mini" } }使用屏幕底部终端中的 Azure 开发人员 CLI 登录到 Azure。
azd auth login从终端复制代码,然后将其粘贴到浏览器中。 按照说明使用 Azure 帐户进行身份验证。
在此开发容器中执行其余任务。
注释
在本地运行 MCP 代理:
- 按照示例存储库的 “入门 ”部分中所述设置环境。
- 按照示例存储库中的 “获取 MCP 服务器应用 ”部分中的说明安装 MCP 服务器。
- 按照示例存储库的 “本地运行” 部分中的说明在本地运行 MCP 代理。
- 跳到“ 使用 TODO MCP 代理 ”部分继续。
部署和运行
示例存储库包含 MCP 代理 Azure 部署的所有代码和配置文件。 以下步骤将引导你完成示例 MCP 代理 Azure 部署过程。
部署到 Azure
重要
本部分中的 Azure 资源会立即开始花费资金,即使先停止命令,再完成该命令。
设置 JWT 令牌
通过在屏幕底部的终端中运行以下命令,为 MCP 服务器设置 JWT 令牌:
# zsh/bash ./scripts/set-jwttoken.sh# PowerShell ./scripts/Set-JwtToken.ps1
将 JWT 令牌添加到 azd environment 配置
通过在屏幕底部的终端中运行以下命令,将 JWT 令牌添加到 azd environment 配置:
# zsh/bash env_dir=".azure/$(azd env get-value AZURE_ENV_NAME)" mkdir -p "$env_dir" cat ./src/McpTodo.ServerApp/.env >> "$env_dir/.env"# PowerShell $dotenv = Get-Content ./src/McpTodo.ServerApp/.env $dotenv | Add-Content -Path ./.azure/$(azd env get-value AZURE_ENV_NAME)/.env -Encoding utf8 -Force注释
默认情况下,MCP 客户端应用受 ACA 内置身份验证功能的保护。 可以通过设置在运行
azd up之前关闭此功能:azd env set USE_LOGIN false针对 Azure 资源预配和源代码部署运行以下 Azure 开发人员 CLI 命令:
azd up使用下表回答提示:
Prompt 答案 环境名称 使用短小写名称。 添加名称或别名。 例如, my-mcp-agent。 环境名称将成为资源组名称的一部分。Subscription 选择要在其中创建资源的订阅。 位置(用于托管) 从列表中选择模型部署位置。 OpenAI 连接字符串 粘贴前面在 “创建 OpenAI 模型”连接字符串部分中创建的 OpenAI 模型的连接字符串 。 应用部署需要 5 到 10 分钟。
部署完成后,可以使用输出中的 URL 访问 MCP 代理。 URL 如下所示:
https://<env-name>.<container-id>.<region>.azurecontainerapps.io在 Web 浏览器中打开 URL 以使用 MCP 代理。
使用 TODO MCP 代理
运行 MCP 代理后,可以使用它在代理模式下提供的工具。 若要在代理模式下使用 MCP 工具,请执行以下作:
导航到客户端应用 URL 并登录到应用。
注释
如果将值
USE_LOGIN设置为false,则可能不会要求你登录。在聊天输入框中输入诸如“我需要在星期三向经理发送电子邮件”等提示,并注意如何根据需要自动调用工具。
MCP 代理使用 MCP 服务器提供的工具来完成请求并在聊天界面中返回响应。
尝试其他提示,例如:
Give me a list of to dos. Set "meeting at 1pm". Give me a list of to dos. Mark #1 as completed. Delete #1 from the to-do list.
浏览代码
示例存储库包含 MCP 代理 Azure 部署的所有代码和配置文件。 以下部分将指导你完成 MCP 代理代码的关键组件。
MCP 客户端配置和设置
应用程序在 . 中 Program.cs设置 MCP 客户端。 此配置定义如何连接以及要使用的选项。 该代码使用多种高级模式,包括 .NET Aspire 集成和服务默认值:
builder.Services.AddSingleton<IMcpClient>(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
var uri = new Uri(config["McpServers:TodoList"]!);
var clientTransportOptions = new SseClientTransportOptions()
{
Endpoint = new Uri($"{uri.AbsoluteUri.TrimEnd('/')}/mcp"),
AdditionalHeaders = new Dictionary<string, string>
{
{ "Authorization", $"Bearer {config["McpServers:JWT:Token"]!}" }
}
};
var clientTransport = new SseClientTransport(clientTransportOptions, loggerFactory);
var clientOptions = new McpClientOptions()
{
ClientInfo = new Implementation()
{
Name = "MCP Todo Client",
Version = "1.0.0",
}
};
return McpClientFactory.CreateAsync(clientTransport, clientOptions, loggerFactory).GetAwaiter().GetResult();
});
关键实现详细信息:
-
传输配置:
SseClientTransportOptions支持 Server-Sent 事件(SSE)和可流式传输 HTTP 传输。 传输方法取决于终结点 URL - 终结点以使用 Server-Sent 事件结尾/sse,而终结点以使用可流 HTTP 结尾/mcp。 此方法支持客户端和服务器之间的实时通信 -
身份验证标头:JWT 令牌进入
AdditionalHeaders,以确保服务器通信安全 -
客户端信息:
McpClientOptions告知服务器客户端的名称和版本 -
工厂模式:
McpClientFactory.CreateAsync()连接并完成协议握手
.NET Aspire 服务默认集成
应用程序使用 .NET Aspire 的服务默认模式进行交叉关注:
// McpTodo.ServiceDefaults/Extensions.cs
public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
{
builder.ConfigureOpenTelemetry();
builder.AddDefaultHealthChecks();
builder.Services.AddServiceDiscovery();
builder.Services.ConfigureHttpClientDefaults(http =>
{
// Turn on resilience by default
http.AddStandardResilienceHandler();
// Turn on service discovery by default
http.AddServiceDiscovery();
});
return builder;
}
服务默认优势:
- 可组合扩展方法:系统使用干净的生成器模式添加共享功能
- 标准复原处理程序:系统为你添加了内置的重试、断路器和超时规则
- 服务发现集成:系统在容器环境中自动查找服务
- OpenTelemetry 默认:系统无需进行任何设置工作即可完全监视
下图显示了交叉关注点与应用程序服务之间的关系:
配置 URL 解析
此示例包括不同环境的复杂 URL 解析:
// AspireUrlParserExtensions.cs
public static Uri Resolve(this Uri uri, IConfiguration config)
{
var absoluteUrl = uri.ToString();
if (absoluteUrl.StartsWith("https+http://"))
{
var appname = absoluteUrl.Substring("https+http://".Length).Split('/')[0];
var https = config[$"services:{appname}:https:0"]!;
var http = config[$"services:{appname}:http:0"]!;
return string.IsNullOrWhiteSpace(https) ? new Uri(http) : new Uri(https);
}
// Handle other URL formats...
}
配置管理功能:
- 服务发现抽象:系统完全处理开发和生产 URL
- 协议协商:系统首先选择 HTTPS,然后回退到 HTTP
- 配置约定:系统使用标准 .NET Aspire 服务设置模式
身份验证实现
此示例使用 JWT(JSON Web 令牌)身份验证来保护 MCP 客户端和服务器之间的连接。
dotnet user-secrets --project ./src/McpTodo.ClientApp set McpServers:JWT:Token "$TOKEN"
注释
脚本在之前在$TOKEN”部分中运行 Bash (set-jwttoken.sh) 或 PowerShell (Set-JwtToken.ps1) 脚本时自动创建变量。
这些脚本执行以下步骤:
- 在 MCP 服务器应用中运行
npm run generate-token以创建 JWT 令牌 - 分析生成的
.env文件以提取JWT_TOKEN值 - 自动将其存储在 MCPClient 的 .NET 用户机密中
MCP 客户端从配置中检索 JWT 令牌,并将其包含在 HTTP 标头中,以便在连接到 MCP 服务器时进行身份验证:
AdditionalHeaders = new Dictionary<string, string>
{
{ "Authorization", $"Bearer {config["McpServers:JWT:Token"]!}" }
}
此方法可确保:
- 安全通信:系统仅允许具有有效令牌的客户端连接到 MCP 服务器
- Token-Based 授权:JWT 令牌使系统无需存储会话数据即可验证用户
- 配置管理:系统在开发过程中安全地将敏感令牌存储在用户机密中
Azure 容器应用身份验证集成
基础结构显示了使用 Azure 容器应用内置身份验证和授权功能(“简易身份验证”)的高级身份验证模式:
// containerapps-authconfigs.bicep
resource containerappAuthConfig 'Microsoft.App/containerApps/authConfigs@2024-10-02-preview' = {
properties: {
identityProviders: {
azureActiveDirectory: {
enabled: true
registration: {
clientId: clientId
openIdIssuer: openIdIssuer
}
}
}
login: {
tokenStore: {
enabled: true
azureBlobStorage: {
blobContainerUri: '${storageAccount.properties.primaryEndpoints.blob}/token-store'
managedIdentityResourceId: userAssignedIdentity.id
}
}
}
}
}
高级身份验证功能:
- Zero-Code 身份验证:Azure 容器应用提供内置身份验证
- 存储的托管标识:系统在不带连接字符串的情况下安全地存储令牌
- 联合标识凭据:系统为 Kubernetes 样式的身份验证启用工作负荷标识
下图显示了组件之间的安全握手:
工具发现和注册
MCP 客户端在组件初始化 Chat.razor期间从服务器发现可用的工具:
protected override async Task OnInitializedAsync()
{
messages.Add(new(ChatRole.System, SystemPrompt));
tools = await McpClient.ListToolsAsync();
chatOptions.Tools = [.. tools];
}
工具发现的工作原理:
-
服务器查询:
McpClient.ListToolsAsync()向 MCP 服务器发送请求以列出可用工具 - 架构检索:服务器发送回具有名称、说明和输入架构的工具定义
-
工具注册:系统向
ChatOptions对象注册工具,使其可供 OpenAI 客户端使用 -
类型安全性:类
McpClientTool继承自AIFunction,与 Microsoft.Extensions.AI 顺利集成
下图显示了如何分析和注册工具架构:
OpenAI 集成和函数调用
聊天客户端配置演示 MCP 工具如何与 Azure OpenAI 集成:
var chatClient = openAIClient.GetChatClient(config["OpenAI:DeploymentName"]).AsIChatClient();
builder.Services.AddChatClient(chatClient)
.UseFunctionInvocation()
.UseLogging();
集成优势:
-
自动函数调用:扩展
.UseFunctionInvocation()基于 LLM 决策启用自动工具执行 - 轻松访问工具:MCP 工具充当 OpenAI 模型的内置功能
- 响应处理:系统自动将工具结果添加到聊天流
Real-Time 聊天实现
聊天界面 Chat.razor 演示了使用高级 Blazor 模式的流式处理响应和工具执行:
private async Task AddUserMessageAsync(ChatMessage userMessage)
{
CancelAnyCurrentResponse();
// Add the user message to the conversation
messages.Add(userMessage);
chatSuggestions?.Clear();
await chatInput!.FocusAsync();
// Stream and display a new response from the IChatClient
var responseText = new TextContent("");
currentResponseMessage = new ChatMessage(ChatRole.Assistant, [responseText]);
currentResponseCancellation = new();
await foreach (var update in ChatClient.GetStreamingResponseAsync([.. messages], chatOptions, currentResponseCancellation.Token))
{
messages.AddMessages(update, filter: c => c is not TextContent);
responseText.Text += update.Text;
ChatMessageItem.NotifyChanged(currentResponseMessage);
}
// Store the final response in the conversation, and begin getting suggestions
messages.Add(currentResponseMessage!);
currentResponseMessage = null;
chatSuggestions?.Update(messages);
}
流式处理实现功能:
-
Real-Time 更新:
GetStreamingResponseAsync()按位发送响应更新 - 工具执行:系统在流式处理过程中自动处理函数调用
-
UI 响应能力:
ChatMessageItem.NotifyChanged()实时更新 UI - 取消支持:用户可以取消长时间运行的作
高级 Blazor UI 模式
实现使用高级 UI 模式进行实时更新:
Memory-Safe 事件处理:
// ChatMessageItem.razor
private static readonly ConditionalWeakTable<ChatMessage, ChatMessageItem> SubscribersLookup = new();
public static void NotifyChanged(ChatMessage source)
{
if (SubscribersLookup.TryGetValue(source, out var subscriber))
{
subscriber.StateHasChanged();
}
}
自定义 Web 组件集成:
// ChatMessageList.razor.js
window.customElements.define('chat-messages', class ChatMessages extends HTMLElement {
connectedCallback() {
this._observer = new MutationObserver(mutations => this._scheduleAutoScroll(mutations));
this._observer.observe(this, { childList: true, attributes: true });
}
_scheduleAutoScroll(mutations) {
// Debounce the calls and handle smart auto-scrolling
cancelAnimationFrame(this._nextAutoScroll);
this._nextAutoScroll = requestAnimationFrame(() => {
const addedUserMessage = mutations.some(m =>
Array.from(m.addedNodes).some(n =>
n.parentElement === this && n.classList?.contains('user-message')));
// Smart scrolling logic...
});
}
});
高级状态管理:
// Chat.razor
private void CancelAnyCurrentResponse()
{
// If a response was cancelled while streaming, include it in the conversation so it's not lost
if (currentResponseMessage is not null)
{
messages.Add(currentResponseMessage);
}
currentResponseCancellation?.Cancel();
currentResponseMessage = null;
}
Blazor UI 优势:
- 混合 Web 组件:系统将 Blazor 服务器与自定义元素相结合,以提高性能
- Memory-Safe 事件处理:系统使用 ConditionalWeakTable 防止内存泄漏
- 智能自动滚动:系统通过取消启动提供用户友好的聊天行为
- 正常取消:当用户取消作时,系统会保存部分工作
请求/响应流
下面是典型的用户交互如何流经系统:
- 用户输入:用户键入一条消息,例如“向我的待办事项列表添加”购买杂货”
- 消息处理:系统将消息添加到对话历史记录
- LLM 分析:Azure OpenAI 分析请求并确定要使用的工具
-
工具发现:模型查找正确的 MCP 工具(例如)
addTodo - 工具执行:MCP 客户端使用所需的参数调用服务器
- 响应处理:系统将服务器响应添加到会话
- UI 更新:系统实时向用户显示结果
下图显示了消息如何从用户输入流经 OpenAI 到工具执行,然后流回到用户界面:
异步模式管理
应用程序演示了后台作的复杂异步模式:
// ChatSuggestions.razor
public void Update(IReadOnlyList<ChatMessage> messages)
{
// Runs in the background and handles its own cancellation/errors
_ = UpdateSuggestionsAsync(messages);
}
private async Task UpdateSuggestionsAsync(IReadOnlyList<ChatMessage> messages)
{
cancellation?.Cancel();
cancellation = new CancellationTokenSource();
try
{
var response = await ChatClient.GetResponseAsync<string[]>(
[.. ReduceMessages(messages), new(ChatRole.User, Prompt)],
cancellationToken: cancellation.Token);
// Handle response...
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
await DispatchExceptionAsync(ex);
}
}
后台任务优势:
-
Fire-and-Forget with Safety:系统使用
_ =模式进行适当的异常处理 - 智能上下文减少:系统限制会话历史记录以防止令牌溢出
- 智能取消:系统正确清理竞争作
错误处理和复原能力
实现包括多个复原模式:
private void CancelAnyCurrentResponse()
{
// If a response was cancelled while streaming, include it in the conversation so it's not lost
if (currentResponseMessage is not null)
{
messages.Add(currentResponseMessage);
}
currentResponseCancellation?.Cancel();
currentResponseMessage = null;
}
复原功能:
- 正常取消:当用户取消响应时,系统会保存正在进行的响应
- 连接恢复:SSE 传输自动处理连接丢弃
- 状态管理:UI 状态在错误期间保持一致
- 日志记录集成:系统提供用于调试和监视的完整日志记录
可观测性和运行状况检查
该应用程序包括复杂的可观测性模式:
智能运行状况检查配置:
// Extensions.cs
public static WebApplication MapDefaultEndpoints(this WebApplication app)
{
if (app.Environment.IsDevelopment())
{
// All health checks must pass for app to be considered ready
app.MapHealthChecks(HealthEndpointPath);
// Only health checks tagged with "live" must pass for app to be considered alive
app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("live")
});
}
return app;
}
使用智能筛选的 OpenTelemetry:
// Extensions.cs
.AddAspNetCoreInstrumentation(tracing =>
// Exclude health check requests from tracing
tracing.Filter = context =>
!context.Request.Path.StartsWithSegments(HealthEndpointPath)
&& !context.Request.Path.StartsWithSegments(AlivenessEndpointPath)
)
可观测性优势:
- Environment-Aware 终结点:安全意识的运行状况检查暴露
- Liveness 与 Readiness:Kubernetes 样式的运行状况检查模式
- 遥测降噪:从跟踪中筛选出例程运行状况检查
配置和环境设置
应用程序通过配置支持多个环境:
var openAIClient = Constants.GitHubModelEndpoints.Contains(endpoint.TrimEnd('/'))
? new OpenAIClient(credential, openAIOptions)
: new AzureOpenAIClient(new Uri(endpoint), credential);
配置选项:
- Azure OpenAI:生产部署通常使用 Azure OpenAI 服务
- GitHub 模型:开发方案可以使用 GitHub 模型
- 本地开发:支持本地 MCP 服务器实例
- 容器部署:用于生产托管的 Azure 容器应用
清理资源
使用 MCP 代理后,清理创建的资源以避免产生不必要的成本。
若要清理资源,请执行以下步骤:
通过在屏幕底部的终端中运行以下命令,删除 Azure 开发人员 CLI 创建的 Azure 资源:
azd down --purge --force
清理 GitHub Codespaces
删除 GitHub Codespaces 环境,以最大化每个核心的免费小时数。
重要
有关 GitHub 帐户的免费存储和核心小时数的详细信息,请参阅 GitHub Codespaces 每月包含的存储和核心小时数。
查找从
Azure-Samples/openai-mcp-agent-dotnetGitHub 存储库创建的活动 Codespaces。打开代码空间的上下文菜单,然后选择“ 删除”。
获取帮助
将问题记录到存储库 的问题。
