@j0hanz/filesystem-mcp 2.1.5 → 2.1.7

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 (112) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +6 -13
  3. package/dist/cli.js.map +1 -1
  4. package/dist/core/diff.d.ts +6 -0
  5. package/dist/core/diff.d.ts.map +1 -0
  6. package/dist/core/diff.js +21 -0
  7. package/dist/core/diff.js.map +1 -0
  8. package/dist/core/errors.d.ts +0 -20
  9. package/dist/core/errors.d.ts.map +1 -1
  10. package/dist/core/errors.js +9 -60
  11. package/dist/core/errors.js.map +1 -1
  12. package/dist/core/file-uri.d.ts +18 -2
  13. package/dist/core/file-uri.d.ts.map +1 -1
  14. package/dist/core/file-uri.js +34 -6
  15. package/dist/core/file-uri.js.map +1 -1
  16. package/dist/core/fs.d.ts +1 -2
  17. package/dist/core/fs.d.ts.map +1 -1
  18. package/dist/core/fs.js +3 -22
  19. package/dist/core/fs.js.map +1 -1
  20. package/dist/core/glob.d.ts +1 -6
  21. package/dist/core/glob.d.ts.map +1 -1
  22. package/dist/core/glob.js +0 -9
  23. package/dist/core/glob.js.map +1 -1
  24. package/dist/core/input-required.d.ts +2 -4
  25. package/dist/core/input-required.d.ts.map +1 -1
  26. package/dist/core/input-required.js +3 -4
  27. package/dist/core/input-required.js.map +1 -1
  28. package/dist/core/path-completer.d.ts.map +1 -1
  29. package/dist/core/path-completer.js +1 -6
  30. package/dist/core/path-completer.js.map +1 -1
  31. package/dist/core/path.d.ts +4 -8
  32. package/dist/core/path.d.ts.map +1 -1
  33. package/dist/core/path.js +13 -26
  34. package/dist/core/path.js.map +1 -1
  35. package/dist/core/primitives.d.ts +8 -2
  36. package/dist/core/primitives.d.ts.map +1 -1
  37. package/dist/core/primitives.js +10 -2
  38. package/dist/core/primitives.js.map +1 -1
  39. package/dist/core/read.d.ts +0 -15
  40. package/dist/core/read.d.ts.map +1 -1
  41. package/dist/core/read.js +12 -41
  42. package/dist/core/read.js.map +1 -1
  43. package/dist/core/schema.d.ts +1 -16
  44. package/dist/core/schema.d.ts.map +1 -1
  45. package/dist/core/schema.js +7 -41
  46. package/dist/core/schema.js.map +1 -1
  47. package/dist/core/search.d.ts +39 -3
  48. package/dist/core/search.d.ts.map +1 -1
  49. package/dist/core/search.js +72 -17
  50. package/dist/core/search.js.map +1 -1
  51. package/dist/core/sensitive.d.ts.map +1 -1
  52. package/dist/core/sensitive.js +7 -15
  53. package/dist/core/sensitive.js.map +1 -1
  54. package/dist/core/store.d.ts +0 -1
  55. package/dist/core/store.d.ts.map +1 -1
  56. package/dist/core/store.js +0 -1
  57. package/dist/core/store.js.map +1 -1
  58. package/dist/index.js +1 -8
  59. package/dist/index.js.map +1 -1
  60. package/dist/prompts.d.ts.map +1 -1
  61. package/dist/prompts.js +13 -28
  62. package/dist/prompts.js.map +1 -1
  63. package/dist/tools/batch.d.ts +5 -78
  64. package/dist/tools/batch.d.ts.map +1 -1
  65. package/dist/tools/batch.js +6 -115
  66. package/dist/tools/batch.js.map +1 -1
  67. package/dist/tools/create.d.ts.map +1 -1
  68. package/dist/tools/create.js +8 -18
  69. package/dist/tools/create.js.map +1 -1
  70. package/dist/tools/define.d.ts +1 -4
  71. package/dist/tools/define.d.ts.map +1 -1
  72. package/dist/tools/define.js +1 -22
  73. package/dist/tools/define.js.map +1 -1
  74. package/dist/tools/delete-file.d.ts.map +1 -1
  75. package/dist/tools/delete-file.js +6 -6
  76. package/dist/tools/delete-file.js.map +1 -1
  77. package/dist/tools/diff.d.ts.map +1 -1
  78. package/dist/tools/diff.js +3 -10
  79. package/dist/tools/diff.js.map +1 -1
  80. package/dist/tools/edit.d.ts.map +1 -1
  81. package/dist/tools/edit.js +35 -72
  82. package/dist/tools/edit.js.map +1 -1
  83. package/dist/tools/index.d.ts +0 -1
  84. package/dist/tools/index.d.ts.map +1 -1
  85. package/dist/tools/index.js +0 -1
  86. package/dist/tools/index.js.map +1 -1
  87. package/dist/tools/list.js +2 -1
  88. package/dist/tools/list.js.map +1 -1
  89. package/dist/tools/move.d.ts.map +1 -1
  90. package/dist/tools/move.js +162 -116
  91. package/dist/tools/move.js.map +1 -1
  92. package/dist/tools/patch.d.ts.map +1 -1
  93. package/dist/tools/patch.js +5 -23
  94. package/dist/tools/patch.js.map +1 -1
  95. package/dist/tools/read.d.ts.map +1 -1
  96. package/dist/tools/read.js +20 -21
  97. package/dist/tools/read.js.map +1 -1
  98. package/dist/tools/replace-in-files.d.ts.map +1 -1
  99. package/dist/tools/replace-in-files.js +33 -51
  100. package/dist/tools/replace-in-files.js.map +1 -1
  101. package/dist/tools/search-content.d.ts.map +1 -1
  102. package/dist/tools/search-content.js +10 -31
  103. package/dist/tools/search-content.js.map +1 -1
  104. package/dist/tools/search-files.d.ts.map +1 -1
  105. package/dist/tools/search-files.js +1 -2
  106. package/dist/tools/search-files.js.map +1 -1
  107. package/dist/tools/stat.d.ts.map +1 -1
  108. package/dist/tools/stat.js +4 -3
  109. package/dist/tools/stat.js.map +1 -1
  110. package/dist/transport/http-policy.js +1 -1
  111. package/dist/transport/http-policy.js.map +1 -1
  112. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"stat.js","sourceRoot":"","sources":["../../src/tools/stat.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { parse } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport { ErrorCode, rethrowIfAborted } from '../core/errors.js';\nimport type { FileInfo, GuardedFileSystem, Stats } from '../core/fs.js';\nimport { getFileType, isHidden } from '../core/fs.js';\nimport { detectMimeType } from '../core/mime.js';\nimport {\n FileInfoSchema,\n NonNegInt,\n OperationSummarySchema,\n PerFileErrorSchema,\n singleOrBatchAccessPaths,\n singleOrBatchPathsInput,\n} from '../core/schema.js';\nimport { putJsonResource } from '../core/store.js';\nimport { DEFAULT_SEARCH_TIMEOUT_MS } from '../core/util.js';\nimport type { PerPathResult } from './batch.js';\nimport { isTotalFailure, runOverPaths } from './batch.js';\nimport type { ToolCtx } from './define.js';\nimport { defineTool } from './define.js';\n\nconst StatInputSchema = singleOrBatchPathsInput({ extra: {} });\n\nconst StatPerPathSchema = z.strictObject({\n path: z.string().describe('Requested path'),\n value: FileInfoSchema.optional().describe('File metadata; present on success'),\n error: PerFileErrorSchema.optional().describe('Error details; present on failure'),\n});\n\nconst StatOutputSchema = z.strictObject({\n results: z\n .array(StatPerPathSchema)\n .describe('Per-path metadata results ordered to match the input paths'),\n summary: OperationSummarySchema,\n fileCount: NonNegInt.optional().describe('Number of regular files in the results'),\n dirCount: NonNegInt.optional().describe('Number of directories in the results'),\n resourceUri: z\n .string()\n .optional()\n .describe(\n 'URI to aggregated stats.json in the resource store (present when resource store is available)',\n ),\n});\n\nfunction getPermissions(mode: number): string {\n let out = '';\n for (const shift of [6, 3, 0]) {\n const bits = (mode >> shift) & 0b111;\n out += (bits & 0b100 ? 'r' : '-') + (bits & 0b010 ? 'w' : '-') + (bits & 0b001 ? 'x' : '-');\n }\n return out;\n}\n\nfunction buildFileInfoResult(\n name: string,\n requestedPath: string,\n isSymlink: boolean,\n stats: Stats,\n mimeType: string | undefined,\n symlinkTarget: string | undefined,\n): FileInfo {\n const tokenEstimate = stats.isFile() ? Math.ceil(stats.size / 4) : undefined;\n return {\n name,\n path: requestedPath,\n type: isSymlink ? 'symlink' : getFileType(stats),\n size: stats.size,\n ...(tokenEstimate !== undefined ? { tokenEstimate } : {}),\n created: stats.birthtime,\n modified: stats.mtime,\n accessed: stats.atime,\n permissions: getPermissions(stats.mode),\n isHidden: isHidden(name),\n ...(mimeType !== undefined ? { mimeType } : {}),\n ...(symlinkTarget !== undefined ? { symlinkTarget } : {}),\n };\n}\n\nasync function getSymlinkTarget(\n pathToRead: string,\n fs: GuardedFileSystem,\n signal?: AbortSignal,\n log?: ToolCtx['log'],\n): Promise<string | undefined> {\n signal?.throwIfAborted();\n try {\n const { linkString } = await fs.readlink(pathToRead);\n return linkString;\n } catch (error) {\n rethrowIfAborted(error);\n log?.('warning', `stat: readlink failed for \"${pathToRead}\": ${String(error)}`, 'stat');\n return undefined;\n }\n}\n\ninterface FileInfoOptions {\n signal?: AbortSignal | undefined;\n fs: GuardedFileSystem;\n log?: ToolCtx['log'] | undefined;\n}\n\nasync function getFileInfo(filePath: string, options: FileInfoOptions): Promise<FileInfo> {\n const { signal, fs, log } = options;\n signal?.throwIfAborted();\n\n const {\n requestedPath,\n isSymlink,\n stats: followedStats,\n } = await fs.statDetailed(filePath, signal ? { signal } : undefined);\n\n const { base: name, ext: rawExt } = parse(requestedPath);\n const mimeType = rawExt.length > 0 ? detectMimeType(requestedPath).mimeType : undefined;\n\n // For a symlink, fsStat follows the link and reports the target's metadata.\n // Report the link's own metadata instead so size/permissions/timestamps\n // describe the link the user asked about, not its target.\n let stats = followedStats;\n if (isSymlink) {\n try {\n ({ stats } = await fs.lstat(requestedPath, signal ? { signal } : undefined));\n } catch (error) {\n // A cancelled request is not a \"link metadata unavailable\" fallback.\n rethrowIfAborted(error);\n // Guarded lstat can be refused where raw lstat succeeded (e.g. an allowed\n // root that is itself a symlink, whose parent sits outside the roots).\n // Falling back to the followed stats degrades the report to the target's\n // metadata, so leave a trace instead of degrading silently.\n log?.('warning', `stat: lstat failed for \"${requestedPath}\": ${String(error)}`, 'stat');\n stats = followedStats;\n }\n }\n\n // isSymlink above is true whenever ANY ancestor is a symlink, because it\n // compares the requested path with its fully-resolved real path. Only the\n // entry's own lstat says whether THIS path is a link, so a regular file under\n // a symlinked parent is reported as a file and no readlink is attempted.\n const isOwnSymlink = stats.isSymbolicLink();\n const symlinkTarget = isOwnSymlink\n ? await getSymlinkTarget(requestedPath, fs, signal, log)\n : undefined;\n\n return buildFileInfoResult(name, requestedPath, isOwnSymlink, stats, mimeType, symlinkTarget);\n}\n\nfunction classifyTypeCounts(results: readonly PerPathResult<FileInfo>[]): {\n fileCount: number;\n dirCount: number;\n} {\n let fileCount = 0;\n let dirCount = 0;\n for (const result of results) {\n if ('error' in result) continue;\n if (result.value.type === 'directory') dirCount += 1;\n else fileCount += 1;\n }\n return { fileCount, dirCount };\n}\n\nfunction toStatPerPathPayload(r: PerPathResult<FileInfo>): z.infer<typeof StatPerPathSchema> {\n if ('error' in r) {\n return { path: r.path, error: r.error };\n }\n const info = r.value;\n return {\n path: r.path,\n value: {\n name: info.name,\n path: info.path,\n type: info.type,\n size: info.size,\n ...(info.tokenEstimate !== undefined ? { tokenEstimate: info.tokenEstimate } : {}),\n created: info.created.toISOString(),\n modified: info.modified.toISOString(),\n accessed: info.accessed.toISOString(),\n permissions: info.permissions,\n isHidden: info.isHidden,\n ...(info.mimeType !== undefined ? { mimeType: info.mimeType } : {}),\n ...(info.symlinkTarget !== undefined ? { symlinkTarget: info.symlinkTarget } : {}),\n },\n };\n}\n\nexport const GET_FILE_INFO = defineTool({\n name: 'stat',\n title: 'Get File Info',\n description:\n 'Get metadata for one or more files or directories: size, type, permissions, MIME type, timestamps, and tokenEstimate. ' +\n 'Use tokenEstimate to pre-screen read cost before calling read. ' +\n 'Single path: pass path. Batch mode: pass paths[].',\n input: StatInputSchema,\n output: StatOutputSchema,\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: false,\n },\n timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS,\n defaultErrorCode: ErrorCode.NOT_FOUND,\n progress: (args) => {\n if (args.paths !== undefined) {\n return { label: 'Stat', subject: `${String(args.paths.length)} paths` };\n }\n return { label: 'Stat', subject: args.path ?? '' };\n },\n accessPaths: singleOrBatchAccessPaths,\n run: async (args, ctx) => {\n const batchInput = args.path !== undefined ? { path: args.path } : { paths: args.paths ?? [] };\n\n const batch = await runOverPaths<undefined, FileInfo>(\n batchInput,\n ctx,\n async ({ path }) =>\n getFileInfo(path, {\n fs: ctx.fs,\n signal: ctx.signal,\n log: ctx.log,\n }),\n { defaultErrorCode: ErrorCode.NOT_FOUND },\n );\n\n const { fileCount, dirCount } = classifyTypeCounts(batch.results);\n const perPathPayload = batch.results.map(toStatPerPathPayload);\n\n let resourceUri: string | undefined;\n const resources: ContentBlock[] = [];\n if (ctx.resourceStore && batch.summary.total > 1) {\n const { entry, link } = putJsonResource(\n ctx.resourceStore,\n `${String(batch.summary.total)} paths`,\n perPathPayload,\n );\n resourceUri = entry.uri;\n resources.push(link);\n }\n\n // No `text` on purpose. The one-liner this used to build (`AGENTS.md: file,\n // 751 B`) dropped tokenEstimate, modified, mimeType and isHidden — the very\n // fields a caller asks `stat` for. Supplying no text makes this a data tool,\n // so `define.ts` renders the JSON and keeps it in `structuredContent`.\n return {\n structured: {\n results: perPathPayload,\n summary: batch.summary,\n fileCount,\n dirCount,\n ...(resourceUri ? { resourceUri } : {}),\n },\n isError: isTotalFailure(batch.summary),\n ...(resources.length > 0 ? { resources } : {}),\n };\n },\n});\n"]}
1
+ {"version":3,"file":"stat.js","sourceRoot":"","sources":["../../src/tools/stat.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { parse } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport { ErrorCode, rethrowIfAborted } from '../core/errors.js';\nimport type { FileInfo, GuardedFileSystem, Stats } from '../core/fs.js';\nimport { isHidden } from '../core/fs.js';\nimport { detectMimeType } from '../core/mime.js';\nimport { resolveEntryType } from '../core/primitives.js';\nimport {\n FileInfoSchema,\n NonNegInt,\n OperationSummarySchema,\n PerFileErrorSchema,\n singleOrBatchAccessPaths,\n singleOrBatchPathsInput,\n} from '../core/schema.js';\nimport { putJsonResource } from '../core/store.js';\nimport { DEFAULT_SEARCH_TIMEOUT_MS } from '../core/util.js';\nimport type { PerPathResult } from './batch.js';\nimport { isTotalFailure, runOverPaths } from './batch.js';\nimport type { ToolCtx } from './define.js';\nimport { defineTool } from './define.js';\n\nconst StatInputSchema = singleOrBatchPathsInput({});\n\nconst StatPerPathSchema = z.strictObject({\n path: z.string().describe('Requested path'),\n value: FileInfoSchema.optional().describe('File metadata; present on success'),\n error: PerFileErrorSchema.optional().describe('Error details; present on failure'),\n});\n\nconst StatOutputSchema = z.strictObject({\n results: z\n .array(StatPerPathSchema)\n .describe('Per-path metadata results ordered to match the input paths'),\n summary: OperationSummarySchema,\n fileCount: NonNegInt.optional().describe('Number of regular files in the results'),\n dirCount: NonNegInt.optional().describe('Number of directories in the results'),\n resourceUri: z\n .string()\n .optional()\n .describe(\n 'URI to aggregated stats.json in the resource store (present when resource store is available)',\n ),\n});\n\nfunction getPermissions(mode: number): string {\n let out = '';\n for (const shift of [6, 3, 0]) {\n const bits = (mode >> shift) & 0b111;\n out += (bits & 0b100 ? 'r' : '-') + (bits & 0b010 ? 'w' : '-') + (bits & 0b001 ? 'x' : '-');\n }\n return out;\n}\n\nfunction buildFileInfoResult(\n name: string,\n requestedPath: string,\n isSymlink: boolean,\n stats: Stats,\n mimeType: string | undefined,\n symlinkTarget: string | undefined,\n): FileInfo {\n const tokenEstimate = stats.isFile() ? Math.ceil(stats.size / 4) : undefined;\n return {\n name,\n path: requestedPath,\n type: isSymlink ? 'symlink' : resolveEntryType(stats),\n size: stats.size,\n ...(tokenEstimate !== undefined ? { tokenEstimate } : {}),\n created: stats.birthtime,\n modified: stats.mtime,\n accessed: stats.atime,\n permissions: getPermissions(stats.mode),\n isHidden: isHidden(name),\n ...(mimeType !== undefined ? { mimeType } : {}),\n ...(symlinkTarget !== undefined ? { symlinkTarget } : {}),\n };\n}\n\nasync function getSymlinkTarget(\n pathToRead: string,\n fs: GuardedFileSystem,\n signal?: AbortSignal,\n log?: ToolCtx['log'],\n): Promise<string | undefined> {\n signal?.throwIfAborted();\n try {\n const { linkString } = await fs.readlink(pathToRead);\n return linkString;\n } catch (error) {\n rethrowIfAborted(error);\n log?.('warning', `stat: readlink failed for \"${pathToRead}\": ${String(error)}`, 'stat');\n return undefined;\n }\n}\n\ninterface FileInfoOptions {\n signal?: AbortSignal | undefined;\n fs: GuardedFileSystem;\n log?: ToolCtx['log'] | undefined;\n}\n\nasync function getFileInfo(filePath: string, options: FileInfoOptions): Promise<FileInfo> {\n const { signal, fs, log } = options;\n signal?.throwIfAborted();\n\n const {\n requestedPath,\n isSymlink,\n stats: followedStats,\n } = await fs.statDetailed(filePath, signal ? { signal } : undefined);\n\n const { base: name, ext: rawExt } = parse(requestedPath);\n const mimeType = rawExt.length > 0 ? detectMimeType(requestedPath).mimeType : undefined;\n\n // For a symlink, fsStat follows the link and reports the target's metadata.\n // Report the link's own metadata instead so size/permissions/timestamps\n // describe the link the user asked about, not its target.\n let stats = followedStats;\n if (isSymlink) {\n try {\n ({ stats } = await fs.lstat(requestedPath, signal ? { signal } : undefined));\n } catch (error) {\n // A cancelled request is not a \"link metadata unavailable\" fallback.\n rethrowIfAborted(error);\n // Guarded lstat can be refused where raw lstat succeeded (e.g. an allowed\n // root that is itself a symlink, whose parent sits outside the roots).\n // Falling back to the followed stats degrades the report to the target's\n // metadata, so leave a trace instead of degrading silently.\n log?.('warning', `stat: lstat failed for \"${requestedPath}\": ${String(error)}`, 'stat');\n stats = followedStats;\n }\n }\n\n // isSymlink above is true whenever ANY ancestor is a symlink, because it\n // compares the requested path with its fully-resolved real path. Only the\n // entry's own lstat says whether THIS path is a link, so a regular file under\n // a symlinked parent is reported as a file and no readlink is attempted.\n const isOwnSymlink = stats.isSymbolicLink();\n const symlinkTarget = isOwnSymlink\n ? await getSymlinkTarget(requestedPath, fs, signal, log)\n : undefined;\n\n return buildFileInfoResult(name, requestedPath, isOwnSymlink, stats, mimeType, symlinkTarget);\n}\n\nfunction classifyTypeCounts(results: readonly PerPathResult<FileInfo>[]): {\n fileCount: number;\n dirCount: number;\n} {\n let fileCount = 0;\n let dirCount = 0;\n for (const result of results) {\n if ('error' in result) continue;\n if (result.value.type === 'directory') dirCount += 1;\n else fileCount += 1;\n }\n return { fileCount, dirCount };\n}\n\nfunction toStatPerPathPayload(r: PerPathResult<FileInfo>): z.infer<typeof StatPerPathSchema> {\n if ('error' in r) {\n return { path: r.path, error: r.error };\n }\n const info = r.value;\n return {\n path: r.path,\n value: {\n name: info.name,\n path: info.path,\n type: info.type,\n size: info.size,\n ...(info.tokenEstimate !== undefined ? { tokenEstimate: info.tokenEstimate } : {}),\n created: info.created.toISOString(),\n modified: info.modified.toISOString(),\n accessed: info.accessed.toISOString(),\n permissions: info.permissions,\n isHidden: info.isHidden,\n ...(info.mimeType !== undefined ? { mimeType: info.mimeType } : {}),\n ...(info.symlinkTarget !== undefined ? { symlinkTarget: info.symlinkTarget } : {}),\n },\n };\n}\n\nexport const GET_FILE_INFO = defineTool({\n name: 'stat',\n title: 'Get File Info',\n description:\n 'Get metadata for one or more files or directories: size, type, permissions, MIME type, timestamps, and tokenEstimate. ' +\n 'Use tokenEstimate to pre-screen read cost before calling read. ' +\n 'Single path: pass path. Batch mode: pass paths[].',\n input: StatInputSchema,\n output: StatOutputSchema,\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: false,\n },\n timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS,\n defaultErrorCode: ErrorCode.NOT_FOUND,\n progress: (args) => {\n if (args.paths !== undefined) {\n return { label: 'Stat', subject: `${String(args.paths.length)} paths` };\n }\n return { label: 'Stat', subject: args.path ?? '' };\n },\n accessPaths: singleOrBatchAccessPaths,\n run: async (args, ctx) => {\n const batchInput = args.path !== undefined ? { path: args.path } : { paths: args.paths ?? [] };\n\n const batch = await runOverPaths<undefined, FileInfo>(\n batchInput,\n ctx,\n async ({ path }) =>\n getFileInfo(path, {\n fs: ctx.fs,\n signal: ctx.signal,\n log: ctx.log,\n }),\n { defaultErrorCode: ErrorCode.NOT_FOUND },\n );\n\n const { fileCount, dirCount } = classifyTypeCounts(batch.results);\n const perPathPayload = batch.results.map(toStatPerPathPayload);\n\n let resourceUri: string | undefined;\n const resources: ContentBlock[] = [];\n if (ctx.resourceStore && batch.summary.total > 1) {\n const { entry, link } = putJsonResource(\n ctx.resourceStore,\n `${String(batch.summary.total)} paths`,\n perPathPayload,\n );\n resourceUri = entry.uri;\n resources.push(link);\n }\n\n // No `text` on purpose. The one-liner this used to build (`AGENTS.md: file,\n // 751 B`) dropped tokenEstimate, modified, mimeType and isHidden — the very\n // fields a caller asks `stat` for. Supplying no text makes this a data tool,\n // so `define.ts` renders the JSON and keeps it in `structuredContent`.\n return {\n structured: {\n results: perPathPayload,\n summary: batch.summary,\n fileCount,\n dirCount,\n ...(resourceUri ? { resourceUri } : {}),\n },\n isError: isTotalFailure(batch.summary),\n ...(resources.length > 0 ? { resources } : {}),\n };\n },\n});\n"]}
@@ -230,7 +230,7 @@ export function bearerAuthMiddleware(apiKey, hostValidated, publicUrl) {
230
230
  if (isSecureApiKey(apiKey) && validateBearerAuthorization(apiKey, authHeader)) {
231
231
  // Forward the validated caller to the SDK pipeline: toNodeHandler reads
232
232
  // req.auth and passes it as the handler's pass-through authInfo, which the
233
- // per-request factory receives and tool handlers read as ctx.http.authInfo.
233
+ // per-request factory receives on ctx.http.
234
234
  // `expiresAt` is deliberately omitted: a static API key has no expiry and
235
235
  // there is no `exp` claim or introspection response to read one from.
236
236
  // 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 { localhostAllowedHostnames } 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.js';\nimport { Logger } from '../core/observability.js';\nimport { splitCsvList } from '../core/util.js';\nimport { jsonRpcError } from './shared.js';\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\nconst LOCALHOST_ORIGIN_RE = /^https?:\\/\\/(localhost|127\\.0\\.0\\.1|\\[::1\\])(:\\d+)?$/u;\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\nexport function isAllowedLocalhostOrigin(origin: string): boolean {\n return LOCALHOST_ORIGIN_RE.test(origin);\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\nfunction originHostname(origin: string): string | undefined {\n try {\n return new URL(origin).hostname;\n } catch {\n return undefined;\n }\n}\n\n/**\n * True if `origin` (a raw `Origin` request header) is allowed given the\n * env-derived `allowedHostnames` set (hostname-form, no scheme/port). Localhost\n * origins are always accepted via {@link isAllowedLocalhostOrigin}; a remote\n * origin is accepted iff its parsed hostname is in the set. Both the SDK app's\n * `allowedOrigins` and this OPTIONS-handler check consume hostname-form, so a\n * remote origin allowed via `FS_ALLOWED_ORIGINS` is reflected\n * end-to-end in `Access-Control-Allow-Origin`.\n */\nexport function isOriginAllowed(origin: string, allowedHostnames: readonly string[]): boolean {\n if (isAllowedLocalhostOrigin(origin)) return true;\n const host = originHostname(origin);\n return host !== undefined && allowedHostnames.includes(host);\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 'API_KEY is configured but 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 (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: `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 if (parsed) return 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 and tool handlers read as ctx.http.authInfo.\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 corsPreflightHandler\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 — deny every remote origin — while an unset or \"\" value\n // falls back to the loopback defaults.\n return originsEnv ? splitCsvList(originsEnv) : localhostAllowedHostnames();\n}\n\n/**\n * Reflect a present Origin on the response if it is allowed — localhost, or\n * in the env-derived `FS_ALLOWED_ORIGINS` set — and avoid\n * emitting a wildcard fallback. Shared by the OPTIONS preflight and the real\n * POST response: `createMcpExpressApp`'s `allowedOrigins` only gates which\n * Origins are accepted, it never sets `Access-Control-Allow-Origin` on the\n * actual response, so without this a browser client would pass preflight and\n * then have the POST response body blocked by CORS.\n */\nfunction reflectAllowedOrigin(\n req: Request,\n res: Response,\n allowedOriginHostnames: readonly string[],\n): void {\n const origin = req.headers.origin;\n if (origin && isOriginAllowed(origin, allowedOriginHostnames)) {\n res.header('Access-Control-Allow-Origin', origin);\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}\n\n/** Mounted ahead of the `/mcp` handlers so every response — not just the\n * OPTIONS preflight — carries `Access-Control-Allow-Origin` for an allowed\n * Origin. */\nexport function corsOriginMiddleware(allowedOriginHostnames: readonly string[]): RequestHandler {\n return (req: Request, res: Response, next: NextFunction): void => {\n reflectAllowedOrigin(req, res, allowedOriginHostnames);\n next();\n };\n}\n\n/**\n * OPTIONS preflight for `/mcp`. The allow-list carries the SEP-2243 standard\n * headers (`mcp-protocol-version`, `mcp-method`, `mcp-name`): SDK clients send\n * all three on every modern request POST and `createMcpHandler` requires them\n * (400 / -32020), so omitting them here would fail every browser preflight —\n * and the HTTP leg is modern-only, so there is no legacy path to fall back to.\n * `mcp-session-id` is deliberately absent: it is 2025-era only and this\n * endpoint rejects legacy traffic.\n */\nexport function corsPreflightHandler(allowedOriginHostnames: readonly string[]): RequestHandler {\n return (req: Request, res: Response): void => {\n reflectAllowedOrigin(req, res, allowedOriginHostnames);\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 { localhostAllowedHostnames } 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.js';\nimport { Logger } from '../core/observability.js';\nimport { splitCsvList } from '../core/util.js';\nimport { jsonRpcError } from './shared.js';\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\nconst LOCALHOST_ORIGIN_RE = /^https?:\\/\\/(localhost|127\\.0\\.0\\.1|\\[::1\\])(:\\d+)?$/u;\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\nexport function isAllowedLocalhostOrigin(origin: string): boolean {\n return LOCALHOST_ORIGIN_RE.test(origin);\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\nfunction originHostname(origin: string): string | undefined {\n try {\n return new URL(origin).hostname;\n } catch {\n return undefined;\n }\n}\n\n/**\n * True if `origin` (a raw `Origin` request header) is allowed given the\n * env-derived `allowedHostnames` set (hostname-form, no scheme/port). Localhost\n * origins are always accepted via {@link isAllowedLocalhostOrigin}; a remote\n * origin is accepted iff its parsed hostname is in the set. Both the SDK app's\n * `allowedOrigins` and this OPTIONS-handler check consume hostname-form, so a\n * remote origin allowed via `FS_ALLOWED_ORIGINS` is reflected\n * end-to-end in `Access-Control-Allow-Origin`.\n */\nexport function isOriginAllowed(origin: string, allowedHostnames: readonly string[]): boolean {\n if (isAllowedLocalhostOrigin(origin)) return true;\n const host = originHostname(origin);\n return host !== undefined && allowedHostnames.includes(host);\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 'API_KEY is configured but 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 (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: `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 if (parsed) return 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 corsPreflightHandler\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 — deny every remote origin — while an unset or \"\" value\n // falls back to the loopback defaults.\n return originsEnv ? splitCsvList(originsEnv) : localhostAllowedHostnames();\n}\n\n/**\n * Reflect a present Origin on the response if it is allowed — localhost, or\n * in the env-derived `FS_ALLOWED_ORIGINS` set — and avoid\n * emitting a wildcard fallback. Shared by the OPTIONS preflight and the real\n * POST response: `createMcpExpressApp`'s `allowedOrigins` only gates which\n * Origins are accepted, it never sets `Access-Control-Allow-Origin` on the\n * actual response, so without this a browser client would pass preflight and\n * then have the POST response body blocked by CORS.\n */\nfunction reflectAllowedOrigin(\n req: Request,\n res: Response,\n allowedOriginHostnames: readonly string[],\n): void {\n const origin = req.headers.origin;\n if (origin && isOriginAllowed(origin, allowedOriginHostnames)) {\n res.header('Access-Control-Allow-Origin', origin);\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}\n\n/** Mounted ahead of the `/mcp` handlers so every response — not just the\n * OPTIONS preflight — carries `Access-Control-Allow-Origin` for an allowed\n * Origin. */\nexport function corsOriginMiddleware(allowedOriginHostnames: readonly string[]): RequestHandler {\n return (req: Request, res: Response, next: NextFunction): void => {\n reflectAllowedOrigin(req, res, allowedOriginHostnames);\n next();\n };\n}\n\n/**\n * OPTIONS preflight for `/mcp`. The allow-list carries the SEP-2243 standard\n * headers (`mcp-protocol-version`, `mcp-method`, `mcp-name`): SDK clients send\n * all three on every modern request POST and `createMcpHandler` requires them\n * (400 / -32020), so omitting them here would fail every browser preflight —\n * and the HTTP leg is modern-only, so there is no legacy path to fall back to.\n * `mcp-session-id` is deliberately absent: it is 2025-era only and this\n * endpoint rejects legacy traffic.\n */\nexport function corsPreflightHandler(allowedOriginHostnames: readonly string[]): RequestHandler {\n return (req: Request, res: Response): void => {\n reflectAllowedOrigin(req, res, allowedOriginHostnames);\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@j0hanz/filesystem-mcp",
3
- "version": "2.1.5",
3
+ "version": "2.1.7",
4
4
  "mcpName": "io.github.j0hanz/filesystem-mcp",
5
5
  "description": "Secure filesystem MCP server for reading, writing, searching, diffing, and patching files.",
6
6
  "author": "j0hanz",