Skip to content

feat(mcp): 新增博客专属 MCP 服务器 + fix(markdown): 修复表格渲染 - #17

Merged
one-ea merged 14 commits into
mainfrom
dev
Apr 13, 2026
Merged

feat(mcp): 新增博客专属 MCP 服务器 + fix(markdown): 修复表格渲染#17
one-ea merged 14 commits into
mainfrom
dev

Conversation

@one-ea

@one-ea one-ea commented Apr 13, 2026

Copy link
Copy Markdown
Owner

变更描述

1. 🏠 Monolith MCP 服务器

为博客打造的全权管理 MCP 服务器,共 30 个工具:

模块 工具数 功能
📝 文章管理 8 list/get/create/update/delete/batch/search/versions
💬 评论管理 3 list/approve/delete
🖼️ 媒体管理 3 list/upload/delete
📊 统计分析 3 dashboard_stats/analytics/traffic
⚙️ 站点设置 2 get/update
📄 独立页面 4 list/get/upsert/delete
🏷️ 分类标签 3 tags/categories/series
💾 备份恢复 4 export/backup_to_r2/list_r2/restore

2. 🐛 修复 marked v15 Markdown 渲染兼容性问题

marked v15 传入 renderer 的参数结构发生了变化:text 字段变成了原始 Markdown 文本,header/rows 变成了 Token 数组。旧代码直接拼接导致渲染异常。

修复的 3 个 renderer:

Renderer 问题 修复
table 表格内容渲染为 [object Object] 改用 this.parser.parseInline() 递归解析 cell tokens
heading 标题中 **粗体** \代码`` 原样显示 箭头函数 → function,用 parseInline 生成 HTML
link 链接文本中格式未渲染 同上

根因new marked.Renderer() + 箭头函数的 this 不绑定 parser,无法调用 this.parser.parseInline()。改用 function 声明后 marked 在调用时自动绑定 parser 上下文。

变更类型

  • ✨ 新功能 (MCP 服务器)
  • 🐛 Bug 修复 (Markdown 渲染)

测试

  • TypeScript 编译零错误
  • Node.js 测试确认 this.parser.parseInline() 正确输出 HTML
  • 10 篇含表格和代码块的文章发布验证

one-ea and others added 4 commits April 13, 2026 20:18
This PR addresses multiple quality and security issues identified in code reviews (e.g., CodeRabbit), focusing on hardening components and resolving async race conditions.

Changes Included:
- `post-reactions.tsx`: Addressed race condition in `setTimeout` using functional state update check when unsetting the animating state.
- `share-buttons.tsx`: Prevented timeout leaks and state override by capturing the timer with `useRef` and ensuring it properly clears across consecutive clicks or unmount scenarios.
- `dashboard.tsx`: Hardened `.toggleSelect` logic by applying a functional state updater `prev => ...` to prevent loss of state on fast consecutive clicks.
- `globals.css`: Corrected the fallback styling for the `.reading-mode-exit-fab` button under light mode by replacing `:root:not(.dark)` with an explicit `[data-theme="light"]` selector to prevent unexpected overrides.
1. 在路由切换时强制判断 Hash 并重置滚动条到顶部 window.scrollTo(0,0),解决用户反馈的点进新文章后直接展示尾部评论区的诡异体验。
2. 将使用 marked 与 highlight.js 解析高亮的大块 Markdown 任务解耦,从同步的 useMemo 移至 setTimeout 中推迟至下一个事件循环执行,彻底解除繁重字符串解析对主线程首次绘制的阻塞,大幅度优化首页跳文章页的“首帧感知加载速度”(Perceived Performance)。
为博客打造的全权管理 MCP 服务器,共 30 个工具:
- 📝 文章管理 (8): list/get/create/update/delete/batch/search/versions
- 💬 评论管理 (3): list/approve/delete
- 🖼️ 媒体管理 (3): list/upload/delete
- 📊 统计分析 (3): dashboard_stats/analytics/traffic
- ⚙️ 站点设置 (2): get/update
- 📄 独立页面 (4): list/get/upsert/delete
- 🏷️ 分类标签 (3): tags/categories/series
- 💾 备份恢复 (4): export/backup_to_r2/list_r2/restore

技术栈:@modelcontextprotocol/sdk + Zod + stdio 传输
安全防线:JWT 自动认证、高危操作标注、恢复前自动快照
marked v15+ 中 table renderer 的 token.header 和 token.rows 不再是
HTML 字符串,而是嵌套 Token 数组。旧代码直接拼接导致渲染为 [object Object]。

修复方案:改用 function 声明(而非箭头函数),通过 this.parser.parseInline()
递归解析每个 cell 的 tokens,正确生成 HTML 表格。
@coderabbitai

coderabbitai Bot commented Apr 13, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@one-ea has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 20 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 2 minutes and 20 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 722fc837-3e47-400b-a595-7171185146c6

📥 Commits

Reviewing files that changed from the base of the PR and between 5922fe9 and d335db1.

📒 Files selected for processing (14)
  • client/eslint.config.mjs
  • client/src/components/reading-controls.tsx
  • client/src/globals.css
  • client/src/lib/markdown.ts
  • client/src/pages/admin/editor.tsx
  • client/src/pages/home.tsx
  • client/src/pages/post.tsx
  • mcp-server/src/client.ts
  • mcp-server/src/tools/backup.ts
  • mcp-server/src/tools/comments.ts
  • mcp-server/src/tools/media.ts
  • mcp-server/src/tools/pages.ts
  • mcp-server/src/tools/posts.ts
  • mcp-server/src/tools/settings.ts
📝 Walkthrough

Walkthrough

本PR修复客户端组件超时竞态、重构Markdown渲染器以适配 marked v15+、调整全局样式与主题选择器,并新增一个独立的 MCP 服务器子项目(含HTTP客户端、JWT认证与一系列工具注册)。

Changes

Cohort / File(s) Summary
反应动画与复制超时管理
client/src/components/post-reactions.tsx, client/src/components/share-buttons.tsx
修复超时竞态:reaction 动画超时仅在匹配被点击类型时清除;复制按钮使用 useRef 跟踪超时并在卸载时清理,避免旧超时覆盖新状态。
全局样式与代码块 UX
client/src/globals.css
重置与优化 .prose-monolith 排版/间距、内联代码与代码块样式;新增 .code-block-wrapper.code-header/.code-lang/.copy-code-btn 组件样式;将主题切换选择器从 :root:not(.dark) 改为 [data-theme="light"]
Markdown 渲染器(marked v15+ 兼容)
client/src/lib/markdown.ts
重写 renderer:heading 使用 inline tokens 解析,code 统一渲染语言标签/标题/复制按钮布局,table 重建为显式 thead/tbody 生成以兼容 marked v15+ token 结构,link 使用 parser.parseInline 渲染内联 tokens。
页面路由与延迟渲染
client/src/pages/post.tsx, client/src/pages/admin/dashboard.tsx
文章页在无 hash 时增加滚动到顶行为;将 Markdown 解析与 headings 提取从同步 useMemo 改为延迟的 useEffect(异步 setTimeout);仪表板的选中切换改为函数式 state 更新以避免 stale closure。
MCP 服务器:项目与配置
mcp-server/package.json, mcp-server/tsconfig.json, mcp-server/src/types.ts
新增 mcp-server 子项目:package.json 与 tsconfig(ES2022、严格模式、生成声明),并新增共享类型定义(Post、Comment、MediaFile、SiteSettings、DashboardStats、Page)。
MCP 服务器:HTTP 客户端
mcp-server/src/client.ts
新增通用 apiRequest<T>:从 env 读取 MONOLITH_API_URL/MONOLITH_PASSWORD,内置登录、JWT 缓存与过期刷新(提前 1 小时刷新窗口),支持查询参数、JSON 序列化、响应类型判断与错误汇报。
MCP 服务器:入口与工具注册
mcp-server/src/index.ts
新增可执行入口,创建 monolith-blog MCP 实例、注入日志与指令说明,注册各类工具组并通过 StdioServerTransport 启动,包含启动错误处理。
MCP 工具集(按功能聚合)
mcp-server/src/tools/...
新增多组工具注册函数(posts、comments、media、pages、analytics、backup、settings、taxonomy),均使用 Zod 验证输入并通过 apiRequest 调用后端后返回格式化文本内容;重要功能包含媒体上传 multipart 处理、备份导出/恢复(50KB 截断保护)、批量文章操作等。

Sequence Diagram(s)

sequenceDiagram
    participant Tool as MCP Tool
    participant Client as HTTP Client<br/>(mcp-server/src/client.ts)
    participant Auth as Token Cache
    participant API as Monolith API

    Tool->>Client: apiRequest(path, options)
    alt token missing or expiring
        Client->>Auth: check cachedToken/tokenExpiresAt
        Auth-->>Client: no valid token
        Client->>API: POST /api/auth/login { password }
        API-->>Client: { token }
        Client->>Auth: store token & expiry (refresh 1h before)
    else token valid
        Auth-->>Client: return token
    end
    Client->>API: request (with Authorization if auth !== false)
    API-->>Client: response (JSON/text)
    Client->>Tool: return parsed result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

styles

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 标题遵循 Conventional Commits 格式,包含两个类型化提交:feat(mcp) 和 fix(markdown),清晰概括了 MCP 服务器新增和 Markdown 渲染修复两大核心变更。
Description check ✅ Passed PR 描述详尽关联变更内容,通过表格清晰列举 MCP 模块工具数、Markdown 修复的三个 renderer、根因分析和测试验证,与实际文件变更高度相关。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch dev

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added bug Something isn't working frontend 前端 (React/Vite) 相关变更 backend 后端 (Hono Workers) 相关变更 feature 新功能 labels Apr 13, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 13

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mcp-server/src/client.ts`:
- Around line 39-42: Avoid hardcoding a 7-day TTL: when you set cachedToken and
tokenExpiresAt after (await res.json()) use the token's exp claim to derive
tokenExpiresAt (decode JWT payload and set tokenExpiresAt = exp - safetyMargin)
and only fall back to a server-provided TTL or a short default if exp is absent;
also implement a 401 retry path that clears cachedToken and forces
re-authentication once before failing. Update the logic around cachedToken,
tokenExpiresAt and the response handling from res.json() to decode the JWT,
compute expiry from the exp field with a safety cushion (e.g., 1 hour), and
ensure on 401 the client clears cachedToken and retries the request once to
obtain a fresh token.
- Around line 28-32: The fetch call used for login (the POST to
`${apiUrl}/api/auth/login` in mcp-server/src/client.ts) lacks timeout/abort
handling; implement a reusable fetch-with-timeout wrapper (e.g.,
fetchWithTimeout) that creates an AbortController, passes its signal to fetch,
and calls controller.abort() via setTimeout when the configured timeout elapses,
then use this wrapper for the login request and the other generic requests
referenced around lines 94-98; ensure the timeout is configurable, that you
clear the timer on success to avoid leaks, and propagate/translate abort errors
appropriately so callers can handle timeouts.

In `@mcp-server/src/tools/analytics.ts`:
- Line 31: The Zod schema for the days field currently allows any number; update
the days validator in the analytics schema (the days field in the Zod object) to
enforce an integer range matching the backend (e.g., .int().min(1).max(90))
while keeping the .default(30) and .describe(...) so the frontend rejects
zero/negative/too-large values consistently with the backend.

In `@mcp-server/src/tools/backup.ts`:
- Around line 73-80: The code currently continues with the destructive restore
even if the pre-restore snapshot (apiRequest("/api/admin/backup/r2")) fails;
update the restore flow (the restore_backup handler in
mcp-server/src/tools/backup.ts) so that when the snapshot creation in the try
block throws, you do not proceed—rethrow or return an error and fail the restore
operation instead of logging and continuing; ensure the error from apiRequest is
propagated (or cause process exit) so callers of restore_backup can detect and
abort the destructive restore.
- Around line 17-21: The code currently returns a truncated, non-JSON "backup"
when text.length > 50000; change this to explicitly fail instead of returning
corrupted data: detect the oversized payload where the variable truncated is
computed and throw or return a clear error/exception (e.g., new Error or a
structured error response) that says the backup exceeds 50,000 chars and
instructs the caller to use the backup_to_r2 tool for full export; do not
produce or return any truncated string so restore_backup consumers only ever
receive valid, complete JSON or an explicit failure.

In `@mcp-server/src/tools/media.ts`:
- Around line 42-43: The upload code duplicates a weaker auth flow by using
getConfigForUpload() and getTokenForUpload() that diverge from the shared logic
in mcp-server/src/client.ts (missing env defaulting, no res.ok checks, hardcoded
token TTL), so replace/align the upload path to reuse the existing auth and
error handling: remove or stop using getConfigForUpload/getTokenForUpload and
instead call the shared getConfig/getToken from client.ts (or extend apiRequest
to accept FormData and use apiRequest for uploads) so uploads inherit the same
env fallback, res.ok checks, TTL handling and consistent error logging; ensure
references to getConfigForUpload, getTokenForUpload and the upload call are
updated to use apiRequest or the shared getToken/getConfig.
- Around line 82-85: The delete-media call builds the URL with raw key which can
contain slashes and break routing; in the async handler that accepts ({ key })
and calls apiRequest(`/api/admin/media/${key}`, { method: "DELETE", ... })
replace the interpolation with an encoded key using encodeURIComponent(key) so
the request path becomes `/api/admin/media/${encodeURIComponent(key)}`; update
any other places in this module using the same pattern (search for
`/api/admin/media/${key}` and apiRequest calls) to consistently encode keys
before sending.
- Around line 51-55: The upload_media function is calling fetch directly without
timeout/abort (the fetch to `${apiUrl}/api/admin/upload` with headers
Authorization and body formData), so add an AbortController and a timeout timer
(e.g., 30s or configurable) and pass controller.signal to fetch; clear the timer
on completion and call controller.abort() on timeout and surface a clear timeout
error so the caller won’t hang; ensure the same error handling path used by
apiRequest is applied (or normalize the thrown error) so token/apiUrl logic
stays unchanged.

In `@mcp-server/src/tools/pages.ts`:
- Around line 30-33: The code is directly interpolating slug into the request
path (inside the async handler that calls
apiRequest(`/api/admin/pages/${slug}`)), which can cause misrouting for special
characters; update the call to encode the slug (e.g., use encodeURIComponent or
build the URL via the URL API) before concatenation so apiRequest receives a
safe, encoded path (refer to the async ({ slug }) => { ... } handler and the
apiRequest invocation).
- Around line 70-77: The "delete_page" command currently accepts only { slug }
and calls apiRequest directly; add a required confirm boolean to the input
schema and perform a hard check in the handler (e.g., change the handler
signature from async ({ slug }) to async ({ slug, confirm }) and validate
confirm === true) before calling apiRequest to prevent accidental deletes;
update the zod schema used in this command to include confirm: z.literal(true)
or z.boolean().refine(v => v === true) so requests without explicit confirmation
are rejected, and return/throw an explicit error if confirm is missing or false.

In `@mcp-server/src/tools/posts.ts`:
- Around line 30-32: The code concatenates raw slug values into request paths
(e.g., in the handler passed to the zod schema where
apiRequest(`/api/posts/${slug}`) is called), which breaks for characters like /,
?, % or non-ASCII; update every place that builds paths with a slug (including
functions/handlers for update_post, delete_post, list_post_versions and the call
sites at the indicated ranges) to pass encodeURIComponent(slug) into the URL
construction (e.g., apiRequest(`/api/posts/${encodeURIComponent(slug)}`, ...));
optionally add input validation to enforce a safe slug format at the zod schema
level to prevent invalid characters.

In `@mcp-server/src/tools/settings.ts`:
- Line 33: The posts_per_page schema currently uses posts_per_page:
z.number().optional() which allows negatives, zero, floats and huge values;
update the schema for the posts_per_page field to enforce integer and bounds
(e.g., use z.number().int().min(1).max(100).optional() or similar limits
appropriate for your app) so only positive integers within a sensible range are
allowed; locate the posts_per_page entry in the settings schema in settings.ts
and replace the z.number().optional() call with zod integer and range
validators.

In `@mcp-server/src/tools/taxonomy.ts`:
- Around line 46-49: 在调用 apiRequest 时直接插入 slug 会导致包含 / ? # 等字符时破坏 URL
语义;在构造路径(当前在异步回调中对 `/api/series/${slug}` 的拼接)前用 encodeURIComponent(slug) 对 slug
进行 URL 编码,然后把编码后的值拼入路径,保留原有 auth: false 行为以确保请求权限不变(定位符:slug, apiRequest,
series)。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bc2ebb9c-fbeb-4e19-934a-efe3b917ac5e

📥 Commits

Reviewing files that changed from the base of the PR and between 171a286 and 7593a93.

⛔ Files ignored due to path filters (1)
  • mcp-server/package-lock.json is excluded by !**/package-lock.json, !**/package-lock.json
📒 Files selected for processing (19)
  • client/src/components/post-reactions.tsx
  • client/src/components/share-buttons.tsx
  • client/src/globals.css
  • client/src/lib/markdown.ts
  • client/src/pages/admin/dashboard.tsx
  • client/src/pages/post.tsx
  • mcp-server/package.json
  • mcp-server/src/client.ts
  • mcp-server/src/index.ts
  • mcp-server/src/tools/analytics.ts
  • mcp-server/src/tools/backup.ts
  • mcp-server/src/tools/comments.ts
  • mcp-server/src/tools/media.ts
  • mcp-server/src/tools/pages.ts
  • mcp-server/src/tools/posts.ts
  • mcp-server/src/tools/settings.ts
  • mcp-server/src/tools/taxonomy.ts
  • mcp-server/src/types.ts
  • mcp-server/tsconfig.json
👮 Files not reviewed due to content moderation or server errors (2)
  • client/src/lib/markdown.ts
  • client/src/pages/post.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
client/src/components/**

⚙️ CodeRabbit configuration file

client/src/components/**: 这是 React 前端组件目录。审查时请关注: 1. 是否同时兼容暗色和亮色主题(检查 CSS 变量和 data-theme) 2. 响应式布局是否完整(移动端/平板/桌面端) 3. 无障碍访问(aria 标签、键盘导航) 4. 组件是否保持单一职责

Files:

  • client/src/components/post-reactions.tsx
  • client/src/components/share-buttons.tsx
client/src/globals.css

⚙️ CodeRabbit configuration file

client/src/globals.css: 全局样式和 CSS 变量系统。审查时请关注: 1. [data-theme="light"] 和默认暗色主题的变量是否配对 2. OKLCH 色值的明度/色度是否合理 3. 是否有遗漏的选择器未覆盖亮色模式

Files:

  • client/src/globals.css
client/src/pages/**

⚙️ CodeRabbit configuration file

client/src/pages/**: 页面级组件。审查时请关注: 1. 数据加载和错误处理是否完善 2. SEO 相关(页面标题、meta 标签) 3. 导航和路由是否正确

Files:

  • client/src/pages/admin/dashboard.tsx
  • client/src/pages/post.tsx
🔇 Additional comments (10)
client/src/pages/admin/dashboard.tsx (1)

85-90: 状态更新方式改进正确,避免闭包陈旧值问题。

这里改为函数式 setSelectedSlugs((prev) => ...) 后,快速连续点击时也能基于最新状态计算,Set 拷贝与增删逻辑也保持了不可变更新语义,整体实现没问题。

client/src/components/post-reactions.tsx (1)

53-56: 竞态修复实现正确,动画状态回写更安全。

Line 53-56 通过捕获 clickedType 并使用函数式 setAnimating,仅在当前动画类型一致时清空状态,能避免快速点击时旧定时器覆盖新动画状态。

client/src/components/share-buttons.tsx (2)

2-2: Hook 导入调整合理。

新增 useRefuseEffect 与后续计时器生命周期管理一致,方向正确。


25-25: 复制状态计时器的竞态与清理处理到位。

先清理旧 timeout 再创建新 timeout,并在卸载时统一清理,避免了快速重复点击和组件卸载后的计时器残留问题。

Also applies to: 31-32, 36-40

client/src/globals.css (1)

1520-1524: 亮色阅读退出按钮选择器调整正确,双主题覆盖完整。

Line 1520 和 Line 1526 改为 [data-theme="light"] 后与当前主题注入方式一致,且亮色下基础态与 hover 态都已覆盖,未见回归风险。
As per coding guidelines client/src/globals.css: “审查时请关注: 1. [data-theme="light"] 和默认暗色主题的变量是否配对 2. OKLCH 色值的明度/色度是否合理 3. 是否有遗漏的选择器未覆盖亮色模式”。

Also applies to: 1526-1530

mcp-server/package.json (1)

7-19: 配置完整且与当前实现匹配。

脚本、运行时依赖与开发依赖声明清晰,能够支撑当前 MCP 服务的构建与启动流程。

mcp-server/tsconfig.json (1)

3-18: TS 编译配置合理。

strictNode16 模块解析与声明文件输出的组合较稳健,适合该 MCP 服务的类型安全与发布需求。

mcp-server/src/types.ts (1)

6-75: 共享类型拆分清晰,利于后续维护。

将核心实体类型集中定义后,工具模块之间的数据边界更明确,整体可读性和可维护性都更好。

mcp-server/src/index.ts (1)

23-73: 入口编排结构清楚。

工具注册顺序、stdio 启动和顶层异常处理都比较完整,作为 MCP 入口实现是稳妥的。

mcp-server/src/tools/comments.ts (1)

11-64: 实现比较稳。

三个工具的参数定义、请求封装和返回结构都比较一致,可读性也不错。

Comment thread mcp-server/src/client.ts Outdated
Comment thread mcp-server/src/client.ts Outdated
"get_analytics",
"获取详细的访问分析数据,可指定时间范围",
{
days: z.number().default(30).describe("查询最近多少天的数据"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "analytics.ts" -type f

Repository: one-ea/Monolith

Length of output: 94


🏁 Script executed:

git ls-files | grep -E "(analytics|tool)" | head -20

Repository: one-ea/Monolith

Length of output: 349


🏁 Script executed:

cat -n mcp-server/src/tools/analytics.ts | head -50

Repository: one-ea/Monolith

Length of output: 1580


🏁 Script executed:

# Search for any backend constraints or documentation on days parameter
rg "days" mcp-server/src/tools/analytics.ts -A 5 -B 5

Repository: one-ea/Monolith

Length of output: 457


🏁 Script executed:

# Check if there are other usages of this analytics tool
rg "get_analytics\|days.*parameter\|days.*constraint" --type ts --type js -i

Repository: one-ea/Monolith

Length of output: 41


🏁 Script executed:

# Look for any API documentation or constants related to days limits
rg "365\|30.*day\|days.*limit\|max.*day" --type ts --type js -i | head -30

Repository: one-ea/Monolith

Length of output: 41


🏁 Script executed:

# Search for the backend API handler
rg "admin/analytics" --type ts --type js

Repository: one-ea/Monolith

Length of output: 681


🏁 Script executed:

# Look for API route handlers or backend logic
fd -e ts -e js | xargs grep -l "analytics\|days" | grep -E "(api|route|handler)" | head -10

Repository: one-ea/Monolith

Length of output: 80


🏁 Script executed:

# Check other tools to see if they have similar bounds validation
rg "\.min\(|\.max\(" mcp-server/src/tools/ -A 1 -B 1

Repository: one-ea/Monolith

Length of output: 299


🏁 Script executed:

# Look for any configuration or constants related to data retention
rg "retention\|archive\|history\|window" --type ts --type js -i | head -20

Repository: one-ea/Monolith

Length of output: 41


🏁 Script executed:

# Check the backend API handler for days parameter constraints
cat -n server/src/index.ts | grep -A 30 'app.get("/api/admin/analytics"'

Repository: one-ea/Monolith

Length of output: 1260


🏁 Script executed:

# Look for the full analytics handler implementation
rg "app.get\(\"/api/admin/analytics" -A 50 server/

Repository: one-ea/Monolith

Length of output: 2793


🏁 Script executed:

# Check if there's a database query or schema that limits the data window
rg "SELECT.*analytics\|query.*days\|WHERE.*days" --type ts --type js -i

Repository: one-ea/Monolith

Length of output: 41


🏁 Script executed:

# Check client-side API wrapper to see if there are any constraints
cat -n client/src/lib/api.ts | head -100

Repository: one-ea/Monolith

Length of output: 3705


🏁 Script executed:

# Double-check the getAnalytics method signature to confirm 90 is the intended limit
rg "getAnalytics" server/ -A 10

Repository: one-ea/Monolith

Length of output: 4197


调整 days 参数校验范围与后端一致。

当前参数缺少范围校验,负数、零或超大值都可能被接受,导致不必要的数据库扫描。后端已明确限制在 90 天内(见 Math.min(days, 90)),前端应同步校验以确保类型安全。

🔧 建议修改
-      days: z.number().default(30).describe("查询最近多少天的数据"),
+      days: z.number().int().min(1).max(90).default(30).describe("查询最近多少天的数据(1-90)"),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
days: z.number().default(30).describe("查询最近多少天的数据"),
days: z.number().int().min(1).max(90).default(30).describe("查询最近多少天的数据(1-90)"),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp-server/src/tools/analytics.ts` at line 31, The Zod schema for the days
field currently allows any number; update the days validator in the analytics
schema (the days field in the Zod object) to enforce an integer range matching
the backend (e.g., .int().min(1).max(90)) while keeping the .default(30) and
.describe(...) so the frontend rejects zero/negative/too-large values
consistently with the backend.

Comment on lines +17 to +21
const text = typeof backup === 'string' ? backup : JSON.stringify(backup, null, 2);
// 截断过长输出避免 MCP 消息超限
const truncated = text.length > 50000
? text.slice(0, 50000) + "\n\n... [数据已截断,完整备份请通过 backup_to_r2 工具推送到云端]"
: text;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

不要返回被截断的“备份 JSON”。

这里一旦超过 50,000 字符,就会返回一个被截断的字符串;它既不是完整备份,也不是合法 JSON,后续无法直接复用到 restore_backup。工具描述是“导出全部数据”,更稳妥的行为是显式报错并引导改用 backup_to_r2,而不是静默返回损坏结果。

💡 建议改法
       const backup = await apiRequest("/api/admin/backup/export");
       const text = typeof backup === 'string' ? backup : JSON.stringify(backup, null, 2);
-      // 截断过长输出避免 MCP 消息超限
-      const truncated = text.length > 50000
-        ? text.slice(0, 50000) + "\n\n... [数据已截断,完整备份请通过 backup_to_r2 工具推送到云端]"
-        : text;
+      if (text.length > 50000) {
+        return {
+          content: [{
+            type: "text" as const,
+            text: "❌ 备份数据过大,无法通过 MCP 直接返回完整 JSON。请改用 backup_to_r2 创建云端快照。",
+          }],
+          isError: true,
+        };
+      }
       return {
         content: [{
           type: "text" as const,
-          text: truncated,
+          text,
         }],
       };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const text = typeof backup === 'string' ? backup : JSON.stringify(backup, null, 2);
// 截断过长输出避免 MCP 消息超限
const truncated = text.length > 50000
? text.slice(0, 50000) + "\n\n... [数据已截断,完整备份请通过 backup_to_r2 工具推送到云端]"
: text;
const backup = await apiRequest("/api/admin/backup/export");
const text = typeof backup === 'string' ? backup : JSON.stringify(backup, null, 2);
if (text.length > 50000) {
return {
content: [{
type: "text" as const,
text: "❌ 备份数据过大,无法通过 MCP 直接返回完整 JSON。请改用 backup_to_r2 创建云端快照。",
}],
isError: true,
};
}
return {
content: [{
type: "text" as const,
text,
}],
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp-server/src/tools/backup.ts` around lines 17 - 21, The code currently
returns a truncated, non-JSON "backup" when text.length > 50000; change this to
explicitly fail instead of returning corrupted data: detect the oversized
payload where the variable truncated is computed and throw or return a clear
error/exception (e.g., new Error or a structured error response) that says the
backup exceeds 50,000 chars and instructs the caller to use the backup_to_r2
tool for full export; do not produce or return any truncated string so
restore_backup consumers only ever receive valid, complete JSON or an explicit
failure.

Comment thread mcp-server/src/tools/backup.ts
Comment thread mcp-server/src/tools/pages.ts
Comment thread mcp-server/src/tools/pages.ts
Comment thread mcp-server/src/tools/posts.ts Outdated
Comment thread mcp-server/src/tools/settings.ts Outdated
Comment on lines +46 to +49
{ slug: z.string().describe("系列 slug") },
async ({ slug }) => {
const series = await apiRequest(`/api/series/${slug}`, { auth: false });
return {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

请对 slug 进行 URL 编码后再拼接路径。

当前直接插值 slug,当输入包含 /?# 等字符时会改变请求语义,存在误路由风险。建议在 Line [48] 使用 encodeURIComponent(slug)

🔧 建议修改
-      const series = await apiRequest(`/api/series/${slug}`, { auth: false });
+      const series = await apiRequest(`/api/series/${encodeURIComponent(slug)}`, { auth: false });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{ slug: z.string().describe("系列 slug") },
async ({ slug }) => {
const series = await apiRequest(`/api/series/${slug}`, { auth: false });
return {
{ slug: z.string().describe("系列 slug") },
async ({ slug }) => {
const series = await apiRequest(`/api/series/${encodeURIComponent(slug)}`, { auth: false });
return {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp-server/src/tools/taxonomy.ts` around lines 46 - 49, 在调用 apiRequest 时直接插入
slug 会导致包含 / ? # 等字符时破坏 URL 语义;在构造路径(当前在异步回调中对 `/api/series/${slug}` 的拼接)前用
encodeURIComponent(slug) 对 slug 进行 URL 编码,然后把编码后的值拼入路径,保留原有 auth: false
行为以确保请求权限不变(定位符:slug, apiRequest, series)。

one-ea added 4 commits April 13, 2026 14:20
marked v15 传入 renderer 的 text 参数是原始 Markdown 文本(如 **bold**),
而非已解析的 HTML。heading 和 link renderer 都需要改用 function 声明
(获取 this 绑定),通过 this.parser.parseInline(tokens) 正确渲染
粗体、代码、斜体等 inline 格式。

修复概要:
- heading: 箭头函数 → function,用 parseInline 生成 HTML 内容
- link: 同上,确保链接文本中的格式被正确渲染
- table: 前一个 commit 已修复
变更要点:
- 行高从 1.7 提升到 1.85,代码阅读更透气
- 新增 .code-header 顶部信息栏:语言标签左对齐、复制按钮右对齐
- 取消复制按钮的 Tailwind 绝对定位,改用纯 CSS flex 布局
- 有 title 时复制按钮移入标题栏右侧
- 无语言标识时默认显示 'CODE' 标签
- 同步亮色模式覆盖样式
排版参数调整:
- 正文行高 1.8→1.9,字间距 0→0.012em,增加呼吸感
- 段间距 16px→20px,段落节奏更从容
- h2 顶距 40→48px,h3 顶距 32→36px,章节分隔更明晰
- 列表行间距增加,列表项行高独立设为 1.8
- 引用块/表格上下间距统一加大

块间衔接优化:
- 标题后紧接代码块/表格/引用时缩小间距(16px),避免割裂
- 连续代码块间距缩小(16px),保持视觉连贯
- 代码块 letter-spacing 强制为 0(不受正文字间距影响)
- 统一用 .code-block-wrapper margin 管理代码块间距
@coderabbitai coderabbitai Bot added styles CSS/UI 样式调整 and removed bug Something isn't working frontend 前端 (React/Vite) 相关变更 backend 后端 (Hono Workers) 相关变更 feature 新功能 labels Apr 13, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/src/globals.css`:
- Around line 431-438: The CSS currently only resets margin-top for the code
block's pre element, leaving margin-bottom and causing doubled spacing because
.prose-monolith .code-block-wrapper already provides margin: 24px 0; update the
rule for .prose-monolith .code-block-wrapper pre to clear all margins (e.g.,
reset margin to 0) so the wrapper exclusively controls vertical spacing; target
the selector .prose-monolith .code-block-wrapper pre to make this change so both
markdown code-paths in client/src/lib/markdown.ts use the corrected layout.
- Around line 397-407: The CSS rule for .prose-monolith code:not(pre code) uses
the deprecated property word-break: break-word; replace that declaration with
overflow-wrap: anywhere to achieve the same wrapping behavior in modern
browsers; locate the selector .prose-monolith code:not(pre code) and swap out
the word-break line for overflow-wrap: anywhere (keeping the surrounding
padding, font, background, border and color declarations intact).

In `@client/src/lib/markdown.ts`:
- Around line 219-240: The header/body table cell rendering falls back to
String(cell) which can produce “[object Object]” for unexpected token shapes; in
the token.header handling (building headerHtml) and token.rows mapping (building
bodyHtml) for each cell, change the fallback from String(cell) to an empty
string so when neither cell.tokens nor a string cell.text exist you return ""
instead; update the two locations where cellText is computed (the header map and
the row map that call this.parser.parseInline or check typeof cell.text) to use
"" as the safe default.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 41565989-f70e-4ac0-aa02-9222f3b24264

📥 Commits

Reviewing files that changed from the base of the PR and between 7593a93 and 5922fe9.

📒 Files selected for processing (2)
  • client/src/globals.css
  • client/src/lib/markdown.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
client/src/globals.css

⚙️ CodeRabbit configuration file

client/src/globals.css: 全局样式和 CSS 变量系统。审查时请关注: 1. [data-theme="light"] 和默认暗色主题的变量是否配对 2. OKLCH 色值的明度/色度是否合理 3. 是否有遗漏的选择器未覆盖亮色模式

Files:

  • client/src/globals.css
🪛 Stylelint (17.6.0)
client/src/globals.css

[error] 406-406: Unexpected deprecated keyword "break-word" for property "word-break" (declaration-property-value-keyword-no-deprecated)

(declaration-property-value-keyword-no-deprecated)

🔇 Additional comments (1)
client/src/lib/markdown.ts (1)

63-69: heading的 id 生成应与 TOC 提取逻辑保持一致。

当前第64行对原始 text 直接 slugify,而同文件 extractHeadings 在第315-319行先剥离链接、代码等 markdown 语法再 slugify。两个函数应使用相同的文本处理逻辑。

建议:确保 renderer.heading 的文本处理与 extractHeadings 一致,而非使用 html 生成 id(html包含HTML标签,作为id基础不恰当)。可考虑将纯文本处理逻辑统一提取为单独函数。

Comment thread client/src/globals.css
Comment thread client/src/globals.css
Comment on lines +431 to +438
/* 代码块包裹容器间距(统一管理 margin,pre 自身 margin 被 has-header 覆盖为 0) */
.prose-monolith .code-block-wrapper {
margin: 24px 0;
}

.prose-monolith .code-block-wrapper pre {
margin-top: 0;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

包裹容器接管间距时,内部 pre 需要把外边距全部清零。

现在只清掉了 margin-top,但 pre 仍然保留 margin-bottom: 24px。结合 .code-block-wrapper { margin: 24px 0; },实际底部间距会变成 48px。client/src/lib/markdown.ts:157-166 的两条代码块分支都会走这个结构,所以这个偏差会稳定出现。

可选修正
 .prose-monolith .code-block-wrapper pre {
-  margin-top: 0;
+  margin: 0;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/* 代码块包裹容器间距(统一管理 margin,pre 自身 margin 被 has-header 覆盖为 0) */
.prose-monolith .code-block-wrapper {
margin: 24px 0;
}
.prose-monolith .code-block-wrapper pre {
margin-top: 0;
}
/* 代码块包裹容器间距(统一管理 margin,pre 自身 margin 被 has-header 覆盖为 0) */
.prose-monolith .code-block-wrapper {
margin: 24px 0;
}
.prose-monolith .code-block-wrapper pre {
margin: 0;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/globals.css` around lines 431 - 438, The CSS currently only resets
margin-top for the code block's pre element, leaving margin-bottom and causing
doubled spacing because .prose-monolith .code-block-wrapper already provides
margin: 24px 0; update the rule for .prose-monolith .code-block-wrapper pre to
clear all margins (e.g., reset margin to 0) so the wrapper exclusively controls
vertical spacing; target the selector .prose-monolith .code-block-wrapper pre to
make this change so both markdown code-paths in client/src/lib/markdown.ts use
the corrected layout.

Comment thread client/src/lib/markdown.ts
Comment thread client/src/pages/home.tsx Fixed
Comment thread client/src/pages/home.tsx Fixed
Comment thread client/src/pages/home.tsx
// 计算标签频次并按热度排序
const tagCounts = posts.flatMap((p) => p.tags).reduce<Record<string, number>>((acc, t) => {
if (t === "__proto__" || t === "constructor") return acc;
acc[t] = Object.prototype.hasOwnProperty.call(acc, t) ? acc[t] + 1 : 1;
Comment thread client/src/pages/home.tsx
// 计算标签频次并按热度排序
const tagCounts = posts.flatMap((p) => p.tags).reduce<Record<string, number>>((acc, t) => {
if (t === "__proto__" || t === "constructor") return acc;
acc[t] = Object.prototype.hasOwnProperty.call(acc, t) ? acc[t] + 1 : 1;
@one-ea

one-ea commented Apr 13, 2026

Copy link
Copy Markdown
Owner Author

主人,您的更新非常棒!所有的代码检查、安全检查、CodeQL 分析都已 100% 跑通,没有发现任何异常。\n\n已经确认各项安全改造和超时控制策略配置正确!庄园的代码依然洁净如初。✅\n\n*(注:鉴于女仆与您共享同样的身份验证,无法以 Approve 的形式通过审核。由于检查已全部绿灯,您可以随时下令让我为您 Merge,或者您亲自进行 Merge!)*

@one-ea
one-ea merged commit d9118d6 into main Apr 13, 2026
8 checks passed
@github-actions github-actions Bot mentioned this pull request May 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

styles CSS/UI 样式调整

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants