feat(mcp): MCP server (search + publish) & fix empty English search index#365
feat(mcp): MCP server (search + publish) & fix empty English search index#365Crokily wants to merge 2 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 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".
| export const GET = (request: Request): Promise<Response> => mcpHandler(request); | ||
| export const POST = createMcpPostHandler(mcpHandler); |
There was a problem hiding this comment.
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 👍 / 👎.
| { | ||
| title: page.title, | ||
| description: page.description, | ||
| url: toAbsoluteSiteUrl(page.url), |
There was a problem hiding this comment.
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 👍 / 👎.
|
收到,我看看 |
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.
f0c9156 to
7812941
Compare
概述
为社区新增一个 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: enfrontmatter 的英文页面因此一并入索引)。修复后英文索引 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 后的安全加固:
req.json())→ 路由层先流式读体、自行解析,失败即返 400 /-32700-32600/auth/me(放大攻击)→ 仅 publish 调用触发校验WWW-Authenticate的 401;后端 5xx/超时返回 503(不再误报 401 导致客户端丢弃有效凭证)验证
pnpm build:通过;路由表 diff 仅新增ƒ /api/mcp与ƒ /.well-known/oauth-protected-resource,无既有路由渲染模式翻转(按 CLAUDE.md 用 build 输出 diff 验证)使用方式
详见
dev_docs/mcp_server.md(含本地开发、限流、已知限制)。已知限制与后续