1. 理解MCP/ChatGPT应用的核心概念
在开始用C#构建MCP/ChatGPT应用之前,我们需要先理解几个关键概念。MCP(Model Context Protocol)是一种开放标准协议,它充当AI模型与外部系统之间的桥梁。这个协议由Anthropic公司于2024年推出,目的是为大型语言模型(LLM)与外部数据、应用和服务之间的通信提供标准化和安全的方式。
MCP协议最大的价值在于它让AI不再局限于静态知识库,而是能够动态地检索实时信息并执行具体操作。想象一下,你正在和ChatGPT聊天,询问"我的订单状态如何",传统的AI可能只能给出一个标准回答。但通过MCP协议,ChatGPT可以实际连接到你的电商系统,查询真实的订单数据并返回给你。
MCP/ChatGPT应用就是基于这个协议构建的应用程序,它们可以在ChatGPT环境中运行,为用户提供丰富的交互体验。这类应用通常有两种形式:一种是直接在ChatGPT内部运行的App,另一种是通过MCP协议与ChatGPT集成的外部服务。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. MCP协议的状态管理机制
MCP协议在设计上非常灵活,支持两种不同的状态管理模式:有状态(Stateful)和无状态(Stateless)。理解这两种模式的差异对于构建高效可靠的MCP应用至关重要。
2.1 有状态MCP的工作原理
有状态MCP的核心特点是服务端会维护会话上下文。当ChatGPT(作为MCP客户端)首次连接到MCP服务时,服务端会创建一个唯一的session_id,并在整个会话期间维护这个状态。这种模式特别适合需要多步骤交互的复杂场景。
举个例子,假设你正在构建一个电商客服机器人,用户可能会先查询订单,然后选择特定订单进行退款,最后确认退款结果。在有状态模式下,服务端可以记住整个对话流程的上下文,不需要客户端在每次请求时都重复传递所有信息。
有状态MCP的技术实现通常需要:
- 会话存储(Session Store):保存session_id、用户上下文、工具状态等信息
- 状态一致性管理:在多服务器环境下确保会话状态同步
- 会话生命周期管理:设置合理的会话过期时间
2.2 无状态MCP的设计理念
与有状态MCP不同,无状态MCP服务不会在服务端保存任何会话信息。每次请求都必须包含执行该操作所需的全部上下文信息。这种模式更接近传统的RESTful API设计理念。
无状态MCP的优势在于:
- 架构简单,易于部署和扩展
- 天然适合Serverless架构
- 不需要额外的会话存储系统
- 水平扩展更加容易
但它的缺点是客户端需要承担更多责任,每次请求都要传递完整的上下文信息,对于复杂的多步骤交互来说会比较麻烦。
2.3 如何选择合适的状态模式
在实际项目中,选择有状态还是无状态MCP取决于你的具体需求:
适合有状态MCP的场景:
- 复杂的多步骤业务流程
- 需要维护长时间会话的应用
- 涉及流式输出或服务端推送的场景
- 需要恢复中间执行状态的功能
适合无状态MCP的场景:
- 简单的查询类API
- 单次请求即可完成的简单操作
- 需要快速部署到Serverless环境的服务
- 高并发但交互简单的接口
对于大多数刚开始接触MCP开发的C#开发者,我建议先从无状态模式入手,因为它的基础设施要求更简单,更容易快速验证概念。
3. 搭建C# MCP服务基础环境
现在让我们进入实战环节,看看如何用C#构建一个MCP服务。我们将使用.NET平台和几个关键NuGet包来实现这一目标。
3.1 项目初始化与依赖安装
首先创建一个新的ASP.NET Core Web API项目,然后添加以下NuGet包依赖:
xml复制<ItemGroup>
<PackageReference Include="ModelContextProtocol.AspNetCore" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.3" />
<PackageReference Include="Microsoft.OpenApi" Version="2.7.0" />
</ItemGroup>
这些包中,ModelContextProtocol.AspNetCore是核心的MCP协议实现,其他则是用于生成API文档的辅助包。
3.2 配置基础服务
在Program.cs中,我们需要配置基本的服务和中间件:
csharp复制var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
services.AddControllers();
// 开发环境下添加Swagger支持
if (builder.Environment.IsDevelopmentOrDev())
{
services.AddOpenApi();
services.AddEndpointsApiExplorer();
services.AddSwaggerGen();
}
// 添加MCP服务支持,配置为无状态模式
services.AddMcpServer().WithHttpTransport(options =>
{
options.Stateless = true;
})
.WithResources<WidgetResource>()
.WithTools<WidgetTool>();
var app = builder.Build();
// 开发环境下启用Swagger UI
if (app.Environment.IsDevelopmentOrDev())
{
app.MapOpenApi();
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseCors(c => c.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
app.MapControllers();
app.UseMiddleware<AppAuthorizationMiddleware>();
app.MapMcp("mcp");
app.Run();
这段代码做了以下几件事:
- 初始化了一个标准的ASP.NET Core应用
- 在开发环境下添加了Swagger支持
- 配置了MCP服务,并指定使用无状态模式
- 注册了自定义的资源(WidgetResource)和工具(WidgetTool)
- 添加了CORS、授权中间件等基础组件
3.3 定义MCP资源
MCP资源代表了你的应用可以提供的内容或功能。下面是一个简单的资源定义示例:
csharp复制[McpServerResourceType]
public class WidgetResource
{
private readonly IWebHostEnvironment environment;
private readonly IFileInfo file;
public WidgetResource(IWebHostEnvironment environment)
{
this.environment = environment;
this.file = new PhysicalFileProvider(
Path.Combine(environment.ContentRootPath, "wwwroot"))
.GetFileInfo("/index.html");
}
[McpServerResource(
MimeType = "text/html;profile=mcp-app",
Name = "sample-app",
Title = "Sample MCP App",
UriTemplate = "ui://widget/app.html")]
[McpMeta("openai/widgetPrefersBorder", false)]
[McpMeta("openai/widgetDomain", "example.com")]
[McpMeta("openai/outputTemplate", "ui://widget/app.html")]
[McpMeta("openai/widgetAccessible", true)]
public async ValueTask<string> Dashboard()
{
using var stream = file.CreateReadStream();
using var reader = new StreamReader(stream);
return await reader.ReadToEndAsync();
}
}
这个资源类定义了一个简单的HTML界面,当ChatGPT请求应用UI时,服务端会返回这个HTML内容。注意各种McpMeta属性,它们定义了应用在ChatGPT环境中的展现方式和行为特性。
3.4 实现MCP工具
工具(Tool)是MCP应用中实际执行业务逻辑的组件。下面是一个简单的工具实现:
csharp复制[McpServerToolType]
public class WidgetTool
{
[McpServerTool(Name = "get-time"), Description("获取服务器当前时间")]
[McpMeta("openai/outputTemplate", "当前服务器时间是: {time}")]
public static object GetServerTime()
{
return new { time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") };
}
}
这个工具提供了一个简单的获取服务器时间的功能。当ChatGPT调用这个工具时,它会返回服务器的当前时间。
4. 构建MCP应用的前端界面
虽然我们的后端使用C#实现,但MCP应用的前端通常还是基于现代Web技术栈。下面介绍如何使用Vue.js和Tailwind CSS构建应用界面。
4.1 前端项目结构
创建一个新的前端项目,目录结构如下:
code复制/ui
├── package.json
├── vite.config.ts
├── index.html
├── src
│ ├── mcp-app.ts
│ └── app.vue
package.json中需要包含以下关键依赖:
json复制{
"dependencies": {
"@modelcontextprotocol/ext-apps": "^1.2.2",
"@modelcontextprotocol/sdk": "^1.27.1",
"vue": "^3.5.30"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.5",
"typescript": "^5.9.3",
"vite": "^8.0.0",
"vite-plugin-singlefile": "^2.3.2"
}
}
4.2 配置Vite构建工具
vite.config.ts文件配置如下:
typescript复制import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { viteSingleFile } from "vite-plugin-singlefile";
const INPUT = process.env.INPUT || "index.html";
const isDevelopment = process.env.NODE_ENV === "development";
export default defineConfig({
plugins: [vue(), viteSingleFile()],
build: {
sourcemap: isDevelopment ? "inline" : undefined,
rollupOptions: { input: INPUT },
outDir: "../MCPServer/wwwroot",
emptyOutDir: false,
},
});
这个配置会将前端应用构建为单个HTML文件,并输出到后端项目的wwwroot目录下。
4.3 实现Vue组件
app.vue是应用的主要组件,其核心逻辑包括:
- 初始化MCP应用连接
- 处理工具调用和结果
- 与ChatGPT宿主环境交互
vue复制<script setup lang="ts">
import { ref, onMounted } from "vue";
import { App } from "@modelcontextprotocol/ext-apps";
const app = ref<App | null>(null);
const serverTime = ref("Loading...");
onMounted(async () => {
const instance = new App({ name: "Time App", version: "1.0.0" });
instance.ontoolresult = (result) => {
serverTime.value = result.content[0].text;
};
await instance.connect();
app.value = instance;
});
async function handleGetTime() {
if (!app.value) return;
const result = await app.value.callServerTool({
name: "get-time",
arguments: {}
});
serverTime.value = result.content[0].text;
}
</script>
<template>
<div class="app-container">
<h1>Time Display App</h1>
<p>Server Time: {{ serverTime }}</p>
<button @click="handleGetTime">Refresh Time</button>
</div>
</template>
<style>
.app-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
</style>
这个组件展示了如何与后端MCP服务交互,调用get-time工具并显示结果。
5. 实现MCP应用授权机制
MCP应用通常需要授权机制来保护敏感操作。MCP标准推荐使用OAuth 2.1协议进行授权。
5.1 配置OAuth元数据端点
首先,我们需要实现几个标准的OAuth元数据端点:
csharp复制[Route("/mcp/.well-known")]
[Route("/.well-known")]
[ApiController]
public class WellKnownController : ControllerBase
{
[HttpGet("oauth-protected-resource")]
public object GetOAuthProtectedResource()
{
return new
{
resource = "https://your-mcp-service.com",
authorization_servers = "https://your-mcp-service.com",
scopes_supported = new[] { "mcp-api" }
};
}
[HttpGet("oauth-authorization-server")]
public object GetOAuthServer()
{
return new
{
issuer = "https://your-mcp-service.com",
authorization_endpoint = "https://your-mcp-service.com/oauth/authorize",
token_endpoint = "https://your-mcp-service.com/oauth/token",
scopes_supported = new[] { "mcp-api" },
response_types_supported = new[] { "code" },
grant_types_supported = new[] { "authorization_code" }
};
}
}
这些端点提供了OAuth服务的基本信息,允许ChatGPT发现并连接到你的授权服务。
5.2 实现授权中间件
为了保护MCP端点,我们需要实现一个授权中间件:
csharp复制public class AppAuthorizationMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if (context.Request.Path.StartsWithSegments("/mcp") &&
!context.Request.Path.StartsWithSegments("/mcp/.well-known"))
{
var authHeader = context.Request.Headers.Authorization.ToString();
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer "))
{
context.Response.Headers.WWWAuthenticate =
"Bearer resource_metadata=http://localhost:5048/mcp/.well-known/oauth-protected-resource";
context.Response.StatusCode = 401;
await context.Response.WriteAsJsonAsync(new {
error = "invalid_token",
error_description = "Authorization required"
});
return;
}
// 实际项目中这里应该验证token的有效性
var token = authHeader.Substring(7);
if (!IsValidToken(token))
{
context.Response.StatusCode = 403;
return;
}
}
await next(context);
}
private bool IsValidToken(string token)
{
// 实际项目中实现token验证逻辑
return !string.IsNullOrEmpty(token);
}
}
这个中间件会检查所有/mcp路径下的请求,确保它们携带了有效的Bearer token。
6. 调试与测试MCP应用
开发MCP应用时,调试是非常重要的一环。Anthropic提供了MCP Inspector工具来帮助开发者测试和调试他们的MCP应用。
6.1 使用MCP Inspector
MCP Inspector是一个图形化工具,可以:
- 模拟ChatGPT对MCP服务的调用
- 查看请求和响应的完整细节
- 测试各种工具调用场景
- 验证授权流程
要使用MCP Inspector,你需要:
- 下载并安装MCP Inspector工具
- 配置你的MCP服务端点URL
- 设置必要的授权信息
- 开始测试各种工具调用
6.2 常见调试技巧
在开发MCP应用时,你可能会遇到以下常见问题及解决方法:
-
工具调用失败
- 检查工具方法的签名是否正确
- 确保所有必需的McpServerTool和McpMeta属性都已添加
- 验证输入参数是否符合预期格式
-
授权问题
- 确认中间件正确配置
- 检查token的生成和验证逻辑
- 确保.well-known端点返回正确的元数据
-
前端与后端通信问题
- 检查CORS配置
- 验证前端是否正确初始化了MCP连接
- 确保前后端的工具名称和参数匹配
-
性能问题
- 对于复杂操作,考虑实现异步处理
- 使用缓存优化频繁访问的数据
- 监控关键端点的响应时间
7. 部署与优化MCP应用
当你的MCP应用开发完成后,下一步就是部署到生产环境。以下是几个关键考虑因素:
7.1 部署架构建议
对于无状态MCP服务,推荐以下架构:
- 使用容器化部署(Docker)
- 通过Kubernetes或类似平台实现自动扩展
- 使用API网关处理流量管理和安全策略
- 考虑使用CDN加速静态资源
对于有状态MCP服务,还需要:
- 配置分布式会话存储(如Redis)
- 实现会话亲和性(Session Affinity)
- 设计合理的会话过期策略
7.2 性能优化技巧
-
减少初始加载时间
- 最小化前端资源
- 使用代码分割和懒加载
- 启用Gzip/Brotli压缩
-
优化工具调用
- 对耗时操作实现异步处理
- 使用缓存减少重复计算
- 批量处理相关操作
-
监控与日志
- 实现全面的日志记录
- 设置性能监控和告警
- 跟踪关键业务指标
7.3 安全最佳实践
-
认证与授权
- 使用标准的OAuth 2.1流程
- 实现精细化的权限控制
- 定期轮换密钥和证书
-
输入验证
- 对所有输入数据进行严格验证
- 防范注入攻击
- 限制请求大小和频率
-
数据保护
- 加密敏感数据
- 遵守数据隐私法规
- 实现安全的日志记录策略
8. 扩展MCP应用功能
基础功能实现后,你可以考虑扩展更高级的功能来提升用户体验。
8.1 实现流式响应
对于耗时的操作,可以实现流式响应,让用户能够实时看到进度:
csharp复制[McpServerTool(Name = "long-task")]
public async IAsyncEnumerable<string> ExecuteLongTask()
{
for (int i = 1; i <= 10; i++)
{
await Task.Delay(1000);
yield return $"Progress: {i * 10}%";
}
yield return "Task completed!";
}
8.2 添加服务端通知
MCP支持服务端主动通知客户端的功能:
csharp复制public class NotificationService
{
private readonly IMcpServerNotificationPublisher _publisher;
public NotificationService(IMcpServerNotificationPublisher publisher)
{
_publisher = publisher;
}
public async Task SendNotification(string sessionId, string message)
{
await _publisher.PublishAsync(sessionId, new Notification {
Content = new[] { new ContentItem { Text = message } }
});
}
}
8.3 集成外部服务
MCP应用可以轻松集成各种外部服务,例如:
csharp复制[McpServerTool(Name = "weather")]
public async Task<object> GetWeather([McpToolArgument]string city)
{
var client = new HttpClient();
var response = await client.GetAsync($"https://weather-api.com/{city}");
response.EnsureSuccessStatusCode();
var data = await response.Content.ReadFromJsonAsync<WeatherData>();
return new {
temperature = data.Temp,
condition = data.Condition
};
}
9. 实际应用场景示例
让我们看几个MCP/ChatGPT应用的实际用例,了解如何将它们应用到真实业务场景中。
9.1 电商客服助手
csharp复制[McpServerToolType]
public class ECommerceTool
{
private readonly OrderService _orderService;
public ECommerceTool(OrderService orderService)
{
_orderService = orderService;
}
[McpServerTool(Name = "get-orders")]
public async Task<object> GetCustomerOrders([McpToolArgument]string customerId)
{
var orders = await _orderService.GetOrdersByCustomer(customerId);
return orders.Select(o => new {
id = o.Id,
date = o.OrderDate,
amount = o.TotalAmount,
status = o.Status
});
}
[McpServerTool(Name = "cancel-order")]
public async Task<object> CancelOrder([McpToolArgument]string orderId)
{
var result = await _orderService.CancelOrder(orderId);
return new { success = result.Success, message = result.Message };
}
}
这个工具集成了电商系统的订单功能,允许客服通过ChatGPT界面查询和取消订单。
9.2 数据分析仪表板
csharp复制[McpServerResourceType]
public class AnalyticsResource
{
private readonly AnalyticsService _analytics;
public AnalyticsResource(AnalyticsService analytics)
{
_analytics = analytics;
}
[McpServerResource(Name = "analytics-dashboard")]
public async Task<object> GetDashboardData([McpToolArgument]DateRange range)
{
var data = await _analytics.GetDashboardData(range.Start, range.End);
return new {
visits = data.Visits,
conversions = data.Conversions,
revenue = data.Revenue,
popularProducts = data.PopularProducts
};
}
}
这个资源提供了业务数据分析功能,管理人员可以通过ChatGPT查询关键业务指标。
9.3 IT运维助手
csharp复制[McpServerToolType]
public class DevOpsTool
{
private readonly ServerMonitoringService _monitoring;
public DevOpsTool(ServerMonitoringService monitoring)
{
_monitoring = monitoring;
}
[McpServerTool(Name = "server-status")]
public async Task<object> CheckServerStatus([McpToolArgument]string serverName)
{
var status = await _monitoring.GetServerStatus(serverName);
return new {
cpu = status.CpuUsage,
memory = status.MemoryUsage,
disk = status.DiskUsage,
uptime = status.Uptime
};
}
[McpServerTool(Name = "restart-service")]
public async Task<object> RestartService(
[McpToolArgument]string serverName,
[McpToolArgument]string serviceName)
{
var result = await _monitoring.RestartService(serverName, serviceName);
return new { success = result.Success, message = result.Message };
}
}
这个工具集成了IT运维系统,允许运维人员通过ChatGPT界面监控服务器状态和执行基本运维操作。
10. 开发经验与最佳实践
在开发了几个MCP应用后,我总结了一些宝贵的经验和最佳实践,希望能帮助你少走弯路。
10.1 设计原则
-
保持工具单一职责
每个工具应该只做一件事,并且做好这件事。避免创建"全能"工具,这样会降低可维护性和复用性。 -
设计清晰的接口
工具的参数和返回值应该简单明了,使用标准的JSON可序列化类型。 -
考虑错误处理
为所有可能的错误情况设计清晰的错误响应,帮助用户理解问题所在。
10.2 性能优化
-
缓存频繁访问的数据
对于不经常变化的数据,使用内存缓存或分布式缓存减少数据库访问。 -
异步处理耗时操作
对于可能长时间运行的操作,考虑实现异步处理模式,先返回任务ID,然后允许客户端查询进度。 -
优化前端资源
前端界面应该尽可能轻量,使用代码分割和懒加载技术减少初始加载时间。
10.3 安全建议
-
最小权限原则
每个工具只授予完成其功能所需的最小权限。 -
输入验证
严格验证所有输入参数,防止注入攻击和其他安全威胁。 -
审计日志
记录所有敏感操作,便于事后审计和问题排查。
10.4 测试策略
-
单元测试工具逻辑
为每个工具方法编写全面的单元测试。 -
集成测试完整流程
测试从ChatGPT发起请求到返回响应的完整流程。 -
性能测试
模拟高负载场景,确保系统能够处理预期的流量。
10.5 调试技巧
-
使用MCP Inspector
充分利用MCP Inspector工具调试协议级别的交互。 -
日志记录
在关键路径添加详细的日志记录,但注意不要记录敏感信息。 -
模拟环境
建立与生产环境隔离的模拟环境进行端到端测试。
通过遵循这些最佳实践,你可以构建出更健壮、更安全、更易维护的MCP/ChatGPT应用。记住,MCP生态还在快速发展中,保持对新技术和新模式的关注,持续优化你的应用。
