@j0hanz/filesystem-mcp 1.9.0 → 1.10.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 -15
- package/dist/completions.js +140 -115
- package/dist/lib/constants.d.ts +1 -0
- package/dist/lib/constants.js +2 -0
- package/dist/lib/file-operations/metadata.d.ts +5 -1
- package/dist/lib/file-operations/metadata.js +14 -1
- package/dist/lib/file-operations/search.d.ts +7 -5
- package/dist/lib/file-operations/search.js +64 -32
- package/dist/lib/fs-helpers.d.ts +3 -1
- package/dist/lib/fs-helpers.js +63 -0
- package/dist/lib/paths.d.ts +8 -0
- package/dist/lib/paths.js +119 -64
- package/dist/lib/resource-store.d.ts +2 -0
- package/dist/lib/resource-store.js +58 -17
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.js +51 -0
- package/dist/resources/generated-instructions.js +36 -9
- package/dist/resources/tool-catalog.js +30 -7
- package/dist/resources/tool-info.d.ts +4 -0
- package/dist/resources/tool-info.js +21 -3
- package/dist/resources/workflows.js +17 -5
- package/dist/schemas.d.ts +47 -12
- package/dist/schemas.js +75 -18
- package/dist/server/bootstrap.js +103 -91
- package/dist/server/roots-manager.d.ts +3 -0
- package/dist/server/roots-manager.js +15 -3
- package/dist/tools/apply-patch.js +135 -31
- package/dist/tools/calculate-hash.js +13 -8
- package/dist/tools/create-directory.js +14 -3
- package/dist/tools/delete-file.js +1 -0
- package/dist/tools/diff-files.js +26 -8
- package/dist/tools/edit-file.js +11 -8
- package/dist/tools/list-directory.js +1 -6
- package/dist/tools/move-file.js +39 -7
- package/dist/tools/read-multiple.js +9 -1
- package/dist/tools/read.js +38 -6
- package/dist/tools/replace-in-files.js +72 -25
- package/dist/tools/roots.js +1 -0
- package/dist/tools/search-content.js +76 -48
- package/dist/tools/search-files.js +6 -7
- package/dist/tools/shared.d.ts +2 -1
- package/dist/tools/shared.js +36 -20
- package/dist/tools/stat-many.js +1 -1
- package/dist/tools/stat.js +4 -0
- package/dist/tools/task-support.js +4 -12
- package/dist/tools/tree.js +4 -0
- package/dist/tools/write-file.js +4 -2
- package/package.json +17 -8
|
@@ -6,6 +6,7 @@ export interface TextResourceEntry {
|
|
|
6
6
|
hash: string;
|
|
7
7
|
size: number;
|
|
8
8
|
storedAt: string;
|
|
9
|
+
expiresAt: string;
|
|
9
10
|
}
|
|
10
11
|
export interface ResourceStore {
|
|
11
12
|
putText(params: {
|
|
@@ -21,6 +22,7 @@ interface ResourceStoreOptions {
|
|
|
21
22
|
maxEntries: number;
|
|
22
23
|
maxTotalBytes: number;
|
|
23
24
|
maxEntryBytes: number;
|
|
25
|
+
entryTtlMs: number;
|
|
24
26
|
}
|
|
25
27
|
export declare function createInMemoryResourceStore(options?: Partial<ResourceStoreOptions>): ResourceStore;
|
|
26
28
|
export {};
|
|
@@ -5,6 +5,7 @@ const DEFAULT_RESOURCE_STORE_OPTIONS = {
|
|
|
5
5
|
maxEntries: 64,
|
|
6
6
|
maxTotalBytes: 25 * 1024 * 1024,
|
|
7
7
|
maxEntryBytes: 10 * 1024 * 1024,
|
|
8
|
+
entryTtlMs: 30 * 60 * 1000, // 30 minutes
|
|
8
9
|
};
|
|
9
10
|
const RESOURCE_STORE_DIAGNOSTICS_CHANNEL = channel('filesystem-mcp:resource-store');
|
|
10
11
|
function publishResourceStoreDiagnostics(event) {
|
|
@@ -18,7 +19,15 @@ function estimateBytes(text) {
|
|
|
18
19
|
function computeSha256(text) {
|
|
19
20
|
return hash('sha256', text, 'hex');
|
|
20
21
|
}
|
|
22
|
+
function buildIndexKey(mimeType, contentHash) {
|
|
23
|
+
return `${mimeType}:${contentHash}`;
|
|
24
|
+
}
|
|
25
|
+
function isExpired(entry, now = Date.now()) {
|
|
26
|
+
const expiresAt = Date.parse(entry.expiresAt);
|
|
27
|
+
return Number.isFinite(expiresAt) && expiresAt <= now;
|
|
28
|
+
}
|
|
21
29
|
function createTextEntry(params) {
|
|
30
|
+
const storedAt = new Date();
|
|
22
31
|
return {
|
|
23
32
|
uri: params.uri,
|
|
24
33
|
name: params.name,
|
|
@@ -26,7 +35,8 @@ function createTextEntry(params) {
|
|
|
26
35
|
text: params.text,
|
|
27
36
|
hash: computeSha256(params.text),
|
|
28
37
|
size: estimateBytes(params.text),
|
|
29
|
-
storedAt:
|
|
38
|
+
storedAt: storedAt.toISOString(),
|
|
39
|
+
expiresAt: new Date(storedAt.getTime() + params.ttlMs).toISOString(),
|
|
30
40
|
};
|
|
31
41
|
}
|
|
32
42
|
export function createInMemoryResourceStore(options = {}) {
|
|
@@ -35,26 +45,36 @@ export function createInMemoryResourceStore(options = {}) {
|
|
|
35
45
|
...options,
|
|
36
46
|
};
|
|
37
47
|
const byUri = new Map();
|
|
38
|
-
const byHashIndex = new Map(); // sha256hex
|
|
48
|
+
const byHashIndex = new Map(); // mimeType:sha256hex -> uri
|
|
39
49
|
let totalBytes = 0;
|
|
40
|
-
function
|
|
41
|
-
const first = byUri.keys().next();
|
|
42
|
-
if (first.done)
|
|
43
|
-
return;
|
|
44
|
-
const uri = first.value;
|
|
50
|
+
function removeEntry(uri, reason) {
|
|
45
51
|
const existing = byUri.get(uri);
|
|
46
52
|
if (!existing)
|
|
47
53
|
return;
|
|
48
54
|
totalBytes -= existing.size;
|
|
49
55
|
byUri.delete(uri);
|
|
50
|
-
byHashIndex.delete(existing.hash);
|
|
56
|
+
byHashIndex.delete(buildIndexKey(existing.mimeType, existing.hash));
|
|
51
57
|
publishResourceStoreDiagnostics({
|
|
52
58
|
phase: 'cache_evict',
|
|
53
59
|
uri,
|
|
54
60
|
name: existing.name,
|
|
55
61
|
bytes: existing.size,
|
|
62
|
+
...(reason !== undefined ? { reason } : {}),
|
|
56
63
|
});
|
|
57
64
|
}
|
|
65
|
+
function evictOldest() {
|
|
66
|
+
const first = byUri.keys().next();
|
|
67
|
+
if (first.done)
|
|
68
|
+
return;
|
|
69
|
+
removeEntry(first.value);
|
|
70
|
+
}
|
|
71
|
+
function pruneExpiredEntries(now = Date.now()) {
|
|
72
|
+
for (const [uri, entry] of byUri) {
|
|
73
|
+
if (isExpired(entry, now)) {
|
|
74
|
+
removeEntry(uri, 'expired');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
58
78
|
function enforceLimits() {
|
|
59
79
|
while (byUri.size > resolved.maxEntries)
|
|
60
80
|
evictOldest();
|
|
@@ -65,6 +85,7 @@ export function createInMemoryResourceStore(options = {}) {
|
|
|
65
85
|
}
|
|
66
86
|
}
|
|
67
87
|
function putText(params) {
|
|
88
|
+
pruneExpiredEntries();
|
|
68
89
|
const mimeType = params.mimeType ?? 'text/plain';
|
|
69
90
|
const entryBytes = estimateBytes(params.text);
|
|
70
91
|
if (entryBytes > resolved.maxEntryBytes) {
|
|
@@ -76,17 +97,26 @@ export function createInMemoryResourceStore(options = {}) {
|
|
|
76
97
|
throw new McpError(ErrorCode.E_TOO_LARGE, `Resource too large to cache (${entryBytes} bytes)`);
|
|
77
98
|
}
|
|
78
99
|
const contentHash = computeSha256(params.text);
|
|
79
|
-
const
|
|
100
|
+
const indexKey = buildIndexKey(mimeType, contentHash);
|
|
101
|
+
const existingUri = byHashIndex.get(indexKey);
|
|
80
102
|
if (existingUri !== undefined) {
|
|
81
103
|
const cached = byUri.get(existingUri);
|
|
82
104
|
if (cached !== undefined) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
105
|
+
if (isExpired(cached)) {
|
|
106
|
+
removeEntry(existingUri, 'expired');
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
publishResourceStoreDiagnostics({
|
|
110
|
+
phase: 'cache_hit',
|
|
111
|
+
uri: cached.uri,
|
|
112
|
+
name: cached.name,
|
|
113
|
+
bytes: cached.size,
|
|
114
|
+
});
|
|
115
|
+
return cached;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
byHashIndex.delete(indexKey);
|
|
90
120
|
}
|
|
91
121
|
}
|
|
92
122
|
const id = randomUUID();
|
|
@@ -96,9 +126,10 @@ export function createInMemoryResourceStore(options = {}) {
|
|
|
96
126
|
name: params.name,
|
|
97
127
|
mimeType,
|
|
98
128
|
text: params.text,
|
|
129
|
+
ttlMs: resolved.entryTtlMs,
|
|
99
130
|
});
|
|
100
131
|
byUri.set(uri, entry);
|
|
101
|
-
byHashIndex.set(
|
|
132
|
+
byHashIndex.set(indexKey, uri);
|
|
102
133
|
totalBytes += entryBytes;
|
|
103
134
|
publishResourceStoreDiagnostics({
|
|
104
135
|
phase: 'cache_store',
|
|
@@ -129,6 +160,15 @@ export function createInMemoryResourceStore(options = {}) {
|
|
|
129
160
|
});
|
|
130
161
|
throw new McpError(ErrorCode.E_NOT_FOUND, `Resource not found: ${uri}. The cached result may have been evicted. Re-run the originating tool to regenerate.`);
|
|
131
162
|
}
|
|
163
|
+
if (isExpired(existing)) {
|
|
164
|
+
removeEntry(uri, 'expired');
|
|
165
|
+
publishResourceStoreDiagnostics({
|
|
166
|
+
phase: 'cache_miss',
|
|
167
|
+
uri,
|
|
168
|
+
reason: 'expired',
|
|
169
|
+
});
|
|
170
|
+
throw new McpError(ErrorCode.E_NOT_FOUND, `Resource expired: ${uri}. Re-run the originating tool to regenerate.`);
|
|
171
|
+
}
|
|
132
172
|
publishResourceStoreDiagnostics({
|
|
133
173
|
phase: 'cache_hit',
|
|
134
174
|
uri: existing.uri,
|
|
@@ -148,6 +188,7 @@ export function createInMemoryResourceStore(options = {}) {
|
|
|
148
188
|
});
|
|
149
189
|
}
|
|
150
190
|
function keys() {
|
|
191
|
+
pruneExpiredEntries();
|
|
151
192
|
return Array.from(byUri.keys());
|
|
152
193
|
}
|
|
153
194
|
return { putText, getText, clear, keys };
|
package/dist/prompts.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { type IconInfo } from './tools/shared.js';
|
|
3
3
|
export declare function registerGetHelpPrompt(server: McpServer, instructions: string, iconInfo?: IconInfo): void;
|
|
4
|
+
export declare function registerCompareFilesPrompt(server: McpServer, iconInfo?: IconInfo): void;
|
|
5
|
+
export declare function registerAnalyzePathPrompt(server: McpServer, iconInfo?: IconInfo): void;
|
package/dist/prompts.js
CHANGED
|
@@ -3,6 +3,12 @@ import { withDefaultIcons } from './tools/shared.js';
|
|
|
3
3
|
const HELP_PROMPT_NAME = 'get-help';
|
|
4
4
|
const HELP_PROMPT_TITLE = 'Get Help';
|
|
5
5
|
const HELP_PROMPT_DESCRIPTION = 'Return filesystem-mcp usage instructions.';
|
|
6
|
+
const COMPARE_FILES_PROMPT_NAME = 'compare-files';
|
|
7
|
+
const COMPARE_FILES_PROMPT_TITLE = 'Compare Files';
|
|
8
|
+
const COMPARE_FILES_PROMPT_DESCRIPTION = 'Generate a workflow for comparing two files using diff_files.';
|
|
9
|
+
const ANALYZE_PATH_PROMPT_NAME = 'analyze-path';
|
|
10
|
+
const ANALYZE_PATH_PROMPT_TITLE = 'Analyze Path';
|
|
11
|
+
const ANALYZE_PATH_PROMPT_DESCRIPTION = 'Generate a workflow for analyzing a file or directory using stat, read, and tree.';
|
|
6
12
|
function filterInstructionsByTopic(instructions, topic) {
|
|
7
13
|
const normalized = topic.trim().toLowerCase();
|
|
8
14
|
if (!normalized)
|
|
@@ -46,3 +52,48 @@ export function registerGetHelpPrompt(server, instructions, iconInfo) {
|
|
|
46
52
|
};
|
|
47
53
|
});
|
|
48
54
|
}
|
|
55
|
+
export function registerCompareFilesPrompt(server, iconInfo) {
|
|
56
|
+
server.registerPrompt(COMPARE_FILES_PROMPT_NAME, {
|
|
57
|
+
...withDefaultIcons({
|
|
58
|
+
title: COMPARE_FILES_PROMPT_TITLE,
|
|
59
|
+
description: COMPARE_FILES_PROMPT_DESCRIPTION,
|
|
60
|
+
}, iconInfo),
|
|
61
|
+
argsSchema: {
|
|
62
|
+
original: z.string().describe('Path to the original file.'),
|
|
63
|
+
modified: z.string().describe('Path to the modified file.'),
|
|
64
|
+
},
|
|
65
|
+
}, ({ original, modified }) => ({
|
|
66
|
+
description: COMPARE_FILES_PROMPT_DESCRIPTION,
|
|
67
|
+
messages: [
|
|
68
|
+
{
|
|
69
|
+
role: 'user',
|
|
70
|
+
content: {
|
|
71
|
+
type: 'text',
|
|
72
|
+
text: `Compare files and explain differences.\n\n1. Call \`diff_files\` with:\n - original: ${original}\n - modified: ${modified}\n2. Summarize: additions, deletions, and semantic changes.\n3. Flag any potential issues (conflicts, regressions, breaking changes).`,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
export function registerAnalyzePathPrompt(server, iconInfo) {
|
|
79
|
+
server.registerPrompt(ANALYZE_PATH_PROMPT_NAME, {
|
|
80
|
+
...withDefaultIcons({
|
|
81
|
+
title: ANALYZE_PATH_PROMPT_TITLE,
|
|
82
|
+
description: ANALYZE_PATH_PROMPT_DESCRIPTION,
|
|
83
|
+
}, iconInfo),
|
|
84
|
+
argsSchema: {
|
|
85
|
+
path: z.string().describe('Absolute path to analyze.'),
|
|
86
|
+
},
|
|
87
|
+
}, ({ path: targetPath }) => ({
|
|
88
|
+
description: ANALYZE_PATH_PROMPT_DESCRIPTION,
|
|
89
|
+
messages: [
|
|
90
|
+
{
|
|
91
|
+
role: 'user',
|
|
92
|
+
content: {
|
|
93
|
+
type: 'text',
|
|
94
|
+
text: `Analyze the path: ${targetPath}\n\n1. Call \`stat\` to determine if it is a file or directory.\n2. If file: call \`read\` with \`includeHash: true\` and summarize contents.\n3. If directory: call \`tree\` (maxDepth: 3) and \`ls\` to summarize structure.\n4. Report: type, size, permissions, key observations.`,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
@@ -1,17 +1,42 @@
|
|
|
1
1
|
import { buildToolCatalogDetailsOnly } from './tool-catalog.js';
|
|
2
|
-
import { buildCoreContextPack, getSharedConstraints, getToolContracts, } from './tool-info.js';
|
|
2
|
+
import { buildCoreContextPack, formatToolNameList, getSharedConstraints, getTaskCapableToolNames, getToolContracts, pickAvailableToolNames, } from './tool-info.js';
|
|
3
3
|
import { buildWorkflowGuide } from './workflows.js';
|
|
4
|
-
|
|
4
|
+
function buildToolsOverview() {
|
|
5
|
+
const rows = [
|
|
6
|
+
['Navigate', pickAvailableToolNames(['roots', 'ls', 'tree', 'find'])],
|
|
7
|
+
[
|
|
8
|
+
'Inspect',
|
|
9
|
+
pickAvailableToolNames(['stat', 'stat_many', 'grep', 'calculate_hash']),
|
|
10
|
+
],
|
|
11
|
+
['Read', pickAvailableToolNames(['read', 'read_many', 'diff_files'])],
|
|
12
|
+
[
|
|
13
|
+
'Write',
|
|
14
|
+
pickAvailableToolNames([
|
|
15
|
+
'mkdir',
|
|
16
|
+
'write',
|
|
17
|
+
'edit',
|
|
18
|
+
'mv',
|
|
19
|
+
'rm',
|
|
20
|
+
'apply_patch',
|
|
21
|
+
'search_and_replace',
|
|
22
|
+
]),
|
|
23
|
+
],
|
|
24
|
+
];
|
|
25
|
+
return rows
|
|
26
|
+
.filter(([, names]) => names.length > 0)
|
|
27
|
+
.map(([category, names]) => `| ${category} | ${formatToolNameList(names)} |`)
|
|
28
|
+
.join('\n');
|
|
29
|
+
}
|
|
30
|
+
function buildInstructionsHeader() {
|
|
31
|
+
const taskCapable = formatToolNameList(getTaskCapableToolNames());
|
|
32
|
+
return `<role>
|
|
5
33
|
Filesystem agent. Scope: allowed roots only. Discover paths before acting — never guess.
|
|
6
34
|
</role>
|
|
7
35
|
|
|
8
36
|
<tools_overview>
|
|
9
37
|
| Category | Tools |
|
|
10
38
|
|----------|-------|
|
|
11
|
-
|
|
12
|
-
| Inspect | \`stat\`, \`stat_many\`, \`grep\`, \`calculate_hash\` |
|
|
13
|
-
| Read | \`read\`, \`read_many\`, \`diff_files\` |
|
|
14
|
-
| Write | \`mkdir\`, \`write\`, \`edit\`, \`mv\`, \`rm\`, \`apply_patch\`, \`search_and_replace\` |
|
|
39
|
+
${buildToolsOverview()}
|
|
15
40
|
</tools_overview>
|
|
16
41
|
|
|
17
42
|
<resources>
|
|
@@ -24,10 +49,12 @@ Filesystem agent. Scope: allowed roots only. Discover paths before acting — ne
|
|
|
24
49
|
</resources>
|
|
25
50
|
|
|
26
51
|
<task_protocol>
|
|
27
|
-
|
|
28
|
-
|
|
52
|
+
Task execution: Tools returning a task ID must be polled via \`tasks/get\`, then retrieved via \`tasks/result\`.
|
|
53
|
+
Progress: Pass \`_meta.progressToken\` in \`tools/call\` to receive \`notifications/progress\`.
|
|
54
|
+
Task-capable: ${taskCapable}.
|
|
29
55
|
</task_protocol>
|
|
30
56
|
`;
|
|
57
|
+
}
|
|
31
58
|
const INSTRUCTIONS_FOOTER = `<constraints>
|
|
32
59
|
${getSharedConstraints()
|
|
33
60
|
.map((c) => `- ${c}`)
|
|
@@ -54,7 +81,7 @@ function formatToolSection(tool) {
|
|
|
54
81
|
export function buildServerInstructions() {
|
|
55
82
|
const toolSections = getToolContracts().map(formatToolSection).join('\n\n');
|
|
56
83
|
return [
|
|
57
|
-
|
|
84
|
+
buildInstructionsHeader(),
|
|
58
85
|
buildCoreContextPack(),
|
|
59
86
|
'',
|
|
60
87
|
buildToolCatalogDetailsOnly(),
|
|
@@ -1,10 +1,24 @@
|
|
|
1
|
-
import { buildCoreContextPack } from './tool-info.js';
|
|
2
|
-
|
|
1
|
+
import { buildCoreContextPack, pickAvailableToolNames } from './tool-info.js';
|
|
2
|
+
function buildCrossToolDataFlow() {
|
|
3
|
+
const flows = [];
|
|
4
|
+
if (pickAvailableToolNames(['find', 'read']).length === 2) {
|
|
5
|
+
flows.push('find.results[].path -> read.path');
|
|
6
|
+
}
|
|
7
|
+
if (pickAvailableToolNames(['grep', 'read']).length === 2) {
|
|
8
|
+
flows.push('grep.matches[].file -> read.path');
|
|
9
|
+
}
|
|
10
|
+
if (pickAvailableToolNames(['diff_files', 'apply_patch']).length === 2) {
|
|
11
|
+
flows.push('diff_files.diff -> apply_patch.patch');
|
|
12
|
+
}
|
|
13
|
+
flows.push('toolResult.resourceUri -> resources/read.uri');
|
|
14
|
+
return flows.join('\n');
|
|
15
|
+
}
|
|
16
|
+
function buildCatalogGuide() {
|
|
17
|
+
return `<tool_selection_guide>
|
|
3
18
|
## Cross-Tool Data Flow
|
|
4
19
|
|
|
5
20
|
\`\`\`
|
|
6
|
-
|
|
7
|
-
diff_files(patch) -> apply_patch.patch
|
|
21
|
+
${buildCrossToolDataFlow()}
|
|
8
22
|
\`\`\`
|
|
9
23
|
|
|
10
24
|
## Search Strategy
|
|
@@ -19,16 +33,25 @@ diff_files(patch) -> apply_patch.patch
|
|
|
19
33
|
- \`write\`: create files or overwrite full contents.
|
|
20
34
|
- \`search_and_replace\`: bulk multi-file replacements.
|
|
21
35
|
|
|
36
|
+
### edit vs write vs search_and_replace Decision
|
|
37
|
+
|
|
38
|
+
1. **Single file, targeted change?** -> \`edit\` (match exact text, replace first occurrence)
|
|
39
|
+
2. **Single file, full rewrite?** -> \`write\` (overwrite entire content)
|
|
40
|
+
3. **Multiple files, same change?** -> \`search_and_replace\` (glob + pattern across files)
|
|
41
|
+
4. **Not sure what to change?** -> \`grep\` first, then decide
|
|
42
|
+
|
|
22
43
|
## Patch Management
|
|
23
44
|
|
|
24
45
|
- Generate patches with \`diff_files\` first.
|
|
25
46
|
- Validate with \`apply_patch(dryRun:true)\` before writing.
|
|
26
|
-
- \`apply_patch\` accepts unified diffs
|
|
47
|
+
- \`apply_patch\` accepts unified diffs - single-file or multi-file.
|
|
48
|
+
- Multi-file: \`path\` is base directory; each file is best-effort with per-file \`results[]\`.
|
|
27
49
|
</tool_selection_guide>
|
|
28
50
|
`;
|
|
51
|
+
}
|
|
29
52
|
export function buildToolCatalog() {
|
|
30
|
-
return `${buildCoreContextPack()}\n\n${
|
|
53
|
+
return `${buildCoreContextPack()}\n\n${buildCatalogGuide()}`;
|
|
31
54
|
}
|
|
32
55
|
export function buildToolCatalogDetailsOnly() {
|
|
33
|
-
return
|
|
56
|
+
return buildCatalogGuide();
|
|
34
57
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { ToolContract } from '../tools/contract.js';
|
|
2
2
|
export declare function getToolContracts(): ToolContract[];
|
|
3
|
+
export declare function getSortedToolContracts(): ToolContract[];
|
|
4
|
+
export declare function pickAvailableToolNames(names: readonly string[]): string[];
|
|
5
|
+
export declare function formatToolNameList(names: readonly string[]): string;
|
|
6
|
+
export declare function getTaskCapableToolNames(): string[];
|
|
3
7
|
export declare function buildCoreContextPack(): string;
|
|
4
8
|
export declare function getSharedConstraints(): string[];
|
|
5
9
|
export declare function buildToolInfo(name: string): string | undefined;
|
|
@@ -8,6 +8,9 @@ function toEntry(contract) {
|
|
|
8
8
|
annotations.push('[Idempotent]');
|
|
9
9
|
if (contract.annotations?.readOnlyHint)
|
|
10
10
|
annotations.push('[Read-Only]');
|
|
11
|
+
if (contract.taskSupport === 'optional' ||
|
|
12
|
+
contract.taskSupport === 'required')
|
|
13
|
+
annotations.push('[Task]');
|
|
11
14
|
return {
|
|
12
15
|
name: contract.name,
|
|
13
16
|
description: contract.description,
|
|
@@ -21,13 +24,28 @@ function toEntry(contract) {
|
|
|
21
24
|
};
|
|
22
25
|
}
|
|
23
26
|
const ENTRIES = Object.fromEntries(ALL_TOOLS.map((contract) => [contract.name, toEntry(contract)]));
|
|
27
|
+
const CONTRACTS_BY_NAME = new Map(ALL_TOOLS.map((contract) => [contract.name, contract]));
|
|
24
28
|
export function getToolContracts() {
|
|
25
29
|
return ALL_TOOLS;
|
|
26
30
|
}
|
|
31
|
+
export function getSortedToolContracts() {
|
|
32
|
+
return [...ALL_TOOLS].sort((left, right) => left.name.localeCompare(right.name));
|
|
33
|
+
}
|
|
34
|
+
export function pickAvailableToolNames(names) {
|
|
35
|
+
return names.filter((name) => CONTRACTS_BY_NAME.has(name));
|
|
36
|
+
}
|
|
37
|
+
export function formatToolNameList(names) {
|
|
38
|
+
return names.map((name) => `\`${name}\``).join(', ');
|
|
39
|
+
}
|
|
40
|
+
export function getTaskCapableToolNames() {
|
|
41
|
+
return getSortedToolContracts()
|
|
42
|
+
.filter((contract) => contract.taskSupport === 'optional' ||
|
|
43
|
+
contract.taskSupport === 'required')
|
|
44
|
+
.map((contract) => contract.name);
|
|
45
|
+
}
|
|
27
46
|
export function buildCoreContextPack() {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
const e = ENTRIES[name];
|
|
47
|
+
const rows = getSortedToolContracts().map((contract) => {
|
|
48
|
+
const e = ENTRIES[contract.name];
|
|
31
49
|
if (!e)
|
|
32
50
|
return '';
|
|
33
51
|
const annotations = e.annotations ? ` ${e.annotations.join(' ')}` : '';
|
|
@@ -1,20 +1,32 @@
|
|
|
1
|
+
import { formatToolNameList, pickAvailableToolNames } from './tool-info.js';
|
|
1
2
|
export function buildWorkflowGuide() {
|
|
3
|
+
const exploreTools = formatToolNameList(pickAvailableToolNames([
|
|
4
|
+
'roots',
|
|
5
|
+
'ls',
|
|
6
|
+
'tree',
|
|
7
|
+
'stat',
|
|
8
|
+
'stat_many',
|
|
9
|
+
'read',
|
|
10
|
+
'read_many',
|
|
11
|
+
]));
|
|
12
|
+
const searchTools = formatToolNameList(pickAvailableToolNames(['find', 'grep', 'read']));
|
|
13
|
+
const editTools = formatToolNameList(pickAvailableToolNames(['edit', 'search_and_replace', 'mv', 'rm', 'mkdir']));
|
|
2
14
|
return `<workflows>
|
|
3
15
|
### A: EXPLORE — directory layout or file content
|
|
4
|
-
1.
|
|
16
|
+
1. ${exploreTools}.
|
|
5
17
|
> **Strict:** Resolve paths first. Never guess.
|
|
6
18
|
|
|
7
19
|
### B: SEARCH — files by pattern or content
|
|
8
|
-
1.
|
|
20
|
+
1. ${searchTools}.
|
|
9
21
|
> **Strict:** Content search with \`grep\`, not \`find\`.
|
|
10
22
|
|
|
11
23
|
### C: EDIT — modify files or layout
|
|
12
|
-
1.
|
|
13
|
-
2. \`
|
|
24
|
+
1. ${editTools}.
|
|
25
|
+
2. Use \`edit\` for targeted changes, \`search_and_replace\` for bulk changes, and \`mv\`/\`rm\`/\`mkdir\` for layout updates.
|
|
14
26
|
> **Strict:** Confirm destructive ops (\`write\`, \`mv\`, \`rm\`, bulk replace).
|
|
15
27
|
|
|
16
28
|
### D: PATCH — apply unified diffs
|
|
17
29
|
1. \`diff_files\` → \`apply_patch(dryRun:true)\` → \`apply_patch\`.
|
|
18
|
-
> **Tip:** Feed \`diff_files\` output directly
|
|
30
|
+
> **Tip:** Feed \`diff_files\` output directly. Multi-file patches: \`path\` = base dir, results per file.
|
|
19
31
|
</workflows>`;
|
|
20
32
|
}
|
package/dist/schemas.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ interface TreeEntry {
|
|
|
9
9
|
name: string;
|
|
10
10
|
type: z.infer<typeof FileTypeSchema>;
|
|
11
11
|
relativePath: string;
|
|
12
|
+
size?: number | undefined;
|
|
12
13
|
children?: TreeEntry[] | undefined;
|
|
13
14
|
}
|
|
14
15
|
export declare const ToolErrorResponseSchema: z.ZodObject<{
|
|
@@ -34,13 +35,14 @@ export declare const ToolErrorResponseSchema: z.ZodObject<{
|
|
|
34
35
|
}, z.core.$strict>;
|
|
35
36
|
}, z.core.$strict>;
|
|
36
37
|
declare const HeadLinesSchema: z.ZodOptional<z.ZodInt>;
|
|
37
|
-
declare const
|
|
38
|
+
declare const TailLinesSchema: z.ZodOptional<z.ZodInt>;
|
|
39
|
+
declare const LineNumberSchema: z.ZodInt;
|
|
38
40
|
export declare const ListDirectoryInputSchema: z.ZodObject<{
|
|
39
41
|
path: z.ZodOptional<z.ZodString>;
|
|
40
42
|
includeHidden: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
41
43
|
includeIgnored: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
42
|
-
maxDepth: z.ZodOptional<z.
|
|
43
|
-
maxEntries: z.ZodDefault<z.ZodOptional<z.
|
|
44
|
+
maxDepth: z.ZodOptional<z.ZodInt>;
|
|
45
|
+
maxEntries: z.ZodDefault<z.ZodOptional<z.ZodInt>>;
|
|
44
46
|
sortBy: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
45
47
|
name: "name";
|
|
46
48
|
size: "size";
|
|
@@ -55,7 +57,7 @@ export declare const ListAllowedDirectoriesInputSchema: z.ZodObject<{}, z.core.$
|
|
|
55
57
|
export declare const SearchFilesInputSchema: z.ZodObject<{
|
|
56
58
|
path: z.ZodOptional<z.ZodString>;
|
|
57
59
|
pattern: z.ZodString;
|
|
58
|
-
maxResults: z.ZodDefault<z.ZodOptional<z.
|
|
60
|
+
maxResults: z.ZodDefault<z.ZodOptional<z.ZodInt>>;
|
|
59
61
|
includeIgnored: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
60
62
|
includeHidden: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
61
63
|
sortBy: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
@@ -64,15 +66,16 @@ export declare const SearchFilesInputSchema: z.ZodObject<{
|
|
|
64
66
|
path: "path";
|
|
65
67
|
modified: "modified";
|
|
66
68
|
}>>>;
|
|
67
|
-
maxDepth: z.ZodOptional<z.
|
|
69
|
+
maxDepth: z.ZodOptional<z.ZodInt>;
|
|
68
70
|
cursor: z.ZodOptional<z.ZodString>;
|
|
69
71
|
}, z.core.$strict>;
|
|
70
72
|
export declare const TreeInputSchema: z.ZodObject<{
|
|
71
73
|
path: z.ZodOptional<z.ZodString>;
|
|
72
|
-
maxDepth: z.ZodDefault<z.ZodOptional<z.
|
|
73
|
-
maxEntries: z.ZodDefault<z.ZodOptional<z.
|
|
74
|
+
maxDepth: z.ZodDefault<z.ZodOptional<z.ZodInt>>;
|
|
75
|
+
maxEntries: z.ZodDefault<z.ZodOptional<z.ZodInt>>;
|
|
74
76
|
includeHidden: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
75
77
|
includeIgnored: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
78
|
+
includeSizes: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
76
79
|
}, z.core.$strict>;
|
|
77
80
|
export declare const SearchContentInputSchema: z.ZodObject<{
|
|
78
81
|
path: z.ZodOptional<z.ZodString>;
|
|
@@ -80,20 +83,24 @@ export declare const SearchContentInputSchema: z.ZodObject<{
|
|
|
80
83
|
isRegex: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
81
84
|
caseSensitive: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
82
85
|
wholeWord: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
83
|
-
contextLines: z.ZodDefault<z.ZodOptional<z.
|
|
84
|
-
maxResults: z.ZodDefault<z.ZodOptional<z.
|
|
86
|
+
contextLines: z.ZodDefault<z.ZodOptional<z.ZodInt>>;
|
|
87
|
+
maxResults: z.ZodDefault<z.ZodOptional<z.ZodInt>>;
|
|
85
88
|
filePattern: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
86
89
|
includeHidden: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
87
90
|
includeIgnored: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
91
|
+
multiline: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
88
92
|
}, z.core.$strict>;
|
|
89
93
|
export declare const ReadFileInputSchema: z.ZodObject<{
|
|
94
|
+
includeHash: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
90
95
|
head: typeof HeadLinesSchema;
|
|
96
|
+
tail: typeof TailLinesSchema;
|
|
91
97
|
startLine: z.ZodOptional<typeof LineNumberSchema>;
|
|
92
98
|
endLine: z.ZodOptional<typeof LineNumberSchema>;
|
|
93
99
|
path: z.ZodString;
|
|
94
100
|
}, z.core.$strict>;
|
|
95
101
|
export declare const ReadMultipleFilesInputSchema: z.ZodObject<{
|
|
96
102
|
head: typeof HeadLinesSchema;
|
|
103
|
+
tail: typeof TailLinesSchema;
|
|
97
104
|
startLine: z.ZodOptional<typeof LineNumberSchema>;
|
|
98
105
|
endLine: z.ZodOptional<typeof LineNumberSchema>;
|
|
99
106
|
paths: z.ZodArray<z.ZodString>;
|
|
@@ -250,6 +257,7 @@ export declare const SearchContentOutputSchema: z.ZodObject<{
|
|
|
250
257
|
matches: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
251
258
|
file: z.ZodString;
|
|
252
259
|
line: z.ZodNumber;
|
|
260
|
+
column: z.ZodOptional<z.ZodNumber>;
|
|
253
261
|
content: z.ZodString;
|
|
254
262
|
matchCount: z.ZodNumber;
|
|
255
263
|
contextBefore: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -303,14 +311,17 @@ export declare const ReadFileOutputSchema: z.ZodObject<{
|
|
|
303
311
|
head: "head";
|
|
304
312
|
full: "full";
|
|
305
313
|
range: "range";
|
|
314
|
+
tail: "tail";
|
|
306
315
|
}>>;
|
|
307
316
|
head: z.ZodOptional<z.ZodNumber>;
|
|
317
|
+
tail: z.ZodOptional<z.ZodNumber>;
|
|
308
318
|
startLine: z.ZodOptional<z.ZodNumber>;
|
|
309
319
|
endLine: z.ZodOptional<z.ZodNumber>;
|
|
310
320
|
linesRead: z.ZodOptional<z.ZodNumber>;
|
|
311
321
|
hasMoreLines: z.ZodOptional<z.ZodBoolean>;
|
|
312
322
|
ok: z.ZodBoolean;
|
|
313
323
|
path: z.ZodOptional<z.ZodString>;
|
|
324
|
+
contentHash: z.ZodOptional<z.ZodString>;
|
|
314
325
|
error: z.ZodOptional<z.ZodObject<{
|
|
315
326
|
code: z.ZodEnum<{
|
|
316
327
|
readonly E_ACCESS_DENIED: "E_ACCESS_DENIED";
|
|
@@ -342,8 +353,10 @@ export declare const ReadMultipleFilesOutputSchema: z.ZodObject<{
|
|
|
342
353
|
head: "head";
|
|
343
354
|
full: "full";
|
|
344
355
|
range: "range";
|
|
356
|
+
tail: "tail";
|
|
345
357
|
}>>;
|
|
346
358
|
head: z.ZodOptional<z.ZodNumber>;
|
|
359
|
+
tail: z.ZodOptional<z.ZodNumber>;
|
|
347
360
|
startLine: z.ZodOptional<z.ZodNumber>;
|
|
348
361
|
endLine: z.ZodOptional<z.ZodNumber>;
|
|
349
362
|
linesRead: z.ZodOptional<z.ZodNumber>;
|
|
@@ -352,6 +365,7 @@ export declare const ReadMultipleFilesOutputSchema: z.ZodObject<{
|
|
|
352
365
|
truncationReason: z.ZodOptional<z.ZodEnum<{
|
|
353
366
|
head: "head";
|
|
354
367
|
range: "range";
|
|
368
|
+
tail: "tail";
|
|
355
369
|
externalized: "externalized";
|
|
356
370
|
}>>;
|
|
357
371
|
maxTotalSize: z.ZodOptional<z.ZodNumber>;
|
|
@@ -544,6 +558,7 @@ export declare const EditFileOutputSchema: z.ZodObject<{
|
|
|
544
558
|
appliedEdits: z.ZodOptional<z.ZodNumber>;
|
|
545
559
|
lineRange: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
|
|
546
560
|
unmatchedEdits: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
561
|
+
diff: z.ZodOptional<z.ZodString>;
|
|
547
562
|
error: z.ZodOptional<z.ZodObject<{
|
|
548
563
|
code: z.ZodEnum<{
|
|
549
564
|
readonly E_ACCESS_DENIED: "E_ACCESS_DENIED";
|
|
@@ -658,7 +673,7 @@ export declare const CalculateHashOutputSchema: z.ZodObject<{
|
|
|
658
673
|
export declare const DiffFilesInputSchema: z.ZodObject<{
|
|
659
674
|
original: z.ZodString;
|
|
660
675
|
modified: z.ZodString;
|
|
661
|
-
context: z.ZodOptional<z.
|
|
676
|
+
context: z.ZodOptional<z.ZodInt>;
|
|
662
677
|
ignoreWhitespace: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
663
678
|
stripTrailingCr: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
664
679
|
}, z.core.$strict>;
|
|
@@ -666,6 +681,9 @@ export declare const DiffFilesOutputSchema: z.ZodObject<{
|
|
|
666
681
|
ok: z.ZodBoolean;
|
|
667
682
|
diff: z.ZodOptional<z.ZodString>;
|
|
668
683
|
isIdentical: z.ZodOptional<z.ZodBoolean>;
|
|
684
|
+
linesAdded: z.ZodOptional<z.ZodNumber>;
|
|
685
|
+
linesRemoved: z.ZodOptional<z.ZodNumber>;
|
|
686
|
+
hunksCount: z.ZodOptional<z.ZodNumber>;
|
|
669
687
|
truncated: z.ZodOptional<z.ZodBoolean>;
|
|
670
688
|
resourceUri: z.ZodOptional<z.ZodString>;
|
|
671
689
|
error: z.ZodOptional<z.ZodObject<{
|
|
@@ -691,7 +709,7 @@ export declare const DiffFilesOutputSchema: z.ZodObject<{
|
|
|
691
709
|
export declare const ApplyPatchInputSchema: z.ZodObject<{
|
|
692
710
|
path: z.ZodString;
|
|
693
711
|
patch: z.ZodString;
|
|
694
|
-
fuzzFactor: z.ZodOptional<z.
|
|
712
|
+
fuzzFactor: z.ZodOptional<z.ZodInt>;
|
|
695
713
|
autoConvertLineEndings: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
696
714
|
dryRun: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
697
715
|
}, z.core.$strict>;
|
|
@@ -699,6 +717,17 @@ export declare const ApplyPatchOutputSchema: z.ZodObject<{
|
|
|
699
717
|
ok: z.ZodBoolean;
|
|
700
718
|
path: z.ZodOptional<z.ZodString>;
|
|
701
719
|
applied: z.ZodOptional<z.ZodBoolean>;
|
|
720
|
+
hunksApplied: z.ZodOptional<z.ZodNumber>;
|
|
721
|
+
linesAdded: z.ZodOptional<z.ZodNumber>;
|
|
722
|
+
linesRemoved: z.ZodOptional<z.ZodNumber>;
|
|
723
|
+
results: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
724
|
+
path: z.ZodString;
|
|
725
|
+
applied: z.ZodBoolean;
|
|
726
|
+
hunksApplied: z.ZodOptional<z.ZodNumber>;
|
|
727
|
+
linesAdded: z.ZodOptional<z.ZodNumber>;
|
|
728
|
+
linesRemoved: z.ZodOptional<z.ZodNumber>;
|
|
729
|
+
error: z.ZodOptional<z.ZodString>;
|
|
730
|
+
}, z.core.$strict>>>;
|
|
702
731
|
error: z.ZodOptional<z.ZodObject<{
|
|
703
732
|
code: z.ZodEnum<{
|
|
704
733
|
readonly E_ACCESS_DENIED: "E_ACCESS_DENIED";
|
|
@@ -721,14 +750,16 @@ export declare const ApplyPatchOutputSchema: z.ZodObject<{
|
|
|
721
750
|
}, z.core.$strict>;
|
|
722
751
|
export declare const SearchAndReplaceInputSchema: z.ZodObject<{
|
|
723
752
|
path: z.ZodOptional<z.ZodString>;
|
|
724
|
-
filePattern: z.ZodString
|
|
753
|
+
filePattern: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
725
754
|
searchPattern: z.ZodString;
|
|
726
755
|
replacement: z.ZodString;
|
|
727
756
|
isRegex: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
757
|
+
caseSensitive: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
728
758
|
dryRun: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
729
759
|
includeHidden: z.ZodOptional<z.ZodBoolean>;
|
|
730
760
|
includeIgnored: z.ZodOptional<z.ZodBoolean>;
|
|
731
761
|
returnDiff: z.ZodOptional<z.ZodBoolean>;
|
|
762
|
+
maxFiles: z.ZodOptional<z.ZodInt>;
|
|
732
763
|
}, z.core.$strict>;
|
|
733
764
|
export declare const SearchAndReplaceOutputSchema: z.ZodObject<{
|
|
734
765
|
ok: z.ZodBoolean;
|
|
@@ -746,6 +777,10 @@ export declare const SearchAndReplaceOutputSchema: z.ZodObject<{
|
|
|
746
777
|
}, z.core.$strict>>>;
|
|
747
778
|
changedFilesTruncated: z.ZodOptional<z.ZodBoolean>;
|
|
748
779
|
diff: z.ZodOptional<z.ZodString>;
|
|
780
|
+
diffTruncated: z.ZodOptional<z.ZodBoolean>;
|
|
781
|
+
stoppedReason: z.ZodOptional<z.ZodEnum<{
|
|
782
|
+
maxFiles: "maxFiles";
|
|
783
|
+
}>>;
|
|
749
784
|
dryRun: z.ZodOptional<z.ZodBoolean>;
|
|
750
785
|
error: z.ZodOptional<z.ZodObject<{
|
|
751
786
|
code: z.ZodEnum<{
|