@j0hanz/filesystem-mcp 2.7.4 → 2.7.6

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 (70) hide show
  1. package/README.md +290 -394
  2. package/dist/cli-help.d.ts.map +1 -1
  3. package/dist/cli-help.js +19 -8
  4. package/dist/cli-help.js.map +1 -1
  5. package/dist/core/concurrency.d.ts +7 -0
  6. package/dist/core/concurrency.d.ts.map +1 -1
  7. package/dist/core/concurrency.js +19 -0
  8. package/dist/core/concurrency.js.map +1 -1
  9. package/dist/core/fs.d.ts +8 -0
  10. package/dist/core/fs.d.ts.map +1 -1
  11. package/dist/core/fs.js +62 -12
  12. package/dist/core/fs.js.map +1 -1
  13. package/dist/core/path.d.ts +7 -16
  14. package/dist/core/path.d.ts.map +1 -1
  15. package/dist/core/path.js +10 -26
  16. package/dist/core/path.js.map +1 -1
  17. package/dist/core/read.d.ts +4 -1
  18. package/dist/core/read.d.ts.map +1 -1
  19. package/dist/core/read.js +13 -3
  20. package/dist/core/read.js.map +1 -1
  21. package/dist/core/search.d.ts +7 -0
  22. package/dist/core/search.d.ts.map +1 -1
  23. package/dist/core/search.js +50 -15
  24. package/dist/core/search.js.map +1 -1
  25. package/dist/core/util.d.ts +17 -0
  26. package/dist/core/util.d.ts.map +1 -1
  27. package/dist/core/util.js +17 -0
  28. package/dist/core/util.js.map +1 -1
  29. package/dist/server.d.ts +2 -0
  30. package/dist/server.d.ts.map +1 -1
  31. package/dist/server.js +8 -0
  32. package/dist/server.js.map +1 -1
  33. package/dist/tools/create.d.ts.map +1 -1
  34. package/dist/tools/create.js +98 -84
  35. package/dist/tools/create.js.map +1 -1
  36. package/dist/tools/define.d.ts +3 -0
  37. package/dist/tools/define.d.ts.map +1 -1
  38. package/dist/tools/define.js +1 -0
  39. package/dist/tools/define.js.map +1 -1
  40. package/dist/tools/edit.d.ts.map +1 -1
  41. package/dist/tools/edit.js +18 -5
  42. package/dist/tools/edit.js.map +1 -1
  43. package/dist/tools/index.d.ts +1 -0
  44. package/dist/tools/index.d.ts.map +1 -1
  45. package/dist/tools/index.js +1 -0
  46. package/dist/tools/index.js.map +1 -1
  47. package/dist/tools/list.js +1 -1
  48. package/dist/tools/list.js.map +1 -1
  49. package/dist/tools/move.d.ts.map +1 -1
  50. package/dist/tools/move.js +35 -5
  51. package/dist/tools/move.js.map +1 -1
  52. package/dist/tools/patch.d.ts.map +1 -1
  53. package/dist/tools/patch.js +91 -86
  54. package/dist/tools/patch.js.map +1 -1
  55. package/dist/tools/read.d.ts.map +1 -1
  56. package/dist/tools/read.js +16 -1
  57. package/dist/tools/read.js.map +1 -1
  58. package/dist/tools/replace-text.d.ts.map +1 -1
  59. package/dist/tools/replace-text.js +38 -15
  60. package/dist/tools/replace-text.js.map +1 -1
  61. package/dist/transport/http-policy.d.ts.map +1 -1
  62. package/dist/transport/http-policy.js +1 -0
  63. package/dist/transport/http-policy.js.map +1 -1
  64. package/dist/transport/http.d.ts.map +1 -1
  65. package/dist/transport/http.js +1 -0
  66. package/dist/transport/http.js.map +1 -1
  67. package/dist/transport/stdio.d.ts.map +1 -1
  68. package/dist/transport/stdio.js +1 -0
  69. package/dist/transport/stdio.js.map +1 -1
  70. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"replace-text.d.ts","sourceRoot":"","sources":["../../src/tools/replace-text.ts"],"names":[],"mappings":"AAgkBA,eAAO,MAAM,YAAY,mCAwEvB,CAAC"}
1
+ {"version":3,"file":"replace-text.d.ts","sourceRoot":"","sources":["../../src/tools/replace-text.ts"],"names":[],"mappings":"AA2lBA,eAAO,MAAM,YAAY,mCA2EvB,CAAC"}
@@ -51,7 +51,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
51
51
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
52
  });
53
53
  import { Buffer, isUtf8 } from 'node:buffer';
54
- import { basename, dirname } from 'node:path';
54
+ import { dirname } from 'node:path';
55
55
  import * as z from 'zod/v4';
56
56
  import { processEntriesConcurrently } from "../core/concurrency.js";
57
57
  import { unifiedPatch } from "../core/diff.js";
@@ -60,9 +60,9 @@ import { buildWrittenFileMeta } from "../core/file-uri.js";
60
60
  import { stoppedEarlyLine, truncateProgressPattern, zeroMatchHint } from "../core/fmt.js";
61
61
  import { globEntries } from "../core/glob.js";
62
62
  import { toPosixRelative } from "../core/path.js";
63
- import { readFileBufferWithLimit } from "../core/read.js";
63
+ import { createTooLargeError, readFileBufferWithLimit } from "../core/read.js";
64
64
  import { defaultFalseBoolean, IncludeHidden, IncludeIgnored, isBlank, MaxDepth, OptionalPath, SafeGlobPattern, } from "../core/schema.js";
65
- import { compileRegex, execMatches, freeRegex, RE2_MAX_INPUT_BYTES } from "../core/search.js";
65
+ import { compileRegex, createYielder, execMatches, freeRegex, RE2_MAX_INPUT_BYTES, } from "../core/search.js";
66
66
  import { DEFAULT_SEARCH_RESULTS, DEFAULT_SEARCH_TIMEOUT_MS, escapeRegExp, getMaxTextFileSize, MAX_SEARCH_RESULTS, PARALLEL_CONCURRENCY, } from "../core/util.js";
67
67
  import { isTotalFailure } from "./batch.js";
68
68
  import { defineTool } from "./define.js";
@@ -175,7 +175,7 @@ function createRegexReplacementMatcher(regex, expandReplacement) {
175
175
  regex.lastIndex = 0;
176
176
  return regex.test(buffer.toString('utf-8'));
177
177
  },
178
- replace(content, replacement) {
178
+ async replace(content, replacement, signal) {
179
179
  // Splicing by hand rather than through `String.replace(regex, …)`: RE2's
180
180
  // own replacer indexes the JS string with the code-point offsets it
181
181
  // reports, which lands the replacement short by one per astral character
@@ -183,6 +183,7 @@ function createRegexReplacementMatcher(regex, expandReplacement) {
183
183
  let matchCount = 0;
184
184
  let updated = '';
185
185
  let cursor = 0;
186
+ const tick = createYielder(signal);
186
187
  for (const match of execMatches(regex, content)) {
187
188
  matchCount++;
188
189
  // Only isRegex=true opts into $1/$& substitution. A literal search
@@ -194,6 +195,8 @@ function createRegexReplacementMatcher(regex, expandReplacement) {
194
195
  ? expandDollarTokens(replacement, match.text, match.groups, match.start, content, match.named)
195
196
  : replacement);
196
197
  cursor = match.end;
198
+ if (await tick())
199
+ signal?.throwIfAborted();
197
200
  }
198
201
  return { content: updated + content.slice(cursor), matchCount };
199
202
  },
@@ -208,13 +211,14 @@ function createCaseSensitiveLiteralMatcher(searchPattern) {
208
211
  testBuffer(buffer) {
209
212
  return buffer.indexOf(searchBuffer) !== -1;
210
213
  },
214
+ // One native O(n) pass; nothing to yield for.
211
215
  replace(content, replacement) {
212
216
  let matchCount = 0;
213
217
  const updated = content.replaceAll(searchPattern, () => {
214
218
  matchCount++;
215
219
  return replacement;
216
220
  });
217
- return { content: updated, matchCount };
221
+ return Promise.resolve({ content: updated, matchCount });
218
222
  },
219
223
  dispose() {
220
224
  // no compiled pattern to release
@@ -224,15 +228,19 @@ function createCaseSensitiveLiteralMatcher(searchPattern) {
224
228
  async function processEntry(entryPath, ctx) {
225
229
  const { options, signal, summary } = ctx;
226
230
  try {
227
- const plan = await readReplacementPlan(entryPath, ctx);
231
+ // Lock the read→write per file so a concurrent writer is not lost; a dry
232
+ // run never writes and skips the lock.
233
+ const plan = options.dryRun
234
+ ? await readReplacementPlan(entryPath, ctx)
235
+ : await ctx.fs.lockForWrite(entryPath, async () => {
236
+ const planned = await readReplacementPlan(entryPath, ctx);
237
+ if (planned)
238
+ await ctx.fs.writeFile(entryPath, planned.updatedContent, { signal });
239
+ return planned;
240
+ });
228
241
  if (!plan) {
229
242
  return;
230
243
  }
231
- if (!options.dryRun) {
232
- await ctx.fs.writeFile(entryPath, plan.updatedContent, {
233
- signal,
234
- });
235
- }
236
244
  // Bookkeep only after the write succeeds (or in dryRun, where there is no
237
245
  // write): a failed write must not count the file as changed.
238
246
  summary.totalMatches += plan.matchCount;
@@ -246,6 +254,11 @@ async function processEntry(entryPath, ctx) {
246
254
  });
247
255
  }
248
256
  catch (error) {
257
+ // A timeout/cancel aborts the whole scan, not just this file: don't record
258
+ // it as a per-file failure. processEntriesConcurrently reports the run as
259
+ // stoppedReason 'timeout' instead.
260
+ if (signal?.aborted)
261
+ return;
249
262
  summary.failedFiles++;
250
263
  recordFailure(summary.failures, {
251
264
  path: toPosixRelative(summary.root, entryPath),
@@ -260,7 +273,7 @@ async function readReplacementPlan(validPath, ctx) {
260
273
  const fileHandle = __addDisposableResource(env_1, await ctx.fs.open(validPath), true);
261
274
  const stats = await fileHandle.stat();
262
275
  if (stats.size > maxFileSize) {
263
- throw new FsError(ErrorCode.TOO_LARGE, `File too large: ${validPath} (${String(stats.size)} bytes > ${String(maxFileSize)} bytes)`);
276
+ throw createTooLargeError(stats.size, maxFileSize, validPath);
264
277
  }
265
278
  const buffer = await readFileBufferWithLimit(fileHandle, maxFileSize, validPath, signal);
266
279
  if (!matcher.testBuffer(buffer))
@@ -276,7 +289,7 @@ async function readReplacementPlan(validPath, ctx) {
276
289
  return undefined;
277
290
  }
278
291
  const originalContent = buffer.toString('utf-8');
279
- const { content: updatedContent, matchCount } = matcher.replace(originalContent, replacement);
292
+ const { content: updatedContent, matchCount } = await matcher.replace(originalContent, replacement, signal);
280
293
  return matchCount === 0 ? undefined : { matchCount, originalContent, updatedContent };
281
294
  }
282
295
  catch (e_1) {
@@ -450,6 +463,13 @@ async function handleSearchAndReplace(args, ctx) {
450
463
  }
451
464
  if (stoppedReason !== undefined)
452
465
  summary.stoppedReason = stoppedReason;
466
+ // A single file that aborts mid-scan throws inside processEntry (caught and
467
+ // swallowed as a timeout) and leaves the dispatch loop with no more entries,
468
+ // so processEntriesConcurrently returns undefined; surface the timeout here so
469
+ // the result is not reported as a clean "0 matches". search_text's engine
470
+ // checks the signal directly, so only replace needs this fallback.
471
+ else if (ctx.signal.aborted)
472
+ summary.stoppedReason = 'timeout';
453
473
  ctx.onProgress({ current: summary.processedFiles });
454
474
  if (!args.dryRun && summary.totalMatches > 0) {
455
475
  ctx.log('info', `${summary.filesChanged} file(s) changed, ${summary.totalMatches} match(es)`, 'replace_text');
@@ -526,9 +546,12 @@ export const REPLACE_TEXT = defineTool({
526
546
  : undefined;
527
547
  if (hint)
528
548
  lines.push(hint);
529
- // A failure's reason lives in `results`, which ships under `_meta`.
549
+ // A failure's reason lives in `results`, which ships under `_meta`. The
550
+ // full relative path, not the basename: the error message may not carry
551
+ // the path, and two same-named files in different directories must not
552
+ // render as the same row.
530
553
  for (const r of structured.results.filter((x) => 'error' in x).slice(0, 3)) {
531
- lines.push(`// ${basename(r.path)}: ${r.error.code} ${r.error.message}`);
554
+ lines.push(`// ${r.path}: ${r.error.code} ${r.error.message}`);
532
555
  }
533
556
  if (structured.diffTruncated) {
534
557
  lines.push(`// diff cut at ${String(MAX_DIFF_SIZE / 1024)} KB: some files' changes are not shown.`);
@@ -1 +1 @@
1
- {"version":3,"file":"replace-text.js","sourceRoot":"","sources":["../../src/tools/replace-text.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { Buffer, isUtf8 } from 'node:buffer';\nimport { basename, dirname } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport type { StoppedReason } from '../core/concurrency.ts';\nimport { processEntriesConcurrently } from '../core/concurrency.ts';\nimport { unifiedPatch } from '../core/diff.ts';\nimport { ErrorCode, FsError, Problem } from '../core/errors.ts';\nimport { buildWrittenFileMeta } from '../core/file-uri.ts';\nimport { stoppedEarlyLine, truncateProgressPattern, zeroMatchHint } from '../core/fmt.ts';\nimport type { GuardedFileSystem } from '../core/fs.ts';\nimport { globEntries } from '../core/glob.ts';\nimport { toPosixRelative } from '../core/path.ts';\nimport { readFileBufferWithLimit } from '../core/read.ts';\nimport {\n defaultFalseBoolean,\n IncludeHidden,\n IncludeIgnored,\n isBlank,\n MaxDepth,\n OptionalPath,\n SafeGlobPattern,\n} from '../core/schema.ts';\nimport type { Regex } from '../core/search.ts';\nimport { compileRegex, execMatches, freeRegex, RE2_MAX_INPUT_BYTES } from '../core/search.ts';\nimport {\n DEFAULT_SEARCH_RESULTS,\n DEFAULT_SEARCH_TIMEOUT_MS,\n escapeRegExp,\n getMaxTextFileSize,\n MAX_SEARCH_RESULTS,\n PARALLEL_CONCURRENCY,\n} from '../core/util.ts';\nimport { type BatchResult, isTotalFailure } from './batch.ts';\nimport { defineTool, type ToolCtx } from './define.ts';\n\nconst SearchAndReplaceInputSchema = z.strictObject({\n path: OptionalPath.describe(\n 'File to rewrite, or directory to rewrite under. Omitting it targets the ENTIRE first allowed root — ' +\n 'scope it deliberately, and pair a wide scope with dryRun=true first',\n ),\n pattern: SafeGlobPattern.optional().describe(\n 'Glob to restrict replacements to specific file types (e.g. **/*.ts); default: all text files',\n ),\n searchPattern: z\n .string()\n .min(1)\n .max(10000)\n .refine((val) => !isBlank(val), {\n message: 'searchPattern cannot be empty or whitespace-only',\n })\n .describe(\n 'Exact literal text or RE2 regex pattern to search for. When isRegex=true, uses RE2 syntax (no lookahead, lookbehind, or backreferences are supported). Cannot be empty or whitespace-only.',\n ),\n replacement: z\n .string()\n .max(10000)\n .describe(\n 'Replacement text. Use capture group references ($1, $2, etc.) when isRegex=true. Use an empty string to delete all matches.',\n )\n .meta({ examples: ['$1_renamed', '', 'TODO: fix'] }),\n isRegex: defaultFalseBoolean('Treat searchPattern as a RE2 regex (default: literal text match)'),\n includeHidden: IncludeHidden,\n includeIgnored: IncludeIgnored,\n caseSensitive: defaultFalseBoolean('Enable case-sensitive matching (default: case-insensitive)'),\n wholeWord: defaultFalseBoolean('Match whole words only (word boundary anchoring)'),\n dryRun: defaultFalseBoolean(\n 'Preview replacements without writing to disk (default: false = apply changes)',\n ),\n returnDiff: defaultFalseBoolean('Include a unified diff of all changes in the response'),\n maxResults: z\n .uint32()\n .min(1)\n .max(MAX_SEARCH_RESULTS)\n .optional()\n .default(DEFAULT_SEARCH_RESULTS)\n .describe('Maximum total match count across all files before stopping'),\n maxFiles: z\n .uint32()\n .min(1)\n .max(MAX_SEARCH_RESULTS)\n .optional()\n .describe('Maximum number of files to process'),\n maxDepth: MaxDepth,\n});\n\ntype SearchAndReplaceOutput = BatchResult<{ matches: number }> & {\n totalMatches: number;\n filesScanned: number;\n skippedBinary?: number;\n resultsTruncated?: boolean;\n diff?: string;\n diffTruncated?: boolean;\n stoppedReason?: StoppedReason;\n};\n\nconst MAX_FAILURES = 20;\nconst REPLACE_CONCURRENCY = Math.min(PARALLEL_CONCURRENCY, 8);\nconst MAX_CHANGED_FILES = 100;\nconst MAX_DIFF_SIZE = 20 * 1024; // 20KB limit for diff output\nconst DIFF_APPEND_BUFFER = 1024;\n\ninterface Failure {\n path: string;\n error: Problem;\n}\n\nfunction recordFailure(failures: Failure[], failure: Failure): void {\n if (failures.length >= MAX_FAILURES) return;\n failures.push(failure);\n}\n\nfunction recordChangedFile(summary: ReplaceSummary, plan: ReplacementPlan, filePath: string): void {\n const relativePath = toPosixRelative(summary.root, filePath);\n // The first changed file is the one the resource_link points at; keep what\n // was written so the link needs no read-back.\n summary.primary ??= { path: filePath, content: plan.updatedContent };\n if (summary.changedFiles.length < MAX_CHANGED_FILES) {\n summary.changedFiles.push({ path: relativePath, matches: plan.matchCount });\n return;\n }\n summary.changedFilesTruncated = true;\n}\n\ninterface ReplacementMatcher {\n replace(content: string, replacement: string): { content: string; matchCount: number };\n testBuffer(buffer: Buffer): boolean;\n /** Releases any compiled pattern this matcher owns. Idempotent. */\n dispose(): void;\n}\n\nconst DOLLAR_TOKEN = /\\$(\\$|&|`|'|<([^>]*)>|\\d{1,2})/g;\n\n/**\n * Expand `$`-substitutions in a replacement template the way `RegExp` does.\n *\n * We cannot hand the template to RE2's own string replacer: it throws\n * `Invalid replacement string` on any `$` not followed by a substitution it\n * recognises (so a replacement of `$100` or a trailing `$` fails outright,\n * where `RegExp` inserts the `$` literally), and it renders an out-of-range\n * `$5` as `$4`. The matcher splices matches itself, so this is the single owner\n * of the syntax.\n */\nfunction expandDollarTokens(\n template: string,\n match: string,\n groups: (string | undefined)[],\n offset: number,\n input: string,\n named: Record<string, string> | undefined,\n): string {\n return template.replace(DOLLAR_TOKEN, (token: string, kind: string, name?: string) => {\n if (kind === '$') return '$';\n if (kind === '&') return match;\n if (kind === '`') return input.slice(0, offset);\n if (kind === \"'\") return input.slice(offset + match.length);\n if (name !== undefined) {\n if (named && Object.hasOwn(named, name)) return named[name] ?? '';\n return token;\n }\n // `$12` prefers group 12, then falls back to group 1 followed by a literal\n // `2`, and stays literal when neither exists — RegExp's own precedence.\n const two = Number.parseInt(kind, 10);\n if (kind.length === 2 && two >= 1 && two <= groups.length) return groups[two - 1] ?? '';\n const one = Number.parseInt(kind.slice(0, 1), 10);\n if (one >= 1 && one <= groups.length) {\n return (groups[one - 1] ?? '') + (kind.length === 2 ? kind.slice(1) : '');\n }\n return token;\n });\n}\n\nfunction createRegexReplacementMatcher(\n regex: Regex,\n expandReplacement: boolean,\n): ReplacementMatcher {\n return {\n testBuffer(buffer: Buffer): boolean {\n if (buffer.length > RE2_MAX_INPUT_BYTES) {\n throw new FsError(\n ErrorCode.TOO_LARGE,\n `File too large for a regex replace (${String(buffer.length)} > ${String(RE2_MAX_INPUT_BYTES)} bytes). Regex mode is used for isRegex, wholeWord, or the default caseSensitive: false; pass caseSensitive: true with a literal searchPattern, or split the file.`,\n );\n }\n // The regex is global and shared across every file in the batch, so a\n // previous file's match would otherwise start this scan mid-string.\n regex.lastIndex = 0;\n return regex.test(buffer.toString('utf-8'));\n },\n replace(content: string, replacement: string): { content: string; matchCount: number } {\n // Splicing by hand rather than through `String.replace(regex, …)`: RE2's\n // own replacer indexes the JS string with the code-point offsets it\n // reports, which lands the replacement short by one per astral character\n // (most emoji) earlier in the file. execMatches reports UTF-16 offsets.\n let matchCount = 0;\n let updated = '';\n let cursor = 0;\n for (const match of execMatches(regex, content)) {\n matchCount++;\n // Only isRegex=true opts into $1/$& substitution. A literal search\n // reaches this matcher too (case-insensitive and wholeWord both need a\n // regex), and there the replacement must be inserted verbatim.\n updated +=\n content.slice(cursor, match.start) +\n (expandReplacement\n ? expandDollarTokens(\n replacement,\n match.text,\n match.groups,\n match.start,\n content,\n match.named,\n )\n : replacement);\n cursor = match.end;\n }\n return { content: updated + content.slice(cursor), matchCount };\n },\n dispose(): void {\n freeRegex(regex);\n },\n };\n}\n\nfunction createCaseSensitiveLiteralMatcher(searchPattern: string): ReplacementMatcher {\n const searchBuffer = Buffer.from(searchPattern, 'utf8');\n\n return {\n testBuffer(buffer: Buffer): boolean {\n return buffer.indexOf(searchBuffer) !== -1;\n },\n replace(content: string, replacement: string): { content: string; matchCount: number } {\n let matchCount = 0;\n const updated = content.replaceAll(searchPattern, () => {\n matchCount++;\n return replacement;\n });\n return { content: updated, matchCount };\n },\n dispose(): void {\n // no compiled pattern to release\n },\n };\n}\n\ntype SearchAndReplaceArgs = z.infer<typeof SearchAndReplaceInputSchema>;\n\ninterface ReplaceContext {\n options: { dryRun: boolean; returnDiff: boolean };\n replacement: string;\n matcher: ReplacementMatcher;\n maxFileSize: number;\n signal: AbortSignal | undefined;\n summary: ReplaceSummary;\n fs: GuardedFileSystem;\n singleFile: boolean;\n}\n\ninterface ReplacementPlan {\n matchCount: number;\n originalContent: string;\n updatedContent: string;\n}\n\nasync function processEntry(entryPath: string, ctx: ReplaceContext): Promise<void> {\n const { options, signal, summary } = ctx;\n\n try {\n const plan = await readReplacementPlan(entryPath, ctx);\n if (!plan) {\n return;\n }\n\n if (!options.dryRun) {\n await ctx.fs.writeFile(entryPath, plan.updatedContent, {\n signal,\n });\n }\n\n // Bookkeep only after the write succeeds (or in dryRun, where there is no\n // write): a failed write must not count the file as changed.\n summary.totalMatches += plan.matchCount;\n summary.filesChanged++;\n\n recordChangedFile(summary, plan, entryPath);\n\n maybeAppendPatchDiff(summary, {\n filePath: entryPath,\n originalContent: plan.originalContent,\n updatedContent: plan.updatedContent,\n includeDiff: options.dryRun || options.returnDiff,\n });\n } catch (error) {\n summary.failedFiles++;\n recordFailure(summary.failures, {\n path: toPosixRelative(summary.root, entryPath),\n error: Problem.fromUnknown(error, ErrorCode.UNKNOWN, entryPath),\n });\n }\n}\n\nasync function readReplacementPlan(\n validPath: string,\n ctx: ReplaceContext,\n): Promise<ReplacementPlan | undefined> {\n const { matcher, replacement, maxFileSize, signal } = ctx;\n await using fileHandle = await ctx.fs.open(validPath);\n const stats = await fileHandle.stat();\n if (stats.size > maxFileSize) {\n throw new FsError(\n ErrorCode.TOO_LARGE,\n `File too large: ${validPath} (${String(stats.size)} bytes > ${String(maxFileSize)} bytes)`,\n );\n }\n\n const buffer = await readFileBufferWithLimit(fileHandle, maxFileSize, validPath, signal);\n if (!matcher.testBuffer(buffer)) return undefined;\n // Only bytes that decode to UTF-8 losslessly survive the string round trip;\n // anything else would be written back with U+FFFD in place of every bad byte.\n // NUL is valid UTF-8 but marks a binary file, as it does for `read`.\n if (!isUtf8(buffer) || buffer.includes(0)) {\n if (ctx.singleFile) {\n throw new FsError(ErrorCode.INVALID_INPUT, 'Binary or non-UTF-8 file detected.', validPath);\n }\n ctx.summary.skippedBinary++;\n return undefined;\n }\n\n const originalContent = buffer.toString('utf-8');\n const { content: updatedContent, matchCount } = matcher.replace(originalContent, replacement);\n return matchCount === 0 ? undefined : { matchCount, originalContent, updatedContent };\n}\n\nfunction maybeAppendPatchDiff(\n summary: ReplaceSummary,\n params: {\n filePath: string;\n originalContent: string;\n updatedContent: string;\n includeDiff: boolean;\n },\n): void {\n if (!params.includeDiff) return;\n\n // Budget first: a full budget must not pay for a diff it will discard, and a\n // diff past its deadline counts as truncated output, not a failed file.\n if (summary.diff.length >= MAX_DIFF_SIZE) {\n summary.diffTruncated = true;\n return;\n }\n const header = toPosixRelative(summary.root, params.filePath);\n const patch = unifiedPatch(header, params.originalContent, params.updatedContent);\n if (patch === undefined) {\n summary.diffTruncated = true;\n return;\n }\n if (summary.diff.length + patch.length <= MAX_DIFF_SIZE + DIFF_APPEND_BUFFER) {\n summary.diff += patch;\n return;\n }\n summary.diffTruncated = true;\n}\n\ninterface ReplaceSummary {\n root: string;\n totalMatches: number;\n filesChanged: number;\n failedFiles: number;\n processedFiles: number;\n skippedBinary: number;\n failures: Failure[];\n changedFiles: { path: string; matches: number }[];\n changedFilesTruncated: boolean;\n /** First file changed, as written. */\n primary?: { path: string; content: string };\n diff: string;\n diffTruncated: boolean;\n stoppedReason?: StoppedReason;\n}\n\nfunction createReplaceSummary(root: string): ReplaceSummary {\n return {\n root,\n totalMatches: 0,\n filesChanged: 0,\n failedFiles: 0,\n processedFiles: 0,\n skippedBinary: 0,\n failures: [],\n changedFiles: [],\n changedFilesTruncated: false,\n diff: '',\n diffTruncated: false,\n };\n}\n\nasync function resolveSearchRoot(\n pathValue: string | undefined,\n fs: GuardedFileSystem,\n): Promise<{ root: string; singleFile?: string }> {\n if (!pathValue) {\n return { root: fs.pathGuard.resolvePathOrRoot(undefined) };\n }\n const resolvedPath = await fs.pathGuard.validateExistingPath(pathValue);\n const { stats: fileStats } = await fs.stat(resolvedPath);\n if (fileStats.isFile()) {\n // A single explicit file target bypasses the glob machinery entirely:\n // routing it through globEntries with baseNameMatch would rewrite the\n // escaped basename to `**/${basename}` and match every same-named file\n // under the parent tree, not just this one.\n return {\n root: dirname(resolvedPath),\n singleFile: resolvedPath,\n };\n }\n return { root: resolvedPath };\n}\n\nfunction buildSearchPattern(args: SearchAndReplaceArgs): string {\n if (args.isRegex) {\n return args.wholeWord ? `\\\\b(?:${args.searchPattern})\\\\b` : args.searchPattern;\n }\n const escaped = escapeRegExp(args.searchPattern);\n return args.wholeWord ? `\\\\b(?:${escaped})\\\\b` : escaped;\n}\n\nfunction createReplacementMatcher(args: SearchAndReplaceArgs): ReplacementMatcher {\n // Use regex when isRegex, wholeWord, or case-insensitive (all require RE2)\n if (args.isRegex || args.wholeWord || !args.caseSensitive) {\n const regex = compileRegex(buildSearchPattern(args), { caseSensitive: args.caseSensitive });\n return createRegexReplacementMatcher(regex, args.isRegex);\n }\n return createCaseSensitiveLiteralMatcher(args.searchPattern);\n}\n\nfunction buildSearchAndReplaceStructuredResult(\n summary: ReplaceSummary,\n args: SearchAndReplaceArgs,\n): SearchAndReplaceOutput {\n const results = [\n ...summary.changedFiles.map((f) => ({ path: f.path, value: { matches: f.matches } })),\n ...summary.failures.map((f) => ({ path: f.path, error: f.error })),\n ];\n return {\n results,\n // `failedFiles` counts every failure; `failures` itself is capped at\n // MAX_FAILURES, so the summary must not be derived from the array length.\n summary: {\n total: summary.filesChanged + summary.failedFiles,\n succeeded: summary.filesChanged,\n failed: summary.failedFiles,\n },\n totalMatches: summary.totalMatches,\n filesScanned: summary.processedFiles,\n ...(summary.skippedBinary ? { skippedBinary: summary.skippedBinary } : {}),\n // Failures are capped at MAX_FAILURES independently of the changed-file\n // cap, so either cap can leave `results` shorter than `summary.total`.\n ...(summary.changedFilesTruncated || summary.failures.length < summary.failedFiles\n ? { resultsTruncated: true }\n : {}),\n ...((args.dryRun || args.returnDiff) && summary.diff ? { diff: summary.diff } : {}),\n ...(summary.diffTruncated ? { diffTruncated: true } : {}),\n ...(summary.stoppedReason ? { stoppedReason: summary.stoppedReason } : {}),\n };\n}\n\nasync function handleSearchAndReplace(\n args: SearchAndReplaceArgs,\n ctx: ToolCtx,\n): Promise<{\n structured: SearchAndReplaceOutput;\n link?: ContentBlock;\n}> {\n const maxFileSize = getMaxTextFileSize();\n const { root, singleFile } = await resolveSearchRoot(args.path, ctx.fs);\n const effectivePattern = args.pattern ?? '**/*';\n const ceiling = ctx.fs.pathGuard.allowedRootContaining(root);\n\n // An explicit single-file target bypasses baseNameMatch/exclude/hidden/\n // gitignore filtering — it should always be processed as the one file named.\n const entries: AsyncIterable<{ path: string }> | Iterable<{ path: string }> = singleFile\n ? [{ path: singleFile }]\n : globEntries({\n cwd: root,\n pattern: effectivePattern,\n includeHidden: args.includeHidden,\n skipIgnored: !args.includeIgnored,\n ...(ceiling !== undefined ? { ignoreCeiling: ceiling } : {}),\n signal: ctx.signal,\n baseNameMatch: true,\n onlyFiles: true,\n suppressErrors: true,\n ...(args.maxDepth !== undefined ? { maxDepth: args.maxDepth } : {}),\n });\n\n const summary = createReplaceSummary(root);\n\n // The matcher may own a compiled RE2 pattern, whose wasm memory re2-wasm\n // never reclaims on its own. Nothing past the scan touches it, so free it the\n // moment the scan is done — success, failure, or abort alike.\n const matcher = createReplacementMatcher(args);\n let stoppedReason: StoppedReason | undefined;\n try {\n const context: ReplaceContext = {\n options: {\n dryRun: args.dryRun,\n returnDiff: args.returnDiff,\n },\n replacement: args.replacement,\n matcher,\n maxFileSize,\n signal: ctx.signal,\n summary,\n fs: ctx.fs,\n singleFile: singleFile !== undefined,\n };\n\n stoppedReason = await processEntriesConcurrently(entries, {\n signal: ctx.signal,\n concurrency: REPLACE_CONCURRENCY,\n ...(args.maxFiles !== undefined ? { maxEntries: args.maxFiles } : {}),\n shouldStop: () => summary.totalMatches >= args.maxResults,\n onEntry: () => {\n summary.processedFiles++;\n ctx.onProgress({ current: summary.processedFiles });\n },\n onError: (entryPath, err) => {\n summary.failedFiles++;\n recordFailure(summary.failures, {\n path: toPosixRelative(summary.root, entryPath),\n error: Problem.fromUnknown(err, ErrorCode.UNKNOWN, entryPath),\n });\n },\n runEntry: (entryPath) => processEntry(entryPath, context),\n });\n } finally {\n matcher.dispose();\n }\n if (stoppedReason !== undefined) summary.stoppedReason = stoppedReason;\n\n ctx.onProgress({ current: summary.processedFiles });\n\n if (!args.dryRun && summary.totalMatches > 0) {\n ctx.log(\n 'info',\n `${summary.filesChanged} file(s) changed, ${summary.totalMatches} match(es)`,\n 'replace_text',\n );\n }\n\n const structured = buildSearchAndReplaceStructuredResult(summary, args);\n\n let primaryModified: string | undefined;\n if (summary.primary && !args.dryRun) {\n try {\n primaryModified = (\n await ctx.fs.stat(summary.primary.path, { signal: ctx.signal })\n ).stats.mtime.toISOString();\n } catch {\n /* result metadata only; the write already succeeded */\n }\n }\n const link = summary.primary\n ? buildWrittenFileMeta({\n validPath: summary.primary.path,\n content: summary.primary.content,\n dryRun: args.dryRun,\n lastModified: primaryModified,\n }).resourceLink\n : undefined;\n return link ? { structured, link } : { structured };\n}\n\nexport const REPLACE_TEXT = defineTool<typeof SearchAndReplaceInputSchema, SearchAndReplaceOutput>({\n name: 'replace_text',\n title: 'Search and Replace',\n description:\n 'Find and replace every occurrence of literal text or an RE2 regex, like sed -i, in one file or all files ' +\n 'under a directory, optionally filtered by glob. The pattern runs over the whole file: it can span lines, ' +\n 'and ^ and $ anchor to file start and end. edit replaces one unique exact match.',\n input: SearchAndReplaceInputSchema,\n readOnlyHint: false,\n timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS,\n progress: (args) => {\n const dryLabel = args.dryRun ? ' [dry run]' : '';\n return {\n label: `Replace${dryLabel}`,\n subject: `${truncateProgressPattern(args.searchPattern)} → ${truncateProgressPattern(args.replacement)}`,\n };\n },\n accessPaths: (args) => (args.path ? [args.path] : []),\n run: async (args, ctx) => {\n const truncatedPattern = truncateProgressPattern(args.searchPattern);\n const { structured, link } = await handleSearchAndReplace(args, ctx);\n const dryLabel = args.dryRun ? ' [dry run]' : '';\n const summaryText =\n `replace_text: '${truncatedPattern}'${dryLabel}` +\n ` \\u00b7 ${String(structured.totalMatches)} match(es)` +\n ` in ${String(structured.summary.succeeded)} file(s)` +\n (structured.summary.failed > 0 ? ` \\u00b7 ${String(structured.summary.failed)} failed` : '') +\n (structured.skippedBinary\n ? ` \\u00b7 ${String(structured.skippedBinary)} binary skipped`\n : '');\n // The structured half ships under `_meta`, which clients do not show the\n // model, so the stop state and the diff preview must ride the text.\n const lines = [summaryText];\n const stop = structured.stoppedReason;\n if (stop === 'timeout') lines.push(stoppedEarlyLine(stop));\n else if (stop !== undefined) {\n // maxResults/maxFiles here are the caller's own caps, so raising one is\n // what reaches the rest; the search engine's advice to narrow is not.\n lines.push(\n `// scan stopped early: hit the ${stop} limit; later files were not scanned. A higher ${stop} reaches them.`,\n );\n }\n const hint =\n structured.totalMatches === 0\n ? zeroMatchHint({\n ...args,\n filesScanned: structured.filesScanned,\n // A failed file, or a binary one the needle did hit, explains the\n // zero better than a guess about the pattern.\n incomplete:\n stop !== undefined ||\n structured.summary.failed > 0 ||\n Boolean(structured.skippedBinary),\n })\n : undefined;\n if (hint) lines.push(hint);\n // A failure's reason lives in `results`, which ships under `_meta`.\n for (const r of structured.results.filter((x) => 'error' in x).slice(0, 3)) {\n lines.push(`// ${basename(r.path)}: ${r.error.code} ${r.error.message}`);\n }\n if (structured.diffTruncated) {\n lines.push(\n `// diff cut at ${String(MAX_DIFF_SIZE / 1024)} KB: some files' changes are not shown.`,\n );\n }\n const text = lines.join('\\n') + (structured.diff ? `\\n\\n${structured.diff}` : '');\n const isError = isTotalFailure(structured.summary);\n if (link) {\n return { structured, text, resources: [link], isError };\n }\n return { structured, text, isError };\n },\n});\n"]}
1
+ {"version":3,"file":"replace-text.js","sourceRoot":"","sources":["../../src/tools/replace-text.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { Buffer, isUtf8 } from 'node:buffer';\nimport { dirname } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport type { StoppedReason } from '../core/concurrency.ts';\nimport { processEntriesConcurrently } from '../core/concurrency.ts';\nimport { unifiedPatch } from '../core/diff.ts';\nimport { ErrorCode, FsError, Problem } from '../core/errors.ts';\nimport { buildWrittenFileMeta } from '../core/file-uri.ts';\nimport { stoppedEarlyLine, truncateProgressPattern, zeroMatchHint } from '../core/fmt.ts';\nimport type { GuardedFileSystem } from '../core/fs.ts';\nimport { globEntries } from '../core/glob.ts';\nimport { toPosixRelative } from '../core/path.ts';\nimport { createTooLargeError, readFileBufferWithLimit } from '../core/read.ts';\nimport {\n defaultFalseBoolean,\n IncludeHidden,\n IncludeIgnored,\n isBlank,\n MaxDepth,\n OptionalPath,\n SafeGlobPattern,\n} from '../core/schema.ts';\nimport type { Regex } from '../core/search.ts';\nimport {\n compileRegex,\n createYielder,\n execMatches,\n freeRegex,\n RE2_MAX_INPUT_BYTES,\n} from '../core/search.ts';\nimport {\n DEFAULT_SEARCH_RESULTS,\n DEFAULT_SEARCH_TIMEOUT_MS,\n escapeRegExp,\n getMaxTextFileSize,\n MAX_SEARCH_RESULTS,\n PARALLEL_CONCURRENCY,\n} from '../core/util.ts';\nimport { type BatchResult, isTotalFailure } from './batch.ts';\nimport { defineTool, type ToolCtx } from './define.ts';\n\nconst SearchAndReplaceInputSchema = z.strictObject({\n path: OptionalPath.describe(\n 'File to rewrite, or directory to rewrite under. Omitting it targets the ENTIRE first allowed root — ' +\n 'scope it deliberately, and pair a wide scope with dryRun=true first',\n ),\n pattern: SafeGlobPattern.optional().describe(\n 'Glob to restrict replacements to specific file types (e.g. **/*.ts); default: all text files',\n ),\n searchPattern: z\n .string()\n .min(1)\n .max(10000)\n .refine((val) => !isBlank(val), {\n message: 'searchPattern cannot be empty or whitespace-only',\n })\n .describe(\n 'Exact literal text or RE2 regex pattern to search for. When isRegex=true, uses RE2 syntax (no lookahead, lookbehind, or backreferences are supported). Cannot be empty or whitespace-only.',\n ),\n replacement: z\n .string()\n .max(10000)\n .describe(\n 'Replacement text. Use capture group references ($1, $2, etc.) when isRegex=true. Use an empty string to delete all matches.',\n )\n .meta({ examples: ['$1_renamed', '', 'TODO: fix'] }),\n isRegex: defaultFalseBoolean('Treat searchPattern as a RE2 regex (default: literal text match)'),\n includeHidden: IncludeHidden,\n includeIgnored: IncludeIgnored,\n caseSensitive: defaultFalseBoolean('Enable case-sensitive matching (default: case-insensitive)'),\n wholeWord: defaultFalseBoolean('Match whole words only (word boundary anchoring)'),\n dryRun: defaultFalseBoolean(\n 'Preview replacements without writing to disk (default: false = apply changes)',\n ),\n returnDiff: defaultFalseBoolean('Include a unified diff of all changes in the response'),\n maxResults: z\n .uint32()\n .min(1)\n .max(MAX_SEARCH_RESULTS)\n .optional()\n .default(DEFAULT_SEARCH_RESULTS)\n .describe('Maximum total match count across all files before stopping'),\n maxFiles: z\n .uint32()\n .min(1)\n .max(MAX_SEARCH_RESULTS)\n .optional()\n .describe('Maximum number of files to process'),\n maxDepth: MaxDepth,\n});\n\ntype SearchAndReplaceOutput = BatchResult<{ matches: number }> & {\n totalMatches: number;\n filesScanned: number;\n skippedBinary?: number;\n resultsTruncated?: boolean;\n diff?: string;\n diffTruncated?: boolean;\n stoppedReason?: StoppedReason;\n};\n\nconst MAX_FAILURES = 20;\nconst REPLACE_CONCURRENCY = Math.min(PARALLEL_CONCURRENCY, 8);\nconst MAX_CHANGED_FILES = 100;\nconst MAX_DIFF_SIZE = 20 * 1024; // 20KB limit for diff output\nconst DIFF_APPEND_BUFFER = 1024;\n\ninterface Failure {\n path: string;\n error: Problem;\n}\n\nfunction recordFailure(failures: Failure[], failure: Failure): void {\n if (failures.length >= MAX_FAILURES) return;\n failures.push(failure);\n}\n\nfunction recordChangedFile(summary: ReplaceSummary, plan: ReplacementPlan, filePath: string): void {\n const relativePath = toPosixRelative(summary.root, filePath);\n // The first changed file is the one the resource_link points at; keep what\n // was written so the link needs no read-back.\n summary.primary ??= { path: filePath, content: plan.updatedContent };\n if (summary.changedFiles.length < MAX_CHANGED_FILES) {\n summary.changedFiles.push({ path: relativePath, matches: plan.matchCount });\n return;\n }\n summary.changedFilesTruncated = true;\n}\n\ninterface ReplacementMatcher {\n /** Yields periodically and throws on abort: a partial splice must never be written. */\n replace(\n content: string,\n replacement: string,\n signal: AbortSignal | undefined,\n ): Promise<{ content: string; matchCount: number }>;\n testBuffer(buffer: Buffer): boolean;\n /** Releases any compiled pattern this matcher owns. Idempotent. */\n dispose(): void;\n}\n\nconst DOLLAR_TOKEN = /\\$(\\$|&|`|'|<([^>]*)>|\\d{1,2})/g;\n\n/**\n * Expand `$`-substitutions in a replacement template the way `RegExp` does.\n *\n * We cannot hand the template to RE2's own string replacer: it throws\n * `Invalid replacement string` on any `$` not followed by a substitution it\n * recognises (so a replacement of `$100` or a trailing `$` fails outright,\n * where `RegExp` inserts the `$` literally), and it renders an out-of-range\n * `$5` as `$4`. The matcher splices matches itself, so this is the single owner\n * of the syntax.\n */\nfunction expandDollarTokens(\n template: string,\n match: string,\n groups: (string | undefined)[],\n offset: number,\n input: string,\n named: Record<string, string> | undefined,\n): string {\n return template.replace(DOLLAR_TOKEN, (token: string, kind: string, name?: string) => {\n if (kind === '$') return '$';\n if (kind === '&') return match;\n if (kind === '`') return input.slice(0, offset);\n if (kind === \"'\") return input.slice(offset + match.length);\n if (name !== undefined) {\n if (named && Object.hasOwn(named, name)) return named[name] ?? '';\n return token;\n }\n // `$12` prefers group 12, then falls back to group 1 followed by a literal\n // `2`, and stays literal when neither exists — RegExp's own precedence.\n const two = Number.parseInt(kind, 10);\n if (kind.length === 2 && two >= 1 && two <= groups.length) return groups[two - 1] ?? '';\n const one = Number.parseInt(kind.slice(0, 1), 10);\n if (one >= 1 && one <= groups.length) {\n return (groups[one - 1] ?? '') + (kind.length === 2 ? kind.slice(1) : '');\n }\n return token;\n });\n}\n\nfunction createRegexReplacementMatcher(\n regex: Regex,\n expandReplacement: boolean,\n): ReplacementMatcher {\n return {\n testBuffer(buffer: Buffer): boolean {\n if (buffer.length > RE2_MAX_INPUT_BYTES) {\n throw new FsError(\n ErrorCode.TOO_LARGE,\n `File too large for a regex replace (${String(buffer.length)} > ${String(RE2_MAX_INPUT_BYTES)} bytes). Regex mode is used for isRegex, wholeWord, or the default caseSensitive: false; pass caseSensitive: true with a literal searchPattern, or split the file.`,\n );\n }\n // The regex is global and shared across every file in the batch, so a\n // previous file's match would otherwise start this scan mid-string.\n regex.lastIndex = 0;\n return regex.test(buffer.toString('utf-8'));\n },\n async replace(content, replacement, signal) {\n // Splicing by hand rather than through `String.replace(regex, …)`: RE2's\n // own replacer indexes the JS string with the code-point offsets it\n // reports, which lands the replacement short by one per astral character\n // (most emoji) earlier in the file. execMatches reports UTF-16 offsets.\n let matchCount = 0;\n let updated = '';\n let cursor = 0;\n const tick = createYielder(signal);\n for (const match of execMatches(regex, content)) {\n matchCount++;\n // Only isRegex=true opts into $1/$& substitution. A literal search\n // reaches this matcher too (case-insensitive and wholeWord both need a\n // regex), and there the replacement must be inserted verbatim.\n updated +=\n content.slice(cursor, match.start) +\n (expandReplacement\n ? expandDollarTokens(\n replacement,\n match.text,\n match.groups,\n match.start,\n content,\n match.named,\n )\n : replacement);\n cursor = match.end;\n if (await tick()) signal?.throwIfAborted();\n }\n return { content: updated + content.slice(cursor), matchCount };\n },\n dispose(): void {\n freeRegex(regex);\n },\n };\n}\n\nfunction createCaseSensitiveLiteralMatcher(searchPattern: string): ReplacementMatcher {\n const searchBuffer = Buffer.from(searchPattern, 'utf8');\n\n return {\n testBuffer(buffer: Buffer): boolean {\n return buffer.indexOf(searchBuffer) !== -1;\n },\n // One native O(n) pass; nothing to yield for.\n replace(content, replacement) {\n let matchCount = 0;\n const updated = content.replaceAll(searchPattern, () => {\n matchCount++;\n return replacement;\n });\n return Promise.resolve({ content: updated, matchCount });\n },\n dispose(): void {\n // no compiled pattern to release\n },\n };\n}\n\ntype SearchAndReplaceArgs = z.infer<typeof SearchAndReplaceInputSchema>;\n\ninterface ReplaceContext {\n options: { dryRun: boolean; returnDiff: boolean };\n replacement: string;\n matcher: ReplacementMatcher;\n maxFileSize: number;\n signal: AbortSignal | undefined;\n summary: ReplaceSummary;\n fs: GuardedFileSystem;\n singleFile: boolean;\n}\n\ninterface ReplacementPlan {\n matchCount: number;\n originalContent: string;\n updatedContent: string;\n}\n\nasync function processEntry(entryPath: string, ctx: ReplaceContext): Promise<void> {\n const { options, signal, summary } = ctx;\n\n try {\n // Lock the read→write per file so a concurrent writer is not lost; a dry\n // run never writes and skips the lock.\n const plan = options.dryRun\n ? await readReplacementPlan(entryPath, ctx)\n : await ctx.fs.lockForWrite(entryPath, async () => {\n const planned = await readReplacementPlan(entryPath, ctx);\n if (planned) await ctx.fs.writeFile(entryPath, planned.updatedContent, { signal });\n return planned;\n });\n if (!plan) {\n return;\n }\n\n // Bookkeep only after the write succeeds (or in dryRun, where there is no\n // write): a failed write must not count the file as changed.\n summary.totalMatches += plan.matchCount;\n summary.filesChanged++;\n\n recordChangedFile(summary, plan, entryPath);\n\n maybeAppendPatchDiff(summary, {\n filePath: entryPath,\n originalContent: plan.originalContent,\n updatedContent: plan.updatedContent,\n includeDiff: options.dryRun || options.returnDiff,\n });\n } catch (error) {\n // A timeout/cancel aborts the whole scan, not just this file: don't record\n // it as a per-file failure. processEntriesConcurrently reports the run as\n // stoppedReason 'timeout' instead.\n if (signal?.aborted) return;\n summary.failedFiles++;\n recordFailure(summary.failures, {\n path: toPosixRelative(summary.root, entryPath),\n error: Problem.fromUnknown(error, ErrorCode.UNKNOWN, entryPath),\n });\n }\n}\n\nasync function readReplacementPlan(\n validPath: string,\n ctx: ReplaceContext,\n): Promise<ReplacementPlan | undefined> {\n const { matcher, replacement, maxFileSize, signal } = ctx;\n await using fileHandle = await ctx.fs.open(validPath);\n const stats = await fileHandle.stat();\n if (stats.size > maxFileSize) {\n throw createTooLargeError(stats.size, maxFileSize, validPath);\n }\n\n const buffer = await readFileBufferWithLimit(fileHandle, maxFileSize, validPath, signal);\n if (!matcher.testBuffer(buffer)) return undefined;\n // Only bytes that decode to UTF-8 losslessly survive the string round trip;\n // anything else would be written back with U+FFFD in place of every bad byte.\n // NUL is valid UTF-8 but marks a binary file, as it does for `read`.\n if (!isUtf8(buffer) || buffer.includes(0)) {\n if (ctx.singleFile) {\n throw new FsError(ErrorCode.INVALID_INPUT, 'Binary or non-UTF-8 file detected.', validPath);\n }\n ctx.summary.skippedBinary++;\n return undefined;\n }\n\n const originalContent = buffer.toString('utf-8');\n const { content: updatedContent, matchCount } = await matcher.replace(\n originalContent,\n replacement,\n signal,\n );\n return matchCount === 0 ? undefined : { matchCount, originalContent, updatedContent };\n}\n\nfunction maybeAppendPatchDiff(\n summary: ReplaceSummary,\n params: {\n filePath: string;\n originalContent: string;\n updatedContent: string;\n includeDiff: boolean;\n },\n): void {\n if (!params.includeDiff) return;\n\n // Budget first: a full budget must not pay for a diff it will discard, and a\n // diff past its deadline counts as truncated output, not a failed file.\n if (summary.diff.length >= MAX_DIFF_SIZE) {\n summary.diffTruncated = true;\n return;\n }\n const header = toPosixRelative(summary.root, params.filePath);\n const patch = unifiedPatch(header, params.originalContent, params.updatedContent);\n if (patch === undefined) {\n summary.diffTruncated = true;\n return;\n }\n if (summary.diff.length + patch.length <= MAX_DIFF_SIZE + DIFF_APPEND_BUFFER) {\n summary.diff += patch;\n return;\n }\n summary.diffTruncated = true;\n}\n\ninterface ReplaceSummary {\n root: string;\n totalMatches: number;\n filesChanged: number;\n failedFiles: number;\n processedFiles: number;\n skippedBinary: number;\n failures: Failure[];\n changedFiles: { path: string; matches: number }[];\n changedFilesTruncated: boolean;\n /** First file changed, as written. */\n primary?: { path: string; content: string };\n diff: string;\n diffTruncated: boolean;\n stoppedReason?: StoppedReason;\n}\n\nfunction createReplaceSummary(root: string): ReplaceSummary {\n return {\n root,\n totalMatches: 0,\n filesChanged: 0,\n failedFiles: 0,\n processedFiles: 0,\n skippedBinary: 0,\n failures: [],\n changedFiles: [],\n changedFilesTruncated: false,\n diff: '',\n diffTruncated: false,\n };\n}\n\nasync function resolveSearchRoot(\n pathValue: string | undefined,\n fs: GuardedFileSystem,\n): Promise<{ root: string; singleFile?: string }> {\n if (!pathValue) {\n return { root: fs.pathGuard.resolvePathOrRoot(undefined) };\n }\n const resolvedPath = await fs.pathGuard.validateExistingPath(pathValue);\n const { stats: fileStats } = await fs.stat(resolvedPath);\n if (fileStats.isFile()) {\n // A single explicit file target bypasses the glob machinery entirely:\n // routing it through globEntries with baseNameMatch would rewrite the\n // escaped basename to `**/${basename}` and match every same-named file\n // under the parent tree, not just this one.\n return {\n root: dirname(resolvedPath),\n singleFile: resolvedPath,\n };\n }\n return { root: resolvedPath };\n}\n\nfunction buildSearchPattern(args: SearchAndReplaceArgs): string {\n if (args.isRegex) {\n return args.wholeWord ? `\\\\b(?:${args.searchPattern})\\\\b` : args.searchPattern;\n }\n const escaped = escapeRegExp(args.searchPattern);\n return args.wholeWord ? `\\\\b(?:${escaped})\\\\b` : escaped;\n}\n\nfunction createReplacementMatcher(args: SearchAndReplaceArgs): ReplacementMatcher {\n // Use regex when isRegex, wholeWord, or case-insensitive (all require RE2)\n if (args.isRegex || args.wholeWord || !args.caseSensitive) {\n const regex = compileRegex(buildSearchPattern(args), { caseSensitive: args.caseSensitive });\n return createRegexReplacementMatcher(regex, args.isRegex);\n }\n return createCaseSensitiveLiteralMatcher(args.searchPattern);\n}\n\nfunction buildSearchAndReplaceStructuredResult(\n summary: ReplaceSummary,\n args: SearchAndReplaceArgs,\n): SearchAndReplaceOutput {\n const results = [\n ...summary.changedFiles.map((f) => ({ path: f.path, value: { matches: f.matches } })),\n ...summary.failures.map((f) => ({ path: f.path, error: f.error })),\n ];\n return {\n results,\n // `failedFiles` counts every failure; `failures` itself is capped at\n // MAX_FAILURES, so the summary must not be derived from the array length.\n summary: {\n total: summary.filesChanged + summary.failedFiles,\n succeeded: summary.filesChanged,\n failed: summary.failedFiles,\n },\n totalMatches: summary.totalMatches,\n filesScanned: summary.processedFiles,\n ...(summary.skippedBinary ? { skippedBinary: summary.skippedBinary } : {}),\n // Failures are capped at MAX_FAILURES independently of the changed-file\n // cap, so either cap can leave `results` shorter than `summary.total`.\n ...(summary.changedFilesTruncated || summary.failures.length < summary.failedFiles\n ? { resultsTruncated: true }\n : {}),\n ...((args.dryRun || args.returnDiff) && summary.diff ? { diff: summary.diff } : {}),\n ...(summary.diffTruncated ? { diffTruncated: true } : {}),\n ...(summary.stoppedReason ? { stoppedReason: summary.stoppedReason } : {}),\n };\n}\n\nasync function handleSearchAndReplace(\n args: SearchAndReplaceArgs,\n ctx: ToolCtx,\n): Promise<{\n structured: SearchAndReplaceOutput;\n link?: ContentBlock;\n}> {\n const maxFileSize = getMaxTextFileSize();\n const { root, singleFile } = await resolveSearchRoot(args.path, ctx.fs);\n const effectivePattern = args.pattern ?? '**/*';\n const ceiling = ctx.fs.pathGuard.allowedRootContaining(root);\n\n // An explicit single-file target bypasses baseNameMatch/exclude/hidden/\n // gitignore filtering — it should always be processed as the one file named.\n const entries: AsyncIterable<{ path: string }> | Iterable<{ path: string }> = singleFile\n ? [{ path: singleFile }]\n : globEntries({\n cwd: root,\n pattern: effectivePattern,\n includeHidden: args.includeHidden,\n skipIgnored: !args.includeIgnored,\n ...(ceiling !== undefined ? { ignoreCeiling: ceiling } : {}),\n signal: ctx.signal,\n baseNameMatch: true,\n onlyFiles: true,\n suppressErrors: true,\n ...(args.maxDepth !== undefined ? { maxDepth: args.maxDepth } : {}),\n });\n\n const summary = createReplaceSummary(root);\n\n // The matcher may own a compiled RE2 pattern, whose wasm memory re2-wasm\n // never reclaims on its own. Nothing past the scan touches it, so free it the\n // moment the scan is done — success, failure, or abort alike.\n const matcher = createReplacementMatcher(args);\n let stoppedReason: StoppedReason | undefined;\n try {\n const context: ReplaceContext = {\n options: {\n dryRun: args.dryRun,\n returnDiff: args.returnDiff,\n },\n replacement: args.replacement,\n matcher,\n maxFileSize,\n signal: ctx.signal,\n summary,\n fs: ctx.fs,\n singleFile: singleFile !== undefined,\n };\n\n stoppedReason = await processEntriesConcurrently(entries, {\n signal: ctx.signal,\n concurrency: REPLACE_CONCURRENCY,\n ...(args.maxFiles !== undefined ? { maxEntries: args.maxFiles } : {}),\n shouldStop: () => summary.totalMatches >= args.maxResults,\n onEntry: () => {\n summary.processedFiles++;\n ctx.onProgress({ current: summary.processedFiles });\n },\n onError: (entryPath, err) => {\n summary.failedFiles++;\n recordFailure(summary.failures, {\n path: toPosixRelative(summary.root, entryPath),\n error: Problem.fromUnknown(err, ErrorCode.UNKNOWN, entryPath),\n });\n },\n runEntry: (entryPath) => processEntry(entryPath, context),\n });\n } finally {\n matcher.dispose();\n }\n if (stoppedReason !== undefined) summary.stoppedReason = stoppedReason;\n // A single file that aborts mid-scan throws inside processEntry (caught and\n // swallowed as a timeout) and leaves the dispatch loop with no more entries,\n // so processEntriesConcurrently returns undefined; surface the timeout here so\n // the result is not reported as a clean \"0 matches\". search_text's engine\n // checks the signal directly, so only replace needs this fallback.\n else if (ctx.signal.aborted) summary.stoppedReason = 'timeout';\n\n ctx.onProgress({ current: summary.processedFiles });\n\n if (!args.dryRun && summary.totalMatches > 0) {\n ctx.log(\n 'info',\n `${summary.filesChanged} file(s) changed, ${summary.totalMatches} match(es)`,\n 'replace_text',\n );\n }\n\n const structured = buildSearchAndReplaceStructuredResult(summary, args);\n\n let primaryModified: string | undefined;\n if (summary.primary && !args.dryRun) {\n try {\n primaryModified = (\n await ctx.fs.stat(summary.primary.path, { signal: ctx.signal })\n ).stats.mtime.toISOString();\n } catch {\n /* result metadata only; the write already succeeded */\n }\n }\n const link = summary.primary\n ? buildWrittenFileMeta({\n validPath: summary.primary.path,\n content: summary.primary.content,\n dryRun: args.dryRun,\n lastModified: primaryModified,\n }).resourceLink\n : undefined;\n return link ? { structured, link } : { structured };\n}\n\nexport const REPLACE_TEXT = defineTool<typeof SearchAndReplaceInputSchema, SearchAndReplaceOutput>({\n name: 'replace_text',\n title: 'Search and Replace',\n description:\n 'Find and replace every occurrence of literal text or an RE2 regex, like sed -i, in one file or all files ' +\n 'under a directory, optionally filtered by glob. The pattern runs over the whole file: it can span lines, ' +\n 'and ^ and $ anchor to file start and end. edit replaces one unique exact match.',\n input: SearchAndReplaceInputSchema,\n readOnlyHint: false,\n timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS,\n progress: (args) => {\n const dryLabel = args.dryRun ? ' [dry run]' : '';\n return {\n label: `Replace${dryLabel}`,\n subject: `${truncateProgressPattern(args.searchPattern)} → ${truncateProgressPattern(args.replacement)}`,\n };\n },\n accessPaths: (args) => (args.path ? [args.path] : []),\n run: async (args, ctx) => {\n const truncatedPattern = truncateProgressPattern(args.searchPattern);\n const { structured, link } = await handleSearchAndReplace(args, ctx);\n const dryLabel = args.dryRun ? ' [dry run]' : '';\n const summaryText =\n `replace_text: '${truncatedPattern}'${dryLabel}` +\n ` \\u00b7 ${String(structured.totalMatches)} match(es)` +\n ` in ${String(structured.summary.succeeded)} file(s)` +\n (structured.summary.failed > 0 ? ` \\u00b7 ${String(structured.summary.failed)} failed` : '') +\n (structured.skippedBinary\n ? ` \\u00b7 ${String(structured.skippedBinary)} binary skipped`\n : '');\n // The structured half ships under `_meta`, which clients do not show the\n // model, so the stop state and the diff preview must ride the text.\n const lines = [summaryText];\n const stop = structured.stoppedReason;\n if (stop === 'timeout') lines.push(stoppedEarlyLine(stop));\n else if (stop !== undefined) {\n // maxResults/maxFiles here are the caller's own caps, so raising one is\n // what reaches the rest; the search engine's advice to narrow is not.\n lines.push(\n `// scan stopped early: hit the ${stop} limit; later files were not scanned. A higher ${stop} reaches them.`,\n );\n }\n const hint =\n structured.totalMatches === 0\n ? zeroMatchHint({\n ...args,\n filesScanned: structured.filesScanned,\n // A failed file, or a binary one the needle did hit, explains the\n // zero better than a guess about the pattern.\n incomplete:\n stop !== undefined ||\n structured.summary.failed > 0 ||\n Boolean(structured.skippedBinary),\n })\n : undefined;\n if (hint) lines.push(hint);\n // A failure's reason lives in `results`, which ships under `_meta`. The\n // full relative path, not the basename: the error message may not carry\n // the path, and two same-named files in different directories must not\n // render as the same row.\n for (const r of structured.results.filter((x) => 'error' in x).slice(0, 3)) {\n lines.push(`// ${r.path}: ${r.error.code} ${r.error.message}`);\n }\n if (structured.diffTruncated) {\n lines.push(\n `// diff cut at ${String(MAX_DIFF_SIZE / 1024)} KB: some files' changes are not shown.`,\n );\n }\n const text = lines.join('\\n') + (structured.diff ? `\\n\\n${structured.diff}` : '');\n const isError = isTotalFailure(structured.summary);\n if (link) {\n return { structured, text, resources: [link], isError };\n }\n return { structured, text, isError };\n },\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"http-policy.d.ts","sourceRoot":"","sources":["../../src/transport/http-policy.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAgB,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAS/E;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,SAAS,CAAC;AAE3C;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,EAAE,GAAE,MAAM,GAAG,MAAM,GAAG,IAAW,EACjC,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACnC,IAAI,CAKN;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAQxD;AAQD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAG5F;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,OAAO,GAClB,UAAU,IAAI,MAAM,CAiBtB;AAMD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAetF;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,GAAG,SAAS,GAClC,MAAM,EAAE,CAMV;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAI/F;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,iBAAiB,EAAE,OAAO,GACzB,IAAI,CAUN;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,OAAO,EACZ,aAAa,EAAE,OAAO,EACtB,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,GAAG,GAAG,IAAI,CAeZ;AAyBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,aAAa,EAAE,OAAO,EACtB,SAAS,CAAC,EAAE,MAAM,GACjB,cAAc,CAgChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAMtF;AAED;;;;;;;;;;;;;;;wEAewE;AACxE,wBAAgB,cAAc,CAAC,sBAAsB,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc,CAqBxF;AAYD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CA+B7D"}
1
+ {"version":3,"file":"http-policy.d.ts","sourceRoot":"","sources":["../../src/transport/http-policy.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAgB,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAS/E;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,SAAS,CAAC;AAE3C;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,EAAE,GAAE,MAAM,GAAG,MAAM,GAAG,IAAW,EACjC,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACnC,IAAI,CAKN;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAQxD;AAQD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAG5F;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,OAAO,GAClB,UAAU,IAAI,MAAM,CAiBtB;AAMD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAetF;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,GAAG,SAAS,GAClC,MAAM,EAAE,CAMV;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAI/F;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,iBAAiB,EAAE,OAAO,GACzB,IAAI,CAUN;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,OAAO,EACZ,aAAa,EAAE,OAAO,EACtB,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,GAAG,GAAG,IAAI,CAeZ;AAyBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,aAAa,EAAE,OAAO,EACtB,SAAS,CAAC,EAAE,MAAM,GACjB,cAAc,CAiChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAMtF;AAED;;;;;;;;;;;;;;;wEAewE;AACxE,wBAAgB,cAAc,CAAC,sBAAsB,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc,CAqBxF;AAYD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CA+B7D"}
@@ -224,6 +224,7 @@ export function bearerAuthMiddleware(apiKey, hostValidated, publicUrl) {
224
224
  // Forward the validated caller to the SDK pipeline: toNodeHandler reads
225
225
  // req.auth and passes it as the handler's pass-through authInfo, which the
226
226
  // per-request factory receives on ctx.http.
227
+ // `req.auth` is typed by @modelcontextprotocol/express's Request augmentation.
227
228
  // `expiresAt` is deliberately omitted: a static API key has no expiry and
228
229
  // there is no `exp` claim or introspection response to read one from.
229
230
  // Populate it from the token if this ever moves to issued tokens.
@@ -1 +1 @@
1
- {"version":3,"file":"http-policy.js","sourceRoot":"","sources":["../../src/transport/http-policy.ts"],"names":[],"mappings":"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","sourcesContent":["import { getOAuthProtectedResourceMetadataUrl } from '@modelcontextprotocol/express';\nimport {\n localhostAllowedHostnames,\n localhostAllowedOrigins,\n resourceUrlFromServerUrl,\n validateOriginHeader,\n} from '@modelcontextprotocol/server';\nimport type { AuthInfo } from '@modelcontextprotocol/server';\n\nimport { createHash, timingSafeEqual } from 'node:crypto';\n\nimport type { NextFunction, Request, RequestHandler, Response } from 'express';\n\nimport { ErrorCode, FsError } from '../core/errors.ts';\nimport { Logger } from '../core/observability.ts';\nimport { splitCsvList } from '../core/util.ts';\nimport { jsonRpcError } from './shared.ts';\n\nconst MAX_BEARER_TOKEN_LENGTH = 4096;\n\n/**\n * JSON-RPC's server-defined error range. No SDK enum maps to \"Unauthorized\" or\n * \"Rate limit exceeded\", both of which this module refuses before any handler\n * has parsed a request.\n */\nexport const JSONRPC_SERVER_ERROR = -32000;\n\n/**\n * The one way an HTTP refusal goes out — this module's pre-handler rejections\n * and `http.ts`'s route-level ones. The envelope itself is `jsonRpcError` in\n * `shared.ts`, so the stdio leg sends the same literal; this wrapper owns only\n * what HTTP adds on top.\n *\n * `headers` carries what a particular refusal owes the client on top of the\n * JSON content type — `WWW-Authenticate` on a 401, `Retry-After` on a 429.\n * Goes out through `res.json` so every refusal keeps the `charset=utf-8` and\n * `Content-Length` a hand-rolled `writeHead`/`end` pair drops.\n */\nexport function sendJsonRpcError(\n res: Response,\n status: number,\n code: number,\n message: string,\n id: string | number | null = null,\n headers: Record<string, string> = {},\n): void {\n res\n .status(status)\n .set(headers)\n .json(jsonRpcError(code, message, id));\n}\n\n/**\n * Pure HTTP auth and binding policy. Holds no state; all functions are\n * directly testable without spinning up a server.\n */\nexport function isLoopbackHttpHost(host: string): boolean {\n const normalizedHost = host.trim().toLowerCase();\n return (\n normalizedHost === '127.0.0.1' ||\n normalizedHost === 'localhost' ||\n normalizedHost === '[::1]' ||\n normalizedHost === '::1'\n );\n}\n\n/** A bind to every interface. Clients never send `Host: 0.0.0.0`, so this bind\n * cannot derive its allowed-host set from itself. */\nfunction isWildcardHttpHost(host: string): boolean {\n return host === '0.0.0.0' || host === '::';\n}\n\n/**\n * True if `origin` (a raw `Origin` request header) may have\n * `Access-Control-Allow-Origin` reflected. `allowedHostnames` is the same\n * hostname-form list `originValidation` receives in `http.ts`, and\n * the check is the SDK's own `validateOriginHeader`, so reflection matches\n * admission: the SDK gate runs first and answers 403 to any Origin outside\n * that list — localhost included once `FS_ALLOWED_ORIGINS` replaces the\n * loopback default. An empty list (an all-blank `FS_ALLOWED_ORIGINS`) mounts no\n * `allowedOrigins`: a loopback bind keeps the SDK's localhost default, which\n * this reuses, but a concrete or wildcard bind then mounts no Origin gate at\n * all, so there admission is wider than reflection — which fails safe, since\n * the browser still blocks an unreflected response. An empty header is never\n * reflected — the SDK treats an\n * absent `Origin` as allowed, which is right for admission but not for echoing.\n */\nexport function isOriginAllowed(origin: string, allowedHostnames: readonly string[]): boolean {\n const allowed = allowedHostnames.length > 0 ? [...allowedHostnames] : localhostAllowedOrigins();\n return origin !== '' && validateOriginHeader(origin, allowed).ok;\n}\n\nexport function validateBearerAuthorization(\n apiKey: string,\n authHeader: unknown,\n): authHeader is string {\n const bearerPrefix = 'Bearer ';\n if (typeof authHeader !== 'string' || !authHeader.startsWith(bearerPrefix)) {\n return false;\n }\n const userKey = authHeader.slice(bearerPrefix.length).trim();\n const normalizedApiKey = apiKey.trim();\n if (userKey.length > MAX_BEARER_TOKEN_LENGTH || normalizedApiKey.length === 0) {\n return false;\n }\n\n // Pure: hash per call. createHash is negligible next to the timingSafeEqual\n // already done per request, and avoiding module-level cache state keeps this\n // testable without post-import env-mutation footguns.\n const expectedHash = createHash('sha256').update(normalizedApiKey).digest();\n const actualHash = createHash('sha256').update(userKey).digest();\n return timingSafeEqual(expectedHash, actualHash);\n}\n\nfunction isSecureApiKey(key: string | undefined): boolean {\n return typeof key === 'string' && key.trim().length >= 16;\n}\n\n/**\n * Refuse to bind to a non-loopback host without an API key. Throws on\n * policy violation; returns silently when allowed.\n */\nexport function assertHttpBindingPolicy(host: string, apiKey: string | undefined): void {\n if (isLoopbackHttpHost(host)) {\n if (apiKey !== undefined && !isSecureApiKey(apiKey)) {\n throw new FsError(\n ErrorCode.PERMISSION_DENIED,\n 'The configured API key (--api-key / FS_API_KEY) is insecure (minimum 16 characters).',\n );\n }\n return;\n }\n if (isSecureApiKey(apiKey)) return;\n throw new FsError(\n ErrorCode.PERMISSION_DENIED,\n `Refusing to bind HTTP server to non-loopback host '${host}' without a secure API key (set --api-key or FS_API_KEY, minimum 16 characters).`,\n );\n}\n\n/**\n * The Host header values this bind accepts. `FS_ALLOWED_HOSTS` wins\n * when set; otherwise a loopback bind takes the whole localhost hostname set (a\n * client dialing http://localhost:<port> sends `Host: localhost`, which a bare\n * ['127.0.0.1'] list would 403), a wildcard bind takes none, and a concrete\n * non-loopback bind takes itself. An empty result means no Host validation is\n * mounted — only reachable under FS_ALLOW_UNRESTRICTED_HOSTS=1.\n */\nexport function resolveAllowedHosts(\n httpHost: string,\n allowedHostsEnv: string | undefined,\n): string[] {\n const configured = splitCsvList(allowedHostsEnv);\n if (configured.length > 0) return configured;\n if (isLoopbackHttpHost(httpHost)) return localhostAllowedHostnames();\n if (isWildcardHttpHost(httpHost)) return [];\n return [httpHost];\n}\n\n/**\n * Express's `trust proxy` setting from `FS_TRUST_PROXY`. A\n * non-negative integer hop count parses to a number (Express counts hops from\n * the socket); anything else (a subnet name/expression, or a negative/\n * non-integer string) passes through unchanged for Express to interpret.\n * `undefined` means the env var was unset or empty — leave Express's default\n * (disabled) in place.\n */\nexport function resolveTrustProxySetting(value: string | undefined): number | string | undefined {\n if (!value) return undefined;\n const hops = Number(value);\n return Number.isInteger(hops) && hops >= 0 ? hops : value;\n}\n\n/**\n * Refuse to bind a wildcard host (`0.0.0.0` / `::`) without an explicit\n * `FS_ALLOWED_HOSTS` list. Clients never send `Host: 0.0.0.0`, so\n * defaulting the allowed-host set to the wildcard string would reject all real\n * traffic. Operators who accept the risk can set\n * `FS_ALLOW_UNRESTRICTED_HOSTS=1` to restore warn-and-bind.\n * Loopback and concrete non-loopback hosts are unaffected.\n */\nexport function assertHttpHostPolicy(\n host: string,\n allowedHosts: readonly string[],\n allowUnrestricted: boolean,\n): void {\n if (isLoopbackHttpHost(host)) return;\n // concrete non-loopback: Host validated against the bind host.\n if (!isWildcardHttpHost(host)) return;\n if (allowUnrestricted) return;\n if (allowedHosts.length > 0) return;\n throw new FsError(\n ErrorCode.PERMISSION_DENIED,\n `Refusing to bind wildcard host '${host}' without FS_ALLOWED_HOSTS. Set it to the public hostname(s) clients send, or set FS_ALLOW_UNRESTRICTED_HOSTS=true to accept the risk.`,\n );\n}\n\n// ─── Protected-resource discovery (RFC 9728 / RFC 6750) ──────────────────────\n\n/**\n * This server is a resource server with no authorization server: `FS_API_KEY` is a\n * static secret the operator hands out of band, not an issued token. So the\n * metadata document deliberately omits `authorization_servers` — RFC 9728 §2\n * makes it optional, and its absence is the accurate statement that a token\n * cannot be obtained from an endpoint. `mcpAuthMetadataRouter` from\n * `@modelcontextprotocol/express` is the tool for the IdP-backed case: it\n * requires RFC 8414 authorization-server metadata, and inventing an issuer with\n * endpoints that answer nothing would send clients into a flow that cannot\n * complete. Adopt it if this ever moves to a real IdP.\n *\n * What discovery buys here: a client hitting 401 learns the resource identifier\n * and that the credential goes in the Authorization header, instead of a bare\n * challenge plus a 404 on the well-known path.\n */\nexport function protectedResourceUrl(\n req: Request,\n hostValidated: boolean,\n configured: string | undefined,\n): URL | null {\n if (configured) {\n const parsed = URL.parse(configured);\n // RFC 8707 §2: a resource indicator must not carry a fragment.\n if (parsed) return resourceUrlFromServerUrl(parsed);\n Logger.warn(\n `[HTTP] Ignoring unparseable FS_PUBLIC_URL: ${configured}. Deriving the resource identifier from the Host header instead.`,\n );\n }\n // With no Host validator mounted (FS_ALLOW_UNRESTRICTED_HOSTS=1)\n // the Host header is attacker-controlled, and naming a resource from it would\n // publish an identifier the operator does not own. Answer with nothing instead.\n if (!hostValidated) return null;\n const host = req.headers.host ?? '127.0.0.1';\n return URL.parse(`${req.secure ? 'https' : 'http'}://${host}/mcp`);\n}\n\n/**\n * RFC 6750 §3: a request with no credentials gets a bare challenge, while one\n * that presented something invalid gets `error=\"invalid_token\"`. Clients use\n * the difference to tell \"you must authenticate\" from \"your token is wrong\".\n */\nfunction buildAuthChallenge(\n req: Request,\n hasCredentials: boolean,\n hostValidated: boolean,\n publicUrl: string | undefined,\n): string {\n const resource = protectedResourceUrl(req, hostValidated, publicUrl);\n const params: string[] = [];\n if (resource) {\n params.push(`resource_metadata=\"${getOAuthProtectedResourceMetadataUrl(resource)}\"`);\n }\n if (hasCredentials) {\n params.push('error=\"invalid_token\"', 'error_description=\"Invalid or expired bearer token\"');\n }\n // A bare `Bearer` is a complete challenge; avoid emitting a trailing space.\n return params.length > 0 ? `Bearer ${params.join(', ')}` : 'Bearer';\n}\n\n/**\n * Express middleware: when `apiKey` is set, require a matching bearer token.\n * No key set = open access (loopback dev mode). `apiKey` is captured once per\n * app setup (passed in from startHttpServer) so the middleware and\n * assertHttpBindingPolicy share one source of truth.\n *\n * `requireBearerAuth` + `verifyAccessToken` from `@modelcontextprotocol/express`\n * is the tool for verifier-backed tokens; this middleware stays hand-rolled\n * because the credential is a static operator key with no verifier, the 401 body\n * must stay a JSON-RPC envelope, and the RFC 6750 bare-vs-`invalid_token`\n * challenge split is implemented here. Adopt `requireBearerAuth` if this ever\n * verifies issued tokens.\n */\nexport function bearerAuthMiddleware(\n apiKey: string | undefined,\n hostValidated: boolean,\n publicUrl?: string,\n): RequestHandler {\n return (req: Request, res: Response, next: NextFunction): void => {\n if (!apiKey) {\n next();\n return;\n }\n const authHeader = req.headers.authorization;\n if (isSecureApiKey(apiKey) && validateBearerAuthorization(apiKey, authHeader)) {\n // Forward the validated caller to the SDK pipeline: toNodeHandler reads\n // req.auth and passes it as the handler's pass-through authInfo, which the\n // per-request factory receives on ctx.http.\n // `expiresAt` is deliberately omitted: a static API key has no expiry and\n // there is no `exp` claim or introspection response to read one from.\n // Populate it from the token if this ever moves to issued tokens.\n const presented = authHeader.slice('Bearer '.length).trim();\n (req as Request & { auth?: AuthInfo }).auth = {\n token: presented,\n clientId: 'api-key',\n scopes: [],\n };\n next();\n return;\n }\n sendJsonRpcError(res, 401, JSONRPC_SERVER_ERROR, 'Unauthorized', null, {\n 'WWW-Authenticate': buildAuthChallenge(\n req,\n req.headers.authorization !== undefined,\n hostValidated,\n publicUrl,\n ),\n });\n };\n}\n\n/**\n * Env-derived CORS origins (hostname-form, no scheme/port — matches the SDK\n * app's `allowedOrigins` consumer). Defaults to the SDK's loopback hostname set\n * so loopback browser clients keep working; operators set\n * `FS_ALLOWED_ORIGINS` to allow remote clients on non-loopback\n * binds. An empty value reads as unset, matching how parseAllowedHostsEnv\n * treats an all-empty list. The same set is consulted by corsMiddleware\n * so a remote origin is reflected end-to-end in Access-Control-Allow-Origin.\n */\nexport function computeAllowedOriginHostnames(originsEnv: string | undefined): string[] {\n // Truthiness, not list length: an all-blank value (\" , \") is a configured but\n // empty allow-list — no remote origin is reflected, and a non-loopback bind\n // then mounts no SDK Origin gate (see isOriginAllowed) — while an unset or \"\"\n // value falls back to the loopback defaults.\n return originsEnv ? splitCsvList(originsEnv) : localhostAllowedOrigins();\n}\n\n/** Mounted at the `/mcp` prefix: every response — not just the OPTIONS preflight —\n * carries `Access-Control-Allow-Origin` for an Origin {@link isOriginAllowed}\n * accepts (no wildcard fallback), and the\n * preflight answers only the exact endpoint. Reflection must happen here\n * because the SDK's `originValidation` gate only decides which Origins\n * are accepted, it never sets `Access-Control-Allow-Origin` on the actual\n * response, so without this a browser client would pass preflight and then\n * have the POST response body blocked by CORS. Mounted at a prefix, Express\n * rewrites `req.url` to the remainder, so `req.path` is mount-relative:\n * `/` means `/mcp` and `/mcp/`\n * — exactly what the old exact-path `app.options('/mcp')` route matched — while\n * `/sub` stays a subpath and falls through to the same 404 it gets today, not a\n * phantom preflight. The allow-list carries the SEP-2243 standard headers\n * (`mcp-protocol-version`, `mcp-method`, `mcp-name`): SDK clients send all three\n * on every modern request POST and `createMcpHandler` requires them (400 /\n * -32020), so omitting them here would fail every browser preflight. */\nexport function corsMiddleware(allowedOriginHostnames: readonly string[]): RequestHandler {\n return (req: Request, res: Response, next: NextFunction): void => {\n const origin = req.headers.origin;\n if (origin && isOriginAllowed(origin, allowedOriginHostnames)) {\n res.header('Access-Control-Allow-Origin', origin);\n res.header('Access-Control-Expose-Headers', 'WWW-Authenticate, Retry-After');\n // Key the response by Origin so a CDN/proxy caching one origin's response\n // cannot replay it for a different origin (cache-poison).\n res.header('Vary', 'Origin');\n }\n if (req.method !== 'OPTIONS' || req.path !== '/') {\n next();\n return;\n }\n res.header('Access-Control-Allow-Methods', 'POST, OPTIONS');\n res.header(\n 'Access-Control-Allow-Headers',\n 'Content-Type, Authorization, mcp-protocol-version, mcp-method, mcp-name',\n );\n res.status(204).end();\n };\n}\n\n// ─── Rate limiting (public bind only) ────────────────────────────────────────\n\ninterface RateBucket {\n windowStart: number;\n count: number;\n}\n\n// Fixed 60s window; never tuned, so it is a constant rather than config plumbing.\nconst RATE_LIMIT_WINDOW_MS = 60_000;\n\n/**\n * Fixed-window per-client-IP rate limiter.\n */\nexport function createRateLimiter(max: number): RequestHandler {\n const buckets = new Map<string, RateBucket>();\n const sweep = setInterval(() => {\n const cutoff = Date.now() - RATE_LIMIT_WINDOW_MS * 2;\n for (const [ip, bucket] of buckets) {\n if (bucket.windowStart < cutoff) buckets.delete(ip);\n }\n }, RATE_LIMIT_WINDOW_MS);\n sweep.unref();\n\n return (req: Request, res: Response, next: NextFunction): void => {\n const ip = req.ip ?? req.socket.remoteAddress ?? 'unknown';\n const now = Date.now();\n let bucket = buckets.get(ip);\n if (!bucket || now - bucket.windowStart >= RATE_LIMIT_WINDOW_MS) {\n bucket = { windowStart: now, count: 0 };\n buckets.set(ip, bucket);\n }\n bucket.count += 1;\n if (bucket.count <= max) {\n next();\n return;\n }\n const retryAfterSec = Math.max(\n 1,\n Math.ceil((bucket.windowStart + RATE_LIMIT_WINDOW_MS - now) / 1000),\n );\n sendJsonRpcError(res, 429, JSONRPC_SERVER_ERROR, 'Rate limit exceeded', null, {\n 'Retry-After': String(retryAfterSec),\n });\n };\n}\n"]}
1
+ {"version":3,"file":"http-policy.js","sourceRoot":"","sources":["../../src/transport/http-policy.ts"],"names":[],"mappings":"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","sourcesContent":["import { getOAuthProtectedResourceMetadataUrl } from '@modelcontextprotocol/express';\nimport {\n localhostAllowedHostnames,\n localhostAllowedOrigins,\n resourceUrlFromServerUrl,\n validateOriginHeader,\n} from '@modelcontextprotocol/server';\n\nimport { createHash, timingSafeEqual } from 'node:crypto';\n\nimport type { NextFunction, Request, RequestHandler, Response } from 'express';\n\nimport { ErrorCode, FsError } from '../core/errors.ts';\nimport { Logger } from '../core/observability.ts';\nimport { splitCsvList } from '../core/util.ts';\nimport { jsonRpcError } from './shared.ts';\n\nconst MAX_BEARER_TOKEN_LENGTH = 4096;\n\n/**\n * JSON-RPC's server-defined error range. No SDK enum maps to \"Unauthorized\" or\n * \"Rate limit exceeded\", both of which this module refuses before any handler\n * has parsed a request.\n */\nexport const JSONRPC_SERVER_ERROR = -32000;\n\n/**\n * The one way an HTTP refusal goes out — this module's pre-handler rejections\n * and `http.ts`'s route-level ones. The envelope itself is `jsonRpcError` in\n * `shared.ts`, so the stdio leg sends the same literal; this wrapper owns only\n * what HTTP adds on top.\n *\n * `headers` carries what a particular refusal owes the client on top of the\n * JSON content type — `WWW-Authenticate` on a 401, `Retry-After` on a 429.\n * Goes out through `res.json` so every refusal keeps the `charset=utf-8` and\n * `Content-Length` a hand-rolled `writeHead`/`end` pair drops.\n */\nexport function sendJsonRpcError(\n res: Response,\n status: number,\n code: number,\n message: string,\n id: string | number | null = null,\n headers: Record<string, string> = {},\n): void {\n res\n .status(status)\n .set(headers)\n .json(jsonRpcError(code, message, id));\n}\n\n/**\n * Pure HTTP auth and binding policy. Holds no state; all functions are\n * directly testable without spinning up a server.\n */\nexport function isLoopbackHttpHost(host: string): boolean {\n const normalizedHost = host.trim().toLowerCase();\n return (\n normalizedHost === '127.0.0.1' ||\n normalizedHost === 'localhost' ||\n normalizedHost === '[::1]' ||\n normalizedHost === '::1'\n );\n}\n\n/** A bind to every interface. Clients never send `Host: 0.0.0.0`, so this bind\n * cannot derive its allowed-host set from itself. */\nfunction isWildcardHttpHost(host: string): boolean {\n return host === '0.0.0.0' || host === '::';\n}\n\n/**\n * True if `origin` (a raw `Origin` request header) may have\n * `Access-Control-Allow-Origin` reflected. `allowedHostnames` is the same\n * hostname-form list `originValidation` receives in `http.ts`, and\n * the check is the SDK's own `validateOriginHeader`, so reflection matches\n * admission: the SDK gate runs first and answers 403 to any Origin outside\n * that list — localhost included once `FS_ALLOWED_ORIGINS` replaces the\n * loopback default. An empty list (an all-blank `FS_ALLOWED_ORIGINS`) mounts no\n * `allowedOrigins`: a loopback bind keeps the SDK's localhost default, which\n * this reuses, but a concrete or wildcard bind then mounts no Origin gate at\n * all, so there admission is wider than reflection — which fails safe, since\n * the browser still blocks an unreflected response. An empty header is never\n * reflected — the SDK treats an\n * absent `Origin` as allowed, which is right for admission but not for echoing.\n */\nexport function isOriginAllowed(origin: string, allowedHostnames: readonly string[]): boolean {\n const allowed = allowedHostnames.length > 0 ? [...allowedHostnames] : localhostAllowedOrigins();\n return origin !== '' && validateOriginHeader(origin, allowed).ok;\n}\n\nexport function validateBearerAuthorization(\n apiKey: string,\n authHeader: unknown,\n): authHeader is string {\n const bearerPrefix = 'Bearer ';\n if (typeof authHeader !== 'string' || !authHeader.startsWith(bearerPrefix)) {\n return false;\n }\n const userKey = authHeader.slice(bearerPrefix.length).trim();\n const normalizedApiKey = apiKey.trim();\n if (userKey.length > MAX_BEARER_TOKEN_LENGTH || normalizedApiKey.length === 0) {\n return false;\n }\n\n // Pure: hash per call. createHash is negligible next to the timingSafeEqual\n // already done per request, and avoiding module-level cache state keeps this\n // testable without post-import env-mutation footguns.\n const expectedHash = createHash('sha256').update(normalizedApiKey).digest();\n const actualHash = createHash('sha256').update(userKey).digest();\n return timingSafeEqual(expectedHash, actualHash);\n}\n\nfunction isSecureApiKey(key: string | undefined): boolean {\n return typeof key === 'string' && key.trim().length >= 16;\n}\n\n/**\n * Refuse to bind to a non-loopback host without an API key. Throws on\n * policy violation; returns silently when allowed.\n */\nexport function assertHttpBindingPolicy(host: string, apiKey: string | undefined): void {\n if (isLoopbackHttpHost(host)) {\n if (apiKey !== undefined && !isSecureApiKey(apiKey)) {\n throw new FsError(\n ErrorCode.PERMISSION_DENIED,\n 'The configured API key (--api-key / FS_API_KEY) is insecure (minimum 16 characters).',\n );\n }\n return;\n }\n if (isSecureApiKey(apiKey)) return;\n throw new FsError(\n ErrorCode.PERMISSION_DENIED,\n `Refusing to bind HTTP server to non-loopback host '${host}' without a secure API key (set --api-key or FS_API_KEY, minimum 16 characters).`,\n );\n}\n\n/**\n * The Host header values this bind accepts. `FS_ALLOWED_HOSTS` wins\n * when set; otherwise a loopback bind takes the whole localhost hostname set (a\n * client dialing http://localhost:<port> sends `Host: localhost`, which a bare\n * ['127.0.0.1'] list would 403), a wildcard bind takes none, and a concrete\n * non-loopback bind takes itself. An empty result means no Host validation is\n * mounted — only reachable under FS_ALLOW_UNRESTRICTED_HOSTS=1.\n */\nexport function resolveAllowedHosts(\n httpHost: string,\n allowedHostsEnv: string | undefined,\n): string[] {\n const configured = splitCsvList(allowedHostsEnv);\n if (configured.length > 0) return configured;\n if (isLoopbackHttpHost(httpHost)) return localhostAllowedHostnames();\n if (isWildcardHttpHost(httpHost)) return [];\n return [httpHost];\n}\n\n/**\n * Express's `trust proxy` setting from `FS_TRUST_PROXY`. A\n * non-negative integer hop count parses to a number (Express counts hops from\n * the socket); anything else (a subnet name/expression, or a negative/\n * non-integer string) passes through unchanged for Express to interpret.\n * `undefined` means the env var was unset or empty — leave Express's default\n * (disabled) in place.\n */\nexport function resolveTrustProxySetting(value: string | undefined): number | string | undefined {\n if (!value) return undefined;\n const hops = Number(value);\n return Number.isInteger(hops) && hops >= 0 ? hops : value;\n}\n\n/**\n * Refuse to bind a wildcard host (`0.0.0.0` / `::`) without an explicit\n * `FS_ALLOWED_HOSTS` list. Clients never send `Host: 0.0.0.0`, so\n * defaulting the allowed-host set to the wildcard string would reject all real\n * traffic. Operators who accept the risk can set\n * `FS_ALLOW_UNRESTRICTED_HOSTS=1` to restore warn-and-bind.\n * Loopback and concrete non-loopback hosts are unaffected.\n */\nexport function assertHttpHostPolicy(\n host: string,\n allowedHosts: readonly string[],\n allowUnrestricted: boolean,\n): void {\n if (isLoopbackHttpHost(host)) return;\n // concrete non-loopback: Host validated against the bind host.\n if (!isWildcardHttpHost(host)) return;\n if (allowUnrestricted) return;\n if (allowedHosts.length > 0) return;\n throw new FsError(\n ErrorCode.PERMISSION_DENIED,\n `Refusing to bind wildcard host '${host}' without FS_ALLOWED_HOSTS. Set it to the public hostname(s) clients send, or set FS_ALLOW_UNRESTRICTED_HOSTS=true to accept the risk.`,\n );\n}\n\n// ─── Protected-resource discovery (RFC 9728 / RFC 6750) ──────────────────────\n\n/**\n * This server is a resource server with no authorization server: `FS_API_KEY` is a\n * static secret the operator hands out of band, not an issued token. So the\n * metadata document deliberately omits `authorization_servers` — RFC 9728 §2\n * makes it optional, and its absence is the accurate statement that a token\n * cannot be obtained from an endpoint. `mcpAuthMetadataRouter` from\n * `@modelcontextprotocol/express` is the tool for the IdP-backed case: it\n * requires RFC 8414 authorization-server metadata, and inventing an issuer with\n * endpoints that answer nothing would send clients into a flow that cannot\n * complete. Adopt it if this ever moves to a real IdP.\n *\n * What discovery buys here: a client hitting 401 learns the resource identifier\n * and that the credential goes in the Authorization header, instead of a bare\n * challenge plus a 404 on the well-known path.\n */\nexport function protectedResourceUrl(\n req: Request,\n hostValidated: boolean,\n configured: string | undefined,\n): URL | null {\n if (configured) {\n const parsed = URL.parse(configured);\n // RFC 8707 §2: a resource indicator must not carry a fragment.\n if (parsed) return resourceUrlFromServerUrl(parsed);\n Logger.warn(\n `[HTTP] Ignoring unparseable FS_PUBLIC_URL: ${configured}. Deriving the resource identifier from the Host header instead.`,\n );\n }\n // With no Host validator mounted (FS_ALLOW_UNRESTRICTED_HOSTS=1)\n // the Host header is attacker-controlled, and naming a resource from it would\n // publish an identifier the operator does not own. Answer with nothing instead.\n if (!hostValidated) return null;\n const host = req.headers.host ?? '127.0.0.1';\n return URL.parse(`${req.secure ? 'https' : 'http'}://${host}/mcp`);\n}\n\n/**\n * RFC 6750 §3: a request with no credentials gets a bare challenge, while one\n * that presented something invalid gets `error=\"invalid_token\"`. Clients use\n * the difference to tell \"you must authenticate\" from \"your token is wrong\".\n */\nfunction buildAuthChallenge(\n req: Request,\n hasCredentials: boolean,\n hostValidated: boolean,\n publicUrl: string | undefined,\n): string {\n const resource = protectedResourceUrl(req, hostValidated, publicUrl);\n const params: string[] = [];\n if (resource) {\n params.push(`resource_metadata=\"${getOAuthProtectedResourceMetadataUrl(resource)}\"`);\n }\n if (hasCredentials) {\n params.push('error=\"invalid_token\"', 'error_description=\"Invalid or expired bearer token\"');\n }\n // A bare `Bearer` is a complete challenge; avoid emitting a trailing space.\n return params.length > 0 ? `Bearer ${params.join(', ')}` : 'Bearer';\n}\n\n/**\n * Express middleware: when `apiKey` is set, require a matching bearer token.\n * No key set = open access (loopback dev mode). `apiKey` is captured once per\n * app setup (passed in from startHttpServer) so the middleware and\n * assertHttpBindingPolicy share one source of truth.\n *\n * `requireBearerAuth` + `verifyAccessToken` from `@modelcontextprotocol/express`\n * is the tool for verifier-backed tokens; this middleware stays hand-rolled\n * because the credential is a static operator key with no verifier, the 401 body\n * must stay a JSON-RPC envelope, and the RFC 6750 bare-vs-`invalid_token`\n * challenge split is implemented here. Adopt `requireBearerAuth` if this ever\n * verifies issued tokens.\n */\nexport function bearerAuthMiddleware(\n apiKey: string | undefined,\n hostValidated: boolean,\n publicUrl?: string,\n): RequestHandler {\n return (req: Request, res: Response, next: NextFunction): void => {\n if (!apiKey) {\n next();\n return;\n }\n const authHeader = req.headers.authorization;\n if (isSecureApiKey(apiKey) && validateBearerAuthorization(apiKey, authHeader)) {\n // Forward the validated caller to the SDK pipeline: toNodeHandler reads\n // req.auth and passes it as the handler's pass-through authInfo, which the\n // per-request factory receives on ctx.http.\n // `req.auth` is typed by @modelcontextprotocol/express's Request augmentation.\n // `expiresAt` is deliberately omitted: a static API key has no expiry and\n // there is no `exp` claim or introspection response to read one from.\n // Populate it from the token if this ever moves to issued tokens.\n const presented = authHeader.slice('Bearer '.length).trim();\n req.auth = {\n token: presented,\n clientId: 'api-key',\n scopes: [],\n };\n next();\n return;\n }\n sendJsonRpcError(res, 401, JSONRPC_SERVER_ERROR, 'Unauthorized', null, {\n 'WWW-Authenticate': buildAuthChallenge(\n req,\n req.headers.authorization !== undefined,\n hostValidated,\n publicUrl,\n ),\n });\n };\n}\n\n/**\n * Env-derived CORS origins (hostname-form, no scheme/port — matches the SDK\n * app's `allowedOrigins` consumer). Defaults to the SDK's loopback hostname set\n * so loopback browser clients keep working; operators set\n * `FS_ALLOWED_ORIGINS` to allow remote clients on non-loopback\n * binds. An empty value reads as unset, matching how parseAllowedHostsEnv\n * treats an all-empty list. The same set is consulted by corsMiddleware\n * so a remote origin is reflected end-to-end in Access-Control-Allow-Origin.\n */\nexport function computeAllowedOriginHostnames(originsEnv: string | undefined): string[] {\n // Truthiness, not list length: an all-blank value (\" , \") is a configured but\n // empty allow-list — no remote origin is reflected, and a non-loopback bind\n // then mounts no SDK Origin gate (see isOriginAllowed) — while an unset or \"\"\n // value falls back to the loopback defaults.\n return originsEnv ? splitCsvList(originsEnv) : localhostAllowedOrigins();\n}\n\n/** Mounted at the `/mcp` prefix: every response — not just the OPTIONS preflight —\n * carries `Access-Control-Allow-Origin` for an Origin {@link isOriginAllowed}\n * accepts (no wildcard fallback), and the\n * preflight answers only the exact endpoint. Reflection must happen here\n * because the SDK's `originValidation` gate only decides which Origins\n * are accepted, it never sets `Access-Control-Allow-Origin` on the actual\n * response, so without this a browser client would pass preflight and then\n * have the POST response body blocked by CORS. Mounted at a prefix, Express\n * rewrites `req.url` to the remainder, so `req.path` is mount-relative:\n * `/` means `/mcp` and `/mcp/`\n * — exactly what the old exact-path `app.options('/mcp')` route matched — while\n * `/sub` stays a subpath and falls through to the same 404 it gets today, not a\n * phantom preflight. The allow-list carries the SEP-2243 standard headers\n * (`mcp-protocol-version`, `mcp-method`, `mcp-name`): SDK clients send all three\n * on every modern request POST and `createMcpHandler` requires them (400 /\n * -32020), so omitting them here would fail every browser preflight. */\nexport function corsMiddleware(allowedOriginHostnames: readonly string[]): RequestHandler {\n return (req: Request, res: Response, next: NextFunction): void => {\n const origin = req.headers.origin;\n if (origin && isOriginAllowed(origin, allowedOriginHostnames)) {\n res.header('Access-Control-Allow-Origin', origin);\n res.header('Access-Control-Expose-Headers', 'WWW-Authenticate, Retry-After');\n // Key the response by Origin so a CDN/proxy caching one origin's response\n // cannot replay it for a different origin (cache-poison).\n res.header('Vary', 'Origin');\n }\n if (req.method !== 'OPTIONS' || req.path !== '/') {\n next();\n return;\n }\n res.header('Access-Control-Allow-Methods', 'POST, OPTIONS');\n res.header(\n 'Access-Control-Allow-Headers',\n 'Content-Type, Authorization, mcp-protocol-version, mcp-method, mcp-name',\n );\n res.status(204).end();\n };\n}\n\n// ─── Rate limiting (public bind only) ────────────────────────────────────────\n\ninterface RateBucket {\n windowStart: number;\n count: number;\n}\n\n// Fixed 60s window; never tuned, so it is a constant rather than config plumbing.\nconst RATE_LIMIT_WINDOW_MS = 60_000;\n\n/**\n * Fixed-window per-client-IP rate limiter.\n */\nexport function createRateLimiter(max: number): RequestHandler {\n const buckets = new Map<string, RateBucket>();\n const sweep = setInterval(() => {\n const cutoff = Date.now() - RATE_LIMIT_WINDOW_MS * 2;\n for (const [ip, bucket] of buckets) {\n if (bucket.windowStart < cutoff) buckets.delete(ip);\n }\n }, RATE_LIMIT_WINDOW_MS);\n sweep.unref();\n\n return (req: Request, res: Response, next: NextFunction): void => {\n const ip = req.ip ?? req.socket.remoteAddress ?? 'unknown';\n const now = Date.now();\n let bucket = buckets.get(ip);\n if (!bucket || now - bucket.windowStart >= RATE_LIMIT_WINDOW_MS) {\n bucket = { windowStart: now, count: 0 };\n buckets.set(ip, bucket);\n }\n bucket.count += 1;\n if (bucket.count <= max) {\n next();\n return;\n }\n const retryAfterSec = Math.max(\n 1,\n Math.ceil((bucket.windowStart + RATE_LIMIT_WINDOW_MS - now) / 1000),\n );\n sendJsonRpcError(res, 429, JSONRPC_SERVER_ERROR, 'Rate limit exceeded', null, {\n 'Retry-After': String(retryAfterSec),\n });\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/transport/http.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAUxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAmBrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAmNjD,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,EACtB,MAAM,GAAE,aAAkB,GACzB,OAAO,CAAC,MAAM,CAAC,CA4HjB"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/transport/http.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAUxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAmBrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAmNjD,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,EACtB,MAAM,GAAE,aAAkB,GACzB,OAAO,CAAC,MAAM,CAAC,CA6HjB"}
@@ -208,6 +208,7 @@ export async function startHttpServer(port, options, config = {}) {
208
208
  resourceStore: sharedStore,
209
209
  pageStore: sharedPageStore,
210
210
  era,
211
+ transport: 'http',
211
212
  ...(apiKey !== undefined ? { apiKey } : {}),
212
213
  });
213
214
  return c.mcp;