mcp-probe-kit 3.0.22 → 3.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +9 -11
  2. package/build/index.js +1 -5
  3. package/build/lib/__tests__/memory-injection.unit.test.js +1 -0
  4. package/build/lib/__tests__/memory-orchestration.unit.test.js +4 -0
  5. package/build/lib/memory-client.d.ts +2 -0
  6. package/build/lib/memory-client.js +1 -0
  7. package/build/lib/memory-config.d.ts +2 -0
  8. package/build/lib/memory-config.js +1 -0
  9. package/build/lib/memory-orchestration.d.ts +5 -0
  10. package/build/lib/memory-orchestration.js +48 -4
  11. package/build/schemas/index.d.ts +0 -48
  12. package/build/schemas/memory-tools.d.ts +0 -48
  13. package/build/schemas/memory-tools.js +0 -29
  14. package/build/tools/__tests__/cursor-history.unit.test.js +0 -49
  15. package/build/tools/__tests__/read_memory_asset.unit.test.d.ts +1 -0
  16. package/build/tools/__tests__/read_memory_asset.unit.test.js +75 -0
  17. package/build/tools/__tests__/search_memory.unit.test.d.ts +1 -0
  18. package/build/tools/__tests__/search_memory.unit.test.js +89 -0
  19. package/build/tools/index.d.ts +0 -2
  20. package/build/tools/index.js +0 -2
  21. package/build/tools/read_memory_asset.js +2 -1
  22. package/build/tools/search_memory.js +4 -2
  23. package/docs/data/tools.js +2 -34
  24. package/docs/i18n/all-tools/en.json +5 -15
  25. package/docs/i18n/all-tools/ja.json +3 -5
  26. package/docs/i18n/all-tools/ko.json +3 -5
  27. package/docs/i18n/all-tools/zh-CN.json +5 -15
  28. package/docs/i18n/en.json +10 -10
  29. package/docs/i18n/ja.json +9 -9
  30. package/docs/i18n/ko.json +9 -9
  31. package/docs/i18n/zh-CN.json +10 -10
  32. package/docs/memory-local-setup.md +1 -0
  33. package/docs/pages/all-tools.html +4 -4
  34. package/docs/pages/examples.html +2 -2
  35. package/docs/pages/getting-started.html +1 -1
  36. package/docs/pages/migration.html +2 -2
  37. package/package.json +2 -2
  38. package/build/tools/cursor_list_conversations.d.ts +0 -7
  39. package/build/tools/cursor_list_conversations.js +0 -35
  40. package/build/tools/cursor_search_conversations.d.ts +0 -7
  41. package/build/tools/cursor_search_conversations.js +0 -36
package/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
 
16
16
  > **Talk is cheap, show me the Context.**
17
17
  >
18
- > mcp-probe-kit is a protocol-level toolkit designed for developers who want AI to truly understand their project's intent. It's not just a collection of 29 tools—it's a context-aware system that helps AI agents grasp what you're building.
18
+ > mcp-probe-kit is a protocol-level toolkit designed for developers who want AI to truly understand their project's intent. It's not just a collection of 27 tools—it's a context-aware system that helps AI agents grasp what you're building.
19
19
 
20
20
  **Languages**: [English](README.md) | [简体中文](i18n/README.zh-CN.md) | [日本語](i18n/README.ja-JP.md) | [한국어](i18n/README.ko-KR.md) | [Español](i18n/README.es-ES.md) | [Français](i18n/README.fr-FR.md) | [Deutsch](i18n/README.de-DE.md) | [Português (BR)](i18n/README.pt-BR.md)
21
21
 
@@ -26,7 +26,7 @@
26
26
 
27
27
  > 🚀 AI-Powered Complete Development Toolkit - Covering the Entire Development Lifecycle
28
28
 
29
- A powerful MCP (Model Context Protocol) server providing **29 tools** covering the complete workflow from product analysis to final release (Requirements → Design → Development → Quality → Release), all tools support **structured output**.
29
+ A powerful MCP (Model Context Protocol) server providing **27 tools** covering the complete workflow from product analysis to final release (Requirements → Design → Development → Quality → Release), all tools support **structured output**.
30
30
 
31
31
  **🎉 v3.0 Major Update**: Streamlined tool count, focus on core competencies, eliminate choice paralysis, let AI do more native work
32
32
 
@@ -42,7 +42,7 @@ A powerful MCP (Model Context Protocol) server providing **29 tools** covering t
42
42
 
43
43
  - [Quick Start](https://mcp-probe-kit.bytezonex.com/pages/getting-started.html) - Setup in 5 minutes
44
44
  - [Local Memory Stack (Qdrant + Nomic Embed)](docs/memory-local-setup.md) - Docker Compose, ports `50008` / `50012`, MCP env
45
- - [All Tools](https://mcp-probe-kit.bytezonex.com/pages/all-tools.html) - Complete list of 29 tools
45
+ - [All Tools](https://mcp-probe-kit.bytezonex.com/pages/all-tools.html) - Complete list of 27 tools
46
46
  - [Best Practices](https://mcp-probe-kit.bytezonex.com/pages/examples.html) - Full development workflow guide
47
47
  - [v3.0 Migration Guide](https://mcp-probe-kit.bytezonex.com/pages/migration.html) - Upgrade from v2.x to v3.0
48
48
 
@@ -64,8 +64,8 @@ A powerful MCP (Model Context Protocol) server providing **29 tools** covering t
64
64
  - `init_project`, `init_project_context`, `add_feature`, `estimate`, `interview`, `ask_user`
65
65
  - **🎨 UI/UX Utilities** (3 tools) - Design systems and UI data synchronization
66
66
  - `ui_design_system`, `ui_search`, `sync_ui_data`
67
- - **🧠 Memory & Cursor History** (7 tools) - Reusable asset memory and local Cursor conversation retrieval
68
- - `search_memory`, `read_memory_asset`, `memorize_asset`, `scan_and_extract_patterns`, `cursor_list_conversations`, `cursor_search_conversations`, `cursor_read_conversation`
67
+ - **🧠 Memory & Cursor History** (5 tools) - Reusable asset memory and local Cursor conversation retrieval
68
+ - `search_memory`, `read_memory_asset`, `memorize_asset`, `scan_and_extract_patterns`, `cursor_read_conversation`
69
69
 
70
70
  ### 🧠 Code Graph Bridge (GitNexus)
71
71
 
@@ -93,16 +93,16 @@ A powerful MCP (Model Context Protocol) server providing **29 tools** covering t
93
93
  - Embedding service supports two modes:
94
94
  - `ollama`
95
95
  - `openai-compatible`
96
- - Cursor history tools read the local Cursor database directly through Node.js, without Python bridge
96
+ - `cursor_read_conversation` reads the local Cursor database directly through Node.js, without Python bridge
97
97
  - Cursor history currently supports:
98
98
  - Windows: `%APPDATA%\\Cursor\\User\\globalStorage\\state.vscdb`
99
99
  - macOS: `~/Library/Application Support/Cursor/User/globalStorage/state.vscdb`
100
100
  - Linux: `~/.config/Cursor/User/globalStorage/state.vscdb`
101
101
 
102
102
  **Memory tools:**
103
- - `search_memory` - Semantic search across the shared memory pool (optionally prefer `type` / `tags`)
103
+ - `search_memory` - Semantic search across the shared memory pool (optionally prefer `type` / `tags`); text output includes `id`, `score`, summary, description, and a `--- content ---` body (default up to 1500 chars via `MEMORY_SEARCH_CONTENT_MAX_CHARS`)
104
104
  - `memorize_asset` - Persist reusable code/spec/pattern assets into vector memory
105
- - `read_memory_asset` - Read full asset content by `asset_id`
105
+ - `read_memory_asset` - Read full asset content by `asset_id` (text output includes the full `content` body)
106
106
  - `scan_and_extract_patterns` - Extract reusable patterns from code/file/directory before deciding whether to persist
107
107
 
108
108
  **Cross-repo memory pools:** do not rely on `source_project` / `source_path` for shared retrieval; put file paths in `content` instead. Search injection hides foreign `sourcePath` unless `MEMORY_REPO_ID` matches or `MEMORY_SEARCH_SHOW_SOURCE=true`.
@@ -194,8 +194,6 @@ ollama pull nomic-embed-text
194
194
  ```
195
195
 
196
196
  **Cursor history tools:**
197
- - `cursor_list_conversations` - List recent local Cursor conversations by title/workspace
198
- - `cursor_search_conversations` - Search local Cursor history by keyword or request id
199
197
  - `cursor_read_conversation` - Read a single local Cursor conversation timeline by `composer_id`
200
198
 
201
199
  ### 🎯 Structured Output
@@ -561,7 +559,7 @@ ollama pull nomic-embed-text
561
559
 
562
560
  ### Cursor History Support
563
561
 
564
- Cursor local history tools do not require Qdrant or embedding configuration.
562
+ `cursor_read_conversation` does not require Qdrant or embedding configuration.
565
563
 
566
564
  Supported platforms:
567
565
 
package/build/index.js CHANGED
@@ -5,7 +5,7 @@ import { InMemoryTaskMessageQueue, InMemoryTaskStore, } from "@modelcontextproto
5
5
  import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ProgressNotificationSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
6
6
  import * as fs from "node:fs";
7
7
  import * as path from "node:path";
8
- import { initProject, gencommit, codeReview, codeInsight, gentest, refactor, initProjectContext, addFeature, fixBug, estimate, startFeature, startBugfix, startOnboard, startRalph, interview, askUser, uiDesignSystem, uiSearch, syncUiData, startUi, startProduct, gitWorkReport, searchMemory, readMemoryAsset, memorizeAsset, scanAndExtractPatterns, cursorListConversations, cursorSearchConversations, cursorReadConversation } from "./tools/index.js";
8
+ import { initProject, gencommit, codeReview, codeInsight, gentest, refactor, initProjectContext, addFeature, fixBug, estimate, startFeature, startBugfix, startOnboard, startRalph, interview, askUser, uiDesignSystem, uiSearch, syncUiData, startUi, startProduct, gitWorkReport, searchMemory, readMemoryAsset, memorizeAsset, scanAndExtractPatterns, cursorReadConversation } from "./tools/index.js";
9
9
  import { VERSION, NAME } from "./version.js";
10
10
  import { allToolSchemas } from "./schemas/index.js";
11
11
  import { filterTools, getToolsetFromEnv } from "./lib/toolset-manager.js";
@@ -467,10 +467,6 @@ async function executeTool(name, args, context) {
467
467
  return await memorizeAsset(args);
468
468
  case "scan_and_extract_patterns":
469
469
  return await scanAndExtractPatterns(args);
470
- case "cursor_list_conversations":
471
- return await cursorListConversations(args);
472
- case "cursor_search_conversations":
473
- return await cursorSearchConversations(args);
474
470
  case "cursor_read_conversation":
475
471
  return await cursorReadConversation(args);
476
472
  default:
@@ -20,6 +20,7 @@ describe('memory injection auto-load', () => {
20
20
  type: 'bugfix',
21
21
  description: 'Feishu proxy mismatch',
22
22
  summary: 'proxy caused 400 on HTTPS',
23
+ content: '【现象】submit 成功但 sync_failed\n【根因】HTTP_PROXY 污染\n【修复】proxy:false',
23
24
  tags: ['bugfix', 'proxy'],
24
25
  },
25
26
  ],
@@ -20,6 +20,7 @@ describe('memory-orchestration', () => {
20
20
  type: 'bugfix',
21
21
  description: 'desc',
22
22
  summary: 'summary',
23
+ content: '',
23
24
  tags: ['bugfix'],
24
25
  sourceProject: 'zhixing/gongyingshang',
25
26
  sourcePath: 'admin-api/app.js',
@@ -41,6 +42,7 @@ describe('memory-orchestration', () => {
41
42
  type: 'bugfix',
42
43
  description: '',
43
44
  summary: '',
45
+ content: '',
44
46
  tags: [],
45
47
  sourceProject: 'zhixing/gongyingshang',
46
48
  sourcePath: 'admin-api/app.js',
@@ -52,6 +54,7 @@ describe('memory-orchestration', () => {
52
54
  type: 'bugfix',
53
55
  description: '',
54
56
  summary: '',
57
+ content: '',
55
58
  tags: [],
56
59
  sourceProject: 'other/repo',
57
60
  sourcePath: 'src/index.ts',
@@ -73,6 +76,7 @@ describe('memory-orchestration', () => {
73
76
  type: 'bugfix',
74
77
  description: '',
75
78
  summary: 's',
79
+ content: '',
76
80
  tags: [],
77
81
  sourcePath: 'admin-api/app.js',
78
82
  },
@@ -23,6 +23,8 @@ export interface MemorySearchResult {
23
23
  type: string;
24
24
  description: string;
25
25
  summary: string;
26
+ /** Full payload content from Qdrant (may be empty on legacy points) */
27
+ content: string;
26
28
  tags: string[];
27
29
  sourceProject?: string;
28
30
  sourcePath?: string;
@@ -245,6 +245,7 @@ export class MemoryClient {
245
245
  type: fields.type,
246
246
  description: fields.description,
247
247
  summary: truncate(fields.summary, this.config.summaryMaxChars),
248
+ content: fields.content,
248
249
  tags: fields.tags,
249
250
  sourceProject: fields.sourceProject,
250
251
  sourcePath: fields.sourcePath,
@@ -16,6 +16,8 @@ export interface MemoryConfig {
16
16
  repoId: string;
17
17
  /** Max chars of each asset content injected into start_* guides */
18
18
  injectionContentMaxChars: number;
19
+ /** Max chars of content in search_memory text; 0 = omit content block */
20
+ searchContentMaxChars: number;
19
21
  }
20
22
  export declare function getMemoryConfig(): MemoryConfig;
21
23
  export declare function isMemoryEnabled(config?: MemoryConfig): boolean;
@@ -40,6 +40,7 @@ export function getMemoryConfig() {
40
40
  searchMinScore: getOptionalNumberEnv('MEMORY_SEARCH_MIN_SCORE', 0),
41
41
  repoId: (process.env.MEMORY_REPO_ID || '').trim(),
42
42
  injectionContentMaxChars: getNumberEnv('MEMORY_INJECTION_CONTENT_MAX_CHARS', 1500),
43
+ searchContentMaxChars: getOptionalNumberEnv('MEMORY_SEARCH_CONTENT_MAX_CHARS', 1500),
43
44
  };
44
45
  }
45
46
  export function isMemoryEnabled(config = getMemoryConfig()) {
@@ -13,7 +13,12 @@ export interface MemoryInjectionContext {
13
13
  }
14
14
  export declare function truncateInjectionText(value: string, maxChars: number): string;
15
15
  export declare function loadMemoryInjectionContext(query: string, kind?: MemoryPlanKind): Promise<MemoryInjectionContext>;
16
+ export declare function formatSearchMemoryResultsText(results: MemorySearchResult[], config?: MemoryConfig): string;
16
17
  export declare function shouldShowSourceInSearch(item: MemorySearchResult, config?: MemoryConfig): boolean;
18
+ export declare function formatMemoryAssetText(asset: MemoryAsset, options?: {
19
+ maxContentChars?: number;
20
+ }): string;
21
+ export declare function formatReadMemoryAssetText(asset: MemoryAsset): string;
17
22
  export declare function renderMemoryGuideSection(context: MemoryInjectionContext): string;
18
23
  export declare function buildMemoryPlanStep(kind?: MemoryPlanKind): {
19
24
  id: string;
@@ -77,6 +77,37 @@ function formatMemoryResultLabel(item) {
77
77
  : '历史资产';
78
78
  return `${item.name} [${item.type}] (${kind})`;
79
79
  }
80
+ export function formatSearchMemoryResultsText(results, config = getMemoryConfig()) {
81
+ if (results.length === 0) {
82
+ return '未找到相关记忆';
83
+ }
84
+ const header = `找到 ${results.length} 条相关记忆`;
85
+ const items = results.map((item, index) => {
86
+ const lines = [
87
+ `${index + 1}. ${item.name} [${item.type}] score=${item.score.toFixed(3)}`,
88
+ ` - id: ${item.id}`,
89
+ item.summary ? ` - 摘要: ${item.summary}` : '',
90
+ item.description ? ` - 描述: ${item.description}` : '',
91
+ item.tags.length > 0 ? ` - 标签: ${item.tags.join(', ')}` : '',
92
+ ];
93
+ if (shouldShowSourceInSearch(item, config) && item.sourcePath) {
94
+ lines.push(` - 来源: ${item.sourcePath}`);
95
+ }
96
+ if (config.searchContentMaxChars > 0) {
97
+ const body = truncateInjectionText(item.content || '', config.searchContentMaxChars);
98
+ lines.push(' --- content ---');
99
+ lines.push(body
100
+ ? body
101
+ .split('\n')
102
+ .map((line) => ` ${line}`)
103
+ .join('\n')
104
+ : ' (empty)');
105
+ }
106
+ lines.push(` - 更长全文: read_memory_asset {"asset_id": "${item.id}"}`);
107
+ return lines.filter(Boolean).join('\n');
108
+ });
109
+ return `${header}\n\n${items.join('\n\n')}`;
110
+ }
80
111
  export function shouldShowSourceInSearch(item, config = getMemoryConfig()) {
81
112
  if (config.searchShowSource) {
82
113
  return Boolean(item.sourcePath);
@@ -92,23 +123,36 @@ function formatSourceHint(item, config) {
92
123
  }
93
124
  return `\n - 来源: ${item.sourcePath}`;
94
125
  }
95
- function formatAssetBody(asset, config) {
126
+ export function formatMemoryAssetText(asset, options) {
127
+ const content = options?.maxContentChars !== undefined
128
+ ? truncateInjectionText(asset.content, options.maxContentChars)
129
+ : asset.content;
96
130
  const lines = [
97
131
  `### ${asset.name}`,
98
132
  `- asset_id: ${asset.id}`,
133
+ asset.type ? `- type: ${asset.type}` : '',
134
+ asset.summary ? `- 摘要: ${asset.summary}` : '',
99
135
  asset.description ? `- 描述: ${asset.description}` : '',
100
136
  asset.usage ? `- 适用: ${asset.usage}` : '',
101
137
  asset.tags.length > 0 ? `- 标签: ${asset.tags.join(', ')}` : '',
138
+ asset.sourcePath ? `- 来源: ${asset.sourcePath}` : '',
102
139
  '',
103
- truncateInjectionText(asset.content, config.injectionContentMaxChars),
140
+ '--- content ---',
141
+ content || '(empty)',
104
142
  ].filter(Boolean);
105
143
  return lines.join('\n');
106
144
  }
145
+ export function formatReadMemoryAssetText(asset) {
146
+ return `已读取记忆资产: ${asset.name}\n\n${formatMemoryAssetText(asset)}`;
147
+ }
148
+ function formatAssetBody(asset, config) {
149
+ return formatMemoryAssetText(asset, { maxContentChars: config.injectionContentMaxChars });
150
+ }
107
151
  function formatResultBlock(item, index, context, config) {
108
152
  const label = formatMemoryResultLabel(item);
109
153
  const asset = context.assetsById[item.id];
110
154
  const header = `${index + 1}. ${label} score=${item.score.toFixed(3)}\n - 摘要: ${item.summary}${formatSourceHint(item, config)}`;
111
- if (asset?.content) {
155
+ if (asset) {
112
156
  return `${header}\n\n${formatAssetBody(asset, config)}\n`;
113
157
  }
114
158
  return `${header}\n - 全文加载失败,可手动: read_memory_asset {"asset_id": "${item.id}"}\n`;
@@ -124,7 +168,7 @@ export function renderMemoryGuideSection(context) {
124
168
  if (context.results.length === 0) {
125
169
  return `\n\n## 🧠 记忆系统\n- 状态: 已启用\n- 检索结果: 未找到高相关记录(含历史 Bug 修复与可复用模式)\n- 处理: 继续主流程;Bug 修复验证通过后必须 \`memorize_asset\` 沉淀;功能/UI 有可复用产出再沉淀\n`;
126
170
  }
127
- const loadedCount = context.results.filter((item) => context.assetsById[item.id]?.content).length;
171
+ const loadedCount = context.results.filter((item) => Boolean(context.assetsById[item.id])).length;
128
172
  const items = context.results
129
173
  .map((item, index) => formatResultBlock(item, index, context, config))
130
174
  .join('\n');
@@ -837,54 +837,6 @@ export declare const allToolSchemas: ({
837
837
  readonly required: readonly [];
838
838
  readonly additionalProperties: true;
839
839
  };
840
- } | {
841
- readonly name: "cursor_list_conversations";
842
- readonly description: "读取 Cursor 本地历史会话摘要。适合按标题、工作区列出最近会话,用于续接旧上下文。";
843
- readonly inputSchema: {
844
- readonly type: "object";
845
- readonly properties: {
846
- readonly title_query: {
847
- readonly type: "string";
848
- readonly description: "按会话标题过滤,支持部分匹配";
849
- };
850
- readonly workspace_query: {
851
- readonly type: "string";
852
- readonly description: "按工作区路径过滤,支持部分匹配";
853
- };
854
- readonly include_archived: {
855
- readonly type: "boolean";
856
- readonly description: "是否包含已归档会话,默认 false";
857
- };
858
- readonly limit: {
859
- readonly type: "number";
860
- readonly description: "最多返回多少条,默认 20,最大 200";
861
- };
862
- };
863
- readonly required: readonly [];
864
- readonly additionalProperties: true;
865
- };
866
- } | {
867
- readonly name: "cursor_search_conversations";
868
- readonly description: "在 Cursor 本地历史消息里按关键词、request id 搜索命中内容,可选限定某个会话。";
869
- readonly inputSchema: {
870
- readonly type: "object";
871
- readonly properties: {
872
- readonly query: {
873
- readonly type: "string";
874
- readonly description: "搜索关键词,可传标题片段、正文片段或 request id";
875
- };
876
- readonly composer_id: {
877
- readonly type: "string";
878
- readonly description: "可选,限定某个 Cursor 会话 ID";
879
- };
880
- readonly limit: {
881
- readonly type: "number";
882
- readonly description: "最多返回多少条,默认 20,最大 200";
883
- };
884
- };
885
- readonly required: readonly ["query"];
886
- readonly additionalProperties: true;
887
- };
888
840
  } | {
889
841
  readonly name: "cursor_read_conversation";
890
842
  readonly description: "按 composer_id 读取一条 Cursor 本地会话的消息时间线。";
@@ -147,54 +147,6 @@ export declare const memoryToolSchemas: readonly [{
147
147
  readonly required: readonly [];
148
148
  readonly additionalProperties: true;
149
149
  };
150
- }, {
151
- readonly name: "cursor_list_conversations";
152
- readonly description: "读取 Cursor 本地历史会话摘要。适合按标题、工作区列出最近会话,用于续接旧上下文。";
153
- readonly inputSchema: {
154
- readonly type: "object";
155
- readonly properties: {
156
- readonly title_query: {
157
- readonly type: "string";
158
- readonly description: "按会话标题过滤,支持部分匹配";
159
- };
160
- readonly workspace_query: {
161
- readonly type: "string";
162
- readonly description: "按工作区路径过滤,支持部分匹配";
163
- };
164
- readonly include_archived: {
165
- readonly type: "boolean";
166
- readonly description: "是否包含已归档会话,默认 false";
167
- };
168
- readonly limit: {
169
- readonly type: "number";
170
- readonly description: "最多返回多少条,默认 20,最大 200";
171
- };
172
- };
173
- readonly required: readonly [];
174
- readonly additionalProperties: true;
175
- };
176
- }, {
177
- readonly name: "cursor_search_conversations";
178
- readonly description: "在 Cursor 本地历史消息里按关键词、request id 搜索命中内容,可选限定某个会话。";
179
- readonly inputSchema: {
180
- readonly type: "object";
181
- readonly properties: {
182
- readonly query: {
183
- readonly type: "string";
184
- readonly description: "搜索关键词,可传标题片段、正文片段或 request id";
185
- };
186
- readonly composer_id: {
187
- readonly type: "string";
188
- readonly description: "可选,限定某个 Cursor 会话 ID";
189
- };
190
- readonly limit: {
191
- readonly type: "number";
192
- readonly description: "最多返回多少条,默认 20,最大 200";
193
- };
194
- };
195
- readonly required: readonly ["query"];
196
- readonly additionalProperties: true;
197
- };
198
150
  }, {
199
151
  readonly name: "cursor_read_conversation";
200
152
  readonly description: "按 composer_id 读取一条 Cursor 本地会话的消息时间线。";
@@ -75,35 +75,6 @@ export const memoryToolSchemas = [
75
75
  additionalProperties: true,
76
76
  },
77
77
  },
78
- {
79
- name: 'cursor_list_conversations',
80
- description: '读取 Cursor 本地历史会话摘要。适合按标题、工作区列出最近会话,用于续接旧上下文。',
81
- inputSchema: {
82
- type: 'object',
83
- properties: {
84
- title_query: { type: 'string', description: '按会话标题过滤,支持部分匹配' },
85
- workspace_query: { type: 'string', description: '按工作区路径过滤,支持部分匹配' },
86
- include_archived: { type: 'boolean', description: '是否包含已归档会话,默认 false' },
87
- limit: { type: 'number', description: '最多返回多少条,默认 20,最大 200' },
88
- },
89
- required: [],
90
- additionalProperties: true,
91
- },
92
- },
93
- {
94
- name: 'cursor_search_conversations',
95
- description: '在 Cursor 本地历史消息里按关键词、request id 搜索命中内容,可选限定某个会话。',
96
- inputSchema: {
97
- type: 'object',
98
- properties: {
99
- query: { type: 'string', description: '搜索关键词,可传标题片段、正文片段或 request id' },
100
- composer_id: { type: 'string', description: '可选,限定某个 Cursor 会话 ID' },
101
- limit: { type: 'number', description: '最多返回多少条,默认 20,最大 200' },
102
- },
103
- required: ['query'],
104
- additionalProperties: true,
105
- },
106
- },
107
78
  {
108
79
  name: 'cursor_read_conversation',
109
80
  description: '按 composer_id 读取一条 Cursor 本地会话的消息时间线。',
@@ -1,67 +1,18 @@
1
1
  import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
2
- const listConversationsMock = vi.fn();
3
- const searchHistoryMock = vi.fn();
4
2
  const readConversationMock = vi.fn();
5
3
  vi.mock('../../lib/cursor-history-client.js', () => ({
6
4
  createCursorHistoryClient: () => ({
7
- listConversations: listConversationsMock,
8
- searchHistory: searchHistoryMock,
9
5
  readConversation: readConversationMock,
10
6
  }),
11
7
  }));
12
- import { cursorListConversations } from '../cursor_list_conversations.js';
13
- import { cursorSearchConversations } from '../cursor_search_conversations.js';
14
8
  import { cursorReadConversation } from '../cursor_read_conversation.js';
15
9
  beforeEach(() => {
16
- listConversationsMock.mockReset();
17
- searchHistoryMock.mockReset();
18
10
  readConversationMock.mockReset();
19
11
  });
20
12
  afterEach(() => {
21
13
  vi.clearAllMocks();
22
14
  });
23
15
  describe('cursor history tools', () => {
24
- test('cursor_list_conversations 返回摘要列表', async () => {
25
- listConversationsMock.mockResolvedValue([
26
- { composerId: 'c1', name: '新需求', source: 'composerHeaders' },
27
- ]);
28
- const result = await cursorListConversations({ title_query: '新需求', limit: 10 });
29
- expect(result.isError).toBe(false);
30
- expect('structuredContent' in result).toBe(true);
31
- if (!('structuredContent' in result)) {
32
- throw new Error('structuredContent 缺失');
33
- }
34
- expect(result.content[0].text).toContain('已获取 1 条 Cursor 会话摘要');
35
- expect(result.structuredContent.count).toBe(1);
36
- expect(listConversationsMock).toHaveBeenCalledWith({
37
- titleQuery: '新需求',
38
- workspaceQuery: '',
39
- includeArchived: false,
40
- limit: 10,
41
- });
42
- });
43
- test('cursor_search_conversations 缺少 query 时返回错误', async () => {
44
- const result = await cursorSearchConversations({});
45
- expect(result.isError).toBe(true);
46
- expect(result.content[0].text).toContain('缺少必填参数: query');
47
- });
48
- test('cursor_search_conversations 返回命中结果', async () => {
49
- searchHistoryMock.mockResolvedValue([
50
- { composerId: 'c1', conversationName: '新需求', bubbleId: 'b1', type: 1, text: '我们先聊需求' },
51
- ]);
52
- const result = await cursorSearchConversations({ query: '需求', composer_id: 'c1', limit: 5 });
53
- expect(result.isError).toBe(false);
54
- expect('structuredContent' in result).toBe(true);
55
- if (!('structuredContent' in result)) {
56
- throw new Error('structuredContent 缺失');
57
- }
58
- expect(result.structuredContent.count).toBe(1);
59
- expect(searchHistoryMock).toHaveBeenCalledWith({
60
- query: '需求',
61
- composerId: 'c1',
62
- limit: 5,
63
- });
64
- });
65
16
  test('cursor_read_conversation 返回消息时间线', async () => {
66
17
  readConversationMock.mockResolvedValue({
67
18
  composerId: 'c1',
@@ -0,0 +1,75 @@
1
+ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
2
+ const getAssetMock = vi.fn();
3
+ const isReadEnabledMock = vi.fn();
4
+ vi.mock('../../lib/memory-client.js', () => ({
5
+ createMemoryClient: () => ({
6
+ isReadEnabled: isReadEnabledMock,
7
+ getAsset: getAssetMock,
8
+ }),
9
+ }));
10
+ import { readMemoryAsset } from '../read_memory_asset.js';
11
+ import { formatReadMemoryAssetText } from '../../lib/memory-orchestration.js';
12
+ beforeEach(() => {
13
+ isReadEnabledMock.mockReset();
14
+ getAssetMock.mockReset();
15
+ });
16
+ afterEach(() => {
17
+ vi.clearAllMocks();
18
+ });
19
+ describe('formatReadMemoryAssetText', () => {
20
+ test('includes full content body', () => {
21
+ const text = formatReadMemoryAssetText({
22
+ id: 'asset-1',
23
+ name: 'playwright-e2e-testing-speed-pattern',
24
+ type: 'pattern',
25
+ description: 'Speed up Playwright E2E suites',
26
+ summary: 'Playwright E2E parallelization pattern',
27
+ content: 'export const parallelWorkers = 4;\n// use sharding',
28
+ tags: ['pattern', 'e2e'],
29
+ confidence: 0.9,
30
+ createdAt: '2026-01-01T00:00:00.000Z',
31
+ updatedAt: '2026-01-01T00:00:00.000Z',
32
+ });
33
+ expect(text).toContain('已读取记忆资产: playwright-e2e-testing-speed-pattern');
34
+ expect(text).toContain('--- content ---');
35
+ expect(text).toContain('export const parallelWorkers = 4;');
36
+ expect(text).toContain('asset_id: asset-1');
37
+ });
38
+ });
39
+ describe('read_memory_asset 单元测试', () => {
40
+ test('记忆服务未开启时返回跳过结果', async () => {
41
+ isReadEnabledMock.mockReturnValue(false);
42
+ const result = await readMemoryAsset({ asset_id: 'asset-1' });
43
+ expect(result.isError).toBe(false);
44
+ expect('structuredContent' in result).toBe(true);
45
+ if (!('structuredContent' in result)) {
46
+ throw new Error('structuredContent 缺失');
47
+ }
48
+ expect(result.content[0].text).toContain('记忆服务未开启');
49
+ expect(getAssetMock).not.toHaveBeenCalled();
50
+ });
51
+ test('命中资产时文本输出包含完整 content', async () => {
52
+ isReadEnabledMock.mockReturnValue(true);
53
+ getAssetMock.mockResolvedValue({
54
+ id: 'asset-1',
55
+ name: 'feishu-proxy-bug',
56
+ type: 'bugfix',
57
+ description: 'Feishu proxy mismatch',
58
+ summary: 'proxy caused 400 on HTTPS',
59
+ content: '【现象】submit 成功\n【根因】HTTP_PROXY 污染\n【修复】proxy:false',
60
+ tags: ['bugfix'],
61
+ confidence: 0.9,
62
+ createdAt: '2026-01-01T00:00:00.000Z',
63
+ updatedAt: '2026-01-01T00:00:00.000Z',
64
+ });
65
+ const result = await readMemoryAsset({ asset_id: 'asset-1' });
66
+ expect(result.isError).toBe(false);
67
+ expect('structuredContent' in result).toBe(true);
68
+ if (!('structuredContent' in result)) {
69
+ throw new Error('structuredContent 缺失');
70
+ }
71
+ expect(result.content[0].text).toContain('【现象】submit 成功');
72
+ expect(result.content[0].text).toContain('【修复】proxy:false');
73
+ expect(result.structuredContent.asset.content).toContain('【根因】HTTP_PROXY 污染');
74
+ });
75
+ });
@@ -0,0 +1 @@
1
+ export {};