@j0hanz/filesystem-mcp 2.8.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -26
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +10 -1
- package/dist/cli-help.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +10 -3
- package/dist/cli.js.map +1 -1
- package/dist/core/concurrency.d.ts +4 -4
- package/dist/core/concurrency.d.ts.map +1 -1
- package/dist/core/concurrency.js +1 -1
- package/dist/core/concurrency.js.map +1 -1
- package/dist/core/config.d.ts +2 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/cursor.d.ts +8 -0
- package/dist/core/cursor.d.ts.map +1 -1
- package/dist/core/cursor.js +14 -0
- package/dist/core/cursor.js.map +1 -1
- package/dist/core/file-uri.d.ts +12 -3
- package/dist/core/file-uri.d.ts.map +1 -1
- package/dist/core/file-uri.js +35 -7
- package/dist/core/file-uri.js.map +1 -1
- package/dist/core/fmt.d.ts +2 -0
- package/dist/core/fmt.d.ts.map +1 -1
- package/dist/core/fmt.js +16 -0
- package/dist/core/fmt.js.map +1 -1
- package/dist/core/fs.d.ts +16 -1
- package/dist/core/fs.d.ts.map +1 -1
- package/dist/core/fs.js +191 -72
- package/dist/core/fs.js.map +1 -1
- package/dist/core/glob.d.ts +8 -0
- package/dist/core/glob.d.ts.map +1 -1
- package/dist/core/glob.js +84 -11
- package/dist/core/glob.js.map +1 -1
- package/dist/core/input-required.d.ts +7 -0
- package/dist/core/input-required.d.ts.map +1 -1
- package/dist/core/input-required.js +13 -0
- package/dist/core/input-required.js.map +1 -1
- package/dist/core/path-completer.d.ts.map +1 -1
- package/dist/core/path-completer.js +5 -3
- package/dist/core/path-completer.js.map +1 -1
- package/dist/core/path-utils.d.ts +51 -12
- package/dist/core/path-utils.d.ts.map +1 -1
- package/dist/core/path-utils.js +69 -29
- package/dist/core/path-utils.js.map +1 -1
- package/dist/core/path.d.ts +108 -21
- package/dist/core/path.d.ts.map +1 -1
- package/dist/core/path.js +436 -83
- package/dist/core/path.js.map +1 -1
- package/dist/core/read.d.ts +13 -3
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +46 -40
- package/dist/core/read.js.map +1 -1
- package/dist/core/schema.d.ts +9 -6
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +22 -19
- package/dist/core/schema.js.map +1 -1
- package/dist/core/search.d.ts +6 -0
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +159 -33
- package/dist/core/search.js.map +1 -1
- package/dist/core/sensitive.d.ts.map +1 -1
- package/dist/core/sensitive.js +71 -29
- package/dist/core/sensitive.js.map +1 -1
- package/dist/core/store.d.ts +1 -0
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/store.js +3 -0
- package/dist/core/store.js.map +1 -1
- package/dist/core/util.d.ts +12 -1
- package/dist/core/util.d.ts.map +1 -1
- package/dist/core/util.js +24 -1
- package/dist/core/util.js.map +1 -1
- package/dist/prompts.d.ts +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +1 -1
- package/dist/prompts.js.map +1 -1
- package/dist/resources.d.ts +1 -2
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +20 -13
- package/dist/resources.js.map +1 -1
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/dist/tools/create.d.ts.map +1 -1
- package/dist/tools/create.js +122 -45
- package/dist/tools/create.js.map +1 -1
- package/dist/tools/define.d.ts +8 -4
- package/dist/tools/define.d.ts.map +1 -1
- package/dist/tools/define.js +36 -9
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/delete.d.ts.map +1 -1
- package/dist/tools/delete.js +57 -17
- package/dist/tools/delete.js.map +1 -1
- package/dist/tools/diff.js +1 -1
- package/dist/tools/diff.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +14 -12
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/find-files.d.ts.map +1 -1
- package/dist/tools/find-files.js +20 -20
- package/dist/tools/find-files.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/list.d.ts.map +1 -1
- package/dist/tools/list.js +142 -73
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/move.d.ts.map +1 -1
- package/dist/tools/move.js +81 -36
- package/dist/tools/move.js.map +1 -1
- package/dist/tools/patch.js +13 -6
- package/dist/tools/patch.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +114 -45
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/replace-text.d.ts.map +1 -1
- package/dist/tools/replace-text.js +49 -35
- package/dist/tools/replace-text.js.map +1 -1
- package/dist/tools/search-text.d.ts.map +1 -1
- package/dist/tools/search-text.js +20 -49
- package/dist/tools/search-text.js.map +1 -1
- package/dist/tools/stat.d.ts.map +1 -1
- package/dist/tools/stat.js +26 -8
- package/dist/tools/stat.js.map +1 -1
- package/dist/transport/http-policy.d.ts +3 -2
- package/dist/transport/http-policy.d.ts.map +1 -1
- package/dist/transport/http-policy.js +3 -2
- package/dist/transport/http-policy.js.map +1 -1
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +2 -2
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/stdio.js +1 -1
- package/dist/transport/stdio.js.map +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-text.js","sourceRoot":"","sources":["../../src/tools/search-text.ts"],"names":[],"mappings":"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","sourcesContent":["import { stat } from 'node:fs/promises';\nimport { dirname } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport { paginate } from '../core/cursor.ts';\nimport { ErrorCode, FsError } from '../core/errors.ts';\nimport { formatCount, pageTrailer, truncateProgressPattern, zeroMatchHint } from '../core/fmt.ts';\nimport { toPosixRelative } from '../core/path.ts';\nimport {\n CursorSchema,\n defaultFalseBoolean,\n IncludeHidden,\n IncludeIgnored,\n MaxDepth,\n nonBlankString,\n OptionalPath,\n SafeGlobPattern,\n} from '../core/schema.ts';\nimport { searchContent } from '../core/search.ts';\nimport type { JsonResourceResult } from '../core/store.ts';\nimport { putJsonResource } from '../core/store.ts';\nimport {\n DEFAULT_SEARCH_CONTENT_RESULTS,\n DEFAULT_SEARCH_TIMEOUT_MS,\n MAX_SEARCH_RESULTS,\n} from '../core/util.ts';\nimport { defineTool, type ToolCtx } from './define.ts';\n\n// Type Definitions\ntype SearchResultValue = Awaited<ReturnType<typeof searchContent>>;\ntype SearchMatchPayload = SearchResultValue['matches'][number];\n/** The engine's own summary is the page metadata; nothing is copied out of it. */\ntype SearchContentPageMetadata = SearchResultValue['summary'];\n\nconst GrepInputSchema = z.strictObject({\n path: OptionalPath.describe(\n 'File to search, or directory to search under (default: the whole first allowed root). Naming a file searches that file alone: pattern is ignored and hidden/ignored filtering does not apply.',\n ),\n pattern: SafeGlobPattern.optional().describe(\n 'Glob to restrict search to specific file types (e.g. **/*.ts); default: all text files',\n ),\n searchPattern: nonBlankString('searchPattern')\n .max(10000)\n .describe(\n 'Exact literal text or RE2 regex pattern to search for in file contents. When isRegex=true, uses RE2 syntax (no lookahead, lookbehind, or backreferences). Cannot be empty or whitespace-only.',\n ),\n isRegex: defaultFalseBoolean('Treat searchPattern as a regex (default: literal text match)'),\n includeHidden: IncludeHidden,\n includeIgnored: IncludeIgnored,\n caseSensitive: defaultFalseBoolean('Enable case-sensitive matching (default: case-insensitive)'),\n maxResults: z\n .uint32()\n .min(1)\n .max(MAX_SEARCH_RESULTS)\n .optional()\n .default(DEFAULT_SEARCH_CONTENT_RESULTS)\n .describe('Maximum number of matching lines to return per page'),\n context: z\n .uint32()\n .max(10)\n .optional()\n .default(0)\n .describe(\n 'Lines of context to return either side of each match, like grep -C (default: 0, max: 10)',\n ),\n maxDepth: MaxDepth,\n cursor: CursorSchema,\n});\n\ninterface SearchOutput {\n matches: SearchMatchPayload[];\n totalMatches?: number;\n filesMatched?: number;\n filesScanned?: number;\n skippedInaccessible?: number;\n skippedTooLarge?: number;\n skippedBinary?: number;\n truncated?: boolean;\n stoppedReason?: 'maxResults' | 'timeout';\n resourceUri?: string;\n nextCursor?: string;\n}\n\nfunction matchRow(m: SearchMatchPayload): string {\n return `${m.file}:${String(m.line)}: ${m.content}`;\n}\n\n/**\n * grep -C layout: `file-line- text` for a context line, `--` between groups\n * that are not adjacent. Every match's window lands in a per-file line map\n * where a match wins over context, so a line two windows share prints once,\n * and as a match when it is one. The maps iterate in line order without a\n * sort: matches arrive sorted, each window is contiguous, and setting an\n * existing key keeps its slot. Without context the rows are the bare match\n * lines, byte-identical to before the option existed.\n */\nfunction renderRows(matches: readonly SearchMatchPayload[], withContext: boolean): string[] {\n if (!withContext) return matches.map(matchRow);\n const byFile = new Map<string, Map<number, string>>();\n for (const m of matches) {\n const lines = byFile.get(m.file) ?? new Map<number, string>();\n byFile.set(m.file, lines);\n const setContext = (n: number, text: string): void => {\n if (!lines.has(n)) lines.set(n, `${m.file}-${String(n)}- ${text}`);\n };\n const before = m.before ?? [];\n before.forEach((text, k) => {\n setContext(m.line - before.length + k, text);\n });\n lines.set(m.line, matchRow(m));\n (m.after ?? []).forEach((text, k) => {\n setContext(m.line + 1 + k, text);\n });\n }\n const rows: string[] = [];\n for (const lines of byFile.values()) {\n let prev: number | undefined;\n for (const [n, row] of lines) {\n if (prev === undefined ? rows.length > 0 : n > prev + 1) rows.push('--');\n rows.push(row);\n prev = n;\n }\n }\n return rows;\n}\n\nfunction buildSearchMatchDetail(totalMatches: number, filesMatched: number): string {\n const matchDetail = formatCount(totalMatches, 'match', 'matches');\n if (filesMatched <= 0) return matchDetail;\n return `${matchDetail} · ${formatCount(filesMatched, 'file', 'files')}`;\n}\n\nfunction searchContentOutput(\n matches: readonly SearchMatchPayload[],\n metadata: SearchContentPageMetadata,\n nextCursor: string | undefined,\n resourceUri: string | undefined,\n): SearchOutput {\n return {\n matches: [...matches],\n totalMatches: metadata.matchingLines,\n filesScanned: metadata.filesScanned,\n ...(metadata.filesMatched ? { filesMatched: metadata.filesMatched } : {}),\n ...(metadata.truncated ? { truncated: true } : {}),\n ...(metadata.stoppedReason !== undefined ? { stoppedReason: metadata.stoppedReason } : {}),\n ...(metadata.skippedInaccessible ? { skippedInaccessible: metadata.skippedInaccessible } : {}),\n ...(metadata.skippedTooLarge ? { skippedTooLarge: metadata.skippedTooLarge } : {}),\n ...(metadata.skippedBinary ? { skippedBinary: metadata.skippedBinary } : {}),\n ...(resourceUri !== undefined ? { resourceUri } : {}),\n ...(nextCursor !== undefined ? { nextCursor } : {}),\n };\n}\n\nfunction buildSortedPayloads(result: SearchResultValue): SearchMatchPayload[] {\n const relativeByFile = new Map<string, string>();\n\n const getRelativeFile = (file: string): string => {\n const cached = relativeByFile.get(file);\n if (cached !== undefined) return cached;\n\n const rel = toPosixRelative(result.basePath, file);\n relativeByFile.set(file, rel);\n return rel;\n };\n\n const payloads = result.matches.map((match): SearchMatchPayload => ({\n file: getRelativeFile(match.file),\n line: match.line,\n column: match.column,\n content: match.content,\n matchCount: match.matchCount,\n ...(match.before !== undefined ? { before: match.before } : {}),\n ...(match.after !== undefined ? { after: match.after } : {}),\n }));\n\n payloads.sort((l, r) => l.file.localeCompare(r.file) || l.line - r.line);\n return payloads;\n}\n\n/** Explicit files bypass glob filtering; their parent anchors the relative result paths. */\nasync function resolveSearchScope(\n path: string | undefined,\n ctx: ToolCtx,\n): Promise<{ basePath: string; explicitFile?: string }> {\n const requested = ctx.fs.pathGuard.resolvePathOrRoot(path);\n // One resolution, one stat: validateExistingDirectory would redo both.\n const resolved = await ctx.fs.pathGuard.validateExistingPath(requested);\n const stats = await stat(resolved);\n if (!stats.isFile()) {\n if (!stats.isDirectory()) {\n throw new FsError(ErrorCode.NOT_DIRECTORY, 'Not a directory', requested);\n }\n return { basePath: resolved };\n }\n return {\n basePath: dirname(resolved),\n // The name is NOT turned into a glob: `[slug].tsx` would read as a\n // character class. searchContent scans exactly this file instead, and\n // hidden/ignored filtering is moot for a file the caller named.\n explicitFile: resolved,\n };\n}\n\nexport const SEARCH_TEXT = defineTool<typeof GrepInputSchema, SearchOutput>({\n name: 'search_text',\n title: 'Search Content',\n description:\n 'Search file contents for literal text or an RE2 regex, like grep; returns matching lines with line numbers ' +\n 'and paths relative to the searched directory. Patterns match within one line. find_files matches file names.',\n input: GrepInputSchema,\n readOnlyHint: true,\n timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS,\n progress: (args) => ({\n label: 'Search',\n subject: truncateProgressPattern(args.searchPattern),\n }),\n progressDone: (_args, result) => ({\n detail: buildSearchMatchDetail(result.totalMatches ?? 0, result.filesMatched ?? 0),\n }),\n accessPaths: (args) => (args.path ? [args.path] : []),\n run: async (args, ctx) => {\n const requestedPath = ctx.fs.pathGuard.resolvePathOrRoot(args.path);\n const queryKey = JSON.stringify({\n method: 'search_text',\n path: requestedPath,\n pattern: args.pattern,\n searchPattern: args.searchPattern,\n isRegex: args.isRegex,\n includeHidden: args.includeHidden,\n includeIgnored: args.includeIgnored,\n caseSensitive: args.caseSensitive,\n maxDepth: args.maxDepth,\n context: args.context,\n });\n const paged = await paginate<SearchMatchPayload, SearchContentPageMetadata, JsonResourceResult>(\n {\n store: ctx.pageStore,\n queryKey,\n cursor: args.cursor,\n pageSize: args.maxResults,\n produce: async () => {\n const { basePath, explicitFile } = await resolveSearchScope(args.path, ctx);\n\n const result = await searchContent(\n basePath,\n args.searchPattern,\n {\n includeHidden: args.includeHidden,\n filePattern: args.pattern ?? '**/*',\n caseSensitive: args.caseSensitive,\n isRegex: args.isRegex,\n maxResults: MAX_SEARCH_RESULTS,\n skipIgnored: !args.includeIgnored,\n context: args.context,\n ...(explicitFile !== undefined ? { explicitFile } : {}),\n ...(args.maxDepth !== undefined ? { maxDepth: args.maxDepth } : {}),\n signal: ctx.signal,\n },\n ctx.fs.pathGuard,\n );\n\n return {\n items: buildSortedPayloads(result),\n metadata: result.summary,\n truncated: result.summary.truncated,\n };\n },\n externalize: (matches, metadata) =>\n putJsonResource(ctx.resourceStore, `'${args.searchPattern}' matches`, {\n ...searchContentOutput(matches, metadata, undefined, undefined),\n }),\n },\n );\n\n const structured = searchContentOutput(\n paged.page,\n paged.metadata,\n paged.nextCursor,\n paged.resource?.entry.uri,\n );\n const rows = renderRows(structured.matches, args.context > 0);\n const body =\n rows.length > 0\n ? rows.join('\\n')\n : [\n `No matches for '${args.searchPattern}'`,\n zeroMatchHint({\n ...args,\n filesScanned: structured.filesScanned ?? 0,\n incomplete: Boolean(\n structured.stoppedReason ??\n structured.skippedTooLarge ??\n structured.skippedInaccessible,\n ),\n }),\n ]\n .filter(Boolean)\n .join('\\n');\n const text =\n body +\n pageTrailer({\n offset: paged.offset,\n shown: structured.matches.length,\n total: paged.metadata.matchingLines,\n noun: 'matches',\n tool: 'search_text',\n nextCursor: structured.nextCursor,\n nextArgs: args,\n stoppedReason: structured.stoppedReason,\n skippedTooLarge: structured.skippedTooLarge,\n skippedInaccessible: structured.skippedInaccessible,\n });\n return { structured, text, resources: paged.resource ? [paged.resource.link] : [] };\n },\n});\n"]}
|
|
1
|
+
{"version":3,"file":"search-text.js","sourceRoot":"","sources":["../../src/tools/search-text.ts"],"names":[],"mappings":"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","sourcesContent":["import * as z from 'zod/v4';\n\nimport { pageQueryKey, paginate } from '../core/cursor.ts';\nimport { formatCount, pageTrailer, truncateProgressPattern, zeroMatchHint } from '../core/fmt.ts';\nimport type { ResultPaths } from '../core/path.ts';\nimport {\n CursorSchema,\n defaultFalseBoolean,\n IncludeHidden,\n IncludeIgnored,\n mapOptionalPath,\n MaxDepth,\n nonBlankString,\n OptionalPath,\n SafeGlobPattern,\n} from '../core/schema.ts';\nimport { resolveSearchScope, searchContent } from '../core/search.ts';\nimport type { JsonResourceResult } from '../core/store.ts';\nimport { putJsonResource } from '../core/store.ts';\nimport { DEFAULT_SEARCH_CONTENT_RESULTS, MAX_SEARCH_RESULTS } from '../core/util.ts';\nimport { defineTool } from './define.ts';\n\n// Type Definitions\ntype SearchResultValue = Awaited<ReturnType<typeof searchContent>>;\ntype SearchMatchPayload = SearchResultValue['matches'][number];\n/** The engine's own summary, plus the root the match paths are relative to. */\ntype SearchContentPageMetadata = SearchResultValue['summary'] & { readonly root: string };\n\nconst GrepInputSchema = z.strictObject({\n path: OptionalPath.describe(\n 'File to search, or directory to search under (default: the whole first allowed root). Naming a file searches that file alone: pattern is ignored and hidden/ignored filtering does not apply.',\n ),\n pattern: SafeGlobPattern.optional().describe(\n 'Glob to restrict search to specific file types (e.g. **/*.ts); default: all text files',\n ),\n searchPattern: nonBlankString('searchPattern')\n .max(10000)\n .describe(\n 'Exact literal text or RE2 regex pattern to search for in file contents. When isRegex=true, uses RE2 syntax (no lookahead, lookbehind, or backreferences). Cannot be empty or whitespace-only.',\n ),\n isRegex: defaultFalseBoolean('Treat searchPattern as a regex (default: literal text match)'),\n includeHidden: IncludeHidden,\n includeIgnored: IncludeIgnored,\n caseSensitive: defaultFalseBoolean('Enable case-sensitive matching (default: case-insensitive)'),\n maxResults: z\n .uint32()\n .min(1)\n .max(MAX_SEARCH_RESULTS)\n .optional()\n .default(DEFAULT_SEARCH_CONTENT_RESULTS)\n .describe('Maximum number of matching lines to return per page'),\n context: z\n .uint32()\n .max(10)\n .optional()\n .default(0)\n .describe(\n 'Lines of context to return either side of each match, like grep -C (default: 0, max: 10)',\n ),\n maxDepth: MaxDepth,\n cursor: CursorSchema,\n});\n\ninterface SearchOutput {\n /** The root `matches[].file` is relative to; a file that would not read back from it is absolute. */\n root: string;\n matches: SearchMatchPayload[];\n totalMatches?: number;\n filesMatched?: number;\n filesScanned?: number;\n skippedInaccessible?: number;\n skippedTooLarge?: number;\n skippedBinary?: number;\n truncated?: boolean;\n stoppedReason?: 'maxResults' | 'timeout';\n resourceUri?: string;\n nextCursor?: string;\n}\n\nfunction matchRow(m: SearchMatchPayload): string {\n return `${m.file}:${String(m.line)}: ${m.content}`;\n}\n\n/**\n * grep -C layout: `file-line- text` for a context line, `--` between groups\n * that are not adjacent. Every match's window lands in a per-file line map\n * where a match wins over context, so a line two windows share prints once,\n * and as a match when it is one. The maps iterate in line order without a\n * sort: matches arrive sorted, each window is contiguous, and setting an\n * existing key keeps its slot. Without context the rows are the bare match\n * lines, byte-identical to before the option existed.\n */\nfunction renderRows(matches: readonly SearchMatchPayload[], withContext: boolean): string[] {\n if (!withContext) return matches.map(matchRow);\n const byFile = new Map<string, Map<number, string>>();\n for (const m of matches) {\n const lines = byFile.get(m.file) ?? new Map<number, string>();\n byFile.set(m.file, lines);\n const setContext = (n: number, text: string): void => {\n if (!lines.has(n)) lines.set(n, `${m.file}-${String(n)}- ${text}`);\n };\n const before = m.before ?? [];\n before.forEach((text, k) => {\n setContext(m.line - before.length + k, text);\n });\n lines.set(m.line, matchRow(m));\n (m.after ?? []).forEach((text, k) => {\n setContext(m.line + 1 + k, text);\n });\n }\n const rows: string[] = [];\n for (const lines of byFile.values()) {\n let prev: number | undefined;\n for (const [n, row] of lines) {\n if (prev === undefined ? rows.length > 0 : n > prev + 1) rows.push('--');\n rows.push(row);\n prev = n;\n }\n }\n return rows;\n}\n\nfunction buildSearchMatchDetail(totalMatches: number, filesMatched: number): string {\n const matchDetail = formatCount(totalMatches, 'match', 'matches');\n if (filesMatched <= 0) return matchDetail;\n return `${matchDetail} · ${formatCount(filesMatched, 'file', 'files')}`;\n}\n\nfunction searchContentOutput(\n matches: readonly SearchMatchPayload[],\n metadata: SearchContentPageMetadata,\n nextCursor: string | undefined,\n resourceUri: string | undefined,\n): SearchOutput {\n return {\n root: metadata.root,\n matches: [...matches],\n totalMatches: metadata.matchingLines,\n filesScanned: metadata.filesScanned,\n ...(metadata.filesMatched ? { filesMatched: metadata.filesMatched } : {}),\n ...(metadata.truncated ? { truncated: true } : {}),\n ...(metadata.stoppedReason !== undefined ? { stoppedReason: metadata.stoppedReason } : {}),\n ...(metadata.skippedInaccessible ? { skippedInaccessible: metadata.skippedInaccessible } : {}),\n ...(metadata.skippedTooLarge ? { skippedTooLarge: metadata.skippedTooLarge } : {}),\n ...(metadata.skippedBinary ? { skippedBinary: metadata.skippedBinary } : {}),\n ...(resourceUri !== undefined ? { resourceUri } : {}),\n ...(nextCursor !== undefined ? { nextCursor } : {}),\n };\n}\n\nfunction buildSortedPayloads(result: SearchResultValue, paths: ResultPaths): SearchMatchPayload[] {\n const relativeByFile = new Map<string, string>();\n\n const getRelativeFile = (file: string): string => {\n const cached = relativeByFile.get(file);\n if (cached !== undefined) return cached;\n\n const rel = paths.show(file);\n relativeByFile.set(file, rel);\n return rel;\n };\n\n const payloads = result.matches.map((match): SearchMatchPayload => ({\n file: getRelativeFile(match.file),\n line: match.line,\n column: match.column,\n content: match.content,\n matchCount: match.matchCount,\n ...(match.before !== undefined ? { before: match.before } : {}),\n ...(match.after !== undefined ? { after: match.after } : {}),\n }));\n\n payloads.sort((l, r) => l.file.localeCompare(r.file) || l.line - r.line);\n return payloads;\n}\n\nexport const SEARCH_TEXT = defineTool<typeof GrepInputSchema, SearchOutput>({\n name: 'search_text',\n title: 'Search Content',\n description:\n 'Search file contents for literal text or an RE2 regex, like grep; returns matching lines with line numbers ' +\n 'and paths relative to the root, ready to pass to read or edit. Patterns match within one line. find_files matches file names.',\n input: GrepInputSchema,\n readOnlyHint: true,\n deadline: true,\n progress: (args) => ({\n label: 'Search',\n subject: truncateProgressPattern(args.searchPattern),\n }),\n progressDone: (_args, result) => ({\n detail: buildSearchMatchDetail(result.totalMatches ?? 0, result.filesMatched ?? 0),\n }),\n mapPaths: mapOptionalPath,\n run: async (args, ctx) => {\n const requestedPath = ctx.fs.pathGuard.resolvePathOrRoot(args.path);\n const paged = await paginate<SearchMatchPayload, SearchContentPageMetadata, JsonResourceResult>(\n {\n store: ctx.pageStore,\n queryKey: pageQueryKey('search_text', { ...args, path: requestedPath }, [\n 'cursor',\n 'maxResults',\n ]),\n cursor: args.cursor,\n pageSize: args.maxResults,\n produce: async () => {\n const { basePath, explicitFile } = await resolveSearchScope(\n args.path,\n ctx.fs,\n ctx.signal,\n );\n\n const result = await searchContent(\n basePath,\n args.searchPattern,\n {\n includeHidden: args.includeHidden,\n filePattern: args.pattern ?? '**/*',\n caseSensitive: args.caseSensitive,\n isRegex: args.isRegex,\n maxResults: MAX_SEARCH_RESULTS,\n skipIgnored: !args.includeIgnored,\n context: args.context,\n ...(explicitFile !== undefined ? { explicitFile } : {}),\n ...(args.maxDepth !== undefined ? { maxDepth: args.maxDepth } : {}),\n signal: ctx.signal,\n },\n ctx.fs.pathGuard,\n );\n\n // Relative to the root, not to basePath: `a.ts` from a search of\n // `src` would name `<root>/a.ts` when passed back.\n const paths = ctx.fs.pathGuard.resultPaths(basePath);\n return {\n items: buildSortedPayloads(result, paths),\n metadata: { ...result.summary, root: paths.root },\n truncated: result.summary.truncated,\n };\n },\n externalize: (matches, metadata) =>\n putJsonResource(ctx.resourceStore, `'${args.searchPattern}' matches`, {\n ...searchContentOutput(matches, metadata, undefined, undefined),\n }),\n },\n );\n\n const structured = searchContentOutput(\n paged.page,\n paged.metadata,\n paged.nextCursor,\n paged.resource?.entry.uri,\n );\n const rows = renderRows(structured.matches, args.context > 0);\n const body =\n rows.length > 0\n ? rows.join('\\n')\n : [\n `No matches for '${args.searchPattern}'`,\n zeroMatchHint({\n ...args,\n filesScanned: structured.filesScanned ?? 0,\n incomplete: Boolean(\n structured.stoppedReason ??\n structured.skippedTooLarge ??\n structured.skippedInaccessible,\n ),\n }),\n ]\n .filter(Boolean)\n .join('\\n');\n const text =\n body +\n pageTrailer({\n offset: paged.offset,\n shown: structured.matches.length,\n total: paged.metadata.matchingLines,\n noun: 'matches',\n tool: 'search_text',\n nextCursor: structured.nextCursor,\n nextArgs: args,\n stoppedReason: structured.stoppedReason,\n skippedTooLarge: structured.skippedTooLarge,\n skippedInaccessible: structured.skippedInaccessible,\n });\n return { structured, text, resources: paged.resource ? [paged.resource.link] : [] };\n },\n});\n"]}
|
package/dist/tools/stat.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stat.d.ts","sourceRoot":"","sources":["../../src/tools/stat.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stat.d.ts","sourceRoot":"","sources":["../../src/tools/stat.ts"],"names":[],"mappings":"AAwKA,eAAO,MAAM,IAAI,mCAkCf,CAAC"}
|
package/dist/tools/stat.js
CHANGED
|
@@ -2,8 +2,7 @@ import { parse } from 'node:path';
|
|
|
2
2
|
import { ErrorCode, rethrowIfAborted } from "../core/errors.js";
|
|
3
3
|
import { detectMimeType } from "../core/mime.js";
|
|
4
4
|
import { resolveEntryType } from "../core/path-utils.js";
|
|
5
|
-
import {
|
|
6
|
-
import { DEFAULT_SEARCH_TIMEOUT_MS } from "../core/util.js";
|
|
5
|
+
import { mapSingleOrBatchPaths, singleOrBatchPathsInput } from "../core/schema.js";
|
|
7
6
|
import { isTotalFailure, runOverPaths } from "./batch.js";
|
|
8
7
|
import { defineTool } from "./define.js";
|
|
9
8
|
const StatInputSchema = singleOrBatchPathsInput({});
|
|
@@ -23,7 +22,7 @@ function accessFields(mode) {
|
|
|
23
22
|
return { readOnly: (mode & 0o222) === 0 };
|
|
24
23
|
return { permissions: getPermissions(mode) };
|
|
25
24
|
}
|
|
26
|
-
function buildFileInfoResult(name, requestedPath, isSymlink, stats, mimeType, symlinkTarget) {
|
|
25
|
+
function buildFileInfoResult(name, requestedPath, isSymlink, stats, mimeType, symlinkTarget, entryCount) {
|
|
27
26
|
const tokenEstimate = stats.isFile() ? Math.ceil(stats.size / 4) : undefined;
|
|
28
27
|
return {
|
|
29
28
|
name,
|
|
@@ -31,7 +30,12 @@ function buildFileInfoResult(name, requestedPath, isSymlink, stats, mimeType, sy
|
|
|
31
30
|
type: isSymlink ? 'symlink' : resolveEntryType(stats),
|
|
32
31
|
// A directory's st_size is 0 on Windows and the block size on POSIX; neither
|
|
33
32
|
// describes its contents, and a literal 0 reads as "empty" (issue #56).
|
|
34
|
-
|
|
33
|
+
// entryCount stands in for it, and is left out when the count failed.
|
|
34
|
+
...(stats.isDirectory()
|
|
35
|
+
? entryCount !== undefined
|
|
36
|
+
? { entryCount }
|
|
37
|
+
: {}
|
|
38
|
+
: { size: stats.size }),
|
|
35
39
|
...(tokenEstimate !== undefined ? { tokenEstimate } : {}),
|
|
36
40
|
created: stats.birthtime.toISOString(),
|
|
37
41
|
modified: stats.mtime.toISOString(),
|
|
@@ -86,17 +90,31 @@ async function getFileInfo(filePath, { signal, fs, log }) {
|
|
|
86
90
|
const symlinkTarget = isOwnSymlink
|
|
87
91
|
? await getSymlinkTarget(requestedPath, fs, signal, log)
|
|
88
92
|
: undefined;
|
|
89
|
-
|
|
93
|
+
// Count only a real directory: a symlink's own stats are not its target's.
|
|
94
|
+
// A failed count must not fail the stat, so it degrades to no entryCount.
|
|
95
|
+
let entryCount;
|
|
96
|
+
if (!isOwnSymlink && stats.isDirectory()) {
|
|
97
|
+
try {
|
|
98
|
+
entryCount = await fs.countEntries(requestedPath, { signal });
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
rethrowIfAborted(error);
|
|
102
|
+
// A deadline is not an abort to rethrowIfAborted, but it must still end the call as TIMEOUT.
|
|
103
|
+
signal.throwIfAborted();
|
|
104
|
+
log('warning', `stat: entry count failed for "${requestedPath}": ${String(error)}`, 'stat');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return buildFileInfoResult(name, requestedPath, isOwnSymlink, stats, mimeType, symlinkTarget, entryCount);
|
|
90
108
|
}
|
|
91
109
|
export const STAT = defineTool({
|
|
92
110
|
name: 'stat',
|
|
93
111
|
title: 'Get File Info',
|
|
94
|
-
description: 'Get metadata without reading contents, like ls -ld: type, size (files
|
|
112
|
+
description: 'Get metadata without reading contents, like ls -ld: type, size (files) or entryCount (directories; secret files not counted), ' +
|
|
95
113
|
'tokenEstimate, timestamps, permissions (readOnly on Windows), MIME type guessed from the extension, and symlink target. ' +
|
|
96
114
|
'Also checks whether paths exist.',
|
|
97
115
|
input: StatInputSchema,
|
|
98
116
|
readOnlyHint: true,
|
|
99
|
-
|
|
117
|
+
deadline: true,
|
|
100
118
|
defaultErrorCode: ErrorCode.NOT_FOUND,
|
|
101
119
|
progress: (args) => {
|
|
102
120
|
if (args.paths !== undefined) {
|
|
@@ -104,7 +122,7 @@ export const STAT = defineTool({
|
|
|
104
122
|
}
|
|
105
123
|
return { label: 'Stat', subject: args.path ?? '' };
|
|
106
124
|
},
|
|
107
|
-
|
|
125
|
+
mapPaths: mapSingleOrBatchPaths,
|
|
108
126
|
run: async (args, ctx) => {
|
|
109
127
|
const paths = args.path !== undefined ? [args.path] : (args.paths ?? []);
|
|
110
128
|
const batch = await runOverPaths(paths, ctx, ErrorCode.NOT_FOUND, async (path) => getFileInfo(path, ctx));
|
package/dist/tools/stat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stat.js","sourceRoot":"","sources":["../../src/tools/stat.ts"],"names":[],"mappings":"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","sourcesContent":["import { parse } from 'node:path';\n\nimport { ErrorCode, rethrowIfAborted } from '../core/errors.ts';\nimport type { GuardedFileSystem, Stats } from '../core/fs.ts';\nimport { detectMimeType } from '../core/mime.ts';\nimport { type EntryType, resolveEntryType } from '../core/path-utils.ts';\nimport { singleOrBatchAccessPaths, singleOrBatchPathsInput } from '../core/schema.ts';\nimport { DEFAULT_SEARCH_TIMEOUT_MS } from '../core/util.ts';\nimport type { BatchResult } from './batch.ts';\nimport { isTotalFailure, runOverPaths } from './batch.ts';\nimport type { ToolCtx } from './define.ts';\nimport { defineTool } from './define.ts';\n\ninterface FileInfo {\n name: string;\n path: string;\n type: EntryType;\n size?: number;\n tokenEstimate?: number;\n created: string;\n modified: string;\n accessed: string;\n permissions?: string;\n readOnly?: boolean;\n isHidden: boolean;\n mimeType?: string;\n symlinkTarget?: string;\n}\n\nconst StatInputSchema = singleOrBatchPathsInput({});\n\ntype StatOutput = BatchResult<FileInfo>;\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\n// On Windows libuv never consults the ACL: mode is 0666, or 0444 when\n// FILE_ATTRIBUTE_READONLY is set. A POSIX triad would claim group/other access\n// it never checked (issue #56), so report the one fact the mode encodes.\nfunction accessFields(mode: number): Pick<FileInfo, 'permissions' | 'readOnly'> {\n if (process.platform === 'win32') return { readOnly: (mode & 0o222) === 0 };\n return { permissions: getPermissions(mode) };\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 // A directory's st_size is 0 on Windows and the block size on POSIX; neither\n // describes its contents, and a literal 0 reads as \"empty\" (issue #56).\n ...(stats.isDirectory() ? {} : { size: stats.size }),\n ...(tokenEstimate !== undefined ? { tokenEstimate } : {}),\n created: stats.birthtime.toISOString(),\n modified: stats.mtime.toISOString(),\n accessed: stats.atime.toISOString(),\n ...accessFields(stats.mode),\n isHidden: name.startsWith('.'),\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\nasync function getFileInfo(\n filePath: string,\n { signal, fs, log }: Pick<ToolCtx, 'fs' | 'signal' | 'log'>,\n): Promise<FileInfo> {\n signal.throwIfAborted();\n\n const {\n requestedPath,\n isSymlink,\n stats: followedStats,\n } = await fs.statDetailed(filePath, { signal });\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 }));\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\nexport const STAT = defineTool<typeof StatInputSchema, StatOutput>({\n name: 'stat',\n title: 'Get File Info',\n description:\n 'Get metadata without reading contents, like ls -ld: type, size (files only; use list for directory contents), ' +\n 'tokenEstimate, timestamps, permissions (readOnly on Windows), MIME type guessed from the extension, and symlink target. ' +\n 'Also checks whether paths exist.',\n input: StatInputSchema,\n readOnlyHint: true,\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 paths = args.path !== undefined ? [args.path] : (args.paths ?? []);\n\n const batch = await runOverPaths(paths, ctx, ErrorCode.NOT_FOUND, async (path) =>\n getFileInfo(path, ctx),\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: { results: batch.results, summary: batch.summary },\n isError: isTotalFailure(batch.summary),\n };\n },\n});\n"]}
|
|
1
|
+
{"version":3,"file":"stat.js","sourceRoot":"","sources":["../../src/tools/stat.ts"],"names":[],"mappings":"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","sourcesContent":["import { parse } from 'node:path';\n\nimport { ErrorCode, rethrowIfAborted } from '../core/errors.ts';\nimport type { GuardedFileSystem, Stats } from '../core/fs.ts';\nimport { detectMimeType } from '../core/mime.ts';\nimport { type EntryType, resolveEntryType } from '../core/path-utils.ts';\nimport { mapSingleOrBatchPaths, singleOrBatchPathsInput } from '../core/schema.ts';\nimport type { BatchResult } from './batch.ts';\nimport { isTotalFailure, runOverPaths } from './batch.ts';\nimport type { ToolCtx } from './define.ts';\nimport { defineTool } from './define.ts';\n\ninterface FileInfo {\n name: string;\n path: string;\n type: EntryType;\n size?: number;\n entryCount?: number;\n tokenEstimate?: number;\n created: string;\n modified: string;\n accessed: string;\n permissions?: string;\n readOnly?: boolean;\n isHidden: boolean;\n mimeType?: string;\n symlinkTarget?: string;\n}\n\nconst StatInputSchema = singleOrBatchPathsInput({});\n\ntype StatOutput = BatchResult<FileInfo>;\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\n// On Windows libuv never consults the ACL: mode is 0666, or 0444 when\n// FILE_ATTRIBUTE_READONLY is set. A POSIX triad would claim group/other access\n// it never checked (issue #56), so report the one fact the mode encodes.\nfunction accessFields(mode: number): Pick<FileInfo, 'permissions' | 'readOnly'> {\n if (process.platform === 'win32') return { readOnly: (mode & 0o222) === 0 };\n return { permissions: getPermissions(mode) };\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 entryCount: number | 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 // A directory's st_size is 0 on Windows and the block size on POSIX; neither\n // describes its contents, and a literal 0 reads as \"empty\" (issue #56).\n // entryCount stands in for it, and is left out when the count failed.\n ...(stats.isDirectory()\n ? entryCount !== undefined\n ? { entryCount }\n : {}\n : { size: stats.size }),\n ...(tokenEstimate !== undefined ? { tokenEstimate } : {}),\n created: stats.birthtime.toISOString(),\n modified: stats.mtime.toISOString(),\n accessed: stats.atime.toISOString(),\n ...accessFields(stats.mode),\n isHidden: name.startsWith('.'),\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\nasync function getFileInfo(\n filePath: string,\n { signal, fs, log }: Pick<ToolCtx, 'fs' | 'signal' | 'log'>,\n): Promise<FileInfo> {\n signal.throwIfAborted();\n\n const {\n requestedPath,\n isSymlink,\n stats: followedStats,\n } = await fs.statDetailed(filePath, { signal });\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 }));\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 // Count only a real directory: a symlink's own stats are not its target's.\n // A failed count must not fail the stat, so it degrades to no entryCount.\n let entryCount: number | undefined;\n if (!isOwnSymlink && stats.isDirectory()) {\n try {\n entryCount = await fs.countEntries(requestedPath, { signal });\n } catch (error) {\n rethrowIfAborted(error);\n // A deadline is not an abort to rethrowIfAborted, but it must still end the call as TIMEOUT.\n signal.throwIfAborted();\n log('warning', `stat: entry count failed for \"${requestedPath}\": ${String(error)}`, 'stat');\n }\n }\n\n return buildFileInfoResult(\n name,\n requestedPath,\n isOwnSymlink,\n stats,\n mimeType,\n symlinkTarget,\n entryCount,\n );\n}\n\nexport const STAT = defineTool<typeof StatInputSchema, StatOutput>({\n name: 'stat',\n title: 'Get File Info',\n description:\n 'Get metadata without reading contents, like ls -ld: type, size (files) or entryCount (directories; secret files not counted), ' +\n 'tokenEstimate, timestamps, permissions (readOnly on Windows), MIME type guessed from the extension, and symlink target. ' +\n 'Also checks whether paths exist.',\n input: StatInputSchema,\n readOnlyHint: true,\n deadline: true,\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 mapPaths: mapSingleOrBatchPaths,\n run: async (args, ctx) => {\n const paths = args.path !== undefined ? [args.path] : (args.paths ?? []);\n\n const batch = await runOverPaths(paths, ctx, ErrorCode.NOT_FOUND, async (path) =>\n getFileInfo(path, ctx),\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: { results: batch.results, summary: batch.summary },\n isError: isTotalFailure(batch.summary),\n };\n },\n});\n"]}
|
|
@@ -106,8 +106,9 @@ export declare function bearerAuthMiddleware(apiKey: string | undefined, hostVal
|
|
|
106
106
|
* app's `allowedOrigins` consumer). Defaults to the SDK's loopback hostname set
|
|
107
107
|
* so loopback browser clients keep working; operators set
|
|
108
108
|
* `FS_ALLOWED_ORIGINS` to allow remote clients on non-loopback
|
|
109
|
-
* binds. An empty value
|
|
110
|
-
*
|
|
109
|
+
* binds. An unset or empty value falls back to those defaults; an all-blank
|
|
110
|
+
* value is a configured but empty list, unlike `resolveAllowedHosts`, which
|
|
111
|
+
* reads an all-blank `FS_ALLOWED_HOSTS` as unset. The same set is consulted by corsMiddleware
|
|
111
112
|
* so a remote origin is reflected end-to-end in Access-Control-Allow-Origin.
|
|
112
113
|
*/
|
|
113
114
|
export declare function computeAllowedOriginHostnames(originsEnv: string | undefined): string[];
|
|
@@ -1 +1 @@
|
|
|
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
|
|
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;;;;;;;;;GASG;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"}
|
|
@@ -247,8 +247,9 @@ export function bearerAuthMiddleware(apiKey, hostValidated, publicUrl) {
|
|
|
247
247
|
* app's `allowedOrigins` consumer). Defaults to the SDK's loopback hostname set
|
|
248
248
|
* so loopback browser clients keep working; operators set
|
|
249
249
|
* `FS_ALLOWED_ORIGINS` to allow remote clients on non-loopback
|
|
250
|
-
* binds. An empty value
|
|
251
|
-
*
|
|
250
|
+
* binds. An unset or empty value falls back to those defaults; an all-blank
|
|
251
|
+
* value is a configured but empty list, unlike `resolveAllowedHosts`, which
|
|
252
|
+
* reads an all-blank `FS_ALLOWED_HOSTS` as unset. The same set is consulted by corsMiddleware
|
|
252
253
|
* so a remote origin is reflected end-to-end in Access-Control-Allow-Origin.
|
|
253
254
|
*/
|
|
254
255
|
export function computeAllowedOriginHostnames(originsEnv) {
|
|
@@ -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';\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
|
+
{"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 unset or empty value falls back to those defaults; an all-blank\n * value is a configured but empty list, unlike `resolveAllowedHosts`, which\n * reads an all-blank `FS_ALLOWED_HOSTS` as unset. 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;
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/transport/http.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AASxC,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,CA8HjB"}
|
package/dist/transport/http.js
CHANGED
|
@@ -7,10 +7,9 @@ import { createServer as createHttpServer } from 'node:http';
|
|
|
7
7
|
import express from 'express';
|
|
8
8
|
import { formatUnknownErrorMessage } from "../core/errors.js";
|
|
9
9
|
import { Logger } from "../core/observability.js";
|
|
10
|
-
import { parseTrueEnvFlag } from "../core/path-utils.js";
|
|
11
10
|
import { PathGuard } from "../core/path.js";
|
|
12
11
|
import { PageSnapshotStore, ResourceStore } from "../core/store.js";
|
|
13
|
-
import { getMaxInboundMessageBytes, parseEnvInt } from "../core/util.js";
|
|
12
|
+
import { getMaxInboundMessageBytes, parseEnvInt, parseTrueEnvFlag } from "../core/util.js";
|
|
14
13
|
import { createWatcherRegistry } from "../core/watcher-registry.js";
|
|
15
14
|
import { createServer } from "../server.js";
|
|
16
15
|
import { assertHttpBindingPolicy, assertHttpHostPolicy, bearerAuthMiddleware, computeAllowedOriginHostnames, corsMiddleware, createRateLimiter, isLoopbackHttpHost, JSONRPC_SERVER_ERROR, protectedResourceUrl, resolveAllowedHosts, resolveTrustProxySetting, sendJsonRpcError, } from "./http-policy.js";
|
|
@@ -249,6 +248,7 @@ export async function startHttpServer(port, options, config = {}) {
|
|
|
249
248
|
const teardown = () => {
|
|
250
249
|
sharedRegistry.destroy();
|
|
251
250
|
sharedPageStore.clear();
|
|
251
|
+
sharedStore.clear();
|
|
252
252
|
return modernHandler.close().catch((err) => {
|
|
253
253
|
Logger.error('[HTTP] Error closing handler:', formatUnknownErrorMessage(err));
|
|
254
254
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/transport/http.ts"],"names":[],"mappings":"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","sourcesContent":["// HTTP hosting: express app assembly, security middleware wiring, the modern\n// per-request server factory, and listen-filter watcher gating on POST /mcp.\nimport {\n hostHeaderValidation,\n localhostOriginValidation,\n originValidation,\n} from '@modelcontextprotocol/express';\nimport { toNodeHandler } from '@modelcontextprotocol/node';\nimport type { McpHttpHandler, ServerNotifier } from '@modelcontextprotocol/server';\nimport {\n createMcpHandler,\n DEFAULT_REQUEST_TIMEOUT_MSEC,\n isJsonContentType,\n ProtocolErrorCode,\n} from '@modelcontextprotocol/server';\n\nimport type { Server } from 'node:http';\nimport { createServer as createHttpServer } from 'node:http';\n\nimport express from 'express';\nimport type { Express, NextFunction, Request, Response } from 'express';\n\nimport { formatUnknownErrorMessage } from '../core/errors.ts';\nimport { Logger } from '../core/observability.ts';\nimport { parseTrueEnvFlag } from '../core/path-utils.ts';\nimport { PathGuard } from '../core/path.ts';\nimport type { ServerOptions } from '../core/path.ts';\nimport { PageSnapshotStore, ResourceStore } from '../core/store.ts';\nimport { getMaxInboundMessageBytes, parseEnvInt } from '../core/util.ts';\nimport { createWatcherRegistry, type WatcherRegistry } from '../core/watcher-registry.ts';\nimport { createServer } from '../server.ts';\nimport {\n assertHttpBindingPolicy,\n assertHttpHostPolicy,\n bearerAuthMiddleware,\n computeAllowedOriginHostnames,\n corsMiddleware,\n createRateLimiter,\n isLoopbackHttpHost,\n JSONRPC_SERVER_ERROR,\n protectedResourceUrl,\n resolveAllowedHosts,\n resolveTrustProxySetting,\n sendJsonRpcError,\n} from './http-policy.ts';\nimport type { RuntimeConfig } from './shared.ts';\nimport { jsonRpcRequestId, listenSubscriptionUris, prepareListenWatchers } from './shared.ts';\n\n// Must exceed the idle timeout of any proxy in front of this server, or the\n// proxy reuses connections the server already closed (intermittent 502s).\nconst KEEPALIVE_TIMEOUT_MS = 5_000;\n\n/** Body-parser rejections and anything else that reaches the end of the chain. */\nfunction errorHandlerMiddleware(\n err: Error & { status?: number },\n _req: Request,\n res: Response,\n next: NextFunction,\n): void {\n if (res.headersSent) {\n next(err);\n return;\n }\n if (err.status === 413) {\n sendJsonRpcError(\n res,\n 413,\n ProtocolErrorCode.InvalidRequest,\n `Request body too large: the limit is ${String(getMaxInboundMessageBytes())} bytes (3 × FS_MAX_FILE_SIZE + 1 MiB). Split the write into smaller calls.`,\n );\n return;\n }\n if (err.status === 400) {\n sendJsonRpcError(res, 400, ProtocolErrorCode.ParseError, 'Invalid JSON in request body');\n return;\n }\n Logger.error('[HTTP] Unhandled middleware error:', formatUnknownErrorMessage(err));\n sendJsonRpcError(res, 500, ProtocolErrorCode.InternalError, 'Internal Server Error');\n}\n\nfunction setupExpressApp(\n httpHost: string,\n apiKey: string | undefined,\n allowedHosts: string[],\n modernNodeHandler: (req: Request, res: Response, parsedBody?: unknown) => Promise<void>,\n sharedPathGuard: PathGuard,\n sharedRegistry: WatcherRegistry,\n notifier: ServerNotifier,\n): Express {\n const allowedOriginHostnames = computeAllowedOriginHostnames(process.env['FS_ALLOWED_ORIGINS']);\n // Read once here and passed down, like every other env-derived policy input:\n // http-policy holds no state and reads no env of its own.\n const publicUrl = process.env['FS_PUBLIC_URL'];\n\n // Assembled from the SDK's own gates rather than `createMcpExpressApp`,\n // which mounts `express.json()` for the whole app: that would parse every\n // body before the rate limiter and bearer auth below can refuse it. Host\n // and Origin validation still run first and app-wide, mounted by the same\n // rules `createMcpExpressApp` applies to these inputs.\n const app = express();\n if (allowedHosts.length > 0) app.use(hostHeaderValidation(allowedHosts));\n if (allowedOriginHostnames.length > 0) {\n app.use(originValidation(allowedOriginHostnames));\n } else if (isLoopbackHttpHost(httpHost)) {\n // An all-blank FS_ALLOWED_ORIGINS on a loopback bind keeps the localhost\n // default (see isOriginAllowed in http-policy.ts).\n app.use(localhostOriginValidation());\n }\n\n const trustProxy = resolveTrustProxySetting(process.env['FS_TRUST_PROXY']);\n if (trustProxy !== undefined) {\n app.set('trust proxy', trustProxy);\n }\n\n if (allowedHosts.length === 0) {\n Logger.warn(\n '[HTTP] FS_ALLOW_UNRESTRICTED_HOSTS is set: binding globally without Host validation.',\n );\n }\n\n app.use('/mcp', corsMiddleware(allowedOriginHostnames));\n\n // Unconditional: the spec's rate-limit MUST is not scoped to authenticated\n // binds. A keyless bind is loopback-only, so the cap is looser, not absent.\n const rpm = parseEnvInt('FS_RATE_LIMIT_RPM', apiKey ? 120 : 6_000, 1, 100_000);\n app.use('/mcp', createRateLimiter(rpm));\n\n if (apiKey && allowedHosts.length > 0) {\n const metadataHandler = (req: Request, res: Response): void => {\n const resource = protectedResourceUrl(req, true, publicUrl);\n if (!resource) {\n // Unreachable for the unrestricted case (the gate above excludes it),\n // but defend a FS_PUBLIC_URL parse failure with a bodyless\n // 404 — a generic OAuth client cannot parse a JSON-RPC error envelope.\n res.status(404).end();\n return;\n }\n res.header('Access-Control-Allow-Origin', '*');\n res.status(200).json({\n resource: resource.href,\n bearer_methods_supported: ['header'],\n resource_name: 'filesystem-mcp',\n });\n };\n app.get(\n ['/.well-known/oauth-protected-resource', '/.well-known/oauth-protected-resource/mcp'],\n metadataHandler,\n );\n }\n\n app.get('/healthz', (_req: Request, res: Response) => {\n res.status(200).json({\n status: 'ok',\n uptime: process.uptime(),\n });\n });\n\n app.use('/mcp', bearerAuthMiddleware(apiKey, allowedHosts.length > 0, publicUrl));\n\n const resourceUpdateSink = (uri: string): void => {\n notifier.resourceUpdated(uri);\n };\n\n // One bound for both transports, derived from the write tools' own limit\n // (getMaxInboundMessageBytes): a create the schema accepts must not be\n // refused here. Mounted after CORS, the rate limiter and auth, so a refused\n // request is answered without its body being read. The SDK's handler and\n // adapter get the same number below so their internal defaults (4 MiB)\n // cannot disagree with this parser, although both only read a body when no\n // parsedBody is supplied — which this route never does.\n const parseJson = express.json({ limit: `${String(getMaxInboundMessageBytes())}b` });\n\n app.post('/mcp', parseJson, (req: Request, res: Response, next: NextFunction) => {\n void (async () => {\n if (!isJsonContentType(req.headers['content-type'])) {\n sendJsonRpcError(\n res,\n 415,\n JSONRPC_SERVER_ERROR,\n 'Unsupported Media Type: Content-Type must be application/json',\n );\n return;\n }\n const parsedBody = req.body as unknown;\n if (parsedBody === undefined) {\n // Undefined tells the Node adapter to read the raw stream without our parser's limit.\n sendJsonRpcError(res, 400, ProtocolErrorCode.ParseError, 'Invalid JSON in request body');\n return;\n }\n // Only a structurally valid listen gets watchers, mirroring the stdio\n // gate. A malformed one cannot succeed downstream, so attaching handles\n // for it only gives the response-close release something to undo — and\n // everything that is not a listen takes no watchers either way.\n const requestedUris = listenSubscriptionUris(parsedBody);\n if (requestedUris === undefined) {\n await modernNodeHandler(req, res, parsedBody);\n return;\n }\n\n // Over-cap listens are rejected inside prepareListenWatchers, before the ack.\n const prepared = await prepareListenWatchers(\n requestedUris,\n sharedPathGuard,\n sharedRegistry,\n resourceUpdateSink,\n );\n if (!prepared.ok) {\n sendJsonRpcError(\n res,\n 400,\n ProtocolErrorCode.InvalidParams,\n prepared.message,\n jsonRpcRequestId(parsedBody),\n );\n return;\n }\n\n let released = false;\n const release = (): void => {\n if (released) return;\n released = true;\n for (const uri of prepared.acquiredUris) sharedRegistry.release(uri);\n };\n if (prepared.acquiredUris.length > 0) {\n // The attach loop awaits per-URI path validation, so the client can be\n // gone by the time it returns. `close` fires once: a listener attached\n // after it would never run and every lease this batch took would leak\n // until process shutdown.\n if (res.closed || res.destroyed || res.writableEnded) release();\n else res.once('close', release);\n }\n\n try {\n await modernNodeHandler(req, res, parsedBody);\n } catch (error) {\n release();\n throw error;\n }\n })().catch(next);\n });\n\n // Answer non-POST here rather than handing it to `modernNodeHandler`. The\n // SDK's own 405 is byte-identical to this one, but it omits the `Allow`\n // header RFC 9110 §15.5.6 requires and routes every routine GET probe through\n // the handler's `onerror` — which this server logs at error level.\n app.all('/mcp', (_req: Request, res: Response) => {\n sendJsonRpcError(res, 405, JSONRPC_SERVER_ERROR, 'Method not allowed.', null, {\n Allow: 'POST, OPTIONS',\n });\n });\n\n app.use(errorHandlerMiddleware);\n\n return app;\n}\n\nexport async function startHttpServer(\n port: number,\n options: ServerOptions,\n config: RuntimeConfig = {},\n): Promise<Server> {\n const httpHost = config.httpHost ?? '127.0.0.1';\n const { apiKey } = config;\n assertHttpBindingPolicy(httpHost, apiKey);\n const allowedHosts = resolveAllowedHosts(httpHost, process.env['FS_ALLOWED_HOSTS']);\n assertHttpHostPolicy(\n httpHost,\n allowedHosts,\n parseTrueEnvFlag(process.env['FS_ALLOW_UNRESTRICTED_HOSTS'], 'FS_ALLOW_UNRESTRICTED_HOSTS'),\n );\n\n const sharedRegistry = createWatcherRegistry();\n // One store for the whole endpoint, same lifetime and same one-credential\n // trust argument as the shared guard below: a result a tool externalized in\n // one POST must survive to the follow-up resources/read, and the modern leg\n // builds a fresh McpServer per request so a per-request store would discard\n // it immediately.\n // Fires only from a tool call, which is long after `modernHandler` below is\n // constructed, so reading it from the closure is safe.\n const sharedStore = new ResourceStore(() => {\n modernHandler.notify.resourcesChanged();\n });\n const sharedPageStore = new PageSnapshotStore();\n // One guard for the whole endpoint. The modern leg builds a fresh McpServer\n // per request, so a per-instance guard would discard every accepted access\n // grant the moment the request ended — re-prompting on each subsequent call\n // and leaving the listen-watcher path validating against a stale allowed set.\n // Grant scope is therefore the endpoint, not the connection: with FS_API_KEY set\n // every caller presents the same key (one auth context by construction), and\n // without it the bind is loopback-only. Split into per-auth-context guards if\n // this ever serves more than one credential.\n const sharedPathGuard = new PathGuard(options);\n await sharedPathGuard.recomputeAllowedDirectories();\n\n const maxRequestBodySize = getMaxInboundMessageBytes();\n\n const modernHandler: McpHttpHandler = createMcpHandler(\n async ({ era }) => {\n const c = await createServer(options, {\n watcherRegistry: sharedRegistry,\n notifier: modernHandler.notify,\n pathGuard: sharedPathGuard,\n resourceStore: sharedStore,\n pageStore: sharedPageStore,\n era,\n transport: 'http',\n cacheScope: apiKey ? 'private' : 'public',\n });\n return c.mcp;\n },\n {\n // The SDK's default: a 2025-era request is answered by a fresh instance\n // from the same factory, no session. What that leg cannot do — answer an\n // input_required confirmation, deliver a subscription — is refused with a\n // named workaround (define.ts, resources.ts). See docs/adr/003.\n legacy: 'stateless',\n maxRequestBodySize,\n onerror: (error: Error) => {\n Logger.error('[HTTP] modern leg error:', formatUnknownErrorMessage(error));\n },\n },\n );\n const modernNodeHandler = toNodeHandler(modernHandler, {\n maxRequestBodySize,\n // The adapter answers 500 when request conversion or handler.fetch\n // itself throws (e.g. a request racing handler.close()); the entry's\n // own onerror above never sees those. Log the adapter-level refusal so\n // the 500 is not silent.\n onerror: (error: Error) => {\n Logger.error('[HTTP] node adapter error:', formatUnknownErrorMessage(error));\n },\n });\n\n const app = setupExpressApp(\n httpHost,\n apiKey,\n allowedHosts,\n modernNodeHandler,\n sharedPathGuard,\n sharedRegistry,\n modernHandler.notify,\n );\n\n const httpServer = createHttpServer(app);\n httpServer.keepAliveTimeout = KEEPALIVE_TIMEOUT_MS;\n httpServer.headersTimeout = KEEPALIVE_TIMEOUT_MS + 5_000;\n httpServer.requestTimeout = DEFAULT_REQUEST_TIMEOUT_MSEC;\n\n const onHttpServerError = (error: Error): void => {\n Logger.error('[HTTP] runtime server error', {\n host: httpHost,\n port,\n error: formatUnknownErrorMessage(error),\n });\n };\n httpServer.on('error', onHttpServerError);\n\n const teardown = (): Promise<void> => {\n sharedRegistry.destroy();\n sharedPageStore.clear();\n return modernHandler.close().catch((err: unknown) => {\n Logger.error('[HTTP] Error closing handler:', formatUnknownErrorMessage(err));\n });\n };\n const originalClose = httpServer.close.bind(httpServer);\n httpServer.close = function (callback?: (error?: Error) => void) {\n void teardown().then(() => {\n originalClose(callback);\n });\n return httpServer;\n };\n\n return new Promise((resolve, reject) => {\n const onError = (err: Error) => {\n void teardown();\n reject(err);\n };\n httpServer.once('error', onError);\n\n httpServer.listen(port, httpHost, () => {\n httpServer.removeListener('error', onError);\n Logger.info(`[HTTP] Server listening on http://${httpHost}:${port}`);\n resolve(httpServer);\n });\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/transport/http.ts"],"names":[],"mappings":"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","sourcesContent":["// HTTP hosting: express app assembly, security middleware wiring, the modern\n// per-request server factory, and listen-filter watcher gating on POST /mcp.\nimport {\n hostHeaderValidation,\n localhostOriginValidation,\n originValidation,\n} from '@modelcontextprotocol/express';\nimport { toNodeHandler } from '@modelcontextprotocol/node';\nimport type { McpHttpHandler, ServerNotifier } from '@modelcontextprotocol/server';\nimport {\n createMcpHandler,\n DEFAULT_REQUEST_TIMEOUT_MSEC,\n isJsonContentType,\n ProtocolErrorCode,\n} from '@modelcontextprotocol/server';\n\nimport type { Server } from 'node:http';\nimport { createServer as createHttpServer } from 'node:http';\n\nimport express from 'express';\nimport type { Express, NextFunction, Request, Response } from 'express';\n\nimport { formatUnknownErrorMessage } from '../core/errors.ts';\nimport { Logger } from '../core/observability.ts';\nimport { PathGuard } from '../core/path.ts';\nimport type { ServerOptions } from '../core/path.ts';\nimport { PageSnapshotStore, ResourceStore } from '../core/store.ts';\nimport { getMaxInboundMessageBytes, parseEnvInt, parseTrueEnvFlag } from '../core/util.ts';\nimport { createWatcherRegistry, type WatcherRegistry } from '../core/watcher-registry.ts';\nimport { createServer } from '../server.ts';\nimport {\n assertHttpBindingPolicy,\n assertHttpHostPolicy,\n bearerAuthMiddleware,\n computeAllowedOriginHostnames,\n corsMiddleware,\n createRateLimiter,\n isLoopbackHttpHost,\n JSONRPC_SERVER_ERROR,\n protectedResourceUrl,\n resolveAllowedHosts,\n resolveTrustProxySetting,\n sendJsonRpcError,\n} from './http-policy.ts';\nimport type { RuntimeConfig } from './shared.ts';\nimport { jsonRpcRequestId, listenSubscriptionUris, prepareListenWatchers } from './shared.ts';\n\n// Must exceed the idle timeout of any proxy in front of this server, or the\n// proxy reuses connections the server already closed (intermittent 502s).\nconst KEEPALIVE_TIMEOUT_MS = 5_000;\n\n/** Body-parser rejections and anything else that reaches the end of the chain. */\nfunction errorHandlerMiddleware(\n err: Error & { status?: number },\n _req: Request,\n res: Response,\n next: NextFunction,\n): void {\n if (res.headersSent) {\n next(err);\n return;\n }\n if (err.status === 413) {\n sendJsonRpcError(\n res,\n 413,\n ProtocolErrorCode.InvalidRequest,\n `Request body too large: the limit is ${String(getMaxInboundMessageBytes())} bytes (3 × FS_MAX_FILE_SIZE + 1 MiB). Split the write into smaller calls.`,\n );\n return;\n }\n if (err.status === 400) {\n sendJsonRpcError(res, 400, ProtocolErrorCode.ParseError, 'Invalid JSON in request body');\n return;\n }\n Logger.error('[HTTP] Unhandled middleware error:', formatUnknownErrorMessage(err));\n sendJsonRpcError(res, 500, ProtocolErrorCode.InternalError, 'Internal Server Error');\n}\n\nfunction setupExpressApp(\n httpHost: string,\n apiKey: string | undefined,\n allowedHosts: string[],\n modernNodeHandler: (req: Request, res: Response, parsedBody?: unknown) => Promise<void>,\n sharedPathGuard: PathGuard,\n sharedRegistry: WatcherRegistry,\n notifier: ServerNotifier,\n): Express {\n const allowedOriginHostnames = computeAllowedOriginHostnames(process.env['FS_ALLOWED_ORIGINS']);\n // Read once here and passed down, like every other env-derived policy input:\n // http-policy holds no state and reads no env of its own.\n const publicUrl = process.env['FS_PUBLIC_URL'];\n\n // Assembled from the SDK's own gates rather than `createMcpExpressApp`,\n // which mounts `express.json()` for the whole app: that would parse every\n // body before the rate limiter and bearer auth below can refuse it. Host\n // and Origin validation still run first and app-wide, mounted by the same\n // rules `createMcpExpressApp` applies to these inputs.\n const app = express();\n if (allowedHosts.length > 0) app.use(hostHeaderValidation(allowedHosts));\n if (allowedOriginHostnames.length > 0) {\n app.use(originValidation(allowedOriginHostnames));\n } else if (isLoopbackHttpHost(httpHost)) {\n // An all-blank FS_ALLOWED_ORIGINS on a loopback bind keeps the localhost\n // default (see isOriginAllowed in http-policy.ts).\n app.use(localhostOriginValidation());\n }\n\n const trustProxy = resolveTrustProxySetting(process.env['FS_TRUST_PROXY']);\n if (trustProxy !== undefined) {\n app.set('trust proxy', trustProxy);\n }\n\n if (allowedHosts.length === 0) {\n Logger.warn(\n '[HTTP] FS_ALLOW_UNRESTRICTED_HOSTS is set: binding globally without Host validation.',\n );\n }\n\n app.use('/mcp', corsMiddleware(allowedOriginHostnames));\n\n // Unconditional: the spec's rate-limit MUST is not scoped to authenticated\n // binds. A keyless bind is loopback-only, so the cap is looser, not absent.\n const rpm = parseEnvInt('FS_RATE_LIMIT_RPM', apiKey ? 120 : 6_000, 1, 100_000);\n app.use('/mcp', createRateLimiter(rpm));\n\n if (apiKey && allowedHosts.length > 0) {\n const metadataHandler = (req: Request, res: Response): void => {\n const resource = protectedResourceUrl(req, true, publicUrl);\n if (!resource) {\n // Unreachable for the unrestricted case (the gate above excludes it),\n // but defend a FS_PUBLIC_URL parse failure with a bodyless\n // 404 — a generic OAuth client cannot parse a JSON-RPC error envelope.\n res.status(404).end();\n return;\n }\n res.header('Access-Control-Allow-Origin', '*');\n res.status(200).json({\n resource: resource.href,\n bearer_methods_supported: ['header'],\n resource_name: 'filesystem-mcp',\n });\n };\n app.get(\n ['/.well-known/oauth-protected-resource', '/.well-known/oauth-protected-resource/mcp'],\n metadataHandler,\n );\n }\n\n app.get('/healthz', (_req: Request, res: Response) => {\n res.status(200).json({\n status: 'ok',\n uptime: process.uptime(),\n });\n });\n\n app.use('/mcp', bearerAuthMiddleware(apiKey, allowedHosts.length > 0, publicUrl));\n\n const resourceUpdateSink = (uri: string): void => {\n notifier.resourceUpdated(uri);\n };\n\n // One bound for both transports, derived from the write tools' own limit\n // (getMaxInboundMessageBytes): a create the schema accepts must not be\n // refused here. Mounted after CORS, the rate limiter and auth, so a refused\n // request is answered without its body being read. The SDK's handler and\n // adapter get the same number below so their internal defaults (4 MiB)\n // cannot disagree with this parser, although both only read a body when no\n // parsedBody is supplied — which this route never does.\n const parseJson = express.json({ limit: `${String(getMaxInboundMessageBytes())}b` });\n\n app.post('/mcp', parseJson, (req: Request, res: Response, next: NextFunction) => {\n void (async () => {\n if (!isJsonContentType(req.headers['content-type'])) {\n sendJsonRpcError(\n res,\n 415,\n JSONRPC_SERVER_ERROR,\n 'Unsupported Media Type: Content-Type must be application/json',\n );\n return;\n }\n const parsedBody = req.body as unknown;\n if (parsedBody === undefined) {\n // Undefined tells the Node adapter to read the raw stream without our parser's limit.\n sendJsonRpcError(res, 400, ProtocolErrorCode.ParseError, 'Invalid JSON in request body');\n return;\n }\n // Only a structurally valid listen gets watchers, mirroring the stdio\n // gate. A malformed one cannot succeed downstream, so attaching handles\n // for it only gives the response-close release something to undo — and\n // everything that is not a listen takes no watchers either way.\n const requestedUris = listenSubscriptionUris(parsedBody);\n if (requestedUris === undefined) {\n await modernNodeHandler(req, res, parsedBody);\n return;\n }\n\n // Over-cap listens are rejected inside prepareListenWatchers, before the ack.\n const prepared = await prepareListenWatchers(\n requestedUris,\n sharedPathGuard,\n sharedRegistry,\n resourceUpdateSink,\n );\n if (!prepared.ok) {\n sendJsonRpcError(\n res,\n 400,\n ProtocolErrorCode.InvalidParams,\n prepared.message,\n jsonRpcRequestId(parsedBody),\n );\n return;\n }\n\n let released = false;\n const release = (): void => {\n if (released) return;\n released = true;\n for (const uri of prepared.acquiredUris) sharedRegistry.release(uri);\n };\n if (prepared.acquiredUris.length > 0) {\n // The attach loop awaits per-URI path validation, so the client can be\n // gone by the time it returns. `close` fires once: a listener attached\n // after it would never run and every lease this batch took would leak\n // until process shutdown.\n if (res.closed || res.destroyed || res.writableEnded) release();\n else res.once('close', release);\n }\n\n try {\n await modernNodeHandler(req, res, parsedBody);\n } catch (error) {\n release();\n throw error;\n }\n })().catch(next);\n });\n\n // Answer non-POST here rather than handing it to `modernNodeHandler`. The\n // SDK's own 405 is byte-identical to this one, but it omits the `Allow`\n // header RFC 9110 §15.5.6 requires and routes every routine GET probe through\n // the handler's `onerror` — which this server logs at error level.\n app.all('/mcp', (_req: Request, res: Response) => {\n sendJsonRpcError(res, 405, JSONRPC_SERVER_ERROR, 'Method not allowed.', null, {\n Allow: 'POST, OPTIONS',\n });\n });\n\n app.use(errorHandlerMiddleware);\n\n return app;\n}\n\nexport async function startHttpServer(\n port: number,\n options: ServerOptions,\n config: RuntimeConfig = {},\n): Promise<Server> {\n const httpHost = config.httpHost ?? '127.0.0.1';\n const { apiKey } = config;\n assertHttpBindingPolicy(httpHost, apiKey);\n const allowedHosts = resolveAllowedHosts(httpHost, process.env['FS_ALLOWED_HOSTS']);\n assertHttpHostPolicy(\n httpHost,\n allowedHosts,\n parseTrueEnvFlag(process.env['FS_ALLOW_UNRESTRICTED_HOSTS'], 'FS_ALLOW_UNRESTRICTED_HOSTS'),\n );\n\n const sharedRegistry = createWatcherRegistry();\n // One store for the whole endpoint, same lifetime and same one-credential\n // trust argument as the shared guard below: a result a tool externalized in\n // one POST must survive to the follow-up resources/read, and the modern leg\n // builds a fresh McpServer per request so a per-request store would discard\n // it immediately.\n // Fires only from a tool call, which is long after `modernHandler` below is\n // constructed, so reading it from the closure is safe.\n const sharedStore = new ResourceStore(() => {\n modernHandler.notify.resourcesChanged();\n });\n const sharedPageStore = new PageSnapshotStore();\n // One guard for the whole endpoint. The modern leg builds a fresh McpServer\n // per request, so a per-instance guard would discard every accepted access\n // grant the moment the request ended — re-prompting on each subsequent call\n // and leaving the listen-watcher path validating against a stale allowed set.\n // Grant scope is therefore the endpoint, not the connection: with FS_API_KEY set\n // every caller presents the same key (one auth context by construction), and\n // without it the bind is loopback-only. Split into per-auth-context guards if\n // this ever serves more than one credential.\n const sharedPathGuard = new PathGuard(options);\n await sharedPathGuard.recomputeAllowedDirectories();\n\n const maxRequestBodySize = getMaxInboundMessageBytes();\n\n const modernHandler: McpHttpHandler = createMcpHandler(\n async ({ era }) => {\n const c = await createServer(options, {\n watcherRegistry: sharedRegistry,\n notifier: modernHandler.notify,\n pathGuard: sharedPathGuard,\n resourceStore: sharedStore,\n pageStore: sharedPageStore,\n era,\n transport: 'http',\n cacheScope: apiKey ? 'private' : 'public',\n });\n return c.mcp;\n },\n {\n // The SDK's default: a 2025-era request is answered by a fresh instance\n // from the same factory, no session. What that leg cannot do — answer an\n // input_required confirmation, deliver a subscription — is refused with a\n // named workaround (define.ts, resources.ts). See docs/adr/003.\n legacy: 'stateless',\n maxRequestBodySize,\n onerror: (error: Error) => {\n Logger.error('[HTTP] modern leg error:', formatUnknownErrorMessage(error));\n },\n },\n );\n const modernNodeHandler = toNodeHandler(modernHandler, {\n maxRequestBodySize,\n // The adapter answers 500 when request conversion or handler.fetch\n // itself throws (e.g. a request racing handler.close()); the entry's\n // own onerror above never sees those. Log the adapter-level refusal so\n // the 500 is not silent.\n onerror: (error: Error) => {\n Logger.error('[HTTP] node adapter error:', formatUnknownErrorMessage(error));\n },\n });\n\n const app = setupExpressApp(\n httpHost,\n apiKey,\n allowedHosts,\n modernNodeHandler,\n sharedPathGuard,\n sharedRegistry,\n modernHandler.notify,\n );\n\n const httpServer = createHttpServer(app);\n httpServer.keepAliveTimeout = KEEPALIVE_TIMEOUT_MS;\n httpServer.headersTimeout = KEEPALIVE_TIMEOUT_MS + 5_000;\n httpServer.requestTimeout = DEFAULT_REQUEST_TIMEOUT_MSEC;\n\n const onHttpServerError = (error: Error): void => {\n Logger.error('[HTTP] runtime server error', {\n host: httpHost,\n port,\n error: formatUnknownErrorMessage(error),\n });\n };\n httpServer.on('error', onHttpServerError);\n\n const teardown = (): Promise<void> => {\n sharedRegistry.destroy();\n sharedPageStore.clear();\n sharedStore.clear();\n return modernHandler.close().catch((err: unknown) => {\n Logger.error('[HTTP] Error closing handler:', formatUnknownErrorMessage(err));\n });\n };\n const originalClose = httpServer.close.bind(httpServer);\n httpServer.close = function (callback?: (error?: Error) => void) {\n void teardown().then(() => {\n originalClose(callback);\n });\n return httpServer;\n };\n\n return new Promise((resolve, reject) => {\n const onError = (err: Error) => {\n void teardown();\n reject(err);\n };\n httpServer.once('error', onError);\n\n httpServer.listen(port, httpHost, () => {\n httpServer.removeListener('error', onError);\n Logger.info(`[HTTP] Server listening on http://${httpHost}:${port}`);\n resolve(httpServer);\n });\n });\n}\n"]}
|
package/dist/transport/stdio.js
CHANGED
|
@@ -77,7 +77,7 @@ export async function seedRootsFromClient(ctx) {
|
|
|
77
77
|
continue; // not a file:// root
|
|
78
78
|
}
|
|
79
79
|
try {
|
|
80
|
-
if (await ctx.pathGuard.applyGrant(dir))
|
|
80
|
+
if (await ctx.pathGuard.applyGrant(dir, 'workspace'))
|
|
81
81
|
granted += 1;
|
|
82
82
|
else
|
|
83
83
|
Logger.debug('[Stdio] client root refused by grant policy', { dir });
|