mcp-probe-kit 3.3.0 → 3.6.0
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 +15 -7
- package/build/index.js +45 -79
- package/build/lib/__tests__/agents-md-template.unit.test.js +1 -0
- package/build/lib/__tests__/dev-workflow.unit.test.d.ts +1 -0
- package/build/lib/__tests__/dev-workflow.unit.test.js +22 -0
- package/build/lib/__tests__/handles.unit.test.d.ts +1 -0
- package/build/lib/__tests__/handles.unit.test.js +19 -0
- package/build/lib/__tests__/mcp-apps.unit.test.d.ts +1 -0
- package/build/lib/__tests__/mcp-apps.unit.test.js +52 -0
- package/build/lib/__tests__/mcp-tool-skill-registry.unit.test.d.ts +1 -0
- package/build/lib/__tests__/mcp-tool-skill-registry.unit.test.js +18 -0
- package/build/lib/__tests__/memory-client.unit.test.js +105 -0
- package/build/lib/__tests__/spec-gate.unit.test.d.ts +1 -0
- package/build/lib/__tests__/spec-gate.unit.test.js +54 -0
- package/build/lib/__tests__/task-defaults.unit.test.d.ts +1 -0
- package/build/lib/__tests__/task-defaults.unit.test.js +27 -0
- package/build/lib/__tests__/workflow-skill-installer.unit.test.d.ts +1 -0
- package/build/lib/__tests__/workflow-skill-installer.unit.test.js +110 -0
- package/build/lib/agents-md-template.js +10 -2
- package/build/lib/dev-workflow.d.ts +36 -0
- package/build/lib/dev-workflow.js +497 -0
- package/build/lib/handles.d.ts +31 -0
- package/build/lib/handles.js +36 -0
- package/build/lib/mcp-apps.d.ts +14 -0
- package/build/lib/mcp-apps.js +234 -0
- package/build/lib/mcp-tool-skill-registry.d.ts +46 -0
- package/build/lib/mcp-tool-skill-registry.js +256 -0
- package/build/lib/memory-client.d.ts +19 -0
- package/build/lib/memory-client.js +69 -0
- package/build/lib/memory-orchestration.d.ts +5 -0
- package/build/lib/memory-orchestration.js +20 -0
- package/build/lib/merge-agents-md.d.ts +2 -2
- package/build/lib/merge-agents-md.js +7 -4
- package/build/lib/output-schema-registry.d.ts +11 -0
- package/build/lib/output-schema-registry.js +80 -0
- package/build/lib/spec-gate.d.ts +32 -0
- package/build/lib/spec-gate.js +83 -0
- package/build/lib/task-defaults.d.ts +6 -0
- package/build/lib/task-defaults.js +20 -0
- package/build/lib/tool-annotations.js +3 -0
- package/build/lib/toolset-manager.js +2 -0
- package/build/lib/workflow-skill-installer.d.ts +38 -0
- package/build/lib/workflow-skill-installer.js +158 -0
- package/build/lib/workflow-skill-template.d.ts +9 -0
- package/build/lib/workflow-skill-template.js +85 -0
- package/build/lib/workflow-skill-version.d.ts +15 -0
- package/build/lib/workflow-skill-version.js +68 -0
- package/build/schemas/index.d.ts +110 -0
- package/build/schemas/index.js +1 -0
- package/build/schemas/memory-tools.d.ts +83 -0
- package/build/schemas/memory-tools.js +43 -0
- package/build/schemas/orchestration-tools.d.ts +8 -0
- package/build/schemas/orchestration-tools.js +8 -0
- package/build/schemas/output/code-insight-tools.d.ts +65 -0
- package/build/schemas/output/code-insight-tools.js +42 -0
- package/build/schemas/output/memory-tools.d.ts +576 -0
- package/build/schemas/output/memory-tools.js +148 -0
- package/build/schemas/project-tools.d.ts +19 -0
- package/build/schemas/project-tools.js +20 -0
- package/build/tools/__tests__/delete_memory_asset.unit.test.d.ts +1 -0
- package/build/tools/__tests__/delete_memory_asset.unit.test.js +87 -0
- package/build/tools/__tests__/search_memory.unit.test.js +1 -0
- package/build/tools/__tests__/start_bugfix.unit.test.js +34 -1
- package/build/tools/__tests__/update_memory_asset.unit.test.d.ts +1 -0
- package/build/tools/__tests__/update_memory_asset.unit.test.js +82 -0
- package/build/tools/__tests__/workflow.unit.test.d.ts +1 -0
- package/build/tools/__tests__/workflow.unit.test.js +24 -0
- package/build/tools/code_insight.js +5 -1
- package/build/tools/delete_memory_asset.d.ts +7 -0
- package/build/tools/delete_memory_asset.js +57 -0
- package/build/tools/index.d.ts +3 -0
- package/build/tools/index.js +3 -0
- package/build/tools/search_memory.js +5 -2
- package/build/tools/start_bugfix.js +37 -4
- package/build/tools/start_feature.js +4 -3
- package/build/tools/start_ui.js +5 -4
- package/build/tools/update_memory_asset.d.ts +7 -0
- package/build/tools/update_memory_asset.js +98 -0
- package/build/tools/workflow.d.ts +13 -0
- package/build/tools/workflow.js +69 -0
- package/package.json +5 -3
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 30 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 **30 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 **27 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 29 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
|
|
|
@@ -50,7 +50,7 @@ A powerful MCP (Model Context Protocol) server providing **27 tools** covering t
|
|
|
50
50
|
|
|
51
51
|
## ✨ Core Features
|
|
52
52
|
|
|
53
|
-
### 📦
|
|
53
|
+
### 📦 29 Tools
|
|
54
54
|
|
|
55
55
|
- **🔄 Workflow Orchestration** (6 tools) - One-click complex development workflows
|
|
56
56
|
- `start_feature`, `start_bugfix`, `start_onboard`, `start_ui`, `start_product`, `start_ralph`
|
|
@@ -64,8 +64,8 @@ A powerful MCP (Model Context Protocol) server providing **27 tools** covering t
|
|
|
64
64
|
- `init_project`, `init_project_context`, `add_feature`, `check_spec`, `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** (
|
|
68
|
-
- `search_memory`, `read_memory_asset`, `memorize_asset`, `scan_and_extract_patterns`
|
|
67
|
+
- **🧠 Memory** (6 tools) - Reusable asset memory
|
|
68
|
+
- `search_memory`, `read_memory_asset`, `memorize_asset`, `update_memory_asset`, `delete_memory_asset`, `scan_and_extract_patterns`
|
|
69
69
|
|
|
70
70
|
### 🛡️ Quality Constraints (single source of truth)
|
|
71
71
|
|
|
@@ -108,6 +108,8 @@ All hard quality rules live in one module (`src/lib/quality-constraints.ts`) and
|
|
|
108
108
|
- `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`)
|
|
109
109
|
- `memorize_asset` - Persist reusable code/spec/pattern assets into vector memory
|
|
110
110
|
- `read_memory_asset` - Read full asset content by `asset_id` (text output includes the full `content` body)
|
|
111
|
+
- `update_memory_asset` - Update an existing asset by `asset_id` (preserves ID; `content` changes re-embed)
|
|
112
|
+
- `delete_memory_asset` - Delete an asset by `asset_id` from the shared pool
|
|
111
113
|
- `scan_and_extract_patterns` - Extract reusable patterns from code/file/directory before deciding whether to persist
|
|
112
114
|
|
|
113
115
|
**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`.
|
|
@@ -478,7 +480,13 @@ Use in config file:
|
|
|
478
480
|
|
|
479
481
|
### Optional Memory System Setup
|
|
480
482
|
|
|
481
|
-
If you want to use `memorize_asset`, `read_memory_asset`, and `scan_and_extract_patterns`,
|
|
483
|
+
If you want to use `memorize_asset`, `update_memory_asset`, `read_memory_asset`, `delete_memory_asset`, and `scan_and_extract_patterns`, configure as follows:
|
|
484
|
+
|
|
485
|
+
- **Qdrant only** (`MEMORY_QDRANT_URL`): `read_memory_asset`, `delete_memory_asset`
|
|
486
|
+
- **Qdrant + embedding** (all three `MEMORY_*` write/search vars): `search_memory`, `memorize_asset`, `update_memory_asset`
|
|
487
|
+
- **No memory backend**: `scan_and_extract_patterns` (local scan only; persist via `memorize_asset` when ready)
|
|
488
|
+
|
|
489
|
+
For full write/search you need both:
|
|
482
490
|
|
|
483
491
|
1. A **Qdrant** vector database
|
|
484
492
|
2. An **embedding service** in either `ollama` or `openai-compatible` mode
|
package/build/index.js
CHANGED
|
@@ -5,11 +5,16 @@ 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, checkSpec, startFeature, startBugfix, startOnboard, startRalph, interview, askUser, uiDesignSystem, uiSearch, syncUiData, startUi, startProduct, gitWorkReport, searchMemory, readMemoryAsset, memorizeAsset, scanAndExtractPatterns } from "./tools/index.js";
|
|
8
|
+
import { initProject, gencommit, codeReview, codeInsight, gentest, refactor, initProjectContext, addFeature, fixBug, estimate, checkSpec, workflow, startFeature, startBugfix, startOnboard, startRalph, interview, askUser, uiDesignSystem, uiSearch, syncUiData, startUi, startProduct, gitWorkReport, searchMemory, readMemoryAsset, memorizeAsset, deleteMemoryAsset, updateMemoryAsset, scanAndExtractPatterns } 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";
|
|
12
12
|
import { withToolAnnotations } from "./lib/tool-annotations.js";
|
|
13
|
+
import { withOutputSchema } from "./lib/output-schema-registry.js";
|
|
14
|
+
import { shouldAutoEscalateToTask } from "./lib/task-defaults.js";
|
|
15
|
+
import { attachHandles } from "./lib/handles.js";
|
|
16
|
+
import { buildMcpAppHtml, isMcpUiAppTool } from "./lib/mcp-apps.js";
|
|
17
|
+
import { ensureMcpProbeKitBootstrapForToolCall } from "./lib/workflow-skill-installer.js";
|
|
13
18
|
import { isAbortError, } from "./lib/tool-execution-context.js";
|
|
14
19
|
const EXTENSIONS_CAPABILITY_KEY = "io.github.mybolide/extensions";
|
|
15
20
|
const MAX_UI_APP_RESOURCES = 30;
|
|
@@ -82,80 +87,6 @@ function withTraceMeta(result, traceMeta) {
|
|
|
82
87
|
},
|
|
83
88
|
};
|
|
84
89
|
}
|
|
85
|
-
function escapeHtml(value) {
|
|
86
|
-
return value
|
|
87
|
-
.replace(/&/g, "&")
|
|
88
|
-
.replace(/</g, "<")
|
|
89
|
-
.replace(/>/g, ">")
|
|
90
|
-
.replace(/"/g, """)
|
|
91
|
-
.replace(/'/g, "'");
|
|
92
|
-
}
|
|
93
|
-
function isUiTool(name) {
|
|
94
|
-
return [
|
|
95
|
-
"ui_design_system",
|
|
96
|
-
"ui_search",
|
|
97
|
-
"sync_ui_data",
|
|
98
|
-
"start_ui",
|
|
99
|
-
"start_product",
|
|
100
|
-
].includes(name);
|
|
101
|
-
}
|
|
102
|
-
function buildUiResourceHtml(name, args, result) {
|
|
103
|
-
const structured = result.structuredContent
|
|
104
|
-
? JSON.stringify(result.structuredContent, null, 2)
|
|
105
|
-
: "{}";
|
|
106
|
-
const argJson = JSON.stringify(args ?? {}, null, 2);
|
|
107
|
-
const textBlocks = Array.isArray(result.content)
|
|
108
|
-
? result.content
|
|
109
|
-
.map((item) => {
|
|
110
|
-
if (!item || typeof item !== "object") {
|
|
111
|
-
return "";
|
|
112
|
-
}
|
|
113
|
-
const text = item.text;
|
|
114
|
-
return typeof text === "string" ? text : "";
|
|
115
|
-
})
|
|
116
|
-
.filter(Boolean)
|
|
117
|
-
.join("\n\n")
|
|
118
|
-
: "";
|
|
119
|
-
const now = new Date().toISOString();
|
|
120
|
-
return `<!doctype html>
|
|
121
|
-
<html lang="zh-CN">
|
|
122
|
-
<head>
|
|
123
|
-
<meta charset="utf-8">
|
|
124
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
125
|
-
<title>${escapeHtml(name)} · MCP Apps</title>
|
|
126
|
-
<style>
|
|
127
|
-
:root { color-scheme: light; }
|
|
128
|
-
body { font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; margin: 0; background: #f4f7fb; color: #1e2a35; }
|
|
129
|
-
.wrap { max-width: 960px; margin: 0 auto; padding: 24px; }
|
|
130
|
-
.card { background: #fff; border-radius: 14px; padding: 18px; box-shadow: 0 4px 18px rgba(30,42,53,.08); margin-bottom: 16px; }
|
|
131
|
-
h1 { margin: 0 0 8px; font-size: 24px; }
|
|
132
|
-
h2 { margin: 0 0 10px; font-size: 16px; color: #2f4a65; }
|
|
133
|
-
pre { white-space: pre-wrap; word-break: break-word; background: #0f1720; color: #d9e7f7; border-radius: 10px; padding: 12px; font-size: 12px; line-height: 1.45; }
|
|
134
|
-
.meta { color: #4f6880; font-size: 12px; }
|
|
135
|
-
</style>
|
|
136
|
-
</head>
|
|
137
|
-
<body>
|
|
138
|
-
<div class="wrap">
|
|
139
|
-
<div class="card">
|
|
140
|
-
<h1>${escapeHtml(name)}</h1>
|
|
141
|
-
<div class="meta">Generated at ${escapeHtml(now)} · MCP Apps preview</div>
|
|
142
|
-
</div>
|
|
143
|
-
<div class="card">
|
|
144
|
-
<h2>Text Output</h2>
|
|
145
|
-
<pre>${escapeHtml(textBlocks || "(no text output)")}</pre>
|
|
146
|
-
</div>
|
|
147
|
-
<div class="card">
|
|
148
|
-
<h2>Structured Content</h2>
|
|
149
|
-
<pre>${escapeHtml(structured)}</pre>
|
|
150
|
-
</div>
|
|
151
|
-
<div class="card">
|
|
152
|
-
<h2>Arguments</h2>
|
|
153
|
-
<pre>${escapeHtml(argJson)}</pre>
|
|
154
|
-
</div>
|
|
155
|
-
</div>
|
|
156
|
-
</body>
|
|
157
|
-
</html>`;
|
|
158
|
-
}
|
|
159
90
|
function putUiAppResource(toolName, args, result) {
|
|
160
91
|
const uid = `${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
|
|
161
92
|
const uri = `ui://mcp-probe-kit/${toolName}/${uid}`;
|
|
@@ -164,7 +95,7 @@ function putUiAppResource(toolName, args, result) {
|
|
|
164
95
|
name: `UI Preview · ${toolName}`,
|
|
165
96
|
description: `MCP Apps preview generated by ${toolName}`,
|
|
166
97
|
mimeType: "text/html",
|
|
167
|
-
text:
|
|
98
|
+
text: buildMcpAppHtml(toolName, args, result),
|
|
168
99
|
createdAt: new Date().toISOString(),
|
|
169
100
|
};
|
|
170
101
|
uiAppResources.set(uri, entry);
|
|
@@ -384,13 +315,26 @@ function rememberGraphSnapshot(toolName, result) {
|
|
|
384
315
|
}
|
|
385
316
|
return snapshot;
|
|
386
317
|
}
|
|
318
|
+
function withStructuredHandles(result, handles) {
|
|
319
|
+
if (!result.structuredContent || typeof result.structuredContent !== "object" || Array.isArray(result.structuredContent)) {
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
...result,
|
|
324
|
+
structuredContent: attachHandles(result.structuredContent, handles),
|
|
325
|
+
};
|
|
326
|
+
}
|
|
387
327
|
function decorateResult(toolName, args, raw, traceMeta) {
|
|
388
328
|
let result = withTraceMeta(raw, traceMeta);
|
|
389
329
|
const snapshot = rememberGraphSnapshot(toolName, result);
|
|
390
330
|
if (snapshot) {
|
|
391
331
|
result = withGraphSnapshotMeta(result, snapshot);
|
|
332
|
+
result = withStructuredHandles(result, {
|
|
333
|
+
graph_snapshot: snapshot.uri,
|
|
334
|
+
graph_resource: snapshot.uri,
|
|
335
|
+
});
|
|
392
336
|
}
|
|
393
|
-
if (uiAppsEnabled &&
|
|
337
|
+
if (uiAppsEnabled && isMcpUiAppTool(toolName) && !result.isError) {
|
|
394
338
|
const resourceUri = putUiAppResource(toolName, args, result);
|
|
395
339
|
result = withUiResourceMeta(result, resourceUri);
|
|
396
340
|
}
|
|
@@ -411,10 +355,23 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
411
355
|
const filteredTools = filterTools(allToolSchemas, toolset);
|
|
412
356
|
console.error(`[MCP Probe Kit] 当前工具集: ${toolset} (${filteredTools.length}/${allToolSchemas.length} 个工具)`);
|
|
413
357
|
return {
|
|
414
|
-
tools: filteredTools.map(withToolAnnotations),
|
|
358
|
+
tools: filteredTools.map((tool) => withOutputSchema(withToolAnnotations(tool))),
|
|
415
359
|
};
|
|
416
360
|
});
|
|
417
361
|
async function executeTool(name, args, context) {
|
|
362
|
+
const bootstrap = ensureMcpProbeKitBootstrapForToolCall(name, args);
|
|
363
|
+
if (bootstrap?.skill.created) {
|
|
364
|
+
console.error(`[MCP Probe Kit] 已创建 MCP Skill: ${bootstrap.skill.skillRelPath} (v${bootstrap.skill.version})`);
|
|
365
|
+
}
|
|
366
|
+
else if (bootstrap?.skill.updated) {
|
|
367
|
+
console.error(`[MCP Probe Kit] 已升级 MCP Skill: ${bootstrap.skill.skillRelPath} ${bootstrap.skill.previousVersion ?? "?"} → v${bootstrap.skill.version}`);
|
|
368
|
+
}
|
|
369
|
+
if (bootstrap?.agentsMd.created) {
|
|
370
|
+
console.error(`[MCP Probe Kit] 已创建 AGENTS.md(含 Skill 引用): ${bootstrap.agentsMd.path}`);
|
|
371
|
+
}
|
|
372
|
+
else if (bootstrap?.agentsMd.updated) {
|
|
373
|
+
console.error(`[MCP Probe Kit] 已更新 AGENTS.md(添加 Skill 引用): ${bootstrap.agentsMd.path}`);
|
|
374
|
+
}
|
|
418
375
|
switch (name) {
|
|
419
376
|
case "init_project":
|
|
420
377
|
return await initProject(args);
|
|
@@ -430,6 +387,8 @@ async function executeTool(name, args, context) {
|
|
|
430
387
|
return await refactor(args);
|
|
431
388
|
case "init_project_context":
|
|
432
389
|
return await initProjectContext(args);
|
|
390
|
+
case "workflow":
|
|
391
|
+
return await workflow(args);
|
|
433
392
|
case "add_feature":
|
|
434
393
|
return await addFeature(args);
|
|
435
394
|
case "check_spec":
|
|
@@ -468,6 +427,10 @@ async function executeTool(name, args, context) {
|
|
|
468
427
|
return await readMemoryAsset(args);
|
|
469
428
|
case "memorize_asset":
|
|
470
429
|
return await memorizeAsset(args);
|
|
430
|
+
case "delete_memory_asset":
|
|
431
|
+
return await deleteMemoryAsset(args);
|
|
432
|
+
case "update_memory_asset":
|
|
433
|
+
return await updateMemoryAsset(args);
|
|
471
434
|
case "scan_and_extract_patterns":
|
|
472
435
|
return await scanAndExtractPatterns(args);
|
|
473
436
|
default:
|
|
@@ -491,8 +454,11 @@ function isTerminalTaskStatus(status) {
|
|
|
491
454
|
// 处理工具调用
|
|
492
455
|
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
493
456
|
const { name, arguments: args } = request.params;
|
|
494
|
-
|
|
457
|
+
let taskRequest = request.params.task;
|
|
495
458
|
const traceMeta = getTraceMeta(extra._meta);
|
|
459
|
+
if (shouldAutoEscalateToTask(name, Boolean(taskRequest))) {
|
|
460
|
+
taskRequest = taskRequest ?? {};
|
|
461
|
+
}
|
|
496
462
|
const emitProgress = async (progress, message) => {
|
|
497
463
|
const progressToken = extra._meta?.progressToken;
|
|
498
464
|
if (progressToken === undefined) {
|
|
@@ -20,6 +20,7 @@ describe('generateAgentsMdInner', () => {
|
|
|
20
20
|
expect(md).toContain('记忆');
|
|
21
21
|
expect(md).toContain('start_bugfix');
|
|
22
22
|
expect(md).toContain('memorize_asset');
|
|
23
|
+
expect(md).toContain('mcp-probe-kit/SKILL.md');
|
|
23
24
|
expect(md).toContain('bugfix');
|
|
24
25
|
expect(md).toContain('search_memory');
|
|
25
26
|
expect(md).toContain('自动注入');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { buildDevWorkflow, detectWorkflowScenario } from '../dev-workflow.js';
|
|
3
|
+
describe('dev-workflow', () => {
|
|
4
|
+
test('识别 bugfix 场景', () => {
|
|
5
|
+
const result = detectWorkflowScenario('登录报错 NullReference 需要排查修复');
|
|
6
|
+
expect(result.scenario).toBe('bugfix');
|
|
7
|
+
});
|
|
8
|
+
test('识别 feature 场景', () => {
|
|
9
|
+
const result = detectWorkflowScenario('开发用户认证新功能');
|
|
10
|
+
expect(result.scenario).toBe('feature');
|
|
11
|
+
});
|
|
12
|
+
test('bugfix 计划首工具为 start_bugfix', () => {
|
|
13
|
+
const plan = buildDevWorkflow('TypeError in checkout');
|
|
14
|
+
expect(plan.firstTool).toBe('start_bugfix');
|
|
15
|
+
expect(plan.phases.some((p) => p.steps.some((s) => s.tool === 'start_bugfix'))).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
test('显式 scenario 覆盖推断', () => {
|
|
18
|
+
const plan = buildDevWorkflow('随便', { scenario: 'ui' });
|
|
19
|
+
expect(plan.scenario).toBe('ui');
|
|
20
|
+
expect(plan.firstTool).toBe('start_ui');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { attachHandles, buildMemoryAssetHandles, mergeHandles, } from '../../lib/handles.js';
|
|
3
|
+
describe('handles', () => {
|
|
4
|
+
test('buildMemoryAssetHandles 生成 read 默认工具名', () => {
|
|
5
|
+
const handles = buildMemoryAssetHandles([{ id: 'a-1', name: 'demo' }]);
|
|
6
|
+
expect(handles).toEqual([{ id: 'a-1', name: 'demo', tool: 'read_memory_asset' }]);
|
|
7
|
+
});
|
|
8
|
+
test('attachHandles 合并已有 handles', () => {
|
|
9
|
+
const result = attachHandles({ handles: { graph_resource: 'probe://graph/latest' } }, { memory_assets: buildMemoryAssetHandles([{ id: 'x' }]) });
|
|
10
|
+
expect(result.handles.graph_resource).toBe('probe://graph/latest');
|
|
11
|
+
expect(result.handles.memory_assets).toHaveLength(1);
|
|
12
|
+
});
|
|
13
|
+
test('mergeHandles 后者覆盖 graph 字段', () => {
|
|
14
|
+
expect(mergeHandles({ graph_resource: 'a' }, { graph_resource: 'b', graph_snapshot: 'probe://graph/s1' })).toEqual({
|
|
15
|
+
graph_resource: 'b',
|
|
16
|
+
graph_snapshot: 'probe://graph/s1',
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { buildCodeInsightAppHtml, buildSearchMemoryAppHtml, isMcpUiAppTool, } from '../mcp-apps.js';
|
|
3
|
+
describe('mcp-apps', () => {
|
|
4
|
+
test('search_memory 与 code_insight 属于 UI App 工具', () => {
|
|
5
|
+
expect(isMcpUiAppTool('search_memory')).toBe(true);
|
|
6
|
+
expect(isMcpUiAppTool('code_insight')).toBe(true);
|
|
7
|
+
expect(isMcpUiAppTool('gencommit')).toBe(false);
|
|
8
|
+
});
|
|
9
|
+
test('search_memory App 渲染命中卡片与 handles', () => {
|
|
10
|
+
const html = buildSearchMemoryAppHtml({ query: 'proxy bug' }, {
|
|
11
|
+
structuredContent: {
|
|
12
|
+
query: 'proxy bug',
|
|
13
|
+
count: 1,
|
|
14
|
+
results: [
|
|
15
|
+
{
|
|
16
|
+
id: 'asset-1',
|
|
17
|
+
name: 'feishu-proxy',
|
|
18
|
+
type: 'bugfix',
|
|
19
|
+
score: 0.88,
|
|
20
|
+
summary: 'proxy mismatch',
|
|
21
|
+
content: '【修复】done',
|
|
22
|
+
tags: ['bugfix'],
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
handles: {
|
|
26
|
+
memory_assets: [{ id: 'asset-1', tool: 'read_memory_asset' }],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
expect(html).toContain('search_memory');
|
|
31
|
+
expect(html).toContain('feishu-proxy');
|
|
32
|
+
expect(html).toContain('asset-1');
|
|
33
|
+
expect(html).toContain('read_memory_asset');
|
|
34
|
+
expect(html).toContain('proxy mismatch');
|
|
35
|
+
});
|
|
36
|
+
test('code_insight App 渲染状态与执行表', () => {
|
|
37
|
+
const html = buildCodeInsightAppHtml({ mode: 'auto' }, {
|
|
38
|
+
structuredContent: {
|
|
39
|
+
status: 'ok',
|
|
40
|
+
provider: 'gitnexus',
|
|
41
|
+
summary: 'login flow analyzed',
|
|
42
|
+
mode: { requested: 'auto', resolved: 'query' },
|
|
43
|
+
executions: [{ tool: 'query', ok: true, text: 'found login handler' }],
|
|
44
|
+
handles: { graph_resource: 'probe://graph/latest' },
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
expect(html).toContain('code_insight');
|
|
48
|
+
expect(html).toContain('login flow analyzed');
|
|
49
|
+
expect(html).toContain('probe://graph/latest');
|
|
50
|
+
expect(html).toContain('found login handler');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { allToolSchemas } from "../../schemas/index.js";
|
|
3
|
+
import { formatSkillRegistryMismatchMessage, listMcpToolSkillRegistryNames, validateMcpToolSkillRegistry, } from "../mcp-tool-skill-registry.js";
|
|
4
|
+
import { generateWorkflowSkillContent } from "../workflow-skill-template.js";
|
|
5
|
+
describe("mcp-tool-skill-registry", () => {
|
|
6
|
+
it("与 allToolSchemas 工具名一一对应", () => {
|
|
7
|
+
const registered = allToolSchemas.map((tool) => tool.name).sort();
|
|
8
|
+
const result = validateMcpToolSkillRegistry(registered);
|
|
9
|
+
expect(result.ok, formatSkillRegistryMismatchMessage(result)).toBe(true);
|
|
10
|
+
expect(listMcpToolSkillRegistryNames().length).toBe(registered.length);
|
|
11
|
+
});
|
|
12
|
+
it("生成的 Skill 正文包含每个注册工具", () => {
|
|
13
|
+
const content = generateWorkflowSkillContent("0.0.0-test");
|
|
14
|
+
for (const name of listMcpToolSkillRegistryNames()) {
|
|
15
|
+
expect(content).toContain(`\`${name}\``);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -80,4 +80,109 @@ describe('memory-client 去重逻辑', () => {
|
|
|
80
80
|
expect(result.normalizedContentHash).toBe(existingAsset.normalizedContentHash);
|
|
81
81
|
expect(fetchMock.mock.calls.some(([url]) => String(url).includes('/points?wait=true'))).toBe(false);
|
|
82
82
|
});
|
|
83
|
+
test('deleteAsset 删除已存在资产并返回快照', async () => {
|
|
84
|
+
vi.stubEnv('MEMORY_QDRANT_URL', 'http://127.0.0.1:50008');
|
|
85
|
+
const asset = {
|
|
86
|
+
id: 'asset-to-delete',
|
|
87
|
+
name: 'ObsoletePattern',
|
|
88
|
+
type: 'pattern',
|
|
89
|
+
description: '过时模式',
|
|
90
|
+
summary: '不再使用',
|
|
91
|
+
content: 'old snippet',
|
|
92
|
+
tags: ['pattern'],
|
|
93
|
+
confidence: 0.7,
|
|
94
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
95
|
+
updatedAt: '2026-01-01T00:00:00.000Z',
|
|
96
|
+
};
|
|
97
|
+
const fetchMock = vi.fn(async (input, init) => {
|
|
98
|
+
const url = String(input);
|
|
99
|
+
if (url.includes(`/points/${asset.id}`) && init?.method !== 'POST' && init?.method !== 'PUT') {
|
|
100
|
+
return new Response(JSON.stringify({ result: { payload: asset } }), {
|
|
101
|
+
status: 200,
|
|
102
|
+
headers: { 'Content-Type': 'application/json' },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (url.includes('/points/delete')) {
|
|
106
|
+
return new Response(JSON.stringify({ status: 'ok' }), {
|
|
107
|
+
status: 200,
|
|
108
|
+
headers: { 'Content-Type': 'application/json' },
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
throw new Error(`unexpected fetch: ${url}`);
|
|
112
|
+
});
|
|
113
|
+
globalThis.fetch = fetchMock;
|
|
114
|
+
const client = new MemoryClient();
|
|
115
|
+
const result = await client.deleteAsset(asset.id);
|
|
116
|
+
expect(result.deleted).toBe(true);
|
|
117
|
+
expect(result.asset?.name).toBe('ObsoletePattern');
|
|
118
|
+
expect(fetchMock.mock.calls.some(([url, init]) => String(url).includes('/points/delete') && init?.method === 'POST')).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
test('deleteAsset 对不存在的资产返回 deleted=false', async () => {
|
|
121
|
+
vi.stubEnv('MEMORY_QDRANT_URL', 'http://127.0.0.1:50008');
|
|
122
|
+
const fetchMock = vi.fn(async (input) => {
|
|
123
|
+
const url = String(input);
|
|
124
|
+
if (url.includes('/points/missing-id')) {
|
|
125
|
+
return new Response(JSON.stringify({ result: null }), {
|
|
126
|
+
status: 200,
|
|
127
|
+
headers: { 'Content-Type': 'application/json' },
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
throw new Error(`unexpected fetch: ${url}`);
|
|
131
|
+
});
|
|
132
|
+
globalThis.fetch = fetchMock;
|
|
133
|
+
const client = new MemoryClient();
|
|
134
|
+
const result = await client.deleteAsset('missing-id');
|
|
135
|
+
expect(result.deleted).toBe(false);
|
|
136
|
+
expect(result.asset).toBeNull();
|
|
137
|
+
});
|
|
138
|
+
test('updateAsset 更新已存在资产并保留 createdAt', async () => {
|
|
139
|
+
vi.stubEnv('MEMORY_QDRANT_URL', 'http://127.0.0.1:50008');
|
|
140
|
+
vi.stubEnv('MEMORY_EMBEDDING_URL', 'http://127.0.0.1:11434/api/embeddings');
|
|
141
|
+
vi.stubEnv('MEMORY_EMBEDDING_MODEL', 'nomic-embed-text');
|
|
142
|
+
const asset = {
|
|
143
|
+
id: 'asset-to-update',
|
|
144
|
+
name: 'PatternA',
|
|
145
|
+
type: 'pattern',
|
|
146
|
+
description: '旧描述',
|
|
147
|
+
summary: '旧摘要',
|
|
148
|
+
content: 'old snippet',
|
|
149
|
+
tags: ['pattern'],
|
|
150
|
+
confidence: 0.7,
|
|
151
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
152
|
+
updatedAt: '2026-01-01T00:00:00.000Z',
|
|
153
|
+
};
|
|
154
|
+
const fetchMock = vi.fn(async (input, init) => {
|
|
155
|
+
const url = String(input);
|
|
156
|
+
if (url.endsWith('/api/embeddings')) {
|
|
157
|
+
return new Response(JSON.stringify({ embedding: [0.1, 0.2, 0.3] }), {
|
|
158
|
+
status: 200,
|
|
159
|
+
headers: { 'Content-Type': 'application/json' },
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (url.includes(`/points/${asset.id}`) && init?.method !== 'POST' && init?.method !== 'PUT') {
|
|
163
|
+
return new Response(JSON.stringify({ result: { payload: asset } }), {
|
|
164
|
+
status: 200,
|
|
165
|
+
headers: { 'Content-Type': 'application/json' },
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
if (url.endsWith('/collections/mcp_probe_memory') && !init?.method) {
|
|
169
|
+
return new Response('', { status: 200 });
|
|
170
|
+
}
|
|
171
|
+
if (url.includes('/points?wait=true') && init?.method === 'PUT') {
|
|
172
|
+
return new Response(JSON.stringify({ status: 'ok' }), {
|
|
173
|
+
status: 200,
|
|
174
|
+
headers: { 'Content-Type': 'application/json' },
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
throw new Error(`unexpected fetch: ${url}`);
|
|
178
|
+
});
|
|
179
|
+
globalThis.fetch = fetchMock;
|
|
180
|
+
const client = new MemoryClient();
|
|
181
|
+
const result = await client.updateAsset(asset.id, { summary: '新摘要' });
|
|
182
|
+
expect(result.updated).toBe(true);
|
|
183
|
+
expect(result.asset?.summary).toBe('新摘要');
|
|
184
|
+
expect(result.asset?.createdAt).toBe(asset.createdAt);
|
|
185
|
+
expect(result.asset?.id).toBe(asset.id);
|
|
186
|
+
expect(fetchMock.mock.calls.some(([url, init]) => String(url).includes('/points?wait=true') && init?.method === 'PUT')).toBe(true);
|
|
187
|
+
});
|
|
83
188
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, test } from 'vitest';
|
|
5
|
+
import { resolveBugfixFeatureName, resolveBugfixSpecGate, specArtifactsExist, } from '../spec-gate.js';
|
|
6
|
+
const tempDirs = [];
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
while (tempDirs.length > 0) {
|
|
9
|
+
const dir = tempDirs.pop();
|
|
10
|
+
if (dir) {
|
|
11
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
function makeSpecTree(featureName) {
|
|
16
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'spec-gate-'));
|
|
17
|
+
tempDirs.push(root);
|
|
18
|
+
const specDir = path.join(root, 'docs', 'specs', featureName);
|
|
19
|
+
fs.mkdirSync(specDir, { recursive: true });
|
|
20
|
+
fs.writeFileSync(path.join(specDir, 'requirements.md'), '# requirements\n');
|
|
21
|
+
return root;
|
|
22
|
+
}
|
|
23
|
+
describe('spec-gate', () => {
|
|
24
|
+
test('显式 feature_name 优先', () => {
|
|
25
|
+
const root = makeSpecTree('user-auth');
|
|
26
|
+
expect(resolveBugfixFeatureName('payment', root, 'docs', 'user-auth login failed')).toBe('payment');
|
|
27
|
+
});
|
|
28
|
+
test('仅一个 spec 目录时自动识别', () => {
|
|
29
|
+
const root = makeSpecTree('only-feature');
|
|
30
|
+
expect(resolveBugfixFeatureName('', root, 'docs', 'random error')).toBe('only-feature');
|
|
31
|
+
});
|
|
32
|
+
test('错误描述唯一匹配 spec 目录', () => {
|
|
33
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'spec-gate-'));
|
|
34
|
+
tempDirs.push(root);
|
|
35
|
+
for (const name of ['user-auth', 'billing']) {
|
|
36
|
+
const specDir = path.join(root, 'docs', 'specs', name);
|
|
37
|
+
fs.mkdirSync(specDir, { recursive: true });
|
|
38
|
+
fs.writeFileSync(path.join(specDir, 'tasks.md'), '# tasks\n');
|
|
39
|
+
}
|
|
40
|
+
expect(resolveBugfixFeatureName('', root, 'docs', 'billing invoice total mismatch')).toBe('billing');
|
|
41
|
+
});
|
|
42
|
+
test('resolveBugfixSpecGate 在规格存在时返回上下文', () => {
|
|
43
|
+
const root = makeSpecTree('checkout-flow');
|
|
44
|
+
const gate = resolveBugfixSpecGate({
|
|
45
|
+
featureName: 'checkout-flow',
|
|
46
|
+
projectRoot: root,
|
|
47
|
+
docsDir: 'docs',
|
|
48
|
+
hintText: 'checkout failed',
|
|
49
|
+
});
|
|
50
|
+
expect(gate?.featureName).toBe('checkout-flow');
|
|
51
|
+
expect(gate?.specDir).toBe('docs/specs/checkout-flow');
|
|
52
|
+
expect(specArtifactsExist(root, 'docs', 'checkout-flow')).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, test, afterEach } from 'vitest';
|
|
2
|
+
import { isAutoTaskTool, shouldAutoEscalateToTask } from '../task-defaults.js';
|
|
3
|
+
const originalDisable = process.env.MCP_DISABLE_AUTO_TASK;
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
if (originalDisable === undefined) {
|
|
6
|
+
delete process.env.MCP_DISABLE_AUTO_TASK;
|
|
7
|
+
}
|
|
8
|
+
else {
|
|
9
|
+
process.env.MCP_DISABLE_AUTO_TASK = originalDisable;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
describe('task-defaults', () => {
|
|
13
|
+
test('code_insight 与 scan 属于自动 Task 工具', () => {
|
|
14
|
+
expect(isAutoTaskTool('code_insight')).toBe(true);
|
|
15
|
+
expect(isAutoTaskTool('scan_and_extract_patterns')).toBe(true);
|
|
16
|
+
expect(isAutoTaskTool('search_memory')).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
test('默认对长耗时工具自动升级为 Task', () => {
|
|
19
|
+
delete process.env.MCP_DISABLE_AUTO_TASK;
|
|
20
|
+
expect(shouldAutoEscalateToTask('code_insight', false)).toBe(true);
|
|
21
|
+
expect(shouldAutoEscalateToTask('code_insight', true)).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
test('MCP_DISABLE_AUTO_TASK=1 时关闭自动 Task', () => {
|
|
24
|
+
process.env.MCP_DISABLE_AUTO_TASK = '1';
|
|
25
|
+
expect(shouldAutoEscalateToTask('code_insight', false)).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|