Express-based MCP server with full OAuth 2.1 authorization and RFC9728 support.
MCP Gateway Demo
Express 实现的 MCP Server,带完整 OAuth 2.1 授权(符合 MCP Authorization 与 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
- Streamable HTTP 传输:
- 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 并修改。
启动
npm install
npm start
开发时:
npm run dev
联调(MCP 客户端,如 Cursor)
发现
客户端先请求 MCP 端点(无 token),收到401后从WWW-Authenticate取resource_metadata,再 GET 该 URL 得到authorization_servers、scopes_supported。授权
客户端用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)。访问 MCP
请求POST/GET /mcp时在 Header 中带Authorization: Bearer <access_token>,且Accept: application/json, text/event-stream。
本地测试时,可将 MCP 服务器 URL 设为 http://localhost:3000,授权服务器与资源服务器均为同源,使用上述发现与授权流程即可。
手动测试(curl)
# 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。
Environment Variables
PORTHTTP port for the serverMCP_RESOURCE_URIrequiredMCP resource canonical URI used for audience validation and metadataAUTH_ISSUERrequiredAuthorization server issuer URLJWT_SECRETrequiredHS256 secret key for signing tokensConfiguration
{"mcpServers": {"mcp-gateway-demo": {"command": "node", "args": ["/path/to/mcp-gateway-demo/index.js"], "env": {"PORT": "3000", "MCP_RESOURCE_URI": "http://localhost:3000", "AUTH_ISSUER": "http://localhost:3000", "JWT_SECRET": "your-secret-key"}}}}