claude-flow 2.7.33 → 2.7.35
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/.claude/settings.local.json +9 -2
- package/.claude/skills/agentic-jujutsu/SKILL.md +1 -1
- package/CHANGELOG.md +140 -0
- package/bin/claude-flow +1 -1
- package/dist/src/cli/commands/mcp.js +61 -7
- package/dist/src/cli/commands/mcp.js.map +1 -1
- package/dist/src/cli/init/index.js +55 -33
- package/dist/src/cli/init/index.js.map +1 -1
- package/dist/src/cli/simple-cli.js +182 -172
- package/dist/src/cli/simple-cli.js.map +1 -1
- package/dist/src/cli/simple-commands/init/agent-copier.js +9 -3
- package/dist/src/cli/simple-commands/init/agent-copier.js.map +1 -1
- package/dist/src/core/DatabaseManager.js +39 -9
- package/dist/src/core/DatabaseManager.js.map +1 -1
- package/dist/src/mcp/async/job-manager-mcp25.js +240 -0
- package/dist/src/mcp/async/job-manager-mcp25.js.map +1 -0
- package/dist/src/mcp/index.js +8 -0
- package/dist/src/mcp/index.js.map +1 -1
- package/dist/src/mcp/protocol/version-negotiation.js +182 -0
- package/dist/src/mcp/protocol/version-negotiation.js.map +1 -0
- package/dist/src/mcp/registry/mcp-registry-client-2025.js +210 -0
- package/dist/src/mcp/registry/mcp-registry-client-2025.js.map +1 -0
- package/dist/src/mcp/server-factory.js +189 -0
- package/dist/src/mcp/server-factory.js.map +1 -0
- package/dist/src/mcp/server-mcp-2025.js +283 -0
- package/dist/src/mcp/server-mcp-2025.js.map +1 -0
- package/dist/src/mcp/tool-registry-progressive.js +319 -0
- package/dist/src/mcp/tool-registry-progressive.js.map +1 -0
- package/dist/src/mcp/tools/_template.js +62 -0
- package/dist/src/mcp/tools/_template.js.map +1 -0
- package/dist/src/mcp/tools/loader.js +228 -0
- package/dist/src/mcp/tools/loader.js.map +1 -0
- package/dist/src/mcp/tools/system/search.js +224 -0
- package/dist/src/mcp/tools/system/search.js.map +1 -0
- package/dist/src/mcp/tools/system/status.js +168 -0
- package/dist/src/mcp/tools/system/status.js.map +1 -0
- package/dist/src/mcp/validation/schema-validator-2025.js +198 -0
- package/dist/src/mcp/validation/schema-validator-2025.js.map +1 -0
- package/dist/src/utils/error-recovery.js +215 -0
- package/dist/src/utils/error-recovery.js.map +1 -0
- package/dist/src/utils/metrics-reader.js +10 -0
- package/dist/src/utils/metrics-reader.js.map +1 -1
- package/docs/.claude-flow/metrics/performance.json +3 -3
- package/docs/.claude-flow/metrics/task-metrics.json +3 -3
- package/docs/.github-release-issue-v2.7.33.md +488 -0
- package/docs/AGENTDB_BRANCH_MERGE_VERIFICATION.md +436 -0
- package/docs/AUTOMATIC_ERROR_RECOVERY_v2.7.35.md +321 -0
- package/docs/BRANCH_REVIEW_SUMMARY.md +439 -0
- package/docs/CONFIRMATION_AUTOMATIC_ERROR_RECOVERY.md +384 -0
- package/docs/DEEP_CODE_REVIEW_v2.7.33.md +1159 -0
- package/docs/DOCKER_TEST_RESULTS_v2.7.35.md +305 -0
- package/docs/MCP_2025_FEATURE_CONFIRMATION.md +698 -0
- package/docs/NPM_PUBLISH_GUIDE_v2.7.33.md +628 -0
- package/docs/REGRESSION_TEST_REPORT_v2.7.33.md +397 -0
- package/docs/RELEASE_NOTES_v2.7.33.md +618 -0
- package/docs/RELEASE_READINESS_SUMMARY.md +377 -0
- package/docs/RELEASE_SUMMARY_v2.7.33.md +456 -0
- package/docs/agentic-flow-agentdb-mcp-integration.md +1198 -0
- package/docs/features/automatic-error-recovery.md +333 -0
- package/docs/github-issues/README.md +88 -0
- package/docs/github-issues/wsl-enotempty-automatic-recovery.md +470 -0
- package/docs/mcp-2025-implementation-summary.md +459 -0
- package/docs/mcp-spec-2025-implementation-plan.md +1330 -0
- package/docs/phase-1-2-implementation-summary.md +676 -0
- package/docs/regression-analysis-phase-1-2.md +555 -0
- package/docs/troubleshooting/wsl-better-sqlite3-error.md +239 -0
- package/package.json +5 -2
- package/scripts/create-github-issue.sh +64 -0
- package/scripts/test-docker-wsl.sh +198 -0
- package/src/cli/commands/mcp.ts +86 -9
- package/src/cli/init/index.ts +72 -42
- package/src/cli/simple-commands/init/agent-copier.js +10 -5
- package/src/core/DatabaseManager.ts +55 -9
- package/src/mcp/async/job-manager-mcp25.ts +456 -0
- package/src/mcp/index.ts +60 -0
- package/src/mcp/protocol/version-negotiation.ts +329 -0
- package/src/mcp/registry/mcp-registry-client-2025.ts +334 -0
- package/src/mcp/server-factory.ts +426 -0
- package/src/mcp/server-mcp-2025.ts +507 -0
- package/src/mcp/tool-registry-progressive.ts +539 -0
- package/src/mcp/tools/_template.ts +174 -0
- package/src/mcp/tools/loader.ts +362 -0
- package/src/mcp/tools/system/search.ts +276 -0
- package/src/mcp/tools/system/status.ts +206 -0
- package/src/mcp/validation/schema-validator-2025.ts +294 -0
- package/src/utils/error-recovery.ts +325 -0
- package/docs/AGENTDB_V1.6.1_DEEP_REVIEW.md +0 -386
- package/docs/AGENT_FOLDER_STRUCTURE_FIX.md +0 -192
- package/docs/RECENT_RELEASES_SUMMARY.md +0 -375
- package/docs/V2.7.31_RELEASE_NOTES.md +0 -375
- /package/.claude/agents/analysis/{analyze-code-quality.md → code-review/analyze-code-quality.md} +0 -0
- /package/.claude/agents/architecture/{arch-system-design.md → system-design/arch-system-design.md} +0 -0
- /package/.claude/agents/data/{data-ml-model.md → ml/data-ml-model.md} +0 -0
- /package/.claude/agents/development/{dev-backend-api.md → backend/dev-backend-api.md} +0 -0
- /package/.claude/agents/devops/{ops-cicd-github.md → ci-cd/ops-cicd-github.md} +0 -0
- /package/.claude/agents/documentation/{docs-api-openapi.md → api-docs/docs-api-openapi.md} +0 -0
- /package/.claude/agents/specialized/{spec-mobile-react-native.md → mobile/spec-mobile-react-native.md} +0 -0
- /package/.claude/agents/testing/{tdd-london-swarm.md → unit/tdd-london-swarm.md} +0 -0
- /package/.claude/agents/testing/{production-validator.md → validation/production-validator.md} +0 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic Tool Loader for Progressive Disclosure
|
|
3
|
+
*
|
|
4
|
+
* Implements filesystem-based tool discovery pattern recommended by Anthropic:
|
|
5
|
+
* - Scans tool directories for metadata only (lightweight)
|
|
6
|
+
* - Loads full tool definitions on-demand (lazy loading)
|
|
7
|
+
* - Supports tiered detail levels for search
|
|
8
|
+
* - Achieves 98.7% token reduction (150k → 2k tokens)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { promises as fs } from 'fs';
|
|
12
|
+
import { join, dirname, extname, resolve } from 'path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
import type { MCPTool } from '../types.js';
|
|
15
|
+
import type { ILogger } from '../../interfaces/logger.js';
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = dirname(__filename);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Lightweight tool metadata for discovery
|
|
22
|
+
* Loaded without executing full tool definition
|
|
23
|
+
*/
|
|
24
|
+
export interface ToolMetadata {
|
|
25
|
+
name: string;
|
|
26
|
+
description: string;
|
|
27
|
+
category: string;
|
|
28
|
+
detailLevel: 'basic' | 'standard' | 'full';
|
|
29
|
+
filePath: string;
|
|
30
|
+
tags?: string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Tool search query interface
|
|
35
|
+
*/
|
|
36
|
+
export interface ToolSearchQuery {
|
|
37
|
+
category?: string;
|
|
38
|
+
tags?: string[];
|
|
39
|
+
detailLevel?: 'basic' | 'standard' | 'full';
|
|
40
|
+
namePattern?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Dynamic tool loader with progressive disclosure
|
|
45
|
+
*/
|
|
46
|
+
export class DynamicToolLoader {
|
|
47
|
+
private metadataCache: Map<string, ToolMetadata> = new Map();
|
|
48
|
+
private toolCache: Map<string, MCPTool> = new Map();
|
|
49
|
+
private scanComplete = false;
|
|
50
|
+
|
|
51
|
+
constructor(
|
|
52
|
+
private toolsDir: string = join(__dirname, '.'),
|
|
53
|
+
private logger: ILogger
|
|
54
|
+
) {}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Scan tool directory and build metadata index
|
|
58
|
+
* Only reads metadata exports, not full tool definitions
|
|
59
|
+
* This is the key to achieving 98.7% token reduction
|
|
60
|
+
*/
|
|
61
|
+
async scanTools(): Promise<Map<string, ToolMetadata>> {
|
|
62
|
+
if (this.scanComplete) {
|
|
63
|
+
return this.metadataCache;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.logger.info('Scanning tools directory for metadata', {
|
|
67
|
+
toolsDir: this.toolsDir,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const startTime = Date.now();
|
|
71
|
+
let scannedFiles = 0;
|
|
72
|
+
let loadedMetadata = 0;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
// Resolve tools directory to absolute path
|
|
76
|
+
const resolvedToolsDir = resolve(this.toolsDir);
|
|
77
|
+
|
|
78
|
+
// Get all subdirectories (categories)
|
|
79
|
+
const entries = await fs.readdir(resolvedToolsDir, { withFileTypes: true });
|
|
80
|
+
const categories = entries.filter(e => e.isDirectory() && !e.name.startsWith('_'));
|
|
81
|
+
|
|
82
|
+
// Scan each category
|
|
83
|
+
for (const categoryEntry of categories) {
|
|
84
|
+
const category = categoryEntry.name;
|
|
85
|
+
const categoryPath = resolve(resolvedToolsDir, category);
|
|
86
|
+
|
|
87
|
+
// Prevent path traversal - ensure category is within tools directory
|
|
88
|
+
if (!categoryPath.startsWith(resolvedToolsDir)) {
|
|
89
|
+
this.logger.warn('Skipping category outside tools directory', {
|
|
90
|
+
category,
|
|
91
|
+
categoryPath,
|
|
92
|
+
toolsDir: resolvedToolsDir,
|
|
93
|
+
});
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// Get tool files in category
|
|
99
|
+
const toolFiles = await fs.readdir(categoryPath);
|
|
100
|
+
const validToolFiles = toolFiles.filter(f => {
|
|
101
|
+
const ext = extname(f);
|
|
102
|
+
return (ext === '.ts' || ext === '.js') && !f.startsWith('_');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Load metadata from each file
|
|
106
|
+
for (const toolFile of validToolFiles) {
|
|
107
|
+
scannedFiles++;
|
|
108
|
+
const toolPath = resolve(categoryPath, toolFile);
|
|
109
|
+
|
|
110
|
+
// Prevent path traversal - ensure tool file is within category
|
|
111
|
+
if (!toolPath.startsWith(categoryPath)) {
|
|
112
|
+
this.logger.warn('Skipping tool file outside category directory', {
|
|
113
|
+
toolFile,
|
|
114
|
+
toolPath,
|
|
115
|
+
categoryPath,
|
|
116
|
+
});
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
// Dynamic import to load metadata only
|
|
122
|
+
const module = await import(toolPath);
|
|
123
|
+
|
|
124
|
+
if (module.toolMetadata) {
|
|
125
|
+
const metadata: ToolMetadata = {
|
|
126
|
+
...module.toolMetadata,
|
|
127
|
+
category, // Override with directory category
|
|
128
|
+
filePath: toolPath,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
this.metadataCache.set(metadata.name, metadata);
|
|
132
|
+
loadedMetadata++;
|
|
133
|
+
|
|
134
|
+
this.logger.debug('Loaded tool metadata', {
|
|
135
|
+
name: metadata.name,
|
|
136
|
+
category: metadata.category,
|
|
137
|
+
filePath: toolPath,
|
|
138
|
+
});
|
|
139
|
+
} else {
|
|
140
|
+
this.logger.warn('Tool file missing toolMetadata export', {
|
|
141
|
+
filePath: toolPath,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
this.logger.error('Failed to load tool metadata', {
|
|
146
|
+
filePath: toolPath,
|
|
147
|
+
error: error instanceof Error ? error.message : String(error),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
this.logger.error('Failed to scan category directory', {
|
|
153
|
+
category,
|
|
154
|
+
error: error instanceof Error ? error.message : String(error),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const scanTime = Date.now() - startTime;
|
|
160
|
+
this.scanComplete = true;
|
|
161
|
+
|
|
162
|
+
this.logger.info('Tool scan complete', {
|
|
163
|
+
scannedFiles,
|
|
164
|
+
loadedMetadata,
|
|
165
|
+
totalTools: this.metadataCache.size,
|
|
166
|
+
scanTimeMs: scanTime,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return this.metadataCache;
|
|
170
|
+
} catch (error) {
|
|
171
|
+
this.logger.error('Failed to scan tools directory', {
|
|
172
|
+
error: error instanceof Error ? error.message : String(error),
|
|
173
|
+
});
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Lazy load a specific tool by name
|
|
180
|
+
* Only loads when actually needed (on invocation)
|
|
181
|
+
*/
|
|
182
|
+
async loadTool(toolName: string, logger: ILogger): Promise<MCPTool | null> {
|
|
183
|
+
// Check cache first
|
|
184
|
+
if (this.toolCache.has(toolName)) {
|
|
185
|
+
this.logger.debug('Tool loaded from cache', { toolName });
|
|
186
|
+
return this.toolCache.get(toolName)!;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Get metadata
|
|
190
|
+
const metadata = this.metadataCache.get(toolName);
|
|
191
|
+
if (!metadata) {
|
|
192
|
+
this.logger.warn('Tool not found in metadata cache', { toolName });
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Load full tool definition
|
|
197
|
+
try {
|
|
198
|
+
this.logger.debug('Loading full tool definition', {
|
|
199
|
+
toolName,
|
|
200
|
+
filePath: metadata.filePath,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const module = await import(metadata.filePath);
|
|
204
|
+
|
|
205
|
+
// Find tool creator function (convention: createXxxTool)
|
|
206
|
+
const creatorFn = Object.values(module).find(
|
|
207
|
+
(exp: any) => typeof exp === 'function' && exp.name.startsWith('create')
|
|
208
|
+
) as ((logger: ILogger) => MCPTool) | undefined;
|
|
209
|
+
|
|
210
|
+
if (!creatorFn) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`No tool creator function found in ${metadata.filePath}. ` +
|
|
213
|
+
`Expected function name starting with 'create'.`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Create tool instance
|
|
218
|
+
const tool = creatorFn(logger);
|
|
219
|
+
|
|
220
|
+
// Validate tool name matches metadata
|
|
221
|
+
if (tool.name !== toolName) {
|
|
222
|
+
this.logger.warn('Tool name mismatch', {
|
|
223
|
+
expected: toolName,
|
|
224
|
+
actual: tool.name,
|
|
225
|
+
filePath: metadata.filePath,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Cache for future use
|
|
230
|
+
this.toolCache.set(toolName, tool);
|
|
231
|
+
|
|
232
|
+
this.logger.info('Tool loaded successfully', {
|
|
233
|
+
toolName,
|
|
234
|
+
category: metadata.category,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
return tool;
|
|
238
|
+
} catch (error) {
|
|
239
|
+
this.logger.error('Failed to load tool', {
|
|
240
|
+
toolName,
|
|
241
|
+
error: error instanceof Error ? error.message : String(error),
|
|
242
|
+
});
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Get tool metadata without loading full definition
|
|
249
|
+
* Used for tool discovery with minimal token usage
|
|
250
|
+
*/
|
|
251
|
+
getToolMetadata(toolName: string): ToolMetadata | undefined {
|
|
252
|
+
return this.metadataCache.get(toolName);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Search tools by query
|
|
257
|
+
* Returns only metadata for matching tools (lightweight)
|
|
258
|
+
*/
|
|
259
|
+
searchTools(query: ToolSearchQuery): ToolMetadata[] {
|
|
260
|
+
const results: ToolMetadata[] = [];
|
|
261
|
+
|
|
262
|
+
for (const metadata of this.metadataCache.values()) {
|
|
263
|
+
// Filter by category
|
|
264
|
+
if (query.category && metadata.category !== query.category) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Filter by detail level
|
|
269
|
+
if (query.detailLevel && metadata.detailLevel !== query.detailLevel) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Filter by tags
|
|
274
|
+
if (query.tags && query.tags.length > 0) {
|
|
275
|
+
const toolTags = metadata.tags || [];
|
|
276
|
+
const hasAllTags = query.tags.every(tag => toolTags.includes(tag));
|
|
277
|
+
if (!hasAllTags) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Filter by name pattern
|
|
283
|
+
if (query.namePattern) {
|
|
284
|
+
const pattern = query.namePattern.toLowerCase();
|
|
285
|
+
if (!metadata.name.toLowerCase().includes(pattern) &&
|
|
286
|
+
!metadata.description.toLowerCase().includes(pattern)) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
results.push(metadata);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Sort by name
|
|
295
|
+
results.sort((a, b) => a.name.localeCompare(b.name));
|
|
296
|
+
|
|
297
|
+
return results;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Get all tool names (minimal metadata)
|
|
302
|
+
* Used for quick tool listing
|
|
303
|
+
*/
|
|
304
|
+
getAllToolNames(): string[] {
|
|
305
|
+
return Array.from(this.metadataCache.keys()).sort();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get tools grouped by category
|
|
310
|
+
*/
|
|
311
|
+
getToolsByCategory(): Map<string, ToolMetadata[]> {
|
|
312
|
+
const byCategory = new Map<string, ToolMetadata[]>();
|
|
313
|
+
|
|
314
|
+
for (const metadata of this.metadataCache.values()) {
|
|
315
|
+
const category = metadata.category;
|
|
316
|
+
if (!byCategory.has(category)) {
|
|
317
|
+
byCategory.set(category, []);
|
|
318
|
+
}
|
|
319
|
+
byCategory.get(category)!.push(metadata);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return byCategory;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Get statistics about loaded tools
|
|
327
|
+
*/
|
|
328
|
+
getStats() {
|
|
329
|
+
const byCategory = this.getToolsByCategory();
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
totalTools: this.metadataCache.size,
|
|
333
|
+
cachedTools: this.toolCache.size,
|
|
334
|
+
categories: Array.from(byCategory.keys()).sort(),
|
|
335
|
+
toolsByCategory: Object.fromEntries(
|
|
336
|
+
Array.from(byCategory.entries()).map(([cat, tools]) => [cat, tools.length])
|
|
337
|
+
),
|
|
338
|
+
scanComplete: this.scanComplete,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Clear tool cache (useful for hot reloading during development)
|
|
344
|
+
*/
|
|
345
|
+
clearCache(): void {
|
|
346
|
+
this.toolCache.clear();
|
|
347
|
+
this.logger.info('Tool cache cleared', {
|
|
348
|
+
previouslyCached: this.toolCache.size,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Reload metadata (useful for hot reloading during development)
|
|
354
|
+
*/
|
|
355
|
+
async reload(): Promise<void> {
|
|
356
|
+
this.metadataCache.clear();
|
|
357
|
+
this.toolCache.clear();
|
|
358
|
+
this.scanComplete = false;
|
|
359
|
+
await this.scanTools();
|
|
360
|
+
this.logger.info('Tool loader reloaded');
|
|
361
|
+
}
|
|
362
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Search Capability (tools/search)
|
|
3
|
+
*
|
|
4
|
+
* Implements progressive disclosure pattern with tiered detail levels:
|
|
5
|
+
* - names-only: Just tool names (minimal tokens)
|
|
6
|
+
* - basic: Name + description + category
|
|
7
|
+
* - full: Complete schemas with examples
|
|
8
|
+
*
|
|
9
|
+
* This is the key to achieving 98.7% token reduction.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { MCPTool, ClaudeFlowToolContext } from '../../types.js';
|
|
13
|
+
import type { ILogger } from '../../../interfaces/logger.js';
|
|
14
|
+
import type { DynamicToolLoader, ToolMetadata } from '../loader.js';
|
|
15
|
+
|
|
16
|
+
interface SearchToolsInput {
|
|
17
|
+
query?: string;
|
|
18
|
+
category?: string;
|
|
19
|
+
tags?: string[];
|
|
20
|
+
detailLevel?: 'names-only' | 'basic' | 'full';
|
|
21
|
+
limit?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ToolSearchResult {
|
|
25
|
+
name: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
category?: string;
|
|
28
|
+
tags?: string[];
|
|
29
|
+
inputSchema?: any;
|
|
30
|
+
examples?: any[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface SearchToolsResult {
|
|
34
|
+
success: boolean;
|
|
35
|
+
tools: ToolSearchResult[];
|
|
36
|
+
totalMatches: number;
|
|
37
|
+
detailLevel: string;
|
|
38
|
+
tokenSavings?: {
|
|
39
|
+
estimatedFullSize: number;
|
|
40
|
+
actualSize: number;
|
|
41
|
+
reductionPercent: number;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Create tool search capability
|
|
47
|
+
*
|
|
48
|
+
* @param loader - Dynamic tool loader instance
|
|
49
|
+
* @param logger - Logger instance
|
|
50
|
+
* @returns MCPTool definition
|
|
51
|
+
*/
|
|
52
|
+
export function createSearchToolsTool(
|
|
53
|
+
loader: DynamicToolLoader,
|
|
54
|
+
logger: ILogger
|
|
55
|
+
): MCPTool {
|
|
56
|
+
return {
|
|
57
|
+
name: 'tools/search',
|
|
58
|
+
description: 'Search for tools with configurable detail levels. Use names-only for quick discovery (saves 98%+ tokens), basic for descriptions, full for complete schemas. This is the primary tool discovery mechanism.',
|
|
59
|
+
|
|
60
|
+
inputSchema: {
|
|
61
|
+
type: 'object',
|
|
62
|
+
properties: {
|
|
63
|
+
query: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
description: 'Search query (searches tool names and descriptions)',
|
|
66
|
+
},
|
|
67
|
+
category: {
|
|
68
|
+
type: 'string',
|
|
69
|
+
description: 'Filter by category',
|
|
70
|
+
enum: [
|
|
71
|
+
'agents',
|
|
72
|
+
'tasks',
|
|
73
|
+
'memory',
|
|
74
|
+
'system',
|
|
75
|
+
'config',
|
|
76
|
+
'workflow',
|
|
77
|
+
'terminal',
|
|
78
|
+
'query',
|
|
79
|
+
'swarm',
|
|
80
|
+
'data',
|
|
81
|
+
'jobs',
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
tags: {
|
|
85
|
+
type: 'array',
|
|
86
|
+
items: { type: 'string' },
|
|
87
|
+
description: 'Filter by tags (all tags must match)',
|
|
88
|
+
},
|
|
89
|
+
detailLevel: {
|
|
90
|
+
type: 'string',
|
|
91
|
+
enum: ['names-only', 'basic', 'full'],
|
|
92
|
+
description: 'Level of detail to return. names-only: just names (fastest, minimal tokens). basic: name + description + category (recommended for discovery). full: complete schemas with examples (use only when needed)',
|
|
93
|
+
default: 'basic',
|
|
94
|
+
},
|
|
95
|
+
limit: {
|
|
96
|
+
type: 'number',
|
|
97
|
+
description: 'Maximum number of results (default: 20)',
|
|
98
|
+
minimum: 1,
|
|
99
|
+
maximum: 100,
|
|
100
|
+
default: 20,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
required: [],
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
metadata: {
|
|
107
|
+
category: 'system',
|
|
108
|
+
tags: ['discovery', 'search', 'progressive-disclosure', 'tools'],
|
|
109
|
+
examples: [
|
|
110
|
+
{
|
|
111
|
+
description: 'Quick search for agent-related tools (minimal tokens)',
|
|
112
|
+
input: {
|
|
113
|
+
query: 'agent',
|
|
114
|
+
detailLevel: 'names-only',
|
|
115
|
+
limit: 10,
|
|
116
|
+
},
|
|
117
|
+
expectedOutput: {
|
|
118
|
+
tools: [
|
|
119
|
+
{ name: 'agents/spawn' },
|
|
120
|
+
{ name: 'agents/list' },
|
|
121
|
+
{ name: 'agents/terminate' },
|
|
122
|
+
],
|
|
123
|
+
totalMatches: 5,
|
|
124
|
+
detailLevel: 'names-only',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
description: 'Get basic info about system tools',
|
|
129
|
+
input: {
|
|
130
|
+
category: 'system',
|
|
131
|
+
detailLevel: 'basic',
|
|
132
|
+
},
|
|
133
|
+
expectedOutput: {
|
|
134
|
+
tools: [
|
|
135
|
+
{
|
|
136
|
+
name: 'system/status',
|
|
137
|
+
description: 'Get system health status',
|
|
138
|
+
category: 'system',
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
totalMatches: 3,
|
|
142
|
+
detailLevel: 'basic',
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
description: 'Get full schema for specific tool',
|
|
147
|
+
input: {
|
|
148
|
+
query: 'agents/spawn',
|
|
149
|
+
detailLevel: 'full',
|
|
150
|
+
limit: 1,
|
|
151
|
+
},
|
|
152
|
+
expectedOutput: {
|
|
153
|
+
tools: [
|
|
154
|
+
{
|
|
155
|
+
name: 'agents/spawn',
|
|
156
|
+
description: 'Spawn a new agent',
|
|
157
|
+
category: 'agents',
|
|
158
|
+
inputSchema: { type: 'object', properties: {} },
|
|
159
|
+
examples: [],
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
totalMatches: 1,
|
|
163
|
+
detailLevel: 'full',
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
detailLevel: 'standard',
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
handler: async (
|
|
171
|
+
input: any,
|
|
172
|
+
context?: ClaudeFlowToolContext
|
|
173
|
+
): Promise<SearchToolsResult> => {
|
|
174
|
+
const validatedInput = input as SearchToolsInput;
|
|
175
|
+
const detailLevel = validatedInput.detailLevel || 'basic';
|
|
176
|
+
const limit = validatedInput.limit || 20;
|
|
177
|
+
|
|
178
|
+
logger.info('tools/search invoked', {
|
|
179
|
+
query: validatedInput.query,
|
|
180
|
+
category: validatedInput.category,
|
|
181
|
+
detailLevel,
|
|
182
|
+
limit,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
// Search tool metadata (lightweight operation)
|
|
187
|
+
const metadata = loader.searchTools({
|
|
188
|
+
category: validatedInput.category,
|
|
189
|
+
tags: validatedInput.tags,
|
|
190
|
+
namePattern: validatedInput.query,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
logger.debug('Tool search results', {
|
|
194
|
+
totalMatches: metadata.length,
|
|
195
|
+
detailLevel,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Process results based on detail level
|
|
199
|
+
const results: ToolSearchResult[] = [];
|
|
200
|
+
const limitedMetadata = metadata.slice(0, limit);
|
|
201
|
+
|
|
202
|
+
for (const meta of limitedMetadata) {
|
|
203
|
+
if (detailLevel === 'names-only') {
|
|
204
|
+
// Minimal: Just name (saves most tokens)
|
|
205
|
+
results.push({ name: meta.name });
|
|
206
|
+
} else if (detailLevel === 'basic') {
|
|
207
|
+
// Basic: Name + description + category + tags
|
|
208
|
+
results.push({
|
|
209
|
+
name: meta.name,
|
|
210
|
+
description: meta.description,
|
|
211
|
+
category: meta.category,
|
|
212
|
+
tags: meta.tags,
|
|
213
|
+
});
|
|
214
|
+
} else if (detailLevel === 'full') {
|
|
215
|
+
// Full: Load complete tool definition including schema
|
|
216
|
+
const tool = await loader.loadTool(meta.name, logger);
|
|
217
|
+
if (tool) {
|
|
218
|
+
results.push({
|
|
219
|
+
name: tool.name,
|
|
220
|
+
description: tool.description,
|
|
221
|
+
category: meta.category,
|
|
222
|
+
tags: meta.tags,
|
|
223
|
+
inputSchema: tool.inputSchema,
|
|
224
|
+
examples: tool.metadata?.examples || [],
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Calculate token savings for demonstration
|
|
231
|
+
const actualSize = JSON.stringify(results).length;
|
|
232
|
+
const estimatedFullSize = limitedMetadata.length * 2000; // Estimate 2KB per full tool
|
|
233
|
+
const reductionPercent = detailLevel === 'full'
|
|
234
|
+
? 0
|
|
235
|
+
: ((estimatedFullSize - actualSize) / estimatedFullSize) * 100;
|
|
236
|
+
|
|
237
|
+
logger.info('tools/search completed successfully', {
|
|
238
|
+
resultsCount: results.length,
|
|
239
|
+
totalMatches: metadata.length,
|
|
240
|
+
detailLevel,
|
|
241
|
+
actualSizeBytes: actualSize,
|
|
242
|
+
reductionPercent: reductionPercent.toFixed(2),
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
success: true,
|
|
247
|
+
tools: results,
|
|
248
|
+
totalMatches: metadata.length,
|
|
249
|
+
detailLevel,
|
|
250
|
+
tokenSavings:
|
|
251
|
+
detailLevel !== 'full'
|
|
252
|
+
? {
|
|
253
|
+
estimatedFullSize,
|
|
254
|
+
actualSize,
|
|
255
|
+
reductionPercent: Math.round(reductionPercent * 100) / 100,
|
|
256
|
+
}
|
|
257
|
+
: undefined,
|
|
258
|
+
};
|
|
259
|
+
} catch (error) {
|
|
260
|
+
logger.error('tools/search failed', {
|
|
261
|
+
error,
|
|
262
|
+
input: validatedInput,
|
|
263
|
+
});
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export const toolMetadata = {
|
|
271
|
+
name: 'tools/search',
|
|
272
|
+
description: 'Search and discover tools with progressive disclosure',
|
|
273
|
+
category: 'system',
|
|
274
|
+
detailLevel: 'standard' as const,
|
|
275
|
+
tags: ['discovery', 'search', 'tools'],
|
|
276
|
+
};
|