@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":"watcher-registry.js","sourceRoot":"","sources":["../../src/core/watcher-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FSWatcher } from 'node:fs';\nimport { statSync, watch } from 'node:fs';\n\nimport { formatUnknownErrorMessage } from './errors.js';\nimport { extractPath } from './file-uri.js';\nimport { Logger } from './observability.js';\nimport type { PathGuard } from './path.js';\nimport { parseEnvInt } from './util.js';\n\n// Cap concurrent file watchers to avoid exhausting OS-level watch handles\n// (e.g. Linux inotify, default ~8192/user). One subscription == one watcher.\nexport const MAX_WATCHERS = parseEnvInt('FS_MAX_WATCHERS', 256, 1, 4096);\n\nfunction warnWatcherCap(uri: string): void {\n Logger.warn(`Cannot subscribe to ${uri}: MAX_WATCHERS limit (${MAX_WATCHERS}) reached.`);\n}\n\n/**\n * `ok` means a watcher is live for this uri and `notify` is registered. Every\n * other outcome names why, and only `invalid-path` carries what\n * `validateExistingPath` threw — so `error` is unreachable on a branch that has\n * none.\n */\nexport type WatcherAttachResult =\n | { ok: true }\n | { ok: false; reason: 'stale' | 'capped' | 'bad-uri' | 'attach-failed' }\n | { ok: false; reason: 'invalid-path'; error: unknown };\n\n/**\n * Owns the uri → FSWatcher map and the subscription bookkeeping around it:\n * notify callbacks, desired subscribe/unsubscribe state, and the watcher cap —\n * plus `acquire`, the one ladder that sequences them correctly. `acquire` awaits\n * path validation midway and re-checks `isStale` and `hasWatcher` itself\n * afterwards, so callers do not: they take a lease and release it. The\n * individual primitives stay closure-private.\n */\nexport function createWatcherRegistry() {\n const watchers = new Map<string, FSWatcher>();\n // A URI may have several notification sinks and several independent leases.\n // Callback identity and lifetime are intentionally separate: the HTTP leg\n // uses one stable bus-publishing callback for every listen stream, while each\n // stream still owns one lease that must be released on close.\n // ponytail: no per-subscription id in the SDK unsubscribe contract, so we\n // ref-count by URI. A departed direct-notification callback can linger until\n // the final lease ends; sends to a closed transport already fail harmlessly.\n // The count outlives the watcher (see `dropWatcher`), so a URI whose watcher\n // errored keeps one map entry per still-unreleased lease until they drain —\n // no watcher slot, just bookkeeping. Per-subscription-id keying retires both\n // this and the unsubscribe-by-URI over-release if that churn is observed.\n const activeCallbacks = new Map<string, Set<(uri: string) => void>>();\n const subscriberCounts = new Map<string, number>();\n const desiredState = new Map<string, 'subscribed' | 'unsubscribed' | 'subscribing'>();\n const debounceTimers = new Map<string, NodeJS.Timeout>();\n let destroyed = false;\n\n // 'unsubscribed' exists only to abort a subscribe that is mid-await\n // (isStale). If no subscribe is in flight, keep no entry at all: a lingering\n // 'unsubscribed' would permanently block the modern attach path (which never\n // calls startSubscribe) and leak one map entry per URI ever watched. This\n // must be idempotent: remove()'s teardown branch calls this directly and\n // then unconditionally calls dropWatcher (which also calls this) for the\n // same uri — the second call must not clobber what the first one set.\n const settleDesiredState = (uri: string): void => {\n const current = desiredState.get(uri);\n if (current === 'subscribing') {\n desiredState.set(uri, 'unsubscribed');\n } else if (current !== 'unsubscribed') {\n desiredState.delete(uri);\n }\n };\n\n // Tears down the watcher itself, NOT the ref-count. The 'error' event calls\n // this with leases still outstanding: clearing the count there would let the\n // next release from one of those leases decrement from zero and drop a\n // watcher a later subscriber re-established for the same URI. `drop` below is\n // the only path that ends leases, and it is the only one that clears them.\n const dropWatcher = (uri: string, watcher: FSWatcher): void => {\n const current = watchers.get(uri);\n if (current !== watcher) return;\n const timer = debounceTimers.get(uri);\n if (timer) {\n clearTimeout(timer);\n debounceTimers.delete(uri);\n }\n watcher.close();\n watchers.delete(uri);\n activeCallbacks.delete(uri);\n settleDesiredState(uri);\n };\n\n /** The last lease ended: drop the watcher and every trace of the URI. */\n const drop = (uri: string): void => {\n const timer = debounceTimers.get(uri);\n if (timer) {\n clearTimeout(timer);\n debounceTimers.delete(uri);\n }\n const watcher = watchers.get(uri);\n if (watcher) dropWatcher(uri, watcher);\n activeCallbacks.delete(uri);\n subscriberCounts.delete(uri);\n settleDesiredState(uri);\n };\n\n const notifyAll = (uri: string): void => {\n const callbacks = activeCallbacks.get(uri);\n if (!callbacks || callbacks.size === 0) return;\n const existing = debounceTimers.get(uri);\n if (existing) {\n clearTimeout(existing);\n }\n const timer = setTimeout(() => {\n debounceTimers.delete(uri);\n const cbs = activeCallbacks.get(uri);\n if (!cbs) return;\n // One debounce timer per URI — fan out to every subscriber inside it, so\n // a burst of changes still fires one notification round per URI, not per\n // subscriber.\n for (const cb of cbs) {\n try {\n cb(uri);\n } catch (err) {\n Logger.warn(`Notify callback error for ${uri}: ${formatUnknownErrorMessage(err)}`);\n }\n }\n }, 50);\n timer.unref();\n debounceTimers.set(uri, timer);\n };\n\n // The primitives below are hoisted out of the returned literal so `acquire`\n // can sequence them from inside the closure. They are closure-private.\n\n const hasWatcher = (uri: string): boolean => watchers.has(uri);\n\n const isAtCap = (): boolean => watchers.size >= MAX_WATCHERS;\n\n /** The registry was destroyed, or this uri was unsubscribed, mid-await. */\n const isStale = (uri: string): boolean => destroyed || desiredState.get(uri) === 'unsubscribed';\n\n const startSubscribe = (uri: string): void => {\n desiredState.set(uri, 'subscribing');\n };\n\n /**\n * A subscribe that declared intent and then failed — bad URI, unvalidatable\n * path, cap, fs.watch refusal — drops the entry entirely. Not\n * `settleDesiredState`: that maps 'subscribing' onto 'unsubscribed' to abort\n * an attach still mid-await, and a *finished* failure leaving 'unsubscribed'\n * behind would make `isStale` true forever, so the modern attach path (which\n * never calls `startSubscribe`) could never watch this uri again. Only\n * clears its own 'subscribing' — a concurrent unsubscribe that already set\n * 'unsubscribed' still wins.\n */\n const cancelSubscribe = (uri: string): void => {\n if (desiredState.get(uri) === 'subscribing') desiredState.delete(uri);\n };\n\n const addCallback = (uri: string, notify: (uri: string) => void): void => {\n let callbacks = activeCallbacks.get(uri);\n if (!callbacks) {\n callbacks = new Set();\n activeCallbacks.set(uri, callbacks);\n }\n callbacks.add(notify);\n desiredState.set(uri, 'subscribed');\n };\n\n const retain = (uri: string): void => {\n subscriberCounts.set(uri, (subscriberCounts.get(uri) ?? 0) + 1);\n };\n\n /**\n * Ref-count by URI: only tear down the shared watcher when the last lease\n * ends. Callback identity is independent from this count. A release with no\n * lease outstanding (a rolled-back attach) drops the watcher outright.\n */\n const release = (uri: string): void => {\n const remaining = (subscriberCounts.get(uri) ?? 0) - 1;\n if (remaining > 0) {\n subscriberCounts.set(uri, remaining);\n return;\n }\n drop(uri);\n };\n\n const attach = (uri: string, resolvedPath: string): boolean => {\n try {\n // Watch directories recursively (children included) and files as-is.\n // `fs.watch` async errors arrive via the 'error' event below, not as a\n // sync throw, so no recursive-fallback try/catch is needed here — the\n // outer catch handles sync throws (inotify exhaustion, path-race).\n // `{ recursive: true }` is honored on macOS, Windows, and — since Node\n // 20.13 — Linux; `engines.node` is >=24, so all three are covered.\n const recursive = statSync(resolvedPath).isDirectory();\n const watcher = watch(resolvedPath, recursive ? { recursive: true } : undefined, () => {\n notifyAll(uri);\n });\n watcher.on('error', (err: Error) => {\n Logger.warn(`Watcher error for ${uri}: ${err.message}`);\n dropWatcher(uri, watcher);\n });\n // Two attaches that both cleared `hasWatcher` before either finished\n // validating land here for the same uri. Keep the one already wired to\n // the callback set and close this one — overwriting the map entry would\n // strand the first watcher's fd with nothing left holding a reference.\n if (watchers.has(uri)) {\n watcher.close();\n return true;\n }\n watchers.set(uri, watcher);\n return true;\n } catch (err) {\n Logger.error(`Failed to create watcher for ${uri}: ${formatUnknownErrorMessage(err)}`);\n return false;\n }\n };\n\n return {\n hasWatcher,\n\n /** Live watcher count, for pre-checking a batched listen against remaining capacity. */\n size: (): number => watchers.size,\n\n release,\n\n /**\n * The one attach ladder both watcher entry points run: `resources/subscribe`\n * (2025 era) and the `subscriptions/listen` filter (modern era, HTTP and\n * stdio). Never throws — it reports the outcome and lets each caller decide\n * what that is worth: subscribe owes its caller a precise error, and listen\n * (`prepareListenWatchers`) treats the batch as all-or-nothing, releasing\n * every lease it already took and rejecting the request. Idempotent per URI\n * — a second call for an already-watched URI re-registers the notify\n * callback (one watcher per URI).\n *\n * `markSubscribe` is the one branch that differs: only `resources/subscribe`\n * declares desired state (what `isStale` aborts against). The listen path\n * must not, or a rejected attach past that point strands a `'subscribing'`\n * entry nothing settles. Whoever declares it, this function settles it:\n * every failing exit past that point cancels the declaration, so no uri is\n * poisoned for a later attach.\n *\n * Every `ok` return takes one lease; lifetime is the caller's to manage.\n * HTTP releases when the listen response closes. Stdio tracks the listen\n * request ID and releases on cancellation, SDK rejection, or graceful\n * completion. In both legs the ref-count is by URI, so a watcher another\n * stream still holds survives.\n */\n async acquire(\n pathGuard: PathGuard,\n uri: string,\n notify: (uri: string) => void,\n { markSubscribe = false }: { markSubscribe?: boolean } = {},\n ): Promise<WatcherAttachResult> {\n // Every failing exit below routes through here, so the declaration made by\n // `startSubscribe` can never outlive the attach that made it.\n const fail = (result: WatcherAttachResult & { ok: false }): WatcherAttachResult => {\n if (markSubscribe) cancelSubscribe(uri);\n return result;\n };\n\n if (hasWatcher(uri)) {\n // A watcher already tracks this uri; just (re)register the callback so\n // its change events reach the new subscriber. No validation or cap work\n // is needed for an already-live watcher.\n addCallback(uri, notify);\n retain(uri);\n return { ok: true };\n }\n // A cap hit before validation and one found after the await are the same\n // condition, and both are reported the same way.\n if (isAtCap()) {\n warnWatcherCap(uri);\n return { ok: false, reason: 'capped' };\n }\n\n if (markSubscribe) startSubscribe(uri);\n\n const filePath = extractPath(uri);\n if (!filePath) return fail({ ok: false, reason: 'bad-uri' });\n\n let resolved: string;\n try {\n resolved = await pathGuard.validateExistingPath(filePath);\n } catch (error: unknown) {\n return fail({ ok: false, reason: 'invalid-path', error });\n }\n\n // Re-check what the await could have changed. A stale uri is the one\n // failure that must NOT cancel: an unsubscribe landed mid-await and its\n // 'unsubscribed' marker is the thing that aborted this attach.\n if (isStale(uri)) return { ok: false, reason: 'stale' };\n if (hasWatcher(uri)) {\n addCallback(uri, notify);\n retain(uri);\n return { ok: true };\n }\n if (isAtCap()) {\n warnWatcherCap(uri);\n return fail({ ok: false, reason: 'capped' });\n }\n\n addCallback(uri, notify);\n if (!attach(uri, resolved)) {\n // fs.watch threw (inotify exhaustion, or a race deleted the path): roll\n // back so no dangling callback is left believing a watcher exists. No\n // lease was taken yet, so `release` drops the entry outright.\n release(uri);\n return fail({ ok: false, reason: 'attach-failed' });\n }\n retain(uri);\n return { ok: true };\n },\n\n destroy(): void {\n destroyed = true;\n for (const timer of debounceTimers.values()) {\n clearTimeout(timer);\n }\n debounceTimers.clear();\n for (const watcher of watchers.values()) {\n try {\n watcher.close();\n } catch {\n /* ignore close errors so all watchers are attempted */\n }\n }\n watchers.clear();\n activeCallbacks.clear();\n subscriberCounts.clear();\n desiredState.clear();\n },\n };\n}\n\nexport type WatcherRegistry = ReturnType<typeof createWatcherRegistry>;\n"]}
|
|
1
|
+
{"version":3,"file":"watcher-registry.js","sourceRoot":"","sources":["../../src/core/watcher-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FSWatcher } from 'node:fs';\nimport { statSync, watch } from 'node:fs';\n\nimport { formatUnknownErrorMessage } from './errors.ts';\nimport { extractPath } from './file-uri.ts';\nimport { Logger } from './observability.ts';\nimport type { PathGuard } from './path.ts';\nimport { parseEnvInt } from './util.ts';\n\n// Cap concurrent file watchers to avoid exhausting OS-level watch handles\n// (e.g. Linux inotify, default ~8192/user). One subscription == one watcher.\nexport const MAX_WATCHERS = parseEnvInt('FS_MAX_WATCHERS', 256, 1, 4096);\n\nfunction warnWatcherCap(uri: string): void {\n Logger.warn(`Cannot subscribe to ${uri}: MAX_WATCHERS limit (${MAX_WATCHERS}) reached.`);\n}\n\n/**\n * `ok` means a watcher is live for this uri and `notify` is registered. Every\n * other outcome names why, and only `invalid-path` carries what\n * `validateExistingPath` threw — so `error` is unreachable on a branch that has\n * none.\n */\nexport type WatcherAttachResult =\n | { ok: true }\n | { ok: false; reason: 'stale' | 'capped' | 'bad-uri' | 'attach-failed' }\n | { ok: false; reason: 'invalid-path'; error: unknown };\n\n/**\n * Owns the uri → FSWatcher map and the subscription bookkeeping around it:\n * notify callbacks, desired subscribe/unsubscribe state, and the watcher cap —\n * plus `acquire`, the one ladder that sequences them correctly. `acquire` awaits\n * path validation midway and re-checks `isStale` and `hasWatcher` itself\n * afterwards, so callers do not: they take a lease and release it. The\n * individual primitives stay closure-private.\n */\nexport function createWatcherRegistry() {\n const watchers = new Map<string, FSWatcher>();\n // A URI may have several notification sinks and several independent leases.\n // Callback identity and lifetime are intentionally separate: the HTTP leg\n // uses one stable bus-publishing callback for every listen stream, while each\n // stream still owns one lease that must be released on close.\n // ponytail: no per-subscription id in the SDK unsubscribe contract, so we\n // ref-count by URI. A departed direct-notification callback can linger until\n // the final lease ends; sends to a closed transport already fail harmlessly.\n // The count outlives the watcher (see `dropWatcher`), so a URI whose watcher\n // errored keeps one map entry per still-unreleased lease until they drain —\n // no watcher slot, just bookkeeping. Per-subscription-id keying retires both\n // this and the unsubscribe-by-URI over-release if that churn is observed.\n const activeCallbacks = new Map<string, Set<(uri: string) => void>>();\n const subscriberCounts = new Map<string, number>();\n const desiredState = new Map<string, 'unsubscribed' | 'subscribing'>();\n const debounceTimers = new Map<string, NodeJS.Timeout>();\n let destroyed = false;\n\n // 'unsubscribed' exists only to abort a subscribe that is mid-await\n // (isStale). If no subscribe is in flight, keep no entry at all: a lingering\n // 'unsubscribed' would permanently block the modern attach path (which never\n // calls startSubscribe) and leak one map entry per URI ever watched. This\n // must be idempotent: remove()'s teardown branch calls this directly and\n // then unconditionally calls dropWatcher (which also calls this) for the\n // same uri — the second call must not clobber what the first one set.\n const settleDesiredState = (uri: string): void => {\n const current = desiredState.get(uri);\n if (current === 'subscribing') {\n desiredState.set(uri, 'unsubscribed');\n } else if (current !== 'unsubscribed') {\n desiredState.delete(uri);\n }\n };\n\n // Tears down the watcher itself, NOT the ref-count. The 'error' event calls\n // this with leases still outstanding: clearing the count there would let the\n // next release from one of those leases decrement from zero and drop a\n // watcher a later subscriber re-established for the same URI. `drop` below is\n // the only path that ends leases, and it is the only one that clears them.\n const dropWatcher = (uri: string, watcher: FSWatcher): void => {\n const current = watchers.get(uri);\n if (current !== watcher) return;\n const timer = debounceTimers.get(uri);\n if (timer) {\n clearTimeout(timer);\n debounceTimers.delete(uri);\n }\n watcher.close();\n watchers.delete(uri);\n activeCallbacks.delete(uri);\n settleDesiredState(uri);\n };\n\n /** The last lease ended: drop the watcher and every trace of the URI. */\n const drop = (uri: string): void => {\n const timer = debounceTimers.get(uri);\n if (timer) {\n clearTimeout(timer);\n debounceTimers.delete(uri);\n }\n const watcher = watchers.get(uri);\n if (watcher) dropWatcher(uri, watcher);\n activeCallbacks.delete(uri);\n subscriberCounts.delete(uri);\n settleDesiredState(uri);\n };\n\n const notifyAll = (uri: string): void => {\n const callbacks = activeCallbacks.get(uri);\n if (!callbacks || callbacks.size === 0) return;\n const existing = debounceTimers.get(uri);\n if (existing) {\n clearTimeout(existing);\n }\n const timer = setTimeout(() => {\n debounceTimers.delete(uri);\n const cbs = activeCallbacks.get(uri);\n if (!cbs) return;\n // One debounce timer per URI — fan out to every subscriber inside it, so\n // a burst of changes still fires one notification round per URI, not per\n // subscriber.\n for (const cb of cbs) {\n try {\n cb(uri);\n } catch (err) {\n Logger.warn(`Notify callback error for ${uri}: ${formatUnknownErrorMessage(err)}`);\n }\n }\n }, 50);\n timer.unref();\n debounceTimers.set(uri, timer);\n };\n\n // The primitives below are hoisted out of the returned literal so `acquire`\n // can sequence them from inside the closure. They are closure-private.\n\n const hasWatcher = (uri: string): boolean => watchers.has(uri);\n\n const isAtCap = (): boolean => watchers.size >= MAX_WATCHERS;\n\n /** The registry was destroyed, or this uri was unsubscribed, mid-await. */\n const isStale = (uri: string): boolean => destroyed || desiredState.get(uri) === 'unsubscribed';\n\n const startSubscribe = (uri: string): void => {\n desiredState.set(uri, 'subscribing');\n };\n\n /**\n * A subscribe that declared intent and then failed — bad URI, unvalidatable\n * path, cap, fs.watch refusal — drops the entry entirely. Not\n * `settleDesiredState`: that maps 'subscribing' onto 'unsubscribed' to abort\n * an attach still mid-await, and a *finished* failure leaving 'unsubscribed'\n * behind would make `isStale` true forever, so the modern attach path (which\n * never calls `startSubscribe`) could never watch this uri again. Only\n * clears its own 'subscribing' — a concurrent unsubscribe that already set\n * 'unsubscribed' still wins.\n */\n const cancelSubscribe = (uri: string): void => {\n if (desiredState.get(uri) === 'subscribing') desiredState.delete(uri);\n };\n\n const addCallback = (uri: string, notify: (uri: string) => void): void => {\n let callbacks = activeCallbacks.get(uri);\n if (!callbacks) {\n callbacks = new Set();\n activeCallbacks.set(uri, callbacks);\n }\n callbacks.add(notify);\n // A live subscription needs no marker: every reader treats \"no entry\" the\n // way it treated 'subscribed'. Clearing here is what retires the\n // 'subscribing' marker `startSubscribe` set, so teardown deletes the entry\n // instead of mapping it onto a 'unsubscribed' that would block a later attach.\n desiredState.delete(uri);\n };\n\n const retain = (uri: string): void => {\n subscriberCounts.set(uri, (subscriberCounts.get(uri) ?? 0) + 1);\n };\n\n /**\n * Ref-count by URI: only tear down the shared watcher when the last lease\n * ends. Callback identity is independent from this count. A release with no\n * lease outstanding (a rolled-back attach) drops the watcher outright.\n */\n const release = (uri: string): void => {\n const remaining = (subscriberCounts.get(uri) ?? 0) - 1;\n if (remaining > 0) {\n subscriberCounts.set(uri, remaining);\n return;\n }\n drop(uri);\n };\n\n const attach = (uri: string, resolvedPath: string): boolean => {\n try {\n // Watch directories recursively (children included) and files as-is.\n // `fs.watch` async errors arrive via the 'error' event below, not as a\n // sync throw, so no recursive-fallback try/catch is needed here — the\n // outer catch handles sync throws (inotify exhaustion, path-race).\n // `{ recursive: true }` is honored on macOS, Windows, and — since Node\n // 20.13 — Linux; `engines.node` is >=24, so all three are covered.\n const recursive = statSync(resolvedPath).isDirectory();\n const watcher = watch(resolvedPath, recursive ? { recursive: true } : undefined, () => {\n notifyAll(uri);\n });\n watcher.on('error', (err: Error) => {\n Logger.warn(`Watcher error for ${uri}: ${err.message}`);\n dropWatcher(uri, watcher);\n });\n // Two attaches that both cleared `hasWatcher` before either finished\n // validating land here for the same uri. Keep the one already wired to\n // the callback set and close this one — overwriting the map entry would\n // strand the first watcher's fd with nothing left holding a reference.\n if (watchers.has(uri)) {\n watcher.close();\n return true;\n }\n watchers.set(uri, watcher);\n return true;\n } catch (err) {\n Logger.error(`Failed to create watcher for ${uri}: ${formatUnknownErrorMessage(err)}`);\n return false;\n }\n };\n\n return {\n hasWatcher,\n\n /** Live watcher count, for pre-checking a batched listen against remaining capacity. */\n size: (): number => watchers.size,\n\n release,\n\n /**\n * The one attach ladder both watcher entry points run: `resources/subscribe`\n * (2025 era) and the `subscriptions/listen` filter (modern era, HTTP and\n * stdio). Never throws — it reports the outcome and lets each caller decide\n * what that is worth: subscribe owes its caller a precise error, and listen\n * (`prepareListenWatchers`) treats the batch as all-or-nothing, releasing\n * every lease it already took and rejecting the request. Idempotent per URI\n * — a second call for an already-watched URI re-registers the notify\n * callback (one watcher per URI).\n *\n * `markSubscribe` is the one branch that differs: only `resources/subscribe`\n * declares desired state (what `isStale` aborts against). The listen path\n * must not, or a rejected attach past that point strands a `'subscribing'`\n * entry nothing settles. Whoever declares it, this function settles it:\n * every failing exit past that point cancels the declaration, so no uri is\n * poisoned for a later attach.\n *\n * Every `ok` return takes one lease; lifetime is the caller's to manage.\n * HTTP releases when the listen response closes. Stdio tracks the listen\n * request ID and releases on cancellation, SDK rejection, or graceful\n * completion. In both legs the ref-count is by URI, so a watcher another\n * stream still holds survives.\n */\n async acquire(\n pathGuard: PathGuard,\n uri: string,\n notify: (uri: string) => void,\n { markSubscribe = false }: { markSubscribe?: boolean } = {},\n ): Promise<WatcherAttachResult> {\n // Every failing exit below routes through here, so the declaration made by\n // `startSubscribe` can never outlive the attach that made it.\n const fail = (result: WatcherAttachResult & { ok: false }): WatcherAttachResult => {\n if (markSubscribe) cancelSubscribe(uri);\n return result;\n };\n\n if (hasWatcher(uri)) {\n // A watcher already tracks this uri; just (re)register the callback so\n // its change events reach the new subscriber. No validation or cap work\n // is needed for an already-live watcher.\n addCallback(uri, notify);\n retain(uri);\n return { ok: true };\n }\n // A cap hit before validation and one found after the await are the same\n // condition, and both are reported the same way.\n if (isAtCap()) {\n warnWatcherCap(uri);\n return { ok: false, reason: 'capped' };\n }\n\n if (markSubscribe) startSubscribe(uri);\n\n const filePath = extractPath(uri);\n if (!filePath) return fail({ ok: false, reason: 'bad-uri' });\n\n let resolved: string;\n try {\n resolved = await pathGuard.validateExistingPath(filePath);\n } catch (error: unknown) {\n return fail({ ok: false, reason: 'invalid-path', error });\n }\n\n // Re-check what the await could have changed. A stale uri is the one\n // failure that must NOT cancel: an unsubscribe landed mid-await and its\n // 'unsubscribed' marker is the thing that aborted this attach.\n if (isStale(uri)) return { ok: false, reason: 'stale' };\n if (hasWatcher(uri)) {\n addCallback(uri, notify);\n retain(uri);\n return { ok: true };\n }\n if (isAtCap()) {\n warnWatcherCap(uri);\n return fail({ ok: false, reason: 'capped' });\n }\n\n addCallback(uri, notify);\n if (!attach(uri, resolved)) {\n // fs.watch threw (inotify exhaustion, or a race deleted the path): roll\n // back so no dangling callback is left believing a watcher exists. No\n // lease was taken yet, so `release` drops the entry outright.\n release(uri);\n return fail({ ok: false, reason: 'attach-failed' });\n }\n retain(uri);\n return { ok: true };\n },\n\n destroy(): void {\n destroyed = true;\n for (const timer of debounceTimers.values()) {\n clearTimeout(timer);\n }\n debounceTimers.clear();\n for (const watcher of watchers.values()) {\n try {\n watcher.close();\n } catch {\n /* ignore close errors so all watchers are attempted */\n }\n }\n watchers.clear();\n activeCallbacks.clear();\n subscriberCounts.clear();\n desiredState.clear();\n },\n };\n}\n\nexport type WatcherRegistry = ReturnType<typeof createWatcherRegistry>;\n"]}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import process from 'node:process';
|
|
3
3
|
import * as z from 'zod/v4';
|
|
4
|
-
import { CliExitError, parseArgs, runPrintConfig } from
|
|
5
|
-
import { NO_POSITIONAL_ROOTS_GUIDANCE } from
|
|
6
|
-
import { logRuntimeFailure } from
|
|
7
|
-
import { startHttpServer, startServer } from
|
|
4
|
+
import { CliExitError, parseArgs, runPrintConfig } from "./cli.js";
|
|
5
|
+
import { NO_POSITIONAL_ROOTS_GUIDANCE } from "./core/config.js";
|
|
6
|
+
import { logRuntimeFailure } from "./core/observability.js";
|
|
7
|
+
import { startHttpServer, startServer } from "./transport.js";
|
|
8
8
|
z.config(z.locales.en());
|
|
9
9
|
const SHUTDOWN_TIMEOUT_MS = 5000;
|
|
10
10
|
let activeStdioHandle;
|
|
@@ -67,13 +67,12 @@ async function main() {
|
|
|
67
67
|
}
|
|
68
68
|
throw error;
|
|
69
69
|
}
|
|
70
|
-
const { allowedDirs, allowCwd, port, readOnly, printConfig,
|
|
70
|
+
const { allowedDirs, allowCwd, port, readOnly, printConfig, httpHost, apiKey } = parsed;
|
|
71
71
|
if (printConfig) {
|
|
72
72
|
await runPrintConfig({
|
|
73
73
|
allowedDirs,
|
|
74
74
|
allowCwd,
|
|
75
75
|
readOnly,
|
|
76
|
-
json,
|
|
77
76
|
...(port !== undefined ? { port } : {}),
|
|
78
77
|
...(httpHost !== undefined ? { httpHost } : {}),
|
|
79
78
|
...(apiKey !== undefined ? { apiKey } : {}),
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAKA,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACnE,OAAO,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE9D,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;AAEzB,MAAM,mBAAmB,GAAG,IAAI,CAAC;AACjC,IAAI,iBAAgD,CAAC;AACrD,IAAI,gBAAyC,CAAC;AAC9C,IAAI,eAAe,GAAG,KAAK,CAAC;AAE5B,SAAS,YAAY,CAAC,KAAuC;IAC3D,OAAO,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,OAAO,CAAC;AAC9C,CAAC;AAED,SAAS,uBAAuB,CAAC,KAAuC;IACtE,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;IAC7D,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE;QACtB,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;QAC9D,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,MAAc,EAAE,QAAQ,GAAG,CAAC;IAClD,IAAI,eAAe,EAAE,CAAC;QACpB,IAAI,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YAChD,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;QACD,OAAO;IACT,CAAC;IACD,eAAe,GAAG,IAAI,CAAC;IAEvB,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAE5B,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;QAC5B,iBAAiB,CACf,kBAAkB,EAClB,SAAS,EACT,UAAU,EACV,uBAAuB,MAAM,kBAAkB,CAChD,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzB,CAAC,EAAE,mBAAmB,CAAC,CAAC;IACxB,KAAK,CAAC,KAAK,EAAE,CAAC;IAEd,IAAI,CAAC;QACH,IAAI,CAAC;YACH,MAAM,gBAAgB,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC;QAClD,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,iBAAiB,CAAC,qBAAqB,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;QACzE,CAAC;QACD,IAAI,CAAC;YACH,MAAM,iBAAiB,EAAE,KAAK,EAAE,CAAC;QACnC,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,iBAAiB,CAAC,oBAAoB,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,IAAI,MAA6C,CAAC;IAClD,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,SAAS,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,KAAK,YAAY,YAAY,EAAE,CAAC;YAClC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC7B,iBAAiB,CAAC,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YACxE,CAAC;YACD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAExF,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,cAAc,CAAC;YACnB,WAAW;YACX,QAAQ;YACR,QAAQ;YACR,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvC,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/C,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5C,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;QACjD,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;YAC9B,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAC9C,CAAC;IAED,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,aAAa,GAAG,EAAE,QAAQ,EAAE,cAAc,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;IAC1E,MAAM,aAAa,GAAG;QACpB,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5C,CAAC;IAEF,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,gBAAgB,GAAG,MAAM,eAAe,CAAC,IAAI,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC;IAC/E,CAAC;SAAM,CAAC;QACN,uBAAuB,CAAC,KAAK,CAAC,CAAC;QAC/B,uBAAuB,CAAC,OAAO,CAAC,CAAC;QACjC,iBAAiB,GAAG,WAAW,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AAED,uBAAuB,CAAC,SAAS,CAAC,CAAC;AACnC,uBAAuB,CAAC,QAAQ,CAAC,CAAC;AAElC,OAAO,CAAC,IAAI,CAAC,oBAAoB,EAAE,CAAC,MAAe,EAAE,EAAE;IACrD,iBAAiB,CAAC,qBAAqB,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,CAAC,CAAC;IAClF,KAAK,QAAQ,CAAC,oBAAoB,EAAE,CAAC,CAAC,CAAC;AACzC,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC,KAAY,EAAE,EAAE;IACjD,iBAAiB,CAAC,oBAAoB,EAAE,SAAS,EAAE,mBAAmB,EAAE,KAAK,CAAC,CAAC;IAC/E,KAAK,QAAQ,CAAC,mBAAmB,EAAE,CAAC,CAAC,CAAC;AACxC,CAAC,CAAC,CAAC;AAEH,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,iBAAiB,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IACrD,KAAK,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;AAC5B,CAAC,CAAC,CAAC","sourcesContent":["#!/usr/bin/env node\n// Type-only imports are erased at runtime — safe to keep static.\nimport type { StdioServerHandle } from '@modelcontextprotocol/server/stdio';\n\nimport type * as http from 'node:http';\nimport process from 'node:process';\n\nimport * as z from 'zod/v4';\n\nimport { CliExitError, parseArgs, runPrintConfig } from './cli.ts';\nimport { NO_POSITIONAL_ROOTS_GUIDANCE } from './core/config.ts';\nimport { logRuntimeFailure } from './core/observability.ts';\nimport { startHttpServer, startServer } from './transport.ts';\n\nz.config(z.locales.en());\n\nconst SHUTDOWN_TIMEOUT_MS = 5000;\nlet activeStdioHandle: StdioServerHandle | undefined;\nlet activeHttpServer: http.Server | undefined;\nlet shutdownStarted = false;\n\nfunction isStdinEvent(event: NodeJS.Signals | 'end' | 'close'): boolean {\n return event === 'end' || event === 'close';\n}\n\nfunction registerShutdownTrigger(event: NodeJS.Signals | 'end' | 'close'): void {\n const target = isStdinEvent(event) ? process.stdin : process;\n target.once(event, () => {\n const reason = isStdinEvent(event) ? `stdin ${event}` : event;\n void shutdown(reason, 0);\n });\n}\n\nasync function shutdown(reason: string, exitCode = 0): Promise<void> {\n if (shutdownStarted) {\n if (reason === 'SIGINT' || reason === 'SIGTERM') {\n process.exit(130);\n }\n return;\n }\n shutdownStarted = true;\n\n process.exitCode = exitCode;\n\n const timer = setTimeout(() => {\n logRuntimeFailure(\n 'shutdown_timeout',\n 'process',\n 'shutdown',\n `Shutdown timed out (${reason}), forcing exit.`,\n );\n process.exit(exitCode);\n }, SHUTDOWN_TIMEOUT_MS);\n timer.unref();\n\n try {\n try {\n await activeHttpServer?.[Symbol.asyncDispose]();\n } catch (error: unknown) {\n logRuntimeFailure('shutdown_http_error', 'process', 'shutdown', error);\n }\n try {\n await activeStdioHandle?.close();\n } catch (error: unknown) {\n logRuntimeFailure('shutdown_mcp_error', 'process', 'shutdown', error);\n }\n } finally {\n clearTimeout(timer);\n }\n}\n\nasync function main(): Promise<void> {\n let parsed: Awaited<ReturnType<typeof parseArgs>>;\n try {\n parsed = await parseArgs();\n } catch (error: unknown) {\n if (error instanceof CliExitError) {\n if (error.message.length > 0) {\n logRuntimeFailure('cli_exit', 'startup', 'parse_args', error.message);\n }\n process.exitCode = 1;\n return;\n }\n throw error;\n }\n const { allowedDirs, allowCwd, port, readOnly, printConfig, httpHost, apiKey } = parsed;\n\n if (printConfig) {\n await runPrintConfig({\n allowedDirs,\n allowCwd,\n readOnly,\n ...(port !== undefined ? { port } : {}),\n ...(httpHost !== undefined ? { httpHost } : {}),\n ...(apiKey !== undefined ? { apiKey } : {}),\n });\n return;\n }\n\n if (allowedDirs.length > 0) {\n console.error('Allowed directories (from CLI):');\n for (const dir of allowedDirs) {\n console.error(`- ${dir}`);\n }\n } else {\n console.error(NO_POSITIONAL_ROOTS_GUIDANCE);\n }\n\n if (readOnly) {\n console.error('Read-only mode: mutating tools disabled.');\n }\n\n const serverOptions = { allowCwd, cliAllowedDirs: allowedDirs, readOnly };\n const runtimeConfig = {\n ...(httpHost !== undefined ? { httpHost } : {}),\n ...(apiKey !== undefined ? { apiKey } : {}),\n };\n\n if (port !== undefined) {\n activeHttpServer = await startHttpServer(port, serverOptions, runtimeConfig);\n } else {\n registerShutdownTrigger('end');\n registerShutdownTrigger('close');\n activeStdioHandle = startServer(serverOptions, runtimeConfig);\n }\n}\n\nregisterShutdownTrigger('SIGTERM');\nregisterShutdownTrigger('SIGINT');\n\nprocess.once('unhandledRejection', (reason: unknown) => {\n logRuntimeFailure('unhandled_rejection', 'process', 'unhandledRejection', reason);\n void shutdown('unhandledRejection', 1);\n});\n\nprocess.once('uncaughtException', (error: Error) => {\n logRuntimeFailure('uncaught_exception', 'process', 'uncaughtException', error);\n void shutdown('uncaughtException', 1);\n});\n\nmain().catch((error: unknown) => {\n logRuntimeFailure('fatal', 'startup', 'main', error);\n void shutdown('fatal', 1);\n});\n"]}
|
package/dist/instructions.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DEFAULT_SEARCH_CONTENT_RESULTS, getMaxTextFileSize, MAX_SEARCH_RESULTS, } from
|
|
2
|
-
import { FIND_FILES, LIST, LIST_ROOTS, MUTATING_TOOL_NAMES, READ, SEARCH_TEXT, STAT, } from
|
|
1
|
+
import { DEFAULT_SEARCH_CONTENT_RESULTS, getMaxTextFileSize, MAX_SEARCH_RESULTS, } from "./core/util.js";
|
|
2
|
+
import { FIND_FILES, LIST, LIST_ROOTS, MUTATING_TOOL_NAMES, READ, SEARCH_TEXT, STAT, } from "./tools/index.js";
|
|
3
3
|
function buildToolsOverview(readOnly) {
|
|
4
4
|
const rows = [
|
|
5
5
|
['Navigate', [LIST_ROOTS.name, LIST.name, FIND_FILES.name]],
|
package/dist/instructions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instructions.js","sourceRoot":"","sources":["../src/instructions.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,UAAU,EACV,IAAI,EACJ,UAAU,EACV,mBAAmB,EACnB,IAAI,EACJ,WAAW,EACX,IAAI,GACL,MAAM,kBAAkB,CAAC;AAE1B,SAAS,kBAAkB,CAAC,QAAiB;IAC3C,MAAM,IAAI,GAAyB;QACjC,CAAC,UAAU,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;QAC3D,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;QAC1C,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;KACtB,CAAC;IAEF,4EAA4E;IAC5E,4EAA4E;IAC5E,iCAAiC;IACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3E,OAAO,WAAW,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC;AAClD,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,yBAAyB,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAC/B,6DAA6D;IAC7D,kHAAkH,CAAC;AAErH,MAAM,UAAU,mBAAmB,CAAC,QAAiB;IACnD,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,kBAAkB,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC;IACjE,OAAO;QACL,UAAU,EAAE;YACV,aAAa;YACb,KAAK;YACL,gBAAgB,UAAU,CAAC,IAAI,0EAA0E;YACzG,qKAAqK;YACrK,wCAAwC,IAAI,CAAC,IAAI,OAAO,UAAU,CAAC,IAAI,uBAAuB;YAC9F,KAAK;SACN,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,cAAc,EAAE;YACd,iBAAiB;YACjB,kBAAkB,CAAC,QAAQ,CAAC;YAC5B,EAAE;YACF,kEAAkE;SACnE,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,WAAW,EAAE;YACX,cAAc;YACd,KAAK;YACL,gIAAgI,UAAU,CAAC,IAAI,2BAA2B;YAC1K,kGAAkG;YAClG,sFAAsF;YACtF,kCAAkC,SAAS,wBAAwB,kBAAkB,gCAAgC,8BAA8B,WAAW;YAC9J,+NAA+N;YAC/N,mOAAmO;YACnO,KAAK;SACN,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,cAAc,EAAE;YACd,iBAAiB;YACjB,KAAK;YACL,sBAAsB,UAAU,CAAC,IAAI,oFAAoF;YACzH,kBAAkB,IAAI,CAAC,IAAI,OAAO,UAAU,CAAC,IAAI,sBAAsB;YACvE,kBAAkB,IAAI,CAAC,IAAI,sEAAsE;YACjG,8CAA8C;YAC9C,uDAAuD;YACvD,KAAK;SACN,CAAC,IAAI,CAAC,IAAI,CAAC;KACb,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,QAAgC;IAC7D,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;AACvD,CAAC","sourcesContent":["import {\n DEFAULT_SEARCH_CONTENT_RESULTS,\n getMaxTextFileSize,\n MAX_SEARCH_RESULTS,\n} from './core/util.
|
|
1
|
+
{"version":3,"file":"instructions.js","sourceRoot":"","sources":["../src/instructions.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,UAAU,EACV,IAAI,EACJ,UAAU,EACV,mBAAmB,EACnB,IAAI,EACJ,WAAW,EACX,IAAI,GACL,MAAM,kBAAkB,CAAC;AAE1B,SAAS,kBAAkB,CAAC,QAAiB;IAC3C,MAAM,IAAI,GAAyB;QACjC,CAAC,UAAU,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;QAC3D,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;QAC1C,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;KACtB,CAAC;IAEF,4EAA4E;IAC5E,4EAA4E;IAC5E,iCAAiC;IACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3E,OAAO,WAAW,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC;AAClD,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,yBAAyB,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAC/B,6DAA6D;IAC7D,kHAAkH,CAAC;AAErH,MAAM,UAAU,mBAAmB,CAAC,QAAiB;IACnD,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,kBAAkB,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC;IACjE,OAAO;QACL,UAAU,EAAE;YACV,aAAa;YACb,KAAK;YACL,gBAAgB,UAAU,CAAC,IAAI,0EAA0E;YACzG,qKAAqK;YACrK,wCAAwC,IAAI,CAAC,IAAI,OAAO,UAAU,CAAC,IAAI,uBAAuB;YAC9F,KAAK;SACN,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,cAAc,EAAE;YACd,iBAAiB;YACjB,kBAAkB,CAAC,QAAQ,CAAC;YAC5B,EAAE;YACF,kEAAkE;SACnE,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,WAAW,EAAE;YACX,cAAc;YACd,KAAK;YACL,gIAAgI,UAAU,CAAC,IAAI,2BAA2B;YAC1K,kGAAkG;YAClG,sFAAsF;YACtF,kCAAkC,SAAS,wBAAwB,kBAAkB,gCAAgC,8BAA8B,WAAW;YAC9J,+NAA+N;YAC/N,mOAAmO;YACnO,KAAK;SACN,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,cAAc,EAAE;YACd,iBAAiB;YACjB,KAAK;YACL,sBAAsB,UAAU,CAAC,IAAI,oFAAoF;YACzH,kBAAkB,IAAI,CAAC,IAAI,OAAO,UAAU,CAAC,IAAI,sBAAsB;YACvE,kBAAkB,IAAI,CAAC,IAAI,sEAAsE;YACjG,8CAA8C;YAC9C,uDAAuD;YACvD,KAAK;SACN,CAAC,IAAI,CAAC,IAAI,CAAC;KACb,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,QAAgC;IAC7D,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;AACvD,CAAC","sourcesContent":["import {\n DEFAULT_SEARCH_CONTENT_RESULTS,\n getMaxTextFileSize,\n MAX_SEARCH_RESULTS,\n} from './core/util.ts';\nimport {\n FIND_FILES,\n LIST,\n LIST_ROOTS,\n MUTATING_TOOL_NAMES,\n READ,\n SEARCH_TEXT,\n STAT,\n} from './tools/index.ts';\n\nfunction buildToolsOverview(readOnly: boolean): string {\n const rows: [string, string[]][] = [\n ['Navigate', [LIST_ROOTS.name, LIST.name, FIND_FILES.name]],\n ['Inspect', [STAT.name, SEARCH_TEXT.name]],\n ['Read', [READ.name]],\n ];\n\n // Under --read-only the mutating tools are never registered, so advertising\n // them here would point the model at tools that are not there. Drop the row\n // rather than emit an empty one.\n if (!readOnly) {\n rows.push(['Write', [...MUTATING_TOOL_NAMES]]);\n }\n\n const rowLines = rows.map(([cat, names]) => `${cat}: ${names.join(', ')}`);\n return `\\`\\`\\`\\n${rowLines.join('\\n')}\\n\\`\\`\\``;\n}\n\nexport const INSTRUCTIONS_URI = 'internal://instructions';\n\n/**\n * The one statement of what this server is and how to approach it. The server's\n * `instructions`, the `get-help` prompt, and the instructions resource all\n * describe the same document, and three hand-maintained paraphrases of it drift\n * against each other and cost the client the same sentence three times.\n */\nexport const INSTRUCTIONS_SUMMARY =\n 'Navigation guide for filesystem-mcp tools and constraints. ' +\n 'Use list_roots to inspect configured or accepted roots, then list/find_files -> stat -> read. Never guess paths.';\n\nexport function buildSectionsRecord(readOnly: boolean): Record<string, string> {\n const maxFileMb = Math.floor(getMaxTextFileSize() / 1024 / 1024);\n return {\n guidelines: [\n 'Guidelines:',\n '```',\n `root_access: ${LIST_ROOTS.name} lists configured or accepted roots; every other tool is scoped to them.`,\n 'modern_root_grants: Modern clients do not automatically send workspace roots. Call a tool with a concrete path and approve its grant when elicitation is available.',\n `path_resolution: Confirm a path with ${LIST.name} or ${FIND_FILES.name} before acting on it.`,\n '```',\n ].join('\\n'),\n tools_overview: [\n 'Tools Overview:',\n buildToolsOverview(readOnly),\n '',\n 'Full schemas, descriptions, and annotations are in `tools/list`.',\n ].join('\\n'),\n constraints: [\n 'Constraints:',\n '```',\n `allowed_roots: Startup roots come from CLI paths, FS_ALLOWED_DIRS, or --allow-cwd; accepted modern grants are additive. Call ${LIST_ROOTS.name} to read the current set.`,\n 'legacy_roots: Legacy clients may additionally seed roots through the deprecated roots/list flow.',\n 'sensitive_paths: Sensitive file paths (.env, *.pem, *id_rsa*) are denied by default.',\n `enforced_limits: max file size ${maxFileMb} MB, file search cap ${MAX_SEARCH_RESULTS} results, content search cap ${DEFAULT_SEARCH_CONTENT_RESULTS} matches.`,\n 'ephemeral_results: When a result carries a resource_link or a resourceUri (in structuredContent or _meta), call resources/read immediately — cached results are ephemeral and expire after ~60 seconds, eviction, or restart.',\n 'pagination: nextCursor appears in the result text and in _meta, backed by a snapshot on the same ~60s clock. Page through promptly; if a cursor is rejected, start again without one. resourceUri appears on the first page only.',\n '```',\n ].join('\\n'),\n error_recovery: [\n 'Error Recovery:',\n '```',\n `ACCESS_DENIED: Run ${LIST_ROOTS.name}; configure a missing startup root or retry a concrete path and approve the grant.`,\n `NOT_FOUND: Run ${LIST.name} or ${FIND_FILES.name} to verify the path.`,\n `TOO_LARGE: Use ${READ.name} with head/tail or startLine/endLine, or split across several calls.`,\n 'TIMEOUT: Reduce scope, depth, or maxResults.',\n 'INVALID_INPUT: Re-read the tool schema in tools/list.',\n '```',\n ].join('\\n'),\n };\n}\n\nexport function renderSections(sections: Record<string, string>): string {\n return `\\n${Object.values(sections).join('\\n\\n')}\\n`;\n}\n"]}
|
package/dist/prompts.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { completable } from '@modelcontextprotocol/server';
|
|
2
2
|
import * as z from 'zod/v4';
|
|
3
|
-
import { buildSectionsRecord, INSTRUCTIONS_SUMMARY, renderSections } from
|
|
3
|
+
import { buildSectionsRecord, INSTRUCTIONS_SUMMARY, renderSections } from "./instructions.js";
|
|
4
4
|
// --- Prompt entries ---
|
|
5
5
|
export function registerPrompts(deps) {
|
|
6
6
|
const sections = buildSectionsRecord(deps.readOnly ?? false);
|
package/dist/prompts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAE3D,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAE5B,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAS9F,yBAAyB;AAEzB,MAAM,UAAU,eAAe,CAAC,IAAyB;IACvD,MAAM,QAAQ,GAAG,mBAAmB,CAAC,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,CAAC;IAC7D,MAAM,YAAY,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAErC,IAAI,CAAC,MAAM,CAAC,cAAc,CACxB,UAAU,EACV;QACE,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,oBAAoB;QACjC,UAAU,EAAE,CAAC,CAAC,YAAY,CAAC;YACzB,KAAK,EAAE,WAAW;YAChB,0EAA0E;YAC1E,4EAA4E;YAC5E,wEAAwE;YACxE,CAAC;iBACE,MAAM,EAAE;iBACR,GAAG,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC;iBACrC,QAAQ,CACP,+CAA+C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,qCAAqC,CACtG,EACH,CAAC,KAAK,EAAE,EAAE;gBACR,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;gBAClC,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;YACzE,CAAC,CACF,CAAC,QAAQ,EAAE;SACb,CAAC;KACH,EACD,CAAC,EAAE,KAAK,EAAkC,EAAmB,EAAE;QAC7D,MAAM,UAAU,GAAG,KAAK,EAAE,WAAW,EAAE,CAAC;QACxC,MAAM,OAAO,GACX,UAAU,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACvF,MAAM,IAAI,GACR,OAAO;YACP,CAAC,KAAK;gBACJ,CAAC,CAAC,YAAY,KAAK,2BAA2B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,YAAY,EAAE;gBACpF,CAAC,CAAC,YAAY,CAAC,CAAC;QACpB,OAAO;YACL,WAAW,EAAE,oBAAoB;YACjC,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,QAAQ,EAAE,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE;iBACvF;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC","sourcesContent":["import type { GetPromptResult, McpServer } from '@modelcontextprotocol/server';\nimport { completable } from '@modelcontextprotocol/server';\n\nimport * as z from 'zod/v4';\n\nimport { buildSectionsRecord, INSTRUCTIONS_SUMMARY, renderSections } from './instructions.
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAE3D,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAE5B,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAS9F,yBAAyB;AAEzB,MAAM,UAAU,eAAe,CAAC,IAAyB;IACvD,MAAM,QAAQ,GAAG,mBAAmB,CAAC,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,CAAC;IAC7D,MAAM,YAAY,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAErC,IAAI,CAAC,MAAM,CAAC,cAAc,CACxB,UAAU,EACV;QACE,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,oBAAoB;QACjC,UAAU,EAAE,CAAC,CAAC,YAAY,CAAC;YACzB,KAAK,EAAE,WAAW;YAChB,0EAA0E;YAC1E,4EAA4E;YAC5E,wEAAwE;YACxE,CAAC;iBACE,MAAM,EAAE;iBACR,GAAG,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC;iBACrC,QAAQ,CACP,+CAA+C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,qCAAqC,CACtG,EACH,CAAC,KAAK,EAAE,EAAE;gBACR,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;gBAClC,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;YACzE,CAAC,CACF,CAAC,QAAQ,EAAE;SACb,CAAC;KACH,EACD,CAAC,EAAE,KAAK,EAAkC,EAAmB,EAAE;QAC7D,MAAM,UAAU,GAAG,KAAK,EAAE,WAAW,EAAE,CAAC;QACxC,MAAM,OAAO,GACX,UAAU,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACvF,MAAM,IAAI,GACR,OAAO;YACP,CAAC,KAAK;gBACJ,CAAC,CAAC,YAAY,KAAK,2BAA2B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,YAAY,EAAE;gBACpF,CAAC,CAAC,YAAY,CAAC,CAAC;QACpB,OAAO;YACL,WAAW,EAAE,oBAAoB;YACjC,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,QAAQ,EAAE,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE;iBACvF;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC","sourcesContent":["import type { GetPromptResult, McpServer } from '@modelcontextprotocol/server';\nimport { completable } from '@modelcontextprotocol/server';\n\nimport * as z from 'zod/v4';\n\nimport { buildSectionsRecord, INSTRUCTIONS_SUMMARY, renderSections } from './instructions.ts';\n\n// --- Types ---\n\ninterface PromptRegistrarDeps {\n readonly server: McpServer;\n readonly readOnly?: boolean;\n}\n\n// --- Prompt entries ---\n\nexport function registerPrompts(deps: PromptRegistrarDeps): void {\n const sections = buildSectionsRecord(deps.readOnly ?? false);\n const instructions = renderSections(sections);\n const topics = Object.keys(sections);\n\n deps.server.registerPrompt(\n 'get-help',\n {\n title: 'Get Help',\n description: INSTRUCTIONS_SUMMARY,\n argsSchema: z.strictObject({\n topic: completable(\n // No content validation beyond non-empty: the handler resolves a topic by\n // `Object.hasOwn` against a frozen record, so anything unrecognized already\n // falls through to the not-found reply without reaching an interpreter.\n z\n .string()\n .min(1, { message: 'Topic required' })\n .describe(\n `Section key to filter instructions (one of: ${topics.join(', ')}); omit to return all instructions.`,\n ),\n (value) => {\n const lower = value.toLowerCase();\n return lower ? topics.filter((t) => t.startsWith(lower)) : [...topics];\n },\n ).optional(),\n }),\n },\n ({ topic }: { topic?: string | undefined }): GetPromptResult => {\n const lowerTopic = topic?.toLowerCase();\n const section =\n lowerTopic && Object.hasOwn(sections, lowerTopic) ? sections[lowerTopic] : undefined;\n const text =\n section ??\n (topic\n ? `Section '${topic}' not found. Available: ${topics.join(', ')}\\n\\n${instructions}`\n : instructions);\n return {\n description: INSTRUCTIONS_SUMMARY,\n messages: [\n {\n role: 'user',\n content: { type: 'text', text, annotations: { audience: ['assistant'], priority: 1 } },\n },\n ],\n };\n },\n );\n}\n"]}
|
package/dist/resources.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CacheHint, CompleteResourceTemplateCallback, McpServer, ReadResourceResult, Resource, Role, ServerContext, ServerNotifier } from '@modelcontextprotocol/server';
|
|
2
|
-
import type { PathGuard } from './core/path.
|
|
3
|
-
import type { ResourceStore } from './core/store.
|
|
4
|
-
import { type WatcherRegistry } from './core/watcher-registry.
|
|
2
|
+
import type { PathGuard } from './core/path.ts';
|
|
3
|
+
import type { ResourceStore } from './core/store.ts';
|
|
4
|
+
import { type WatcherRegistry } from './core/watcher-registry.ts';
|
|
5
5
|
export interface ResourceRegistrationOptions {
|
|
6
6
|
resourceStore: ResourceStore;
|
|
7
7
|
pathGuard: PathGuard;
|
|
@@ -30,7 +30,7 @@ type ResourceRegistrarDeps = Omit<ResourceRegistrationOptions, 'pathGuard' | 're
|
|
|
30
30
|
readonly pathGuard: PathGuard;
|
|
31
31
|
readonly readOnly?: boolean;
|
|
32
32
|
};
|
|
33
|
-
interface
|
|
33
|
+
interface ResourceContract {
|
|
34
34
|
name: string;
|
|
35
35
|
title?: string;
|
|
36
36
|
description?: string;
|
|
@@ -40,6 +40,13 @@ interface BaseResourceContract {
|
|
|
40
40
|
audience?: Role[];
|
|
41
41
|
priority?: number;
|
|
42
42
|
};
|
|
43
|
+
/** A fixed, enumerable URI (e.g. internal://instructions)... */
|
|
44
|
+
uri?: string;
|
|
45
|
+
/** ...or a URI template, optionally with a completer for its variable. */
|
|
46
|
+
uriTemplate?: string;
|
|
47
|
+
complete?: {
|
|
48
|
+
path: CompleteResourceTemplateCallback;
|
|
49
|
+
};
|
|
43
50
|
readonly read: (uri: URL, variables: Record<string, string | string[]>, ctx: ServerContext) => Promise<ReadResourceResult> | ReadResourceResult;
|
|
44
51
|
readonly subscribe?: (uri: string, notify: (uri: string) => void) => Promise<boolean | undefined> | boolean | undefined;
|
|
45
52
|
readonly unsubscribe?: (uri: string) => void;
|
|
@@ -50,21 +57,6 @@ interface BaseResourceContract {
|
|
|
50
57
|
resources: Resource[];
|
|
51
58
|
};
|
|
52
59
|
}
|
|
53
|
-
/** A resource with a fixed, enumerable URI (e.g. internal://instructions). */
|
|
54
|
-
interface StaticResourceContract extends BaseResourceContract {
|
|
55
|
-
uri: string;
|
|
56
|
-
uriTemplate?: never;
|
|
57
|
-
complete?: never;
|
|
58
|
-
}
|
|
59
|
-
/** A resource identified by a URI template (FILESYSTEM_FILE_URI_TEMPLATE). */
|
|
60
|
-
interface TemplateResourceContract extends BaseResourceContract {
|
|
61
|
-
uriTemplate: string;
|
|
62
|
-
uri?: never;
|
|
63
|
-
complete?: {
|
|
64
|
-
path: CompleteResourceTemplateCallback;
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
type ResourceContract = StaticResourceContract | TemplateResourceContract;
|
|
68
60
|
export declare function getResourceContracts(options: ResourceRegistrationOptions): ResourceContract[];
|
|
69
61
|
export declare function registerResources(deps: ResourceRegistrarDeps): {
|
|
70
62
|
dispose(): void;
|
package/dist/resources.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,gCAAgC,EAChC,SAAS,EACT,kBAAkB,EAClB,QAAQ,EAER,IAAI,EACJ,aAAa,EACb,cAAc,EAGf,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,gCAAgC,EAChC,SAAS,EACT,kBAAkB,EAClB,QAAQ,EAER,IAAI,EACJ,aAAa,EACb,cAAc,EAGf,MAAM,8BAA8B,CAAC;AAqBtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,4BAA4B,CAAC;AAYpC,MAAM,WAAW,2BAA2B;IAC1C,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,SAAS,CAAC;IACrB,8FAA8F;IAC9F,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,qFAAqF;IACrF,GAAG,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B;;;;OAIG;IACH,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;CACnC;AAED,KAAK,qBAAqB,GAAG,IAAI,CAAC,2BAA2B,EAAE,WAAW,GAAG,UAAU,CAAC,GAAG;IACzF,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAoBF,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,WAAW,CAAC,EAAE;QACZ,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,gEAAgE;IAChE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,gCAAgC,CAAA;KAAE,CAAC;IACtD,QAAQ,CAAC,IAAI,EAAE,CACb,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,EAC5C,GAAG,EAAE,aAAa,KACf,OAAO,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,CAAC;IACtD,QAAQ,CAAC,SAAS,CAAC,EAAE,CACnB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,KAC1B,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,GAAG,OAAO,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,CACd,GAAG,EAAE,aAAa,KACf,OAAO,CAAC;QAAE,SAAS,EAAE,QAAQ,EAAE,CAAA;KAAE,CAAC,GAAG;QAAE,SAAS,EAAE,QAAQ,EAAE,CAAA;KAAE,CAAC;CACrE;AA2PD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,gBAAgB,EAAE,CAM7F;AA8BD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,qBAAqB,GAAG;IAAE,OAAO,IAAI,IAAI,CAAA;CAAE,CAoIlF"}
|
package/dist/resources.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { checkResourceAllowed, ProtocolError, ProtocolErrorCode, ResourceNotFoundError, ResourceTemplate, resourceUrlFromServerUrl, } from '@modelcontextprotocol/server';
|
|
2
|
-
import { ErrorCode, formatUnknownErrorMessage, fsErrorCode,
|
|
3
|
-
import { decodeFileUriPath, encodeFileUriPath, extractPath, FILESYSTEM_FILE_URI_TEMPLATE, } from
|
|
4
|
-
import { GuardedFileSystem } from
|
|
5
|
-
import { Logger } from
|
|
6
|
-
import {
|
|
7
|
-
import { createWatcherRegistry, MAX_WATCHERS, } from
|
|
8
|
-
import { buildSectionsRecord, INSTRUCTIONS_SUMMARY, INSTRUCTIONS_URI, renderSections, } from
|
|
2
|
+
import { ErrorCode, formatUnknownErrorMessage, fsErrorCode, isFsError } from "./core/errors.js";
|
|
3
|
+
import { decodeFileUriPath, encodeFileUriPath, extractPath, FILESYSTEM_FILE_URI_TEMPLATE, } from "./core/file-uri.js";
|
|
4
|
+
import { GuardedFileSystem } from "./core/fs.js";
|
|
5
|
+
import { Logger } from "./core/observability.js";
|
|
6
|
+
import { suggestPaths } from "./core/path-completer.js";
|
|
7
|
+
import { createWatcherRegistry, MAX_WATCHERS, } from "./core/watcher-registry.js";
|
|
8
|
+
import { buildSectionsRecord, INSTRUCTIONS_SUMMARY, INSTRUCTIONS_URI, renderSections, } from "./instructions.js";
|
|
9
9
|
/**
|
|
10
10
|
* A filesystem failure that reads as not-found on the resource wire, for both
|
|
11
11
|
* `resources/read` and `resources/subscribe`. ACCESS_DENIED folds in
|
|
@@ -47,7 +47,6 @@ function createInstructionsResource(options) {
|
|
|
47
47
|
// filesystem
|
|
48
48
|
// ═══════════════════════════════════════════════════════════════
|
|
49
49
|
function createFilesystemResource(options) {
|
|
50
|
-
const completer = new PathCompleter(options.pathGuard);
|
|
51
50
|
const registry = options.watcherRegistry ?? createWatcherRegistry();
|
|
52
51
|
// Only the per-server (legacy/stdio) registry is owned by this resource and
|
|
53
52
|
// destroyed on dispose; the shared modern-leg registry is owned by the host
|
|
@@ -115,7 +114,7 @@ function createFilesystemResource(options) {
|
|
|
115
114
|
// paths: the partial arriving here is whatever this returned last, so the
|
|
116
115
|
// decode mirrors the encode. An undecodable partial is matched as typed.
|
|
117
116
|
path: async (value) => {
|
|
118
|
-
const suggestions = await
|
|
117
|
+
const suggestions = await suggestPaths(options.pathGuard, decodeFileUriPath(value) ?? value);
|
|
119
118
|
return suggestions.map(encodeFileUriPath);
|
|
120
119
|
},
|
|
121
120
|
},
|
|
@@ -254,7 +253,9 @@ function wrapRead(contract) {
|
|
|
254
253
|
return await contract.read(uri, variables, ctx);
|
|
255
254
|
}
|
|
256
255
|
catch (error) {
|
|
257
|
-
|
|
256
|
+
// The SDK brands ProtocolError with `static [Symbol.hasInstance]`, so this
|
|
257
|
+
// holds across separately bundled SDK copies and realms.
|
|
258
|
+
if (error instanceof ProtocolError)
|
|
258
259
|
throw error;
|
|
259
260
|
// A missing or out-of-root path is a not-found, not a malformed request.
|
|
260
261
|
// The SDK puts ResourceNotFoundError on the wire as -32602 with
|
|
@@ -273,7 +274,13 @@ function wrapRead(contract) {
|
|
|
273
274
|
}
|
|
274
275
|
export function registerResources(deps) {
|
|
275
276
|
const server = deps.server;
|
|
276
|
-
const
|
|
277
|
+
const options = { ...deps, readOnly: deps.readOnly ?? false };
|
|
278
|
+
const file = createFilesystemResource(options);
|
|
279
|
+
const resourceContracts = [
|
|
280
|
+
createInstructionsResource(options),
|
|
281
|
+
createResultResource(options),
|
|
282
|
+
file,
|
|
283
|
+
];
|
|
277
284
|
for (const contract of resourceContracts) {
|
|
278
285
|
const config = {
|
|
279
286
|
...(contract.title !== undefined ? { title: contract.title } : {}),
|
|
@@ -296,8 +303,15 @@ export function registerResources(deps) {
|
|
|
296
303
|
// `resources/subscribe`/`unsubscribe` are 2025-era-only verbs; a modern
|
|
297
304
|
// server answers `-32601 Method not found` for them, so registering these
|
|
298
305
|
// handlers on a modern-era instance would dispatch to code no request can
|
|
299
|
-
// reach.
|
|
300
|
-
|
|
306
|
+
// reach. A legacy instance on the HTTP leg (the one built with a `notifier`)
|
|
307
|
+
// lives for one request: a subscription accepted there would take a watcher
|
|
308
|
+
// lease for an instance that is gone before the file changes. Leave the
|
|
309
|
+
// handlers off so the SDK refuses the verb, and no lease is taken. The
|
|
310
|
+
// predicate mirrors the capability gate in server.ts exactly, so the
|
|
311
|
+
// handler is registered if and only if `resources.subscribe` is advertised.
|
|
312
|
+
// sunset(SEP-2577): removal trigger in docs/adr/002-legacy-protocol-paths-sunset.md.
|
|
313
|
+
const legacyHttp = deps.era === 'legacy' && deps.notifier !== undefined;
|
|
314
|
+
if (deps.era !== 'modern' && !legacyHttp) {
|
|
301
315
|
server.server.assertCanSetRequestHandler('resources/subscribe');
|
|
302
316
|
server.server.assertCanSetRequestHandler('resources/unsubscribe');
|
|
303
317
|
// One stable callback for the whole registrar, NOT one per subscribe. The
|
|
@@ -320,62 +334,37 @@ export function registerResources(deps) {
|
|
|
320
334
|
});
|
|
321
335
|
});
|
|
322
336
|
};
|
|
337
|
+
// The URI prefix a contract answers for: a fixed URI, or a template up to
|
|
338
|
+
// its first variable.
|
|
339
|
+
const prefixOf = (contract) => contract.uri ?? contract.uriTemplate?.split('{')[0];
|
|
340
|
+
const matches = (contract, requested) => {
|
|
341
|
+
const configured = prefixOf(contract);
|
|
342
|
+
return (configured !== undefined &&
|
|
343
|
+
checkResourceAllowed({ requestedResource: requested, configuredResource: configured }));
|
|
344
|
+
};
|
|
323
345
|
server.server.setRequestHandler('resources/subscribe', async (req) => {
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const configured = contract.uri ?? contract.uriTemplate.split('{')[0];
|
|
332
|
-
if (!contract.subscribe) {
|
|
333
|
-
if (configured &&
|
|
334
|
-
checkResourceAllowed({ requestedResource, configuredResource: configured })) {
|
|
335
|
-
knownButNotSubscribable = true;
|
|
336
|
-
}
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
if (!configured)
|
|
340
|
-
continue;
|
|
341
|
-
if (checkResourceAllowed({
|
|
342
|
-
requestedResource,
|
|
343
|
-
configuredResource: configured,
|
|
344
|
-
})) {
|
|
345
|
-
foundMatch = true;
|
|
346
|
-
const subscribeResult = await contract.subscribe(requestedResource.toString(), notifyUpdated);
|
|
347
|
-
if (subscribeResult === false) {
|
|
348
|
-
// InternalError for want of anything better: ProtocolErrorCode
|
|
349
|
-
// has no resource-limit member, and the message already names
|
|
350
|
-
// the actionable cause.
|
|
351
|
-
throw new ProtocolError(ProtocolErrorCode.InternalError, `Subscription rejected: no watcher attached (watcher limit ${MAX_WATCHERS} reached, or fs.watch failed to start).`);
|
|
352
|
-
}
|
|
353
|
-
break;
|
|
346
|
+
const requested = resourceUrlFromServerUrl(req.params.uri);
|
|
347
|
+
if (!matches(file, requested)) {
|
|
348
|
+
// A resource that exists but has no watcher (the instructions text,
|
|
349
|
+
// a cached result) is NOT a not-found: reporting it as one told
|
|
350
|
+
// clients a URI they can list and read does not exist.
|
|
351
|
+
if (resourceContracts.some((contract) => matches(contract, requested))) {
|
|
352
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Resource ${requested.toString()} does not support subscriptions; only ${FILESYSTEM_FILE_URI_TEMPLATE} does. Read it again for the current contents.`);
|
|
354
353
|
}
|
|
354
|
+
throw new ResourceNotFoundError(requested.toString(), `Resource not found: ${requested.toString()}`);
|
|
355
355
|
}
|
|
356
|
-
if (
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
throw new
|
|
356
|
+
if ((await file.subscribe(requested.toString(), notifyUpdated)) === false) {
|
|
357
|
+
// InternalError for want of anything better: ProtocolErrorCode has
|
|
358
|
+
// no resource-limit member, and the message already names the
|
|
359
|
+
// actionable cause.
|
|
360
|
+
throw new ProtocolError(ProtocolErrorCode.InternalError, `Subscription rejected: no watcher attached (watcher limit ${MAX_WATCHERS} reached, or fs.watch failed to start).`);
|
|
361
361
|
}
|
|
362
362
|
return {};
|
|
363
363
|
});
|
|
364
364
|
server.server.setRequestHandler('resources/unsubscribe', (req) => {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const requestedResource = resourceUrlFromServerUrl(req.params.uri);
|
|
369
|
-
for (const contract of resourceContracts) {
|
|
370
|
-
if (!contract.unsubscribe)
|
|
371
|
-
continue;
|
|
372
|
-
const configured = contract.uri ?? contract.uriTemplate.split('{')[0];
|
|
373
|
-
if (!configured)
|
|
374
|
-
continue;
|
|
375
|
-
if (checkResourceAllowed({ requestedResource, configuredResource: configured })) {
|
|
376
|
-
contract.unsubscribe(requestedResource.toString());
|
|
377
|
-
}
|
|
378
|
-
}
|
|
365
|
+
const requested = resourceUrlFromServerUrl(req.params.uri);
|
|
366
|
+
if (matches(file, requested))
|
|
367
|
+
file.unsubscribe(requested.toString());
|
|
379
368
|
return {};
|
|
380
369
|
});
|
|
381
370
|
}
|