Просмотр исходного кода

feat: Admin MCP 支持从 Authorization header 读取 token

- 修改 MCP endpoint 从请求 header 中提取 Bearer token
- 更新技能文档,添加 Authorization header 配置说明
- 支持通过 --header 参数配置固定 token

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 3 недель назад
Родитель
Сommit
86263a7f7c

BIN
.claude/skills/install-admin-mcp.skill


+ 74 - 2
.claude/skills/install-admin-mcp/SKILL.md

@@ -102,9 +102,72 @@ curl -X POST http://localhost:3000/mcp \
   }'
 ```
 
+### 测试带 Token 的请求
+
+```bash
+# 1. 获取 token
+TOKEN=$(curl -s -X POST http://localhost:8080/api/v1/auth/login \
+  -H "Content-Type: application/json" \
+  -d '{"username":"admin","password":"admin123"}' | jq -r '.token')
+
+# 2. 测试工具调用(如获取当前用户)
+curl -X POST http://localhost:3000/mcp \
+  -H "Content-Type: application/json" \
+  -H "Accept: application/json, text/event-stream" \
+  -H "Authorization: Bearer $TOKEN" \
+  -d '{
+    "jsonrpc": "2.0",
+    "id": 2,
+    "method": "tools/call",
+    "params": {
+      "name": "admin_get_current_user",
+      "arguments": {}
+    }
+  }' | jq -r '.result.content[0].text'
+```
+
+### Token 管理
+
+**获取新 Token**:
+```bash
+curl -X POST http://localhost:8080/api/v1/auth/login \
+  -H "Content-Type: application/json" \
+  -d '{"username":"admin","password":"admin123"}'
+```
+
+**更新 MCP 配置的 Token**:
+```bash
+# 删除旧配置
+/root/.local/bin/claude mcp remove admin-mcp
+
+# 重新添加(使用新 token)
+/root/.local/bin/claude mcp add --transport http admin-mcp http://localhost:3000/mcp \
+  --header "Authorization: Bearer new-token-here"
+```
+
 ## Claude Desktop 配置
 
-如需在 Claude Desktop 中使用,编辑 `~/.claude/claude_desktop_config.json`:
+### 方式一:使用 HTTP 传输(推荐)
+
+使用命令行添加 MCP 服务器时,可以配置固定的 Authorization header:
+
+```bash
+/root/.local/bin/claude mcp add --transport http admin-mcp http://localhost:3000/mcp \
+  --header "Authorization: Bearer your-token"
+```
+
+**注意**:需要将 `your-token` 替换为实际的管理员 token。token 可以通过登录获取:
+
+```bash
+# 获取管理员 token
+curl -X POST http://localhost:8080/api/v1/auth/login \
+  -H "Content-Type: application/json" \
+  -d '{"username":"admin","password":"admin123"}' | jq -r '.token'
+```
+
+### 方式二:配置文件方式
+
+编辑 `~/.claude/claude_desktop_config.json`(注意:配置文件方式不支持动态 header):
 
 ```json
 {
@@ -112,13 +175,22 @@ curl -X POST http://localhost:3000/mcp \
     "admin-mcp-server": {
       "transport": {
         "type": "http",
-        "url": "http://localhost:3000/mcp"
+        "url": "http://localhost:3000/mcp",
+        "headers": {
+          "Authorization": "Bearer your-token"
+        }
       }
     }
   }
 }
 ```
 
+### Token 说明
+
+- Admin MCP 服务器从 HTTP 请求的 `Authorization` header 中读取 Bearer token
+- 每次调用 MCP 工具时,该 token 会被用于后端 API 认证
+- 如果 token 过期,需要重新配置并重启 MCP 服务
+
 ## 故障排查
 
 **端口被占用**

+ 22 - 7
packages/admin-mcp-server/src/index.ts

@@ -18,6 +18,7 @@ import { roleTools } from './tools/role-tools.js';
 import { systemConfigTools } from './tools/system-config-tools.js';
 import { orderTools } from './tools/order-tools.js';
 import { authTools } from './tools/auth-tools.js';
+import { getApiClient } from './services/api-client.js';
 
 // ============================================================================
 // Server Configuration
@@ -739,19 +740,33 @@ async function runHTTP() {
   });
 
   // MCP endpoint
-  app.post('/mcp', async (req, res) => {
+  app.post('/mcp', async (req: express.Request, res: express.Response) => {
+    // Extract Authorization token from request header
+    const authHeader = req.headers.authorization;
+    if (authHeader && authHeader.startsWith('Bearer ')) {
+      const token = authHeader.substring(7); // Remove 'Bearer ' prefix
+      getApiClient().setToken(token);
+    }
+
     // Create a new transport for each request (stateless, prevents request ID collisions)
     const transport = new StreamableHTTPServerTransport({
       sessionIdGenerator: undefined,
       enableJsonResponse: true
     });
 
-    // Clean up transport when response closes
-    res.on('close', () => transport.close());
-
-    // Connect server to transport and handle request
-    await server.connect(transport);
-    await transport.handleRequest(req, res, req.body);
+    try {
+      // Clean up transport when response closes
+      res.on('close', () => {
+        transport.close();
+      });
+
+      // Connect server to transport and handle request
+      await server.connect(transport);
+      await transport.handleRequest(req, res, req.body);
+    } finally {
+      // Always close transport to allow reconnection on next request
+      transport.close();
+    }
   });
 
   // Start listening