MCP Gateway Demo
by yylgit
README.md
# MCP Gateway Demo
Express 实现的 MCP Server,带完整 OAuth 2.1 授权(符合 [MCP Authorization](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization) 与 [RFC9728](https://datatracker.ietf.org/doc/html/rfc9728) Protected Resource Metadata)。
## 功能
- **MCP Server(资源服务器)**
- Streamable HTTP 传输:`POST /mcp`、`GET /mcp`(SSE)
- 未认证请求返回 `401`,并带 `WWW-Authenticate: Bearer resource_metadata=..., scope=...`
- JWT 校验(audience = MCP 资源 URI),不足 scope 时返回 `403 insufficient_scope`
- **Protected Resource Metadata(RFC9728)**
- `GET /.well-known/oauth-protected-resource` 返回 `resource`、`authorization_servers`、`scopes_supported`
- 可选:`GET /.well-known/oauth-protected-resource/mcp`
- **内置 Authorization Server(Demo)**
- `GET /.well-known/oauth-authorization-server`(RFC8414)
- `GET /.well-known/openid-configuration`(含 `code_challenge_methods_supported`)
- `GET /oauth/authorize`(授权码 + PKCE S256)
- `POST /oauth/token`(换发 JWT,`aud` = 请求中的 `resource`)
## 环境变量
| 变量 | 说明 | 示例 |
|------|------|------|
| `PORT` | HTTP 端口 | `3000` |
| `MCP_RESOURCE_URI` | MCP 资源 canonical URI(用于 aud 校验与 metadata) | `http://localhost:3000` |
| `AUTH_ISSUER` | 授权服务器 issuer(可与 MCP 同源) | `http://localhost:3000` |
| `JWT_SECRET` | HS256 密钥(生产建议 RS256 + JWKS) | 至少 32 字符 |
复制 `.env.example` 为 `.env` 并修改。
## 启动
```bash
npm install
npm start
```
开发时:
```bash
npm run dev
```
## 联调(MCP 客户端,如 Cursor)
1. **发现**
客户端先请求 MCP 端点(无 token),收到 `401` 后从 `WWW-Authenticate` 取 `resource_metadata`,再 GET 该 URL 得到 `authorization_servers`、`scopes_supported`。
2. **授权**
客户端用 `authorization_servers` 中任一项作为 issuer,请求其 `/.well-known/oauth-authorization-server`,得到 `authorization_endpoint`、`token_endpoint`、`code_challenge_methods_supported`。
使用授权码 + PKCE(S256),并在授权与 token 请求中带上 `resource` = MCP 的 canonical URI(如 `http://localhost:3000`)。
3. **访问 MCP**
请求 `POST/GET /mcp` 时在 Header 中带 `Authorization: Bearer <access_token>`,且 `Accept: application/json, text/event-stream`。
本地测试时,可将 MCP 服务器 URL 设为 `http://localhost:3000`,授权服务器与资源服务器均为同源,使用上述发现与授权流程即可。
## 手动测试(curl)
```bash
# 1. 未认证 → 401
curl -i -X POST http://localhost:3000/mcp -H "Content-Type: application/json" -d '{}'
# 2. Protected Resource Metadata
curl -s http://localhost:3000/.well-known/oauth-protected-resource
# 3. Authorization Server 发现
curl -s http://localhost:3000/.well-known/oauth-authorization-server
# 4. 授权(浏览器或带 approve=yes 的 GET)
# 生成 PKCE:node -e "const c=require('crypto'); const v=c.randomBytes(32).toString('base64url'); console.log('verifier:', v, 'challenge:', c.createHash('sha256').update(v).digest('base64url'));"
# 在浏览器打开 /oauth/authorize?response_type=code&client_id=test&redirect_uri=http://localhost:3000/callback&scope=mcp:read%20mcp:write&state=1&code_challenge=<CHALLENGE>&code_challenge_method=S256&resource=http://localhost:3000
# 同意后从 redirect 的 URL 中取出 code
# 5. 换 Token
curl -s -X POST http://localhost:3000/oauth/token -H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code&code=<CODE>&redirect_uri=http://localhost:3000/callback&client_id=test&code_verifier=<VERIFIER>&resource=http://localhost:3000"
# 6. 带 Token 调用 MCP
curl -s -X POST http://localhost:3000/mcp -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'
```
## 安全说明
- 生产环境请使用 **HTTPS**。
- `JWT_SECRET` 需足够强,或改用 RS256 + JWKS。
- 本 Demo 允许的 `redirect_uri` 包含 localhost;生产应严格限制为已注册的 redirect_uri。
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues