最小MCP服务器+OAuth演示(裸安装指南)
本指南显示了一个最小的、可复制的运行流程:
- 端口3000上的可流式HTTP MCP服务器(带SSE)
- 端口3001上的一个单独的OAuth授权服务器(演示),您稍后可以将其交换为托管IdP。
⚠️ 请勿在生产环境中使用此OAuth服务,它仅用于演示目的。
建筑:两个独立的服务器,用于现实的生产设置。MCP服务器专注于MCP功能,而OAuth服务器处理所有身份验证流。
将此用作裸服务器仓库的README。
0)Docker快速入门(无身份验证)
npm run docker:build
npm run docker:run1) 安装并启动
安装依赖项:
npm ci启动OAuth演示(端口3001):
npm run dev:oauth工具模式与开发
此服务器支持三种不同的工具模式,每种模式都针对不同的用例进行了优化。选择最适合您需求的模式:
🛠️ 基本工具模式(默认)
最适合: 学习MCP基础知识、CLI工具和简单的基于文本的交互。
基本模式通过简单的基于文本的工具响应提供核心MCP功能。非常适合理解MCP协议,而无需UI复杂性。
npm run dev特征:
- 纯MCP协议实现
- 基于文本的工具响应
- 最小依赖性
- 快速轻便
🎨 HTML用户界面模式
最适合: 快速原型、简单表单和轻量级交互组件。
HTML UI模式使用普通HTML、CSS和JavaScript启用丰富的交互式组件。组件被用作可以嵌入MCP客户端的独立HTML文件。
npm run dev:ui-html特征:
- 交互式HTML组件
- 无需构建步骤
- 快速迭代
- 轻量级捆绑尺寸
⚛️ React UI模式
最适合: 复杂的、有状态的组件和现代基于React的UI。
React UI模式通过TypeScript提供完整的React组件支持,支持具有状态管理、钩子和现代React模式的复杂用户界面。
npm run dev:ui-react特征:
- 全面支持React 18+
- TypeScript用于类型安全
- 组件状态管理
- 现代React模式(钩子、上下文等)
🧪 React组件开发
这 web/ 该目录包含一个轻量级的React开发环境,用于在本地设计和测试UI组件。
启动开发服务器:
cd web
npm run dev这将启动一个Vite驱动的开发服务器,该服务器具有:
- ⚡ 热模块更换(即时更新)
- 🔍 浏览器中的组件预览
- 🎯 模拟
window.openaiAPI测试 - 📦 自动TypeScript编译
编辑 web/src/components/greet.tsx 并立即查看您的更改。mock API模拟工具调用,因此您可以在不运行完整MCP服务器的情况下开发和测试组件的行为。
生产建设:
cd web
npm run build这会产生 web/dist/greet.js,在React UI模式下运行MCP服务器时自动包含。
🐳 Docker配置
在生产部署中,通过更改工具模式来配置工具模式 CMD 在您的Dockerfile中使用适当的npm脚本。npm脚本已经设置了 TOOL_MODE 环境变量在内部,所以您不需要单独设置它。
Dockerfile:
# Choose the start script that matches your desired mode:
# CMD ["npm", "run", "start"] # Basic tools (default)
# CMD ["npm", "run", "start:ui-html"] # HTML UI
CMD ["npm", "run", "start:ui-react"] # React UI不同模式的示例:
# Basic mode (no UI)
CMD ["npm", "run", "start"]
# HTML UI mode
CMD ["npm", "run", "start:ui-html"]
# React UI mode
CMD ["npm", "run", "start:ui-react"]Dockerfile在镜像构建过程中自动构建React组件,因此React UI模式已准备好在生产环境中使用。
______________________________________________________________________
开发技巧
无身份验证运行(本地测试):
DISABLE_AUTH=true npm run dev在模式之间切换:
npm run dev→ 基本工具npm run dev:ui-html→ htmluinpm run dev:ui-react→ React UI(首先构建web组件)
令牌模式:自检(本地默认)或JWT(托管IdP)
此服务器支持两种身份验证模式,由 AUTH_TOKEN_MODE:
introspection(默认):通过以下方式验证不透明令牌OAUTH_INTROSPECT_URL(RFC 7662)。jwt:使用JWKS验证JWT访问令牌。
本地默认使用不透明令牌(不需要JWT配置)。对于发出JWT访问令牌的托管IdP,请切换到JWT模式并设置以下内容:
DISABLE_AUTH=false
AUTH_TOKEN_MODE=jwt
JWT_ISSUER=https://your-tenant.example.com/
JWT_AUDIENCE=https://api.example.com # optional depending on IdP
# JWT_JWKS_URL defaults to ${JWT_ISSUER}/.well-known/jwks.json使用托管IdP(JWT模式)运行示例:
# .env
DISABLE_AUTH=false
AUTH_TOKEN_MODE=jwt
JWT_ISSUER=https://your-tenant.example.com/
# Optional, depends on IdP
JWT_AUDIENCE=https://api.example.com
# Optional override if issuer does not expose well-known JWKS
# JWT_JWKS_URL=https://your-tenant.example.com/.well-known/jwks.json
OAUTH_SERVER_URL=https://your-tentant-url.com
npm run dev2) 生成PKCE(验证器+S256挑战)
node -e "const c=require('crypto');const v=c.randomBytes(48).toString('base64url');const ch=c.createHash('sha256').update(v).digest('base64url');console.log({code_verifier:v,code_challenge:ch})"两者都保存 code_verifier 和 code_challenge 接下来的步骤。
3) 注册客户
curl -sS -X POST http://localhost:3001/register \
-H "Content-Type: application/json" \
-d '{
"client_name":"cli",
"redirect_uris":["https://oauth.pstmn.io/v1/callback"],
"grant_types":["authorization_code"],
"response_types":["code"],
"token_endpoint_auth_method":"client_secret_post",
"scope":"mcp:tools"
}'保存 client_id 和 client_secret 从回应中。
4) 授权(不自动跟踪重定向)
curl -sS -o /dev/null -D - -G http://localhost:3001/authorize \
--data-urlencode "client_id=YOUR_CLIENT_ID" \
--data-urlencode "redirect_uri=https://oauth.pstmn.io/v1/callback" \
--data-urlencode "response_type=code" \
--data-urlencode "scope=mcp:tools" \
--data-urlencode "state=xyz" \
--data-urlencode "code_challenge=YOUR_CODE_CHALLENGE" \
--data-urlencode "code_challenge_method=S256" \
--data-urlencode "resource=http://localhost:3000/mcp"复制 code 从 Location: header(或打开重定向URL并复制 code 查询参数)。
5) 代币的交换代码
curl -sS -X POST http://localhost:3001/token \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "code=THE_CODE" \
--data-urlencode "code_verifier=YOUR_CODE_VERIFIER" \
--data-urlencode "redirect_uri=https://oauth.pstmn.io/v1/callback" \
--data-urlencode "client_id=YOUR_CLIENT_ID" \
--data-urlencode "client_secret=YOUR_CLIENT_SECRET" \
--data-urlencode "resource=http://localhost:3000/mcp"保存 access_token 从回应中。
6) 初始化MCP(第一次POST;还没有会话头)
curl -i -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":"2025-03-26",
"capabilities":{},
"clientInfo":{"name":"cli","version":"1.0.0"}
}
}'复制 Mcp-Session-Id 从响应标头中。
7) 打开SSE(在另一个终端中)
curl -i -N http://localhost:3000/mcp \
-H "Accept: text/event-stream" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Mcp-Session-Id: SESSION_ID"保持此运行状态以接收通知。
8) 列出工具并调用一个
列出工具:
curl -i -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Mcp-Session-Id: SESSION_ID" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'呼叫 greet:
curl -i -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Mcp-Session-Id: SESSION_ID" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"greet","arguments":{"name":"Elin"}}}'刀具模式差异:
- 基本模式: 返回简单的文本响应:
"Hello, Elin!" - HTML UI模式: 返回HTML组件模板(
ui://widget/greet.html)具有用于水合的结构化内容物 - React UI模式: 返回React组件模板(
ui://widget/greet.js)完全支持React和状态管理
在UI模式下,富客户端可以呈现交互式组件,允许用户直接与工具交互,组件可以通过 window.openai.callTool 支持API时。
呼叫 count:
curl -i -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Mcp-Session-Id: SESSION_ID" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"count","arguments":{"number":5}}}'注意事项/故障排除
- 发布
/mcp必须包括Accept: application/json, text/event-stream(不是*/*). - 第一个POST必须是
initialize没有Mcp-Session-Id。所有后续请求必须包括返回的Mcp-Session-Id头球 - 如果您以令牌验证启动MCP,则每个请求
/mcp必须包括Authorization: Bearer ACCESS_TOKEN. - 演示AS中的代币是短暂的。如果调用开始失败,请重做步骤2-5并重新初始化。
- 快速令牌检查:
curl -sS -X POST http://localhost:3001/introspect \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "token=ACCESS_TOKEN"- SSE托管:确保您的平台支持长期响应,并且不会缓冲SSE。
- 重定向:如果你使用
https://oauth.pstmn.io/v1/callback,它只为您捕获代码;您的令牌仍然由OAuth服务器颁发。
稍后切换到托管IdP
轻松迁移:由于服务器是分开的,您可以轻松地将OAuth服务器替换为托管提供商:
- 替换演示OAuth服务器 与真正的提供商(Auth0/Okta/Keycloak/ORY/Alues/Cognito)合作
- 更新MCP服务器配置 -只需更改
OAUTH_SERVER_URL环境变量:
OAUTH_SERVER_URL=https://yourcompany.okta.com npm run dev- 保持MCP合同不变 -通过JWT(JWKS)或自检验证令牌
- 客户注册:如果您的IdP支持动态客户端注册,请公开
/register;否则,通过IdP的管理员API/UI创建客户端
生产架构:
┌─────────────────┐ ┌─────────────────┐
│ MCP Server │ │ Managed IdP │
│ Your Domain │◄──►│ (Okta/etc.) │
│ │ │ │
│ • MCP endpoints │ │ • OAuth flows │
│ • OAuth metadata│ │ • Token mgmt │
│ • Bearer auth │ │ • User mgmt │
└─────────────────┘ └─────────────────┘