[开发]mcp_server 插件开发方案(让 FlintHub 成为 MCP Server)

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

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/docs/mcp_server插件开发方案_2026-10-11.md。

mcp_server 插件开发方案(让 FlintHub 成为 MCP Server)

文档性质:评估稿(只做方案,不写代码)

目标读者:站长 / 项目评审

依据:docs/插件开发规范.md(权威规范)、app/Helpers/Plugin.php、app/Core/Router.php、app/Models/*、现有插件实现(ai_assistant / forum_required_tag / content_review / mod_system / user_level)、MCP 官方规范

原始输入:站长提供的《MCP Server 插件规划(供 Agent 参考)》

版本:v1(2026-10-11)


0. 一句话结论

可行,且比原规划更简单 — 但必须改两处关键设计。

  1. 传输必须做成「无状态 Streamable HTTP + 纯 JSON 响应」,不启用 SSE / 长连接 / 会话。这样插件在普通 php-fpm 请求模型下即可跑通,不需要 Swoole / ReactPHP / 常驻进程。(MCP 规范明确允许服务器用 application/json 返回单对象而不开 SSE。)
  2. 「动态 Schema」不能直接去读 forum_required_tag 的库或类——那违反《插件开发规范》「插件间禁止直耦」。正确做法是新增一个自定义钩子契约 mcp_tool_schema,由关心规则的一方(如 forum_required_tag)自己订阅、自己上报规则;无人订阅时工具走「最小 schema + 可读错误」。

除此之外,原规划的判断(插件形态、Token 体系、走 Model 层、不做管理操作)全部成立,可以直接沿用。

建议分期:P0(Token + 只读工具)→ P1(发帖 / 回帖)→ P2(生态联动)→ P3(审计 / 限流 / 后台)→ P4(点赞 / 收藏等高级工具)。


1. 前提验证(先答「能不能插件全包」)

1.1 结论

能。 不需要动核心:/mcp 走 route_register 注册;Token 存插件独立 SQLite;写操作全部落到核心 Model 层。

1.2 证据链(逐条可复核)

#问题结论证据
1插件能注册根路径路由吗能app/Core/Router.php 提供 get/post/any/group;oidc_provider 已成功注册站点根 /.well-known/openid-configuration
2插件能做独立数据库吗能Plugin::db($name) → plugins/{name}/data/{name}.sqlite(app/Helpers/Plugin.php:113-141);Plugin::ensureSchema($name,$ddl) 幂等建表(同文件 :150-153)
3插件私有库会被 Web 直接下载吗不会Plugin::db() 会自动补写 data/.htaccess + data/web.config 防护(app/Helpers/Plugin.php:118-139)
4插件能发起自定义钩子吗能(关键)Plugin::hook(string $hookName, array $params = []) 是 public static,按 plugin.json 的 hooks 键任意名分发(app/Helpers/Plugin.php:673-728)。→ 可用来做去耦的跨插件契约
5能不能以「某用户」身份发帖/回帖能Thread::insert()(app/Models/Thread.php:54-180)、Post::insert()(app/Models/Post.php:52-172)。ai_assistant 正是这样以「AI 会员(绑定真实用户)」身份发帖:publishReply() → Post::insert() + Points::award()
6核心有没有现成的 API/Token 鉴权没有全库搜 Authorization / Bearer / api_token / access_token,app/ 下零命中。→ Token 体系必须插件自建
7现有插件生态里有没有同构先例有plugins/ai_assistant/:非真人身份写帖 + 敏感词过滤 + 审核模式 + 积分 + 日志,几乎就是 mcp_server 的「同步版」

1.3 由此产生的三条硬边界(必须写进插件自检)

  1. 不碰核心:/mcp 只注册路由;不修改任何 app/ 下文件;不新增核心钩子名(用自定义钩子名做扩展点)。
  2. 不自己写业务表:所有帖子/回复必须经 Thread::insert() / Post::insert(),禁止插件直写核心业务库(否则插件生态的钩子全部不会触发)。
  3. 不给 /mcp 加 CSRF:第三方 Agent 拿不到 CSRF token;鉴权只能靠 Authorization: Bearer <token>。(与 oidc_provider 的 token 端点同理——那处也明确不调 Csrf::verifyOrDie()。)

2. 总体设计

2.1 定位与边界

内容
做什么让本机 Agent(Claude Desktop / Cursor / Trae 等)以「某个已登录用户」的身份,读(列版块 / 搜帖 / 看帖 / 看回帖 / 看用户公开资料)与写(发主题 / 回帖 / 编辑自己的帖 / 软删自己的帖)
不做① 管理操作(删别人帖、封号、改版块)② 私信 ③ 后台数据 ④ 隐藏版块 / 隐藏内容 ⑤ 越权(Token 权限 ≤ 绑定用户权限)
形态独立插件,可单独启用/禁用
依赖无硬依赖;对 forum_required_tag / content_review / mod_system / user_level / points_mall 一律软依赖 + 优雅降级

2.2 命名与目录硬约束(含必须先踩平的坑)

  • 插件目录名:mcp_server(全小写 + 下划线,符合 /^[a-z0-9_]+$/)。
  • ⚠️ 目录名含下划线 → 核心生命周期类名推导会失败(规范 §4.1 / docs/插件开发规范.md:350-390)→ 必须在 Plugin.php 末尾加 class_alias:
  • 命名空间统一写 Plugin\McpServer(PascalCase 段)
  • class_alias(\Plugin\McpServer\Plugin::class, '\Plugin\mcp_server\Plugin')
  • 这条与 oidc_provider / oidc_client 完全同款,已是本库既定写法。
  • ⚠️ 路由前缀不能与真实目录同名(规范 §11.34b):/mcp 站点根无同名目录,安全。

2.3 路由规划表

方法路径用途鉴权
POST/mcpMCP 唯一端点(JSON-RPC 2.0;initialize / tools/list / tools/call / ping)Authorization: Bearer <mcp_token>(initialize 亦要求,避免匿名探测)
GET/mcp规范要求端点支持 GET;返回 405(不提供服务器→客户端 SSE 流)—
GET/mcp-token前台:Token 管理页(生成 / 查看 / 撤销)登录态(Session)
POST/mcp-token/create前台:生成 Token登录态 + CSRF
POST/mcp-token/revoke前台:撤销 Token登录态 + CSRF
—/admin/mcp-server后台:总开关 / 权限分级 / 限流 / 审核策略 / 审计日志管理员(admin_route_register,需声明 route:admin)

⚠️ 注册顺序:/mcp-token/* 必须早于任何 /mcp-token 通配;/mcp 与 /mcp-token 不冲突(精确匹配优先)。


3. 协议与传输设计(本方案的技术核心)

3.1 协议版本策略

版本状态结论
2025-03-26引入 Streamable HTTP兼容下限
2025-06-18客户端覆盖最好本项目实现基线
2025-11-25官方 Versioning 页标注 current握手时代,兼容即可
2026-07-28破坏性大改:删掉 initialize 握手与 Mcp-Session-Id、删 GET SSE、新增 server/discover暂不作为唯一实现(客户端采纳未普及)

落地口径:

  • initialize 时若客户端 protocolVersion 在此表内 → 原样回;否则回我们支持的最新版本(规范要求服务器必须返回一个自己支持的版本)。
  • 容忍客户端跳过 initialize 直接 tools/list(无状态服务器的自然行为),不做「必须先握手」的强校验。

3.2 传输形态:无状态 Streamable HTTP

项设计依据
端点单端点 POST /mcp规范要求单 MCP endpoint
Accept要求同时含 application/json 与 text/event-stream(客户端会带);服务器只回 application/json规范允许二选一
响应单个 JSON-RPC 对象,Content-Type: application/json不开 SSE → 不需要常驻进程
会话不分配 Mcp-Session-Id,每请求独立无状态,php-fpm 友好
通知notifications/initialized 等 → 202 Accepted,无 body规范要求
GET405规范允许
MCP-Protocol-Version 头读取并宽容处理:缺失按 2025-03-26 视作默认;非法/不支持才回 400规范要求
Origin必须校验(缺失或非白名单 → 403),防 DNS rebinding规范 MUST

⚠️ 这是本方案能否落地的前提。若要做 SSE / 服务器推送,就得上 Swoole/ReactPHP 常驻进程——本期不做。

3.3 JSON-RPC 方法最小集(只做 tools)

方法处理
initialize回 protocolVersion + capabilities.tools(不做 listChanged)+ serverInfo{name,version} + instructions(可放一句「本站 Token 权限说明」)
notifications/initialized202 空响应
ping空 result
tools/list返回按 Token 权限过滤后的工具数组(nextCursor 省略,工具数量少,不做分页)
tools/callparams.name + params.arguments → 执行 → result.content[] + result.isError

错误口径(重要,直接决定 Agent 能否自我修正):

  • 协议级错误(未知方法 / 未知工具 / 参数结构非法)→ JSON-RPC error(如 -32602)。
  • 业务级错误(版块要前缀、积分不足、无权限、被限流、被封禁)→ result.isError: true + content[0].text 写人类可读中文,让模型自己改参数重试。
  • 例:{"isError":true,"content":[{"type":"text","text":"该版块要求选择标题前缀,请从 [BUG提交] [优化提交] 中选一个"}]}

3.4 鉴权:自定义 Bearer Token

  • 规范中 Authorization 是 OPTIONAL;只有在「支持鉴权」时才 SHOULD 遵循 OAuth 章。→ 自定义静态 Bearer 不违规。
  • 规范同时禁止 token passthrough(不得接受/透传非本服务签发的令牌)→ 我们只认本插件签发的 token,符合要求。
  • 三家客户端均支持静态请求头:
  • Cursor:mcp.json 的 headers
  • Trae:HTTP 类型的 headers
  • Claude:Custom Connector → 「No sign-in」+ Request headers(beta)

3.5 客户端接入配置(示例)

{
  "mcpServers": {
    "flinthub": {
      "url": "https://www.flinthub.top/mcp",
      "headers": { "Authorization": "Bearer fh_mcp_xxxxxxxx" }
    }
  }
}

本地 stdio 形态本期不做(PHP CLI 桥接需要额外分发一个脚本,收益低、维护成本高)。


4. 详细方案

4.1 目录结构

plugins/mcp_server/
├── plugin.json               # permissions: ["route:admin"]
├── Plugin.php                # 生命周期 + class_alias(目录含下划线)
├── McpServer.php             # JSON-RPC 2.0 分发(initialize/ping/tools.list/tools.call)
├── Transport.php             # HTTP 层:Origin 校验、Accept 校验、Bearer 解析、202/405/400 语义
├── TokenManager.php          # Token 生成 / 校验 / 撤销 / 限流 / 额度
├── ToolRegistry.php          # 工具清单 + 权限过滤 + 动态 schema 合并
├── Tools/
│   ├── ReadTools.php         # list_categories / search_threads / get_thread / list_replies / get_user
│   └── WriteTools.php        # create_thread / reply_thread / edit_post / delete_post
├── Audit.php                 # 审计日志写入(截断 + 保留期)
├── FrontController.php       # 前台:Token 管理页
├── AdminController.php       # 后台:开关 / 限流 / 策略 / 审计
├── hook/
│   ├── init_after.php        # 幂等建表
│   ├── route_register.php    # /mcp + /mcp-token*
│   └── admin_route_register.php
├── views/
│   ├── token.php             # 前台 Token 页
│   └── admin.php             # 后台
├── assets/{script.js,style.css}
├── lang/{zh,en,zh_tw}.php
└── data/mcp_server.sqlite    # 独立库(Web 层已被 Plugin::db() 自动拦截)

4.2 plugin.json 关键字段

{
    "name": "MCP 服务",
    "version": "1.0.0",
    "author": "FlintHub",
    "description": "让本机 AI Agent(Claude Desktop / Cursor / Trae)以你的账号读写论坛",
    "activated": false,
    "category": "integration",
    "icon": "robot",
    "admin_url": "/admin/mcp-server",
    "hooks": {
        "init_after": "hook/init_after.php",
        "route_register": "hook/route_register.php",
        "admin_route_register": "hook/admin_route_register.php"
    },
    "permissions": ["route:admin"]
}

  • icon 必须是核心图标表里真实存在的项(robot 已在 ai_assistant 使用,开发前需再 grep 核对)。
  • 不声明 net:fetch(MCP 不主动外呼;纯 JSON 响应无需外网);不声明 system:settings。
  • 权限强制模式:permission_enforce 默认 1(app/Helpers/Plugin.php:155-160)→ 声明必须准确,否则后台路由会被拒。

4.3 Token 体系(用户级)

用户在前台生成 Token → 只在生成时展示一次 → 库里只存 hash

项设计
形态fh_mcp_ + 32 字节随机(base64url);前缀便于正则识别与日志打码
存储只存 sha256 hash;明文永不回显、不入日志
绑定user_id —— 权限完全按该用户走
分级read(默认)/ write(用户主动开)/ admin(默认关,不推荐)
生命周期可设过期(默认不过期)、可随时撤销、可查看最近使用时间
展示前台 Token 页展示「权限范围 / 最近使用 / 创建时间 / 撤销」

校验链:Bearer → 取 hash → 查库 → 校验 enabled/expires → 取 user_id → 载入用户 → 权限/封禁/限流校验 → 执行。

4.4 权限对齐(核心原则)

MCP 请求 → Token 验证 → 用户身份 → 调核心 Model(与「用户正常发帖」同一条路径)→ 落库
                                          ↑
                            版块权限 / 等级 / 积分 / 审核 全部自然生效

  • 绝不自己写库(否则 content_review、points_mall、user_level 的钩子全部不触发)。
  • 审计独立:审计日志额外记录「本次写操作由 MCP 发起」(token_id / tool / 结果)。
  • ⚠️ Model 调用方式必须逐一实测:ai_assistant 的踩坑记录明确写着「Model 读取必须使用实例方法」(plugins/ai_assistant/说明.md:157-164)。Thread::insert() / Post::insert() 是实例方法还是静态方法,开发第一步就核实,不要照抄本方案的写法。

4.5 工具清单

只读工具

工具参数(JSON Schema 概要)落点
list_categories无版块列表(含「是否可发帖」标记)
search_threadskeyword / category_id? / limit?搜索(复用核心搜索路径,开发前确认调用点)
get_threadthread_idThread::find() / getById()(app/Models/Thread.php:184-221)
list_repliesthread_id / page?Post::getByThread()(app/Models/Post.php:337-399)
get_useruser_id 或 username仅公开字段(绝不含邮箱/私信/后台字段)

可写工具

工具参数落点
create_threadcategory_id / title / content / prefix? / tags?Thread::insert()
reply_threadthread_id / content / reply_to?Post::insert()
edit_postpost_id / content仅自己的帖
delete_postpost_id软删,仅自己的帖

P4 候选(见 §4.6,可能不是核心能力)

工具落点说明
vote(点赞/踩)疑为插件 like_points 提供核心可能无内置点赞 → 软依赖
favorite(收藏)疑为插件 post_favorite 提供同上

⚠️ 重要修正:like_points 与 post_favorite 都出现在官方插件市场清单里,说明点赞/收藏很可能是插件能力而非核心能力。若如此,vote / favorite 不应写进 P0/P1,而应放到 P4,并做成「提供方插件在线才注册该工具」的动态注册(见 §4.6)。开发前必须先核实这两个能力的归属。

4.6 动态 Schema 机制(★ 与原规划的关键差异)

问题:forum_required_tag 会在发帖时强制前缀/标签;Agent 不知道规则 → 发帖被拒 → 体验差。

原规划的解法:MCP 直接读 forum_required_tag 的配置。

问题所在:该插件的数据在它自己的独立库(plugins/forum_required_tag/data/forum_required_tag.sqlite,表 frt_forum_tags / frt_forum_prefixes),需要调它的静态方法 Plugin\ForumRequiredTag\Plugin::getForumTags($forumId)。这属于插件间直耦,违反《插件开发规范》§11.6。

正确解法:自定义钩子契约 mcp_tool_schema

                    ┌──────────────────────────────────────┐
  tools/list ──────►│ ToolRegistry                          │
                    │  1. 内置基础 schema                    │
                    │  2. Plugin::hook('mcp_tool_schema', …) │──► 各插件订阅
                    │  3. 合并 → 返回给 Agent                │     (forum_required_tag
                    └──────────────────────────────────────┘      上报前缀 enum / 标签规则)

  • McpServer 在 tools/list 与 tools/call 前调用 Plugin::hook('mcp_tool_schema', $ctx)($ctx 含 category_id、tool_name)。
  • 任何插件想参与,只需在自己 plugin.json 里加一行 "hooks": { "mcp_tool_schema": "hook/mcp_tool_schema.php" }。
  • forum_required_tag 上报:该版块的前缀列表(enum)与标签规则(required / 数量下限),前缀展示形式为 [xxx],库内存储不含方括号。
  • 无人订阅时零影响:退回基础 schema;发帖被拒时,由核心/插件的拒绝信息转成可读中文回给 Agent。
  • ✅ 这样 mcp_server 不认识 forum_required_tag,两个插件永不直耦,未来任何插件都能自助接入。

同样的机制可用于三件事:

  1. 动态 schema(版本前缀 enum、标签必填)
  2. 动态工具注册(like_points / post_favorite 在线时才暴露 vote / favorite)
  3. 动态前置提示(content_review 上报「本版块发帖将进入审核」)

⚠️ 契约的两个硬约束:① 钩子参数用引用回传(Plugin::hook() 的 extract(..., EXTR_REFS) 支持),② 钩子文件必须 include_once 安全(不声明全局符号,或接受只执行一次)。

4.7 安全约束

项要求
权限分级默认 read;write 由用户主动开;admin 默认关
限流按 Token(非按 IP):写操作每 Token 每分钟 N 次(建议 5)。⚠️ 核心 RateLimiter::check($action,$max,$window) 是 IP + Session 双计数(app/Helpers/RateLimiter.php:225-253),MCP 无 Session → 必须插件自建 Token 级计数(用独立库计数表)
冷静期同一 Token 对同一 tool + category_id 的重复调用间隔 ≥ X 秒(建议 30s)
额度每 Token 每日发帖上限(建议 10),可后台配置
审核「AI 发帖先过审核」开关;开启时写操作不进正文库而是走审核路径(复用 content_review 的既有判定入口)
审计所有调用落 mcp_logs:token_id / user_id / tool / 参数摘要 / 结果 / ip_hash / 时间;参数与结果截断存储;保留 90 天
越权Token 权限 ≤ 绑定用户权限;每次调用都校验封禁状态
隐私不读私信、不读后台数据、不读隐藏版块;get_user 只回公开字段
不越权重放写工具必须幂等考虑:冷静期 + 额度 + 可选 idempotency_key 参数

4.8 内容归属与标注

  • 内容归属 Token 绑定用户(无独立「MCP 用户」概念)。
  • 可选标注「此帖由 MCP 客户端发布」:不改核心 → 用渲染钩子注入;标记存独立库(mcp_post_marks 表,记 thread_id/post_id)。
  • 后台总开关:是否允许 AI 发帖(有些站长不想让 AI 灌水)。

4.9 独立库 schema(初稿,SQLite 语法)

-- Token
CREATE TABLE IF NOT EXISTS mcp_tokens (
  id           INTEGER PRIMARY KEY,
  user_id      INTEGER NOT NULL,
  token_hash   TEXT    NOT NULL UNIQUE,
  name         TEXT    NOT NULL DEFAULT '',
  scope        TEXT    NOT NULL DEFAULT 'read',   -- read | write
  enabled      INTEGER NOT NULL DEFAULT 1,
  daily_quota  INTEGER NOT NULL DEFAULT 10,
  last_used_at INTEGER,
  expires_at   INTEGER,
  created_at   INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_mcp_tokens_user ON mcp_tokens(user_id);

-- 审计
CREATE TABLE IF NOT EXISTS mcp_logs (
  id         INTEGER PRIMARY KEY,
  token_id   INTEGER NOT NULL,
  user_id    INTEGER NOT NULL,
  tool       TEXT    NOT NULL,
  params     TEXT    NOT NULL DEFAULT '',   -- 截断
  result     TEXT    NOT NULL DEFAULT '',   -- 截断
  ip_hash    TEXT    NOT NULL DEFAULT '',
  created_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_mcp_logs_created ON mcp_logs(created_at);
CREATE INDEX IF NOT EXISTS idx_mcp_logs_token   ON mcp_logs(token_id, created_at);

-- 限流计数(Token 级)
CREATE TABLE IF NOT EXISTS mcp_rate (
  id       INTEGER PRIMARY KEY,
  token_id INTEGER NOT NULL,
  action   TEXT    NOT NULL,       -- 如 write / tool:create_thread
  window_at INTEGER NOT NULL,      -- 窗口起点(分钟/天)
  counter  INTEGER NOT NULL DEFAULT 0
);
CREATE UNIQUE INDEX IF NOT EXISTS uq_mcp_rate ON mcp_rate(token_id, action, window_at);

-- 内容标记(「由 MCP 发布」)
CREATE TABLE IF NOT EXISTS mcp_post_marks (
  id        INTEGER PRIMARY KEY,
  kind      TEXT    NOT NULL,      -- thread | post
  ref_id    INTEGER NOT NULL,
  token_id  INTEGER NOT NULL,
  created_at INTEGER NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS uq_mcp_marks ON mcp_post_marks(kind, ref_id);

建表走 Plugin::ensureSchema('mcp_server', $ddl);表名统一 mcp_ 前缀防冲突。

4.10 前台 / 后台

前台(用户侧):生成 Token(一次性展示 + 复制)、权限范围(只读/可写)、最近使用记录、撤销。

后台(管理员侧):

项默认
总开关(启用/关闭 MCP)关
允许的最高权限等级read
是否允许 AI 发帖关
AI 发帖是否走审核开(站长可关)
写操作限流(次/分钟/Token)5
每日发帖额度(帖/Token/天)10
冷静期(秒)30
审计保留天数90
审计日志列表 + 一键撤销某 Token 全部会话—


5. 与现有插件生态的交互

总原则:MCP 走 Model 层 → 插件钩子自然触发;规则读取走 mcp_tool_schema 钩子 → 插件自己上报;两者都不产生直耦。

插件影响的 MCP 工具需要读取的规则源需要反映到 schema / 错误信息
forum_required_tagcreate_thread / edit_post独立库 frt_forum_prefixes / frt_forum_tags(通过它自己订阅 mcp_tool_schema 上报)prefix 加 enum;tags 标 required 与数量下限;错误信息给可选项清单
content_reviewcreate_thread / reply_threadcr_* settings;isThreadReviewRequired()返回体标注「已提交审核,暂不可见」;schema 里给出前置提示
mod_system全部mod_bans;isBanned()被禁用户 → 每次调用直接拒绝(Token 视同失效)
user_levelcreate_thread / reply_threaduser_level_cfg 的 gate 字段;calc()等级不足 → 可读中文拒绝,提示所需等级
points_mallcreate_thread / reply_thread走 Model 自动扣积分不足 → 核心错误转可读中文
ai_assistantget_thread 等—反向防护:Agent 读到的 AI 回复应可识别(避免「AI 读 AI」,参考其 防 AI 回 AI 逻辑)
like_points / post_favoritevote / favorite(P4)待核实在线才注册该工具(动态注册)


6. 风险清单与规避

#风险级别规避
1php-fpm 下开 SSE 会占死 worker高不做 SSE,纯 JSON 响应(本方案已定)
2插件间直耦(读别人的库)高走 mcp_tool_schema 自定义钩子契约
3Token 明文泄露高只存 hash、仅生成时展示、日志打码
4Agent 被诱导滥用(灌水/刷帖)高Token 级限流 + 冷静期 + 每日额度 + 站长总开关
5越权(Token 权限 > 用户权限)高每次调用重新校验用户权限与封禁状态
6CSRF 误加到 /mcp中明确不加 Csrf::verifyOrDie(),代码注释写明原因
7Origin 未校验 → DNS rebinding中规范 MUST:校验 Origin,非白名单 403
8MCP-Protocol-Version 版本不一致中宽容读取;非法/不支持才 400
9Model 实例/静态方法搞错中开发第一步实测(ai_assistant 已有踩坑记录)
10点赞/收藏可能是插件能力中先核实归属;P4 + 动态注册
11/mcp 与真实目录冲突低已验证站点根无 mcp 目录
12目录名下划线导致 activate 静默跳过中class_alias 硬要求
13审计表膨胀低保留 90 天 + params/result 截断
14Agent 看到的工具名/描述被误当可信低description 只写事实,不含指令性内容
15协议版本快速演进(2026-07-28 破坏性变更)中锁 2025-06-18 基线;后续再加 server/discover


7. 分期实施与验收标准

验收标准 = 逐条勾选,全绿才算过。

P0:Token 体系 + 只读工具(先跑通「读」)

  1. 插件可激活;Plugin::db('mcp_server') 建库成功;data/.htaccess 自动生成
  2. 前台可生成 Token(明文只显示一次);库里只有 hash
  3. initialize 握手通过(Claude / Cursor / Trae 至少 1 个真实客户端连上)
  4. tools/list 返回 5 个只读工具
  5. search_threads / get_thread / list_replies / list_categories / get_user 均返回正确数据
  6. 无 Token / 错 Token → 401;get_user 不含邮箱等私密字段
  7. GET /mcp → 405;Origin 非法 → 403

P1:可写工具(跑通「发帖 / 回帖」)

  1. create_thread 落库成功,且帖子归属 Token 绑定用户
  2. reply_thread 落库成功,reply_count / last_reply_time 正确
  3. edit_post / delete_post 只能操作自己的帖;操作别人的 → 可读拒绝
  4. 审计日志记录 token_id / user / tool / 结果 / ip_hash
  5. Token 级限流生效(超限返回可读中文);无关 CSRF 报错

P2:生态联动

  1. 开启 forum_required_tag 后,create_thread 的 schema 出现前缀 enum(且 mcp_server 代码里零 forum_required_tag 字样)
  2. 不填前缀发帖 → 错误信息列出可选前缀,Agent 能据此重试成功
  3. 开启 content_review 后,发帖返回体标注「已提交审核」
  4. mod_system 封禁用户 → 全部工具拒绝

P3:审计 / 限流 / 后台

  1. 后台总开关可关闭整个 MCP(关闭后 /mcp 一律 401/503)
  2. 后台可配限流/额度/冷静期/审核策略,改完立即生效
  3. 后台审计列表可翻页、可按 Token / 用户筛选
  4. 站长可一键撤销某 Token 全部权限

P4:高级工具

  1. 点赞/收藏归属核实完成;若为插件能力 → 做成「插件在线才注册工具」
  2. vote / favorite 与前台行为一致(积分、通知等钩子均触发)

出包

  1. php cli/release_build.php --type=plugin --name=mcp_server --version=1.0.0 --dry-run → 9 项门禁全 PASS(含 #9 命名空间一致性)
  2. data/(含 sqlite)不进包;plugin.json / 表单 / zip 名三处版本号一致

8. 待拍板决策(建议,供评审确认)

#问题**建议**影响面
D1传输协议无状态 Streamable HTTP + 纯 JSON(不开 SSE、不分配 Session)决定能否在 php-fpm 直接跑;最关键
D2协议版本锁 2025-06-18 为基线,兼容 2025-11-25决定握手实现与未来兼容成本
D3默认权限只读,write 用户主动开安全基线
D4插件生态联动方式自定义钩子 mcp_tool_schema(禁止直读他插件库)决定是否违反 §11.6;第二关键
D5AI 发帖是否默认走审核默认开(站长可关)内容风险
D6是否标注「由 MCP 发布」默认标注(可开关)透明度
D7限流阈值写操作 5 次/分钟/Token;额度 10 帖/天/Token;冷静期 30s防灌水
D8限流实现插件自建 Token 级计数(核心 RateLimiter 是 IP+Session 口径,不适用)实现方式
D9点赞/收藏归属先核实;非核心能力则降到 P4 + 动态注册工具范围
D10是否支持本地 stdio本期不做分发形态
D11会话/SSE不做运行环境依赖
D12分期节奏P0+P1 先做,验证「读 + 发帖回帖」核心链路,其余按 §7 推进人力投入


附:本方案用到的规范条目索引

§三条硬规则(basename 校验 / SQL 预处理 / 转义) / §红线 8(AJAX 必须 CSRF,但协议端点例外需注明) / §2 目录结构 / §3 plugin.json(icon 必须真实存在) / §4.1 生命周期类名推导 + class_alias / §5.1 自动加载器方向不对称 / §8 语言包(键前缀 plugin.{插件名}.*) / §9 钩子清单与通信约定 / §10.3 SQLite DDL / §11.6 插件间禁止直耦 / §11.10 后台 Tab 标准 / §11.15 建表兜底 / §11.28 登录回跳 / §11.31 禁止每请求写库 / §11.34 命名空间逐字节 / §11.34b 前台路由避开真实目录 / §11.35 三条发布路径 / §12.2 出包前自检

外部依据:MCP 规范 Versioning / Transports(2025-06-18) / Authorization / Tools / Security Best Practices;Claude · Cursor · Trae 的 MCP 接入文档。

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

全部回复 (1)

🌳Lv.4 中级 🌛 见习会员
2026-10-26 01:55:54
「可行,且比原规划更简单」这个结论很有价值——MCP Server 落地最大的坑是「为了协议完整性塞进一堆用不上的能力」,能判断出「只需 tools、不需要 resources/prompts」就砍掉了大半工作量。

三点:

1. 无状态 Streamable HTTP 是正确的取舍(现在线上跑的就是这套),但要注意「写操作幂等键」和「滚动配额」是站方自加的能力,不属于 MCP 规范,客户端不一定传 client_token。服务端必须能容忍缺省(缺省时用 thread 内容哈希兜底),否则重试就会重复发帖。
2. 安全边界写清楚:Token = 用户身份,所以「能做什么」完全取决于该用户权限。要防的是「Token 泄漏后能干什么」,建议把 scope(read/write)做成 Token 级而不是用户级,这样可以发只读 Token。
—— 现在线上就是这么做的,说明方案落地时已经想到了。
3. 端点 POST {BASE_PATH}/mcp 要确认二级目录部署下的路径拼接,以及反向代理对 chunked / SSE 的处理(有些 WAF 会截断分块响应)。

评估稿只做方案不写代码,这个边界守得对。
#1 楼

请 登录