Skip to content

fix: MCP server 外部客户端无法启动 + getMessages 分页失效 - #340

Open
gy-0 wants to merge 2 commits into
ILoveBingLu:mainfrom
gy-0:fix/mcp-asar-and-pagination-clean
Open

fix: MCP server 外部客户端无法启动 + getMessages 分页失效#340
gy-0 wants to merge 2 commits into
ILoveBingLu:mainfrom
gy-0:fix/mcp-asar-and-pagination-clean

Conversation

@gy-0

@gy-0 gy-0 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

问题

1. 第三方 MCP 客户端无法启动 ciphertalk-mcp

dist-electron/mcp.js(MCP server 入口)位于 app.asar.unpacked,但它 require 的依赖 @modelcontextprotocol/sdk 未列入 asarUnpack,仍被打进 app.asar 内。任何外部 MCP 客户端(如 Claude Desktop、Hermes)启动时都会报:

Error: Cannot find module '@modelcontextprotocol/sdk/server/stdio.js'
Require stack:
- .../app.asar.unpacked/dist-electron/mcp.js

因为 mcp.js 能解析到 unpacked 目录下的 zod/better-sqlite3,唯独 SDK 留在 asar 内无法解析。

修复build.asarUnpack 补上 node_modules/@modelcontextprotocol/**/*,与已有的 zod、@ai-sdk 等保持一致。

2. getMessages 分页(offset/时间过滤)失效

electron/services/chat/messageQueries.ts 中三处 SQL 的 OFFSET 参数硬编码为 0

params = [myRowId, minFetchPerDb, 0]   // OFFSET 恒为 0

传入的 offset 从未真正进入 SQL。readService 的 scanOffset 循环每次取到的都是同一批最新消息,导致 MCP get_messages 的 offset 分页与 startTime/endTime 过滤全部失效(翻页永远返回最新一批)。

修复:将 SQL 的 OFFSET 改为传入的 offset,JS 层相应只截前 limit 条(避免双重偏移),hasMore 判断同步修正。

验证

  • npm run build:mcp 通过
  • 外部 MCP 客户端(stdio)可正常启动并完成 initialize 握手
  • get_messages offset 翻页可返回不同批次(修复前 offset=0/200/400 返回完全相同结果)

Fixes #339

gy-0 added 2 commits August 8, 2026 16:40
MCP server 入口 dist-electron/mcp.js 位于 app.asar.unpacked,但其依赖
@modelcontextprotocol/sdk 未列入 asarUnpack,仍留在 app.asar 内,
导致第三方 MCP 客户端启动时报 Cannot find module
'@modelcontextprotocol/sdk/server/stdio.js'。
SQL 查询的 OFFSET 参数被硬编码为 0,传入的 offset 从未真正生效,
导致 scanOffset 循环每次取到的都是同一批最新消息,
get_messages 的分页与时间过滤全部失效。
@ILoveBingLu
ILoveBingLu force-pushed the fix/mcp-asar-and-pagination-clean branch from 88a24e7 to 93d0f03 Compare August 8, 2026 12:28
@ILoveBingLu

Copy link
Copy Markdown
Owner

感谢 PR。两处改动我分开看,结论是:第 1 处建议合入,第 2 处建议撤掉


✅ 修改 1:asarUnpacknode_modules/@modelcontextprotocol/**/* — 正确,请合

问题定位准确。dist-electron/mcp.js 物理位于 app.asar.unpacked/,Node 的模块解析只会沿 app.asar.unpacked/node_modules 往上找,不会回落到 app.asar/node_modules,所以 SDK 留在 asar 内必然 Cannot find module。这是本仓库踩过多次的「传递依赖闭包必须整条 unpack」问题。

我核对了传递闭包,确认这一行是充分的,不需要再补其他包:

  • server/mcp.jsrequire("zod")
  • server/stdio.js / shared/protocol.js / types.jsrequire("zod/v4") + node 内置

zod 已在 asarUnpack 列表中。SDK 的 ajv / hono / express / cross-spawn 等声明依赖在 stdio server 链路上不会被加载。

同时确认对主进程内的 mcpClientService 无回归:主进程入口在 asar 内,@modelcontextprotocol/sdk/client/stdio.js 仍解析到 app.asar/node_modules/...,其 require("cross-spawn")(未 unpack)照旧在 asar 内解析得到。


❌ 修改 2:messageQueries.tsoffset 推进 SQL — 会引入回归,建议撤掉

前提判断有误:SQL 里的 0 不是硬编码 bug,是设计

getMessages 的分页语义是「先跨库归并 → 过滤 → 去重 → 再应用 offset」,三段是配套的:

  • L64 minFetchPerDb = Math.max(offset + limit + 1, 100) —— 每个分库都取够 offset+limit+1
  • L227–237 归并排序 + isMessageVisibleForSession 过滤 + messageIdentityKey 去重
  • L242 slice(offset, offset + limit) —— offset 在这里才生效

offset 必须作用在归并去重之后的列表上,SQL 层传 0 是刻意的。

回归 (a):多分库场景漏消息 + 分页提前终止

findSessionTables 明确会返回跨多个 message_*.db 的 pair(tableResolver.ts L140–147),L229 那句「同一条消息可能在多个数据库中」的去重逻辑就是为多分库写的。改动后每个分库各自 skip 自己的 offset 行,是分布式分页的经典错误:

DB-A 持有 sort_seq 1000–900,DB-B 持有 899–800,offset=100, limit=50
正确结果应为 900–851。
改动后:A 跳过自己最新 100 条 → 只剩 1 条;B 跳过自己最新 100 条 → 返回 0 条。合并后只有 1 条,899–851 全部丢失
同时 hasMore = (1 > 50) || false = false → 翻页在此永久终止,用户再也拉不到更早的历史。

回归 (b):单库场景会重复返回

SQL 层的 offset 是「过滤前行号」,而调用方消费的是「过滤后序号」,两者不等价。readService 的扫描循环按过滤后条数推进(readService.ts L2029 scanOffset += part.length),只要有任意一行被 isMessageVisibleForSession 滤掉,下一页的 SQL 起点就会落在上一页已返回的范围内 → 重复消息。MCP 侧 matched 没有二次去重,重复会直接进结果。

(c) minFetchPerDb 没跟着改

现在变成「先 skip 掉 offset 行,再多捞 offset+limit+1 行,最后只用前 limit 条」,深翻页时 IO 与 XML 解析量比改动前更大;同时让 anyDbHitFetchLimit 更难为真,反过来加剧 (a) 的提前收口。

影响面

chatService.getMessages(offset > 0) 的调用方不止 MCP:ChatPage.tsx L573 的聊天记录上滑翻页也走这条路径。


关于 #339 描述的现象

offset=0/200/400 返回完全相同的最新一批」这个现象,我按现有代码路径复现不出来 —— dbAdapter.all 没有结果缓存,预加载缓存也只在 offset === 0 时命中。能否补一下可复现的会话形态(单库还是多库、消息总量、具体调用参数)?这是分歧的核心。

另外「startTime/endTime 过滤失效」的真实原因不在 SQL OFFSET —— 时间过滤是在 readService.getMessages 的 JS 循环里做的,真正的限制是 L2011 的 scanned < 5000 扫描上限:时间过滤只在「最新 5000 条」范围内生效,起点更早就必然返回空。本 PR 的 diff 没有任何一行涉及时间过滤,PR 描述里这条属于过度声明。

建议的方向

深翻页和时间回溯这两个问题,用仓库里已有的游标 API 一起解:

chatService.getMessagesBefore(sessionId, sortSeq, limit, createTime, localId) 是 keyset 游标分页,chatSearchIndexService 建索引已经在用(chatSearchIndexService.ts L1026)。把 readService.getMessages 的扫描循环从 offset 改成 getMessagesBefore 游标推进,既避开 O(offset²) 的重复解析,也能突破 5000 条上限,而 messageQueries.ts 一行都不用动。

其他


建议动作:把 messageQueries.ts 的改动从本 PR 摘掉,只留 package.json 那一行合入(真 bug、真修好了、风险为零);分页另开一个 PR 走游标方案,附上复现和多分库回归测试。

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.

[Bug] MCP server 第三方客户端无法启动 + get_messages 分页/时间过滤失效

2 participants