Skip to content

feat(mcp): MCP server (search + publish) & fix empty English search index#365

Open
Crokily wants to merge 2 commits into
mainfrom
feat/Crokily/mcp-server
Open

feat(mcp): MCP server (search + publish) & fix empty English search index#365
Crokily wants to merge 2 commits into
mainfrom
feat/Crokily/mcp-server

Conversation

@Crokily

@Crokily Crokily commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

概述

为社区新增一个 MCP server(/api/mcp,Streamable HTTP、无状态、随站部署 Vercel),让所有支持 MCP 的 AI 客户端(Claude Code / claude.ai / ChatGPT / Cursor / VS Code 等)可以直接搜索站内文章、发布轻量帖子。同时修复了排查过程中发现的一个存量 bug:主站英文搜索索引一直是空的

意外发现的存量 bug:/search.en.json 为空

source.getPages() 不带 locale 参数只返回默认语言(zh)的页面数据,因此 .filter(isEnglishPage) 恒为空集——构建出的英文静态索引 Orama dump 里 docs count = 0(中文索引 7859 条),站内英文搜索一直静默返回空结果。

修复(单独 commit fix(search)):改用 getPages("en") 枚举 + 语言判别增加 .en.md(x) 文件后缀识别(8 个缺 lang: en frontmatter 的英文页面因此一并入索引)。修复后英文索引 152 个页面 / 7709+ 条记录,中文索引不受影响。

MCP server(commit feat(mcp)

两个工具:

  • search(query, locale?, limit?)——服务端 Orama 查询,完全复用现有 /search.zh.json/search.en.json 的索引数据管线(source.getPages + pageToIndex + mandarin tokenizer),公开可用,Upstash 每 IP 60s/30 次限流(未配置环境变量时自动放行,适配本地开发)。短中文词增加 exact-phrase 重排避免 tolerance: 1 的近形词干扰。
  • publish(title, content_md, description?, tags?, slug?)——转发到后端 POST /api/posts,需要 Authorization: Bearer <satoken>。成功返回文章公开 URL。

鉴权设计(为 OAuth 升级预留):所有凭证校验收敛在 lib/mcp/auth.ts 单模块,只对 publish 调用触发(调后端 /auth/me 校验);/.well-known/oauth-protected-resource 元数据端点已就位。后端授权服务器的两条落地路线、端点清单、迁移共存方案和测试义务见 dev_docs/mcp_auth_upgrade.md,供后端团队评估排期——上线后前端只需在 verifyToken 加一条分支。

对抗性 review 后的安全加固:

  • 畸形 JSON 会挂死函数到 60s 超时(上游 mcp-handler 不 catch req.json())→ 路由层先流式读体、自行解析,失败即返 400 / -32700
  • 请求体 1MB 上限(413)+ 全部工具输入字段长度上限
  • 拒绝 JSON-RPC batch(协议 2025-06-18 起已移除,SDK 仍会执行)→ 400 / -32600
  • 任意伪造 token 可绕过限流直打后端 /auth/me(放大攻击)→ 仅 publish 调用触发校验
  • 无效 token 返回带 WWW-Authenticate 的 401;后端 5xx/超时返回 503(不再误报 401 导致客户端丢弃有效凭证)
  • 两处后端 fetch 增加 10s 超时;shard 构建失败不再永久毒化缓存;索引文本 normalize 预计算(省每查询 2-3.6ms CPU)

验证

  • vitest:99/99 通过(新增 47 个覆盖 schema 校验、鉴权路径、错误转译、请求防线、缓存驱逐、locale 判别)
  • lint / typecheck:与 main 基线完全一致(0 errors)
  • pnpm build:通过;路由表 diff 仅新增 ƒ /api/mcpƒ /.well-known/oauth-protected-resource无既有路由渲染模式翻转(按 CLAUDE.md 用 build 输出 diff 验证)
  • E2E(本地 dev server,MCP 协议 JSON-RPC 实测):initialize / tools list(恰好 2 个工具、均带 outputSchema)/ 中英文真实语料搜索 / 匿名与无效 token 发布 401 / 后端不可达 503(7ms 返回)/ 畸形 JSON 400 / batch 400 / 超长输入 -32602 / >1MB 413,全程无 unhandledRejection

使用方式

# 搜索(匿名)
claude mcp add --transport http involutionhell https://involutionhell.com/api/mcp

# 发布(配 satoken)
claude mcp add --transport http involutionhell https://involutionhell.com/api/mcp \
  --header "Authorization: Bearer <你的satoken>"

详见 dev_docs/mcp_server.md(含本地开发、限流、已知限制)。

已知限制与后续

  • satoken 30 天有效期,过期需重新配置;OAuth 上线前网页端 MCP 客户端建议仅用匿名 search
  • 后续计划:配套 Agent Skill + CLI(独立仓库)、MCP Registry 登记、OAuth 接线(等后端)

@vercel

vercel Bot commented Jul 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
involutionhell-github-io Ready Ready Preview, Comment Jul 12, 2026 8:06am
website-preview Ready Ready Preview, Comment Jul 12, 2026 8:06am

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f0c9156e8a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/api/mcp/route.ts
Comment on lines +26 to +27
export const GET = (request: Request): Promise<Response> => mcpHandler(request);
export const POST = createMcpPostHandler(mcpHandler);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add CORS handling to the MCP endpoint

For browser-hosted MCP clients, tool calls are cross-origin POSTs with Content-Type: application/json and often Authorization/Mcp-Protocol-Version, so the browser preflights OPTIONS /api/mcp and then requires Access-Control-Allow-Origin on the POST response. This route only exports GET/POST and returns handler responses as-is, while only the metadata route has CORS, so those clients are blocked before even anonymous search can run. Please export OPTIONS and wrap the MCP responses with CORS headers.

Useful? React with 👍 / 👎.

Comment thread lib/mcp/search-core.ts
{
title: page.title,
description: page.description,
url: toAbsoluteSiteUrl(page.url),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Return locale-prefixed article URLs from search

When locale: "en" is requested, page.url still comes from Fumadocs' base URL (/docs/...) rather than the next-intl route (/en/docs/...); this repo's routing uses localePrefix: "always" with default zh. An MCP client opening the absolute URL returned here has no current locale context, so English search results can be negotiated or defaulted to the Chinese page instead of the English translation. Include the shard locale when building URLs, e.g. /en${page.url} for English and /zh${page.url} for Chinese.

Useful? React with 👍 / 👎.

@longsizhuo

Copy link
Copy Markdown
Member

收到,我看看

Crokily added 2 commits July 12, 2026 17:57
source.getPages() without a locale argument only returns default-locale
(zh) pages, so the static /search.en.json index has been empty since the
i18n split. Enumerate pages with getPages("en") and classify English
pages by the .en.md(x) file suffix in addition to lang frontmatter, so
translations missing lang: en are indexed and excluded from the zh shard.
Expose /api/mcp (Streamable HTTP, stateless) with two tools:

- search: server-side Orama query over the existing zh/en Fumadocs
  index pipeline, public with Upstash rate limiting
- publish: forwards to the backend POST /api/posts with the caller's
  satoken (Authorization: Bearer), returning the public post URL

Credential verification is isolated in lib/mcp/auth.ts and only runs
for publish calls; invalid tokens get a 401 challenge, auth-service
outages a 503, and both backend fetches carry 10s timeouts. The route
validates body size (1 MB), JSON syntax, and rejects JSON-RPC batches
before the transport. /.well-known/oauth-protected-resource prepares
the OAuth resource-server metadata; the authorization-server rollout
plan for the backend team is documented in dev_docs/mcp_auth_upgrade.md.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants