MCP Janus代理服务器
安全 符合OAuth 2.1的MCP(模型上下文协议)代理服务器 用Go编写,位于MCP客户端和受保护的MCP服务器之间,通过强大的安全控制管理所有通信。
🏛️ 为什么选择Janus?
泛科普特语第11个月-ShortNamePossible古罗马的门、大门、过渡和通道之神永恒地站着,有两张脸——一张凝视过去,另一张凝视未来。作为阈值和开端的守护者,Janus监视着所有进入和退出的事物,主持着变革和二元性。
该代理体现了Janus的精神:
- 🚪 网关守护者:就像入口处的Janus一样,这个代理站在客户端和服务器之间,以坚定不移的警惕控制通道
- 👁️ 双面视野:一个面验证来自客户端的传入请求,另一个面保护与上游服务器的通信——同时看到两个世界
- 🔄 过渡大师:将客户端令牌转换为安全凭据,调解信任域之间的传递
- ⚖️ 边界守护者:强制公共领域和受保护领域之间的神圣边界,只允许有价值的人通过
- 🌅 新开端的先驱:每一个请求都是一个新的开始,每一个令牌都是新的开始
正如古罗马人在任何尝试开始时调用Janus一样,这个代理启动了每一个安全的MCP连接,成为世界之间门户的永恒哨兵。
🎯 项目目标
实现一个安全代理,该代理:
- ✅ 问题 不透明的不记名代币 发送给MCP客户端(非直通)
- ✅ 实现 OAuth 2.0/OAuth 2.1 使用PKCE的流
- ✅ 用途 AEAD加密 (AES-256-GCM)用于令牌安全
- ✅ 强制执行 受众绑定 资源验证
- ✅ 支持 关键点旋转 基于KID的管理
- ✅ 提供 结构化测井 不泄露秘密
📋 特性
安全
- 无令牌传递:代理发出自己的不透明令牌,从不转发客户端令牌
- AEAD加密:AES-256-GCM用于不透明令牌加密
- JWT集成:解密不透明令牌以验证JWT声明
- 索赔映射:IdP声明到HTTP标头的可配置映射
- 动态客户端注册:具有唯一客户端ID的加密客户端凭据
- HTTPS就绪:支持TLS的生产就绪(HTTP用于开发)
OAuth 2.1合规性
- 授权码+PKCE:全面支持安全授权流
- 动态客户端注册:符合RFC 7591的客户端注册
- 受保护资源元数据:资源发现的RFC 9728合规性
- 代币交换:与上游IdP进行安全令牌交换
- 刷新令牌支持:已实现令牌刷新端点
建筑
- Gin框架:支持中间件的高性能HTTP路由器
- 模块化服务:与身份验证、元数据和代理服务的关注点分离
- 配置驱动:基于YAML的配置,带有环境变量覆盖
- 可测试的:包含模拟和表驱动测试的全面测试套件
- 生产准备就绪:优雅的关机、健康检查、结构化日志记录
🏗️ 建筑
┌─────────────┐
│ MCP Client │ ← Receives opaque bearer token
└──────┬──────┘
│ Authorization: Bearer
▼
┌─────────────────────────────────┐
│ MCP Proxy Server (Go) │
│ ┌──────────────────────────┐ │
│ │ Gin HTTP Router │ │
│ │ - Auth Service │ │
│ │ - Metadata Service │ │
│ │ - Encryption Utility │ │
│ │ - Config Management │ │
│ └──────────────────────────┘ │
└──────┬─────────────┬────────────┘
│ │ Authorization: Bearer
│ ▼
│ ┌─────────────────┐
│ │ Protected MCP │
│ │ Server │
│ └─────────────────┘
│
│ OAuth 2.1 flow
▼
┌──────────────────────┐
│ Identity Provider │
│ (Authorization Svr) │
└──────────────────────┘🚀 快速开始
先决条件
- 转到1.21或更高版本
- TLS证书(用于生产)
安装
git clone
cd mcp-janus
task install
task build配置
创建一个 config.yaml 文件或设置环境变量:
proxy:
base_url: http://localhost:8080
listen_addr: ":8080"
idp:
issuer_url: https://auth.example.com
client_id: mcp-proxy-client
client_secret: your-secret-here
authorization_endpoint: https://auth.example.com/oauth/authorize
token_endpoint: https://auth.example.com/oauth/token
claims_mapping:
sub: X-Sub
name: X-Full-Name
email: X-Email
encryption:
master_key: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
upstream:
name: my-mcp-server
resource: https://mcp.example.com
base_url: https://mcp.example.com
path_prefix: /mcp环境变量覆盖:
export MCP_PROXY_BASE_URL="https://proxy.example.com"
export MCP_IDP_CLIENT_SECRET="your-secret-here"跑步
# Development (HTTP) - using Task
task run
# Or run directly with go
go run cmd/proxy/main.go
# Production - build first
task build
./bin/mcpproxy健康检查
curl http://localhost:8080/health
# OK🧪 使用测试服务器进行测试
该项目包括一个测试MCP服务器(cmd/mcpserver)它实现了一个伪天气工具,非常适合在不需要真正的MCP服务器的情况下测试代理。
快速测试设置
# Build both servers
task build
task build-testserver
# Terminal 1: Start the test MCP server
task run-testserver
# Runs on http://localhost:8081
# Terminal 2: Start the proxy (configure to point to localhost:8081)
task run
# Terminal 3: Run the test script
task test-testserver测试服务器功能
- 假天气工具:返回任何城市和日期的确定性天气数据
- MCP协议:实施
tools/list,tools/call,以及initialize方法 - 无依赖关系:独立运行,便于测试
看 测试指导 和 测试服务器README 了解详情。
📖 API终点
发现端点
受保护资源元数据(RFC 9728)
GET /.well-known/oauth-protected-resource返回包含授权服务器信息的代理资源元数据。
OpenID配置
GET /.well-known/openid-configuration返回OpenID连接发现文档。
动态客户端注册
POST /register
Content-Type: application/json
{
"client_name": "My MCP Client",
"redirect_uris": ["http://localhost:3000/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"]
}响应:使用加密的客户端凭据 client_id 和 client_secret.
OAuth授权流程
授权端点
GET /auth?response_type=code&client_id=&redirect_uri=&state=&code_challenge=&code_challenge_method=S256使用上游IdP启动OAuth授权。
回调端点
GET /callback?code=&state=处理来自上游IdP的OAuth回调,并向客户端返回加密的授权码。
令牌端点
POST /token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=&redirect_uri=&client_id=&client_secret=&code_verifier=答复:
{
"access_token": "",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "",
"scope": "openid profile email"
}刷新令牌终结点
POST /refresh
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=&client_id=&client_secret=注意:目前返回501 Not Implemented。
MCP代理
GET /mcp/*
Authorization: Bearer 使用解密的真实令牌将经过身份验证的请求转发到上游MCP服务器。
🔐 安全模型
不透明令牌流
- 客户注册:客户端注册并接收加密
client_id包含重定向URI和生成的秘密 - 授权:客户端启动OAuth流,代理与上游IdP协调
- 代币交换:代理与IdP交换授权码,接收JWT令牌
- 加密:代理使用AES-256-GCM加密JWT令牌
- 不透明令牌:客户端接收加密令牌(对客户端不透明,包含真实的JWT)
- 请求转发:代理解密令牌,验证JWT,将上游令牌注入转发的请求中
加密客户端ID结构
这 client_id 在注册过程中返回的是一个加密的有效载荷,其中包含:
- 重定向URI
- 生成的客户端机密
- 注册元数据
这确保了客户端凭据的安全性和防篡改性。
令牌加密
加密方法:AES-256-GCM(AEAD)
过程:
- 使用主密钥加密的IdP中的真实JWT令牌
- 为每次加密操作生成Nonce
- 密文+随机数编码为base64url
- 客户端收到不透明的令牌字符串
解密和验证:
- 从中提取承载令牌
Authorization头球 - 使用主密钥解密
- 解析和验证JWT声明
- 根据配置将声明映射到HTTP标头
- 使用真实令牌转发请求
索赔映射
代理支持将IdP JWT声明映射到HTTP标头以供上游使用:
idp:
claims_mapping:
sub: X-Sub
name: X-Full-Name
email: X-Email
upn: X-UPN关键安全原则
- 无令牌传递:客户端永远看不到或使用真实的IdP令牌
- 加密存储:所有在静止和传输过程中加密的敏感令牌
- JWT验证:转发请求前进行完整的JWT验证
- 可配置索赔:灵活的标题映射声明
- HTTPS就绪:生产部署应使用TLS
- 主密钥安全:安全地存储主密钥(环境变量、机密管理器)
🧪 测试
运行所有测试
task test
# or
go test ./... -v运行覆盖率测试
task coverage
# Opens HTML coverage report in browser运行特定包测试
go test ./internal/service/auth/... -v
go test ./internal/utility/... -v
go test ./internal/infrastructure/wire/... -v与测试服务器的集成测试
该项目包括一个用于端到端测试的测试MCP服务器:
# Terminal 1: Start test MCP server
task run-testserver
# Terminal 2: Start proxy
task run
# Terminal 3: Run integration tests
task test-testserver看 测试指导 获取详细的测试文档。
📁 项目结构
mcp-janus/
├── cmd/
│ ├── proxy/
│ │ └── main.go # Proxy server entry point
│ └── mcpserver/
│ └── main.go # Test MCP server
├── internal/
│ ├── infrastructure/
│ │ ├── config/
│ │ │ └── config.go # YAML-based configuration
│ │ └── wire/
│ │ └── gin.go # Gin router setup & handlers
│ ├── server/
│ │ └── proxy.go # Auth middleware & proxy logic
│ ├── service/
│ │ ├── auth/
│ │ │ ├── service.go # Auth service interface
│ │ │ ├── impl.go # Auth implementation
│ │ │ └── types.go # Auth request/response types
│ │ └── metadata/
│ │ └── metadata.go # RFC 9728 & OpenID metadata
│ └── utility/
│ └── encryption.go # AES-GCM encryption service
├── docs/
│ ├── mcp-auth-notes.md # MCP spec summary
│ ├── design.md # Architecture documentation
│ ├── auth-flow.md # Flow diagrams
│ └── testing-guide.md # Testing documentation
├── config.yaml # Configuration file
├── Taskfile.yaml # Task runner commands
├── go.mod
├── go.sum
└── README.md # This file🔧 发展
代码的风格
此项目遵循惯用的Go惯例:
gofmt用于格式化golangci-lint用于棉绒(如有)- 表驱动测试
- 结构化错误处理
- 明确区分关注点
可用任务命令
查看所有可用命令:
task --list关键命令:
task build-构建代理服务器task run-在开发模式下运行代理task test-运行所有测试task coverage-生成覆盖率报告task lint-运行门楣(如果安装了golangci棉绒)task fmt-格式代码task build-testserver-构建测试MCP服务器task run-testserver-运行测试MCP服务器
添加新功能
- 更新配置
internal/infrastructure/config/config.go - 在中定义服务接口
internal/service/*/service.go - 实施服务
internal/service/*/impl.go - 电线服务
internal/infrastructure/wire/gin.go - 使用表驱动模式添加全面的测试
- 更新文档
密钥管理
生成新的主密钥:
# Generate 32-byte (256-bit) hex key
openssl rand -hex 32在中配置 config.yaml 或通过环境变量 MCP_ENCRYPTION_MASTER_KEY.
🛡️ 威胁模型
防止
- ✅ 令牌传递攻击(代理发出自己的令牌)
- ✅ 令牌篡改(带身份验证的AEAD加密)
- ✅ 未经授权的访问(OAuth 2.1与PKCE)
- ✅ 凭证暴露(加密的客户端ID和令牌)
- ✅ 中间人(HTTPS支持生产)
- ✅ 令牌重放(JWT过期验证)
安全最佳实践
- 安全地存储主加密密钥(机密管理器、环境变量)
- 在生产环境中使用HTTPS
- 定期轮换加密密钥
- 监视和记录身份验证事件
- 确保IdP凭据的安全
- 定期对依赖关系进行安全审计
请参阅文档 docs/ 获取详细的安全信息。
📚 参考文献
MCP规格
OAuth标准
- OAuth 2.1(IETF草案)
- RFC 8414:OAuth 2.0授权服务器元数据
- RFC 7591:OAuth 2.0动态客户端注册
- RFC 9728:OAuth 2.0受保护的资源元数据
- RFC 8707:OAuth 2.0的资源指示器
🤝 贡献
- 遵循Go最佳实践和项目惯例
- 使用
task fmt在提交之前格式化代码 - 为所有新功能添加测试
- 根据需要更新文档
- 确保所有测试通过:
task test
🙋 支持
对于问题和疑问:
✅ 实施状态
- \[x\] Go模块已初始化
- \[x\] 基于YAML的环境覆盖配置
- \[x\] AEAD加密(AES-256-GCM)
- \[x\] 使用JWT加密生成不透明令牌
- \[x\] 使用加密凭据进行动态客户端注册
- \[x\] 使用PKCE的OAuth授权代码流
- \[x\] 代币交换和验证
- \[x\] 使用身份验证中间件转发MCP请求
- \[x\] 声明映射到HTTP标头
- \[x\] 基于Gin的HTTP服务器,可正常关闭
- \[x\] 全面的测试套件
- \[x\] 文档(README、设计文档、流程图)
- \[x\] 用于常见操作的任务运行器
- \[x\] 测试MCP服务器进行集成测试
- \[\]刷新令牌实现
- \[\]速率限制
- \[\]高级监控和指标
- \[\]OpenAPI规范
