@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.
Files changed (194) hide show
  1. package/README.md +38 -32
  2. package/dist/cli-help.d.ts.map +1 -1
  3. package/dist/cli-help.js +21 -40
  4. package/dist/cli-help.js.map +1 -1
  5. package/dist/cli.d.ts +0 -2
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +24 -46
  8. package/dist/cli.js.map +1 -1
  9. package/dist/core/concurrency.d.ts +5 -17
  10. package/dist/core/concurrency.d.ts.map +1 -1
  11. package/dist/core/concurrency.js +32 -40
  12. package/dist/core/concurrency.js.map +1 -1
  13. package/dist/core/config.d.ts +2 -0
  14. package/dist/core/config.d.ts.map +1 -1
  15. package/dist/core/config.js.map +1 -1
  16. package/dist/core/cursor.d.ts +1 -12
  17. package/dist/core/cursor.d.ts.map +1 -1
  18. package/dist/core/cursor.js +2 -12
  19. package/dist/core/cursor.js.map +1 -1
  20. package/dist/core/errors.d.ts +6 -11
  21. package/dist/core/errors.d.ts.map +1 -1
  22. package/dist/core/errors.js +62 -117
  23. package/dist/core/errors.js.map +1 -1
  24. package/dist/core/file-uri.d.ts +7 -3
  25. package/dist/core/file-uri.d.ts.map +1 -1
  26. package/dist/core/file-uri.js +8 -4
  27. package/dist/core/file-uri.js.map +1 -1
  28. package/dist/core/fmt.d.ts +0 -11
  29. package/dist/core/fmt.d.ts.map +1 -1
  30. package/dist/core/fmt.js +1 -19
  31. package/dist/core/fmt.js.map +1 -1
  32. package/dist/core/fs.d.ts +34 -4
  33. package/dist/core/fs.d.ts.map +1 -1
  34. package/dist/core/fs.js +151 -12
  35. package/dist/core/fs.js.map +1 -1
  36. package/dist/core/glob.d.ts +1 -2
  37. package/dist/core/glob.d.ts.map +1 -1
  38. package/dist/core/glob.js +12 -28
  39. package/dist/core/glob.js.map +1 -1
  40. package/dist/core/input-required.d.ts +19 -29
  41. package/dist/core/input-required.d.ts.map +1 -1
  42. package/dist/core/input-required.js +58 -73
  43. package/dist/core/input-required.js.map +1 -1
  44. package/dist/core/observability.d.ts +9 -0
  45. package/dist/core/observability.d.ts.map +1 -1
  46. package/dist/core/observability.js +13 -2
  47. package/dist/core/observability.js.map +1 -1
  48. package/dist/core/path-completer.d.ts +6 -9
  49. package/dist/core/path-completer.d.ts.map +1 -1
  50. package/dist/core/path-completer.js +28 -55
  51. package/dist/core/path-completer.js.map +1 -1
  52. package/dist/core/path-discovery.d.ts.map +1 -1
  53. package/dist/core/path-discovery.js +3 -4
  54. package/dist/core/path-discovery.js.map +1 -1
  55. package/dist/core/path-utils.d.ts +28 -0
  56. package/dist/core/path-utils.d.ts.map +1 -1
  57. package/dist/core/path-utils.js +57 -2
  58. package/dist/core/path-utils.js.map +1 -1
  59. package/dist/core/path.d.ts +3 -8
  60. package/dist/core/path.d.ts.map +1 -1
  61. package/dist/core/path.js +8 -9
  62. package/dist/core/path.js.map +1 -1
  63. package/dist/core/read.d.ts +3 -22
  64. package/dist/core/read.d.ts.map +1 -1
  65. package/dist/core/read.js +46 -96
  66. package/dist/core/read.js.map +1 -1
  67. package/dist/core/schema.d.ts +8 -9
  68. package/dist/core/schema.d.ts.map +1 -1
  69. package/dist/core/schema.js +11 -14
  70. package/dist/core/schema.js.map +1 -1
  71. package/dist/core/search.d.ts +6 -1
  72. package/dist/core/search.d.ts.map +1 -1
  73. package/dist/core/search.js +21 -23
  74. package/dist/core/search.js.map +1 -1
  75. package/dist/core/sensitive.d.ts +4 -2
  76. package/dist/core/sensitive.d.ts.map +1 -1
  77. package/dist/core/sensitive.js +262 -45
  78. package/dist/core/sensitive.js.map +1 -1
  79. package/dist/core/store.d.ts +41 -16
  80. package/dist/core/store.d.ts.map +1 -1
  81. package/dist/core/store.js +147 -82
  82. package/dist/core/store.js.map +1 -1
  83. package/dist/core/util.d.ts +3 -2
  84. package/dist/core/util.d.ts.map +1 -1
  85. package/dist/core/util.js +5 -6
  86. package/dist/core/util.js.map +1 -1
  87. package/dist/core/watcher-registry.d.ts +1 -1
  88. package/dist/core/watcher-registry.d.ts.map +1 -1
  89. package/dist/core/watcher-registry.js +9 -5
  90. package/dist/core/watcher-registry.js.map +1 -1
  91. package/dist/index.js +5 -6
  92. package/dist/index.js.map +1 -1
  93. package/dist/instructions.js +2 -2
  94. package/dist/instructions.js.map +1 -1
  95. package/dist/prompts.js +1 -1
  96. package/dist/prompts.js.map +1 -1
  97. package/dist/resources.d.ts +11 -19
  98. package/dist/resources.d.ts.map +1 -1
  99. package/dist/resources.js +51 -62
  100. package/dist/resources.js.map +1 -1
  101. package/dist/server.d.ts +4 -5
  102. package/dist/server.d.ts.map +1 -1
  103. package/dist/server.js +15 -11
  104. package/dist/server.js.map +1 -1
  105. package/dist/tools/batch.d.ts +2 -2
  106. package/dist/tools/batch.js +3 -3
  107. package/dist/tools/batch.js.map +1 -1
  108. package/dist/tools/create.d.ts +1 -1
  109. package/dist/tools/create.d.ts.map +1 -1
  110. package/dist/tools/create.js +168 -17
  111. package/dist/tools/create.js.map +1 -1
  112. package/dist/tools/define.d.ts +15 -9
  113. package/dist/tools/define.d.ts.map +1 -1
  114. package/dist/tools/define.js +48 -94
  115. package/dist/tools/define.js.map +1 -1
  116. package/dist/tools/delete.d.ts +1 -1
  117. package/dist/tools/delete.d.ts.map +1 -1
  118. package/dist/tools/delete.js +23 -23
  119. package/dist/tools/delete.js.map +1 -1
  120. package/dist/tools/diff.d.ts +1 -1
  121. package/dist/tools/diff.d.ts.map +1 -1
  122. package/dist/tools/diff.js +7 -37
  123. package/dist/tools/diff.js.map +1 -1
  124. package/dist/tools/edit.d.ts +1 -1
  125. package/dist/tools/edit.d.ts.map +1 -1
  126. package/dist/tools/edit.js +101 -92
  127. package/dist/tools/edit.js.map +1 -1
  128. package/dist/tools/find-files.d.ts +1 -1
  129. package/dist/tools/find-files.d.ts.map +1 -1
  130. package/dist/tools/find-files.js +18 -37
  131. package/dist/tools/find-files.js.map +1 -1
  132. package/dist/tools/index.d.ts +10 -10
  133. package/dist/tools/index.d.ts.map +1 -1
  134. package/dist/tools/index.js +14 -13
  135. package/dist/tools/index.js.map +1 -1
  136. package/dist/tools/list-roots.d.ts +1 -1
  137. package/dist/tools/list-roots.js +3 -3
  138. package/dist/tools/list-roots.js.map +1 -1
  139. package/dist/tools/list.d.ts +1 -1
  140. package/dist/tools/list.d.ts.map +1 -1
  141. package/dist/tools/list.js +23 -29
  142. package/dist/tools/list.js.map +1 -1
  143. package/dist/tools/move.d.ts +1 -1
  144. package/dist/tools/move.d.ts.map +1 -1
  145. package/dist/tools/move.js +31 -45
  146. package/dist/tools/move.js.map +1 -1
  147. package/dist/tools/patch.d.ts +1 -1
  148. package/dist/tools/patch.js +7 -7
  149. package/dist/tools/patch.js.map +1 -1
  150. package/dist/tools/progress.d.ts +17 -28
  151. package/dist/tools/progress.d.ts.map +1 -1
  152. package/dist/tools/progress.js +42 -94
  153. package/dist/tools/progress.js.map +1 -1
  154. package/dist/tools/read.d.ts +1 -1
  155. package/dist/tools/read.d.ts.map +1 -1
  156. package/dist/tools/read.js +10 -12
  157. package/dist/tools/read.js.map +1 -1
  158. package/dist/tools/replace-text.d.ts +1 -1
  159. package/dist/tools/replace-text.d.ts.map +1 -1
  160. package/dist/tools/replace-text.js +32 -49
  161. package/dist/tools/replace-text.js.map +1 -1
  162. package/dist/tools/search-text.d.ts +1 -1
  163. package/dist/tools/search-text.d.ts.map +1 -1
  164. package/dist/tools/search-text.js +82 -45
  165. package/dist/tools/search-text.js.map +1 -1
  166. package/dist/tools/stat.d.ts +1 -1
  167. package/dist/tools/stat.d.ts.map +1 -1
  168. package/dist/tools/stat.js +9 -29
  169. package/dist/tools/stat.js.map +1 -1
  170. package/dist/transport/http-policy.js +4 -4
  171. package/dist/transport/http-policy.js.map +1 -1
  172. package/dist/transport/http.d.ts +2 -2
  173. package/dist/transport/http.d.ts.map +1 -1
  174. package/dist/transport/http.js +21 -18
  175. package/dist/transport/http.js.map +1 -1
  176. package/dist/transport/shared.d.ts +2 -2
  177. package/dist/transport/shared.js +2 -2
  178. package/dist/transport/shared.js.map +1 -1
  179. package/dist/transport/stdio.d.ts +4 -3
  180. package/dist/transport/stdio.d.ts.map +1 -1
  181. package/dist/transport/stdio.js +7 -6
  182. package/dist/transport/stdio.js.map +1 -1
  183. package/dist/transport.d.ts +2 -2
  184. package/dist/transport.js +2 -2
  185. package/dist/transport.js.map +1 -1
  186. package/package.json +6 -13
  187. package/dist/core/page-store.d.ts +0 -41
  188. package/dist/core/page-store.d.ts.map +0 -1
  189. package/dist/core/page-store.js +0 -74
  190. package/dist/core/page-store.js.map +0 -1
  191. package/dist/core/primitives.d.ts +0 -34
  192. package/dist/core/primitives.d.ts.map +0 -1
  193. package/dist/core/primitives.js +0 -63
  194. package/dist/core/primitives.js.map +0 -1
@@ -1,11 +1,16 @@
1
1
  import { basename, dirname } from 'node:path';
2
2
  import * as z from 'zod/v4';
3
- import { ErrorCode } from '../core/errors.js';
4
- import { buildWrittenFileMeta } from '../core/file-uri.js';
5
- import { FileKind, IsoDateTime, NonNegInt, PathFailureSchema, RequiredPath, } from '../core/schema.js';
6
- import { getMaxTextFileSize } from '../core/util.js';
7
- import { isTotalFailure, runOverPaths } from './batch.js';
8
- import { defineTool } from './define.js';
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
- .describe('Resource URI pointing to the created file content in the resource store'),
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), writing or overwriting content and creating parent directories as needed. ' +
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
- 'Silently overwrites existing files — read first if you need to preserve existing content.',
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
- const { validPath } = await ctx.fs.writeFile(path, content, {
64
- encoding: 'utf-8',
65
- signal: ctx.signal,
66
- });
67
- const { stats: fileStats } = await ctx.fs.stat(path, { signal: ctx.signal });
68
- const meta = buildWrittenFileMeta(validPath, content, ctx.resourceStore);
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: fileStats.birthtime.toISOString(),
77
- modified: fileStats.mtime.toISOString(),
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
@@ -1 +1 @@
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 { 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"]}
@@ -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.js';
4
- import type { ProgressCtx } from '../core/fmt.js';
5
- import { GuardedFileSystem } from '../core/fs.js';
6
- import type { LoggingLevel } from '../core/observability.js';
7
- import type { PageSnapshotStore } from '../core/page-store.js';
8
- import type { PathGuard } from '../core/path.js';
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":"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"}
1
+ {"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../../src/tools/define.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,11 @@
1
- import { CLIENT_CAPABILITIES_META_KEY, fromJsonSchema, isInputRequiredResult, } from '@modelcontextprotocol/server';
2
- import * as z from 'zod/v4';
3
- import { ErrorCode, formatUnknownErrorMessage, Problem } from '../core/errors.js';
4
- import { plainMessage } from '../core/fmt.js';
5
- import { GuardedFileSystem } from '../core/fs.js';
6
- import { confirmKey, multiSelectInput, pendingRoundTrip, readAcceptedConfirm, readAcceptedMultiChoice, } from '../core/input-required.js';
7
- import { Logger } from '../core/observability.js';
8
- import { isSamePath } from '../core/path-utils.js';
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
- if (token !== undefined && ctx.sendNotification !== undefined) {
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
- ...(this.#mcpSink ? { sink: this.#mcpSink } : {}),
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
- Logger.emit(level, `${prefix}${msg}`);
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.#flushProgress();
131
+ await this.#progressSession.flush();
121
132
  }
122
133
  async failProgress(error) {
123
134
  const errMsg = formatUnknownErrorMessage(error);
124
- const message = plainMessage('fail', { ...this.#progressCtx, error: errMsg });
125
- this.#progressSession.fail(error, message);
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
- multiSelectInput('grant', 'Grant filesystem access to these directories? Select the ones to allow.', dirs.map((d) => ({ value: d, title: d }))),
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.#flushProgress();
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
- inputSchema: fromJsonSchema(inputJsonSchema, zodJsonSchemaValidator(def.input)),
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