// MCP 服务 (Streamable HTTP, 无状态) // 入口: POST/GET/DELETE /api/mcp // 鉴权: Authorization: Bearer <个人 MCP Token>, 与用户绑定, 拥有该用户全部业务权限. import { Router } from 'express' import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js' import { authenticateMcpToken } from './auth.js' import { registerTaskToolsGroup } from './tools/tasks.js' import { registerChecklistTools } from './tools/checklists.js' import { registerCheckinTools } from './tools/checkin.js' const SERVER_INFO = { name: 'iboard', version: '0.2.1' } export function buildMcpServer(ctx) { const server = new McpServer(SERVER_INFO, { instructions: [ '本服务连接 iBoard 个人工作台, 当前 token 已绑定到具体用户。', '可查询、新建、修改、删除、排序项目 / 看板列 / 任务, 以及检查清单和打卡。', '所有写操作立即生效, 删除类操作不可恢复, 调用前请确认用户意图。', '任务状态: todo / doing / done; 看板列可通过 boundStatus 与状态联动。', ].join('\n'), }) registerTaskToolsGroup(server, ctx) registerChecklistTools(server, ctx) registerCheckinTools(server, ctx) return server } const router = Router() router.all('/', async (req, res) => { let auth try { auth = await authenticateMcpToken(req) } catch (e) { console.error('[mcp] token 校验失败:', e) return res.status(500).json({ jsonrpc: '2.0', id: req.body?.id ?? null, error: { code: -32603, message: 'MCP 鉴权失败' }, }) } if (!auth) { res.set('WWW-Authenticate', 'Bearer realm="iboard-mcp"') return res.status(401).json({ jsonrpc: '2.0', id: req.body?.id ?? null, error: { code: -32001, message: '未授权: 请在「我的 → AI 接入 (MCP)」创建 Token, 并使用 Authorization: Bearer ', }, }) } const server = buildMcpServer({ userId: auth.userId, user: auth.user }) const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true, }) res.on('close', () => { transport.close().catch(() => {}) server.close().catch(() => {}) }) try { await server.connect(transport) await transport.handleRequest(req, res, req.body) } catch (e) { console.error('[mcp] 请求处理失败:', e) if (!res.headersSent) { res.status(500).json({ jsonrpc: '2.0', id: req.body?.id ?? null, error: { code: -32603, message: e?.message || 'MCP 处理失败' }, }) } } }) export default router