@j0hanz/filesystem-mcp 1.19.1 → 2.1.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 (301) hide show
  1. package/README.md +333 -838
  2. package/dist/cli-help.d.ts +3 -0
  3. package/dist/cli-help.d.ts.map +1 -0
  4. package/dist/cli-help.js +169 -0
  5. package/dist/cli-help.js.map +1 -0
  6. package/dist/cli.d.ts +28 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +168 -123
  9. package/dist/cli.js.map +1 -0
  10. package/dist/core/concurrency.d.ts +70 -0
  11. package/dist/core/concurrency.d.ts.map +1 -0
  12. package/dist/core/concurrency.js +173 -0
  13. package/dist/core/concurrency.js.map +1 -0
  14. package/dist/core/config.d.ts +26 -0
  15. package/dist/core/config.d.ts.map +1 -0
  16. package/dist/core/config.js +18 -0
  17. package/dist/core/config.js.map +1 -0
  18. package/dist/core/cursor.d.ts +23 -0
  19. package/dist/core/cursor.d.ts.map +1 -0
  20. package/dist/core/cursor.js +62 -0
  21. package/dist/core/cursor.js.map +1 -0
  22. package/dist/core/errors.d.ts +96 -0
  23. package/dist/core/errors.d.ts.map +1 -0
  24. package/dist/core/errors.js +356 -0
  25. package/dist/core/errors.js.map +1 -0
  26. package/dist/core/file-uri.d.ts +24 -0
  27. package/dist/core/file-uri.d.ts.map +1 -0
  28. package/dist/core/file-uri.js +52 -0
  29. package/dist/core/file-uri.js.map +1 -0
  30. package/dist/core/fmt.d.ts +37 -0
  31. package/dist/core/fmt.d.ts.map +1 -0
  32. package/dist/core/fmt.js +93 -0
  33. package/dist/core/fmt.js.map +1 -0
  34. package/dist/core/fs.d.ts +108 -0
  35. package/dist/core/fs.d.ts.map +1 -0
  36. package/dist/core/fs.js +243 -0
  37. package/dist/core/fs.js.map +1 -0
  38. package/dist/core/glob.d.ts +41 -0
  39. package/dist/core/glob.d.ts.map +1 -0
  40. package/dist/core/glob.js +399 -0
  41. package/dist/core/glob.js.map +1 -0
  42. package/dist/core/input-required.d.ts +147 -0
  43. package/dist/core/input-required.d.ts.map +1 -0
  44. package/dist/core/input-required.js +261 -0
  45. package/dist/core/input-required.js.map +1 -0
  46. package/dist/core/mime.d.ts +15 -0
  47. package/dist/core/mime.d.ts.map +1 -0
  48. package/dist/core/mime.js +188 -0
  49. package/dist/core/mime.js.map +1 -0
  50. package/dist/core/observability.d.ts +10 -0
  51. package/dist/core/observability.d.ts.map +1 -0
  52. package/dist/core/observability.js +76 -0
  53. package/dist/core/observability.js.map +1 -0
  54. package/dist/core/page-store.d.ts +41 -0
  55. package/dist/core/page-store.d.ts.map +1 -0
  56. package/dist/core/page-store.js +74 -0
  57. package/dist/core/page-store.js.map +1 -0
  58. package/dist/core/path-completer.d.ts +10 -0
  59. package/dist/core/path-completer.d.ts.map +1 -0
  60. package/dist/core/path-completer.js +227 -0
  61. package/dist/core/path-completer.js.map +1 -0
  62. package/dist/core/path-discovery.d.ts +8 -0
  63. package/dist/core/path-discovery.d.ts.map +1 -0
  64. package/dist/core/path-discovery.js +105 -0
  65. package/dist/core/path-discovery.js.map +1 -0
  66. package/dist/core/path-utils.d.ts +9 -0
  67. package/dist/core/path-utils.d.ts.map +1 -0
  68. package/dist/core/path-utils.js +121 -0
  69. package/dist/core/path-utils.js.map +1 -0
  70. package/dist/core/path.d.ts +148 -0
  71. package/dist/core/path.d.ts.map +1 -0
  72. package/dist/core/path.js +674 -0
  73. package/dist/core/path.js.map +1 -0
  74. package/dist/core/primitives.d.ts +23 -0
  75. package/dist/core/primitives.d.ts.map +1 -0
  76. package/dist/core/primitives.js +54 -0
  77. package/dist/core/primitives.js.map +1 -0
  78. package/dist/core/read.d.ts +74 -0
  79. package/dist/core/read.d.ts.map +1 -0
  80. package/dist/core/read.js +562 -0
  81. package/dist/core/read.js.map +1 -0
  82. package/dist/core/schema.d.ts +139 -0
  83. package/dist/core/schema.d.ts.map +1 -0
  84. package/dist/core/schema.js +348 -0
  85. package/dist/core/schema.js.map +1 -0
  86. package/dist/core/search.d.ts +100 -0
  87. package/dist/core/search.d.ts.map +1 -0
  88. package/dist/core/search.js +248 -0
  89. package/dist/core/search.js.map +1 -0
  90. package/dist/core/sensitive.d.ts +6 -0
  91. package/dist/core/sensitive.d.ts.map +1 -0
  92. package/dist/core/sensitive.js +142 -0
  93. package/dist/core/sensitive.js.map +1 -0
  94. package/dist/core/store.d.ts +57 -0
  95. package/dist/core/store.d.ts.map +1 -0
  96. package/dist/core/store.js +162 -0
  97. package/dist/core/store.js.map +1 -0
  98. package/dist/core/util.d.ts +24 -0
  99. package/dist/core/util.d.ts.map +1 -0
  100. package/dist/core/util.js +65 -0
  101. package/dist/core/util.js.map +1 -0
  102. package/dist/core/watcher-registry.d.ts +68 -0
  103. package/dist/core/watcher-registry.d.ts.map +1 -0
  104. package/dist/core/watcher-registry.js +312 -0
  105. package/dist/core/watcher-registry.js.map +1 -0
  106. package/dist/http-policy.d.ts +121 -0
  107. package/dist/http-policy.d.ts.map +1 -0
  108. package/dist/http-policy.js +340 -0
  109. package/dist/http-policy.js.map +1 -0
  110. package/dist/index.d.ts +1 -0
  111. package/dist/index.d.ts.map +1 -0
  112. package/dist/index.js +77 -41
  113. package/dist/index.js.map +1 -0
  114. package/dist/instructions.d.ts +11 -0
  115. package/dist/instructions.d.ts.map +1 -0
  116. package/dist/instructions.js +70 -0
  117. package/dist/instructions.js.map +1 -0
  118. package/dist/prompts.d.ts +8 -6
  119. package/dist/prompts.d.ts.map +1 -0
  120. package/dist/prompts.js +74 -168
  121. package/dist/prompts.js.map +1 -0
  122. package/dist/resources.d.ts +73 -9
  123. package/dist/resources.d.ts.map +1 -0
  124. package/dist/resources.js +392 -163
  125. package/dist/resources.js.map +1 -0
  126. package/dist/server.d.ts +62 -1
  127. package/dist/server.d.ts.map +1 -0
  128. package/dist/server.js +139 -1
  129. package/dist/server.js.map +1 -0
  130. package/dist/tools/batch.d.ts +89 -0
  131. package/dist/tools/batch.d.ts.map +1 -0
  132. package/dist/tools/batch.js +157 -0
  133. package/dist/tools/batch.js.map +1 -0
  134. package/dist/tools/create.d.ts +2 -0
  135. package/dist/tools/create.d.ts.map +1 -0
  136. package/dist/tools/create.js +124 -0
  137. package/dist/tools/create.js.map +1 -0
  138. package/dist/tools/define.d.ts +102 -0
  139. package/dist/tools/define.d.ts.map +1 -0
  140. package/dist/tools/define.js +404 -0
  141. package/dist/tools/define.js.map +1 -0
  142. package/dist/tools/delete-file.d.ts +2 -4
  143. package/dist/tools/delete-file.d.ts.map +1 -0
  144. package/dist/tools/delete-file.js +327 -87
  145. package/dist/tools/delete-file.js.map +1 -0
  146. package/dist/tools/diff.d.ts +2 -0
  147. package/dist/tools/diff.d.ts.map +1 -0
  148. package/dist/tools/diff.js +94 -0
  149. package/dist/tools/diff.js.map +1 -0
  150. package/dist/tools/edit.d.ts +2 -0
  151. package/dist/tools/edit.d.ts.map +1 -0
  152. package/dist/tools/edit.js +440 -0
  153. package/dist/tools/edit.js.map +1 -0
  154. package/dist/tools/index.d.ts +26 -0
  155. package/dist/tools/index.d.ts.map +1 -0
  156. package/dist/tools/index.js +52 -0
  157. package/dist/tools/index.js.map +1 -0
  158. package/dist/tools/list.d.ts +2 -0
  159. package/dist/tools/list.d.ts.map +1 -0
  160. package/dist/tools/list.js +301 -0
  161. package/dist/tools/list.js.map +1 -0
  162. package/dist/tools/move.d.ts +2 -0
  163. package/dist/tools/move.d.ts.map +1 -0
  164. package/dist/tools/move.js +321 -0
  165. package/dist/tools/move.js.map +1 -0
  166. package/dist/tools/patch.d.ts +2 -0
  167. package/dist/tools/patch.d.ts.map +1 -0
  168. package/dist/tools/patch.js +155 -0
  169. package/dist/tools/patch.js.map +1 -0
  170. package/dist/tools/progress.d.ts +52 -0
  171. package/dist/tools/progress.d.ts.map +1 -0
  172. package/dist/tools/progress.js +172 -0
  173. package/dist/tools/progress.js.map +1 -0
  174. package/dist/tools/read.d.ts +2 -4
  175. package/dist/tools/read.d.ts.map +1 -0
  176. package/dist/tools/read.js +423 -150
  177. package/dist/tools/read.js.map +1 -0
  178. package/dist/tools/replace-in-files.d.ts +2 -4
  179. package/dist/tools/replace-in-files.d.ts.map +1 -0
  180. package/dist/tools/replace-in-files.js +432 -264
  181. package/dist/tools/replace-in-files.js.map +1 -0
  182. package/dist/tools/roots.d.ts +2 -4
  183. package/dist/tools/roots.d.ts.map +1 -0
  184. package/dist/tools/roots.js +45 -52
  185. package/dist/tools/roots.js.map +1 -0
  186. package/dist/tools/search-content.d.ts +2 -4
  187. package/dist/tools/search-content.d.ts.map +1 -0
  188. package/dist/tools/search-content.js +262 -228
  189. package/dist/tools/search-content.js.map +1 -0
  190. package/dist/tools/search-files.d.ts +2 -4
  191. package/dist/tools/search-files.d.ts.map +1 -0
  192. package/dist/tools/search-files.js +162 -161
  193. package/dist/tools/search-files.js.map +1 -0
  194. package/dist/tools/stat.d.ts +2 -4
  195. package/dist/tools/stat.d.ts.map +1 -0
  196. package/dist/tools/stat.js +189 -62
  197. package/dist/tools/stat.js.map +1 -0
  198. package/dist/transport/http.d.ts +5 -0
  199. package/dist/transport/http.d.ts.map +1 -0
  200. package/dist/transport/http.js +276 -0
  201. package/dist/transport/http.js.map +1 -0
  202. package/dist/transport/shared.d.ts +45 -0
  203. package/dist/transport/shared.d.ts.map +1 -0
  204. package/dist/transport/shared.js +65 -0
  205. package/dist/transport/shared.js.map +1 -0
  206. package/dist/transport/stdio.d.ts +32 -0
  207. package/dist/transport/stdio.d.ts.map +1 -0
  208. package/dist/transport/stdio.js +279 -0
  209. package/dist/transport/stdio.js.map +1 -0
  210. package/dist/transport.d.ts +5 -0
  211. package/dist/transport.d.ts.map +1 -0
  212. package/dist/transport.js +4 -0
  213. package/dist/transport.js.map +1 -0
  214. package/package.json +37 -46
  215. package/dist/assets/logo.svg +0 -35
  216. package/dist/completions.d.ts +0 -2
  217. package/dist/completions.js +0 -572
  218. package/dist/config.d.ts +0 -119
  219. package/dist/config.js +0 -31
  220. package/dist/lib/abort.d.ts +0 -7
  221. package/dist/lib/abort.js +0 -81
  222. package/dist/lib/constants.d.ts +0 -42
  223. package/dist/lib/constants.js +0 -313
  224. package/dist/lib/errors.d.ts +0 -28
  225. package/dist/lib/errors.js +0 -301
  226. package/dist/lib/file-operations/core.d.ts +0 -53
  227. package/dist/lib/file-operations/core.js +0 -144
  228. package/dist/lib/file-operations/metadata.d.ts +0 -77
  229. package/dist/lib/file-operations/metadata.js +0 -878
  230. package/dist/lib/file-operations/search.d.ts +0 -46
  231. package/dist/lib/file-operations/search.js +0 -1194
  232. package/dist/lib/file-operations/traversal.d.ts +0 -40
  233. package/dist/lib/file-operations/traversal.js +0 -310
  234. package/dist/lib/fs-helpers.d.ts +0 -46
  235. package/dist/lib/fs-helpers.js +0 -606
  236. package/dist/lib/globs.d.ts +0 -2
  237. package/dist/lib/globs.js +0 -19
  238. package/dist/lib/logger.d.ts +0 -27
  239. package/dist/lib/logger.js +0 -91
  240. package/dist/lib/observability.d.ts +0 -33
  241. package/dist/lib/observability.js +0 -373
  242. package/dist/lib/paths.d.ts +0 -35
  243. package/dist/lib/paths.js +0 -634
  244. package/dist/lib/resource-store.d.ts +0 -28
  245. package/dist/lib/resource-store.js +0 -195
  246. package/dist/lib/utils.d.ts +0 -7
  247. package/dist/lib/utils.js +0 -37
  248. package/dist/lib/zod-codecs.d.ts +0 -2
  249. package/dist/lib/zod-codecs.js +0 -18
  250. package/dist/pkg-info.d.ts +0 -7
  251. package/dist/pkg-info.js +0 -9
  252. package/dist/resources/generated-instructions.d.ts +0 -1
  253. package/dist/resources/generated-instructions.js +0 -105
  254. package/dist/resources/tool-catalog.d.ts +0 -2
  255. package/dist/resources/tool-catalog.js +0 -78
  256. package/dist/resources/tool-info.d.ts +0 -9
  257. package/dist/resources/tool-info.js +0 -206
  258. package/dist/resources/workflows.d.ts +0 -1
  259. package/dist/resources/workflows.js +0 -72
  260. package/dist/schemas.d.ts +0 -505
  261. package/dist/schemas.js +0 -676
  262. package/dist/server/bootstrap.d.ts +0 -6
  263. package/dist/server/bootstrap.js +0 -585
  264. package/dist/server/event-store.d.ts +0 -18
  265. package/dist/server/event-store.js +0 -71
  266. package/dist/server/roots-manager.d.ts +0 -28
  267. package/dist/server/roots-manager.js +0 -227
  268. package/dist/server/task-store.d.ts +0 -10
  269. package/dist/server/task-store.js +0 -91
  270. package/dist/tools/apply-patch.d.ts +0 -4
  271. package/dist/tools/apply-patch.js +0 -235
  272. package/dist/tools/calculate-hash.d.ts +0 -4
  273. package/dist/tools/calculate-hash.js +0 -187
  274. package/dist/tools/contract.d.ts +0 -50
  275. package/dist/tools/contract.js +0 -1
  276. package/dist/tools/create-directory.d.ts +0 -4
  277. package/dist/tools/create-directory.js +0 -68
  278. package/dist/tools/diff-files.d.ts +0 -4
  279. package/dist/tools/diff-files.js +0 -136
  280. package/dist/tools/edit-file.d.ts +0 -4
  281. package/dist/tools/edit-file.js +0 -256
  282. package/dist/tools/icons.d.ts +0 -15
  283. package/dist/tools/icons.js +0 -24
  284. package/dist/tools/list-directory.d.ts +0 -4
  285. package/dist/tools/list-directory.js +0 -218
  286. package/dist/tools/move-file.d.ts +0 -4
  287. package/dist/tools/move-file.js +0 -178
  288. package/dist/tools/read-multiple.d.ts +0 -4
  289. package/dist/tools/read-multiple.js +0 -207
  290. package/dist/tools/shared.d.ts +0 -172
  291. package/dist/tools/shared.js +0 -599
  292. package/dist/tools/stat-many.d.ts +0 -4
  293. package/dist/tools/stat-many.js +0 -104
  294. package/dist/tools/task-support.d.ts +0 -29
  295. package/dist/tools/task-support.js +0 -473
  296. package/dist/tools/tree.d.ts +0 -4
  297. package/dist/tools/tree.js +0 -102
  298. package/dist/tools/write-file.d.ts +0 -4
  299. package/dist/tools/write-file.js +0 -60
  300. package/dist/tools.d.ts +0 -5
  301. package/dist/tools.js +0 -54
@@ -0,0 +1,124 @@
1
+ import { basename, dirname } from 'node:path';
2
+ import * as z from 'zod/v4';
3
+ import { ErrorCode } from '../core/errors.js';
4
+ import { buildFileResourceLink, buildFileResourceUri } from '../core/file-uri.js';
5
+ import { detectMimeFromContent } from '../core/mime.js';
6
+ import { countLines } from '../core/read.js';
7
+ import { FileKind, IsoDateTime, NonNegInt, PathFailureSchema, RequiredPath, } from '../core/schema.js';
8
+ import { getMaxTextFileSize } from '../core/util.js';
9
+ import { runOverPaths } from './batch.js';
10
+ import { defineTool } from './define.js';
11
+ const CreateFileItemSchema = z.strictObject({
12
+ path: RequiredPath.describe('Absolute path where the file will be created'),
13
+ content: z
14
+ .string()
15
+ .refine((val) => val.length <= getMaxTextFileSize(), {
16
+ message: 'Content exceeds maximum allowed text file size',
17
+ })
18
+ .describe('Text content to write, verbatim.'),
19
+ });
20
+ const CreateFileResultSchema = z.strictObject({
21
+ path: z.string().describe('Resolved absolute path of the created file'),
22
+ size: NonNegInt.describe('File size in bytes after writing'),
23
+ lineCount: NonNegInt.describe('Number of lines in the written file'),
24
+ mimeType: z.string().describe('Detected MIME type of the file'),
25
+ kind: FileKind.describe('Broad file kind: text, binary, image, audio, or pdf'),
26
+ resourceUri: z
27
+ .string()
28
+ .describe('Resource URI pointing to the created file content in the resource store'),
29
+ created: IsoDateTime.describe('File creation timestamp (ISO 8601 UTC)'),
30
+ modified: IsoDateTime.describe('File last-modification timestamp (ISO 8601 UTC)'),
31
+ });
32
+ const CreateInputSchema = z.strictObject({
33
+ files: z
34
+ .array(CreateFileItemSchema)
35
+ .min(1)
36
+ .max(100)
37
+ .describe('List of files to create (max 100); each entry requires path and content'),
38
+ });
39
+ const CreateOutputSchema = z.strictObject({
40
+ files: z.array(CreateFileResultSchema).describe('Successfully created files'),
41
+ failures: z
42
+ .array(PathFailureSchema)
43
+ .optional()
44
+ .describe('Files that failed to create with per-file error details'),
45
+ });
46
+ export const CREATE = defineTool({
47
+ name: 'create',
48
+ title: 'Create Files',
49
+ description: 'Create one or more files (max 100), writing or overwriting content and creating parent directories as needed. ' +
50
+ 'Pass files: [{ path, content }] — there is no single-path form. ' +
51
+ 'Silently overwrites existing files — read first if you need to preserve existing content.',
52
+ input: CreateInputSchema,
53
+ output: CreateOutputSchema,
54
+ annotations: {
55
+ readOnlyHint: false,
56
+ idempotentHint: false,
57
+ destructiveHint: true,
58
+ openWorldHint: false,
59
+ },
60
+ accessPaths: (args) => args.files.map((f) => f.path),
61
+ run: async (args, ctx) => {
62
+ const batch = await runOverPaths({ files: args.files }, ctx, async ({ path, override }) => {
63
+ const content = override?.content ?? '';
64
+ await ctx.fs.mkdir(dirname(path), { recursive: true });
65
+ const { validPath } = await ctx.fs.writeFile(path, content, {
66
+ encoding: 'utf-8',
67
+ signal: ctx.signal,
68
+ });
69
+ const { stats: fileStats } = await ctx.fs.stat(path, { signal: ctx.signal });
70
+ const bytesWritten = Buffer.byteLength(content, 'utf-8');
71
+ const lineCount = countLines(content);
72
+ const mimeInfo = detectMimeFromContent(validPath, content);
73
+ const resourceUri = buildFileResourceUri(validPath);
74
+ const file = {
75
+ path: validPath,
76
+ size: bytesWritten,
77
+ lineCount,
78
+ mimeType: mimeInfo.mimeType,
79
+ kind: mimeInfo.kind,
80
+ resourceUri,
81
+ created: fileStats.birthtime.toISOString(),
82
+ modified: fileStats.mtime.toISOString(),
83
+ };
84
+ return ctx.resourceStore
85
+ ? {
86
+ file,
87
+ resourceLink: buildFileResourceLink(validPath, mimeInfo.mimeType, bytesWritten),
88
+ }
89
+ : { file };
90
+ }, { defaultErrorCode: ErrorCode.UNKNOWN });
91
+ const results = [];
92
+ const failures = [];
93
+ const links = [];
94
+ for (const r of batch.results) {
95
+ if ('error' in r) {
96
+ failures.push({ path: r.path, error: r.error });
97
+ continue;
98
+ }
99
+ results.push(r.value.file);
100
+ if (r.value.resourceLink)
101
+ links.push(r.value.resourceLink);
102
+ }
103
+ const structured = {
104
+ files: results,
105
+ ...(failures.length > 0 ? { failures } : {}),
106
+ };
107
+ // No `text` on purpose. The one-line roster this used to build named the
108
+ // paths and dropped every per-file size, line count and error the caller
109
+ // asked `create` for. Supplying no text makes this a data tool, so
110
+ // `define.ts` renders the JSON and keeps it in `structuredContent`.
111
+ if (links.length > 0) {
112
+ return { structured, resources: links };
113
+ }
114
+ return { structured };
115
+ },
116
+ progress: (args) => ({
117
+ label: 'Create',
118
+ subject: args.files.length === 1
119
+ ? basename(args.files[0]?.path ?? '')
120
+ : `${String(args.files.length)} files`,
121
+ }),
122
+ defaultErrorCode: ErrorCode.UNKNOWN,
123
+ });
124
+ //# sourceMappingURL=create.js.map
@@ -0,0 +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 { buildFileResourceLink, buildFileResourceUri } from '../core/file-uri.js';\nimport { detectMimeFromContent } from '../core/mime.js';\nimport { countLines } from '../core/read.js';\nimport {\n FileKind,\n IsoDateTime,\n NonNegInt,\n PathFailureSchema,\n RequiredPath,\n} from '../core/schema.js';\nimport { getMaxTextFileSize } from '../core/util.js';\nimport { 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 bytesWritten = Buffer.byteLength(content, 'utf-8');\n const lineCount = countLines(content);\n const mimeInfo = detectMimeFromContent(validPath, content);\n\n const resourceUri = buildFileResourceUri(validPath);\n const file: CreateFileResult = {\n path: validPath,\n size: bytesWritten,\n lineCount,\n mimeType: mimeInfo.mimeType,\n kind: mimeInfo.kind,\n resourceUri,\n created: fileStats.birthtime.toISOString(),\n modified: fileStats.mtime.toISOString(),\n };\n\n return ctx.resourceStore\n ? {\n file,\n resourceLink: buildFileResourceLink(validPath, mimeInfo.mimeType, bytesWritten),\n }\n : { 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 };\n }\n\n return { structured };\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"]}
@@ -0,0 +1,102 @@
1
+ import type { AuthInfo, 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';
10
+ export interface ToolCtx {
11
+ readonly signal: AbortSignal;
12
+ readonly sessionId?: string;
13
+ readonly authInfo?: AuthInfo;
14
+ readonly _meta?: RequestMeta | undefined;
15
+ readonly fs: GuardedFileSystem;
16
+ readonly pageStore: PageSnapshotStore;
17
+ readonly resourceStore: ResourceStore | undefined;
18
+ /** Emits a log line to stderr via `Logger.emit`, gated by `LOG_LEVEL`. */
19
+ readonly log?: (level: LoggingLevel, data: unknown, logger?: string) => void;
20
+ readonly sendNotification?: (notification: Notification) => Promise<void>;
21
+ readonly onProgress?: (params: {
22
+ current: number;
23
+ total?: number;
24
+ }) => void;
25
+ /**
26
+ * Multi-round-trip `inputResponses` carried by a retried `tools/call`
27
+ * (protocol revision 2026-07-28); `undefined` on the first round. Bare,
28
+ * unvalidated client values — handlers read them through
29
+ * `readAcceptedConfirm`.
30
+ */
31
+ readonly inputResponses?: Record<string, unknown> | undefined;
32
+ /**
33
+ * Reads the verified multi-round-trip `requestState` for the current round:
34
+ * the decoded `PendingState` the `requestStateCodec` verified, or `undefined`
35
+ * when the round carried no state (the first round). `undefined` when no
36
+ * live request context is available.
37
+ */
38
+ readonly requestState?: RequestStateAccessor | undefined;
39
+ /**
40
+ * What the connected client declared it can do, or `undefined` when this
41
+ * connection cannot say. Read by the `input_required` flows: an embedded
42
+ * elicitation sent to a client that never declared `elicitation` is rejected
43
+ * by the SDK with `-32021` before it reaches the wire, so the handlers check
44
+ * first and answer with an actionable tool error instead.
45
+ *
46
+ * Two sources, because the eras differ: a legacy connection negotiated them
47
+ * at `initialize` (`Server.getClientCapabilities()`), a modern one carries
48
+ * them per request in the `_meta` envelope and never runs `initialize` at
49
+ * all. `undefined` means "cannot tell" — never "no capabilities".
50
+ */
51
+ readonly clientCapabilities?: ClientCapabilities | undefined;
52
+ readonly server?: McpServer;
53
+ }
54
+ interface ToolDeps {
55
+ readonly server: McpServer;
56
+ readonly pathGuard: PathGuard;
57
+ readonly pageStore: PageSnapshotStore;
58
+ readonly resourceStore: ResourceStore | undefined;
59
+ }
60
+ interface RunResult<T> {
61
+ readonly structured: T;
62
+ readonly text?: string;
63
+ readonly resources?: ContentBlock[];
64
+ }
65
+ /**
66
+ * `ToolAnnotations` with `readOnlyHint` required. It is optional in the SDK, but
67
+ * MUTATING_TOOL_NAMES derives the `--read-only` gate from it, so a tool that
68
+ * forgets it must be a compile error rather than a silent reclassification.
69
+ */
70
+ type DeclaredAnnotations = ToolAnnotations & {
71
+ readonly readOnlyHint: boolean;
72
+ };
73
+ export interface ToolDef<I extends z.ZodType, O extends z.ZodType> {
74
+ readonly name: string;
75
+ readonly title: string;
76
+ readonly description: string;
77
+ readonly input: I;
78
+ readonly output: O;
79
+ readonly annotations: DeclaredAnnotations;
80
+ readonly timeoutMs?: number;
81
+ readonly progress?: (args: z.infer<I>) => ProgressCtx;
82
+ readonly progressDone?: (args: z.infer<I>, result: z.infer<O>) => Partial<ProgressCtx>;
83
+ readonly defaultErrorCode?: ErrorCode;
84
+ /**
85
+ * The filesystem paths this tool will operate on, extracted from its parsed
86
+ * args. The executor runs an access-grant pre-check over these BEFORE `run`
87
+ * (R7): any path outside the granted roots yields an `input_required`
88
+ * round-trip requesting a grant, and nothing is touched until the client
89
+ * retries with an accepted grant (R8). Omit for tools that do not operate on
90
+ * caller-supplied filesystem paths.
91
+ */
92
+ readonly accessPaths?: (args: z.infer<I>) => readonly string[];
93
+ readonly run: (args: z.infer<I>, ctx: ToolCtx) => Promise<RunResult<z.infer<O>> | InputRequiredResult>;
94
+ }
95
+ export interface DefinedTool {
96
+ readonly name: string;
97
+ readonly annotations: DeclaredAnnotations;
98
+ register(deps: ToolDeps): RegisteredTool;
99
+ }
100
+ export declare function defineTool<I extends z.ZodType, O extends z.ZodType>(def: ToolDef<I, O>): DefinedTool;
101
+ export {};
102
+ //# sourceMappingURL=define.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../../src/tools/define.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,404 @@
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 { confirmInput, 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';
10
+ function toToolCtx(ctx, deps) {
11
+ if (!ctx) {
12
+ const signal = new AbortController().signal;
13
+ return {
14
+ signal,
15
+ fs: new GuardedFileSystem(deps.pathGuard),
16
+ pageStore: deps.pageStore,
17
+ resourceStore: deps.resourceStore,
18
+ server: deps.server,
19
+ };
20
+ }
21
+ // Envelope first, accessor second — the two eras carry this differently.
22
+ // A modern request states the capabilities in its own `_meta` envelope; a
23
+ // legacy connection fixed them at `initialize` and has no envelope at all.
24
+ // The deprecated accessor still answers for both today (the SDK backfills it
25
+ // per request from the validated envelope on modern instances), but reading
26
+ // the envelope directly is the supported path, so it leads: when the accessor
27
+ // goes, legacy degrades to "cannot tell", which fails open to the behavior
28
+ // that predates this check.
29
+ //
30
+ // The cast is the SDK's shipped `RequestMetaEnvelope` declaration collapsing
31
+ // to `{}` — the reserved keys survive at runtime but not in the .d.ts, so the
32
+ // key constant cannot index the declared type.
33
+ const envelope = ctx.mcpReq.envelope;
34
+ const clientCapabilities = envelope?.[CLIENT_CAPABILITIES_META_KEY] ??
35
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- the only source of client capabilities on a legacy connection, where no envelope exists.
36
+ deps.server.server.getClientCapabilities();
37
+ return {
38
+ signal: ctx.mcpReq.signal,
39
+ ...(ctx.sessionId ? { sessionId: ctx.sessionId } : {}),
40
+ ...(ctx.http?.authInfo ? { authInfo: ctx.http.authInfo } : {}),
41
+ ...(ctx.mcpReq._meta ? { _meta: ctx.mcpReq._meta } : {}),
42
+ fs: new GuardedFileSystem(deps.pathGuard),
43
+ pageStore: deps.pageStore,
44
+ resourceStore: deps.resourceStore,
45
+ sendNotification: async (notification) => ctx.mcpReq.notify(notification),
46
+ inputResponses: ctx.mcpReq.inputResponses,
47
+ requestState: ctx.mcpReq.requestState,
48
+ ...(clientCapabilities ? { clientCapabilities } : {}),
49
+ server: deps.server,
50
+ };
51
+ }
52
+ function resolveProgressCtx(def, args) {
53
+ if (!def.progress)
54
+ return { label: def.title };
55
+ try {
56
+ return def.progress(args);
57
+ }
58
+ catch (err) {
59
+ Logger.warn(`resolveProgressCtx: ${def.name}.progress threw: ${String(err)}`);
60
+ return { label: def.title };
61
+ }
62
+ }
63
+ function composeSignal(base, timeoutMs) {
64
+ if (!timeoutMs)
65
+ return base;
66
+ return AbortSignal.any([base, AbortSignal.timeout(timeoutMs)]);
67
+ }
68
+ /**
69
+ * A batch result where every path failed. The call produced no work at all, so
70
+ * it is a failed call — `isError` must say so. Partial failure is deliberately
71
+ * NOT an error: the per-path entries carry which paths failed and the succeeded
72
+ * ones really were done.
73
+ *
74
+ * Two result shapes reach here. Most batch tools carry a `summary`. `create` and
75
+ * `move` do not: they publish `{files|moves, failures, skipped}` (see
76
+ * create.ts's CreateOutputSchema and move.ts's MoveOutputSchema), so a
77
+ * summary-only check reported a fully-denied write as a successful call. Both
78
+ * shapes are handled here rather than by growing the two output schemas, which
79
+ * are the client-facing contract.
80
+ */
81
+ function isTotalBatchFailure(structured) {
82
+ if (typeof structured !== 'object' || structured === null)
83
+ return false;
84
+ const result = structured;
85
+ const { summary } = result;
86
+ if (typeof summary?.total === 'number') {
87
+ return summary.total > 0 && summary.failed === summary.total;
88
+ }
89
+ const len = (value) => (Array.isArray(value) ? value.length : 0);
90
+ if (len(result.failures) === 0)
91
+ return false;
92
+ // A skipped entry is work the caller asked to skip, not work that failed —
93
+ // a self-move that was silently dropped means the call did something.
94
+ return len(result.files) + len(result.moves) + len(result.skipped) === 0;
95
+ }
96
+ /**
97
+ * Claude Code — and any client that treats `structuredContent` as the canonical
98
+ * model view — discards the `text` blocks when `structuredContent` is present,
99
+ * showing the model `JSON.stringify(structuredContent)` instead. A tool that
100
+ * authored a text result (list's ASCII tree, read's file) meant that text for
101
+ * the model, so it ships its metadata under `_meta`, which no client renders in
102
+ * place of the content. Tools with no `text` keep `structuredContent`: the JSON
103
+ * *is* their model-facing view.
104
+ */
105
+ function buildSuccessResponse(result) {
106
+ const hasText = result.text !== undefined;
107
+ const text = result.text ?? JSON.stringify(result.structured);
108
+ const content = [{ type: 'text', text }, ...(result.resources ?? [])];
109
+ return {
110
+ content,
111
+ ...(hasText
112
+ ? // Safe because every `ToolDef.output` is a Zod object schema, so
113
+ // `structured` is always a plain object. Constraining `O` to prove it
114
+ // is not worth the cost: the bound has to thread through five generic
115
+ // sites and still loses to `exactOptionalPropertyTypes` variance on
116
+ // `RunResult`'s optional fields.
117
+ { _meta: result.structured }
118
+ : { structuredContent: result.structured }),
119
+ ...(isTotalBatchFailure(result.structured) ? { isError: true } : {}),
120
+ };
121
+ }
122
+ class ToolExecutor {
123
+ signal;
124
+ def;
125
+ parsedArgs;
126
+ toolCtx;
127
+ #progressClosed = false;
128
+ #progressCtx;
129
+ #mcpSink;
130
+ #progressSession;
131
+ constructor(toolName, ctx, def, parsedArgs) {
132
+ this.def = def;
133
+ this.parsedArgs = parsedArgs;
134
+ this.signal = composeSignal(ctx.signal, def.timeoutMs);
135
+ this.#progressCtx = resolveProgressCtx(def, parsedArgs);
136
+ const sinks = [];
137
+ if (ctx._meta?.progressToken !== undefined && ctx.sendNotification !== undefined) {
138
+ this.#mcpSink = new McpProgressSink(toolName, ctx._meta.progressToken, ctx.sendNotification);
139
+ sinks.push(this.#mcpSink);
140
+ }
141
+ const isTest = process.env['NODE_ENV'] === 'test' || process.execArgv.includes('--test');
142
+ this.#progressSession = new ProgressSession({
143
+ label: this.#progressCtx.label,
144
+ sinks,
145
+ ...(isTest ? { rateLimitMs: 0 } : {}),
146
+ });
147
+ this.toolCtx = {
148
+ ...ctx,
149
+ signal: this.signal,
150
+ log: (level, data, logger) => {
151
+ const msg = typeof data === 'string' ? data : String(data);
152
+ const prefix = logger ? `[${logger}] ` : '';
153
+ Logger.emit(level, `${prefix}${msg}`);
154
+ },
155
+ onProgress: (p) => {
156
+ this.#tick(p);
157
+ },
158
+ };
159
+ }
160
+ #tick(p) {
161
+ if (this.#progressClosed)
162
+ return;
163
+ const tickCtx = {
164
+ ...this.#progressCtx,
165
+ current: p.current,
166
+ ...(p.total !== undefined ? { total: p.total } : {}),
167
+ };
168
+ this.#progressSession.set({ ...p, message: plainMessage('tick', tickCtx) });
169
+ }
170
+ async #closeWithDone(message) {
171
+ this.#progressClosed = true;
172
+ this.#progressSession.complete(message);
173
+ if (this.#mcpSink)
174
+ await this.#mcpSink.flush();
175
+ }
176
+ async #closeWithFail(error, message) {
177
+ this.#progressClosed = true;
178
+ this.#progressSession.fail(error, message);
179
+ if (this.#mcpSink)
180
+ await this.#mcpSink.flush();
181
+ }
182
+ async #flushProgress() {
183
+ if (this.#mcpSink)
184
+ await this.#mcpSink.flush();
185
+ }
186
+ async completeProgress(result) {
187
+ const doneCtx = this.def.progressDone
188
+ ? { ...this.#progressCtx, ...this.def.progressDone(this.parsedArgs, result) }
189
+ : this.#progressCtx;
190
+ await this.#closeWithDone(plainMessage('done', doneCtx));
191
+ }
192
+ async failProgress(error) {
193
+ const errMsg = formatUnknownErrorMessage(error);
194
+ const message = plainMessage('fail', { ...this.#progressCtx, error: errMsg });
195
+ await this.#closeWithFail(error, message);
196
+ const { text: errorText } = Problem.toText(error, this.def.defaultErrorCode ?? ErrorCode.UNKNOWN);
197
+ return {
198
+ content: [{ type: 'text', text: errorText }],
199
+ isError: true,
200
+ };
201
+ }
202
+ /**
203
+ * Access-grant pre-check (R7/R8/R9). Before `run` touches the filesystem,
204
+ * ask PathGuard which of the tool's declared paths are out-of-root and
205
+ * grantable. If any are and this round carries no verified `requestState`,
206
+ * return `input_required` (nothing is read or written — R7). On retry, the
207
+ * verified state must bind this grant set (R9); apply each accepted grant for
208
+ * the session (R8) and proceed. A declined or ungrantable path is left for
209
+ * `validateAccess` to fail closed during the operation. Returns `undefined`
210
+ * when no round-trip is needed and any applicable grants have been applied.
211
+ */
212
+ async precheckGrant() {
213
+ const extract = this.def.accessPaths;
214
+ if (!extract)
215
+ return undefined;
216
+ const paths = extract(this.parsedArgs);
217
+ if (paths.length === 0)
218
+ return undefined;
219
+ const grantDirs = await this.toolCtx.fs.pathGuard.precheckAccess(paths);
220
+ if (grantDirs.length === 0)
221
+ return undefined;
222
+ // One grant dir: a single boolean confirm. Multiple: one multi-select
223
+ // elicitation so the client picks the subset to allow in one round-trip
224
+ // (N dirs → 1 round-trip, not N). `pendingRoundTrip` still binds
225
+ // `paths: grantDirs` sorted, so R9 path-binding is unaffected.
226
+ const multi = grantDirs.length > 1;
227
+ const round = await pendingRoundTrip({
228
+ op: 'grant',
229
+ pending: grantDirs,
230
+ requestState: this.toolCtx.requestState,
231
+ clientCapabilities: this.toolCtx.clientCapabilities,
232
+ buildInputs: (dirs) => multi
233
+ ? [
234
+ multiSelectInput('grant', 'Grant filesystem access to these directories? Select the ones to allow.', dirs.map((d) => ({ value: d, title: d }))),
235
+ ]
236
+ : dirs.map((dir, i) => confirmInput(`confirm_${i}`, `Grant filesystem access to "${dir}"?`)),
237
+ });
238
+ if (round !== undefined)
239
+ return round;
240
+ // Apply accepted grants for the session (R8). Declined/missing dirs are
241
+ // skipped here; their paths fail with ACCESS_DENIED during the operation.
242
+ //
243
+ // No list-changed notification follows: a grant widens the allowed roots,
244
+ // and no resource list reads them. The instructions resource has a fixed
245
+ // URI, the result template lists the ResourceStore, and the file template
246
+ // lists nothing at all (see resources.ts). Notifying here only bought the
247
+ // client a round trip to re-fetch a list that could not have changed.
248
+ //
249
+ // `readAcceptedMultiChoice` does NOT validate against the offered choices
250
+ // (attacker-controlled inputResponses on re-entry). Only grant dirs that
251
+ // were actually offered by precheckAccess — a value outside grantDirs is
252
+ // ignored and fails closed in validateAccess. The single-select path reads
253
+ // `confirm_${i}` for `grantDirs[i]`, so the isSamePath filter is a no-op
254
+ // there; it only bites for the multi-select array.
255
+ const accepted = multi
256
+ ? (readAcceptedMultiChoice(this.toolCtx.inputResponses, 'grant') ?? [])
257
+ : grantDirs.filter((_dir, i) => readAcceptedConfirm(this.toolCtx.inputResponses, `confirm_${i}`));
258
+ for (const dir of accepted) {
259
+ if (!grantDirs.some((g) => isSamePath(g, dir)))
260
+ continue;
261
+ await this.toolCtx.fs.pathGuard.applyGrant(dir);
262
+ }
263
+ return undefined;
264
+ }
265
+ async execute() {
266
+ const runTool = async () => {
267
+ try {
268
+ // Access-grant pre-check before any filesystem touch (R7/R8/R9). A
269
+ // grant input_required short-circuits here (progress paused, not
270
+ // finished); an R9 mismatch throws, which this catch surfaces as an
271
+ // isError tool result like every other handler failure — not a raw
272
+ // JSON-RPC error (GRANT-1 impact #2).
273
+ const grantRequired = await this.precheckGrant();
274
+ if (grantRequired !== undefined)
275
+ return grantRequired;
276
+ const result = await this.def.run(this.parsedArgs, this.toolCtx);
277
+ // input_required is a return value, not a completed call: the client
278
+ // retries the same tools/call carrying inputResponses, and this
279
+ // handler re-enters from the top. Skip the progress "done" close and
280
+ // return it verbatim — progress is paused, not finished.
281
+ if (isInputRequiredResult(result)) {
282
+ return result;
283
+ }
284
+ await this.completeProgress(result.structured);
285
+ return buildSuccessResponse(result);
286
+ }
287
+ catch (error) {
288
+ return await this.failProgress(error);
289
+ }
290
+ finally {
291
+ await this.#flushProgress();
292
+ }
293
+ };
294
+ return runTool();
295
+ }
296
+ }
297
+ function createServerToolHandler(def, deps) {
298
+ return async (args, ctx) => new ToolExecutor(def.name, toToolCtx(ctx, deps), def, args).execute();
299
+ }
300
+ /**
301
+ * Validate with Zod while publishing the precomputed JSON Schema: clients get
302
+ * draft-2020-12, handlers get Zod's coercions, defaults and error messages.
303
+ *
304
+ * Each validator instance is built for exactly one schema and handed straight
305
+ * to `fromJsonSchema(thatSchema, ...)`, which calls `getValidator` once with
306
+ * the same schema — so the argument is redundant here and deliberately ignored.
307
+ * Reusing one instance across schemas would silently validate against `schema`.
308
+ */
309
+ function zodJsonSchemaValidator(schema) {
310
+ return {
311
+ getValidator() {
312
+ return (input) => {
313
+ const result = schema.safeParse(input);
314
+ if (result.success) {
315
+ return {
316
+ valid: true,
317
+ data: result.data,
318
+ errorMessage: undefined,
319
+ };
320
+ }
321
+ return {
322
+ valid: false,
323
+ data: undefined,
324
+ errorMessage: z.prettifyError(result.error),
325
+ };
326
+ };
327
+ },
328
+ };
329
+ }
330
+ /**
331
+ * Generate the wire copy of a schema for `tools/list`. The Zod schema keeps
332
+ * every constraint for runtime validation; the `override` pass only trims what
333
+ * the wire copy costs every client at session start:
334
+ *
335
+ * - `$schema` and `title` carry no information the host uses.
336
+ * - `maximum: Number.MAX_SAFE_INTEGER` is zod's int() artifact, not a bound.
337
+ * - `examples` is dropped in both directions: on output it describes a field the
338
+ * server itself fills in, and on input every description that carried one
339
+ * already spells the same example out inline, so the keyword paid twice.
340
+ *
341
+ * Output `description`s are NOT dropped. They were, and the result was a wire
342
+ * contract the model had to guess at: `delete` returns `path` XOR `paths`,
343
+ * `edit.diff` appears only under dryRun, `read.value` has no required field at
344
+ * all. None of that is inferable from types alone.
345
+ *
346
+ * No shared subschema carries a `.meta({ id })`, so zod inlines every one of
347
+ * them and the emitted document has no `$defs`/`$ref` to dereference.
348
+ */
349
+ function toDraft202012(schema, io) {
350
+ const generated = z.toJSONSchema(schema, {
351
+ target: 'draft-2020-12',
352
+ io,
353
+ override: ({ jsonSchema }) => {
354
+ const node = jsonSchema;
355
+ delete node['title'];
356
+ if (node['maximum'] === Number.MAX_SAFE_INTEGER)
357
+ delete node['maximum'];
358
+ delete node['examples'];
359
+ },
360
+ });
361
+ delete generated['$schema'];
362
+ return generated;
363
+ }
364
+ export function defineTool(def) {
365
+ const inputJsonSchema = toDraft202012(def.input, 'input');
366
+ // Nothing here depends on `deps`, so it is built once per tool definition
367
+ // rather than once per `register` — the HTTP leg registers every tool afresh
368
+ // on each request.
369
+ // Published annotations are derived, not passed through. `readOnlyHint` is
370
+ // load-bearing (MUTATING_TOOL_NAMES derives the --read-only gate from it) and
371
+ // `openWorldHint: false` is a real claim for a filesystem server. The other
372
+ // two describe behavior a read-only tool cannot have, and restate the default
373
+ // for a mutating one — 29 tokens per tool for nothing a client acts on.
374
+ const publishedAnnotations = {
375
+ readOnlyHint: def.annotations.readOnlyHint,
376
+ openWorldHint: def.annotations.openWorldHint ?? false,
377
+ ...(def.annotations.readOnlyHint
378
+ ? {}
379
+ : { destructiveHint: def.annotations.destructiveHint ?? true }),
380
+ };
381
+ const toolDefShape = {
382
+ title: def.title,
383
+ description: def.description,
384
+ inputSchema: fromJsonSchema(inputJsonSchema, zodJsonSchemaValidator(def.input)),
385
+ // No `outputSchema`, ever. Publishing one obliges the result to carry
386
+ // `structuredContent` (clients enforce it), and every tool that authors its
387
+ // own text ships its metadata under `_meta` instead — see
388
+ // `buildSuccessResponse`. The two cannot both hold, and the text is what
389
+ // the model reads. `TOOL-SURFACE-001` pins this for the whole surface.
390
+ //
391
+ // `def.output` stays: it types `RunResult<z.infer<O>>`, which is what makes
392
+ // a tool returning the wrong shape a compile error.
393
+ annotations: publishedAnnotations,
394
+ };
395
+ const tool = {
396
+ name: def.name,
397
+ annotations: def.annotations,
398
+ register(deps) {
399
+ return deps.server.registerTool(def.name, toolDefShape, createServerToolHandler(def, deps));
400
+ },
401
+ };
402
+ return tool;
403
+ }
404
+ //# sourceMappingURL=define.js.map