@j0hanz/filesystem-mcp 2.4.0 → 2.5.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.
Files changed (127) hide show
  1. package/README.md +23 -23
  2. package/dist/cli-help.js +1 -1
  3. package/dist/cli-help.js.map +1 -1
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +3 -10
  6. package/dist/cli.js.map +1 -1
  7. package/dist/core/concurrency.d.ts +1 -1
  8. package/dist/core/concurrency.d.ts.map +1 -1
  9. package/dist/core/concurrency.js +1 -1
  10. package/dist/core/concurrency.js.map +1 -1
  11. package/dist/core/cursor.d.ts.map +1 -1
  12. package/dist/core/cursor.js +2 -5
  13. package/dist/core/cursor.js.map +1 -1
  14. package/dist/core/errors.d.ts +1 -4
  15. package/dist/core/errors.d.ts.map +1 -1
  16. package/dist/core/errors.js +14 -20
  17. package/dist/core/errors.js.map +1 -1
  18. package/dist/core/file-uri.d.ts.map +1 -1
  19. package/dist/core/file-uri.js +3 -8
  20. package/dist/core/file-uri.js.map +1 -1
  21. package/dist/core/fmt.d.ts +13 -7
  22. package/dist/core/fmt.d.ts.map +1 -1
  23. package/dist/core/fmt.js +22 -10
  24. package/dist/core/fmt.js.map +1 -1
  25. package/dist/core/fs.d.ts +5 -18
  26. package/dist/core/fs.d.ts.map +1 -1
  27. package/dist/core/fs.js +1 -6
  28. package/dist/core/fs.js.map +1 -1
  29. package/dist/core/glob.d.ts +1 -2
  30. package/dist/core/glob.d.ts.map +1 -1
  31. package/dist/core/glob.js +1 -7
  32. package/dist/core/glob.js.map +1 -1
  33. package/dist/core/input-required.d.ts +0 -8
  34. package/dist/core/input-required.d.ts.map +1 -1
  35. package/dist/core/input-required.js +5 -15
  36. package/dist/core/input-required.js.map +1 -1
  37. package/dist/core/path-completer.d.ts.map +1 -1
  38. package/dist/core/path-completer.js +0 -2
  39. package/dist/core/path-completer.js.map +1 -1
  40. package/dist/core/path.d.ts.map +1 -1
  41. package/dist/core/path.js +1 -8
  42. package/dist/core/path.js.map +1 -1
  43. package/dist/core/read.d.ts.map +1 -1
  44. package/dist/core/read.js +13 -35
  45. package/dist/core/read.js.map +1 -1
  46. package/dist/core/schema.d.ts.map +1 -1
  47. package/dist/core/schema.js +1 -1
  48. package/dist/core/schema.js.map +1 -1
  49. package/dist/core/search.d.ts +3 -4
  50. package/dist/core/search.d.ts.map +1 -1
  51. package/dist/core/search.js +4 -0
  52. package/dist/core/search.js.map +1 -1
  53. package/dist/core/store.d.ts.map +1 -1
  54. package/dist/core/store.js +0 -3
  55. package/dist/core/store.js.map +1 -1
  56. package/dist/index.js +3 -5
  57. package/dist/index.js.map +1 -1
  58. package/dist/prompts.d.ts.map +1 -1
  59. package/dist/prompts.js +1 -4
  60. package/dist/prompts.js.map +1 -1
  61. package/dist/resources.d.ts.map +1 -1
  62. package/dist/resources.js +23 -18
  63. package/dist/resources.js.map +1 -1
  64. package/dist/server.d.ts.map +1 -1
  65. package/dist/server.js +4 -3
  66. package/dist/server.js.map +1 -1
  67. package/dist/tools/batch.d.ts.map +1 -1
  68. package/dist/tools/batch.js +1 -2
  69. package/dist/tools/batch.js.map +1 -1
  70. package/dist/tools/create.d.ts.map +1 -1
  71. package/dist/tools/create.js +2 -6
  72. package/dist/tools/create.js.map +1 -1
  73. package/dist/tools/define.d.ts +6 -4
  74. package/dist/tools/define.d.ts.map +1 -1
  75. package/dist/tools/define.js +1 -1
  76. package/dist/tools/define.js.map +1 -1
  77. package/dist/tools/delete.d.ts.map +1 -1
  78. package/dist/tools/delete.js +1 -7
  79. package/dist/tools/delete.js.map +1 -1
  80. package/dist/tools/diff.d.ts.map +1 -1
  81. package/dist/tools/diff.js +17 -21
  82. package/dist/tools/diff.js.map +1 -1
  83. package/dist/tools/edit.d.ts.map +1 -1
  84. package/dist/tools/edit.js +22 -16
  85. package/dist/tools/edit.js.map +1 -1
  86. package/dist/tools/find-files.d.ts.map +1 -1
  87. package/dist/tools/find-files.js +3 -5
  88. package/dist/tools/find-files.js.map +1 -1
  89. package/dist/tools/list-roots.d.ts.map +1 -1
  90. package/dist/tools/list-roots.js +4 -6
  91. package/dist/tools/list-roots.js.map +1 -1
  92. package/dist/tools/list.d.ts.map +1 -1
  93. package/dist/tools/list.js +18 -8
  94. package/dist/tools/list.js.map +1 -1
  95. package/dist/tools/move.d.ts.map +1 -1
  96. package/dist/tools/move.js +2 -14
  97. package/dist/tools/move.js.map +1 -1
  98. package/dist/tools/patch.d.ts.map +1 -1
  99. package/dist/tools/patch.js +83 -76
  100. package/dist/tools/patch.js.map +1 -1
  101. package/dist/tools/read.d.ts.map +1 -1
  102. package/dist/tools/read.js +2 -6
  103. package/dist/tools/read.js.map +1 -1
  104. package/dist/tools/replace-text.d.ts.map +1 -1
  105. package/dist/tools/replace-text.js +51 -17
  106. package/dist/tools/replace-text.js.map +1 -1
  107. package/dist/tools/search-text.d.ts.map +1 -1
  108. package/dist/tools/search-text.js +24 -23
  109. package/dist/tools/search-text.js.map +1 -1
  110. package/dist/tools/stat.d.ts.map +1 -1
  111. package/dist/tools/stat.js +3 -5
  112. package/dist/tools/stat.js.map +1 -1
  113. package/dist/transport/http-policy.d.ts +15 -10
  114. package/dist/transport/http-policy.d.ts.map +1 -1
  115. package/dist/transport/http-policy.js +21 -28
  116. package/dist/transport/http-policy.js.map +1 -1
  117. package/dist/transport/http.d.ts.map +1 -1
  118. package/dist/transport/http.js +4 -4
  119. package/dist/transport/http.js.map +1 -1
  120. package/dist/transport/shared.d.ts +13 -12
  121. package/dist/transport/shared.d.ts.map +1 -1
  122. package/dist/transport/shared.js +21 -25
  123. package/dist/transport/shared.js.map +1 -1
  124. package/dist/transport/stdio.d.ts.map +1 -1
  125. package/dist/transport/stdio.js +5 -8
  126. package/dist/transport/stdio.js.map +1 -1
  127. package/package.json +1 -2
@@ -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.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"]}
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 /** 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/index.js CHANGED
@@ -10,13 +10,11 @@ const SHUTDOWN_TIMEOUT_MS = 5000;
10
10
  let activeStdioHandle;
11
11
  let activeHttpServer;
12
12
  let shutdownStarted = false;
13
- function isStdinEvent(event) {
14
- return event === 'end' || event === 'close';
15
- }
16
13
  function registerShutdownTrigger(event) {
17
- const target = isStdinEvent(event) ? process.stdin : process;
14
+ const isStdinEvent = event === 'end' || event === 'close';
15
+ const target = isStdinEvent ? process.stdin : process;
18
16
  target.once(event, () => {
19
- const reason = isStdinEvent(event) ? `stdin ${event}` : event;
17
+ const reason = isStdinEvent ? `stdin ${event}` : event;
20
18
  void shutdown(reason, 0);
21
19
  });
22
20
  }
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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","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"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","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 registerShutdownTrigger(event: NodeJS.Signals | 'end' | 'close'): void {\n const isStdinEvent = event === 'end' || event === 'close';\n const target = isStdinEvent ? process.stdin : process;\n target.once(event, () => {\n const reason = isStdinEvent ? `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"]}
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAS/E,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAID,wBAAgB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAgD/D"}
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAS/E,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAID,wBAAgB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,CA6C/D"}
package/dist/prompts.js CHANGED
@@ -17,10 +17,7 @@ export function registerPrompts(deps) {
17
17
  z
18
18
  .string()
19
19
  .min(1, { message: 'Topic required' })
20
- .describe(`Section key to filter instructions (one of: ${topics.join(', ')}); omit to return all instructions.`), (value) => {
21
- const lower = value.toLowerCase();
22
- return lower ? topics.filter((t) => t.startsWith(lower)) : [...topics];
23
- }).optional(),
20
+ .describe(`Section key to filter instructions (one of: ${topics.join(', ')}); omit to return all instructions.`), (value) => topics.filter((t) => t.startsWith(value.toLowerCase()))).optional(),
24
21
  }),
25
22
  }, ({ topic }) => {
26
23
  const lowerTopic = topic?.toLowerCase();
@@ -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.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"]}
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,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CACnE,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) => topics.filter((t) => t.startsWith(value.toLowerCase())),\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"]}
@@ -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;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"}
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;AAoBtC,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;AA8CD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,qBAAqB,GAAG;IAAE,OAAO,IAAI,IAAI,CAAA;CAAE,CAoHlF"}
package/dist/resources.js CHANGED
@@ -1,4 +1,4 @@
1
- import { checkResourceAllowed, ProtocolError, ProtocolErrorCode, ResourceNotFoundError, ResourceTemplate, resourceUrlFromServerUrl, } from '@modelcontextprotocol/server';
1
+ import { ProtocolError, ProtocolErrorCode, ResourceNotFoundError, ResourceTemplate, UriTemplate, } from '@modelcontextprotocol/server';
2
2
  import { ErrorCode, formatUnknownErrorMessage, fsErrorCode, isFsError } from "./core/errors.js";
3
3
  import { decodeFileUriPath, encodeFileUriPath, extractPath, FILESYSTEM_FILE_URI_TEMPLATE, } from "./core/file-uri.js";
4
4
  import { GuardedFileSystem } from "./core/fs.js";
@@ -272,6 +272,20 @@ function wrapRead(contract) {
272
272
  }
273
273
  };
274
274
  }
275
+ /**
276
+ * Whether `contract` answers for `uri`, decided by the template matcher the SDK
277
+ * dispatches `resources/read` with, so `resources/subscribe` and
278
+ * `resources/read` agree on which resource owns a URI. Not
279
+ * `checkResourceAllowed`: that is an RFC 8707 audience check comparing
280
+ * `URL.origin`, which is the opaque `"null"` for every custom scheme, so it
281
+ * matched `filesystem-mcp://result/…` — and any other scheme — to the file
282
+ * template.
283
+ */
284
+ function owns(contract, uri) {
285
+ if (contract.uri !== undefined)
286
+ return uri === contract.uri;
287
+ return (contract.uriTemplate !== undefined && new UriTemplate(contract.uriTemplate).match(uri) !== null);
288
+ }
275
289
  export function registerResources(deps) {
276
290
  const server = deps.server;
277
291
  const options = { ...deps, readOnly: deps.readOnly ?? false };
@@ -334,26 +348,18 @@ export function registerResources(deps) {
334
348
  });
335
349
  });
336
350
  };
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
- };
345
351
  server.server.setRequestHandler('resources/subscribe', async (req) => {
346
- const requested = resourceUrlFromServerUrl(req.params.uri);
347
- if (!matches(file, requested)) {
352
+ const { uri } = req.params;
353
+ if (!owns(file, uri)) {
348
354
  // A resource that exists but has no watcher (the instructions text,
349
355
  // a cached result) is NOT a not-found: reporting it as one told
350
356
  // 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.`);
357
+ if (resourceContracts.some((contract) => owns(contract, uri))) {
358
+ throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Resource ${uri} does not support subscriptions; only ${FILESYSTEM_FILE_URI_TEMPLATE} does. Read it again for the current contents.`);
353
359
  }
354
- throw new ResourceNotFoundError(requested.toString(), `Resource not found: ${requested.toString()}`);
360
+ throw new ResourceNotFoundError(uri, `Resource not found: ${uri}`);
355
361
  }
356
- if ((await file.subscribe(requested.toString(), notifyUpdated)) === false) {
362
+ if ((await file.subscribe(uri, notifyUpdated)) === false) {
357
363
  // InternalError for want of anything better: ProtocolErrorCode has
358
364
  // no resource-limit member, and the message already names the
359
365
  // actionable cause.
@@ -362,9 +368,8 @@ export function registerResources(deps) {
362
368
  return {};
363
369
  });
364
370
  server.server.setRequestHandler('resources/unsubscribe', (req) => {
365
- const requested = resourceUrlFromServerUrl(req.params.uri);
366
- if (matches(file, requested))
367
- file.unsubscribe(requested.toString());
371
+ if (owns(file, req.params.uri))
372
+ file.unsubscribe(req.params.uri);
368
373
  return {};
369
374
  });
370
375
  }
@@ -1 +1 @@
1
- {"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CacheHint,\n CompleteResourceTemplateCallback,\n McpServer,\n ReadResourceResult,\n Resource,\n ResourceUpdatedNotificationParams,\n Role,\n ServerContext,\n ServerNotifier,\n SubscribeRequestParams,\n UnsubscribeRequestParams,\n} from '@modelcontextprotocol/server';\nimport {\n checkResourceAllowed,\n ProtocolError,\n ProtocolErrorCode,\n ResourceNotFoundError,\n ResourceTemplate,\n resourceUrlFromServerUrl,\n} from '@modelcontextprotocol/server';\n\nimport type { FsError } from './core/errors.ts';\nimport { ErrorCode, formatUnknownErrorMessage, fsErrorCode, isFsError } from './core/errors.ts';\nimport {\n decodeFileUriPath,\n encodeFileUriPath,\n extractPath,\n FILESYSTEM_FILE_URI_TEMPLATE,\n} from './core/file-uri.ts';\nimport { GuardedFileSystem } from './core/fs.ts';\nimport { Logger } from './core/observability.ts';\nimport { suggestPaths } from './core/path-completer.ts';\nimport type { PathGuard } from './core/path.ts';\nimport type { ResourceStore } from './core/store.ts';\nimport {\n createWatcherRegistry,\n MAX_WATCHERS,\n type WatcherRegistry,\n} from './core/watcher-registry.ts';\nimport {\n buildSectionsRecord,\n INSTRUCTIONS_SUMMARY,\n INSTRUCTIONS_URI,\n renderSections,\n} from './instructions.ts';\n\n// ═══════════════════════════════════════════════════════════════\n// shared\n// ═══════════════════════════════════════════════════════════════\n\nexport interface ResourceRegistrationOptions {\n resourceStore: ResourceStore;\n pathGuard: PathGuard;\n /** Mirrors the `--read-only` gate so the instructions match the tools actually registered. */\n readOnly: boolean;\n /**\n * Shared file-watcher registry for the modern (per-request) HTTP leg. When set,\n * the resource uses this handler-scoped registry instead of creating its own\n * per-server one, so file-change watchers persist across requests and publish\n * to the shared ServerEventBus. Omitted (per-server registry) on legacy/stdio.\n */\n watcherRegistry?: WatcherRegistry;\n /** Modern-leg typed notification publisher. */\n notifier?: ServerNotifier;\n /** The protocol era this instance serves; omitted where the caller does not know. */\n era?: 'legacy' | 'modern';\n /**\n * Scope for shared-content cache hints. 'private' when the endpoint is\n * API-key gated, mirroring the list hints in server.ts — a shared cache must\n * not store an authenticated response body. Defaults to 'public'.\n */\n cacheScope?: 'public' | 'private';\n}\n\ntype ResourceRegistrarDeps = Omit<ResourceRegistrationOptions, 'pathGuard' | 'readOnly'> & {\n readonly server: McpServer;\n readonly pathGuard: PathGuard;\n readonly readOnly?: boolean;\n};\n\n/**\n * A filesystem failure that reads as not-found on the resource wire, for both\n * `resources/read` and `resources/subscribe`. ACCESS_DENIED folds in\n * deliberately: masking whether an out-of-root or denylisted path exists is the\n * security-correct answer, and the `FsError` message still carries the real\n * cause to the client.\n */\nfunction isNotFoundish(error: unknown): error is FsError {\n return (\n isFsError(error) &&\n (error.code === ErrorCode.NOT_FOUND || error.code === ErrorCode.ACCESS_DENIED)\n );\n}\n\n// ═══════════════════════════════════════════════════════════════\n// contract\n// ═══════════════════════════════════════════════════════════════\n\ninterface ResourceContract {\n name: string;\n title?: string;\n description?: string;\n mimeType?: string;\n cacheHint?: CacheHint;\n annotations?: {\n audience?: Role[];\n priority?: number;\n };\n /** A fixed, enumerable URI (e.g. internal://instructions)... */\n uri?: string;\n /** ...or a URI template, optionally with a completer for its variable. */\n uriTemplate?: string;\n complete?: { path: CompleteResourceTemplateCallback };\n readonly read: (\n uri: URL,\n variables: Record<string, string | string[]>,\n ctx: ServerContext,\n ) => Promise<ReadResourceResult> | ReadResourceResult;\n readonly subscribe?: (\n uri: string,\n notify: (uri: string) => void,\n ) => Promise<boolean | undefined> | boolean | undefined;\n readonly unsubscribe?: (uri: string) => void;\n readonly destroy?: () => void;\n readonly list?: (\n ctx: ServerContext,\n ) => Promise<{ resources: Resource[] }> | { resources: Resource[] };\n}\n\n/** The one contract with a live watcher behind it. */\ntype SubscribableContract = ResourceContract &\n Required<Pick<ResourceContract, 'subscribe' | 'unsubscribe'>>;\n\n// ═══════════════════════════════════════════════════════════════\n// instructions\n// ═══════════════════════════════════════════════════════════════\n\nfunction createInstructionsResource(options: ResourceRegistrationOptions): ResourceContract {\n const text = renderSections(buildSectionsRecord(options.readOnly));\n return {\n name: 'filesystem-mcp-instructions',\n title: 'Server Instructions',\n description: INSTRUCTIONS_SUMMARY,\n mimeType: 'text/markdown',\n uri: INSTRUCTIONS_URI,\n annotations: { audience: ['assistant'], priority: 0.8 },\n cacheHint: { cacheScope: options.cacheScope ?? 'public', ttlMs: 300_000 },\n read(uri) {\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'text/markdown',\n text,\n },\n ],\n };\n },\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// filesystem\n// ═══════════════════════════════════════════════════════════════\n\nfunction createFilesystemResource(options: ResourceRegistrationOptions): SubscribableContract {\n const registry = options.watcherRegistry ?? createWatcherRegistry();\n // Only the per-server (legacy/stdio) registry is owned by this resource and\n // destroyed on dispose; the shared modern-leg registry is owned by the host\n // and torn down at server shutdown.\n const ownsRegistry = options.watcherRegistry === undefined;\n // The URIs this connection holds a `resources/subscribe` lease for. The wire\n // verb is per-URI and idempotent — a second subscribe is the same\n // subscription, and one unsubscribe ends it — but the registry ref-counts\n // leases (the HTTP listen leg needs that). Without this set a double\n // subscribe took two leases that one unsubscribe could not release, and an\n // unsubscribe for a URI never subscribed released a lease this connection\n // never took, dropping a watcher some other holder still wanted.\n const leasedUris = new Set<string>();\n // Subscribes mid-acquire, keyed by URI. Two concurrent subscribes for the\n // same URI must share one acquire: both passing the `leasedUris` check\n // before either finished took two registry leases that the single set entry\n // could release only once — on the shared modern registry that orphan lease\n // pinned a watcher until process shutdown.\n const inflight = new Map<string, Promise<boolean | undefined>>();\n\n return {\n name: 'filesystem-mcp-file',\n title: 'Workspace File',\n description: 'Read a file from the workspace. Subscribe to get updates when the file changes.',\n uriTemplate: FILESYSTEM_FILE_URI_TEMPLATE,\n annotations: { audience: ['assistant'], priority: 0.8 },\n // Without this the SDK falls back to its conservative `{ ttlMs: 0,\n // cacheScope: 'private' }`, making every file read uncacheable — and this\n // is the one resource with a live invalidation channel, so TTL and the\n // watcher compose exactly as the spec's caching section describes. The TTL\n // stays short because a non-subscriber gets no invalidation signal at all.\n cacheHint: { cacheScope: options.cacheScope ?? 'public', ttlMs: 5_000 },\n\n // No `list`: the template registers with `list: undefined` — readable but\n // not enumerable. Listing each allowed root as a concrete resource\n // duplicated the template and grew resources/list linearly with roots;\n // `list_roots` owns root discovery.\n\n async read(uri, _variables, _ctx: ServerContext) {\n // Decode via extractPath — the same decoder resources/subscribe uses — so\n // both consumers are symmetric with buildFileResourceUri's encoding. The\n // {+path} template variable arrives still percent-encoded, so validating\n // it directly would treat \"c%3A/proj/a.ts\" as a literal relative path;\n // no fallback to it here, or the two branches would validate different\n // strings. extractPath only fails past the template match on undecodable\n // percent-encoding, which is the caller's malformed URI.\n const rawPath = extractPath(uri.href);\n if (rawPath === undefined) {\n throw new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n 'Malformed file URI: path is not valid percent-encoding',\n );\n }\n await options.pathGuard.validateExistingPath(rawPath);\n const fs = new GuardedFileSystem(options.pathGuard);\n const readResult = await fs.readRaw(rawPath);\n\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: readResult.mimeType || 'application/octet-stream',\n ...(readResult.isBinary\n ? { blob: readResult.content.toString('base64') }\n : { text: readResult.content.toString('utf-8') }),\n },\n ],\n };\n },\n\n complete: {\n // Both ends speak the `{+path}` form (see encodeFileUriPath), not raw OS\n // paths: the partial arriving here is whatever this returned last, so the\n // decode mirrors the encode. An undecodable partial is matched as typed.\n path: async (value) => {\n const suggestions = await suggestPaths(\n options.pathGuard,\n decodeFileUriPath(value) ?? value,\n );\n return suggestions.map(encodeFileUriPath);\n },\n },\n\n subscribe(uri, notify) {\n // Already subscribed on this connection: the watcher is live and the sink\n // is registered, so this is a no-op success rather than a second lease.\n if (leasedUris.has(uri)) return undefined;\n // A subscribe for this URI is mid-acquire: share its outcome instead of\n // taking a second lease the lease set cannot represent.\n const pending = inflight.get(uri);\n if (pending) return pending;\n\n const acquire = async (): Promise<boolean | undefined> => {\n const result = await registry.acquire(options.pathGuard, uri, notify, {\n markSubscribe: true,\n });\n if (result.ok) {\n leasedUris.add(uri);\n return undefined;\n }\n\n if (result.reason === 'bad-uri') {\n throw new ResourceNotFoundError(uri, `Cannot subscribe: not a filesystem URI`);\n }\n if (result.reason === 'invalid-path') {\n const err = result.error;\n if (isNotFoundish(err)) {\n throw new ResourceNotFoundError(uri, `Cannot subscribe to ${uri}: ${err.message}`);\n }\n Logger.warn(\n `Unexpected error validating path for watcher ${uri}: ${formatUnknownErrorMessage(err)}`,\n );\n throw err;\n }\n // Unsubscribed (or the registry destroyed) mid-await: the caller already\n // asked for this to stop, so there is nothing to reject.\n if (result.reason === 'stale') return undefined;\n // capped / attach-failed: `false` tells the handler to reject, since\n // undefined would report success with no watcher attached.\n return false;\n };\n\n const promise = acquire().finally(() => inflight.delete(uri));\n inflight.set(uri, promise);\n return promise;\n },\n\n unsubscribe(uri) {\n // Only release a lease this connection actually took.\n if (!leasedUris.delete(uri)) return;\n registry.release(uri);\n },\n\n destroy() {\n // Release what this connection still holds before the registry goes: on\n // the shared (modern) registry nothing else would ever end these leases.\n for (const uri of leasedUris) registry.release(uri);\n leasedUris.clear();\n if (ownsRegistry) registry.destroy();\n },\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// result\n// ═══════════════════════════════════════════════════════════════\n\nfunction createResultResource(options: ResourceRegistrationOptions): ResourceContract {\n return {\n name: 'filesystem-mcp-result',\n title: 'Cached Tool Result',\n description:\n 'Ephemeral cached tool output. Listed via resources/list; entries expire after the cache TTL.',\n mimeType: 'application/json',\n uriTemplate: 'filesystem-mcp://result/{id}',\n annotations: { audience: ['assistant'], priority: 0.3 },\n cacheHint: { cacheScope: 'private', ttlMs: 60_000 },\n list() {\n const store = options.resourceStore;\n const uris = store.keys(); // prunes expired first\n const resources: Resource[] = [];\n for (const uri of uris) {\n try {\n const entry = store.getEntry(uri);\n resources.push({\n uri: entry.uri,\n name: entry.name,\n mimeType: entry.mimeType,\n size: entry.size,\n });\n } catch (err) {\n // An entry may expire between keys() and getEntry; skip it.\n if (isFsError(err) && err.code === ErrorCode.NOT_FOUND) continue;\n throw err;\n }\n }\n // Recent-first: keys() returns insertion order, so the newest is last.\n resources.reverse();\n return { resources };\n },\n read(uri, variables) {\n const { id } = variables;\n if (typeof id !== 'string' || id.length === 0) {\n throw new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n 'Malformed result URI: missing {id}. Use the exact resourceUri returned by the tool.',\n );\n }\n\n let entry;\n try {\n entry = options.resourceStore.getEntry(uri.toString());\n } catch (err) {\n if (isFsError(err) && err.code === ErrorCode.NOT_FOUND) {\n throw new ResourceNotFoundError(\n uri.toString(),\n 'Cached result not found or expired. Re-run the tool to regenerate.',\n );\n }\n throw err;\n }\n return {\n contents: [{ uri: entry.uri, mimeType: entry.mimeType, text: entry.text }],\n };\n },\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// export contracts\n// ═══════════════════════════════════════════════════════════════\n\nexport function getResourceContracts(options: ResourceRegistrationOptions): ResourceContract[] {\n return [\n createInstructionsResource(options),\n createResultResource(options),\n createFilesystemResource(options),\n ];\n}\n\n// ═══════════════════════════════════════════════════════════════\n// registrar\n// ═══════════════════════════════════════════════════════════════\n\nfunction wrapRead(contract: ResourceContract) {\n return async (uri: URL, variables: Record<string, string | string[]>, ctx: ServerContext) => {\n try {\n return await contract.read(uri, variables, ctx);\n } catch (error) {\n // The SDK brands ProtocolError with `static [Symbol.hasInstance]`, so this\n // holds across separately bundled SDK copies and realms.\n if (error instanceof ProtocolError) throw error;\n // A missing or out-of-root path is a not-found, not a malformed request.\n // The SDK puts ResourceNotFoundError on the wire as -32602 with\n // `data.uri`, which is what clients match on; -32002 is the older code\n // they also accept, and this SDK never emits it.\n if (isNotFoundish(error)) {\n throw new ResourceNotFoundError(uri.toString(), error.message);\n }\n // A remaining FsError (NOT_FILE, TOO_LARGE, ...) traces to the\n // caller-supplied URI; anything else is a server-side failure and must\n // not be blamed on the request.\n const msg = isFsError(error) ? error.message : formatUnknownErrorMessage(error);\n throw new ProtocolError(fsErrorCode(error), msg);\n }\n };\n}\n\nexport function registerResources(deps: ResourceRegistrarDeps): { dispose(): void } {\n const server = deps.server;\n const options = { ...deps, readOnly: deps.readOnly ?? false };\n const file = createFilesystemResource(options);\n const resourceContracts = [\n createInstructionsResource(options),\n createResultResource(options),\n file,\n ];\n\n for (const contract of resourceContracts) {\n const config = {\n ...(contract.title !== undefined ? { title: contract.title } : {}),\n ...(contract.description !== undefined ? { description: contract.description } : {}),\n ...(contract.mimeType !== undefined ? { mimeType: contract.mimeType } : {}),\n ...(contract.annotations !== undefined ? { annotations: contract.annotations } : {}),\n ...(contract.cacheHint !== undefined ? { cacheHint: contract.cacheHint } : {}),\n };\n\n if (contract.uriTemplate) {\n const template = new ResourceTemplate(contract.uriTemplate, {\n list: contract.list,\n ...(contract.complete ? { complete: contract.complete } : {}),\n });\n\n server.registerResource(contract.name, template, config, wrapRead(contract));\n } else if (contract.uri) {\n server.registerResource(contract.name, contract.uri, config, (uri: URL, ctx: ServerContext) =>\n wrapRead(contract)(uri, {}, ctx),\n );\n }\n }\n\n // `resources/subscribe`/`unsubscribe` are 2025-era-only verbs; a modern\n // server answers `-32601 Method not found` for them, so registering these\n // handlers on a modern-era instance would dispatch to code no request can\n // reach. A legacy instance on the HTTP leg (the one built with a `notifier`)\n // lives for one request: a subscription accepted there would take a watcher\n // lease for an instance that is gone before the file changes. Leave the\n // handlers off so the SDK refuses the verb, and no lease is taken. The\n // predicate mirrors the capability gate in server.ts exactly, so the\n // handler is registered if and only if `resources.subscribe` is advertised.\n // sunset(SEP-2577): removal trigger in docs/adr/002-legacy-protocol-paths-sunset.md.\n const legacyHttp = deps.era === 'legacy' && deps.notifier !== undefined;\n if (deps.era !== 'modern' && !legacyHttp) {\n server.server.assertCanSetRequestHandler('resources/subscribe');\n server.server.assertCanSetRequestHandler('resources/unsubscribe');\n\n // One stable callback for the whole registrar, NOT one per subscribe. The\n // registry holds these in a Set keyed by identity, so a fresh closure per\n // request made a second `resources/subscribe` for the same URI register a\n // second sink: one file change then sent N notifications, and `unsubscribe`\n // (which only ends a lease) removed none of them. Subscribe is per-URI on\n // the wire, so the sink must be too — this makes it idempotent.\n const notifyUpdated = (updatedUri: string): void => {\n if (deps.notifier) {\n deps.notifier.resourceUpdated(updatedUri);\n return;\n }\n const updatePayload: ResourceUpdatedNotificationParams = { uri: updatedUri };\n // A failed notify means the connection went away; nothing to recover.\n void server.server.sendResourceUpdated(updatePayload).catch((err: unknown) => {\n Logger.debug('resource update not delivered', {\n uri: updatedUri,\n error: formatUnknownErrorMessage(err),\n });\n });\n };\n\n // The URI prefix a contract answers for: a fixed URI, or a template up to\n // its first variable.\n const prefixOf = (contract: ResourceContract): string | undefined =>\n contract.uri ?? contract.uriTemplate?.split('{')[0];\n const matches = (contract: ResourceContract, requested: URL): boolean => {\n const configured = prefixOf(contract);\n return (\n configured !== undefined &&\n checkResourceAllowed({ requestedResource: requested, configuredResource: configured })\n );\n };\n\n server.server.setRequestHandler(\n 'resources/subscribe',\n async (req: { params: SubscribeRequestParams }) => {\n const requested = resourceUrlFromServerUrl(req.params.uri);\n if (!matches(file, requested)) {\n // A resource that exists but has no watcher (the instructions text,\n // a cached result) is NOT a not-found: reporting it as one told\n // clients a URI they can list and read does not exist.\n if (resourceContracts.some((contract) => matches(contract, requested))) {\n throw new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n `Resource ${requested.toString()} does not support subscriptions; only ${FILESYSTEM_FILE_URI_TEMPLATE} does. Read it again for the current contents.`,\n );\n }\n throw new ResourceNotFoundError(\n requested.toString(),\n `Resource not found: ${requested.toString()}`,\n );\n }\n if ((await file.subscribe(requested.toString(), notifyUpdated)) === false) {\n // InternalError for want of anything better: ProtocolErrorCode has\n // no resource-limit member, and the message already names the\n // actionable cause.\n throw new ProtocolError(\n ProtocolErrorCode.InternalError,\n `Subscription rejected: no watcher attached (watcher limit ${MAX_WATCHERS} reached, or fs.watch failed to start).`,\n );\n }\n return {};\n },\n );\n\n server.server.setRequestHandler(\n 'resources/unsubscribe',\n (req: { params: UnsubscribeRequestParams }) => {\n const requested = resourceUrlFromServerUrl(req.params.uri);\n if (matches(file, requested)) file.unsubscribe(requested.toString());\n return {};\n },\n );\n }\n\n return {\n dispose(): void {\n for (const contract of resourceContracts) {\n if (contract.destroy) {\n contract.destroy();\n }\n }\n },\n };\n}\n"]}
1
+ {"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CacheHint,\n CompleteResourceTemplateCallback,\n McpServer,\n ReadResourceResult,\n Resource,\n ResourceUpdatedNotificationParams,\n Role,\n ServerContext,\n ServerNotifier,\n SubscribeRequestParams,\n UnsubscribeRequestParams,\n} from '@modelcontextprotocol/server';\nimport {\n ProtocolError,\n ProtocolErrorCode,\n ResourceNotFoundError,\n ResourceTemplate,\n UriTemplate,\n} from '@modelcontextprotocol/server';\n\nimport type { FsError } from './core/errors.ts';\nimport { ErrorCode, formatUnknownErrorMessage, fsErrorCode, isFsError } from './core/errors.ts';\nimport {\n decodeFileUriPath,\n encodeFileUriPath,\n extractPath,\n FILESYSTEM_FILE_URI_TEMPLATE,\n} from './core/file-uri.ts';\nimport { GuardedFileSystem } from './core/fs.ts';\nimport { Logger } from './core/observability.ts';\nimport { suggestPaths } from './core/path-completer.ts';\nimport type { PathGuard } from './core/path.ts';\nimport type { ResourceStore } from './core/store.ts';\nimport {\n createWatcherRegistry,\n MAX_WATCHERS,\n type WatcherRegistry,\n} from './core/watcher-registry.ts';\nimport {\n buildSectionsRecord,\n INSTRUCTIONS_SUMMARY,\n INSTRUCTIONS_URI,\n renderSections,\n} from './instructions.ts';\n\n// ═══════════════════════════════════════════════════════════════\n// shared\n// ═══════════════════════════════════════════════════════════════\n\nexport interface ResourceRegistrationOptions {\n resourceStore: ResourceStore;\n pathGuard: PathGuard;\n /** Mirrors the `--read-only` gate so the instructions match the tools actually registered. */\n readOnly: boolean;\n /**\n * Shared file-watcher registry for the modern (per-request) HTTP leg. When set,\n * the resource uses this handler-scoped registry instead of creating its own\n * per-server one, so file-change watchers persist across requests and publish\n * to the shared ServerEventBus. Omitted (per-server registry) on legacy/stdio.\n */\n watcherRegistry?: WatcherRegistry;\n /** Modern-leg typed notification publisher. */\n notifier?: ServerNotifier;\n /** The protocol era this instance serves; omitted where the caller does not know. */\n era?: 'legacy' | 'modern';\n /**\n * Scope for shared-content cache hints. 'private' when the endpoint is\n * API-key gated, mirroring the list hints in server.ts — a shared cache must\n * not store an authenticated response body. Defaults to 'public'.\n */\n cacheScope?: 'public' | 'private';\n}\n\ntype ResourceRegistrarDeps = Omit<ResourceRegistrationOptions, 'pathGuard' | 'readOnly'> & {\n readonly server: McpServer;\n readonly pathGuard: PathGuard;\n readonly readOnly?: boolean;\n};\n\n/**\n * A filesystem failure that reads as not-found on the resource wire, for both\n * `resources/read` and `resources/subscribe`. ACCESS_DENIED folds in\n * deliberately: masking whether an out-of-root or denylisted path exists is the\n * security-correct answer, and the `FsError` message still carries the real\n * cause to the client.\n */\nfunction isNotFoundish(error: unknown): error is FsError {\n return (\n isFsError(error) &&\n (error.code === ErrorCode.NOT_FOUND || error.code === ErrorCode.ACCESS_DENIED)\n );\n}\n\n// ═══════════════════════════════════════════════════════════════\n// contract\n// ═══════════════════════════════════════════════════════════════\n\ninterface ResourceContract {\n name: string;\n title?: string;\n description?: string;\n mimeType?: string;\n cacheHint?: CacheHint;\n annotations?: {\n audience?: Role[];\n priority?: number;\n };\n /** A fixed, enumerable URI (e.g. internal://instructions)... */\n uri?: string;\n /** ...or a URI template, optionally with a completer for its variable. */\n uriTemplate?: string;\n complete?: { path: CompleteResourceTemplateCallback };\n readonly read: (\n uri: URL,\n variables: Record<string, string | string[]>,\n ctx: ServerContext,\n ) => Promise<ReadResourceResult> | ReadResourceResult;\n readonly subscribe?: (\n uri: string,\n notify: (uri: string) => void,\n ) => Promise<boolean | undefined> | boolean | undefined;\n readonly unsubscribe?: (uri: string) => void;\n readonly destroy?: () => void;\n readonly list?: (\n ctx: ServerContext,\n ) => Promise<{ resources: Resource[] }> | { resources: Resource[] };\n}\n\n/** The one contract with a live watcher behind it. */\ntype SubscribableContract = ResourceContract &\n Required<Pick<ResourceContract, 'subscribe' | 'unsubscribe'>>;\n\n// ═══════════════════════════════════════════════════════════════\n// instructions\n// ═══════════════════════════════════════════════════════════════\n\nfunction createInstructionsResource(options: ResourceRegistrationOptions): ResourceContract {\n const text = renderSections(buildSectionsRecord(options.readOnly));\n return {\n name: 'filesystem-mcp-instructions',\n title: 'Server Instructions',\n description: INSTRUCTIONS_SUMMARY,\n mimeType: 'text/markdown',\n uri: INSTRUCTIONS_URI,\n annotations: { audience: ['assistant'], priority: 0.8 },\n cacheHint: { cacheScope: options.cacheScope ?? 'public', ttlMs: 300_000 },\n read(uri) {\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'text/markdown',\n text,\n },\n ],\n };\n },\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// filesystem\n// ═══════════════════════════════════════════════════════════════\n\nfunction createFilesystemResource(options: ResourceRegistrationOptions): SubscribableContract {\n const registry = options.watcherRegistry ?? createWatcherRegistry();\n // Only the per-server (legacy/stdio) registry is owned by this resource and\n // destroyed on dispose; the shared modern-leg registry is owned by the host\n // and torn down at server shutdown.\n const ownsRegistry = options.watcherRegistry === undefined;\n // The URIs this connection holds a `resources/subscribe` lease for. The wire\n // verb is per-URI and idempotent — a second subscribe is the same\n // subscription, and one unsubscribe ends it — but the registry ref-counts\n // leases (the HTTP listen leg needs that). Without this set a double\n // subscribe took two leases that one unsubscribe could not release, and an\n // unsubscribe for a URI never subscribed released a lease this connection\n // never took, dropping a watcher some other holder still wanted.\n const leasedUris = new Set<string>();\n // Subscribes mid-acquire, keyed by URI. Two concurrent subscribes for the\n // same URI must share one acquire: both passing the `leasedUris` check\n // before either finished took two registry leases that the single set entry\n // could release only once — on the shared modern registry that orphan lease\n // pinned a watcher until process shutdown.\n const inflight = new Map<string, Promise<boolean | undefined>>();\n\n return {\n name: 'filesystem-mcp-file',\n title: 'Workspace File',\n description: 'Read a file from the workspace. Subscribe to get updates when the file changes.',\n uriTemplate: FILESYSTEM_FILE_URI_TEMPLATE,\n annotations: { audience: ['assistant'], priority: 0.8 },\n // Without this the SDK falls back to its conservative `{ ttlMs: 0,\n // cacheScope: 'private' }`, making every file read uncacheable — and this\n // is the one resource with a live invalidation channel, so TTL and the\n // watcher compose exactly as the spec's caching section describes. The TTL\n // stays short because a non-subscriber gets no invalidation signal at all.\n cacheHint: { cacheScope: options.cacheScope ?? 'public', ttlMs: 5_000 },\n\n // No `list`: the template registers with `list: undefined` — readable but\n // not enumerable. Listing each allowed root as a concrete resource\n // duplicated the template and grew resources/list linearly with roots;\n // `list_roots` owns root discovery.\n\n async read(uri, _variables, _ctx: ServerContext) {\n // Decode via extractPath — the same decoder resources/subscribe uses — so\n // both consumers are symmetric with buildFileResourceUri's encoding. The\n // {+path} template variable arrives still percent-encoded, so validating\n // it directly would treat \"c%3A/proj/a.ts\" as a literal relative path;\n // no fallback to it here, or the two branches would validate different\n // strings. extractPath only fails past the template match on undecodable\n // percent-encoding, which is the caller's malformed URI.\n const rawPath = extractPath(uri.href);\n if (rawPath === undefined) {\n throw new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n 'Malformed file URI: path is not valid percent-encoding',\n );\n }\n await options.pathGuard.validateExistingPath(rawPath);\n const fs = new GuardedFileSystem(options.pathGuard);\n const readResult = await fs.readRaw(rawPath);\n\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: readResult.mimeType || 'application/octet-stream',\n ...(readResult.isBinary\n ? { blob: readResult.content.toString('base64') }\n : { text: readResult.content.toString('utf-8') }),\n },\n ],\n };\n },\n\n complete: {\n // Both ends speak the `{+path}` form (see encodeFileUriPath), not raw OS\n // paths: the partial arriving here is whatever this returned last, so the\n // decode mirrors the encode. An undecodable partial is matched as typed.\n path: async (value) => {\n const suggestions = await suggestPaths(\n options.pathGuard,\n decodeFileUriPath(value) ?? value,\n );\n return suggestions.map(encodeFileUriPath);\n },\n },\n\n subscribe(uri, notify) {\n // Already subscribed on this connection: the watcher is live and the sink\n // is registered, so this is a no-op success rather than a second lease.\n if (leasedUris.has(uri)) return undefined;\n // A subscribe for this URI is mid-acquire: share its outcome instead of\n // taking a second lease the lease set cannot represent.\n const pending = inflight.get(uri);\n if (pending) return pending;\n\n const acquire = async (): Promise<boolean | undefined> => {\n const result = await registry.acquire(options.pathGuard, uri, notify, {\n markSubscribe: true,\n });\n if (result.ok) {\n leasedUris.add(uri);\n return undefined;\n }\n\n if (result.reason === 'bad-uri') {\n throw new ResourceNotFoundError(uri, `Cannot subscribe: not a filesystem URI`);\n }\n if (result.reason === 'invalid-path') {\n const err = result.error;\n if (isNotFoundish(err)) {\n throw new ResourceNotFoundError(uri, `Cannot subscribe to ${uri}: ${err.message}`);\n }\n Logger.warn(\n `Unexpected error validating path for watcher ${uri}: ${formatUnknownErrorMessage(err)}`,\n );\n throw err;\n }\n // Unsubscribed (or the registry destroyed) mid-await: the caller already\n // asked for this to stop, so there is nothing to reject.\n if (result.reason === 'stale') return undefined;\n // capped / attach-failed: `false` tells the handler to reject, since\n // undefined would report success with no watcher attached.\n return false;\n };\n\n const promise = acquire().finally(() => inflight.delete(uri));\n inflight.set(uri, promise);\n return promise;\n },\n\n unsubscribe(uri) {\n // Only release a lease this connection actually took.\n if (!leasedUris.delete(uri)) return;\n registry.release(uri);\n },\n\n destroy() {\n // Release what this connection still holds before the registry goes: on\n // the shared (modern) registry nothing else would ever end these leases.\n for (const uri of leasedUris) registry.release(uri);\n leasedUris.clear();\n if (ownsRegistry) registry.destroy();\n },\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// result\n// ═══════════════════════════════════════════════════════════════\n\nfunction createResultResource(options: ResourceRegistrationOptions): ResourceContract {\n return {\n name: 'filesystem-mcp-result',\n title: 'Cached Tool Result',\n description:\n 'Ephemeral cached tool output. Listed via resources/list; entries expire after the cache TTL.',\n mimeType: 'application/json',\n uriTemplate: 'filesystem-mcp://result/{id}',\n annotations: { audience: ['assistant'], priority: 0.3 },\n cacheHint: { cacheScope: 'private', ttlMs: 60_000 },\n list() {\n const store = options.resourceStore;\n const uris = store.keys(); // prunes expired first\n const resources: Resource[] = [];\n for (const uri of uris) {\n try {\n const entry = store.getEntry(uri);\n resources.push({\n uri: entry.uri,\n name: entry.name,\n mimeType: entry.mimeType,\n size: entry.size,\n });\n } catch (err) {\n // An entry may expire between keys() and getEntry; skip it.\n if (isFsError(err) && err.code === ErrorCode.NOT_FOUND) continue;\n throw err;\n }\n }\n // Recent-first: keys() returns insertion order, so the newest is last.\n resources.reverse();\n return { resources };\n },\n read(uri, variables) {\n const { id } = variables;\n if (typeof id !== 'string' || id.length === 0) {\n throw new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n 'Malformed result URI: missing {id}. Use the exact resourceUri returned by the tool.',\n );\n }\n\n let entry;\n try {\n entry = options.resourceStore.getEntry(uri.toString());\n } catch (err) {\n if (isFsError(err) && err.code === ErrorCode.NOT_FOUND) {\n throw new ResourceNotFoundError(\n uri.toString(),\n 'Cached result not found or expired. Re-run the tool to regenerate.',\n );\n }\n throw err;\n }\n return {\n contents: [{ uri: entry.uri, mimeType: entry.mimeType, text: entry.text }],\n };\n },\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// export contracts\n// ═══════════════════════════════════════════════════════════════\n\nexport function getResourceContracts(options: ResourceRegistrationOptions): ResourceContract[] {\n return [\n createInstructionsResource(options),\n createResultResource(options),\n createFilesystemResource(options),\n ];\n}\n\n// ═══════════════════════════════════════════════════════════════\n// registrar\n// ═══════════════════════════════════════════════════════════════\n\nfunction wrapRead(contract: ResourceContract) {\n return async (uri: URL, variables: Record<string, string | string[]>, ctx: ServerContext) => {\n try {\n return await contract.read(uri, variables, ctx);\n } catch (error) {\n // The SDK brands ProtocolError with `static [Symbol.hasInstance]`, so this\n // holds across separately bundled SDK copies and realms.\n if (error instanceof ProtocolError) throw error;\n // A missing or out-of-root path is a not-found, not a malformed request.\n // The SDK puts ResourceNotFoundError on the wire as -32602 with\n // `data.uri`, which is what clients match on; -32002 is the older code\n // they also accept, and this SDK never emits it.\n if (isNotFoundish(error)) {\n throw new ResourceNotFoundError(uri.toString(), error.message);\n }\n // A remaining FsError (NOT_FILE, TOO_LARGE, ...) traces to the\n // caller-supplied URI; anything else is a server-side failure and must\n // not be blamed on the request.\n const msg = isFsError(error) ? error.message : formatUnknownErrorMessage(error);\n throw new ProtocolError(fsErrorCode(error), msg);\n }\n };\n}\n\n/**\n * Whether `contract` answers for `uri`, decided by the template matcher the SDK\n * dispatches `resources/read` with, so `resources/subscribe` and\n * `resources/read` agree on which resource owns a URI. Not\n * `checkResourceAllowed`: that is an RFC 8707 audience check comparing\n * `URL.origin`, which is the opaque `\"null\"` for every custom scheme, so it\n * matched `filesystem-mcp://result/…` — and any other scheme — to the file\n * template.\n */\nfunction owns(contract: ResourceContract, uri: string): boolean {\n if (contract.uri !== undefined) return uri === contract.uri;\n return (\n contract.uriTemplate !== undefined && new UriTemplate(contract.uriTemplate).match(uri) !== null\n );\n}\n\nexport function registerResources(deps: ResourceRegistrarDeps): { dispose(): void } {\n const server = deps.server;\n const options = { ...deps, readOnly: deps.readOnly ?? false };\n const file = createFilesystemResource(options);\n const resourceContracts = [\n createInstructionsResource(options),\n createResultResource(options),\n file,\n ];\n\n for (const contract of resourceContracts) {\n const config = {\n ...(contract.title !== undefined ? { title: contract.title } : {}),\n ...(contract.description !== undefined ? { description: contract.description } : {}),\n ...(contract.mimeType !== undefined ? { mimeType: contract.mimeType } : {}),\n ...(contract.annotations !== undefined ? { annotations: contract.annotations } : {}),\n ...(contract.cacheHint !== undefined ? { cacheHint: contract.cacheHint } : {}),\n };\n\n if (contract.uriTemplate) {\n const template = new ResourceTemplate(contract.uriTemplate, {\n list: contract.list,\n ...(contract.complete ? { complete: contract.complete } : {}),\n });\n\n server.registerResource(contract.name, template, config, wrapRead(contract));\n } else if (contract.uri) {\n server.registerResource(contract.name, contract.uri, config, (uri: URL, ctx: ServerContext) =>\n wrapRead(contract)(uri, {}, ctx),\n );\n }\n }\n\n // `resources/subscribe`/`unsubscribe` are 2025-era-only verbs; a modern\n // server answers `-32601 Method not found` for them, so registering these\n // handlers on a modern-era instance would dispatch to code no request can\n // reach. A legacy instance on the HTTP leg (the one built with a `notifier`)\n // lives for one request: a subscription accepted there would take a watcher\n // lease for an instance that is gone before the file changes. Leave the\n // handlers off so the SDK refuses the verb, and no lease is taken. The\n // predicate mirrors the capability gate in server.ts exactly, so the\n // handler is registered if and only if `resources.subscribe` is advertised.\n // sunset(SEP-2577): removal trigger in docs/adr/002-legacy-protocol-paths-sunset.md.\n const legacyHttp = deps.era === 'legacy' && deps.notifier !== undefined;\n if (deps.era !== 'modern' && !legacyHttp) {\n server.server.assertCanSetRequestHandler('resources/subscribe');\n server.server.assertCanSetRequestHandler('resources/unsubscribe');\n\n // One stable callback for the whole registrar, NOT one per subscribe. The\n // registry holds these in a Set keyed by identity, so a fresh closure per\n // request made a second `resources/subscribe` for the same URI register a\n // second sink: one file change then sent N notifications, and `unsubscribe`\n // (which only ends a lease) removed none of them. Subscribe is per-URI on\n // the wire, so the sink must be too — this makes it idempotent.\n const notifyUpdated = (updatedUri: string): void => {\n if (deps.notifier) {\n deps.notifier.resourceUpdated(updatedUri);\n return;\n }\n const updatePayload: ResourceUpdatedNotificationParams = { uri: updatedUri };\n // A failed notify means the connection went away; nothing to recover.\n void server.server.sendResourceUpdated(updatePayload).catch((err: unknown) => {\n Logger.debug('resource update not delivered', {\n uri: updatedUri,\n error: formatUnknownErrorMessage(err),\n });\n });\n };\n\n server.server.setRequestHandler(\n 'resources/subscribe',\n async (req: { params: SubscribeRequestParams }) => {\n const { uri } = req.params;\n if (!owns(file, uri)) {\n // A resource that exists but has no watcher (the instructions text,\n // a cached result) is NOT a not-found: reporting it as one told\n // clients a URI they can list and read does not exist.\n if (resourceContracts.some((contract) => owns(contract, uri))) {\n throw new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n `Resource ${uri} does not support subscriptions; only ${FILESYSTEM_FILE_URI_TEMPLATE} does. Read it again for the current contents.`,\n );\n }\n throw new ResourceNotFoundError(uri, `Resource not found: ${uri}`);\n }\n if ((await file.subscribe(uri, notifyUpdated)) === false) {\n // InternalError for want of anything better: ProtocolErrorCode has\n // no resource-limit member, and the message already names the\n // actionable cause.\n throw new ProtocolError(\n ProtocolErrorCode.InternalError,\n `Subscription rejected: no watcher attached (watcher limit ${MAX_WATCHERS} reached, or fs.watch failed to start).`,\n );\n }\n return {};\n },\n );\n\n server.server.setRequestHandler(\n 'resources/unsubscribe',\n (req: { params: UnsubscribeRequestParams }) => {\n if (owns(file, req.params.uri)) file.unsubscribe(req.params.uri);\n return {};\n },\n );\n }\n\n return {\n dispose(): void {\n for (const contract of resourceContracts) {\n if (contract.destroy) {\n contract.destroy();\n }\n }\n },\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAKzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAgBlE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,mBAAmB,IAAI,IAAI,CAAC;CAC7B;AAED,wBAAsB,YAAY,CAChC,OAAO,GAAE,aAAkB,EAC3B,SAAS,CAAC,EAAE;IACV,0EAA0E;IAC1E,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;OAGG;IACH,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,qFAAqF;IACrF,GAAG,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACA,OAAO,CAAC,uBAAuB,CAAC,CAkHlC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAKzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAgBlE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,mBAAmB,IAAI,IAAI,CAAC;CAC7B;AAED,wBAAsB,YAAY,CAChC,OAAO,GAAE,aAAkB,EAC3B,SAAS,CAAC,EAAE;IACV,0EAA0E;IAC1E,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;OAGG;IACH,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,qFAAqF;IACrF,GAAG,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACA,OAAO,CAAC,uBAAuB,CAAC,CAmHlC"}
package/dist/server.js CHANGED
@@ -13,6 +13,10 @@ import { registerTools } from "./tools/index.js";
13
13
  // ═══════════════════════════════════════════════════════════════
14
14
  const { version: SERVER_VERSION, description: SERVER_DESCRIPTION, homepage: SERVER_HOMEPAGE, } = packageJson;
15
15
  export async function createServer(options = {}, extraDeps) {
16
+ // Only `resources` is declared: `McpServer` advertises `tools` and `prompts`
17
+ // (with `listChanged: true`) and `completions` from the registrations below,
18
+ // but never infers `subscribe`.
19
+ //
16
20
  // `resources.subscribe` stays advertised on both eras: the verb itself is
17
21
  // 2025-only, but with enforceStrictCapabilities the SDK also gates outbound
18
22
  // `notifications/resources/updated` — which the modern `subscriptions/listen`
@@ -24,9 +28,6 @@ export async function createServer(options = {}, extraDeps) {
24
28
  const legacyHttp = extraDeps?.era === 'legacy' && extraDeps.notifier !== undefined;
25
29
  const capabilities = {
26
30
  resources: { subscribe: !legacyHttp, listChanged: !legacyHttp },
27
- tools: {},
28
- prompts: {},
29
- completions: {},
30
31
  };
31
32
  const cacheScope = extraDeps?.apiKey ? 'private' : 'public';
32
33
  const serverConfig = {
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n Implementation,\n ServerCapabilities,\n ServerNotifier,\n} from '@modelcontextprotocol/server';\nimport { McpServer } from '@modelcontextprotocol/server';\n\nimport packageJson from '../package.json' with { type: 'json' };\nimport { requestStateCodec } from './core/input-required.ts';\nimport { Logger } from './core/observability.ts';\nimport type { ServerOptions } from './core/path.ts';\nimport { PathGuard } from './core/path.ts';\nimport { PageSnapshotStore, ResourceStore } from './core/store.ts';\nimport type { WatcherRegistry } from './core/watcher-registry.ts';\nimport { INSTRUCTIONS_SUMMARY, INSTRUCTIONS_URI } from './instructions.ts';\nimport { registerPrompts } from './prompts.ts';\nimport { registerResources } from './resources.ts';\nimport { registerTools } from './tools/index.ts';\n\n// ═══════════════════════════════════════════════════════════════\n// bootstrap\n// ═══════════════════════════════════════════════════════════════\n\nconst {\n version: SERVER_VERSION,\n description: SERVER_DESCRIPTION,\n homepage: SERVER_HOMEPAGE,\n} = packageJson;\n\nexport interface FilesystemServerContext {\n readonly mcp: McpServer;\n readonly pathGuard: PathGuard;\n disposeRuntimeState(): void;\n}\n\nexport async function createServer(\n options: ServerOptions = {},\n extraDeps?: {\n /** Shared file-watcher registry for the modern (per-request) HTTP leg. */\n watcherRegistry?: WatcherRegistry;\n /** Modern-leg typed notification publisher. */\n notifier?: ServerNotifier;\n /**\n * Guard to use instead of constructing one. The HTTP leg builds a single\n * guard for the whole endpoint and passes it to every per-request instance,\n * so an accepted access grant survives the request that accepted it (R8) and\n * the listen-watcher path validates against the same allowed set. Omitted on\n * stdio, where the instance is pinned for the connection and owns its guard.\n */\n pathGuard?: PathGuard;\n /**\n * Store to use instead of constructing one. The HTTP modern leg shares one\n * store across every per-request instance, so a result a tool externalized\n * in one POST survives to the follow-up resources/read. Omitted on stdio,\n * where the pinned instance owns its store for the connection.\n */\n resourceStore?: ResourceStore;\n /**\n * Pagination snapshots shared by every modern HTTP request. Omitted on\n * stdio, where the connection-owned context clears its store on close.\n */\n pageStore?: PageSnapshotStore;\n /** The protocol era this instance serves; omitted where the caller does not know. */\n era?: 'legacy' | 'modern';\n /**\n * The resolved HTTP credential, from `--api-key` or `API_KEY`. Lives here\n * rather than on `ServerOptions` because that object is `PathGuard`'s\n * constructor argument and is exposed on its public `options` field — a\n * bearer secret has no business being reachable from a tool handler.\n */\n apiKey?: string;\n },\n): Promise<FilesystemServerContext> {\n // `resources.subscribe` stays advertised on both eras: the verb itself is\n // 2025-only, but with enforceStrictCapabilities the SDK also gates outbound\n // `notifications/resources/updated` — which the modern `subscriptions/listen`\n // stream delivers — on this same capability bit. The one exception is a\n // legacy instance on the HTTP leg (era 'legacy' with a notifier): it serves\n // one request, registers no subscribe handler (resources.ts, same\n // predicate) and sends no notification of any kind — there is no stream to\n // send it on — so neither `subscribe` nor `listChanged` is advertised.\n const legacyHttp = extraDeps?.era === 'legacy' && extraDeps.notifier !== undefined;\n const capabilities = {\n resources: { subscribe: !legacyHttp, listChanged: !legacyHttp },\n tools: {},\n prompts: {},\n completions: {},\n } satisfies ServerCapabilities;\n\n const cacheScope: 'private' | 'public' = extraDeps?.apiKey ? 'private' : 'public';\n const serverConfig: NonNullable<ConstructorParameters<typeof McpServer>[1]> = {\n capabilities,\n enforceStrictCapabilities: true,\n cacheHints: {\n 'tools/list': { ttlMs: 3_600_000, cacheScope },\n 'prompts/list': { ttlMs: 3_600_000, cacheScope },\n // Always private, never `cacheScope`: this list enumerates the\n // ResourceStore, so its entries are one caller's externalized tool output\n // — the same entries whose own read hint is `private` (resources.ts). The\n // other four lists are the same for every caller and follow the key, and\n // they are built from frozen module-level arrays that cannot change at\n // runtime, so an hour is honest: a shorter hint only buys the client a\n // re-fetch of bytes that cannot have differed, at the cost of the prompt\n // cache stability the spec asks deterministic ordering to preserve.\n 'resources/list': { ttlMs: 30_000, cacheScope: 'private' },\n 'resources/templates/list': { ttlMs: 3_600_000, cacheScope },\n 'server/discover': { ttlMs: 3_600_000, cacheScope },\n },\n // Multi-round-trip `requestState` integrity (protocol revision 2026-07-28):\n // the codec verifies the HMAC on every retried round before the handler\n // runs, so a tampered or expired state is rejected as `-32602` rather than\n // trusted. The decoded `PendingState` reaches handlers via\n // `ctx.mcpReq.requestState<T>()`.\n // eslint-disable-next-line @typescript-eslint/unbound-method\n requestState: { verify: requestStateCodec.verify },\n };\n\n serverConfig.instructions =\n `${INSTRUCTIONS_SUMMARY} ` +\n `For full guidance, read ${INSTRUCTIONS_URI} or run the get-help prompt.`;\n\n const implementation: Implementation = {\n name: 'filesystem-mcp',\n title: 'Filesystem MCP',\n version: SERVER_VERSION,\n ...(SERVER_DESCRIPTION ? { description: SERVER_DESCRIPTION } : {}),\n ...(SERVER_HOMEPAGE ? { websiteUrl: SERVER_HOMEPAGE } : {}),\n };\n const server = new McpServer(implementation, serverConfig);\n server.server.fallbackNotificationHandler = (notification) => {\n Logger.debug('Unhandled client notification', { method: notification.method });\n return Promise.resolve();\n };\n\n // A failed send means the connection went away; nothing to recover.\n const resourceStore =\n extraDeps?.resourceStore ??\n new ResourceStore(() => {\n if (extraDeps?.notifier) {\n extraDeps.notifier.resourcesChanged();\n return;\n }\n void server.server.sendResourceListChanged().catch((error: unknown) => {\n Logger.debug('resource list_changed not delivered', { error: String(error) });\n });\n });\n const pageStore = extraDeps?.pageStore ?? new PageSnapshotStore();\n\n const pathGuard = extraDeps?.pathGuard ?? new PathGuard(options);\n // Recompute once per guard, keyed on the guard's own state rather than on\n // whether it was injected — an injected-but-uninitialized guard would\n // otherwise deny every path. Not unconditional: the HTTP leg shares one guard\n // across a fresh instance per request, and a repeat recompute would both redo\n // the root resolution per request and race a concurrent `applyGrant`, which\n // commits under a mutation lock this call does not take.\n if (!pathGuard.isInitialized()) await pathGuard.recomputeAllowedDirectories();\n\n const deps = {\n server,\n pathGuard,\n pageStore,\n resourceStore,\n cacheScope,\n ...(options.readOnly ? { readOnly: true } : {}),\n ...(extraDeps?.watcherRegistry ? { watcherRegistry: extraDeps.watcherRegistry } : {}),\n ...(extraDeps?.notifier ? { notifier: extraDeps.notifier } : {}),\n ...(extraDeps?.era ? { era: extraDeps.era } : {}),\n };\n\n const resourceDisposable = registerResources(deps);\n registerPrompts(deps);\n registerTools(deps);\n\n // False when the store is shared across instances and outlives this one.\n const ownsPages = extraDeps?.pageStore === undefined;\n let cleanedUp = false;\n return {\n mcp: server,\n pathGuard,\n disposeRuntimeState() {\n if (cleanedUp) return;\n cleanedUp = true;\n if (ownsPages) pageStore.clear();\n resourceDisposable.dispose();\n },\n };\n}\n"]}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n Implementation,\n ServerCapabilities,\n ServerNotifier,\n} from '@modelcontextprotocol/server';\nimport { McpServer } from '@modelcontextprotocol/server';\n\nimport packageJson from '../package.json' with { type: 'json' };\nimport { requestStateCodec } from './core/input-required.ts';\nimport { Logger } from './core/observability.ts';\nimport type { ServerOptions } from './core/path.ts';\nimport { PathGuard } from './core/path.ts';\nimport { PageSnapshotStore, ResourceStore } from './core/store.ts';\nimport type { WatcherRegistry } from './core/watcher-registry.ts';\nimport { INSTRUCTIONS_SUMMARY, INSTRUCTIONS_URI } from './instructions.ts';\nimport { registerPrompts } from './prompts.ts';\nimport { registerResources } from './resources.ts';\nimport { registerTools } from './tools/index.ts';\n\n// ═══════════════════════════════════════════════════════════════\n// bootstrap\n// ═══════════════════════════════════════════════════════════════\n\nconst {\n version: SERVER_VERSION,\n description: SERVER_DESCRIPTION,\n homepage: SERVER_HOMEPAGE,\n} = packageJson;\n\nexport interface FilesystemServerContext {\n readonly mcp: McpServer;\n readonly pathGuard: PathGuard;\n disposeRuntimeState(): void;\n}\n\nexport async function createServer(\n options: ServerOptions = {},\n extraDeps?: {\n /** Shared file-watcher registry for the modern (per-request) HTTP leg. */\n watcherRegistry?: WatcherRegistry;\n /** Modern-leg typed notification publisher. */\n notifier?: ServerNotifier;\n /**\n * Guard to use instead of constructing one. The HTTP leg builds a single\n * guard for the whole endpoint and passes it to every per-request instance,\n * so an accepted access grant survives the request that accepted it (R8) and\n * the listen-watcher path validates against the same allowed set. Omitted on\n * stdio, where the instance is pinned for the connection and owns its guard.\n */\n pathGuard?: PathGuard;\n /**\n * Store to use instead of constructing one. The HTTP modern leg shares one\n * store across every per-request instance, so a result a tool externalized\n * in one POST survives to the follow-up resources/read. Omitted on stdio,\n * where the pinned instance owns its store for the connection.\n */\n resourceStore?: ResourceStore;\n /**\n * Pagination snapshots shared by every modern HTTP request. Omitted on\n * stdio, where the connection-owned context clears its store on close.\n */\n pageStore?: PageSnapshotStore;\n /** The protocol era this instance serves; omitted where the caller does not know. */\n era?: 'legacy' | 'modern';\n /**\n * The resolved HTTP credential, from `--api-key` or `API_KEY`. Lives here\n * rather than on `ServerOptions` because that object is `PathGuard`'s\n * constructor argument and is exposed on its public `options` field — a\n * bearer secret has no business being reachable from a tool handler.\n */\n apiKey?: string;\n },\n): Promise<FilesystemServerContext> {\n // Only `resources` is declared: `McpServer` advertises `tools` and `prompts`\n // (with `listChanged: true`) and `completions` from the registrations below,\n // but never infers `subscribe`.\n //\n // `resources.subscribe` stays advertised on both eras: the verb itself is\n // 2025-only, but with enforceStrictCapabilities the SDK also gates outbound\n // `notifications/resources/updated` — which the modern `subscriptions/listen`\n // stream delivers — on this same capability bit. The one exception is a\n // legacy instance on the HTTP leg (era 'legacy' with a notifier): it serves\n // one request, registers no subscribe handler (resources.ts, same\n // predicate) and sends no notification of any kind — there is no stream to\n // send it on — so neither `subscribe` nor `listChanged` is advertised.\n const legacyHttp = extraDeps?.era === 'legacy' && extraDeps.notifier !== undefined;\n const capabilities = {\n resources: { subscribe: !legacyHttp, listChanged: !legacyHttp },\n } satisfies ServerCapabilities;\n\n const cacheScope: 'private' | 'public' = extraDeps?.apiKey ? 'private' : 'public';\n const serverConfig: NonNullable<ConstructorParameters<typeof McpServer>[1]> = {\n capabilities,\n enforceStrictCapabilities: true,\n cacheHints: {\n 'tools/list': { ttlMs: 3_600_000, cacheScope },\n 'prompts/list': { ttlMs: 3_600_000, cacheScope },\n // Always private, never `cacheScope`: this list enumerates the\n // ResourceStore, so its entries are one caller's externalized tool output\n // — the same entries whose own read hint is `private` (resources.ts). The\n // other four lists are the same for every caller and follow the key, and\n // they are built from frozen module-level arrays that cannot change at\n // runtime, so an hour is honest: a shorter hint only buys the client a\n // re-fetch of bytes that cannot have differed, at the cost of the prompt\n // cache stability the spec asks deterministic ordering to preserve.\n 'resources/list': { ttlMs: 30_000, cacheScope: 'private' },\n 'resources/templates/list': { ttlMs: 3_600_000, cacheScope },\n 'server/discover': { ttlMs: 3_600_000, cacheScope },\n },\n // Multi-round-trip `requestState` integrity (protocol revision 2026-07-28):\n // the codec verifies the HMAC on every retried round before the handler\n // runs, so a tampered or expired state is rejected as `-32602` rather than\n // trusted. The decoded `PendingState` reaches handlers via\n // `ctx.mcpReq.requestState<T>()`.\n // eslint-disable-next-line @typescript-eslint/unbound-method\n requestState: { verify: requestStateCodec.verify },\n };\n\n serverConfig.instructions =\n `${INSTRUCTIONS_SUMMARY} ` +\n `For full guidance, read ${INSTRUCTIONS_URI} or run the get-help prompt.`;\n\n const implementation: Implementation = {\n name: 'filesystem-mcp',\n title: 'Filesystem MCP',\n version: SERVER_VERSION,\n ...(SERVER_DESCRIPTION ? { description: SERVER_DESCRIPTION } : {}),\n ...(SERVER_HOMEPAGE ? { websiteUrl: SERVER_HOMEPAGE } : {}),\n };\n const server = new McpServer(implementation, serverConfig);\n server.server.fallbackNotificationHandler = (notification) => {\n Logger.debug('Unhandled client notification', { method: notification.method });\n return Promise.resolve();\n };\n\n // A failed send means the connection went away; nothing to recover.\n const resourceStore =\n extraDeps?.resourceStore ??\n new ResourceStore(() => {\n if (extraDeps?.notifier) {\n extraDeps.notifier.resourcesChanged();\n return;\n }\n void server.server.sendResourceListChanged().catch((error: unknown) => {\n Logger.debug('resource list_changed not delivered', { error: String(error) });\n });\n });\n const pageStore = extraDeps?.pageStore ?? new PageSnapshotStore();\n\n const pathGuard = extraDeps?.pathGuard ?? new PathGuard(options);\n // Recompute once per guard, keyed on the guard's own state rather than on\n // whether it was injected — an injected-but-uninitialized guard would\n // otherwise deny every path. Not unconditional: the HTTP leg shares one guard\n // across a fresh instance per request, and a repeat recompute would both redo\n // the root resolution per request and race a concurrent `applyGrant`, which\n // commits under a mutation lock this call does not take.\n if (!pathGuard.isInitialized()) await pathGuard.recomputeAllowedDirectories();\n\n const deps = {\n server,\n pathGuard,\n pageStore,\n resourceStore,\n cacheScope,\n ...(options.readOnly ? { readOnly: true } : {}),\n ...(extraDeps?.watcherRegistry ? { watcherRegistry: extraDeps.watcherRegistry } : {}),\n ...(extraDeps?.notifier ? { notifier: extraDeps.notifier } : {}),\n ...(extraDeps?.era ? { era: extraDeps.era } : {}),\n };\n\n const resourceDisposable = registerResources(deps);\n registerPrompts(deps);\n registerTools(deps);\n\n // False when the store is shared across instances and outlives this one.\n const ownsPages = extraDeps?.pageStore === undefined;\n let cleanedUp = false;\n return {\n mcp: server,\n pathGuard,\n disposeRuntimeState() {\n if (cleanedUp) return;\n cleanedUp = true;\n if (ownsPages) pageStore.clear();\n resourceDisposable.dispose();\n },\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../../src/tools/batch.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAW,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE7F,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5B,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/D;AAED,KAAK,UAAU,CAAC,SAAS,IACvB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC,EAAE,CAAA;CAAE,CAAC;AAEvF,UAAU,mBAAmB;IAC3B,gBAAgB,CAAC,EAAE,SAAS,CAAC;CAC9B;AAeD,wBAAsB,YAAY,CAAC,SAAS,EAAE,QAAQ,EACpD,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,EAC3B,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,EAAE,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,EAC1F,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAoDhC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAEV"}
1
+ {"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../../src/tools/batch.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAW,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE7F,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5B,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/D;AAED,KAAK,UAAU,CAAC,SAAS,IACvB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC,EAAE,CAAA;CAAE,CAAC;AAEvF,UAAU,mBAAmB;IAC3B,gBAAgB,CAAC,EAAE,SAAS,CAAC;CAC9B;AAeD,wBAAsB,YAAY,CAAC,SAAS,EAAE,QAAQ,EACpD,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,EAC3B,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,EAAE,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,EAC1F,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAgDhC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAEV"}
@@ -26,7 +26,7 @@ export async function runOverPaths(args, ctx, perPath, options) {
26
26
  completed += 1;
27
27
  ctx.onProgress?.({ current: completed, total });
28
28
  };
29
- await processInParallel(items.map((item, index) => ({ item, index })), async ({ item, index }) => {
29
+ await processInParallel(items, async (item, index) => {
30
30
  try {
31
31
  const value = await perPath(item, ctx);
32
32
  results[index] = { path: item.path, value };
@@ -40,7 +40,6 @@ export async function runOverPaths(args, ctx, perPath, options) {
40
40
  finally {
41
41
  tick();
42
42
  }
43
- return undefined;
44
43
  }, PARALLEL_CONCURRENCY, ctx.signal);
45
44
  let succeeded = 0;
46
45
  for (const result of results) {
@@ -1 +1 @@
1
- {"version":3,"file":"batch.js","sourceRoot":"","sources":["../../src/tools/batch.ts"],"names":[],"mappings":"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","sourcesContent":["import { processInParallel } from '../core/concurrency.ts';\nimport { ErrorCode, FsError, Problem } from '../core/errors.ts';\nimport { PARALLEL_CONCURRENCY } from '../core/util.ts';\nimport type { ToolCtx } from './define.ts';\n\nexport type PerPathResult<T> = { path: string; value: T } | { path: string; error: Problem };\n\nexport interface BatchResult<T> {\n results: PerPathResult<T>[];\n summary: { total: number; succeeded: number; failed: number };\n}\n\ntype BatchInput<TOverride> =\n { path: string } | { paths: string[] } | { files: ({ path: string } & TOverride)[] };\n\ninterface RunOverPathsOptions {\n defaultErrorCode?: ErrorCode;\n}\n\nfunction normalizeBatchItems<TOverride>(\n args: BatchInput<TOverride>,\n): { path: string; override?: TOverride }[] {\n if ('path' in args) return [{ path: args.path }];\n if ('paths' in args) return args.paths.map((path) => ({ path }));\n if ('files' in args)\n return args.files.map(({ path, ...rest }) => ({\n path,\n override: rest as TOverride,\n }));\n return [];\n}\n\nexport async function runOverPaths<TOverride, TPerPath>(\n args: BatchInput<TOverride>,\n ctx: ToolCtx,\n perPath: (item: { path: string; override?: TOverride }, ctx: ToolCtx) => Promise<TPerPath>,\n options?: RunOverPathsOptions,\n): Promise<BatchResult<TPerPath>> {\n const items = normalizeBatchItems(args);\n if (items.length === 0) {\n throw new FsError(\n ErrorCode.INVALID_INPUT,\n \"runOverPaths: at least one of 'path', 'paths', or 'files' must be provided\",\n );\n }\n\n const defaultErrorCode = options?.defaultErrorCode ?? ErrorCode.UNKNOWN;\n\n const total = items.length;\n let completed = 0;\n const results: PerPathResult<TPerPath>[] = new Array<PerPathResult<TPerPath>>(total);\n\n const tick = (): void => {\n completed += 1;\n ctx.onProgress?.({ current: completed, total });\n };\n\n await processInParallel<\n { item: { path: string; override?: TOverride }; index: number },\n undefined\n >(\n items.map((item, index) => ({ item, index })),\n async ({ item, index }) => {\n try {\n const value = await perPath(item, ctx);\n results[index] = { path: item.path, value };\n } catch (error: unknown) {\n results[index] = {\n path: item.path,\n error: Problem.fromUnknown(error, defaultErrorCode, item.path),\n };\n } finally {\n tick();\n }\n return undefined;\n },\n PARALLEL_CONCURRENCY,\n ctx.signal,\n );\n\n let succeeded = 0;\n for (const result of results) {\n if (!('error' in result)) succeeded += 1;\n }\n\n return {\n results,\n summary: { total, succeeded, failed: total - succeeded },\n };\n}\n\n/**\n * A batch where every requested item failed produced no work at all, so it is a\n * failed call - `isError` must say so. Partial failure is deliberately NOT an\n * error: the per-item entries carry which failed, and the succeeded ones really\n * were done. Zero items is not a failure either.\n */\nexport function isTotalFailure(summary: {\n readonly total: number;\n readonly failed: number;\n}): boolean {\n return summary.total > 0 && summary.failed === summary.total;\n}\n"]}
1
+ {"version":3,"file":"batch.js","sourceRoot":"","sources":["../../src/tools/batch.ts"],"names":[],"mappings":"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","sourcesContent":["import { processInParallel } from '../core/concurrency.ts';\nimport { ErrorCode, FsError, Problem } from '../core/errors.ts';\nimport { PARALLEL_CONCURRENCY } from '../core/util.ts';\nimport type { ToolCtx } from './define.ts';\n\nexport type PerPathResult<T> = { path: string; value: T } | { path: string; error: Problem };\n\nexport interface BatchResult<T> {\n results: PerPathResult<T>[];\n summary: { total: number; succeeded: number; failed: number };\n}\n\ntype BatchInput<TOverride> =\n { path: string } | { paths: string[] } | { files: ({ path: string } & TOverride)[] };\n\ninterface RunOverPathsOptions {\n defaultErrorCode?: ErrorCode;\n}\n\nfunction normalizeBatchItems<TOverride>(\n args: BatchInput<TOverride>,\n): { path: string; override?: TOverride }[] {\n if ('path' in args) return [{ path: args.path }];\n if ('paths' in args) return args.paths.map((path) => ({ path }));\n if ('files' in args)\n return args.files.map(({ path, ...rest }) => ({\n path,\n override: rest as TOverride,\n }));\n return [];\n}\n\nexport async function runOverPaths<TOverride, TPerPath>(\n args: BatchInput<TOverride>,\n ctx: ToolCtx,\n perPath: (item: { path: string; override?: TOverride }, ctx: ToolCtx) => Promise<TPerPath>,\n options?: RunOverPathsOptions,\n): Promise<BatchResult<TPerPath>> {\n const items = normalizeBatchItems(args);\n if (items.length === 0) {\n throw new FsError(\n ErrorCode.INVALID_INPUT,\n \"runOverPaths: at least one of 'path', 'paths', or 'files' must be provided\",\n );\n }\n\n const defaultErrorCode = options?.defaultErrorCode ?? ErrorCode.UNKNOWN;\n\n const total = items.length;\n let completed = 0;\n const results: PerPathResult<TPerPath>[] = new Array<PerPathResult<TPerPath>>(total);\n\n const tick = (): void => {\n completed += 1;\n ctx.onProgress?.({ current: completed, total });\n };\n\n await processInParallel(\n items,\n async (item, index) => {\n try {\n const value = await perPath(item, ctx);\n results[index] = { path: item.path, value };\n } catch (error: unknown) {\n results[index] = {\n path: item.path,\n error: Problem.fromUnknown(error, defaultErrorCode, item.path),\n };\n } finally {\n tick();\n }\n },\n PARALLEL_CONCURRENCY,\n ctx.signal,\n );\n\n let succeeded = 0;\n for (const result of results) {\n if (!('error' in result)) succeeded += 1;\n }\n\n return {\n results,\n summary: { total, succeeded, failed: total - succeeded },\n };\n}\n\n/**\n * A batch where every requested item failed produced no work at all, so it is a\n * failed call - `isError` must say so. Partial failure is deliberately NOT an\n * error: the per-item entries carry which failed, and the succeeded ones really\n * were done. Zero items is not a failure either.\n */\nexport function isTotalFailure(summary: {\n readonly total: number;\n readonly failed: number;\n}): boolean {\n return summary.total > 0 && summary.failed === summary.total;\n}\n"]}