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.
- package/README.md +9 -11
- package/build/index.js +1 -5
- package/build/lib/__tests__/memory-injection.unit.test.js +1 -0
- package/build/lib/__tests__/memory-orchestration.unit.test.js +4 -0
- package/build/lib/memory-client.d.ts +2 -0
- package/build/lib/memory-client.js +1 -0
- package/build/lib/memory-config.d.ts +2 -0
- package/build/lib/memory-config.js +1 -0
- package/build/lib/memory-orchestration.d.ts +5 -0
- package/build/lib/memory-orchestration.js +48 -4
- package/build/schemas/index.d.ts +0 -48
- package/build/schemas/memory-tools.d.ts +0 -48
- package/build/schemas/memory-tools.js +0 -29
- package/build/tools/__tests__/cursor-history.unit.test.js +0 -49
- package/build/tools/__tests__/read_memory_asset.unit.test.d.ts +1 -0
- package/build/tools/__tests__/read_memory_asset.unit.test.js +75 -0
- package/build/tools/__tests__/search_memory.unit.test.d.ts +1 -0
- package/build/tools/__tests__/search_memory.unit.test.js +89 -0
- package/build/tools/index.d.ts +0 -2
- package/build/tools/index.js +0 -2
- package/build/tools/read_memory_asset.js +2 -1
- package/build/tools/search_memory.js +4 -2
- package/docs/data/tools.js +2 -34
- package/docs/i18n/all-tools/en.json +5 -15
- package/docs/i18n/all-tools/ja.json +3 -5
- package/docs/i18n/all-tools/ko.json +3 -5
- package/docs/i18n/all-tools/zh-CN.json +5 -15
- package/docs/i18n/en.json +10 -10
- package/docs/i18n/ja.json +9 -9
- package/docs/i18n/ko.json +9 -9
- package/docs/i18n/zh-CN.json +10 -10
- package/docs/memory-local-setup.md +1 -0
- package/docs/pages/all-tools.html +4 -4
- package/docs/pages/examples.html +2 -2
- package/docs/pages/getting-started.html +1 -1
- package/docs/pages/migration.html +2 -2
- package/package.json +2 -2
- package/build/tools/cursor_list_conversations.d.ts +0 -7
- package/build/tools/cursor_list_conversations.js +0 -35
- package/build/tools/cursor_search_conversations.d.ts +0 -7
- 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
|
|
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
|
+
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
|
|
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** (
|
|
68
|
-
- `search_memory`, `read_memory_asset`, `memorize_asset`, `scan_and_extract_patterns`, `
|
|
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
|
-
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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]
|
|
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');
|
package/build/schemas/index.d.ts
CHANGED
|
@@ -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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 {};
|