@j0hanz/filesystem-mcp 1.11.0 → 1.11.1
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/dist/completions.js +25 -0
- package/dist/prompts.d.ts +1 -0
- package/dist/prompts.js +58 -0
- package/dist/resources/generated-instructions.js +11 -3
- package/dist/resources/tool-catalog.js +49 -3
- package/dist/resources/tool-info.d.ts +1 -0
- package/dist/resources/tool-info.js +135 -8
- package/dist/resources.js +2 -0
- package/dist/schemas.d.ts +3 -1
- package/dist/schemas.js +2 -0
- package/dist/server/bootstrap.js +2 -1
- package/dist/tools/apply-patch.js +7 -4
- package/dist/tools/contract.d.ts +1 -1
- package/dist/tools/create-directory.js +3 -3
- package/dist/tools/delete-file.js +2 -2
- package/dist/tools/diff-files.js +6 -2
- package/dist/tools/edit-file.js +26 -9
- package/dist/tools/move-file.js +3 -3
- package/dist/tools/read-multiple.js +77 -89
- package/dist/tools/read.js +1 -1
- package/dist/tools/replace-in-files.d.ts +5 -2
- package/dist/tools/replace-in-files.js +67 -48
- package/dist/tools/roots.js +1 -1
- package/dist/tools/search-content.js +48 -46
- package/dist/tools/shared.js +4 -3
- package/dist/tools/stat-many.js +12 -19
- package/dist/tools/stat.js +1 -1
- package/dist/tools/task-support.js +3 -1
- package/dist/tools/write-file.js +4 -4
- package/package.json +1 -1
package/dist/completions.js
CHANGED
|
@@ -3,6 +3,7 @@ import * as path from 'node:path';
|
|
|
3
3
|
import { CompleteRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
4
4
|
import { getAllowedDirectories, isPathWithinDirectories, normalizePath, toPosixPath, } from './lib/paths.js';
|
|
5
5
|
import { isRecord } from './lib/utils.js';
|
|
6
|
+
import { getSortedToolContracts } from './resources/tool-info.js';
|
|
6
7
|
const MAX_COMPLETION_ITEMS = 100;
|
|
7
8
|
const COMPLETION_RATE_LIMIT_MS = 100;
|
|
8
9
|
const MAX_COMPLETION_CACHE_KEYS = 128;
|
|
@@ -29,6 +30,9 @@ function extractTopicCompletions(instructions) {
|
|
|
29
30
|
}
|
|
30
31
|
return headers;
|
|
31
32
|
}
|
|
33
|
+
function extractToolNameCompletions() {
|
|
34
|
+
return getSortedToolContracts().map((contract) => contract.name);
|
|
35
|
+
}
|
|
32
36
|
const PATH_ARGUMENTS = new Set([
|
|
33
37
|
'path',
|
|
34
38
|
'source',
|
|
@@ -415,6 +419,7 @@ export async function getPathCompletions(currentValue, options = {}) {
|
|
|
415
419
|
}
|
|
416
420
|
export function registerCompletions(server, instructions = '') {
|
|
417
421
|
const topicValues = extractTopicCompletions(instructions);
|
|
422
|
+
const toolNameValues = extractToolNameCompletions();
|
|
418
423
|
server.server.setRequestHandler(CompleteRequestSchema, async (request) => {
|
|
419
424
|
const { params } = request;
|
|
420
425
|
const { argument, ref } = params;
|
|
@@ -427,6 +432,26 @@ export function registerCompletions(server, instructions = '') {
|
|
|
427
432
|
: topicValues;
|
|
428
433
|
return buildCompletionResponse(buildCompletionResult(filtered));
|
|
429
434
|
}
|
|
435
|
+
if (isRecord(ref) &&
|
|
436
|
+
ref['type'] === 'ref/prompt' &&
|
|
437
|
+
ref['name'] === 'get-tool-help' &&
|
|
438
|
+
argName === 'name') {
|
|
439
|
+
const currentValue = argument.value.toLowerCase();
|
|
440
|
+
const filtered = currentValue
|
|
441
|
+
? toolNameValues.filter((value) => value.startsWith(currentValue))
|
|
442
|
+
: toolNameValues;
|
|
443
|
+
return buildCompletionResponse(buildCompletionResult(filtered));
|
|
444
|
+
}
|
|
445
|
+
if (isRecord(ref) &&
|
|
446
|
+
ref['type'] === 'ref/resource' &&
|
|
447
|
+
ref['uri'] === 'internal://tool-info/{name}' &&
|
|
448
|
+
argName === 'name') {
|
|
449
|
+
const currentValue = argument.value.toLowerCase();
|
|
450
|
+
const filtered = currentValue
|
|
451
|
+
? toolNameValues.filter((value) => value.startsWith(currentValue))
|
|
452
|
+
: toolNameValues;
|
|
453
|
+
return buildCompletionResponse(buildCompletionResult(filtered));
|
|
454
|
+
}
|
|
430
455
|
const isPathArg = isPathLikeArgumentName(argName) ||
|
|
431
456
|
isPathArgumentFromReference(argName, ref);
|
|
432
457
|
if (!isPathArg) {
|
package/dist/prompts.d.ts
CHANGED
|
@@ -3,3 +3,4 @@ import { type IconInfo } from './tools/shared.js';
|
|
|
3
3
|
export declare function registerGetHelpPrompt(server: McpServer, instructions: string, iconInfo?: IconInfo): void;
|
|
4
4
|
export declare function registerCompareFilesPrompt(server: McpServer, iconInfo?: IconInfo): void;
|
|
5
5
|
export declare function registerAnalyzePathPrompt(server: McpServer, iconInfo?: IconInfo): void;
|
|
6
|
+
export declare function registerGetToolHelpPrompt(server: McpServer, iconInfo?: IconInfo): void;
|
package/dist/prompts.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { ErrorCode, McpError } from './lib/errors.js';
|
|
3
|
+
import { buildToolInfo, getSortedToolContracts, } from './resources/tool-info.js';
|
|
2
4
|
import { withDefaultIcons } from './tools/shared.js';
|
|
3
5
|
const HELP_PROMPT_NAME = 'get-help';
|
|
4
6
|
const HELP_PROMPT_TITLE = 'Get Help';
|
|
@@ -9,6 +11,9 @@ const COMPARE_FILES_PROMPT_DESCRIPTION = 'Generate a workflow for comparing two
|
|
|
9
11
|
const ANALYZE_PATH_PROMPT_NAME = 'analyze-path';
|
|
10
12
|
const ANALYZE_PATH_PROMPT_TITLE = 'Analyze Path';
|
|
11
13
|
const ANALYZE_PATH_PROMPT_DESCRIPTION = 'Generate a workflow for analyzing a file or directory using stat, read, and tree.';
|
|
14
|
+
const GET_TOOL_HELP_PROMPT_NAME = 'get-tool-help';
|
|
15
|
+
const GET_TOOL_HELP_PROMPT_TITLE = 'Get Tool Help';
|
|
16
|
+
const GET_TOOL_HELP_PROMPT_DESCRIPTION = 'Return a prompt with the authoritative contract for a specific filesystem-mcp tool.';
|
|
12
17
|
function filterInstructionsByTopic(instructions, topic) {
|
|
13
18
|
const normalized = topic.trim().toLowerCase();
|
|
14
19
|
if (!normalized)
|
|
@@ -24,6 +29,12 @@ function filterInstructionsByTopic(instructions, topic) {
|
|
|
24
29
|
.join(', ');
|
|
25
30
|
return `Section '${topic}' not found. Available: ${available}\n\n${instructions}`;
|
|
26
31
|
}
|
|
32
|
+
function findKnownToolName(rawName) {
|
|
33
|
+
const normalized = rawName.trim().toLowerCase();
|
|
34
|
+
if (!normalized)
|
|
35
|
+
return undefined;
|
|
36
|
+
return getSortedToolContracts().find((contract) => contract.name.toLowerCase() === normalized)?.name;
|
|
37
|
+
}
|
|
27
38
|
export function registerGetHelpPrompt(server, instructions, iconInfo) {
|
|
28
39
|
const baseConfig = withDefaultIcons({ title: HELP_PROMPT_TITLE, description: HELP_PROMPT_DESCRIPTION }, iconInfo);
|
|
29
40
|
server.registerPrompt(HELP_PROMPT_NAME, {
|
|
@@ -97,3 +108,50 @@ export function registerAnalyzePathPrompt(server, iconInfo) {
|
|
|
97
108
|
],
|
|
98
109
|
}));
|
|
99
110
|
}
|
|
111
|
+
export function registerGetToolHelpPrompt(server, iconInfo) {
|
|
112
|
+
server.registerPrompt(GET_TOOL_HELP_PROMPT_NAME, {
|
|
113
|
+
...withDefaultIcons({
|
|
114
|
+
title: GET_TOOL_HELP_PROMPT_TITLE,
|
|
115
|
+
description: GET_TOOL_HELP_PROMPT_DESCRIPTION,
|
|
116
|
+
}, iconInfo),
|
|
117
|
+
argsSchema: {
|
|
118
|
+
name: z
|
|
119
|
+
.string()
|
|
120
|
+
.min(1)
|
|
121
|
+
.describe('Tool name from tools/list or internal://tool-info/{name}.'),
|
|
122
|
+
},
|
|
123
|
+
}, ({ name }) => {
|
|
124
|
+
const toolName = findKnownToolName(name);
|
|
125
|
+
if (!toolName) {
|
|
126
|
+
throw new McpError(ErrorCode.E_INVALID_INPUT, `Unknown tool: ${name}`);
|
|
127
|
+
}
|
|
128
|
+
const toolInfo = buildToolInfo(toolName);
|
|
129
|
+
if (!toolInfo) {
|
|
130
|
+
throw new McpError(ErrorCode.E_INVALID_INPUT, `Unknown tool: ${toolName}`);
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
description: GET_TOOL_HELP_PROMPT_DESCRIPTION,
|
|
134
|
+
messages: [
|
|
135
|
+
{
|
|
136
|
+
role: 'user',
|
|
137
|
+
content: {
|
|
138
|
+
type: 'text',
|
|
139
|
+
text: `Use the embedded contract for \`${toolName}\` as the authoritative reference. ` +
|
|
140
|
+
'Summarize when to use it, its key constraints, and the safest next action.',
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
role: 'user',
|
|
145
|
+
content: {
|
|
146
|
+
type: 'resource',
|
|
147
|
+
resource: {
|
|
148
|
+
uri: `internal://tool-info/${toolName}`,
|
|
149
|
+
mimeType: 'text/markdown',
|
|
150
|
+
text: toolInfo,
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { buildToolCatalogDetailsOnly } from './tool-catalog.js';
|
|
2
|
-
import { buildCoreContextPack, formatToolNameList, getSharedConstraints, getTaskCapableToolNames, getToolContracts, pickAvailableToolNames, } from './tool-info.js';
|
|
2
|
+
import { buildCoreContextPack, formatToolNameList, getSharedConstraints, getTaskCapableToolNames, getTaskToolNamesBySupport, getToolContracts, pickAvailableToolNames, } from './tool-info.js';
|
|
3
3
|
import { buildWorkflowGuide } from './workflows.js';
|
|
4
|
+
function formatTaskModeLine(label, names) {
|
|
5
|
+
return `${label}: ${names.length > 0 ? formatToolNameList(names) : 'none'}.`;
|
|
6
|
+
}
|
|
4
7
|
function buildToolsOverview() {
|
|
5
8
|
const rows = [
|
|
6
9
|
['Navigate', pickAvailableToolNames(['roots', 'ls', 'tree', 'find'])],
|
|
@@ -29,6 +32,8 @@ function buildToolsOverview() {
|
|
|
29
32
|
}
|
|
30
33
|
function buildInstructionsHeader() {
|
|
31
34
|
const taskCapable = formatToolNameList(getTaskCapableToolNames());
|
|
35
|
+
const optionalTaskTools = getTaskToolNamesBySupport('optional');
|
|
36
|
+
const requiredTaskTools = getTaskToolNamesBySupport('required');
|
|
32
37
|
return `<role>
|
|
33
38
|
Filesystem agent. Scope: allowed roots only. Discover paths before acting — never guess.
|
|
34
39
|
</role>
|
|
@@ -49,9 +54,12 @@ ${buildToolsOverview()}
|
|
|
49
54
|
</resources>
|
|
50
55
|
|
|
51
56
|
<task_protocol>
|
|
52
|
-
Task execution:
|
|
57
|
+
Task execution: Call task-capable tools inline by default; add \`task\` only when durable polling or deferred results are needed.
|
|
58
|
+
Task results: When a task is requested, poll via \`tasks/get\`, then retrieve the final payload via \`tasks/result\`.
|
|
53
59
|
Progress: Pass \`_meta.progressToken\` in \`tools/call\` to receive \`notifications/progress\`.
|
|
54
|
-
Task-capable: ${taskCapable}.
|
|
60
|
+
Task-capable: ${taskCapable || 'none'}.
|
|
61
|
+
${formatTaskModeLine('Optional task mode', optionalTaskTools)}
|
|
62
|
+
${formatTaskModeLine('Required task mode', requiredTaskTools)}
|
|
55
63
|
</task_protocol>
|
|
56
64
|
`;
|
|
57
65
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { buildCoreContextPack, pickAvailableToolNames } from './tool-info.js';
|
|
1
|
+
import { buildCoreContextPack, getTaskCapableToolNames, pickAvailableToolNames, } from './tool-info.js';
|
|
2
2
|
function buildCrossToolDataFlow() {
|
|
3
3
|
const flows = [];
|
|
4
4
|
if (pickAvailableToolNames(['find', 'read']).length === 2) {
|
|
@@ -14,13 +14,59 @@ function buildCrossToolDataFlow() {
|
|
|
14
14
|
return flows.join('\n');
|
|
15
15
|
}
|
|
16
16
|
function buildCatalogGuide() {
|
|
17
|
-
|
|
17
|
+
const taskCapable = getTaskCapableToolNames();
|
|
18
|
+
return (`<tool_selection_guide>
|
|
19
|
+
## Primitive Routing
|
|
20
|
+
|
|
21
|
+
- ` +
|
|
22
|
+
'`tools`' +
|
|
23
|
+
`: model-controlled operations that inspect or mutate the allowed filesystem.
|
|
24
|
+
- ` +
|
|
25
|
+
'`resources`' +
|
|
26
|
+
`: application-driven context such as ` +
|
|
27
|
+
'`internal://instructions`' +
|
|
28
|
+
`, ` +
|
|
29
|
+
'`internal://tool-info/{name}`' +
|
|
30
|
+
`, and cached ` +
|
|
31
|
+
'`filesystem-mcp://result/{id}`' +
|
|
32
|
+
` output.
|
|
33
|
+
- ` +
|
|
34
|
+
'`prompts`' +
|
|
35
|
+
`: user-controlled workflow templates for help, comparison, and guided inspection.
|
|
36
|
+
- ` +
|
|
37
|
+
'`completion`' +
|
|
38
|
+
`: argument suggestions for prompts and resource templates; not a discovery mechanism.
|
|
39
|
+
|
|
18
40
|
## Cross-Tool Data Flow
|
|
19
41
|
|
|
20
42
|
\`\`\`
|
|
21
43
|
${buildCrossToolDataFlow()}
|
|
22
44
|
\`\`\`
|
|
23
45
|
|
|
46
|
+
## Result Contract
|
|
47
|
+
|
|
48
|
+
- Successful tools return ` +
|
|
49
|
+
'`content`' +
|
|
50
|
+
` and usually ` +
|
|
51
|
+
'`structuredContent`' +
|
|
52
|
+
`.
|
|
53
|
+
- Tool/business failures return ` +
|
|
54
|
+
'`isError: true`' +
|
|
55
|
+
` inside the tool result, not a JSON-RPC protocol error.
|
|
56
|
+
- When a tool returns ` +
|
|
57
|
+
'`resourceUri`' +
|
|
58
|
+
` or a ` +
|
|
59
|
+
'`resource_link`' +
|
|
60
|
+
`, follow it with ` +
|
|
61
|
+
'`resources/read`' +
|
|
62
|
+
` immediately.
|
|
63
|
+
|
|
64
|
+
## Task Mode Routing
|
|
65
|
+
|
|
66
|
+
- Inline first for fast operations.
|
|
67
|
+
- Add task mode only when the caller needs durable polling, deferred retrieval, or cancellation after the initial response.
|
|
68
|
+
- Task-capable tools: ${taskCapable.length > 0 ? taskCapable.map((name) => `\`${name}\``).join(', ') : 'none'}.
|
|
69
|
+
|
|
24
70
|
## Search Strategy
|
|
25
71
|
|
|
26
72
|
- \`find\`: glob file discovery.
|
|
@@ -47,7 +93,7 @@ ${buildCrossToolDataFlow()}
|
|
|
47
93
|
- \`apply_patch\` accepts unified diffs - single-file or multi-file.
|
|
48
94
|
- Multi-file: \`path\` is base directory; each file is best-effort with per-file \`results[]\`.
|
|
49
95
|
</tool_selection_guide>
|
|
50
|
-
|
|
96
|
+
`);
|
|
51
97
|
}
|
|
52
98
|
export function buildToolCatalog() {
|
|
53
99
|
return `${buildCoreContextPack()}\n\n${buildCatalogGuide()}`;
|
|
@@ -4,6 +4,7 @@ export declare function getSortedToolContracts(): ToolContract[];
|
|
|
4
4
|
export declare function pickAvailableToolNames(names: readonly string[]): string[];
|
|
5
5
|
export declare function formatToolNameList(names: readonly string[]): string;
|
|
6
6
|
export declare function getTaskCapableToolNames(): string[];
|
|
7
|
+
export declare function getTaskToolNamesBySupport(taskSupport: Extract<ToolContract['taskSupport'], 'optional' | 'required'>): string[];
|
|
7
8
|
export declare function buildCoreContextPack(): string;
|
|
8
9
|
export declare function getSharedConstraints(): string[];
|
|
9
10
|
export declare function buildToolInfo(name: string): string | undefined;
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import { DEFAULT_SEARCH_CONTENT_RESULTS, MAX_SEARCH_RESULTS, MAX_TEXT_FILE_SIZE, } from '../lib/constants.js';
|
|
2
3
|
import { ALL_TOOLS } from '../tools.js';
|
|
4
|
+
function getTaskSupportLabel(taskSupport) {
|
|
5
|
+
switch (taskSupport) {
|
|
6
|
+
case 'optional':
|
|
7
|
+
return '[Task: Optional]';
|
|
8
|
+
case 'required':
|
|
9
|
+
return '[Task: Required]';
|
|
10
|
+
default:
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
3
14
|
function toEntry(contract) {
|
|
4
15
|
const annotations = [];
|
|
5
16
|
if (contract.annotations?.destructiveHint)
|
|
@@ -8,11 +19,12 @@ function toEntry(contract) {
|
|
|
8
19
|
annotations.push('[Idempotent]');
|
|
9
20
|
if (contract.annotations?.readOnlyHint)
|
|
10
21
|
annotations.push('[Read-Only]');
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
annotations.push(
|
|
22
|
+
const taskLabel = getTaskSupportLabel(contract.taskSupport);
|
|
23
|
+
if (taskLabel)
|
|
24
|
+
annotations.push(taskLabel);
|
|
14
25
|
return {
|
|
15
26
|
name: contract.name,
|
|
27
|
+
title: contract.title,
|
|
16
28
|
description: contract.description,
|
|
17
29
|
...(annotations.length > 0 ? { annotations } : {}),
|
|
18
30
|
...(contract.nuances && contract.nuances.length > 0
|
|
@@ -43,6 +55,11 @@ export function getTaskCapableToolNames() {
|
|
|
43
55
|
contract.taskSupport === 'required')
|
|
44
56
|
.map((contract) => contract.name);
|
|
45
57
|
}
|
|
58
|
+
export function getTaskToolNamesBySupport(taskSupport) {
|
|
59
|
+
return getSortedToolContracts()
|
|
60
|
+
.filter((contract) => contract.taskSupport === taskSupport)
|
|
61
|
+
.map((contract) => contract.name);
|
|
62
|
+
}
|
|
46
63
|
export function buildCoreContextPack() {
|
|
47
64
|
const rows = getSortedToolContracts().map((contract) => {
|
|
48
65
|
const e = ENTRIES[contract.name];
|
|
@@ -61,25 +78,135 @@ export function getSharedConstraints() {
|
|
|
61
78
|
'If response includes `resourceUri`, call `resources/read` immediately — cached results expire on restart.',
|
|
62
79
|
];
|
|
63
80
|
}
|
|
81
|
+
function formatTaskSupportLabel(taskSupport) {
|
|
82
|
+
switch (taskSupport) {
|
|
83
|
+
case 'optional':
|
|
84
|
+
return 'optional';
|
|
85
|
+
case 'required':
|
|
86
|
+
return 'required';
|
|
87
|
+
default:
|
|
88
|
+
return 'forbidden';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function formatAnnotationValue(value) {
|
|
92
|
+
return value ? 'true' : 'false';
|
|
93
|
+
}
|
|
94
|
+
function toJsonSchemaObject(schema) {
|
|
95
|
+
return z.toJSONSchema(schema);
|
|
96
|
+
}
|
|
97
|
+
function summarizeSchemaType(schema) {
|
|
98
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0) {
|
|
99
|
+
return `enum(${schema.enum.map((value) => JSON.stringify(value)).join(', ')})`;
|
|
100
|
+
}
|
|
101
|
+
if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) {
|
|
102
|
+
return schema.anyOf.map(summarizeSchemaType).join(' | ');
|
|
103
|
+
}
|
|
104
|
+
if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) {
|
|
105
|
+
return schema.oneOf.map(summarizeSchemaType).join(' | ');
|
|
106
|
+
}
|
|
107
|
+
if (schema.type === 'array') {
|
|
108
|
+
const itemType = schema.items
|
|
109
|
+
? summarizeSchemaType(schema.items)
|
|
110
|
+
: 'unknown';
|
|
111
|
+
return `array<${itemType}>`;
|
|
112
|
+
}
|
|
113
|
+
if (typeof schema.type === 'string' && schema.type.length > 0) {
|
|
114
|
+
return schema.type;
|
|
115
|
+
}
|
|
116
|
+
return 'unknown';
|
|
117
|
+
}
|
|
118
|
+
function buildSchemaFieldLines(label, schema) {
|
|
119
|
+
if (!schema) {
|
|
120
|
+
return [`<${label}>`, '- none', `</${label}>`];
|
|
121
|
+
}
|
|
122
|
+
const jsonSchema = toJsonSchemaObject(schema);
|
|
123
|
+
const properties = jsonSchema.properties ?? {};
|
|
124
|
+
const required = new Set(jsonSchema.required ?? []);
|
|
125
|
+
const fieldNames = Object.keys(properties);
|
|
126
|
+
if (fieldNames.length === 0) {
|
|
127
|
+
return [`<${label}>`, '- object with no fields', `</${label}>`];
|
|
128
|
+
}
|
|
129
|
+
return [
|
|
130
|
+
`<${label}>`,
|
|
131
|
+
...fieldNames.map((fieldName) => {
|
|
132
|
+
const fieldSchema = properties[fieldName] ?? {};
|
|
133
|
+
const type = summarizeSchemaType(fieldSchema);
|
|
134
|
+
const requiredLabel = required.has(fieldName) ? 'required' : 'optional';
|
|
135
|
+
const description = typeof fieldSchema.description === 'string' &&
|
|
136
|
+
fieldSchema.description.length > 0
|
|
137
|
+
? fieldSchema.description
|
|
138
|
+
: 'No description.';
|
|
139
|
+
return `- ${fieldName} (${type}, ${requiredLabel}): ${description}`;
|
|
140
|
+
}),
|
|
141
|
+
`</${label}>`,
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
function buildProtocolNotes(contract) {
|
|
145
|
+
const notes = [
|
|
146
|
+
'- Protocol failures use JSON-RPC `error`; execution failures use tool result `isError: true`.',
|
|
147
|
+
];
|
|
148
|
+
if (contract.outputSchema) {
|
|
149
|
+
notes.push('- Successful responses include `structuredContent` that must match the declared output schema.');
|
|
150
|
+
}
|
|
151
|
+
if (contract.taskSupport === 'optional') {
|
|
152
|
+
notes.push('- Supports inline execution by default and task mode when durable polling or deferred results are needed.');
|
|
153
|
+
}
|
|
154
|
+
if (contract.taskSupport === 'required') {
|
|
155
|
+
notes.push('- Must run in task mode; callers should poll `tasks/get` and fetch the payload via `tasks/result`.');
|
|
156
|
+
}
|
|
157
|
+
if (contract.taskSupport === 'forbidden') {
|
|
158
|
+
notes.push('- Runs inline only; task augmentation is not supported for this tool.');
|
|
159
|
+
}
|
|
160
|
+
return notes;
|
|
161
|
+
}
|
|
64
162
|
export function buildToolInfo(name) {
|
|
163
|
+
const contract = CONTRACTS_BY_NAME.get(name);
|
|
65
164
|
const entry = ENTRIES[name];
|
|
66
|
-
if (!entry)
|
|
165
|
+
if (!entry || !contract)
|
|
67
166
|
return undefined;
|
|
68
|
-
const lines = [
|
|
167
|
+
const lines = [
|
|
168
|
+
`<tool_info name="${entry.name}">`,
|
|
169
|
+
`## ${entry.name}`,
|
|
170
|
+
'',
|
|
171
|
+
`Title: ${entry.title}`,
|
|
172
|
+
`Description: ${entry.description}`,
|
|
173
|
+
'',
|
|
174
|
+
'<execution>',
|
|
175
|
+
`- taskSupport: ${formatTaskSupportLabel(contract.taskSupport)}`,
|
|
176
|
+
'</execution>',
|
|
177
|
+
'',
|
|
178
|
+
'<annotations>',
|
|
179
|
+
`- readOnlyHint: ${formatAnnotationValue(contract.annotations?.readOnlyHint)}`,
|
|
180
|
+
`- idempotentHint: ${formatAnnotationValue(contract.annotations?.idempotentHint)}`,
|
|
181
|
+
`- destructiveHint: ${formatAnnotationValue(contract.annotations?.destructiveHint)}`,
|
|
182
|
+
`- openWorldHint: ${formatAnnotationValue(contract.annotations?.openWorldHint)}`,
|
|
183
|
+
'</annotations>',
|
|
184
|
+
'',
|
|
185
|
+
...buildSchemaFieldLines('input_fields', contract.inputSchema),
|
|
186
|
+
'',
|
|
187
|
+
...buildSchemaFieldLines('output_fields', contract.outputSchema),
|
|
188
|
+
'',
|
|
189
|
+
'<protocol_notes>',
|
|
190
|
+
...buildProtocolNotes(contract),
|
|
191
|
+
'</protocol_notes>',
|
|
192
|
+
];
|
|
69
193
|
if (entry.annotations && entry.annotations.length > 0) {
|
|
70
|
-
lines.push('',
|
|
194
|
+
lines.push('', `<quick_hints>${entry.annotations.join(' ')}</quick_hints>`);
|
|
71
195
|
}
|
|
72
196
|
if (entry.nuances && entry.nuances.length > 0) {
|
|
73
|
-
lines.push('', '
|
|
197
|
+
lines.push('', '<nuances>');
|
|
74
198
|
for (const nuance of entry.nuances) {
|
|
75
199
|
lines.push(`- ${nuance}`);
|
|
76
200
|
}
|
|
201
|
+
lines.push('</nuances>');
|
|
77
202
|
}
|
|
78
203
|
if (entry.gotchas && entry.gotchas.length > 0) {
|
|
79
|
-
lines.push('', '
|
|
204
|
+
lines.push('', '<gotchas>');
|
|
80
205
|
for (const gotcha of entry.gotchas) {
|
|
81
206
|
lines.push(`- ${gotcha}`);
|
|
82
207
|
}
|
|
208
|
+
lines.push('</gotchas>');
|
|
83
209
|
}
|
|
210
|
+
lines.push('</tool_info>');
|
|
84
211
|
return lines.join('\n');
|
|
85
212
|
}
|
package/dist/resources.js
CHANGED
|
@@ -13,6 +13,8 @@ const TOOL_INFO_TEMPLATE = new ResourceTemplate('internal://tool-info/{name}', {
|
|
|
13
13
|
resources: getToolContracts().map((contract) => ({
|
|
14
14
|
uri: `internal://tool-info/${contract.name}`,
|
|
15
15
|
name: contract.name,
|
|
16
|
+
title: contract.title,
|
|
17
|
+
description: contract.description,
|
|
16
18
|
mimeType: 'text/markdown',
|
|
17
19
|
})),
|
|
18
20
|
}),
|
package/dist/schemas.d.ts
CHANGED
|
@@ -63,8 +63,8 @@ export declare const SearchFilesInputSchema: z.ZodObject<{
|
|
|
63
63
|
sortBy: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
64
64
|
name: "name";
|
|
65
65
|
size: "size";
|
|
66
|
-
path: "path";
|
|
67
66
|
modified: "modified";
|
|
67
|
+
path: "path";
|
|
68
68
|
}>>>;
|
|
69
69
|
maxDepth: z.ZodOptional<z.ZodInt>;
|
|
70
70
|
cursor: z.ZodOptional<z.ZodString>;
|
|
@@ -556,6 +556,8 @@ export declare const EditFileOutputSchema: z.ZodObject<{
|
|
|
556
556
|
ok: z.ZodBoolean;
|
|
557
557
|
path: z.ZodOptional<z.ZodString>;
|
|
558
558
|
appliedEdits: z.ZodOptional<z.ZodNumber>;
|
|
559
|
+
linesAdded: z.ZodOptional<z.ZodNumber>;
|
|
560
|
+
linesRemoved: z.ZodOptional<z.ZodNumber>;
|
|
559
561
|
lineRange: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
|
|
560
562
|
unmatchedEdits: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
561
563
|
diff: z.ZodOptional<z.ZodString>;
|
package/dist/schemas.js
CHANGED
|
@@ -488,6 +488,8 @@ export const EditFileOutputSchema = z.strictObject({
|
|
|
488
488
|
ok: z.boolean(),
|
|
489
489
|
path: z.string().optional(),
|
|
490
490
|
appliedEdits: z.number().optional(),
|
|
491
|
+
linesAdded: z.number().optional().describe('Lines added'),
|
|
492
|
+
linesRemoved: z.number().optional().describe('Lines removed'),
|
|
491
493
|
lineRange: z
|
|
492
494
|
.tuple([z.number(), z.number()])
|
|
493
495
|
.optional()
|
package/dist/server/bootstrap.js
CHANGED
|
@@ -13,7 +13,7 @@ import { createInMemoryResourceStore } from '../lib/resource-store.js';
|
|
|
13
13
|
import { isRecord } from '../lib/utils.js';
|
|
14
14
|
import { registerCompletions } from '../completions.js';
|
|
15
15
|
import { pkgInfo } from '../pkg-info.js';
|
|
16
|
-
import { registerAnalyzePathPrompt, registerCompareFilesPrompt, registerGetHelpPrompt, } from '../prompts.js';
|
|
16
|
+
import { registerAnalyzePathPrompt, registerCompareFilesPrompt, registerGetHelpPrompt, registerGetToolHelpPrompt, } from '../prompts.js';
|
|
17
17
|
import { registerInstructionResource, registerMetricsResource, registerResultResources, registerToolCatalogResource, registerToolInfoResource, registerWorkflowGuideResource, } from '../resources.js';
|
|
18
18
|
import { buildServerInstructions } from '../resources/generated-instructions.js';
|
|
19
19
|
import { registerAllTools } from '../tools.js';
|
|
@@ -174,6 +174,7 @@ export async function createServer(options = {}) {
|
|
|
174
174
|
registerGetHelpPrompt(server, serverInstructions, localIcon);
|
|
175
175
|
registerCompareFilesPrompt(server, localIcon);
|
|
176
176
|
registerAnalyzePathPrompt(server, localIcon);
|
|
177
|
+
registerGetToolHelpPrompt(server, localIcon);
|
|
177
178
|
registerResultResources(server, resourceStore, localIcon);
|
|
178
179
|
registerMetricsResource(server, localIcon);
|
|
179
180
|
registerCompletions(server, serverInstructions);
|
|
@@ -22,7 +22,7 @@ export const APPLY_PATCH_TOOL = {
|
|
|
22
22
|
'Multi-file patches use `path` as base directory; per-file results in `results[]`.',
|
|
23
23
|
],
|
|
24
24
|
gotchas: ['Patch must include valid hunk headers; use `dryRun=true` first.'],
|
|
25
|
-
taskSupport: '
|
|
25
|
+
taskSupport: 'forbidden',
|
|
26
26
|
};
|
|
27
27
|
function assertPatchTargetSizeWithinLimit(filePath, size, maxFileSize) {
|
|
28
28
|
if (size <= maxFileSize)
|
|
@@ -194,9 +194,12 @@ export function registerApplyPatchTool(server, options = {}) {
|
|
|
194
194
|
const sc = result.structuredContent;
|
|
195
195
|
if (!sc.ok)
|
|
196
196
|
return `🛠 patch: ${name} • failed`;
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
const added = sc.linesAdded ?? 0;
|
|
198
|
+
const removed = sc.linesRemoved ?? 0;
|
|
199
|
+
const dry = args.dryRun ? 'dry run — ' : '';
|
|
200
|
+
if (added > 0 || removed > 0)
|
|
201
|
+
return `🛠 patch: ${name} • ${dry} +${added} -${removed}`;
|
|
202
|
+
return `🛠 patch: ${name} • ${dry}no changes`;
|
|
200
203
|
},
|
|
201
204
|
});
|
|
202
205
|
const validatedHandler = withValidatedArgs(ApplyPatchInputSchema, wrappedHandler);
|
package/dist/tools/contract.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export const CREATE_DIRECTORY_TOOL = {
|
|
|
14
14
|
outputSchema: CreateDirectoryOutputSchema,
|
|
15
15
|
annotations: IDEMPOTENT_WRITE_TOOL_ANNOTATIONS,
|
|
16
16
|
nuances: ['Succeeds silently if the directory already exists (idempotent).'],
|
|
17
|
-
taskSupport: '
|
|
17
|
+
taskSupport: 'forbidden',
|
|
18
18
|
};
|
|
19
19
|
export async function handleCreateDirectory(args, signal) {
|
|
20
20
|
const allPaths = [];
|
|
@@ -55,12 +55,12 @@ export function registerCreateDirectoryTool(server, options = {}) {
|
|
|
55
55
|
const name = path.basename(args.path);
|
|
56
56
|
if (result.isError)
|
|
57
57
|
return `🛠 mkdir: ${name} • failed`;
|
|
58
|
-
return `🛠 mkdir: ${name}
|
|
58
|
+
return `🛠 mkdir: ${name}`;
|
|
59
59
|
}
|
|
60
60
|
const count = (args.path ? 1 : 0) + (args.paths?.length ?? 0);
|
|
61
61
|
if (result.isError)
|
|
62
62
|
return `🛠 mkdir: ${count} directories • failed`;
|
|
63
|
-
return `🛠 mkdir: ${count} directories
|
|
63
|
+
return `🛠 mkdir: ${count} directories`;
|
|
64
64
|
},
|
|
65
65
|
});
|
|
66
66
|
const validatedHandler = withValidatedArgs(CreateDirectoryInputSchema, wrappedHandler);
|
|
@@ -17,7 +17,7 @@ export const DELETE_FILE_TOOL = {
|
|
|
17
17
|
'No undo — deletion is permanent.',
|
|
18
18
|
'Non-empty directories require `recursive=true`.',
|
|
19
19
|
],
|
|
20
|
-
taskSupport: '
|
|
20
|
+
taskSupport: 'forbidden',
|
|
21
21
|
};
|
|
22
22
|
async function handleDeleteFile(args, signal) {
|
|
23
23
|
const validPath = await validatePathForWrite(args.path, signal);
|
|
@@ -90,7 +90,7 @@ export function registerDeleteFileTool(server, options = {}) {
|
|
|
90
90
|
const name = path.basename(args.path);
|
|
91
91
|
if (result.isError)
|
|
92
92
|
return `🛠 rm: ${name} • failed`;
|
|
93
|
-
return `🛠 rm: ${name}
|
|
93
|
+
return `🛠 rm: ${name}`;
|
|
94
94
|
},
|
|
95
95
|
});
|
|
96
96
|
const validatedHandler = withValidatedArgs(DeleteFileInputSchema, wrappedHandler);
|
package/dist/tools/diff-files.js
CHANGED
|
@@ -18,7 +18,7 @@ export const DIFF_FILES_TOOL = {
|
|
|
18
18
|
outputSchema: DiffFilesOutputSchema,
|
|
19
19
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
20
20
|
gotchas: ['`isIdentical=true` means no hunks (`@@`) and empty diff.'],
|
|
21
|
-
taskSupport: '
|
|
21
|
+
taskSupport: 'forbidden',
|
|
22
22
|
};
|
|
23
23
|
function computeDiffStats(patch) {
|
|
24
24
|
let linesAdded = 0;
|
|
@@ -119,7 +119,11 @@ export function registerDiffFilesTool(server, options = {}) {
|
|
|
119
119
|
return `🕮 diff: ${n1} ⟷ ${n2} • failed`;
|
|
120
120
|
if (sc.isIdentical)
|
|
121
121
|
return `🕮 diff: ${n1} ⟷ ${n2} • identical`;
|
|
122
|
-
|
|
122
|
+
const added = sc.linesAdded ?? 0;
|
|
123
|
+
const removed = sc.linesRemoved ?? 0;
|
|
124
|
+
if (added > 0 || removed > 0)
|
|
125
|
+
return `🕮 diff: ${n1} ⟷ ${n2} • +${added} -${removed}`;
|
|
126
|
+
return `🕮 diff: ${n1} ⟷ ${n2}`;
|
|
123
127
|
},
|
|
124
128
|
});
|
|
125
129
|
const validatedHandler = withValidatedArgs(DiffFilesInputSchema, wrappedHandler);
|