- 新增 /api/mcp (Streamable HTTP) 与 41 个工具: 项目/看板列/任务/清单/打卡; - 新增 McpToken 模型与迁移, DB 仅存 SHA-256 哈希, token 绑定用户; - 新增 /api/mcp-tokens 增删改查, 支持独立命名 (如 R1); - 「我的」页新增 AI 接入 (MCP) 区块: 地址复制/新建/一次性展示/删除; - 补充 docs/MCP.md 与样式
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
// 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 <Token>',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user