@j0hanz/filesystem-mcp 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -32
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +21 -40
- package/dist/cli-help.js.map +1 -1
- package/dist/cli.d.ts +0 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +24 -46
- package/dist/cli.js.map +1 -1
- package/dist/core/concurrency.d.ts +5 -17
- package/dist/core/concurrency.d.ts.map +1 -1
- package/dist/core/concurrency.js +32 -40
- 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 +1 -12
- package/dist/core/cursor.d.ts.map +1 -1
- package/dist/core/cursor.js +2 -12
- package/dist/core/cursor.js.map +1 -1
- package/dist/core/errors.d.ts +6 -11
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +62 -117
- package/dist/core/errors.js.map +1 -1
- package/dist/core/file-uri.d.ts +7 -3
- package/dist/core/file-uri.d.ts.map +1 -1
- package/dist/core/file-uri.js +8 -4
- package/dist/core/file-uri.js.map +1 -1
- package/dist/core/fmt.d.ts +0 -11
- package/dist/core/fmt.d.ts.map +1 -1
- package/dist/core/fmt.js +1 -19
- package/dist/core/fmt.js.map +1 -1
- package/dist/core/fs.d.ts +34 -4
- package/dist/core/fs.d.ts.map +1 -1
- package/dist/core/fs.js +151 -12
- package/dist/core/fs.js.map +1 -1
- package/dist/core/glob.d.ts +1 -2
- package/dist/core/glob.d.ts.map +1 -1
- package/dist/core/glob.js +12 -28
- package/dist/core/glob.js.map +1 -1
- package/dist/core/input-required.d.ts +19 -29
- package/dist/core/input-required.d.ts.map +1 -1
- package/dist/core/input-required.js +58 -73
- package/dist/core/input-required.js.map +1 -1
- package/dist/core/observability.d.ts +9 -0
- package/dist/core/observability.d.ts.map +1 -1
- package/dist/core/observability.js +13 -2
- package/dist/core/observability.js.map +1 -1
- package/dist/core/path-completer.d.ts +6 -9
- package/dist/core/path-completer.d.ts.map +1 -1
- package/dist/core/path-completer.js +28 -55
- package/dist/core/path-completer.js.map +1 -1
- package/dist/core/path-discovery.d.ts.map +1 -1
- package/dist/core/path-discovery.js +3 -4
- package/dist/core/path-discovery.js.map +1 -1
- package/dist/core/path-utils.d.ts +28 -0
- package/dist/core/path-utils.d.ts.map +1 -1
- package/dist/core/path-utils.js +57 -2
- package/dist/core/path-utils.js.map +1 -1
- package/dist/core/path.d.ts +3 -8
- package/dist/core/path.d.ts.map +1 -1
- package/dist/core/path.js +8 -9
- package/dist/core/path.js.map +1 -1
- package/dist/core/read.d.ts +3 -22
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +46 -96
- package/dist/core/read.js.map +1 -1
- package/dist/core/schema.d.ts +8 -9
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +11 -14
- package/dist/core/schema.js.map +1 -1
- package/dist/core/search.d.ts +6 -1
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +21 -23
- package/dist/core/search.js.map +1 -1
- package/dist/core/sensitive.d.ts +4 -2
- package/dist/core/sensitive.d.ts.map +1 -1
- package/dist/core/sensitive.js +262 -45
- package/dist/core/sensitive.js.map +1 -1
- package/dist/core/store.d.ts +41 -16
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/store.js +147 -82
- package/dist/core/store.js.map +1 -1
- package/dist/core/util.d.ts +3 -2
- package/dist/core/util.d.ts.map +1 -1
- package/dist/core/util.js +5 -6
- package/dist/core/util.js.map +1 -1
- package/dist/core/watcher-registry.d.ts +1 -1
- package/dist/core/watcher-registry.d.ts.map +1 -1
- package/dist/core/watcher-registry.js +9 -5
- package/dist/core/watcher-registry.js.map +1 -1
- package/dist/index.js +5 -6
- package/dist/index.js.map +1 -1
- package/dist/instructions.js +2 -2
- package/dist/instructions.js.map +1 -1
- package/dist/prompts.js +1 -1
- package/dist/prompts.js.map +1 -1
- package/dist/resources.d.ts +11 -19
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +51 -62
- package/dist/resources.js.map +1 -1
- package/dist/server.d.ts +4 -5
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +15 -11
- package/dist/server.js.map +1 -1
- package/dist/tools/batch.d.ts +2 -2
- package/dist/tools/batch.js +3 -3
- package/dist/tools/batch.js.map +1 -1
- package/dist/tools/create.d.ts +1 -1
- package/dist/tools/create.d.ts.map +1 -1
- package/dist/tools/create.js +168 -17
- package/dist/tools/create.js.map +1 -1
- package/dist/tools/define.d.ts +15 -9
- package/dist/tools/define.d.ts.map +1 -1
- package/dist/tools/define.js +48 -94
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/delete.d.ts +1 -1
- package/dist/tools/delete.d.ts.map +1 -1
- package/dist/tools/delete.js +23 -23
- package/dist/tools/delete.js.map +1 -1
- package/dist/tools/diff.d.ts +1 -1
- package/dist/tools/diff.d.ts.map +1 -1
- package/dist/tools/diff.js +7 -37
- package/dist/tools/diff.js.map +1 -1
- package/dist/tools/edit.d.ts +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +101 -92
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/find-files.d.ts +1 -1
- package/dist/tools/find-files.d.ts.map +1 -1
- package/dist/tools/find-files.js +18 -37
- package/dist/tools/find-files.js.map +1 -1
- package/dist/tools/index.d.ts +10 -10
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +14 -13
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/list-roots.d.ts +1 -1
- package/dist/tools/list-roots.js +3 -3
- package/dist/tools/list-roots.js.map +1 -1
- package/dist/tools/list.d.ts +1 -1
- package/dist/tools/list.d.ts.map +1 -1
- package/dist/tools/list.js +23 -29
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/move.d.ts +1 -1
- package/dist/tools/move.d.ts.map +1 -1
- package/dist/tools/move.js +31 -45
- package/dist/tools/move.js.map +1 -1
- package/dist/tools/patch.d.ts +1 -1
- package/dist/tools/patch.js +7 -7
- package/dist/tools/patch.js.map +1 -1
- package/dist/tools/progress.d.ts +17 -28
- package/dist/tools/progress.d.ts.map +1 -1
- package/dist/tools/progress.js +42 -94
- package/dist/tools/progress.js.map +1 -1
- package/dist/tools/read.d.ts +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +10 -12
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/replace-text.d.ts +1 -1
- package/dist/tools/replace-text.d.ts.map +1 -1
- package/dist/tools/replace-text.js +32 -49
- package/dist/tools/replace-text.js.map +1 -1
- package/dist/tools/search-text.d.ts +1 -1
- package/dist/tools/search-text.d.ts.map +1 -1
- package/dist/tools/search-text.js +82 -45
- package/dist/tools/search-text.js.map +1 -1
- package/dist/tools/stat.d.ts +1 -1
- package/dist/tools/stat.d.ts.map +1 -1
- package/dist/tools/stat.js +9 -29
- package/dist/tools/stat.js.map +1 -1
- package/dist/transport/http-policy.js +4 -4
- package/dist/transport/http-policy.js.map +1 -1
- package/dist/transport/http.d.ts +2 -2
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +21 -18
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/shared.d.ts +2 -2
- package/dist/transport/shared.js +2 -2
- package/dist/transport/shared.js.map +1 -1
- package/dist/transport/stdio.d.ts +4 -3
- package/dist/transport/stdio.d.ts.map +1 -1
- package/dist/transport/stdio.js +7 -6
- package/dist/transport/stdio.js.map +1 -1
- package/dist/transport.d.ts +2 -2
- package/dist/transport.js +2 -2
- package/dist/transport.js.map +1 -1
- package/package.json +6 -13
- package/dist/core/page-store.d.ts +0 -41
- package/dist/core/page-store.d.ts.map +0 -1
- package/dist/core/page-store.js +0 -74
- package/dist/core/page-store.js.map +0 -1
- package/dist/core/primitives.d.ts +0 -34
- package/dist/core/primitives.d.ts.map +0 -1
- package/dist/core/primitives.js +0 -63
- package/dist/core/primitives.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sensitive.js","sourceRoot":"","sources":["../../src/core/sensitive.ts"],"names":[],"mappings":"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","sourcesContent":["// Sensitive-file denylist policy: the compiled pattern set, the path\n// normalization that feeds it, and the NTFS alternate-data-stream stripping\n// Windows needs. Split out of path.ts so the denylist has its own home — it\n// shares only isAlpha / toPosixPath / IS_WINDOWS with the primitives in\n// primitives.ts, not the allowed-directory assembly. path-completer.ts and\n// glob.ts reach it through PathGuard.isSensitive, which delegates here.\nimport { normalize, posix, sep } from 'node:path';\n\nimport { cli } from './config.js';\nimport { IS_WINDOWS, isAlpha, parseTrueEnvFlag, toPosixPath } from './primitives.js';\n\nconst CHAR_COLON = 58;\nconst CHAR_FORWARD_SLASH = 47;\n\nfunction normalizeForMatch(input: string): string {\n // Always lowercase for case-insensitive denylist matching on all platforms.\n return toPosixPath(normalize(input)).toLowerCase();\n}\n\ninterface CompiledPatternSet {\n pathGlobs: readonly string[];\n nameGlobs: readonly string[];\n}\n\nfunction isWindowsAbsolutePosixPath(normalizedPattern: string): boolean {\n return (\n normalizedPattern.length >= 3 &&\n normalizedPattern.charCodeAt(1) === CHAR_COLON &&\n normalizedPattern.charCodeAt(2) === CHAR_FORWARD_SLASH &&\n isAlpha(normalizedPattern.charCodeAt(0))\n );\n}\n\nfunction compilePatternGlobs(normalizedPattern: string): readonly string[] {\n const globs = new Set<string>([normalizedPattern]);\n\n const isRooted =\n normalizedPattern.startsWith('**/') || isWindowsAbsolutePosixPath(normalizedPattern);\n if (!isRooted) {\n const withoutRoot = normalizedPattern.replace(/^\\/+/, '');\n if (withoutRoot) {\n globs.add(`**/${withoutRoot}`);\n }\n }\n\n return Array.from(globs);\n}\n\nfunction toPatternSet(patterns: readonly string[]): CompiledPatternSet {\n const pathGlobs = new Set<string>();\n const nameGlobs = new Set<string>();\n\n const deduped = Array.from(new Set(patterns.map((p) => p.trim()).filter((p) => p.length > 0)));\n for (const pattern of deduped) {\n const normalized = normalizeForMatch(pattern);\n const matchesPath = normalized.includes('/');\n const target = matchesPath ? pathGlobs : nameGlobs;\n for (const glob of matchesPath ? compilePatternGlobs(normalized) : [normalized]) {\n target.add(glob);\n }\n }\n\n return {\n pathGlobs: [...pathGlobs],\n nameGlobs: [...nameGlobs],\n };\n}\n\nfunction matchesAnyGlob(globs: readonly string[], candidate: string): boolean {\n if (globs.length === 0) return false;\n\n for (const glob of globs) {\n if (posix.matchesGlob(candidate, glob)) return true;\n }\n\n return false;\n}\n\n// Strip NTFS alternate-data-stream suffixes (the \":stream\" after a segment)\n// before denylist matching, so `.env:secret` is caught as `.env`. Preserves the\n// drive-letter colon in the first segment of an absolute path. Also trims\n// trailing dots/spaces: Win32 strips them at the syscall boundary (a request\n// to create `.env ` actually creates `.env`), so without trimming them here the\n// exact-name patterns (`.env`, `.npmrc`) are bypassed on Windows. Mirrors the\n// precedent in getReservedDeviceName (path.ts) which trims for the same reason.\nfunction stripAlternateDataStreams(filePath: string): string {\n const parts = filePath.split(/[\\\\/]/);\n const stripped = parts.map((segment, i) => {\n if (\n i === 0 &&\n segment.length === 2 &&\n isAlpha(segment.charCodeAt(0)) &&\n segment.charCodeAt(1) === CHAR_COLON\n ) {\n return segment;\n }\n const colonIdx = segment.indexOf(':');\n const withoutStream = colonIdx !== -1 ? segment.slice(0, colonIdx) : segment;\n // Win32 strips trailing dots and spaces at the syscall boundary, so a\n // request for \".env \" creates \".env\". Strip them before denylist matching\n // or the exact-name patterns (\".env\", \".npmrc\") are bypassed on Windows.\n return trimTrailingDotsAndSpaces(withoutStream);\n });\n return stripped.join(sep);\n}\n\nconst trimTrailingDotsAndSpaces = (segment: string): string => segment.replace(/[. ]+$/, '');\n\nconst DEFAULT_SENSITIVE_PATTERNS = [\n '.env',\n '.env.*',\n '.npmrc',\n '.pypirc',\n '.aws/credentials',\n '.aws/config',\n '.mcpregistry_*_token',\n '*.pem',\n '*.key',\n '*.p12',\n '*.pfx',\n '*.crt',\n '*.cer',\n '*id_rsa*',\n '*id_dsa*',\n] as const;\n\nfunction buildSensitivePatterns(): readonly string[] {\n const allowSensitive =\n cli.allowSensitive ?? parseTrueEnvFlag(process.env['FS_ALLOW_SENSITIVE'], 'FS_ALLOW_SENSITIVE');\n const envValue = process.env['FS_DENYLIST'];\n const envDenylist = envValue\n ? envValue\n .split(/[,\\n]/u)\n .map((t) => t.trim())\n .filter((t) => t.length > 0)\n : [];\n const flagDenylist = cli.denyPatterns ?? [];\n // FS_ALLOW_SENSITIVE suppresses built-ins only; deny entries (env and --deny)\n // always apply. Set-dedupe so a pattern in both sources matches once.\n return [\n ...(allowSensitive ? [] : DEFAULT_SENSITIVE_PATTERNS),\n ...new Set([...envDenylist, ...flagDenylist]),\n ];\n}\n\nexport class SensitiveMatcher {\n private readonly patterns: CompiledPatternSet;\n\n constructor(patterns: readonly string[] = buildSensitivePatterns()) {\n this.patterns = toPatternSet(patterns);\n }\n\n isSensitive(filePath: string): boolean {\n if (this.patterns.pathGlobs.length === 0 && this.patterns.nameGlobs.length === 0) {\n return false;\n }\n const pathToCheck = IS_WINDOWS ? stripAlternateDataStreams(filePath) : filePath;\n const normalizedPath = normalizeForMatch(pathToCheck);\n return (\n matchesAnyGlob(this.patterns.pathGlobs, normalizedPath) ||\n matchesAnyGlob(this.patterns.nameGlobs, posix.basename(normalizedPath))\n );\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sensitive.js","sourceRoot":"","sources":["../../src/core/sensitive.ts"],"names":[],"mappings":"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","sourcesContent":["// Sensitive-file denylist policy: the compiled pattern set, the path\n// normalization that feeds it, and the NTFS alternate-data-stream stripping\n// Windows needs. Split out of path.ts so the denylist has its own home — it\n// shares only isAlpha / toPosixPath / IS_WINDOWS with the primitives in\n// primitives.ts, not the allowed-directory assembly. path-completer.ts and\n// glob.ts reach it through PathGuard.isSensitive, which delegates here.\nimport { normalize, posix, sep } from 'node:path';\n\nimport { cli } from './config.ts';\nimport { IS_WINDOWS, isAlpha, parseTrueEnvFlag, toPosixPath } from './path-utils.ts';\n\nconst CHAR_COLON = 58;\n\nfunction normalizeForMatch(input: string): string {\n // Always lowercase for case-insensitive denylist matching on all platforms.\n // normalize() preserves the double leading slash of UNC paths\n // (//server/share/...) on Windows; collapse it to one so the compiled\n // patterns — which carry no leading slash — still match their segments.\n return toPosixPath(normalize(input)).toLowerCase().replace(/^\\/+/u, '/');\n}\n\ninterface CompiledPatternSet {\n pathGlobs: readonly RegExp[];\n nameGlobs: readonly RegExp[];\n}\n\n// posix.matchesGlob runs with dot:false semantics and does not honor a dot\n// option at all (verified on Node 24): '*' and '**' never match dot-leading\n// path segments, so a deny of `secrets/**` silently let `secrets/.env`\n// through — fail-open against exactly the hidden files this list exists to\n// guard. Compile our own matcher instead. Supported metacharacters: '*'\n// (any run within a segment), '**' (any run of whole segments), '?' (one\n// char), '[...]' classes, and '{a,b}' alternation; everything else is\n// literal, and dot-leading segments match like any other.\n\n// Character classes can carry '{', '}' and ',' as members, so every scanner\n// below must skip a class's interior. classEnd returns the index of the ']'\n// closing the class opened at openIdx, or -1 when unclosed (then '[' is a\n// literal, matching globToRegExp's fallback). A ']' right after '[' or the\n// '!' negation is a literal member, per glob syntax.\nfunction classEnd(glob: string, openIdx: number): number {\n let i = openIdx + 1;\n if (glob[i] === '!') i++;\n if (glob[i] === ']') i++;\n while (i < glob.length && glob[i] !== ']') i++;\n return i < glob.length ? i : -1;\n}\n\nfunction expandBraces(glob: string): string[] {\n // First '{' outside any [...] class.\n let open = -1;\n for (let i = 0; i < glob.length; i++) {\n const char = glob[i] ?? '';\n if (char === '[') {\n const end = classEnd(glob, i);\n if (end !== -1) i = end;\n } else if (char === '{') {\n open = i;\n break;\n }\n }\n if (open === -1) return [glob];\n // Pair the outer '{' with ITS matching '}' — indexOf would grab the inner\n // group's closer and produce garbage like 'data/x}/secret'. Then split the\n // body on depth-0 commas so nested groups stay intact for the recursion.\n let depth = 0;\n let close = -1;\n for (let i = open; i < glob.length; i++) {\n const char = glob[i] ?? '';\n if (char === '[') {\n const end = classEnd(glob, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') {\n depth--;\n if (depth === 0) {\n close = i;\n break;\n }\n }\n }\n if (close === -1) return [glob];\n const prefix = glob.slice(0, open);\n const suffix = glob.slice(close + 1);\n const body = glob.slice(open + 1, close);\n const parts: string[] = [];\n let start = 0;\n depth = 0;\n for (let i = 0; i < body.length; i++) {\n const char = body[i] ?? '';\n if (char === '[') {\n const end = classEnd(body, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') depth--;\n else if (char === ',' && depth === 0) {\n parts.push(body.slice(start, i));\n start = i + 1;\n }\n }\n parts.push(body.slice(start));\n return parts.flatMap((part) => expandBraces(`${prefix}${part}${suffix}`));\n}\n\nfunction globToRegExp(glob: string): RegExp {\n const segments = glob.split('/');\n let source = '';\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i] ?? '';\n const last = i === segments.length - 1;\n if (segment === '**') {\n source += last ? '(?:.*)?' : '(?:[^/]+/)*';\n continue;\n }\n for (let j = 0; j < segment.length; j++) {\n const char = segment[j] ?? '';\n if (char === '*') {\n while (segment[j + 1] === '*') j++;\n source += '[^/]*';\n } else if (char === '?') {\n source += '[^/]';\n } else if (char === '[') {\n // Same classEnd rule the scanners use: a ']' right after '[' (or\n // after the '!' negation) is a literal member, not the closer.\n const close = classEnd(segment, j);\n if (close !== -1) {\n // Pass the class through, flipping glob '!' negation to '^' and\n // escaping backslashes so no body can make new RegExp throw. A\n // leading ']' must be escaped or the 'u' flag parses '[]a]' as an\n // empty class followed by stray literals; a leading '^' is a\n // literal member (glob negation is '!' only), unescaped it would\n // negate the class and relieve more than the operator named.\n let body = segment.slice(j + 1, close).replace(/\\\\/gu, '\\\\\\\\');\n const negated = body.startsWith('!');\n if (negated) body = body.slice(1);\n if (body.startsWith(']') || body.startsWith('^')) body = `\\\\${body}`;\n source += `[${negated ? '^' : ''}${body}]`;\n j = close;\n } else {\n source += RegExp.escape(char);\n }\n } else {\n source += RegExp.escape(char);\n }\n }\n if (!last) source += '/';\n }\n // Candidates are absolute paths but patterns carry no leading '/', so the\n // anchor optionally eats the root slash — `**/` then reaches from any\n // parent, exactly as matchesGlob's globstar did. '$' is strict in\n // JavaScript (it matches end-of-input only, unlike PCRE's, which also\n // matches before a final newline), so an exact-name pattern can never\n // match a '.env\\n' lookalike. The 's' flag makes the terminal-`**` `.`\n // match newlines too: filenames can contain them, and a deny ending in\n // '/**' must not fail open on 'secrets/plan\\nnotes'.\n return new RegExp(`^/?${source}$`, 'su');\n}\n\nfunction compilePatternGlobs(normalizedPattern: string): readonly string[] {\n // A leading '/' (POSIX-absolute) is rooted like a Windows drive path:\n // aliasing '/abs/secret' as '**/abs/secret' would also match\n // 'other/abs/secret', and on the allow side that is over-relief. The\n // drive-letter regex is safe on [a-z] because normalizeForMatch lowercased.\n const isRooted =\n normalizedPattern.startsWith('/') ||\n normalizedPattern.startsWith('**/') ||\n /^[a-z]:\\//u.test(normalizedPattern);\n const withoutRoot = isRooted ? '' : normalizedPattern.replace(/^\\/+/, '');\n return withoutRoot ? [normalizedPattern, `**/${withoutRoot}`] : [normalizedPattern];\n}\n\nfunction toPatternSet(patterns: readonly string[]): CompiledPatternSet {\n const pathGlobs = new Set<RegExp>();\n const nameGlobs = new Set<RegExp>();\n\n const deduped = Array.from(new Set(patterns.map((p) => p.trim()).filter((p) => p.length > 0)));\n for (const pattern of deduped) {\n const normalized = normalizeForMatch(pattern);\n const matchesPath = normalized.includes('/');\n const target = matchesPath ? pathGlobs : nameGlobs;\n const globs = matchesPath ? compilePatternGlobs(normalized) : [normalized];\n for (const glob of globs) {\n for (const expanded of expandBraces(glob)) {\n target.add(globToRegExp(expanded));\n }\n }\n }\n\n return {\n pathGlobs: [...pathGlobs],\n nameGlobs: [...nameGlobs],\n };\n}\n\nfunction matchesAnyGlob(globs: readonly RegExp[], candidate: string): boolean {\n return globs.some((glob) => glob.test(candidate));\n}\n\n// Strip NTFS alternate-data-stream suffixes (the \":stream\" after a segment)\n// before denylist matching, so `.env:secret` is caught as `.env`. Preserves the\n// drive-letter colon in the first segment of an absolute path. Also trims\n// trailing dots/spaces: Win32 strips them at the syscall boundary (a request\n// to create `.env ` actually creates `.env`), so without trimming them here the\n// exact-name patterns (`.env`, `.npmrc`) are bypassed on Windows. Mirrors the\n// precedent in getReservedDeviceName (path.ts) which trims for the same reason.\nfunction stripAlternateDataStreams(filePath: string): string {\n const parts = filePath.split(/[\\\\/]/);\n const stripped = parts.map((segment, i) => {\n if (\n i === 0 &&\n segment.length === 2 &&\n isAlpha(segment.charCodeAt(0)) &&\n segment.charCodeAt(1) === CHAR_COLON\n ) {\n return segment;\n }\n const colonIdx = segment.indexOf(':');\n const withoutStream = colonIdx !== -1 ? segment.slice(0, colonIdx) : segment;\n // Win32 strips trailing dots and spaces at the syscall boundary, so a\n // request for \".env \" creates \".env\". Strip them before denylist matching\n // or the exact-name patterns (\".env\", \".npmrc\") are bypassed on Windows.\n return trimTrailingDotsAndSpaces(withoutStream);\n });\n return stripped.join(sep);\n}\n\nconst trimTrailingDotsAndSpaces = (segment: string): string => segment.replace(/[. ]+$/, '');\n\nconst DEFAULT_SENSITIVE_PATTERNS = [\n '.env',\n '.env.*',\n '.npmrc',\n '.pypirc',\n '.aws/credentials',\n '.aws/config',\n '.mcpregistry_*_token',\n '*.pem',\n '*.key',\n '*.p12',\n '*.pfx',\n '*.crt',\n '*.cer',\n '*id_rsa*',\n '*id_dsa*',\n] as const;\n\n// Built-ins and operator-supplied deny entries live in separate tiers because\n// they answer to different relief switches: an allow entry (FS_ALLOWLIST /\n// --allow) can lift a built-in hit, but never an explicit deny.\n\n// Split an env pattern list on commas and newlines at brace depth 0 only and\n// outside any [...] class, so documented syntax like '*.{pem,key}' or\n// 'x[1,2].env' survives as one pattern instead of tearing apart.\nfunction splitPatternList(value: string): string[] {\n const naive = (): string[] =>\n value\n .split(/,|\\n/u)\n .map((t) => t.trim())\n .filter((t) => t.length > 0);\n const parts: string[] = [];\n let depth = 0;\n let start = 0;\n let unbalanced = false;\n for (let i = 0; i < value.length; i++) {\n const char = value[i] ?? '';\n if (char === '[') {\n const end = classEnd(value, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') {\n if (depth === 0) unbalanced = true;\n else depth--;\n } else if (depth === 0 && (char === ',' || char === '\\n')) {\n parts.push(value.slice(start, i));\n start = i + 1;\n }\n }\n // An unbalanced brace would swallow every separator behind it into one\n // bogus literal pattern and silently drop the real entries — '{bad,\n // secrets/**' must not lose 'secrets/**'. A typo'd brace costs the brace\n // pattern only: fall back to the naive split.\n if (unbalanced || depth !== 0) return naive();\n parts.push(value.slice(start));\n return parts.map((t) => t.trim()).filter((t) => t.length > 0);\n}\n\nfunction buildDenyTiers(): { builtin: readonly string[]; operator: readonly string[] } {\n const allowSensitive =\n cli.allowSensitive ?? parseTrueEnvFlag(process.env['FS_ALLOW_SENSITIVE'], 'FS_ALLOW_SENSITIVE');\n const envDenylist = process.env['FS_DENYLIST']\n ? splitPatternList(process.env['FS_DENYLIST'])\n : [];\n const flagDenylist = cli.denyPatterns ?? [];\n // FS_ALLOW_SENSITIVE suppresses built-ins only; deny entries (env and --deny)\n // always apply. Set-dedupe so a pattern in both sources matches once.\n return {\n builtin: allowSensitive ? [] : DEFAULT_SENSITIVE_PATTERNS,\n operator: [...new Set([...envDenylist, ...flagDenylist])],\n };\n}\n\nfunction buildAllowPatterns(): readonly string[] {\n const envAllowlist = process.env['FS_ALLOWLIST']\n ? splitPatternList(process.env['FS_ALLOWLIST'])\n : [];\n const flagAllowlist = cli.allowPatterns ?? [];\n return [...new Set([...envAllowlist, ...flagAllowlist])];\n}\n\nconst EMPTY_PATTERN_SET: CompiledPatternSet = { pathGlobs: [], nameGlobs: [] };\n\nexport class SensitiveMatcher {\n private readonly builtin: CompiledPatternSet;\n private readonly operator: CompiledPatternSet;\n private readonly allow: CompiledPatternSet;\n\n // An explicit pattern list (tests, custom guards) is one relievable tier;\n // the default construction splits built-ins from operator-supplied\n // FS_DENYLIST/--deny entries, which allow entries can never lift.\n constructor(patterns?: readonly string[], allow?: readonly string[]) {\n if (patterns !== undefined || allow !== undefined) {\n this.builtin = toPatternSet(patterns ?? []);\n this.operator = EMPTY_PATTERN_SET;\n this.allow = toPatternSet(allow ?? []);\n } else {\n const tiers = buildDenyTiers();\n this.builtin = toPatternSet(tiers.builtin);\n this.operator = toPatternSet(tiers.operator);\n this.allow = toPatternSet(buildAllowPatterns());\n }\n }\n\n isSensitive(filePath: string): boolean {\n const pathToCheck = IS_WINDOWS ? stripAlternateDataStreams(filePath) : filePath;\n const normalizedPath = normalizeForMatch(pathToCheck);\n const name = posix.basename(normalizedPath);\n const matches = (set: CompiledPatternSet): boolean =>\n matchesAnyGlob(set.pathGlobs, normalizedPath) || matchesAnyGlob(set.nameGlobs, name);\n\n // Explicit deny entries (FS_DENYLIST/--deny) always apply.\n if (matches(this.operator)) return true;\n if (!matches(this.builtin)) return false;\n // A built-in hit is lifted only by an allow entry that matches the same\n // normalized candidate — a pattern that matches nothing relieves nothing.\n return !matches(this.allow);\n }\n}\n"]}
|
package/dist/core/store.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ContentBlock } from '@modelcontextprotocol/server';
|
|
2
|
+
import { FsError } from './errors.ts';
|
|
2
3
|
interface ResourceEntry {
|
|
3
4
|
uri: string;
|
|
4
5
|
name: string;
|
|
@@ -8,30 +9,18 @@ interface ResourceEntry {
|
|
|
8
9
|
text: string;
|
|
9
10
|
}
|
|
10
11
|
export declare class ResourceStore {
|
|
11
|
-
private
|
|
12
|
-
private _totalBytes;
|
|
13
|
-
private readonly onListChanged;
|
|
12
|
+
#private;
|
|
14
13
|
constructor(onListChanged?: () => void);
|
|
15
|
-
private emitListChanged;
|
|
16
|
-
private bumpLru;
|
|
17
|
-
private removeEntry;
|
|
18
|
-
private evictOldest;
|
|
19
|
-
private pruneExpiredEntries;
|
|
20
|
-
private enforceLimits;
|
|
21
14
|
getEntry(uri: string): ResourceEntry;
|
|
22
|
-
private checkBeforePut;
|
|
23
15
|
putText(params: {
|
|
24
16
|
name: string;
|
|
25
17
|
mimeType?: string;
|
|
26
18
|
text: string;
|
|
27
19
|
}): ResourceEntry;
|
|
28
20
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* Deliberately silent: this is the read path `resources/list` runs through,
|
|
33
|
-
* and the caller is already receiving the post-prune list. Emitting here
|
|
34
|
-
* would tell the client its list changed while handing it that same list.
|
|
21
|
+
* Live URIs, oldest first. Deliberately silent about the prune it runs:
|
|
22
|
+
* this is the read path `resources/list` runs through, and the caller is
|
|
23
|
+
* already receiving the post-prune list.
|
|
35
24
|
*/
|
|
36
25
|
keys(): string[];
|
|
37
26
|
}
|
|
@@ -51,5 +40,41 @@ export interface JsonResourceResult {
|
|
|
51
40
|
* it to the user.
|
|
52
41
|
*/
|
|
53
42
|
export declare function putJsonResource(store: ResourceStore, name: string, value: Record<string, unknown> | readonly unknown[]): JsonResourceResult;
|
|
43
|
+
export interface PageSnapshot<T = unknown, M = unknown> {
|
|
44
|
+
readonly items: readonly T[];
|
|
45
|
+
readonly metadata: M;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The one cursor rejection: a cursor whose snapshot expired, was evicted, was
|
|
49
|
+
* already consumed past its end, or belongs to a different query. Every case
|
|
50
|
+
* has the same remedy, so they share one message.
|
|
51
|
+
*/
|
|
52
|
+
export declare function invalidCursor(): FsError;
|
|
53
|
+
/**
|
|
54
|
+
* Short-lived snapshots of a completed query's full result set, so later pages
|
|
55
|
+
* slice a stored array instead of re-scanning and re-sorting the filesystem.
|
|
56
|
+
*
|
|
57
|
+
* ponytail: bounded by snapshot count and TTL, not by bytes — 32 x 20,000
|
|
58
|
+
* `list` entries or 32 x 10,000 `search_text` matches, each match retaining up
|
|
59
|
+
* to 20 more line strings at `context: 10`, order of a few hundred MB held for
|
|
60
|
+
* 60s, and the HTTP leg shares one store so any caller can drive it. If that
|
|
61
|
+
* shows up as memory pressure, pass a byte weight to `set` the way
|
|
62
|
+
* `ResourceStore` does.
|
|
63
|
+
*/
|
|
64
|
+
export declare class PageSnapshotStore {
|
|
65
|
+
#private;
|
|
66
|
+
constructor(options?: {
|
|
67
|
+
maxSnapshots?: number;
|
|
68
|
+
ttlMs?: number;
|
|
69
|
+
now?: () => number;
|
|
70
|
+
});
|
|
71
|
+
create(params: {
|
|
72
|
+
queryKey: string;
|
|
73
|
+
items: readonly unknown[];
|
|
74
|
+
metadata?: unknown;
|
|
75
|
+
}): string;
|
|
76
|
+
read<T, M = undefined>(snapshotId: string, queryKey: string): PageSnapshot<T, M>;
|
|
77
|
+
clear(): void;
|
|
78
|
+
}
|
|
54
79
|
export {};
|
|
55
80
|
//# sourceMappingURL=store.d.ts.map
|
package/dist/core/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/core/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/core/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAIjE,OAAO,EAAa,OAAO,EAAE,MAAM,aAAa,CAAC;AAyFjD,UAAU,aAAa;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAOD,qBAAa,aAAa;;gBAQZ,aAAa,CAAC,EAAE,MAAM,IAAI;IAItC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa;IAYpC,OAAO,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,aAAa;IAmBjF;;;;OAIG;IACH,IAAI,IAAI,MAAM,EAAE;CAGjB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,IAAI,EAAE,YAAY,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,MAAM,EAIZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,OAAO,EAAE,GAClD,kBAAkB,CAuBpB;AAID,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO;IACpD,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAKvC;AAED;;;;;;;;;;GAUG;AACH,qBAAa,iBAAiB;;gBAGhB,OAAO,GAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;KAAO;IAQvF,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,SAAS,OAAO,EAAE,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,MAAM;IAU3F,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC;IAMhF,KAAK,IAAI,IAAI;CAGd"}
|
package/dist/core/store.js
CHANGED
|
@@ -1,114 +1,132 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { ErrorCode, FsError } from
|
|
3
|
-
import { MIB } from
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
this
|
|
2
|
+
import { ErrorCode, FsError } from "./errors.js";
|
|
3
|
+
import { MIB } from "./util.js";
|
|
4
|
+
// ─── TTL + LRU map ───────────────────────────────────────────────────────────
|
|
5
|
+
/**
|
|
6
|
+
* A bounded, expiring map. `Map` keeps insertion order and a read re-inserts,
|
|
7
|
+
* so the first key is always the least recently used one. Entries carry an
|
|
8
|
+
* optional weight so a byte budget can be enforced next to the entry cap.
|
|
9
|
+
*/
|
|
10
|
+
class TtlLru {
|
|
11
|
+
#byKey = new Map();
|
|
12
|
+
#maxEntries;
|
|
13
|
+
#maxWeight;
|
|
14
|
+
#ttlMs;
|
|
15
|
+
#now;
|
|
16
|
+
#weight = 0;
|
|
17
|
+
constructor(opts) {
|
|
18
|
+
this.#maxEntries = opts.maxEntries;
|
|
19
|
+
this.#maxWeight = opts.maxWeight ?? Number.POSITIVE_INFINITY;
|
|
20
|
+
this.#ttlMs = opts.ttlMs;
|
|
21
|
+
this.#now = opts.now ?? Date.now;
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
this.onListChanged?.();
|
|
23
|
+
get size() {
|
|
24
|
+
return this.#byKey.size;
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
/** Drop every expired entry; true when at least one went. */
|
|
27
|
+
prune() {
|
|
28
|
+
const now = this.#now();
|
|
29
|
+
const before = this.#byKey.size;
|
|
30
|
+
for (const [key, entry] of this.#byKey) {
|
|
31
|
+
if (entry.expiresAt <= now)
|
|
32
|
+
this.delete(key);
|
|
29
33
|
}
|
|
30
|
-
this.
|
|
31
|
-
this.byUri.set(uri, entry);
|
|
34
|
+
return this.#byKey.size !== before;
|
|
32
35
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
/** The live value, bumped to most recently used; `undefined` when missing or expired. */
|
|
37
|
+
get(key) {
|
|
38
|
+
const entry = this.#byKey.get(key);
|
|
39
|
+
if (!entry)
|
|
40
|
+
return undefined;
|
|
41
|
+
if (entry.expiresAt <= this.#now()) {
|
|
42
|
+
this.delete(key);
|
|
36
43
|
return undefined;
|
|
37
44
|
}
|
|
38
|
-
this.
|
|
39
|
-
this.
|
|
40
|
-
return
|
|
45
|
+
this.#byKey.delete(key);
|
|
46
|
+
this.#byKey.set(key, entry);
|
|
47
|
+
return entry.value;
|
|
41
48
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
/** Insert (pruning expired first) and evict least recently used entries past either cap. */
|
|
50
|
+
set(key, value, weight = 0) {
|
|
51
|
+
this.prune();
|
|
52
|
+
this.delete(key);
|
|
53
|
+
this.#byKey.set(key, { value, expiresAt: this.#now() + this.#ttlMs, weight });
|
|
54
|
+
this.#weight += weight;
|
|
55
|
+
while (this.#byKey.size > 0 &&
|
|
56
|
+
(this.#byKey.size > this.#maxEntries || this.#weight > this.#maxWeight)) {
|
|
57
|
+
const oldest = this.#byKey.keys().next();
|
|
58
|
+
if (oldest.done)
|
|
59
|
+
break;
|
|
60
|
+
this.delete(oldest.value);
|
|
46
61
|
}
|
|
47
|
-
this.removeEntry(first.value.uri);
|
|
48
62
|
}
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return this.byUri.size !== before;
|
|
63
|
+
delete(key) {
|
|
64
|
+
const entry = this.#byKey.get(key);
|
|
65
|
+
if (!entry)
|
|
66
|
+
return;
|
|
67
|
+
this.#weight -= entry.weight;
|
|
68
|
+
this.#byKey.delete(key);
|
|
56
69
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
70
|
+
/** Live keys, least recently used first. Prunes expired entries first. */
|
|
71
|
+
keys() {
|
|
72
|
+
this.prune();
|
|
73
|
+
return [...this.#byKey.keys()];
|
|
74
|
+
}
|
|
75
|
+
clear() {
|
|
76
|
+
this.#byKey.clear();
|
|
77
|
+
this.#weight = 0;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const MAX_ENTRIES = 64;
|
|
81
|
+
const MAX_TOTAL_BYTES = 25 * MIB;
|
|
82
|
+
const MAX_ENTRY_BYTES = 10 * MIB;
|
|
83
|
+
const ENTRY_TTL_MS = 60 * 1000;
|
|
84
|
+
export class ResourceStore {
|
|
85
|
+
#entries = new TtlLru({
|
|
86
|
+
maxEntries: MAX_ENTRIES,
|
|
87
|
+
maxWeight: MAX_TOTAL_BYTES,
|
|
88
|
+
ttlMs: ENTRY_TTL_MS,
|
|
89
|
+
});
|
|
90
|
+
#onListChanged;
|
|
91
|
+
constructor(onListChanged) {
|
|
92
|
+
this.#onListChanged = onListChanged;
|
|
62
93
|
}
|
|
63
94
|
getEntry(uri) {
|
|
64
|
-
const
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
this
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
this.bumpLru(uri, existing);
|
|
74
|
-
return existing;
|
|
95
|
+
const entry = this.#entries.get(uri);
|
|
96
|
+
if (entry)
|
|
97
|
+
return entry;
|
|
98
|
+
// Not found and expired share a remedy; only the wording differs, and an
|
|
99
|
+
// expiry seen here changed the list a client may hold.
|
|
100
|
+
if (this.#entries.prune())
|
|
101
|
+
this.#onListChanged?.();
|
|
102
|
+
throw new FsError(ErrorCode.NOT_FOUND, `Resource not found: ${uri}. Re-run the tool to regenerate.`);
|
|
75
103
|
}
|
|
76
|
-
|
|
77
|
-
const
|
|
104
|
+
putText(params) {
|
|
105
|
+
const entryBytes = Buffer.byteLength(params.text, 'utf8');
|
|
78
106
|
if (entryBytes > MAX_ENTRY_BYTES) {
|
|
79
|
-
this.
|
|
107
|
+
if (this.#entries.prune())
|
|
108
|
+
this.#onListChanged?.();
|
|
80
109
|
throw new FsError(ErrorCode.TOO_LARGE, `Resource too large to cache (${entryBytes} bytes).`);
|
|
81
110
|
}
|
|
82
|
-
}
|
|
83
|
-
putText(params) {
|
|
84
|
-
const entryBytes = Buffer.byteLength(params.text, 'utf8');
|
|
85
|
-
this.checkBeforePut(entryBytes);
|
|
86
|
-
const storedAt = new Date();
|
|
87
111
|
const entry = {
|
|
88
112
|
uri: `filesystem-mcp://result/${randomUUID()}`,
|
|
89
113
|
name: params.name,
|
|
90
114
|
mimeType: params.mimeType ?? 'text/plain',
|
|
91
115
|
size: entryBytes,
|
|
92
|
-
expiresAt: new Date(
|
|
116
|
+
expiresAt: new Date(Date.now() + ENTRY_TTL_MS).toISOString(),
|
|
93
117
|
text: params.text,
|
|
94
118
|
};
|
|
95
|
-
this.
|
|
96
|
-
this
|
|
97
|
-
this.enforceLimits();
|
|
98
|
-
this.emitListChanged(true);
|
|
119
|
+
this.#entries.set(entry.uri, entry, entryBytes);
|
|
120
|
+
this.#onListChanged?.();
|
|
99
121
|
return entry;
|
|
100
122
|
}
|
|
101
123
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* Deliberately silent: this is the read path `resources/list` runs through,
|
|
106
|
-
* and the caller is already receiving the post-prune list. Emitting here
|
|
107
|
-
* would tell the client its list changed while handing it that same list.
|
|
124
|
+
* Live URIs, oldest first. Deliberately silent about the prune it runs:
|
|
125
|
+
* this is the read path `resources/list` runs through, and the caller is
|
|
126
|
+
* already receiving the post-prune list.
|
|
108
127
|
*/
|
|
109
128
|
keys() {
|
|
110
|
-
this.
|
|
111
|
-
return Array.from(this.byUri.keys());
|
|
129
|
+
return this.#entries.keys();
|
|
112
130
|
}
|
|
113
131
|
}
|
|
114
132
|
/**
|
|
@@ -144,4 +162,51 @@ value) {
|
|
|
144
162
|
},
|
|
145
163
|
};
|
|
146
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* The one cursor rejection: a cursor whose snapshot expired, was evicted, was
|
|
167
|
+
* already consumed past its end, or belongs to a different query. Every case
|
|
168
|
+
* has the same remedy, so they share one message.
|
|
169
|
+
*/
|
|
170
|
+
export function invalidCursor() {
|
|
171
|
+
return new FsError(ErrorCode.INVALID_INPUT, 'Invalid cursor. Request the first page without a cursor.');
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Short-lived snapshots of a completed query's full result set, so later pages
|
|
175
|
+
* slice a stored array instead of re-scanning and re-sorting the filesystem.
|
|
176
|
+
*
|
|
177
|
+
* ponytail: bounded by snapshot count and TTL, not by bytes — 32 x 20,000
|
|
178
|
+
* `list` entries or 32 x 10,000 `search_text` matches, each match retaining up
|
|
179
|
+
* to 20 more line strings at `context: 10`, order of a few hundred MB held for
|
|
180
|
+
* 60s, and the HTTP leg shares one store so any caller can drive it. If that
|
|
181
|
+
* shows up as memory pressure, pass a byte weight to `set` the way
|
|
182
|
+
* `ResourceStore` does.
|
|
183
|
+
*/
|
|
184
|
+
export class PageSnapshotStore {
|
|
185
|
+
#byId;
|
|
186
|
+
constructor(options = {}) {
|
|
187
|
+
this.#byId = new TtlLru({
|
|
188
|
+
maxEntries: options.maxSnapshots ?? 32,
|
|
189
|
+
ttlMs: options.ttlMs ?? 60 * 1000,
|
|
190
|
+
...(options.now ? { now: options.now } : {}),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
create(params) {
|
|
194
|
+
const snapshotId = randomUUID();
|
|
195
|
+
this.#byId.set(snapshotId, {
|
|
196
|
+
queryKey: params.queryKey,
|
|
197
|
+
items: params.items,
|
|
198
|
+
metadata: params.metadata,
|
|
199
|
+
});
|
|
200
|
+
return snapshotId;
|
|
201
|
+
}
|
|
202
|
+
read(snapshotId, queryKey) {
|
|
203
|
+
const entry = this.#byId.get(snapshotId);
|
|
204
|
+
if (entry?.queryKey !== queryKey)
|
|
205
|
+
throw invalidCursor();
|
|
206
|
+
return { items: entry.items, metadata: entry.metadata };
|
|
207
|
+
}
|
|
208
|
+
clear() {
|
|
209
|
+
this.#byId.clear();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
147
212
|
//# sourceMappingURL=store.js.map
|
package/dist/core/store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/core/store.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { randomUUID } from 'node:crypto';\n\nimport { ErrorCode, FsError } from './errors.js';\nimport { MIB } from './util.js';\n\ninterface ResourceEntry {\n uri: string;\n name: string;\n mimeType: string;\n size: number;\n expiresAt: string;\n text: string;\n}\n\nconst MAX_ENTRIES = 64;\nconst MAX_TOTAL_BYTES = 25 * MIB;\nconst MAX_ENTRY_BYTES = 10 * MIB;\nconst ENTRY_TTL_MS = 60 * 1000;\n\nfunction isExpired(entry: ResourceEntry, now = Date.now()): boolean {\n const expiresAt = Date.parse(entry.expiresAt);\n if (!Number.isFinite(expiresAt)) {\n return true;\n }\n return expiresAt <= now;\n}\n\nexport class ResourceStore {\n private readonly byUri = new Map<string, ResourceEntry>();\n private _totalBytes = 0;\n\n private readonly onListChanged: (() => void) | undefined;\n\n constructor(onListChanged?: () => void) {\n this.onListChanged = onListChanged;\n }\n\n private emitListChanged(changed: boolean): void {\n if (changed) this.onListChanged?.();\n }\n\n private bumpLru(uri: string, entry: ResourceEntry): void {\n if (!this.byUri.has(uri)) {\n return;\n }\n this.byUri.delete(uri);\n this.byUri.set(uri, entry);\n }\n\n private removeEntry(uri: string): ResourceEntry | undefined {\n const existing = this.byUri.get(uri);\n if (!existing) {\n return undefined;\n }\n this._totalBytes -= existing.size;\n this.byUri.delete(uri);\n return existing;\n }\n\n private evictOldest(): void {\n const first = this.byUri.values().next();\n if (first.done) {\n return;\n }\n this.removeEntry(first.value.uri);\n }\n\n private pruneExpiredEntries(now = Date.now()): boolean {\n const before = this.byUri.size;\n for (const entry of this.byUri.values()) {\n if (isExpired(entry, now)) this.removeEntry(entry.uri);\n }\n return this.byUri.size !== before;\n }\n\n private enforceLimits(): void {\n while (\n this.byUri.size > 0 &&\n (this.byUri.size > MAX_ENTRIES || this._totalBytes > MAX_TOTAL_BYTES)\n ) {\n this.evictOldest();\n }\n }\n\n getEntry(uri: string): ResourceEntry {\n const existing = this.byUri.get(uri);\n\n if (!existing) {\n throw new FsError(\n ErrorCode.NOT_FOUND,\n `Resource not found: ${uri}. Re-run the tool to regenerate.`,\n );\n }\n\n if (isExpired(existing)) {\n this.removeEntry(uri);\n this.emitListChanged(true);\n throw new FsError(\n ErrorCode.NOT_FOUND,\n `Resource expired: ${uri}. Re-run the tool to regenerate.`,\n );\n }\n\n this.bumpLru(uri, existing);\n return existing;\n }\n\n private checkBeforePut(entryBytes: number): void {\n const changed = this.pruneExpiredEntries();\n if (entryBytes > MAX_ENTRY_BYTES) {\n this.emitListChanged(changed);\n throw new FsError(ErrorCode.TOO_LARGE, `Resource too large to cache (${entryBytes} bytes).`);\n }\n }\n\n putText(params: { name: string; mimeType?: string; text: string }): ResourceEntry {\n const entryBytes = Buffer.byteLength(params.text, 'utf8');\n this.checkBeforePut(entryBytes);\n\n const storedAt = new Date();\n const entry: ResourceEntry = {\n uri: `filesystem-mcp://result/${randomUUID()}`,\n name: params.name,\n mimeType: params.mimeType ?? 'text/plain',\n size: entryBytes,\n expiresAt: new Date(storedAt.getTime() + ENTRY_TTL_MS).toISOString(),\n text: params.text,\n };\n this.byUri.set(entry.uri, entry);\n this._totalBytes += entryBytes;\n this.enforceLimits();\n this.emitListChanged(true);\n return entry;\n }\n\n /**\n * Returns live URIs currently in the store.\n * Side effect: prunes expired entries before returning.\n *\n * Deliberately silent: this is the read path `resources/list` runs through,\n * and the caller is already receiving the post-prune list. Emitting here\n * would tell the client its list changed while handing it that same list.\n */\n keys(): string[] {\n this.pruneExpiredEntries();\n return Array.from(this.byUri.keys());\n }\n}\n\nexport interface JsonResourceResult {\n entry: {\n uri: string;\n size: number;\n mimeType: string;\n expiresAt: string;\n };\n link: ContentBlock;\n}\n\n/**\n * Owner of the externalize-a-payload rule: a tool whose inline response is\n * truncated publishes the full value to the resource store as pretty-printed\n * JSON and hands back the URI to reach it by, plus the link block that offers\n * it to the user.\n */\nexport function putJsonResource(\n store: ResourceStore,\n name: string,\n // `Record<string, unknown> | readonly unknown[]`, not `unknown` or `object`:\n // JSON.stringify returns undefined for undefined and for functions, and `null`\n // is excluded so non-string payloads cannot enter the store's text entry.\n value: Record<string, unknown> | readonly unknown[],\n): JsonResourceResult {\n const entry = store.putText({\n name,\n mimeType: 'application/json',\n text: JSON.stringify(value, null, 2),\n });\n\n return {\n entry: {\n uri: entry.uri,\n size: entry.size,\n mimeType: entry.mimeType,\n expiresAt: entry.expiresAt,\n },\n link: {\n type: 'resource_link',\n uri: entry.uri,\n name: entry.name,\n mimeType: entry.mimeType,\n size: entry.size,\n annotations: { audience: ['user'] },\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/core/store.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { randomUUID } from 'node:crypto';\n\nimport { ErrorCode, FsError } from './errors.ts';\nimport { MIB } from './util.ts';\n\n// ─── TTL + LRU map ───────────────────────────────────────────────────────────\n\n/**\n * A bounded, expiring map. `Map` keeps insertion order and a read re-inserts,\n * so the first key is always the least recently used one. Entries carry an\n * optional weight so a byte budget can be enforced next to the entry cap.\n */\nclass TtlLru<V> {\n readonly #byKey = new Map<string, { value: V; expiresAt: number; weight: number }>();\n readonly #maxEntries: number;\n readonly #maxWeight: number;\n readonly #ttlMs: number;\n readonly #now: () => number;\n #weight = 0;\n\n constructor(opts: { maxEntries: number; ttlMs: number; maxWeight?: number; now?: () => number }) {\n this.#maxEntries = opts.maxEntries;\n this.#maxWeight = opts.maxWeight ?? Number.POSITIVE_INFINITY;\n this.#ttlMs = opts.ttlMs;\n this.#now = opts.now ?? Date.now;\n }\n\n get size(): number {\n return this.#byKey.size;\n }\n\n /** Drop every expired entry; true when at least one went. */\n prune(): boolean {\n const now = this.#now();\n const before = this.#byKey.size;\n for (const [key, entry] of this.#byKey) {\n if (entry.expiresAt <= now) this.delete(key);\n }\n return this.#byKey.size !== before;\n }\n\n /** The live value, bumped to most recently used; `undefined` when missing or expired. */\n get(key: string): V | undefined {\n const entry = this.#byKey.get(key);\n if (!entry) return undefined;\n if (entry.expiresAt <= this.#now()) {\n this.delete(key);\n return undefined;\n }\n this.#byKey.delete(key);\n this.#byKey.set(key, entry);\n return entry.value;\n }\n\n /** Insert (pruning expired first) and evict least recently used entries past either cap. */\n set(key: string, value: V, weight = 0): void {\n this.prune();\n this.delete(key);\n this.#byKey.set(key, { value, expiresAt: this.#now() + this.#ttlMs, weight });\n this.#weight += weight;\n while (\n this.#byKey.size > 0 &&\n (this.#byKey.size > this.#maxEntries || this.#weight > this.#maxWeight)\n ) {\n const oldest = this.#byKey.keys().next();\n if (oldest.done) break;\n this.delete(oldest.value);\n }\n }\n\n delete(key: string): void {\n const entry = this.#byKey.get(key);\n if (!entry) return;\n this.#weight -= entry.weight;\n this.#byKey.delete(key);\n }\n\n /** Live keys, least recently used first. Prunes expired entries first. */\n keys(): string[] {\n this.prune();\n return [...this.#byKey.keys()];\n }\n\n clear(): void {\n this.#byKey.clear();\n this.#weight = 0;\n }\n}\n\n// ─── Resource store: externalized tool results ───────────────────────────────\n\ninterface ResourceEntry {\n uri: string;\n name: string;\n mimeType: string;\n size: number;\n expiresAt: string;\n text: string;\n}\n\nconst MAX_ENTRIES = 64;\nconst MAX_TOTAL_BYTES = 25 * MIB;\nconst MAX_ENTRY_BYTES = 10 * MIB;\nconst ENTRY_TTL_MS = 60 * 1000;\n\nexport class ResourceStore {\n readonly #entries = new TtlLru<ResourceEntry>({\n maxEntries: MAX_ENTRIES,\n maxWeight: MAX_TOTAL_BYTES,\n ttlMs: ENTRY_TTL_MS,\n });\n readonly #onListChanged: (() => void) | undefined;\n\n constructor(onListChanged?: () => void) {\n this.#onListChanged = onListChanged;\n }\n\n getEntry(uri: string): ResourceEntry {\n const entry = this.#entries.get(uri);\n if (entry) return entry;\n // Not found and expired share a remedy; only the wording differs, and an\n // expiry seen here changed the list a client may hold.\n if (this.#entries.prune()) this.#onListChanged?.();\n throw new FsError(\n ErrorCode.NOT_FOUND,\n `Resource not found: ${uri}. Re-run the tool to regenerate.`,\n );\n }\n\n putText(params: { name: string; mimeType?: string; text: string }): ResourceEntry {\n const entryBytes = Buffer.byteLength(params.text, 'utf8');\n if (entryBytes > MAX_ENTRY_BYTES) {\n if (this.#entries.prune()) this.#onListChanged?.();\n throw new FsError(ErrorCode.TOO_LARGE, `Resource too large to cache (${entryBytes} bytes).`);\n }\n const entry: ResourceEntry = {\n uri: `filesystem-mcp://result/${randomUUID()}`,\n name: params.name,\n mimeType: params.mimeType ?? 'text/plain',\n size: entryBytes,\n expiresAt: new Date(Date.now() + ENTRY_TTL_MS).toISOString(),\n text: params.text,\n };\n this.#entries.set(entry.uri, entry, entryBytes);\n this.#onListChanged?.();\n return entry;\n }\n\n /**\n * Live URIs, oldest first. Deliberately silent about the prune it runs:\n * this is the read path `resources/list` runs through, and the caller is\n * already receiving the post-prune list.\n */\n keys(): string[] {\n return this.#entries.keys();\n }\n}\n\nexport interface JsonResourceResult {\n entry: {\n uri: string;\n size: number;\n mimeType: string;\n expiresAt: string;\n };\n link: ContentBlock;\n}\n\n/**\n * Owner of the externalize-a-payload rule: a tool whose inline response is\n * truncated publishes the full value to the resource store as pretty-printed\n * JSON and hands back the URI to reach it by, plus the link block that offers\n * it to the user.\n */\nexport function putJsonResource(\n store: ResourceStore,\n name: string,\n // `Record<string, unknown> | readonly unknown[]`, not `unknown` or `object`:\n // JSON.stringify returns undefined for undefined and for functions, and `null`\n // is excluded so non-string payloads cannot enter the store's text entry.\n value: Record<string, unknown> | readonly unknown[],\n): JsonResourceResult {\n const entry = store.putText({\n name,\n mimeType: 'application/json',\n text: JSON.stringify(value, null, 2),\n });\n\n return {\n entry: {\n uri: entry.uri,\n size: entry.size,\n mimeType: entry.mimeType,\n expiresAt: entry.expiresAt,\n },\n link: {\n type: 'resource_link',\n uri: entry.uri,\n name: entry.name,\n mimeType: entry.mimeType,\n size: entry.size,\n annotations: { audience: ['user'] },\n },\n };\n}\n\n// ─── Page snapshots: paginated result sets ───────────────────────────────────\n\nexport interface PageSnapshot<T = unknown, M = unknown> {\n readonly items: readonly T[];\n readonly metadata: M;\n}\n\n/**\n * The one cursor rejection: a cursor whose snapshot expired, was evicted, was\n * already consumed past its end, or belongs to a different query. Every case\n * has the same remedy, so they share one message.\n */\nexport function invalidCursor(): FsError {\n return new FsError(\n ErrorCode.INVALID_INPUT,\n 'Invalid cursor. Request the first page without a cursor.',\n );\n}\n\n/**\n * Short-lived snapshots of a completed query's full result set, so later pages\n * slice a stored array instead of re-scanning and re-sorting the filesystem.\n *\n * ponytail: bounded by snapshot count and TTL, not by bytes — 32 x 20,000\n * `list` entries or 32 x 10,000 `search_text` matches, each match retaining up\n * to 20 more line strings at `context: 10`, order of a few hundred MB held for\n * 60s, and the HTTP leg shares one store so any caller can drive it. If that\n * shows up as memory pressure, pass a byte weight to `set` the way\n * `ResourceStore` does.\n */\nexport class PageSnapshotStore {\n readonly #byId: TtlLru<PageSnapshot & { queryKey: string }>;\n\n constructor(options: { maxSnapshots?: number; ttlMs?: number; now?: () => number } = {}) {\n this.#byId = new TtlLru({\n maxEntries: options.maxSnapshots ?? 32,\n ttlMs: options.ttlMs ?? 60 * 1000,\n ...(options.now ? { now: options.now } : {}),\n });\n }\n\n create(params: { queryKey: string; items: readonly unknown[]; metadata?: unknown }): string {\n const snapshotId = randomUUID();\n this.#byId.set(snapshotId, {\n queryKey: params.queryKey,\n items: params.items,\n metadata: params.metadata,\n });\n return snapshotId;\n }\n\n read<T, M = undefined>(snapshotId: string, queryKey: string): PageSnapshot<T, M> {\n const entry = this.#byId.get(snapshotId);\n if (entry?.queryKey !== queryKey) throw invalidCursor();\n return { items: entry.items as readonly T[], metadata: entry.metadata as M };\n }\n\n clear(): void {\n this.#byId.clear();\n }\n}\n"]}
|
package/dist/core/util.d.ts
CHANGED
|
@@ -10,10 +10,11 @@ export declare function splitCsvList(value: string | undefined): string[];
|
|
|
10
10
|
export declare const PARALLEL_CONCURRENCY: number;
|
|
11
11
|
export declare const ROOTS_TIMEOUT_MS = 5000;
|
|
12
12
|
export declare function getMaxTextFileSize(): number;
|
|
13
|
-
|
|
13
|
+
/** Combined byte budget for one batched `read`. */
|
|
14
|
+
export declare const READ_MANY_MAX_TOTAL_BYTES: number;
|
|
14
15
|
/** Default line chunk size for read continuation when no explicit range was given. */
|
|
15
16
|
export declare const DEFAULT_CONTINUATION_CHUNK_SIZE = 200;
|
|
16
|
-
export declare const DEFAULT_SEARCH_TIMEOUT_MS
|
|
17
|
+
export declare const DEFAULT_SEARCH_TIMEOUT_MS = 5000;
|
|
17
18
|
export declare const MAX_TREE_DEPTH = 50;
|
|
18
19
|
export declare const DEFAULT_TREE_ENTRIES = 1000;
|
|
19
20
|
export declare const MAX_LIST_ENTRIES = 20000;
|
package/dist/core/util.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/core/util.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,GAAG,QAAa,CAAC;AAE9B,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,MAAM,CAER;AA6BD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAKhE;AAED,eAAO,MAAM,oBAAoB,QAAoD,CAAC;AAEtF,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,wBAAgB,kBAAkB,IAAI,MAAM,CAQ3C;AAED,
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/core/util.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,GAAG,QAAa,CAAC;AAE9B,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,MAAM,CAER;AA6BD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAKhE;AAED,eAAO,MAAM,oBAAoB,QAAoD,CAAC;AAEtF,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,wBAAgB,kBAAkB,IAAI,MAAM,CAQ3C;AAED,mDAAmD;AACnD,eAAO,MAAM,yBAAyB,QAAY,CAAC;AAEnD,sFAAsF;AACtF,eAAO,MAAM,+BAA+B,MAAM,CAAC;AAEnD,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,eAAO,MAAM,gBAAgB,QAAQ,CAAC;AAEtC,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AACxC,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,eAAO,MAAM,8BAA8B,MAAM,CAAC"}
|
package/dist/core/util.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { availableParallelism } from 'node:os';
|
|
2
|
-
import { cli } from
|
|
3
|
-
import { warnInvalidSetting } from
|
|
2
|
+
import { cli } from "./config.js";
|
|
3
|
+
import { warnInvalidSetting } from "./path-utils.js";
|
|
4
4
|
const KIB = 1024;
|
|
5
5
|
export const MIB = 1024 * KIB;
|
|
6
6
|
export function parseEnvInt(envVar, defaultValue, min, max) {
|
|
@@ -40,12 +40,11 @@ export const ROOTS_TIMEOUT_MS = 5000;
|
|
|
40
40
|
export function getMaxTextFileSize() {
|
|
41
41
|
return parseIntSetting('FS_MAX_FILE_SIZE', cli.maxFileSize ?? process.env['FS_MAX_FILE_SIZE'], 10 * MIB, MIB, 100 * MIB);
|
|
42
42
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
43
|
+
/** Combined byte budget for one batched `read`. */
|
|
44
|
+
export const READ_MANY_MAX_TOTAL_BYTES = 512 * KIB;
|
|
46
45
|
/** Default line chunk size for read continuation when no explicit range was given. */
|
|
47
46
|
export const DEFAULT_CONTINUATION_CHUNK_SIZE = 200;
|
|
48
|
-
export const DEFAULT_SEARCH_TIMEOUT_MS =
|
|
47
|
+
export const DEFAULT_SEARCH_TIMEOUT_MS = 5000;
|
|
49
48
|
export const MAX_TREE_DEPTH = 50;
|
|
50
49
|
export const DEFAULT_TREE_ENTRIES = 1000;
|
|
51
50
|
export const MAX_LIST_ENTRIES = 20000;
|
package/dist/core/util.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/core/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,GAAG,GAAG,IAAI,CAAC;AACjB,MAAM,CAAC,MAAM,GAAG,GAAG,IAAI,GAAG,GAAG,CAAC;AAE9B,MAAM,UAAU,WAAW,CACzB,MAAc,EACd,YAAoB,EACpB,GAAW,EACX,GAAW;IAEX,OAAO,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAC9E,CAAC;AAED,uFAAuF;AACvF,SAAS,eAAe,CACtB,IAAY,EACZ,KAAyB,EACzB,YAAoB,EACpB,GAAW,EACX,GAAW;IAEX,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/B,IACE,OAAO,KAAK,EAAE;QACd,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;QACpB,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;QACzB,MAAM,GAAG,GAAG;QACZ,MAAM,GAAG,GAAG,EACZ,CAAC;QACD,kBAAkB,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;QAC/E,OAAO,YAAY,CAAC;IACtB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,KAAyB;IACpD,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;SACjB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;SAC5B,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAEtF,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAErC,MAAM,UAAU,kBAAkB;IAChC,OAAO,eAAe,CACpB,kBAAkB,EAClB,GAAG,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,EAClD,EAAE,GAAG,GAAG,EACR,GAAG,EACH,GAAG,GAAG,GAAG,CACV,CAAC;AACJ,CAAC;AAED,
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/core/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,GAAG,GAAG,IAAI,CAAC;AACjB,MAAM,CAAC,MAAM,GAAG,GAAG,IAAI,GAAG,GAAG,CAAC;AAE9B,MAAM,UAAU,WAAW,CACzB,MAAc,EACd,YAAoB,EACpB,GAAW,EACX,GAAW;IAEX,OAAO,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAC9E,CAAC;AAED,uFAAuF;AACvF,SAAS,eAAe,CACtB,IAAY,EACZ,KAAyB,EACzB,YAAoB,EACpB,GAAW,EACX,GAAW;IAEX,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/B,IACE,OAAO,KAAK,EAAE;QACd,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;QACpB,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;QACzB,MAAM,GAAG,GAAG;QACZ,MAAM,GAAG,GAAG,EACZ,CAAC;QACD,kBAAkB,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;QAC/E,OAAO,YAAY,CAAC;IACtB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,KAAyB;IACpD,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;SACjB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;SAC5B,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAEtF,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAErC,MAAM,UAAU,kBAAkB;IAChC,OAAO,eAAe,CACpB,kBAAkB,EAClB,GAAG,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,EAClD,EAAE,GAAG,GAAG,EACR,GAAG,EACH,GAAG,GAAG,GAAG,CACV,CAAC;AACJ,CAAC;AAED,mDAAmD;AACnD,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,GAAG,GAAG,CAAC;AAEnD,sFAAsF;AACtF,MAAM,CAAC,MAAM,+BAA+B,GAAG,GAAG,CAAC;AAEnD,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,CAAC;AAE9C,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,CAAC;AACjC,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAEzC,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAEtC,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,CAAC;AACxC,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAC1C,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAEpC,MAAM,CAAC,MAAM,8BAA8B,GAAG,GAAG,CAAC","sourcesContent":["import { availableParallelism } from 'node:os';\n\nimport { cli } from './config.ts';\nimport { warnInvalidSetting } from './path-utils.ts';\n\nconst KIB = 1024;\nexport const MIB = 1024 * KIB;\n\nexport function parseEnvInt(\n envVar: string,\n defaultValue: number,\n min: number,\n max: number,\n): number {\n return parseIntSetting(envVar, process.env[envVar], defaultValue, min, max);\n}\n\n/** Validate a raw integer setting (env var or CLI override) with a logged fallback. */\nfunction parseIntSetting(\n name: string,\n value: string | undefined,\n defaultValue: number,\n min: number,\n max: number,\n): number {\n if (!value) {\n return defaultValue;\n }\n\n const trimmed = value.trim();\n const parsed = Number(trimmed);\n if (\n trimmed === '' ||\n Number.isNaN(parsed) ||\n !Number.isInteger(parsed) ||\n parsed < min ||\n parsed > max\n ) {\n warnInvalidSetting(name, value, `${String(min)}-${String(max)}`, defaultValue);\n return defaultValue;\n }\n return parsed;\n}\n\n/**\n * Split a comma-separated config value into trimmed, non-empty entries. Empty\n * entries are dropped so \",\" or \" \" reads as unset rather than as a list of\n * blanks. Every comma-separated env var this server accepts parses through\n * here, so they all agree on what \"configured\" means.\n */\nexport function splitCsvList(value: string | undefined): string[] {\n return (value ?? '')\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean);\n}\n\nexport const PARALLEL_CONCURRENCY = Math.min(Math.max(availableParallelism(), 4), 32);\n\nexport const ROOTS_TIMEOUT_MS = 5000;\n\nexport function getMaxTextFileSize(): number {\n return parseIntSetting(\n 'FS_MAX_FILE_SIZE',\n cli.maxFileSize ?? process.env['FS_MAX_FILE_SIZE'],\n 10 * MIB,\n MIB,\n 100 * MIB,\n );\n}\n\n/** Combined byte budget for one batched `read`. */\nexport const READ_MANY_MAX_TOTAL_BYTES = 512 * KIB;\n\n/** Default line chunk size for read continuation when no explicit range was given. */\nexport const DEFAULT_CONTINUATION_CHUNK_SIZE = 200;\n\nexport const DEFAULT_SEARCH_TIMEOUT_MS = 5000;\n\nexport const MAX_TREE_DEPTH = 50;\nexport const DEFAULT_TREE_ENTRIES = 1000;\n\nexport const MAX_LIST_ENTRIES = 20000;\n\nexport const MAX_SEARCH_RESULTS = 10000;\nexport const DEFAULT_SEARCH_RESULTS = 100;\nexport const MAX_SEARCH_DEPTH = 100;\n\nexport const DEFAULT_SEARCH_CONTENT_RESULTS = 500;\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watcher-registry.d.ts","sourceRoot":"","sources":["../../src/core/watcher-registry.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAK3C,eAAO,MAAM,YAAY,QAA+C,CAAC;AAMzE;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,eAAe,CAAA;CAAE,GACvE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE1D;;;;;;;GAOG;AACH,wBAAgB,qBAAqB;sBAiGV,MAAM,KAAG,OAAO;
|
|
1
|
+
{"version":3,"file":"watcher-registry.d.ts","sourceRoot":"","sources":["../../src/core/watcher-registry.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAK3C,eAAO,MAAM,YAAY,QAA+C,CAAC;AAMzE;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,eAAe,CAAA;CAAE,GACvE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE1D;;;;;;;GAOG;AACH,wBAAgB,qBAAqB;sBAiGV,MAAM,KAAG,OAAO;IA4FvC,wFAAwF;gBAC9E,MAAM;mBA7CI,MAAM,KAAG,IAAI;IAiDjC;;;;;;;;;;;;;;;;;;;;;;OAsBG;uBAEU,SAAS,OACf,MAAM,UACH,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,sBACF;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GACrD,OAAO,CAAC,mBAAmB,CAAC;eA6DpB,IAAI;EAmBlB;AAED,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { statSync, watch } from 'node:fs';
|
|
2
|
-
import { formatUnknownErrorMessage } from
|
|
3
|
-
import { extractPath } from
|
|
4
|
-
import { Logger } from
|
|
5
|
-
import { parseEnvInt } from
|
|
2
|
+
import { formatUnknownErrorMessage } from "./errors.js";
|
|
3
|
+
import { extractPath } from "./file-uri.js";
|
|
4
|
+
import { Logger } from "./observability.js";
|
|
5
|
+
import { parseEnvInt } from "./util.js";
|
|
6
6
|
// Cap concurrent file watchers to avoid exhausting OS-level watch handles
|
|
7
7
|
// (e.g. Linux inotify, default ~8192/user). One subscription == one watcher.
|
|
8
8
|
export const MAX_WATCHERS = parseEnvInt('FS_MAX_WATCHERS', 256, 1, 4096);
|
|
@@ -142,7 +142,11 @@ export function createWatcherRegistry() {
|
|
|
142
142
|
activeCallbacks.set(uri, callbacks);
|
|
143
143
|
}
|
|
144
144
|
callbacks.add(notify);
|
|
145
|
-
|
|
145
|
+
// A live subscription needs no marker: every reader treats "no entry" the
|
|
146
|
+
// way it treated 'subscribed'. Clearing here is what retires the
|
|
147
|
+
// 'subscribing' marker `startSubscribe` set, so teardown deletes the entry
|
|
148
|
+
// instead of mapping it onto a 'unsubscribed' that would block a later attach.
|
|
149
|
+
desiredState.delete(uri);
|
|
146
150
|
};
|
|
147
151
|
const retain = (uri) => {
|
|
148
152
|
subscriberCounts.set(uri, (subscriberCounts.get(uri) ?? 0) + 1);
|