@j0hanz/filesystem-mcp 2.2.0 → 2.4.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 +38 -32
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +21 -40
- package/dist/cli-help.js.map +1 -1
- package/dist/cli.d.ts +0 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +24 -46
- package/dist/cli.js.map +1 -1
- package/dist/core/concurrency.d.ts +5 -17
- package/dist/core/concurrency.d.ts.map +1 -1
- package/dist/core/concurrency.js +32 -40
- package/dist/core/concurrency.js.map +1 -1
- package/dist/core/config.d.ts +2 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/cursor.d.ts +1 -12
- package/dist/core/cursor.d.ts.map +1 -1
- package/dist/core/cursor.js +2 -12
- package/dist/core/cursor.js.map +1 -1
- package/dist/core/errors.d.ts +6 -11
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +62 -117
- package/dist/core/errors.js.map +1 -1
- package/dist/core/file-uri.d.ts +7 -3
- package/dist/core/file-uri.d.ts.map +1 -1
- package/dist/core/file-uri.js +8 -4
- package/dist/core/file-uri.js.map +1 -1
- package/dist/core/fmt.d.ts +0 -11
- package/dist/core/fmt.d.ts.map +1 -1
- package/dist/core/fmt.js +1 -19
- package/dist/core/fmt.js.map +1 -1
- package/dist/core/fs.d.ts +34 -4
- package/dist/core/fs.d.ts.map +1 -1
- package/dist/core/fs.js +151 -12
- package/dist/core/fs.js.map +1 -1
- package/dist/core/glob.d.ts +1 -2
- package/dist/core/glob.d.ts.map +1 -1
- package/dist/core/glob.js +12 -28
- package/dist/core/glob.js.map +1 -1
- package/dist/core/input-required.d.ts +19 -29
- package/dist/core/input-required.d.ts.map +1 -1
- package/dist/core/input-required.js +58 -73
- package/dist/core/input-required.js.map +1 -1
- package/dist/core/observability.d.ts +9 -0
- package/dist/core/observability.d.ts.map +1 -1
- package/dist/core/observability.js +13 -2
- package/dist/core/observability.js.map +1 -1
- package/dist/core/path-completer.d.ts +6 -9
- package/dist/core/path-completer.d.ts.map +1 -1
- package/dist/core/path-completer.js +28 -55
- package/dist/core/path-completer.js.map +1 -1
- package/dist/core/path-discovery.d.ts.map +1 -1
- package/dist/core/path-discovery.js +3 -4
- package/dist/core/path-discovery.js.map +1 -1
- package/dist/core/path-utils.d.ts +28 -0
- package/dist/core/path-utils.d.ts.map +1 -1
- package/dist/core/path-utils.js +57 -2
- package/dist/core/path-utils.js.map +1 -1
- package/dist/core/path.d.ts +3 -8
- package/dist/core/path.d.ts.map +1 -1
- package/dist/core/path.js +8 -9
- package/dist/core/path.js.map +1 -1
- package/dist/core/read.d.ts +3 -22
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +46 -96
- package/dist/core/read.js.map +1 -1
- package/dist/core/schema.d.ts +8 -9
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +11 -14
- package/dist/core/schema.js.map +1 -1
- package/dist/core/search.d.ts +6 -1
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +21 -23
- package/dist/core/search.js.map +1 -1
- package/dist/core/sensitive.d.ts +4 -2
- package/dist/core/sensitive.d.ts.map +1 -1
- package/dist/core/sensitive.js +262 -45
- package/dist/core/sensitive.js.map +1 -1
- package/dist/core/store.d.ts +41 -16
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/store.js +147 -82
- package/dist/core/store.js.map +1 -1
- package/dist/core/util.d.ts +3 -2
- package/dist/core/util.d.ts.map +1 -1
- package/dist/core/util.js +5 -6
- package/dist/core/util.js.map +1 -1
- package/dist/core/watcher-registry.d.ts +1 -1
- package/dist/core/watcher-registry.d.ts.map +1 -1
- package/dist/core/watcher-registry.js +9 -5
- package/dist/core/watcher-registry.js.map +1 -1
- package/dist/index.js +5 -6
- package/dist/index.js.map +1 -1
- package/dist/instructions.js +2 -2
- package/dist/instructions.js.map +1 -1
- package/dist/prompts.js +1 -1
- package/dist/prompts.js.map +1 -1
- package/dist/resources.d.ts +11 -19
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +51 -62
- package/dist/resources.js.map +1 -1
- package/dist/server.d.ts +4 -5
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +15 -11
- package/dist/server.js.map +1 -1
- package/dist/tools/batch.d.ts +2 -2
- package/dist/tools/batch.js +3 -3
- package/dist/tools/batch.js.map +1 -1
- package/dist/tools/create.d.ts +1 -1
- package/dist/tools/create.d.ts.map +1 -1
- package/dist/tools/create.js +168 -17
- package/dist/tools/create.js.map +1 -1
- package/dist/tools/define.d.ts +15 -9
- package/dist/tools/define.d.ts.map +1 -1
- package/dist/tools/define.js +48 -94
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/delete.d.ts +1 -1
- package/dist/tools/delete.d.ts.map +1 -1
- package/dist/tools/delete.js +23 -23
- package/dist/tools/delete.js.map +1 -1
- package/dist/tools/diff.d.ts +1 -1
- package/dist/tools/diff.d.ts.map +1 -1
- package/dist/tools/diff.js +7 -37
- package/dist/tools/diff.js.map +1 -1
- package/dist/tools/edit.d.ts +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +101 -92
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/find-files.d.ts +1 -1
- package/dist/tools/find-files.d.ts.map +1 -1
- package/dist/tools/find-files.js +18 -37
- package/dist/tools/find-files.js.map +1 -1
- package/dist/tools/index.d.ts +10 -10
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +14 -13
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/list-roots.d.ts +1 -1
- package/dist/tools/list-roots.js +3 -3
- package/dist/tools/list-roots.js.map +1 -1
- package/dist/tools/list.d.ts +1 -1
- package/dist/tools/list.d.ts.map +1 -1
- package/dist/tools/list.js +23 -29
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/move.d.ts +1 -1
- package/dist/tools/move.d.ts.map +1 -1
- package/dist/tools/move.js +31 -45
- package/dist/tools/move.js.map +1 -1
- package/dist/tools/patch.d.ts +1 -1
- package/dist/tools/patch.js +7 -7
- package/dist/tools/patch.js.map +1 -1
- package/dist/tools/progress.d.ts +17 -28
- package/dist/tools/progress.d.ts.map +1 -1
- package/dist/tools/progress.js +42 -94
- package/dist/tools/progress.js.map +1 -1
- package/dist/tools/read.d.ts +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +10 -12
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/replace-text.d.ts +1 -1
- package/dist/tools/replace-text.d.ts.map +1 -1
- package/dist/tools/replace-text.js +32 -49
- package/dist/tools/replace-text.js.map +1 -1
- package/dist/tools/search-text.d.ts +1 -1
- package/dist/tools/search-text.d.ts.map +1 -1
- package/dist/tools/search-text.js +82 -45
- package/dist/tools/search-text.js.map +1 -1
- package/dist/tools/stat.d.ts +1 -1
- package/dist/tools/stat.d.ts.map +1 -1
- package/dist/tools/stat.js +9 -29
- package/dist/tools/stat.js.map +1 -1
- package/dist/transport/http-policy.js +4 -4
- package/dist/transport/http-policy.js.map +1 -1
- package/dist/transport/http.d.ts +2 -2
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +21 -18
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/shared.d.ts +2 -2
- package/dist/transport/shared.js +2 -2
- package/dist/transport/shared.js.map +1 -1
- package/dist/transport/stdio.d.ts +4 -3
- package/dist/transport/stdio.d.ts.map +1 -1
- package/dist/transport/stdio.js +7 -6
- package/dist/transport/stdio.js.map +1 -1
- package/dist/transport.d.ts +2 -2
- package/dist/transport.js +2 -2
- package/dist/transport.js.map +1 -1
- package/package.json +6 -13
- package/dist/core/page-store.d.ts +0 -41
- package/dist/core/page-store.d.ts.map +0 -1
- package/dist/core/page-store.js +0 -74
- package/dist/core/page-store.js.map +0 -1
- package/dist/core/primitives.d.ts +0 -34
- package/dist/core/primitives.d.ts.map +0 -1
- package/dist/core/primitives.js +0 -63
- package/dist/core/primitives.js.map +0 -1
package/dist/tools/create.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { basename, dirname } from 'node:path';
|
|
2
2
|
import * as z from 'zod/v4';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
3
|
+
import { processInParallel } from "../core/concurrency.js";
|
|
4
|
+
import { ErrorCode, formatUnknownErrorMessage, FsError, rethrowIfAborted } from "../core/errors.js";
|
|
5
|
+
import { buildFileResourceLink, buildFileResourceUri, buildWrittenFileMeta, } from "../core/file-uri.js";
|
|
6
|
+
import { countFileLines, destExists } from "../core/fs.js";
|
|
7
|
+
import { confirmKey, describeRefusal, pendingRoundTrip, readAcceptedChoice, } from "../core/input-required.js";
|
|
8
|
+
import { detectMimeFromContent, MIME_SAMPLE_SIZE } from "../core/mime.js";
|
|
9
|
+
import { FileKind, IsoDateTime, NonNegInt, PathFailureSchema, RequiredPath, } from "../core/schema.js";
|
|
10
|
+
import { getMaxTextFileSize, PARALLEL_CONCURRENCY } from "../core/util.js";
|
|
11
|
+
import { isTotalFailure, runOverPaths } from "./batch.js";
|
|
12
|
+
import { defineTool } from "./define.js";
|
|
13
|
+
const EPOCH = new Date(0);
|
|
9
14
|
const CreateFileItemSchema = z.strictObject({
|
|
10
15
|
path: RequiredPath.describe('Absolute path where the file will be created'),
|
|
11
16
|
content: z
|
|
@@ -14,6 +19,14 @@ const CreateFileItemSchema = z.strictObject({
|
|
|
14
19
|
message: 'Content exceeds maximum allowed text file size',
|
|
15
20
|
})
|
|
16
21
|
.describe('Text content to write, verbatim.'),
|
|
22
|
+
append: z
|
|
23
|
+
.boolean()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('Append the content to the end of the file instead of overwriting; creates the file if it does not exist'),
|
|
26
|
+
overwrite: z
|
|
27
|
+
.boolean()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('Replace an existing file without asking the user; without it an existing file prompts for confirmation'),
|
|
17
30
|
});
|
|
18
31
|
const CreateFileResultSchema = z.strictObject({
|
|
19
32
|
path: z.string().describe('Resolved absolute path of the created file'),
|
|
@@ -23,7 +36,8 @@ const CreateFileResultSchema = z.strictObject({
|
|
|
23
36
|
kind: FileKind.describe('Broad file kind: text, binary, image, audio, or pdf'),
|
|
24
37
|
resourceUri: z
|
|
25
38
|
.string()
|
|
26
|
-
.
|
|
39
|
+
.optional()
|
|
40
|
+
.describe('Resource URI pointing to the created file content in the resource store; omitted when the resulting file exceeds the text-size cap, which the store would reject'),
|
|
27
41
|
created: IsoDateTime.describe('File creation timestamp (ISO 8601 UTC)'),
|
|
28
42
|
modified: IsoDateTime.describe('File last-modification timestamp (ISO 8601 UTC)'),
|
|
29
43
|
});
|
|
@@ -40,13 +54,19 @@ const CreateOutputSchema = z.strictObject({
|
|
|
40
54
|
.array(PathFailureSchema)
|
|
41
55
|
.optional()
|
|
42
56
|
.describe('Files that failed to create with per-file error details'),
|
|
57
|
+
skipped: z
|
|
58
|
+
.array(z.string())
|
|
59
|
+
.optional()
|
|
60
|
+
.describe('Paths left untouched because the user chose Skip'),
|
|
43
61
|
});
|
|
44
62
|
export const CREATE = defineTool({
|
|
45
63
|
name: 'create',
|
|
46
64
|
title: 'Create Files',
|
|
47
|
-
description: 'Create one or more files (max 100),
|
|
65
|
+
description: 'Create one or more files (max 100), creating parent directories as needed. ' +
|
|
48
66
|
'Pass files: [{ path, content }] — there is no single-path form. ' +
|
|
49
|
-
'
|
|
67
|
+
'An existing file prompts the user to confirm the overwrite, so the call returns without writing ' +
|
|
68
|
+
'anything until that confirmation comes back; set overwrite: true on an entry to replace it without the prompt. ' +
|
|
69
|
+
'Set append: true on an entry to add to the end of an existing file (created if missing) instead of overwriting.',
|
|
50
70
|
input: CreateInputSchema,
|
|
51
71
|
output: CreateOutputSchema,
|
|
52
72
|
annotations: {
|
|
@@ -57,15 +77,140 @@ export const CREATE = defineTool({
|
|
|
57
77
|
},
|
|
58
78
|
accessPaths: (args) => args.files.map((f) => f.path),
|
|
59
79
|
run: async (args, ctx) => {
|
|
80
|
+
// Phase 1 (no mutation): which entries would replace a file that exists
|
|
81
|
+
// right now? Those need the user's word before anything is written (R14).
|
|
82
|
+
// append never destroys, and overwrite: true is that word given up front.
|
|
83
|
+
// A path that fails validation here is left for runOverPaths to report.
|
|
84
|
+
// A file that appears between rounds needs no re-stat before the write:
|
|
85
|
+
// the retry re-plans, and the grown pending set fails the R9 check.
|
|
86
|
+
const { results: planned } = await processInParallel(args.files, async (entry) => {
|
|
87
|
+
if (entry.append || entry.overwrite)
|
|
88
|
+
return undefined;
|
|
89
|
+
try {
|
|
90
|
+
const validPath = await ctx.fs.pathGuard.validatePathForWrite(entry.path);
|
|
91
|
+
return (await destExists(ctx.fs, validPath, 'create')) ? validPath : undefined;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
rethrowIfAborted(error);
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
}, PARALLEL_CONCURRENCY, ctx.signal);
|
|
98
|
+
const pendingByPath = new Map();
|
|
99
|
+
for (const { index, value } of planned) {
|
|
100
|
+
const requested = args.files[index]?.path;
|
|
101
|
+
if (value && requested)
|
|
102
|
+
pendingByPath.set(requested, value);
|
|
103
|
+
}
|
|
104
|
+
const pendingSorted = [...new Set(pendingByPath.values())].sort();
|
|
105
|
+
if (pendingSorted.length > 0) {
|
|
106
|
+
// Round 1 returns input_required; a retry whose verified state does not
|
|
107
|
+
// bind this overwrite set throws (R9) via `pendingRoundTrip`.
|
|
108
|
+
const round = await pendingRoundTrip({
|
|
109
|
+
op: 'create',
|
|
110
|
+
pending: pendingSorted,
|
|
111
|
+
requestState: ctx.requestState,
|
|
112
|
+
clientCapabilities: ctx.clientCapabilities,
|
|
113
|
+
serverCtx: ctx.serverCtx,
|
|
114
|
+
buildInputs: (paths) => paths.map((target, i) => ({
|
|
115
|
+
key: confirmKey(i),
|
|
116
|
+
message: `"${target}" already exists. Overwrite it?`,
|
|
117
|
+
choices: ['overwrite', 'skip'],
|
|
118
|
+
})),
|
|
119
|
+
});
|
|
120
|
+
if (round !== undefined)
|
|
121
|
+
return round;
|
|
122
|
+
}
|
|
60
123
|
const batch = await runOverPaths({ files: args.files }, ctx, async ({ path, override }) => {
|
|
61
124
|
const content = override?.content ?? '';
|
|
125
|
+
const pendingPath = pendingByPath.get(path);
|
|
126
|
+
if (pendingPath !== undefined) {
|
|
127
|
+
const key = confirmKey(pendingSorted.indexOf(pendingPath));
|
|
128
|
+
const choice = readAcceptedChoice(ctx.inputResponses, key);
|
|
129
|
+
if (choice === 'skip')
|
|
130
|
+
return { skipped: path };
|
|
131
|
+
if (choice !== 'overwrite') {
|
|
132
|
+
throw new FsError(ErrorCode.CANCELLED, `create cancelled: overwrite of "${path}" was ${describeRefusal(ctx.inputResponses, key)}`, path);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
62
135
|
await ctx.fs.mkdir(dirname(path), { recursive: true });
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
136
|
+
// Overwrite: file content == `content`, so content-derived meta is
|
|
137
|
+
// exact and the atomic temp+rename write protects the existing mode.
|
|
138
|
+
// Append: the resulting file is everything that was there plus
|
|
139
|
+
// `content`, so size/created/modified come from a real post-append
|
|
140
|
+
// stat, MIME from the resulting file's leading bytes, and the line
|
|
141
|
+
// count streams the file — reading a multi-GB log back whole is the
|
|
142
|
+
// round-trip append exists to avoid.
|
|
143
|
+
let validPath;
|
|
144
|
+
let meta;
|
|
145
|
+
let created;
|
|
146
|
+
let modified;
|
|
147
|
+
if (override?.append) {
|
|
148
|
+
const appended = await ctx.fs.appendFile(path, content, {
|
|
149
|
+
encoding: 'utf-8',
|
|
150
|
+
signal: ctx.signal,
|
|
151
|
+
});
|
|
152
|
+
validPath = appended.validPath;
|
|
153
|
+
// The append is the commit point: the bytes are on disk, so
|
|
154
|
+
// everything below is best-effort RESULT METADATA, not part of the
|
|
155
|
+
// write. It runs unwired to ctx.signal, and a metadata failure
|
|
156
|
+
// degrades the result instead of failing it — either would report
|
|
157
|
+
// the append as failed and a client retry would append twice. The
|
|
158
|
+
// stat covers the committed validPath, never the input path: a
|
|
159
|
+
// symlink there could have been swapped between write and stat,
|
|
160
|
+
// and the result must describe the file the bytes landed in.
|
|
161
|
+
let stats;
|
|
162
|
+
let mimeType;
|
|
163
|
+
let kind;
|
|
164
|
+
let lineCount = 0;
|
|
165
|
+
try {
|
|
166
|
+
stats = (await ctx.fs.stat(appended.validPath)).stats;
|
|
167
|
+
// Sniff the resulting file's leading bytes, not the appended
|
|
168
|
+
// chunk: text appended to a binary file is still a binary file.
|
|
169
|
+
const sample = await ctx.fs.readLeadingSample(appended.validPath, MIME_SAMPLE_SIZE);
|
|
170
|
+
const mimeInfo = detectMimeFromContent(appended.validPath, sample);
|
|
171
|
+
mimeType = mimeInfo.mimeType;
|
|
172
|
+
kind = mimeInfo.kind;
|
|
173
|
+
lineCount = await countFileLines(appended.validPath);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
ctx.log?.('warning', `create append: result metadata degraded for ${appended.validPath}: ${formatUnknownErrorMessage(error)}`, 'create');
|
|
177
|
+
// A POSIX mode-0222 file appends fine but cannot be opened 'r';
|
|
178
|
+
// fall back to the chunk for MIME (an approximation) and report
|
|
179
|
+
// the line count as unknown-as-zero rather than fail the append.
|
|
180
|
+
const mimeInfo = detectMimeFromContent(appended.validPath, content);
|
|
181
|
+
mimeType = mimeInfo.mimeType;
|
|
182
|
+
kind = mimeInfo.kind;
|
|
183
|
+
}
|
|
184
|
+
// The resource store serves this URI via readRaw, which rejects
|
|
185
|
+
// files over the text-size cap with TOO_LARGE — never advertise a
|
|
186
|
+
// link the store deterministically cannot serve. A failed stat
|
|
187
|
+
// leaves the size unknown, so nothing is advertised either.
|
|
188
|
+
const servable = stats !== undefined && stats.size <= getMaxTextFileSize();
|
|
189
|
+
const size = stats?.size ?? 0;
|
|
190
|
+
meta = {
|
|
191
|
+
size,
|
|
192
|
+
lineCount,
|
|
193
|
+
mimeType,
|
|
194
|
+
kind,
|
|
195
|
+
resourceUri: servable ? buildFileResourceUri(appended.validPath) : undefined,
|
|
196
|
+
resourceLink: servable && ctx.resourceStore
|
|
197
|
+
? buildFileResourceLink(appended.validPath, mimeType, size)
|
|
198
|
+
: undefined,
|
|
199
|
+
};
|
|
200
|
+
created = (stats?.birthtime ?? EPOCH).toISOString();
|
|
201
|
+
modified = (stats?.mtime ?? EPOCH).toISOString();
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
const written = await ctx.fs.writeFile(path, content, {
|
|
205
|
+
encoding: 'utf-8',
|
|
206
|
+
signal: ctx.signal,
|
|
207
|
+
});
|
|
208
|
+
validPath = written.validPath;
|
|
209
|
+
const fileStats = (await ctx.fs.stat(path, { signal: ctx.signal })).stats;
|
|
210
|
+
created = fileStats.birthtime.toISOString();
|
|
211
|
+
modified = fileStats.mtime.toISOString();
|
|
212
|
+
meta = buildWrittenFileMeta(written.validPath, content, ctx.resourceStore);
|
|
213
|
+
}
|
|
69
214
|
const file = {
|
|
70
215
|
path: validPath,
|
|
71
216
|
size: meta.size,
|
|
@@ -73,19 +218,24 @@ export const CREATE = defineTool({
|
|
|
73
218
|
mimeType: meta.mimeType,
|
|
74
219
|
kind: meta.kind,
|
|
75
220
|
resourceUri: meta.resourceUri,
|
|
76
|
-
created
|
|
77
|
-
modified
|
|
221
|
+
created,
|
|
222
|
+
modified,
|
|
78
223
|
};
|
|
79
224
|
return meta.resourceLink ? { file, resourceLink: meta.resourceLink } : { file };
|
|
80
225
|
}, { defaultErrorCode: ErrorCode.UNKNOWN });
|
|
81
226
|
const results = [];
|
|
82
227
|
const failures = [];
|
|
228
|
+
const skipped = [];
|
|
83
229
|
const links = [];
|
|
84
230
|
for (const r of batch.results) {
|
|
85
231
|
if ('error' in r) {
|
|
86
232
|
failures.push({ path: r.path, error: r.error });
|
|
87
233
|
continue;
|
|
88
234
|
}
|
|
235
|
+
if ('skipped' in r.value) {
|
|
236
|
+
skipped.push(r.value.skipped);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
89
239
|
results.push(r.value.file);
|
|
90
240
|
if (r.value.resourceLink)
|
|
91
241
|
links.push(r.value.resourceLink);
|
|
@@ -93,6 +243,7 @@ export const CREATE = defineTool({
|
|
|
93
243
|
const structured = {
|
|
94
244
|
files: results,
|
|
95
245
|
...(failures.length > 0 ? { failures } : {}),
|
|
246
|
+
...(skipped.length > 0 ? { skipped } : {}),
|
|
96
247
|
};
|
|
97
248
|
// No `text` on purpose. The one-line roster this used to build named the
|
|
98
249
|
// paths and dropped every per-file size, line count and error the caller
|
package/dist/tools/create.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../src/tools/create.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE9C,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAE5B,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EACL,QAAQ,EACR,WAAW,EACX,SAAS,EACT,iBAAiB,EACjB,YAAY,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,oBAAoB,GAAG,CAAC,CAAC,YAAY,CAAC;IAC1C,IAAI,EAAE,YAAY,CAAC,QAAQ,CAAC,8CAA8C,CAAC;IAC3E,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,IAAI,kBAAkB,EAAE,EAAE;QACnD,OAAO,EAAE,gDAAgD;KAC1D,CAAC;SACD,QAAQ,CAAC,kCAAkC,CAAC;CAChD,CAAC,CAAC;AAEH,MAAM,sBAAsB,GAAG,CAAC,CAAC,YAAY,CAAC;IAC5C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,4CAA4C,CAAC;IACvE,IAAI,EAAE,SAAS,CAAC,QAAQ,CAAC,kCAAkC,CAAC;IAC5D,SAAS,EAAE,SAAS,CAAC,QAAQ,CAAC,qCAAqC,CAAC;IACpE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gCAAgC,CAAC;IAC/D,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,qDAAqD,CAAC;IAC9E,WAAW,EAAE,CAAC;SACX,MAAM,EAAE;SACR,QAAQ,CAAC,yEAAyE,CAAC;IACtF,OAAO,EAAE,WAAW,CAAC,QAAQ,CAAC,wCAAwC,CAAC;IACvE,QAAQ,EAAE,WAAW,CAAC,QAAQ,CAAC,iDAAiD,CAAC;CAClF,CAAC,CAAC;AAEH,MAAM,iBAAiB,GAAG,CAAC,CAAC,YAAY,CAAC;IACvC,KAAK,EAAE,CAAC;SACL,KAAK,CAAC,oBAAoB,CAAC;SAC3B,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,GAAG,CAAC;SACR,QAAQ,CAAC,yEAAyE,CAAC;CACvF,CAAC,CAAC;AAIH,MAAM,kBAAkB,GAAG,CAAC,CAAC,YAAY,CAAC;IACxC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC,QAAQ,CAAC,4BAA4B,CAAC;IAC7E,QAAQ,EAAE,CAAC;SACR,KAAK,CAAC,iBAAiB,CAAC;SACxB,QAAQ,EAAE;SACV,QAAQ,CAAC,yDAAyD,CAAC;CACvE,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,MAAM,GAAG,UAAU,CAAC;IAC/B,IAAI,EAAE,QAAQ;IACd,KAAK,EAAE,cAAc;IACrB,WAAW,EACT,gHAAgH;QAChH,kEAAkE;QAClE,2FAA2F;IAC7F,KAAK,EAAE,iBAAiB;IACxB,MAAM,EAAE,kBAAkB;IAC1B,WAAW,EAAE;QACX,YAAY,EAAE,KAAK;QACnB,cAAc,EAAE,KAAK;QACrB,eAAe,EAAE,IAAI;QACrB,aAAa,EAAE,KAAK;KACrB;IACD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACpD,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;QACvB,MAAM,KAAK,GAAG,MAAM,YAAY,CAI9B,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,EACrB,GAAG,EACH,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE;YAC3B,MAAM,OAAO,GAAG,QAAQ,EAAE,OAAO,IAAI,EAAE,CAAC;YAExC,MAAM,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAEvD,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE;gBAC1D,QAAQ,EAAE,OAAO;gBACjB,MAAM,EAAE,GAAG,CAAC,MAAM;aACnB,CAAC,CAAC;YAEH,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YAC7E,MAAM,IAAI,GAAG,oBAAoB,CAAC,SAAS,EAAE,OAAO,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC;YAEzE,MAAM,IAAI,GAAqB;gBAC7B,IAAI,EAAE,SAAS;gBACf,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,OAAO,EAAE,SAAS,CAAC,SAAS,CAAC,WAAW,EAAE;gBAC1C,QAAQ,EAAE,SAAS,CAAC,KAAK,CAAC,WAAW,EAAE;aACxC,CAAC;YAEF,OAAO,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QAClF,CAAC,EACD,EAAE,gBAAgB,EAAE,SAAS,CAAC,OAAO,EAAE,CACxC,CAAC;QAEF,MAAM,OAAO,GAAuB,EAAE,CAAC;QACvC,MAAM,QAAQ,GAAwB,EAAE,CAAC;QACzC,MAAM,KAAK,GAAmB,EAAE,CAAC;QACjC,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YAC9B,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;gBACjB,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;gBAChD,SAAS;YACX,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,IAAI,CAAC,CAAC,KAAK,CAAC,YAAY;gBAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAC7D,CAAC;QAED,MAAM,UAAU,GAAG;YACjB,KAAK,EAAE,OAAO;YACd,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC7C,CAAC;QACF,yEAAyE;QACzE,yEAAyE;QACzE,mEAAmE;QACnE,oEAAoE;QACpE,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QAClF,CAAC;QAED,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;IAChE,CAAC;IACD,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACnB,KAAK,EAAE,QAAQ;QACf,OAAO,EACL,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;YACrB,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC;YACrC,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ;KAC3C,CAAC;IACF,gBAAgB,EAAE,SAAS,CAAC,OAAO;CACpC,CAAC,CAAC","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { basename, dirname } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport { ErrorCode } from '../core/errors.js';\nimport { buildWrittenFileMeta } from '../core/file-uri.js';\nimport {\n FileKind,\n IsoDateTime,\n NonNegInt,\n PathFailureSchema,\n RequiredPath,\n} from '../core/schema.js';\nimport { getMaxTextFileSize } from '../core/util.js';\nimport { isTotalFailure, runOverPaths } from './batch.js';\nimport { defineTool } from './define.js';\n\nconst CreateFileItemSchema = z.strictObject({\n path: RequiredPath.describe('Absolute path where the file will be created'),\n content: z\n .string()\n .refine((val) => val.length <= getMaxTextFileSize(), {\n message: 'Content exceeds maximum allowed text file size',\n })\n .describe('Text content to write, verbatim.'),\n});\n\nconst CreateFileResultSchema = z.strictObject({\n path: z.string().describe('Resolved absolute path of the created file'),\n size: NonNegInt.describe('File size in bytes after writing'),\n lineCount: NonNegInt.describe('Number of lines in the written file'),\n mimeType: z.string().describe('Detected MIME type of the file'),\n kind: FileKind.describe('Broad file kind: text, binary, image, audio, or pdf'),\n resourceUri: z\n .string()\n .describe('Resource URI pointing to the created file content in the resource store'),\n created: IsoDateTime.describe('File creation timestamp (ISO 8601 UTC)'),\n modified: IsoDateTime.describe('File last-modification timestamp (ISO 8601 UTC)'),\n});\n\nconst CreateInputSchema = z.strictObject({\n files: z\n .array(CreateFileItemSchema)\n .min(1)\n .max(100)\n .describe('List of files to create (max 100); each entry requires path and content'),\n});\n\ntype CreateFailureItem = z.infer<typeof PathFailureSchema>;\n\nconst CreateOutputSchema = z.strictObject({\n files: z.array(CreateFileResultSchema).describe('Successfully created files'),\n failures: z\n .array(PathFailureSchema)\n .optional()\n .describe('Files that failed to create with per-file error details'),\n});\n\ntype CreateFileResult = z.infer<typeof CreateFileResultSchema>;\n\nexport const CREATE = defineTool({\n name: 'create',\n title: 'Create Files',\n description:\n 'Create one or more files (max 100), writing or overwriting content and creating parent directories as needed. ' +\n 'Pass files: [{ path, content }] — there is no single-path form. ' +\n 'Silently overwrites existing files — read first if you need to preserve existing content.',\n input: CreateInputSchema,\n output: CreateOutputSchema,\n annotations: {\n readOnlyHint: false,\n idempotentHint: false,\n destructiveHint: true,\n openWorldHint: false,\n },\n accessPaths: (args) => args.files.map((f) => f.path),\n run: async (args, ctx) => {\n const batch = await runOverPaths<\n { content: string },\n { file: CreateFileResult; resourceLink?: ContentBlock }\n >(\n { files: args.files },\n ctx,\n async ({ path, override }) => {\n const content = override?.content ?? '';\n\n await ctx.fs.mkdir(dirname(path), { recursive: true });\n\n const { validPath } = await ctx.fs.writeFile(path, content, {\n encoding: 'utf-8',\n signal: ctx.signal,\n });\n\n const { stats: fileStats } = await ctx.fs.stat(path, { signal: ctx.signal });\n const meta = buildWrittenFileMeta(validPath, content, ctx.resourceStore);\n\n const file: CreateFileResult = {\n path: validPath,\n size: meta.size,\n lineCount: meta.lineCount,\n mimeType: meta.mimeType,\n kind: meta.kind,\n resourceUri: meta.resourceUri,\n created: fileStats.birthtime.toISOString(),\n modified: fileStats.mtime.toISOString(),\n };\n\n return meta.resourceLink ? { file, resourceLink: meta.resourceLink } : { file };\n },\n { defaultErrorCode: ErrorCode.UNKNOWN },\n );\n\n const results: CreateFileResult[] = [];\n const failures: CreateFailureItem[] = [];\n const links: ContentBlock[] = [];\n for (const r of batch.results) {\n if ('error' in r) {\n failures.push({ path: r.path, error: r.error });\n continue;\n }\n results.push(r.value.file);\n if (r.value.resourceLink) links.push(r.value.resourceLink);\n }\n\n const structured = {\n files: results,\n ...(failures.length > 0 ? { failures } : {}),\n };\n // No `text` on purpose. The one-line roster this used to build named the\n // paths and dropped every per-file size, line count and error the caller\n // asked `create` for. Supplying no text makes this a data tool, so\n // `define.ts` renders the JSON and keeps it in `structuredContent`.\n if (links.length > 0) {\n return { structured, resources: links, isError: isTotalFailure(batch.summary) };\n }\n\n return { structured, isError: isTotalFailure(batch.summary) };\n },\n progress: (args) => ({\n label: 'Create',\n subject:\n args.files.length === 1\n ? basename(args.files[0]?.path ?? '')\n : `${String(args.files.length)} files`,\n }),\n defaultErrorCode: ErrorCode.UNKNOWN,\n});\n"]}
|
|
1
|
+
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../src/tools/create.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { basename, dirname } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport { processInParallel } from '../core/concurrency.ts';\nimport { ErrorCode, formatUnknownErrorMessage, FsError, rethrowIfAborted } from '../core/errors.ts';\nimport {\n buildFileResourceLink,\n buildFileResourceUri,\n buildWrittenFileMeta,\n type WrittenFileMeta,\n} from '../core/file-uri.ts';\nimport { countFileLines, destExists, type Stats } from '../core/fs.ts';\nimport {\n confirmKey,\n describeRefusal,\n pendingRoundTrip,\n readAcceptedChoice,\n} from '../core/input-required.ts';\nimport { detectMimeFromContent, MIME_SAMPLE_SIZE } from '../core/mime.ts';\nimport {\n FileKind,\n IsoDateTime,\n NonNegInt,\n PathFailureSchema,\n RequiredPath,\n} from '../core/schema.ts';\nimport { getMaxTextFileSize, PARALLEL_CONCURRENCY } from '../core/util.ts';\nimport { isTotalFailure, runOverPaths } from './batch.ts';\nimport { defineTool } from './define.ts';\n\nconst EPOCH = new Date(0);\n\nconst CreateFileItemSchema = z.strictObject({\n path: RequiredPath.describe('Absolute path where the file will be created'),\n content: z\n .string()\n .refine((val) => val.length <= getMaxTextFileSize(), {\n message: 'Content exceeds maximum allowed text file size',\n })\n .describe('Text content to write, verbatim.'),\n append: z\n .boolean()\n .optional()\n .describe(\n 'Append the content to the end of the file instead of overwriting; creates the file if it does not exist',\n ),\n overwrite: z\n .boolean()\n .optional()\n .describe(\n 'Replace an existing file without asking the user; without it an existing file prompts for confirmation',\n ),\n});\n\nconst CreateFileResultSchema = z.strictObject({\n path: z.string().describe('Resolved absolute path of the created file'),\n size: NonNegInt.describe('File size in bytes after writing'),\n lineCount: NonNegInt.describe('Number of lines in the written file'),\n mimeType: z.string().describe('Detected MIME type of the file'),\n kind: FileKind.describe('Broad file kind: text, binary, image, audio, or pdf'),\n resourceUri: z\n .string()\n .optional()\n .describe(\n 'Resource URI pointing to the created file content in the resource store; omitted when the resulting file exceeds the text-size cap, which the store would reject',\n ),\n created: IsoDateTime.describe('File creation timestamp (ISO 8601 UTC)'),\n modified: IsoDateTime.describe('File last-modification timestamp (ISO 8601 UTC)'),\n});\n\nconst CreateInputSchema = z.strictObject({\n files: z\n .array(CreateFileItemSchema)\n .min(1)\n .max(100)\n .describe('List of files to create (max 100); each entry requires path and content'),\n});\n\ntype CreateFailureItem = z.infer<typeof PathFailureSchema>;\n\nconst CreateOutputSchema = z.strictObject({\n files: z.array(CreateFileResultSchema).describe('Successfully created files'),\n failures: z\n .array(PathFailureSchema)\n .optional()\n .describe('Files that failed to create with per-file error details'),\n skipped: z\n .array(z.string())\n .optional()\n .describe('Paths left untouched because the user chose Skip'),\n});\n\ntype CreateFileResult = z.infer<typeof CreateFileResultSchema>;\n\nexport const CREATE = defineTool({\n name: 'create',\n title: 'Create Files',\n description:\n 'Create one or more files (max 100), creating parent directories as needed. ' +\n 'Pass files: [{ path, content }] — there is no single-path form. ' +\n 'An existing file prompts the user to confirm the overwrite, so the call returns without writing ' +\n 'anything until that confirmation comes back; set overwrite: true on an entry to replace it without the prompt. ' +\n 'Set append: true on an entry to add to the end of an existing file (created if missing) instead of overwriting.',\n input: CreateInputSchema,\n output: CreateOutputSchema,\n annotations: {\n readOnlyHint: false,\n idempotentHint: false,\n destructiveHint: true,\n openWorldHint: false,\n },\n accessPaths: (args) => args.files.map((f) => f.path),\n run: async (args, ctx) => {\n // Phase 1 (no mutation): which entries would replace a file that exists\n // right now? Those need the user's word before anything is written (R14).\n // append never destroys, and overwrite: true is that word given up front.\n // A path that fails validation here is left for runOverPaths to report.\n // A file that appears between rounds needs no re-stat before the write:\n // the retry re-plans, and the grown pending set fails the R9 check.\n const { results: planned } = await processInParallel(\n args.files,\n async (entry) => {\n if (entry.append || entry.overwrite) return undefined;\n try {\n const validPath = await ctx.fs.pathGuard.validatePathForWrite(entry.path);\n return (await destExists(ctx.fs, validPath, 'create')) ? validPath : undefined;\n } catch (error) {\n rethrowIfAborted(error);\n return undefined;\n }\n },\n PARALLEL_CONCURRENCY,\n ctx.signal,\n );\n const pendingByPath = new Map<string, string>();\n for (const { index, value } of planned) {\n const requested = args.files[index]?.path;\n if (value && requested) pendingByPath.set(requested, value);\n }\n const pendingSorted = [...new Set(pendingByPath.values())].sort();\n if (pendingSorted.length > 0) {\n // Round 1 returns input_required; a retry whose verified state does not\n // bind this overwrite set throws (R9) via `pendingRoundTrip`.\n const round = await pendingRoundTrip({\n op: 'create',\n pending: pendingSorted,\n requestState: ctx.requestState,\n clientCapabilities: ctx.clientCapabilities,\n serverCtx: ctx.serverCtx,\n buildInputs: (paths) =>\n paths.map((target, i) => ({\n key: confirmKey(i),\n message: `\"${target}\" already exists. Overwrite it?`,\n choices: ['overwrite', 'skip'],\n })),\n });\n if (round !== undefined) return round;\n }\n\n const batch = await runOverPaths<\n { content: string; append?: boolean | undefined; overwrite?: boolean | undefined },\n { file: CreateFileResult; resourceLink?: ContentBlock } | { skipped: string }\n >(\n { files: args.files },\n ctx,\n async ({ path, override }) => {\n const content = override?.content ?? '';\n\n const pendingPath = pendingByPath.get(path);\n if (pendingPath !== undefined) {\n const key = confirmKey(pendingSorted.indexOf(pendingPath));\n const choice = readAcceptedChoice(ctx.inputResponses, key);\n if (choice === 'skip') return { skipped: path };\n if (choice !== 'overwrite') {\n throw new FsError(\n ErrorCode.CANCELLED,\n `create cancelled: overwrite of \"${path}\" was ${describeRefusal(ctx.inputResponses, key)}`,\n path,\n );\n }\n }\n\n await ctx.fs.mkdir(dirname(path), { recursive: true });\n\n // Overwrite: file content == `content`, so content-derived meta is\n // exact and the atomic temp+rename write protects the existing mode.\n // Append: the resulting file is everything that was there plus\n // `content`, so size/created/modified come from a real post-append\n // stat, MIME from the resulting file's leading bytes, and the line\n // count streams the file — reading a multi-GB log back whole is the\n // round-trip append exists to avoid.\n let validPath: string;\n let meta: WrittenFileMeta;\n let created: string;\n let modified: string;\n if (override?.append) {\n const appended = await ctx.fs.appendFile(path, content, {\n encoding: 'utf-8',\n signal: ctx.signal,\n });\n validPath = appended.validPath;\n // The append is the commit point: the bytes are on disk, so\n // everything below is best-effort RESULT METADATA, not part of the\n // write. It runs unwired to ctx.signal, and a metadata failure\n // degrades the result instead of failing it — either would report\n // the append as failed and a client retry would append twice. The\n // stat covers the committed validPath, never the input path: a\n // symlink there could have been swapped between write and stat,\n // and the result must describe the file the bytes landed in.\n let stats: Stats | undefined;\n let mimeType: string;\n let kind: FileKind;\n let lineCount = 0;\n try {\n stats = (await ctx.fs.stat(appended.validPath)).stats;\n // Sniff the resulting file's leading bytes, not the appended\n // chunk: text appended to a binary file is still a binary file.\n const sample = await ctx.fs.readLeadingSample(appended.validPath, MIME_SAMPLE_SIZE);\n const mimeInfo = detectMimeFromContent(appended.validPath, sample);\n mimeType = mimeInfo.mimeType;\n kind = mimeInfo.kind;\n lineCount = await countFileLines(appended.validPath);\n } catch (error) {\n ctx.log?.(\n 'warning',\n `create append: result metadata degraded for ${appended.validPath}: ${formatUnknownErrorMessage(error)}`,\n 'create',\n );\n // A POSIX mode-0222 file appends fine but cannot be opened 'r';\n // fall back to the chunk for MIME (an approximation) and report\n // the line count as unknown-as-zero rather than fail the append.\n const mimeInfo = detectMimeFromContent(appended.validPath, content);\n mimeType = mimeInfo.mimeType;\n kind = mimeInfo.kind;\n }\n // The resource store serves this URI via readRaw, which rejects\n // files over the text-size cap with TOO_LARGE — never advertise a\n // link the store deterministically cannot serve. A failed stat\n // leaves the size unknown, so nothing is advertised either.\n const servable = stats !== undefined && stats.size <= getMaxTextFileSize();\n const size = stats?.size ?? 0;\n meta = {\n size,\n lineCount,\n mimeType,\n kind,\n resourceUri: servable ? buildFileResourceUri(appended.validPath) : undefined,\n resourceLink:\n servable && ctx.resourceStore\n ? buildFileResourceLink(appended.validPath, mimeType, size)\n : undefined,\n };\n created = (stats?.birthtime ?? EPOCH).toISOString();\n modified = (stats?.mtime ?? EPOCH).toISOString();\n } else {\n const written = await ctx.fs.writeFile(path, content, {\n encoding: 'utf-8',\n signal: ctx.signal,\n });\n validPath = written.validPath;\n const fileStats = (await ctx.fs.stat(path, { signal: ctx.signal })).stats;\n created = fileStats.birthtime.toISOString();\n modified = fileStats.mtime.toISOString();\n meta = buildWrittenFileMeta(written.validPath, content, ctx.resourceStore);\n }\n\n const file: CreateFileResult = {\n path: validPath,\n size: meta.size,\n lineCount: meta.lineCount,\n mimeType: meta.mimeType,\n kind: meta.kind,\n resourceUri: meta.resourceUri,\n created,\n modified,\n };\n\n return meta.resourceLink ? { file, resourceLink: meta.resourceLink } : { file };\n },\n { defaultErrorCode: ErrorCode.UNKNOWN },\n );\n\n const results: CreateFileResult[] = [];\n const failures: CreateFailureItem[] = [];\n const skipped: string[] = [];\n const links: ContentBlock[] = [];\n for (const r of batch.results) {\n if ('error' in r) {\n failures.push({ path: r.path, error: r.error });\n continue;\n }\n if ('skipped' in r.value) {\n skipped.push(r.value.skipped);\n continue;\n }\n results.push(r.value.file);\n if (r.value.resourceLink) links.push(r.value.resourceLink);\n }\n\n const structured = {\n files: results,\n ...(failures.length > 0 ? { failures } : {}),\n ...(skipped.length > 0 ? { skipped } : {}),\n };\n // No `text` on purpose. The one-line roster this used to build named the\n // paths and dropped every per-file size, line count and error the caller\n // asked `create` for. Supplying no text makes this a data tool, so\n // `define.ts` renders the JSON and keeps it in `structuredContent`.\n if (links.length > 0) {\n return { structured, resources: links, isError: isTotalFailure(batch.summary) };\n }\n\n return { structured, isError: isTotalFailure(batch.summary) };\n },\n progress: (args) => ({\n label: 'Create',\n subject:\n args.files.length === 1\n ? basename(args.files[0]?.path ?? '')\n : `${String(args.files.length)} files`,\n }),\n defaultErrorCode: ErrorCode.UNKNOWN,\n});\n"]}
|
package/dist/tools/define.d.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import type { ClientCapabilities, ContentBlock, InputRequiredResult, McpServer, Notification, RegisteredTool, RequestMeta, RequestStateAccessor, ToolAnnotations } from '@modelcontextprotocol/server';
|
|
2
|
-
import * as z from 'zod/v4';
|
|
3
|
-
import { ErrorCode } from '../core/errors.
|
|
4
|
-
import type { ProgressCtx } from '../core/fmt.
|
|
5
|
-
import { GuardedFileSystem } from '../core/fs.
|
|
6
|
-
import type { LoggingLevel } from '../core/observability.
|
|
7
|
-
import type {
|
|
8
|
-
import type {
|
|
9
|
-
import type { ResourceStore } from '../core/store.js';
|
|
1
|
+
import type { ClientCapabilities, ContentBlock, InputRequiredResult, McpServer, Notification, RegisteredTool, RequestId, RequestMeta, RequestStateAccessor, ServerContext, ToolAnnotations } from '@modelcontextprotocol/server';
|
|
2
|
+
import type * as z from 'zod/v4';
|
|
3
|
+
import { ErrorCode } from '../core/errors.ts';
|
|
4
|
+
import type { ProgressCtx } from '../core/fmt.ts';
|
|
5
|
+
import { GuardedFileSystem } from '../core/fs.ts';
|
|
6
|
+
import type { LoggingLevel } from '../core/observability.ts';
|
|
7
|
+
import type { PathGuard } from '../core/path.ts';
|
|
8
|
+
import type { PageSnapshotStore, ResourceStore } from '../core/store.ts';
|
|
10
9
|
export interface ToolCtx {
|
|
11
10
|
readonly signal: AbortSignal;
|
|
12
11
|
readonly _meta?: RequestMeta | undefined;
|
|
12
|
+
/** The JSON-RPC id of the `tools/call` this context serves; prefixes every log line. */
|
|
13
|
+
readonly requestId: RequestId;
|
|
14
|
+
/** W3C trace context the client put in `_meta`, when it did; rides the log prefix. */
|
|
15
|
+
readonly traceparent?: string | undefined;
|
|
13
16
|
readonly fs: GuardedFileSystem;
|
|
14
17
|
readonly pageStore: PageSnapshotStore;
|
|
15
18
|
readonly resourceStore: ResourceStore | undefined;
|
|
@@ -47,12 +50,15 @@ export interface ToolCtx {
|
|
|
47
50
|
* all. `undefined` means "cannot tell" — never "no capabilities".
|
|
48
51
|
*/
|
|
49
52
|
readonly clientCapabilities?: ClientCapabilities | undefined;
|
|
53
|
+
/** The SDK request context this call runs under; `pendingRoundTrip` binds minted state to it. */
|
|
54
|
+
readonly serverCtx: ServerContext;
|
|
50
55
|
}
|
|
51
56
|
interface ToolDeps {
|
|
52
57
|
readonly server: McpServer;
|
|
53
58
|
readonly pathGuard: PathGuard;
|
|
54
59
|
readonly pageStore: PageSnapshotStore;
|
|
55
60
|
readonly resourceStore: ResourceStore | undefined;
|
|
61
|
+
readonly era?: 'legacy' | 'modern';
|
|
56
62
|
}
|
|
57
63
|
interface RunResult<T> {
|
|
58
64
|
readonly structured: T;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../../src/tools/define.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,kBAAkB,EAClB,YAAY,EACZ,mBAAmB,
|
|
1
|
+
{"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../../src/tools/define.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/define.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { CLIENT_CAPABILITIES_META_KEY,
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { McpProgressSink, ProgressSession } from './progress.js';
|
|
1
|
+
import { CLIENT_CAPABILITIES_META_KEY, isInputRequiredResult, TRACEPARENT_META_KEY, } from '@modelcontextprotocol/server';
|
|
2
|
+
import { ErrorCode, formatUnknownErrorMessage, Problem } from "../core/errors.js";
|
|
3
|
+
import { plainMessage } from "../core/fmt.js";
|
|
4
|
+
import { GuardedFileSystem } from "../core/fs.js";
|
|
5
|
+
import { confirmKey, pendingRoundTrip, readAcceptedConfirm, readAcceptedMultiChoice, } from "../core/input-required.js";
|
|
6
|
+
import { Logger, sanitizeLogField } from "../core/observability.js";
|
|
7
|
+
import { isSamePath } from "../core/path-utils.js";
|
|
8
|
+
import { ProgressSession } from "./progress.js";
|
|
10
9
|
function toToolCtx(ctx, deps) {
|
|
11
10
|
// Envelope first, accessor second — the two eras carry this differently.
|
|
12
11
|
// A modern request states the capabilities in its own `_meta` envelope; a
|
|
@@ -23,16 +22,28 @@ function toToolCtx(ctx, deps) {
|
|
|
23
22
|
const envelope = ctx.mcpReq.envelope;
|
|
24
23
|
const clientCapabilities = envelope?.[CLIENT_CAPABILITIES_META_KEY] ??
|
|
25
24
|
// eslint-disable-next-line @typescript-eslint/no-deprecated -- the only source of client capabilities on a legacy connection, where no envelope exists.
|
|
26
|
-
deps.server.server.getClientCapabilities()
|
|
25
|
+
deps.server.server.getClientCapabilities() ??
|
|
26
|
+
// A legacy instance that never saw `initialize` is the HTTP leg's
|
|
27
|
+
// per-request serving (`createMcpHandler` with `legacy: 'stateless'`):
|
|
28
|
+
// there is no return path for a server-to-client request, so read the
|
|
29
|
+
// client as having declared nothing. The `input_required` flows then
|
|
30
|
+
// answer with their named workaround instead of the SDK's generic refusal.
|
|
31
|
+
// sunset(SEP-2577): removal trigger in docs/adr/002-legacy-protocol-paths-sunset.md.
|
|
32
|
+
(deps.era === 'legacy' ? {} : undefined);
|
|
27
33
|
return {
|
|
28
34
|
signal: ctx.mcpReq.signal,
|
|
29
35
|
...(ctx.mcpReq._meta ? { _meta: ctx.mcpReq._meta } : {}),
|
|
36
|
+
requestId: ctx.mcpReq.id,
|
|
37
|
+
...(typeof ctx.mcpReq._meta?.[TRACEPARENT_META_KEY] === 'string'
|
|
38
|
+
? { traceparent: ctx.mcpReq._meta[TRACEPARENT_META_KEY] }
|
|
39
|
+
: {}),
|
|
30
40
|
fs: new GuardedFileSystem(deps.pathGuard),
|
|
31
41
|
pageStore: deps.pageStore,
|
|
32
42
|
resourceStore: deps.resourceStore,
|
|
33
43
|
sendNotification: async (notification) => ctx.mcpReq.notify(notification),
|
|
34
44
|
inputResponses: ctx.mcpReq.inputResponses,
|
|
35
45
|
requestState: ctx.mcpReq.requestState,
|
|
46
|
+
serverCtx: ctx,
|
|
36
47
|
...(clientCapabilities ? { clientCapabilities } : {}),
|
|
37
48
|
};
|
|
38
49
|
}
|
|
@@ -68,7 +79,6 @@ class ToolExecutor {
|
|
|
68
79
|
parsedArgs;
|
|
69
80
|
toolCtx;
|
|
70
81
|
#progressCtx;
|
|
71
|
-
#mcpSink;
|
|
72
82
|
#progressSession;
|
|
73
83
|
constructor(toolName, ctx, def, parsedArgs) {
|
|
74
84
|
this.def = def;
|
|
@@ -78,22 +88,27 @@ class ToolExecutor {
|
|
|
78
88
|
: ctx.signal;
|
|
79
89
|
this.#progressCtx = def.progress ? def.progress(parsedArgs) : { label: def.title };
|
|
80
90
|
const token = ctx._meta?.progressToken;
|
|
81
|
-
|
|
82
|
-
this.#mcpSink = new McpProgressSink(toolName, token, ctx.sendNotification);
|
|
83
|
-
}
|
|
84
|
-
const isTest = process.env['NODE_ENV'] === 'test' || process.execArgv.includes('--test');
|
|
91
|
+
const notify = ctx.sendNotification;
|
|
85
92
|
this.#progressSession = new ProgressSession({
|
|
86
93
|
label: this.#progressCtx.label,
|
|
87
|
-
...(
|
|
88
|
-
...(isTest ? { rateLimitMs: 0 } : {}),
|
|
94
|
+
...(token !== undefined && notify !== undefined ? { sink: { toolName, token, notify } } : {}),
|
|
89
95
|
});
|
|
90
96
|
this.toolCtx = {
|
|
91
97
|
...ctx,
|
|
92
98
|
signal: this.signal,
|
|
93
99
|
log: (level, data, logger) => {
|
|
94
100
|
const msg = typeof data === 'string' ? data : String(data);
|
|
101
|
+
// `[req <id>]` first so one grep finds every line of one call; the
|
|
102
|
+
// traceparent rides along only when the client sent one. Both are
|
|
103
|
+
// client-supplied, so they pass through `sanitizeLogField`: a control
|
|
104
|
+
// character in a JSON-RPC id or a traceparent would forge log lines.
|
|
105
|
+
// The message is flattened too (uncapped): it can carry a resolved
|
|
106
|
+
// path or OS error text, and input schemas reject CR/LF but not the
|
|
107
|
+
// Unicode line separators.
|
|
108
|
+
const trace = ctx.traceparent ? ` ${sanitizeLogField(ctx.traceparent)}` : '';
|
|
95
109
|
const prefix = logger ? `[${logger}] ` : '';
|
|
96
|
-
|
|
110
|
+
const text = sanitizeLogField(msg, Infinity);
|
|
111
|
+
Logger.emit(level, `[req ${sanitizeLogField(String(ctx.requestId))}${trace}] ${prefix}${text}`);
|
|
97
112
|
},
|
|
98
113
|
onProgress: (p) => {
|
|
99
114
|
this.#tick(p);
|
|
@@ -108,22 +123,17 @@ class ToolExecutor {
|
|
|
108
123
|
};
|
|
109
124
|
this.#progressSession.set({ ...p, message: plainMessage('tick', tickCtx) });
|
|
110
125
|
}
|
|
111
|
-
async #flushProgress() {
|
|
112
|
-
if (this.#mcpSink)
|
|
113
|
-
await this.#mcpSink.flush();
|
|
114
|
-
}
|
|
115
126
|
async completeProgress(result) {
|
|
116
127
|
const doneCtx = this.def.progressDone
|
|
117
128
|
? { ...this.#progressCtx, ...this.def.progressDone(this.parsedArgs, result) }
|
|
118
129
|
: this.#progressCtx;
|
|
119
130
|
this.#progressSession.complete(plainMessage('done', doneCtx));
|
|
120
|
-
await this.#
|
|
131
|
+
await this.#progressSession.flush();
|
|
121
132
|
}
|
|
122
133
|
async failProgress(error) {
|
|
123
134
|
const errMsg = formatUnknownErrorMessage(error);
|
|
124
|
-
|
|
125
|
-
this.#progressSession.
|
|
126
|
-
await this.#flushProgress();
|
|
135
|
+
this.#progressSession.fail(plainMessage('fail', { ...this.#progressCtx, error: errMsg }));
|
|
136
|
+
await this.#progressSession.flush();
|
|
127
137
|
const { text: errorText } = Problem.toText(error, this.def.defaultErrorCode ?? ErrorCode.UNKNOWN);
|
|
128
138
|
return {
|
|
129
139
|
content: [{ type: 'text', text: errorText }],
|
|
@@ -160,9 +170,15 @@ class ToolExecutor {
|
|
|
160
170
|
pending: grantDirs,
|
|
161
171
|
requestState: this.toolCtx.requestState,
|
|
162
172
|
clientCapabilities: this.toolCtx.clientCapabilities,
|
|
173
|
+
serverCtx: this.toolCtx.serverCtx,
|
|
163
174
|
buildInputs: (dirs) => multi
|
|
164
175
|
? [
|
|
165
|
-
|
|
176
|
+
{
|
|
177
|
+
key: 'grant',
|
|
178
|
+
message: 'Grant filesystem access to these directories? Select the ones to allow.',
|
|
179
|
+
choices: dirs,
|
|
180
|
+
multi: true,
|
|
181
|
+
},
|
|
166
182
|
]
|
|
167
183
|
: dirs.map((dir, i) => ({
|
|
168
184
|
key: confirmKey(i),
|
|
@@ -221,76 +237,11 @@ class ToolExecutor {
|
|
|
221
237
|
return await this.failProgress(error);
|
|
222
238
|
}
|
|
223
239
|
finally {
|
|
224
|
-
await this.#
|
|
240
|
+
await this.#progressSession.flush();
|
|
225
241
|
}
|
|
226
242
|
}
|
|
227
243
|
}
|
|
228
|
-
/**
|
|
229
|
-
* Validate with Zod while publishing the precomputed JSON Schema: clients get
|
|
230
|
-
* draft-2020-12, handlers get Zod's coercions, defaults and error messages.
|
|
231
|
-
*
|
|
232
|
-
* Each validator instance is built for exactly one schema and handed straight
|
|
233
|
-
* to `fromJsonSchema(thatSchema, ...)`, which calls `getValidator` once with
|
|
234
|
-
* the same schema — so the argument is redundant here and deliberately ignored.
|
|
235
|
-
* Reusing one instance across schemas would silently validate against `schema`.
|
|
236
|
-
*/
|
|
237
|
-
function zodJsonSchemaValidator(schema) {
|
|
238
|
-
return {
|
|
239
|
-
getValidator() {
|
|
240
|
-
return (input) => {
|
|
241
|
-
const result = schema.safeParse(input);
|
|
242
|
-
if (result.success) {
|
|
243
|
-
return {
|
|
244
|
-
valid: true,
|
|
245
|
-
data: result.data,
|
|
246
|
-
errorMessage: undefined,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
return {
|
|
250
|
-
valid: false,
|
|
251
|
-
data: undefined,
|
|
252
|
-
errorMessage: z.prettifyError(result.error),
|
|
253
|
-
};
|
|
254
|
-
};
|
|
255
|
-
},
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* Generate the wire copy of a schema for `tools/list`. The Zod schema keeps
|
|
260
|
-
* every constraint for runtime validation; the `override` pass only trims what
|
|
261
|
-
* the wire copy costs every client at session start:
|
|
262
|
-
*
|
|
263
|
-
* - `$schema` and `title` carry no information the host uses.
|
|
264
|
-
* - `maximum: Number.MAX_SAFE_INTEGER` is zod's int() artifact, not a bound.
|
|
265
|
-
* - `examples` is dropped in both directions: on output it describes a field the
|
|
266
|
-
* server itself fills in, and on input every description that carried one
|
|
267
|
-
* already spells the same example out inline, so the keyword paid twice.
|
|
268
|
-
*
|
|
269
|
-
* Output `description`s are NOT dropped. They were, and the result was a wire
|
|
270
|
-
* contract the model had to guess at: `delete` returns `path` XOR `paths`,
|
|
271
|
-
* `edit.diff` appears only under dryRun, `read.value` has no required field at
|
|
272
|
-
* all. None of that is inferable from types alone.
|
|
273
|
-
*
|
|
274
|
-
* No shared subschema carries a `.meta({ id })`, so zod inlines every one of
|
|
275
|
-
* them and the emitted document has no `$defs`/`$ref` to dereference.
|
|
276
|
-
*/
|
|
277
|
-
function toDraft202012(schema, io) {
|
|
278
|
-
const generated = z.toJSONSchema(schema, {
|
|
279
|
-
target: 'draft-2020-12',
|
|
280
|
-
io,
|
|
281
|
-
override: ({ jsonSchema }) => {
|
|
282
|
-
const node = jsonSchema;
|
|
283
|
-
delete node['title'];
|
|
284
|
-
if (node['maximum'] === Number.MAX_SAFE_INTEGER)
|
|
285
|
-
delete node['maximum'];
|
|
286
|
-
delete node['examples'];
|
|
287
|
-
},
|
|
288
|
-
});
|
|
289
|
-
delete generated['$schema'];
|
|
290
|
-
return generated;
|
|
291
|
-
}
|
|
292
244
|
export function defineTool(def) {
|
|
293
|
-
const inputJsonSchema = toDraft202012(def.input, 'input');
|
|
294
245
|
// Nothing here depends on `deps`, so it is built once per tool definition
|
|
295
246
|
// rather than once per `register` — the HTTP leg registers every tool afresh
|
|
296
247
|
// on each request.
|
|
@@ -309,7 +260,10 @@ export function defineTool(def) {
|
|
|
309
260
|
const toolDefShape = {
|
|
310
261
|
title: def.title,
|
|
311
262
|
description: def.description,
|
|
312
|
-
|
|
263
|
+
// The Zod schema goes in as-is: the SDK validates with it and publishes its
|
|
264
|
+
// own draft-2020-12 conversion in `tools/list`. The cast only names the
|
|
265
|
+
// in/out types a generic `ZodType` cannot surface through `~standard`.
|
|
266
|
+
inputSchema: def.input,
|
|
313
267
|
// No `outputSchema`, ever. Publishing one obliges the result to carry
|
|
314
268
|
// `structuredContent` (clients enforce it), and every tool that authors its
|
|
315
269
|
// own text ships its metadata under `_meta` instead — see
|