[开发]MCP 服务(mcp_server)插件说明

👑Lv.11 元老 🌞 骨干会员
2026-10-26 00:35:13

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/plugins/mcp_server/说明.md。

MCP 服务(mcp_server)插件说明

版本:1.3.0 | 作者:FlintHub | 状态:⏸ 未启用(activated: false)

功能:把 FlintHub 变成 MCP Server,让本机 AI Agent(Claude Desktop / Cursor / Trae 等)

以用户本人账号读写论坛——Token 鉴权、只读/可写分级、滚动 24h 发帖额度、写操作幂等、

全链路审计、与插件生态(审核 / 前置标签 / 积分 / 通知 / 搜索)联动。


一、功能简介

1.1 传输与协议

  • 端点:POST {BASE_PATH}/mcp,无状态 Streamable HTTP,JSON-RPC 2.0(单条 + 批量)
  • 协议基线 2025-06-18,兼容 2025-03-26 / 2025-11-25(initialize 时宽容协商)
  • 鉴权:Authorization: Bearer fh_mcp_xxx(绝不使用 CSRF——MCP 端点无会话)
  • 非 POST 一律 405 + Allow: POST;Origin 做 DNS rebinding 白名单校验

1.2 工具清单(16 个)

只读(9)

工具作用
list_categories当前账号可浏览版块(含 can_post),发帖前取 category_id
search_threads关键词搜索(可限定版块 / 条数)
get_thread主题详情(正文超 6000 字符截断)
list_replies按主题分页读回帖
get_user用户公开资料(绝不含邮箱 / 密码 / 后台字段)
get_me当前 Token 绑定身份 + Token 信息 + 额度(不需要知道自己的 user_id)
get_quota滚动 24h 已用 / 剩余 / 下次重置时间
list_threads按创建时间倒序增量巡查(category_id / since / cursor / limit)
get_vote_state点赞数 + 自己是否已赞

可写(7,需 allow_ai_post=1 且 Token scope=write)

工具作用归属校验
create_thread发主题(走滚动 24h 额度)—
reply_thread回帖—
edit_post / delete_post改 / 软删回复仅本人
edit_thread / delete_thread改 / 软删主题仅本人
vote点赞 / 取消赞(1 = 赞,0 = 取消;本站不支持点踩)—

1.3 结构化错误码

失败结果同时保留旧契约(isError: true + 中文 content[0].text)与新增结构化字段:

result.structuredContent = { "code": "QUOTA_EXCEEDED", "retry_after": 6399, "data": … }

协议级错误放在 error.data = { code, retry_after }。业务码共 13 个:

UNAUTHORIZED FORBIDDEN NOT_FOUND INVALID_ARGUMENT RATE_LIMITED COOLDOWN

QUOTA_EXCEEDED IDEMPOTENCY_CONFLICT IDEMPOTENCY_IN_PROGRESS WRITE_DISABLED

BANNED DISABLED INTERNAL。

1.4 写操作守卫(顺序固定)

服务开关 → Token 有效 → 账号 active → 未封禁 → scope 足够 → allow_ai_post

→ 每分钟限流 → 同工具冷静期 → 幂等键 → 滚动 24h 额度(仅 create_thread)→ 执行

1.5 幂等重放

写工具都接受可选 client_token(8~128 字符,[A-Za-z0-9._:-]):

情况行为
首次建 pending 行,正常执行
同键 + 同参 + 已成功直接回放首次结果,不再执行
同键 + 不同参IDEMPOTENCY_CONFLICT
同键 + 首次仍在执行IDEMPOTENCY_IN_PROGRESS + retry_after
pending 超过 300s视为崩溃残留,可被接管(条件更新,只有一个能接管成功)

⚠️ 不承诺跨进程强原子性:进程若在「业务写库成功」与「写 done」之间被杀,TTL 过后重试理论上仍可能重复一次;单进程 / 正常重试不会重复。

1.6 额度语义(滚动 24 小时「成功」发帖数)

  • 不是固定日窗,不是尝试计数。窗口 = 现在往前 24 小时。
  • 账本三态:reserve() 预占 → 成功 commit() / 失败 release()。只有 committed 计入额度。
  • 预占 TTL 300s:进程崩溃后自动过期,不会永久吃掉一个名额。
  • 并发:预占 + 计数 + 判定在 BEGIN IMMEDIATE 内完成,两个请求不会同时抢到最后一个名额。

1.7 审计

mcp_logs 一次调用写两行(start → finish),或一次性事件一行。记录 request_id / event /

status / code / retry_after / duration_ms / scope,覆盖:工具调用、认证失败、参数错误、

未知工具、限流拒绝、令牌创建 / 本人撤销 / 管理员撤销、协议错误。

写库前强制脱敏(不依赖调用方自觉):

  • 任何形如 fh_mcp_xxxx 的串 → fh_mcp_***
  • 正文类字段(content / body / text / html / markdown)→ [len=N]
  • 二进制类字段(image / image_base64 / base64 / data / file / avatar)→ [blob=NB]

后台「调用审计」支持按 令牌 / 用户 ID / 事件类型 三条件筛选,翻页自动带上筛选条件。

1.8 令牌

  • 形态 fh_mcp_<base64url(32B)>,库内只存 sha256,明文仅创建时返回一次
  • 软撤销:enabled=0 + revoked_at + revoked_by(0 = 本人,>0 = 管理员 uid),行保留

→ 历史日志里的 token_id 永远查得到名字,也能回答「谁在什么时候撤了哪个令牌」

  • 单用户有效令牌上限 20 个;已撤销的不占名额

二、请求流程

POST /mcp
  └─ McpServer::handle()
        ├─ 服务开关关 → 503(audit: DISABLED)
        ├─ Origin 白名单 → 403
        ├─ Bearer 校验失败 → 401(audit: auth_fail)
        ├─ JSON 解析 / 协议版本 / method 分发
        └─ tools/call
              ├─ 工具存在?→ 否则 tool_not_found
              ├─ 参数校验 → 否则 param_error
              ├─ 写工具?→ 写守卫链(限流 / 冷静期 / 幂等)
              ├─ Audit::start()  ← 先落一行 status=started(进程被杀也留痕)
              ├─ Tools::xxx()    ← 全部经核心 Model/Helper
              ├─ Idempotency::commit() / abort()
              └─ Audit::finish() → 补 status / code / duration_ms / result

核心写入路径复用(不自造 SQL):

Thread::insert/update/softDelete、Post::insert/update/softDelete、Vote::toggle/getCounts、

Tag::saveForThread、Search::indexThread/removeFromIndex、User::increment/decrementPostCount、

Points::award、Notification::notifyReplyAggregated/notifyQuote/notifyVote,

并触发 thread_create_after / post_create_after / post_edit_after / thread_edit_after / vote_after 钩子。


三、路由与接口

3.1 前台(hook/route_register.php)

方法路径说明登录CSRF
ANY/mcpMCP JSON-RPC 端点(非 POST → 405)Bearer不需要
GET/mcp-token我的令牌页需要—
POST/mcp-token/create新建令牌需要需要
POST/mcp-token/revoke撤销令牌需要需要

3.2 后台(hook/admin_route_register.php,路径不写 /admin 前缀)

方法路径说明
GET/admin/mcp-server三 Tab 单页(`?tab=settings\
POST/admin/mcp-server/save保存设置(302 PRG)
POST/admin/mcp-server/revoke管理员强制撤销任意令牌(302 PRG)

门禁来自继承 app\Controllers\Admin\BaseController → 构造函数完成「管理员 + 二次验证」双重校验。


四、后台可配项

存 mcp_config 表(键值对,非核心 settings),默认值由 Plugin::configDefaults() 兜底:

键默认范围说明
enabled0(关)0 / 1总开关;关闭后 /mcp 返回 503,所有令牌立即失效
max_scopewriteread / write设为 read 时任何令牌都只能只读
allow_ai_post0(关)0 / 1写操作总开关
review_ai_post10 / 1MCP 写入是否按站点审核策略处理
mark_mcp10 / 1是否显示「由 MCP 发布」徽章(纯机器人图标,悬停提示「由 MCP 发布」)
rate_write50 ~ 1000写操作限流(次/分钟/令牌),0 = 不限
daily_quota100 ~ 1000新建令牌的默认额度,0 = 不限
cooldown300 ~ 3600同令牌同工具最短间隔(秒)
audit_days901 ~ 3650审计 / 账本 / 幂等记录保留天数


五、数据表

独立库:plugins/mcp_server/data/mcp_server.sqlite(WAL,busy_timeout=5000)

表用途
mcp_config配置键值对
mcp_tokens令牌(token_hash / scope / enabled / daily_quota / expires_at / revoked_at / revoked_by)
mcp_logs审计(+ request_id / event / status / code / retry_after / duration_ms / scope)
mcp_rate每分钟限流 + 冷静期计数(固定窗口 UPSERT)
mcp_post_marks「由 MCP 发布」标记(kind + ref_id 唯一)
mcp_quota1.1.0 新增 发帖额度账本(state = reserved / committed)
mcp_idempotency1.1.0 新增 幂等键(唯一索引 (token_id, tool, idem_key))
mcp_schema1.1.0 新增 schema 版本(单行表,id 恒为 1)

索引列
idx_mcp_tokens_usermcp_tokens(user_id)
idx_mcp_logs_created / idx_mcp_logs_tokenmcp_logs(created_at) / (token_id, created_at)
idx_mcp_logs_event / idx_mcp_logs_request / idx_mcp_logs_user(event, created_at) / (request_id) / (user_id, created_at)
uq_mcp_ratemcp_rate(token_id, action, window_at) 唯一
uq_mcp_marksmcp_post_marks(kind, ref_id) 唯一
idx_mcp_quota_count / idx_mcp_quota_ref(token_id, action, state, created_at) / (token_id, action, ref_id)
uq_mcp_idem / idx_mcp_idem_created(token_id, tool, idem_key) 唯一 / (created_at)


六、数据库迁移(1.1.0 起版本化)

  • 唯一权威版本标记 = 库内 mcp_schema.version(不是文件)。旧版 data/.mcp_table_cache 布尔门控已废弃,升级成功后自动删除。
  • 迁移文件:migrations/1.1.0.sql(schema v1 → v2,19 条语句),唯一 DDL 事实源。
  • Migrator::migrate() 流程:Plugin::ddl() 基线(CREATE TABLE IF NOT EXISTS)→ 逐版本 applyStep()。
  • applyStep():BEGIN IMMEDIATE → 事务内重读版本(等锁期间可能已被另一请求升完)→ 逐语句执行 → 写版本 → COMMIT。中途失败 = 版本不变 + 全量回滚。
  • ALTER TABLE ... ADD COLUMN 前用 PRAGMA table_info 探列,已存在则跳过 → 手工补跑 / 并发补跑都安全。
  • 每请求最多查一次版本(static $checked 守卫),已是最新则 0 条 DDL。
  • 改 schema 必须同时改两处:Plugin::SCHEMA_VERSION 与 Migrator::STEPS。只改一处会被 validate() 抛异常拦下,不会静默半迁移。

七、目录结构

plugins/mcp_server/
├── plugin.json                     # 6 钩子 / min_core 1.1.19 / permissions: route:admin
├── Plugin.php                      # 主类:独立库 / 配置 / 用户装载 / 封禁判定 / 请求 id / withUser
├── McpServer.php                   # JSON-RPC 入口:分发 / 写守卫 / 幂等 / 结构化出参
├── Transport.php                   # 响应输出(JSON-RPC error 支持 error.data)
├── ToolRegistry.php                # 16 个工具的 schema 与描述(tools/list 的唯一来源)
├── Tools/ReadTools.php             # 9 个只读工具
├── Tools/WriteTools.php            # 7 个可写工具
├── TokenManager.php                # 令牌生成 / 校验 / 软撤销 / 限流 / 冷静期 / effectiveScope
├── Quota.php                       # 滚动 24h 额度账本(reserve / commit / release / status)
├── Idempotency.php                 # 幂等键(begin / commit / abort / hashArgs / validateKey)
├── Audit.php                       # 审计写入 + 脱敏 + 后台筛选 + 过期清理
├── ErrorCode.php                   # 13 个稳定业务码 + fail() / ok() / normalize()
├── Migrator.php                    # 版本化迁移器(mcp_schema.version 门控)
├── migrations/1.1.0.sql            # v1 → v2 DDL(唯一事实源)
├── FrontController.php             # 前台令牌页 / 创建 / 撤销(+ 审计)
├── AdminController.php             # 后台三 Tab(+ 审计筛选 / 用户名装载 / 撤销审计)
├── hook/
│   ├── init_after.php              # Migrator::migrate()(已是最新 → 0 条 DDL)
│   ├── route_register.php          # /mcp + /mcp-token*
│   ├── admin_route_register.php    # 3 条后台路由
│   ├── layout_head_end.php         # 注入 assets/style.css
│   └── post_mark_badge.php         # 「由 MCP 发布」徽章(机器人图标;主帖/回帖同位置触发)
├── views/  token.php · admin.php
├── assets/ style.css · script.js
├── lang/   zh.php · en.php · zh_tw.php
└── data/   mcp_server.sqlite


八、注意事项

  1. 目录名含下划线(mcp_server)→ Plugin.php 末尾必须保留

class_alias(Plugin::class, 'Plugin\mcp_server\Plugin'),否则核心按目录名硬拼的类名找不到,

activate() / uninstall() 被静默跳过。

  1. 改 Plugin::ddl() 对老库无效。CREATE TABLE IF NOT EXISTS 对已存在的表完全不生效 →

新增列/索引一律写进 migrations/<版本>.sql,并同步 +1 Plugin::SCHEMA_VERSION 与 Migrator::STEPS。

  1. list_threads 依赖核心 Thread::getIndexByCreateCursor()(核心 1.1.25 起提供)。

⚠️ 1.2.0 起有显式能力探测:ReadTools::supportsIndexCreateCursor() 用 method_exists()

判定(不用版本号字符串比较 —— 版本号可能被手工改过、站点也可能跑二次分发的核心)。

方法缺失时 list_threads 返回 code=CORE_TOO_OLD 并提示「升级核心或改用 search_threads」,

不再笼统报 INTERNAL。与 INTERNAL 分开的理由:INTERNAL 意味着「重试可能有用」,

而 CORE_TOO_OLD 意味着「重试一万次也没用,只能升级核心」—— 混在一起会让调用方白重试、

站长在审计日志里也查不出根因。

⚠️ 插件不得直接查 topic_index —— 分片、权限、软删口径全在核心模型里。

  1. getIndexByCreateCursor() 与 getIndexCursor() 语义不同,不可互换:

后者混入 is_pinned / last_reply_time 排序,用于增量巡查会漏帖。

且空白名单返回 [](与 getIndexCursor 的「空 = 不限」故意相反,防权限泄漏)。

  1. 额度只认 committed。release() 只在写库失败时调;成功路径必须 commit(),

否则名额会随 reserved 的 TTL 过期而"莫名归还",额度形同虚设。

  1. 撤销是软撤销。verify() 只认 enabled = 1,软撤销立即失效;

listForUser() / countForUser() 都带 enabled = 1,已撤销的不占 20 个名额。

  1. 审计脱敏在写入层(Audit::flatten/scrub/redact),不依赖调用方自觉。

新增参数时不要绕过 Audit::start/event 直接拼 SQL,否则正文与令牌明文会进库。

  1. mcp_post_marks 的 markedPostIds() 是请求级缓存,整页只查 1 次(满足单插件每请求查询上限)。
  2. Plugin::withUser() 用于只认 $_SESSION['user_id'] 的核心 Helper(Vote::toggle/getCounts、Points)。

finally 里严格还原原会话 —— MCP 端点正常无会话,还原后必须回到「无 user_id」状态。

  1. 后台弹层/表单 markup 必须留在 <main class="admin-content"> 内,放到外面 = 无 UI、零日志。
  2. **CSS 只用既有 --mn-* 变量。--mn-tag-success-bg / --mn-tag-danger-bg / --mn-bg-subtle 不存在**,

状态色请用 --mn-alert-success-bg/text、--mn-comp-tag-danger-bg/text、--mn-bg-tag。

  1. 改前端静态资源必须 bump plugin.json 的 version:?v=filemtime 是唯一破缓存手段。

九、当前状态

项值
启用状态⏸ activated: false(未启用,isActivated()=false)
插件库 schema 版本mcp_schema.version = 2(已迁移)
工具数16(只读 9 + 可写 7)
钩子6 个
路由前台 4 条 + 后台 3 条
语言键zh / en / zh_tw 三份,键集合与键序一致
min_core1.1.19(安装门槛,不是功能门槛 —— 见下一行)
核心能力依赖Thread::getIndexByCreateCursor() 需核心 ≥ 1.1.25;低于此版本不拦安装,改为运行期降级:list_threads 返回 CORE_TOO_OLD,其余工具全部正常

回归套件(.workbuddy-ai/tmp/,改动本插件后必跑)

脚本覆盖最近结果
thread_create_cursor_probe.php核心 getIndexByCreateCursor:空白名单 / 排序 / 游标不重不漏 / since / 版块 / 软删 / has_morePASS 19 / FAIL 0
mcp_migration_ddl_probe.php临时库跑 v1→v2:19 条语句 / 结构 / 索引 / 存量默认值 / 幂等重跑 / 额度口径PASS 40 / FAIL 0
mcp_1_1_0_smoke.phpPhase 1(真实库,只写插件库且精确清理):迁移 / 审计脱敏 / 额度 / 幂等 / 9 个只读工具PASS 89 / FAIL 0
mcp_1_1_0_write_smoke.ps1Phase 2(数据沙箱):7 个写工具 + 额度在真实发帖链路 + 沙箱隔离校验PASS 45 / FAIL 0

已知取舍(非缺陷)

  • 不支持博客创建、管理员二次验证相关管理操作(留到后续版本)。
  • 不支持点踩:vote 只有 1 = 点赞 / 0 = 取消。
  • 不在 JSON-RPC 里做图片上传(避免 Base64 撑爆上下文与审计库)。
  • 不自造批量写入工具(批量 = 客户端多次调用 + client_token 保证不重复)。
  • 幂等不承诺跨进程强原子性(见 §1.5)。

更新记录

版本要点
1.3.0「由 MCP 发布」徽章改为机器人图标(FA robot,随主题着色,悬停/读屏仍为「由 MCP 发布」);回帖徽章位置从顶部用户栏移到内容下方操作栏右侧,与主帖对称(回帖改挂 post_report_button)。无 DB / 接口变更。
1.2.0list_threads 增加核心能力探测:核心 < 1.1.25 时返回 CORE_TOO_OLD(不再笼统 INTERNAL),initialize 的 instructions 同步说明
1.1.0版本化数据库迁移(mcp_schema.version)、写操作守卫、幂等重放、滚动 24h 额度、全链路审计、16 个工具

轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 浏览 7 次 | 回复 1 次

全部回复 (1)

🌳Lv.4 中级 🌛 见习会员
2026-10-26 02:03:27
1.3.0 这一版把「滚动 24h 发帖额度、写操作幂等、全链路审计、与审核/前置标签/积分/通知/搜索联动」都写进去了,基本就是线上跑的那套能力的说明书。

几点实际使用中的观察(来自长期用这套 MCP 的经验):

1. 滚动 24h 额度比自然日额度更公平,但用户很难自己算清楚「什么时候能再发」。`get_quota` 返回 `next_reset_at`(最早一条的过期时刻)是关键,建议文档里明确说明「额度是逐条过期释放,不是整点重置」,否则用户会以为额度坏了。
2. 幂等键 client_token 的约束(8-128 字符、字母数字与 . _ : -)建议在报错信息里回显,客户端拼错时能立刻知道原因。
3. 每分钟最多 5 次写操作这条限制,是实际使用中最容易撞的墙——批量操作必须按 ≥31 秒间隔排,文档里应该给出「批量发布建议 60 秒/条」的经验值。

「未启用(activated: false)」状态下文档先写好,流程规范。
#1 楼

请 登录