1. 项目概述:C#与MCP/ChatGPT应用开发
在当今AI技术快速发展的背景下,大型语言模型(LLM)与外部系统的集成已成为提升AI实用性的关键。MCP(Model Context Protocol)作为一种开放标准协议,为LLM与外部数据、应用和服务之间的通信提供了安全且标准化的"语言"。使用C#开发基于MCP协议的ChatGPT应用,能够充分利用.NET生态系统的强大功能和稳定性,构建高效、可靠的AI集成解决方案。
MCP协议由Anthropic于2024年推出,旨在解决LLM与外部系统交互的标准化问题。它充当桥梁,使AI不再局限于静态知识,而成为能够检索实时信息并执行操作的动态智能体。通过MCP,开发者可以构建复杂的AI应用,如多步骤业务流程、文件处理、任务编排等,同时支持流式输出、进度通知和服务端主动推送等高级功能。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. MCP协议核心概念解析
2.1 MCP协议架构设计
MCP协议在设计上支持两种主要模式:有状态(Stateful)和无状态(Stateless)。这两种模式各有特点,适用于不同的应用场景。
有状态MCP架构:
code复制┌────────────────────┐
│ ChatGPT │
│ (MCP Client) │
└────────┬───────────┘
│ 1. init + OAuth
▼
┌──────────────────────────────┐
│ MCP Server (Stateful) │
│ ┌──────────────────────────┐ │
│ │ Session Store │ │
│ │ - session_id │ │
│ │ - user context │ │
│ │ - tool state │ │
│ │ - progress │ │
│ │ - auth/session cache │ │
│ └──────────────────────────┘ │
│ │
│ Tools / Resources / Logic │
└────────┬─────────────────────┘
│
▼
Backend APIs / DB
无状态MCP架构:
code复制┌────────────────────┐
│ ChatGPT │
│ (MCP Client) │
└────────┬───────────┘
│ 每次请求都完整、自包含
▼
┌──────────────────────────────┐
│ MCP Server (Stateless) │
│ │
│ No session storage │
│ No remembered context │
│ │
│ Tools / Resources / Logic │
└────────┬─────────────────────┘
│
▼
Backend APIs / DB
2.2 有状态与无状态MCP对比
| 维度 | 有状态MCP | 无状态MCP |
|---|---|---|
| 会话管理 | 有session_id | 无 |
| 上下文保存 | Server保存 | Client每次传递 |
| OAuth/Token处理 | 可缓存、可复用 | 每次解析 |
| 初始化协商 | 协商后复用 | 每次独立处理 |
| Streaming支持 | 强支持 | 较弱 |
| 服务端通知 | 更自然 | 较难实现 |
| 多步骤流程 | 非常适合 | 实现麻烦 |
| 长任务 | 适合 | 不理想 |
| 部署难度 | 较高 | 较低 |
| 扩展性 | 需考虑状态一致性 | 更容易水平扩展 |
2.3 MCP协议选择建议
有状态MCP适合以下场景:
- AI Agent工具链
- 多步骤业务流程
- ChatGPT App的复杂交互
- 文件处理、任务编排
- 长任务执行
- Streaming输出
- 进度通知
- 服务端主动推送
- 需要恢复中间执行状态
无状态MCP适合以下场景:
- 简单查询API
- 单轮调用即完成
- 数据读取类工具
- 高并发简单接口
- 快速部署到serverless或edge
- 不需要通知、不需要长流程
3. C#实现MCP/ChatGPT应用开发
3.1 开发环境准备
首先需要安装必要的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>
3.2 基础服务配置
在Program.cs中配置基础服务:
csharp复制var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
services.AddControllers();
if (builder.Environment.IsDevelopmentOrDev())
{
services.AddOpenApi();
services.AddEndpointsApiExplorer();
services.AddSwaggerGen();
}
services.AddMcpServer().WithHttpTransport(options =>
{
options.Stateless = true; // 使用无状态模式
})
.WithResources<WidgetResource>()
.WithTools<WigetTool>(); // 添加MCP资源和工具
var app = builder.Build();
if (app.Environment.IsDevelopmentOrDev())
{
app.MapOpenApi();
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseCors(c => c.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
app.MapControllers();
app.UseHttpMetrics();
app.MapMetrics();
app.UseMiddleware<AppAuthorizationMiddleware>(); // 授权验证中间件
app.MapMcp("mcp"); // 映射MCP端点
app.Run();
3.3 MCP资源定义
定义MCP资源类,用于提供UI界面:
csharp复制[McpServerResourceType]
public class WidgetResource
{
private readonly bool isDevelopment;
private readonly IFileInfo file;
public WidgetResource()
{
this.file = new PhysicalFileProvider(Path.Combine(environment.ContentRootPath, "wwwroot"))
.GetFileInfo("/index.html");
}
[McpServerResource(MimeType = "text/html;profile=mcp-app", Name = "xxx-app",
Title = "xxx-app", UriTemplate = "ui://widget/app.html")]
[McpMeta("openai/widgetPrefersBorder", false)]
[McpMeta("openai/widgetDomain", "xxxx")]
[McpMeta("openai/outputTemplate", "ui://widget/app.html")]
[McpMeta("openai/widgetAccessible", true)]
[McpMeta("openai/widgetCSP", """
{
"connect_domains": [
"https://xxx.com"
],
"resource_domains": [
]
}
""")]
public async ValueTask<string> Dashboard()
{
using var s = file.CreateReadStream();
return await s.ReadToEndAsync();
}
}
3.4 MCP工具定义
定义MCP工具类,用于处理业务逻辑:
csharp复制[McpServerToolType]
public class WigetTool
{
[AppAuth]
[McpServerTool(Name = "app"), Description(@"xxxx mcp app.")]
[McpMeta("openai/outputTemplate", "ui://widget/app.html")]
[McpMeta("openai/toolInvocation/invoking", "Loading app...")]
[McpMeta("openai/toolInvocation/invoked", "App loaded")]
[McpMeta("openai/widgetAccessible", true)]
[McpMeta("ui", JsonValue = """
{
"resourceUri":"ui://widget/app.html"
}
""")]
public static string GetApp()
{
return $"Loading";
}
}
4. 前端UI开发与集成
4.1 前端项目配置
前端项目使用Vue 3和Tailwind CSS构建,package.json配置如下:
json复制{
"name": "ui",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"watch": "cross-env INPUT=index.html vite build --watch"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"@modelcontextprotocol/ext-apps": "^1.2.2",
"@modelcontextprotocol/sdk": "^1.27.1",
"vue": "^3.5.30",
"zod": "^4.3.6"
},
"devDependencies": {
"@tailwindcss/vite": "^4.2.1",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/node": "^25.5.0",
"@vitejs/plugin-vue": "^6.0.5",
"concurrently": "^9.2.1",
"cross-env": "^10.1.0",
"tailwindcss": "^4.2.1",
"ts-to-zod": "^5.1.0",
"tsx": "^4.21.0",
"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";
import tailwindcss from '@tailwindcss/vite'
const INPUT = process.env.INPUT;
if (!INPUT) {
throw new Error("INPUT environment variable is not set");
}
const isDevelopment = process.env.NODE_ENV === "development";
export default defineConfig({
plugins: [vue(),tailwindcss(), viteSingleFile()],
build: {
sourcemap: isDevelopment ? "inline" : undefined,
cssMinify: !isDevelopment,
minify: !isDevelopment,
rollupOptions: {
input: INPUT,
},
outDir: "../mcp/src/MCPServer/wwwroot",
emptyOutDir: false,
},
});
4.3 Vue组件实现
App.vue组件实现MCP功能:
vue复制<script setup lang="ts">
import { ref, onMounted, watchEffect } from "vue";
import {
App,
applyDocumentTheme,
applyHostFonts,
applyHostStyleVariables,
type McpUiHostContext,
} from "@modelcontextprotocol/ext-apps";
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
function extractTime(result: CallToolResult): string {
const { text } = result.content?.find((c) => c.type === "text")!;
return text;
}
const app = ref<App | null>(null);
const hostContext = ref<McpUiHostContext | undefined>();
const serverTime = ref("Loading...");
const messageText = ref("This is a messsssage text.");
const logText = ref("This is log text.");
const linkUrl = ref("https://modelcontextprotocol.io/");
// 应用主机样式
watchEffect(() => {
const ctx = hostContext.value;
if (ctx?.theme) {
applyDocumentTheme(ctx.theme);
}
if (ctx?.styles?.variables) {
applyHostStyleVariables(ctx.styles.variables);
}
if (ctx?.styles?.css?.fonts) {
applyHostFonts(ctx.styles.css.fonts);
}
});
onMounted(async () => {
const instance = new App({ name: "Sellingpilot App", version: "1.0.0" });
instance.ontoolinput = (params) => {
console.info("Received tool call input:", params);
};
instance.ontoolresult = (result) => {
console.info("Received tool call result:", result);
serverTime.value = extractTime(result);
};
instance.ontoolcancelled = (params) => {
console.info("Tool call cancelled:", params.reason);
};
instance.onerror = console.error;
instance.onhostcontextchanged = (params) => {
hostContext.value = { ...hostContext.value, ...params };
};
await instance.connect();
app.value = instance;
hostContext.value = instance.getHostContext();
});
async function handleGetTime() {
if (!app.value) return;
try {
console.info("Calling get-time tool...");
const result = await app.value.callServerTool({
name: "get-time",
arguments: {}
});
console.info("get-time result:", result);
serverTime.value = extractTime(result);
} catch (e) {
console.error(e);
serverTime.value = "[ERROR]";
}
}
// 其他方法实现...
</script>
<template>
<div class="min-h-screen bg-gray-50 flex items-center justify-center p-6">
<div class="w-full max-w-md bg-white p-8 rounded-xl shadow-lg">
<h1 class="text-2xl font-semibold text-center text-gray-800 mb-6">
Vue 3 + Tailwind CSS 示例
</h1>
<!-- 界面元素实现 -->
</div>
</div>
</template>
5. 授权与安全实现
5.1 OAuth 2.1元数据端点
实现OAuth 2.1标准的元数据端点:
csharp复制[Route("/mcp/.well-known")]
[Route("/.well-known")]
[ApiController]
public class WellKnownController : ControllerBase
{
[HttpGet("oauth-protected-resource")]
[HttpGet("oauth-protected-resource/mcp")]
public object GetOAuthProtectedResource()
{
return new
{
resource = "https://xxxxmcp",
authorization_servers = "https://xxxxmcp",
scopes_supported = new string[] { "xxxxmcp" }
};
}
[HttpGet("oauth-authorization-server")]
public object GetOAuthServer()
{
return new
{
issuer = "https://xxxxmcp",
authorization_endpoint = "https://xxxxmcp/oauth/authorize",
token_endpoint = "https://xxxxmcp/oauth/token",
registration_endpoint = "http://localhost:5048/mcp/.well-known/oauth-client-registration",
response_types_supported = new string[] { "code" },
grant_types_supported = new string[] { "authorization_code", "refresh_token" },
subject_types_supported = new string[] { "public" },
id_token_signing_alg_values_supported = new string[] { "RS256" },
scopes_supported = new string[] { "xxxxmcp" },
};
}
[HttpGet("oauth-client-registration")]
public object GetOAuthClientRegistration()
{
return new
{
client_id = "xxx",
client_secret = "xxx",
client_id_issued_at = DateTime.UtcNow.ToUnixTimestamp() / 1000,
client_secret_expires_at = 0,
};
}
}
5.2 授权中间件实现
实现授权验证中间件:
csharp复制public class AppAuthorizationMiddleware : IMiddleware
{
private readonly IRestApiClientFactory clientFactory;
public AppAuthorizationMiddleware(IRestApiClientFactory clientFactory)
{
this.clientFactory = clientFactory;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if (context.Request.Path.StartsWithSegments("/mcp") &&
!context.Request.Path.StartsWithSegments("/mcp/.well-known"))
{
var h = context.Request.Headers.Authorization.ToString();
if (h.StartsWith("Bearer "))
{
h = h.Substring(7);
}
var invalid = h.IsNullOrEmpty();
// 实际验证逻辑,如JWT验证
if (invalid)
{
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 or access token is invalid"
});
return;
}
}
await next(context);
}
}
6. 开发注意事项与最佳实践
6.1 开发环境配置建议
-
开发工具选择:
- 推荐使用Visual Studio 2022或Rider作为C#开发IDE
- 前端开发可使用VS Code配合Volar插件
- 安装MCP Inspector工具用于调试MCP协议交互
-
调试技巧:
- 使用Swagger UI测试API端点
- 配置好CORS策略以便前端开发调试
- 使用日志记录MCP协议交互细节
-
性能优化:
- 对于无状态MCP,考虑使用内存缓存减少重复计算
- 优化数据库查询,特别是频繁调用的工具方法
- 前端使用Vite的HMR功能提高开发效率
6.2 常见问题解决方案
-
授权问题:
- 确保OAuth元数据端点可访问
- 检查Bearer token格式是否正确
- 验证token签名和有效期
-
跨域问题:
- 配置正确的CORS策略
- 检查前端请求的Origin头
- 确保开发服务器端口配置正确
-
协议兼容性问题:
- 确认使用的MCP协议版本
- 检查工具和资源的注解配置
- 验证前端SDK版本与后端协议兼容性
-
部署问题:
- 确保生产环境HTTPS配置正确
- 检查静态文件发布路径
- 验证数据库连接字符串
6.3 性能优化建议
-
后端优化:
- 使用异步编程模式提高吞吐量
- 实现响应缓存减少重复计算
- 优化数据库查询和索引
-
前端优化:
- 使用Vite的生产构建配置
- 实现按需加载减少初始包大小
- 使用CDN分发静态资源
-
协议优化:
- 精简MCP元数据大小
- 优化工具调用参数结构
- 考虑使用二进制协议替代JSON
7. 项目扩展与进阶方向
7.1 复杂业务流程实现
对于需要多步骤交互的复杂业务流程,可以考虑以下实现方式:
-
工作流引擎集成:
- 集成如WorkflowCore等.NET工作流引擎
- 将业务流程建模为工作流
- 使用MCP工具作为工作流活动节点
-
状态机实现:
- 使用状态模式设计复杂交互
- 持久化业务流程状态
- 提供状态恢复机制
-
事件驱动架构:
- 使用消息队列解耦系统组件
- 实现事件溯源模式
- 提供业务流程可视化监控
7.2 AI能力增强
-
LLM集成扩展:
- 支持多模型切换和路由
- 实现模型微调接口
- 提供提示工程管理
-
知识库集成:
- 连接企业知识库系统
- 实现向量搜索能力
- 提供知识更新机制
-
多模态支持:
- 扩展支持图像处理
- 集成语音输入输出
- 提供文件解析能力
7.3 系统监控与运维
-
可观测性实现:
- 集成Prometheus和Grafana
- 收集MCP协议指标
- 设置业务告警规则
-
日志分析:
- 集中式日志收集
- 结构化日志记录
- 关键操作审计跟踪
-
性能分析:
- 使用Application Insights
- 分析工具调用性能
- 识别系统瓶颈
在实际开发中,根据具体业务需求选择合适的扩展方向,逐步构建功能完善、性能优异的MCP/ChatGPT应用系统。
