MCP模拟代理服务器

一个模拟MCP(模型上下文协议)代理服务器,支持两部分认证、SSE通知和基于CIMD的客户端身份验证,适用于开发和测试环境。

  • by joerob-msft
  • C#
  • 本地部署
  • 更新于
在 GitHub 查看 joerob-msft/mock-mcp-server

使用说明

模拟MCP服务器

一个模拟MCP(模型上下文协议)代理服务器,具有两部分身份验证、SSE通知和基于CIMD的客户端身份。作为一个。NET 8 ASP。NET Core应用程序。模拟一个真实世界的架构,其中MCP代理通过CIMD(AAD应用程序#1)对客户端进行身份验证,并通过单独的AAD应用程序(应用程序#2)对后端MCP服务器进行内部身份验证。

两部分身份验证流程

VS Code (MCP Client)
  │
  ├─ CIMD OAuth ──→ AAD App #1 ──→ proxy token
  │
  ├─ tools/list ──→ [discover_tools only]
  │
  ├─ tools/call discover_tools ──→ elicitation URL
  │       │
  │       └─ User opens browser → /backend-auth/login
  │             → Entra login (AAD App #2)
  │             → Backend tokens stored server-side
  │             → "Success, close window"
  │
  ├─ tools/list ──→ [full tools list]
  │
  └─ tools/call echo ──→ proxy uses stored backend token internally

第1部分:代理身份验证(CIMD+AAD应用程序#1)

  1. 客户在以下位置发现PRM /.well-known/oauth-protected-resource
  2. 客户端通过CIMD OAuth流进行身份验证(/oauth/authorize → /oauth/token)
  3. 客户收到 代理令牌 --这是客户端唯一看到的令牌
  4. 使用代理令牌, tools/list 仅返回 discover_tools

第2部分:后端认证(AAD应用程序#2,服务器端)

  1. 客户电话 discover_tools → 接收启发URL
  2. 用户在浏览器中打开URL→ 根据AAD应用程序#2进行身份验证
  3. 存储后端令牌(访问+刷新) 服务器端,由代理令牌键入
  4. 现在 tools/list 返回完整的工具和 tools/call 作品
  5. 客户端继续使用相同的代理令牌——后端令牌永远不会暴露

端点

方法 路线 描述
得到 /.well-known/oauth-protected-resource 受保护资源元数据(PRM)
得到 /.well-known/oauth-authorization-server OAuth授权服务器元数据
得到 /oauth/authorize OAuth授权端点(CIMD)
得到 /oauth/callback Entra ID回调(代理身份验证)
职位 /oauth/token OAuth令牌端点(代理身份验证)
获取/发布 /mcp MCP JSON-RPC端点
得到 /backend-auth/login 启动后端身份验证(AAD应用程序#2)
得到 /backend-auth/callback 后端身份验证回调
得到 /backend-auth/status 检查后端身份验证状态
得到 /cimd-policy 当前CIMD策略配置
获取/发布 /backend/mcp 后端MCP服务器(JSON-RPC+SSE)

支持的MCP方法

  • initialize -返回服务器功能
  • tools/list -返回可用工具(同意后展开)
  • tools/call -执行工具调用(echo、get_weather、compute)
  • resources/list -返回可用资源(空)
  • prompts/list -返回可用提示(空)

流媒体代理架构(SSE Sidecar)

后端身份验证完成后,代理需要从后端MCP服务器接收服务器发起的通知。这 后端服务器管理器 充当进程内SSE sidecar:

  1. 客户端↔ 代理人:上海证券交易所成立于 initialize (代理令牌)
  2. 后端认证后:sidecar打开SSE GET /backend/mcp (后端令牌)
  3. 后端发送通知→ sidecar读到了→ 继电器通过 McpServer.NotifyClient() → 写给客户SSE
  4. 工具调用是无状态的POST转发(代理→ 后端),不涉及SSE

在生产环境中,sidecar将是每个环境的一个单独的Azure容器应用程序,使后端SSE连接远离APIM。

配置

身份验证模式

集 MCP_AUTH_MODE 在 local.settings.json:

模式 描述
mock (默认) 无外部呼叫。本地生成的身份验证码和令牌。适合离线测试。
entra 真正的Azure AD集成。重定向到Entra ID进行用户身份验证。需要AAD应用程序注册。

授权策略

集 MCP_AUTH_STRATEGY 在 local.settings.json:

策略 描述
cimd (默认) 代理充当自己的OAuth授权服务器。客户端通过CIMD进行身份验证。适用于任何MCP客户端。
entra-direct PRM直接指向Entra ID。VS Code使用WAM进行无声身份验证(无浏览器弹出窗口)。仅适用于Entra注册客户。

环境变量

代理身份验证(AAD应用程序#1)

变量 默认值 描述
MCP_AUTH_MODE mock 身份验证模式: mock 或 entra
MCP_AUTH_STRATEGY cimd 授权策略: cimd (代理是OAuth AS)或 entra-direct (参赛者ID为AS)
MCP_EXPOSED_SCOPE Runtime.All AAD应用程序上暴露的作用域名称(用于entra直接PRM)
AZURE_TENANT_ID common 登录 ID
AZURE_CLIENT_ID -- AAD应用程序#1客户端ID(代理)
AZURE_CLIENT_SECRET -- AAD应用程序#1客户端机密(仅CIMD策略需要)

后端认证(AAD应用程序#2——真正的MCP服务器)

变量 默认值 描述
BACKEND_CLIENT_ID -- AAD应用程序#2客户端ID(后端)
BACKEND_CLIENT_SECRET -- AAD应用程序#2客户端机密
BACKEND_TENANT_ID AZURE_TENANT_ID 后端租户(默认为代理租户)
BACKEND_SCOPES openid profile offline_access 应用程序#2的范围

其他

变量 默认值 描述
MCP_VERBOSE_LOGGING false 启用详细的请求/响应日志记录
CIMD_POLICY_MODE open CIMD客户政策: open, allowlist,或 denylist
CIMD_ALLOWED_CLIENTS -- 允许的CIMD客户端模式的分号分隔列表(在模式下使用=allowlist)
CIMD_DENIED_CLIENTS -- 被拒绝的CIMD客户端模式的分号分隔列表(在模式下使用=denylist)

CIMD客户端策略

控制允许哪些MCP客户端通过CIMD进行身份验证。对政策进行评估 之前 获取CIMD文档(防止SSRF不受信任 client_id URL)。

模式 行为
open (默认) 允许所有CIMD客户端
allowlist 只允许明确列出的客户端/域
denylist 允许所有客户端,但明确阻止的客户端除外

图案类型 (以分号分隔 CIMD_ALLOWED_CLIENTS / CIMD_DENIED_CLIENTS):

  • 确切URL: https://vscode.dev/oauth/client-metadata.json
  • 确切域名: vscode.dev (匹配该主机上的任何CIMD)
  • 通配符域: *.microsoft.com (仅匹配子域)

示例——只允许列出VS代码和GitHub客户端:

{
  "Values": {
    "CIMD_POLICY_MODE": "allowlist",
    "CIMD_ALLOWED_CLIENTS": "https://vscode.dev/oauth/client-metadata.json;*.github.com"
  }
}

示例--阻止特定客户端:

{
  "Values": {
    "CIMD_POLICY_MODE": "denylist",
    "CIMD_DENIED_CLIENTS": "*.untrusted.dev"
  }
}

查看当前政策: curl http://localhost:7071/cimd-policy

入口模式设置(CIMD策略)

  1. 应用程序#1(代理): 在Azure门户中注册,添加重定向URI http://localhost:7071/oauth/callback
  2. 应用程序#2(后端): 单独注册,添加重定向URI http://localhost:7071/backend-auth/callback
  3. 在中设置环境变量 local.settings.json:
    {
      "Values": {
        "MCP_AUTH_MODE": "entra",
        "MCP_AUTH_STRATEGY": "cimd",
        "AZURE_TENANT_ID": "your-tenant-id",
        "AZURE_CLIENT_ID": "app1-client-id",
        "AZURE_CLIENT_SECRET": "app1-secret",
        "BACKEND_CLIENT_ID": "app2-client-id",
        "BACKEND_CLIENT_SECRET": "app2-secret",
        "BACKEND_SCOPES": "api://app2-client-id/.default"
      }
    }
    

Entra直接设置(通过WAM进行静默认证)

在此模式下,VS Code使用用户现有的Microsoft会话进行静默身份验证,不需要浏览器弹出窗口。PRM直接指向Entra ID作为授权服务器。

  1. 应用程序#1(代理): 在Azure门户中注册
  2. 公开API 在应用程序#1上:
    • 添加范围(例如。, Runtime.All)
    • 预授权VS Code的客户端ID: aebc6443-996d-45c2-90f0-388ff96faa56
  3. 应用程序#2(后端): 单独注册,添加重定向URI http://localhost:7071/backend-auth/callback
  4. 在中设置环境变量 local.settings.json:
    {
      "Values": {
        "MCP_AUTH_MODE": "entra",
        "MCP_AUTH_STRATEGY": "entra-direct",
        "MCP_EXPOSED_SCOPE": "Runtime.All",
        "AZURE_TENANT_ID": "your-tenant-id",
        "AZURE_CLIENT_ID": "app1-client-id",
        "BACKEND_CLIENT_ID": "app2-client-id",
        "BACKEND_CLIENT_SECRET": "app2-secret",
        "BACKEND_SCOPES": "api://app2-client-id/.default"
      }
    }
    
    注: AZURE_CLIENT_SECRET entra-direct不需要代理(代理只是一个资源服务器,而不是OAuth AS)。

本地开发

先决条件

本地运行

dotnet run
# Server starts on http://localhost:7071

测试CIMD流量

# 1. Check Protected Resource Metadata
curl http://localhost:7071/.well-known/oauth-protected-resource

# 2. Check OAuth Authorization Server Metadata
curl http://localhost:7071/.well-known/oauth-authorization-server

# 3. Start authorization (opens redirect)
curl -v "http://localhost:7071/oauth/authorize?response_type=code&client_id=https://client.example.dev/oauth/metadata.json&redirect_uri=https://client.example.dev/oauth/callback&scope=mcp.tools.execute&state=test123"

# 4. Exchange code for token
curl -X POST http://localhost:7071/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code&code=<AUTH_CODE>&client_id=https://client.example.dev/oauth/metadata.json"

# 5. Use token for MCP calls
curl -X POST http://localhost:7071/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -d '{"jsonrpc":"2.0","method":"initialize","params":{},"id":1}'

项目结构

mock-mcp-server/
├── McpServer.cs               # Proxy server logic (MCP, OAuth/CIMD, SSE, backend auth)
├── BackendMcpServer.cs        # Simulated backend MCP server (tools, SSE notifications)
├── BackendSseManager.cs       # SSE sidecar — manages backend SSE connections per session
├── Program.cs                 # ASP.NET Core host, route mapping, config loading
├── MockMcpServer.csproj       # Project file (.NET 8 Web SDK)
├── local.settings.json        # Local dev settings (auth mode, credentials)
├── samplecalls.http           # HTTP test file for VS Code REST Client
├── APIHUB_MCP_INTEGRATION.md  # Architecture, requirements, and demo script
└── README.md

许可证

麻省理工学院