@j0hanz/filesystem-mcp 1.19.0 → 2.0.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 (301) hide show
  1. package/README.md +333 -838
  2. package/dist/cli-help.d.ts +3 -0
  3. package/dist/cli-help.d.ts.map +1 -0
  4. package/dist/cli-help.js +169 -0
  5. package/dist/cli-help.js.map +1 -0
  6. package/dist/cli.d.ts +28 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +168 -123
  9. package/dist/cli.js.map +1 -0
  10. package/dist/core/concurrency.d.ts +70 -0
  11. package/dist/core/concurrency.d.ts.map +1 -0
  12. package/dist/core/concurrency.js +173 -0
  13. package/dist/core/concurrency.js.map +1 -0
  14. package/dist/core/config.d.ts +19 -0
  15. package/dist/core/config.d.ts.map +1 -0
  16. package/dist/core/config.js +11 -0
  17. package/dist/core/config.js.map +1 -0
  18. package/dist/core/cursor.d.ts +23 -0
  19. package/dist/core/cursor.d.ts.map +1 -0
  20. package/dist/core/cursor.js +62 -0
  21. package/dist/core/cursor.js.map +1 -0
  22. package/dist/core/errors.d.ts +96 -0
  23. package/dist/core/errors.d.ts.map +1 -0
  24. package/dist/core/errors.js +356 -0
  25. package/dist/core/errors.js.map +1 -0
  26. package/dist/core/file-uri.d.ts +24 -0
  27. package/dist/core/file-uri.d.ts.map +1 -0
  28. package/dist/core/file-uri.js +52 -0
  29. package/dist/core/file-uri.js.map +1 -0
  30. package/dist/core/fmt.d.ts +38 -0
  31. package/dist/core/fmt.d.ts.map +1 -0
  32. package/dist/core/fmt.js +109 -0
  33. package/dist/core/fmt.js.map +1 -0
  34. package/dist/core/fs.d.ts +108 -0
  35. package/dist/core/fs.d.ts.map +1 -0
  36. package/dist/core/fs.js +243 -0
  37. package/dist/core/fs.js.map +1 -0
  38. package/dist/core/glob.d.ts +41 -0
  39. package/dist/core/glob.d.ts.map +1 -0
  40. package/dist/core/glob.js +399 -0
  41. package/dist/core/glob.js.map +1 -0
  42. package/dist/core/input-required.d.ts +147 -0
  43. package/dist/core/input-required.d.ts.map +1 -0
  44. package/dist/core/input-required.js +261 -0
  45. package/dist/core/input-required.js.map +1 -0
  46. package/dist/core/mime.d.ts +15 -0
  47. package/dist/core/mime.d.ts.map +1 -0
  48. package/dist/core/mime.js +188 -0
  49. package/dist/core/mime.js.map +1 -0
  50. package/dist/core/observability.d.ts +10 -0
  51. package/dist/core/observability.d.ts.map +1 -0
  52. package/dist/core/observability.js +76 -0
  53. package/dist/core/observability.js.map +1 -0
  54. package/dist/core/page-store.d.ts +41 -0
  55. package/dist/core/page-store.d.ts.map +1 -0
  56. package/dist/core/page-store.js +74 -0
  57. package/dist/core/page-store.js.map +1 -0
  58. package/dist/core/path-completer.d.ts +10 -0
  59. package/dist/core/path-completer.d.ts.map +1 -0
  60. package/dist/core/path-completer.js +227 -0
  61. package/dist/core/path-completer.js.map +1 -0
  62. package/dist/core/path-discovery.d.ts +8 -0
  63. package/dist/core/path-discovery.d.ts.map +1 -0
  64. package/dist/core/path-discovery.js +105 -0
  65. package/dist/core/path-discovery.js.map +1 -0
  66. package/dist/core/path-utils.d.ts +9 -0
  67. package/dist/core/path-utils.d.ts.map +1 -0
  68. package/dist/core/path-utils.js +121 -0
  69. package/dist/core/path-utils.js.map +1 -0
  70. package/dist/core/path.d.ts +148 -0
  71. package/dist/core/path.d.ts.map +1 -0
  72. package/dist/core/path.js +674 -0
  73. package/dist/core/path.js.map +1 -0
  74. package/dist/core/primitives.d.ts +23 -0
  75. package/dist/core/primitives.d.ts.map +1 -0
  76. package/dist/core/primitives.js +54 -0
  77. package/dist/core/primitives.js.map +1 -0
  78. package/dist/core/read.d.ts +74 -0
  79. package/dist/core/read.d.ts.map +1 -0
  80. package/dist/core/read.js +562 -0
  81. package/dist/core/read.js.map +1 -0
  82. package/dist/core/schema.d.ts +139 -0
  83. package/dist/core/schema.d.ts.map +1 -0
  84. package/dist/core/schema.js +345 -0
  85. package/dist/core/schema.js.map +1 -0
  86. package/dist/core/search.d.ts +100 -0
  87. package/dist/core/search.d.ts.map +1 -0
  88. package/dist/core/search.js +248 -0
  89. package/dist/core/search.js.map +1 -0
  90. package/dist/core/sensitive.d.ts +6 -0
  91. package/dist/core/sensitive.d.ts.map +1 -0
  92. package/dist/core/sensitive.js +142 -0
  93. package/dist/core/sensitive.js.map +1 -0
  94. package/dist/core/store.d.ts +57 -0
  95. package/dist/core/store.d.ts.map +1 -0
  96. package/dist/core/store.js +162 -0
  97. package/dist/core/store.js.map +1 -0
  98. package/dist/core/util.d.ts +26 -0
  99. package/dist/core/util.d.ts.map +1 -0
  100. package/dist/core/util.js +66 -0
  101. package/dist/core/util.js.map +1 -0
  102. package/dist/core/watcher-registry.d.ts +68 -0
  103. package/dist/core/watcher-registry.d.ts.map +1 -0
  104. package/dist/core/watcher-registry.js +312 -0
  105. package/dist/core/watcher-registry.js.map +1 -0
  106. package/dist/http-policy.d.ts +121 -0
  107. package/dist/http-policy.d.ts.map +1 -0
  108. package/dist/http-policy.js +340 -0
  109. package/dist/http-policy.js.map +1 -0
  110. package/dist/index.d.ts +1 -0
  111. package/dist/index.d.ts.map +1 -0
  112. package/dist/index.js +77 -41
  113. package/dist/index.js.map +1 -0
  114. package/dist/instructions.d.ts +12 -0
  115. package/dist/instructions.d.ts.map +1 -0
  116. package/dist/instructions.js +71 -0
  117. package/dist/instructions.js.map +1 -0
  118. package/dist/prompts.d.ts +8 -6
  119. package/dist/prompts.d.ts.map +1 -0
  120. package/dist/prompts.js +74 -168
  121. package/dist/prompts.js.map +1 -0
  122. package/dist/resources.d.ts +73 -9
  123. package/dist/resources.d.ts.map +1 -0
  124. package/dist/resources.js +392 -163
  125. package/dist/resources.js.map +1 -0
  126. package/dist/server.d.ts +62 -1
  127. package/dist/server.d.ts.map +1 -0
  128. package/dist/server.js +139 -1
  129. package/dist/server.js.map +1 -0
  130. package/dist/tools/batch.d.ts +89 -0
  131. package/dist/tools/batch.d.ts.map +1 -0
  132. package/dist/tools/batch.js +157 -0
  133. package/dist/tools/batch.js.map +1 -0
  134. package/dist/tools/create.d.ts +2 -0
  135. package/dist/tools/create.d.ts.map +1 -0
  136. package/dist/tools/create.js +142 -0
  137. package/dist/tools/create.js.map +1 -0
  138. package/dist/tools/define.d.ts +110 -0
  139. package/dist/tools/define.d.ts.map +1 -0
  140. package/dist/tools/define.js +394 -0
  141. package/dist/tools/define.js.map +1 -0
  142. package/dist/tools/delete-file.d.ts +2 -4
  143. package/dist/tools/delete-file.d.ts.map +1 -0
  144. package/dist/tools/delete-file.js +330 -87
  145. package/dist/tools/delete-file.js.map +1 -0
  146. package/dist/tools/diff.d.ts +2 -0
  147. package/dist/tools/diff.d.ts.map +1 -0
  148. package/dist/tools/diff.js +94 -0
  149. package/dist/tools/diff.js.map +1 -0
  150. package/dist/tools/edit.d.ts +2 -0
  151. package/dist/tools/edit.d.ts.map +1 -0
  152. package/dist/tools/edit.js +442 -0
  153. package/dist/tools/edit.js.map +1 -0
  154. package/dist/tools/index.d.ts +26 -0
  155. package/dist/tools/index.d.ts.map +1 -0
  156. package/dist/tools/index.js +52 -0
  157. package/dist/tools/index.js.map +1 -0
  158. package/dist/tools/list.d.ts +2 -0
  159. package/dist/tools/list.d.ts.map +1 -0
  160. package/dist/tools/list.js +291 -0
  161. package/dist/tools/list.js.map +1 -0
  162. package/dist/tools/move.d.ts +2 -0
  163. package/dist/tools/move.d.ts.map +1 -0
  164. package/dist/tools/move.js +321 -0
  165. package/dist/tools/move.js.map +1 -0
  166. package/dist/tools/patch.d.ts +2 -0
  167. package/dist/tools/patch.d.ts.map +1 -0
  168. package/dist/tools/patch.js +155 -0
  169. package/dist/tools/patch.js.map +1 -0
  170. package/dist/tools/progress.d.ts +52 -0
  171. package/dist/tools/progress.d.ts.map +1 -0
  172. package/dist/tools/progress.js +172 -0
  173. package/dist/tools/progress.js.map +1 -0
  174. package/dist/tools/read.d.ts +2 -4
  175. package/dist/tools/read.d.ts.map +1 -0
  176. package/dist/tools/read.js +398 -152
  177. package/dist/tools/read.js.map +1 -0
  178. package/dist/tools/replace-in-files.d.ts +2 -4
  179. package/dist/tools/replace-in-files.d.ts.map +1 -0
  180. package/dist/tools/replace-in-files.js +435 -264
  181. package/dist/tools/replace-in-files.js.map +1 -0
  182. package/dist/tools/roots.d.ts +2 -4
  183. package/dist/tools/roots.d.ts.map +1 -0
  184. package/dist/tools/roots.js +29 -52
  185. package/dist/tools/roots.js.map +1 -0
  186. package/dist/tools/search-content.d.ts +2 -4
  187. package/dist/tools/search-content.d.ts.map +1 -0
  188. package/dist/tools/search-content.js +262 -228
  189. package/dist/tools/search-content.js.map +1 -0
  190. package/dist/tools/search-files.d.ts +2 -4
  191. package/dist/tools/search-files.d.ts.map +1 -0
  192. package/dist/tools/search-files.js +162 -161
  193. package/dist/tools/search-files.js.map +1 -0
  194. package/dist/tools/stat.d.ts +2 -4
  195. package/dist/tools/stat.d.ts.map +1 -0
  196. package/dist/tools/stat.js +202 -62
  197. package/dist/tools/stat.js.map +1 -0
  198. package/dist/transport/http.d.ts +5 -0
  199. package/dist/transport/http.d.ts.map +1 -0
  200. package/dist/transport/http.js +276 -0
  201. package/dist/transport/http.js.map +1 -0
  202. package/dist/transport/shared.d.ts +45 -0
  203. package/dist/transport/shared.d.ts.map +1 -0
  204. package/dist/transport/shared.js +65 -0
  205. package/dist/transport/shared.js.map +1 -0
  206. package/dist/transport/stdio.d.ts +32 -0
  207. package/dist/transport/stdio.d.ts.map +1 -0
  208. package/dist/transport/stdio.js +279 -0
  209. package/dist/transport/stdio.js.map +1 -0
  210. package/dist/transport.d.ts +5 -0
  211. package/dist/transport.d.ts.map +1 -0
  212. package/dist/transport.js +4 -0
  213. package/dist/transport.js.map +1 -0
  214. package/package.json +37 -46
  215. package/dist/assets/logo.svg +0 -35
  216. package/dist/completions.d.ts +0 -2
  217. package/dist/completions.js +0 -572
  218. package/dist/config.d.ts +0 -119
  219. package/dist/config.js +0 -31
  220. package/dist/lib/abort.d.ts +0 -7
  221. package/dist/lib/abort.js +0 -81
  222. package/dist/lib/constants.d.ts +0 -42
  223. package/dist/lib/constants.js +0 -313
  224. package/dist/lib/errors.d.ts +0 -28
  225. package/dist/lib/errors.js +0 -301
  226. package/dist/lib/file-operations/core.d.ts +0 -53
  227. package/dist/lib/file-operations/core.js +0 -144
  228. package/dist/lib/file-operations/metadata.d.ts +0 -77
  229. package/dist/lib/file-operations/metadata.js +0 -878
  230. package/dist/lib/file-operations/search.d.ts +0 -46
  231. package/dist/lib/file-operations/search.js +0 -1194
  232. package/dist/lib/file-operations/traversal.d.ts +0 -40
  233. package/dist/lib/file-operations/traversal.js +0 -310
  234. package/dist/lib/fs-helpers.d.ts +0 -46
  235. package/dist/lib/fs-helpers.js +0 -606
  236. package/dist/lib/globs.d.ts +0 -2
  237. package/dist/lib/globs.js +0 -19
  238. package/dist/lib/logger.d.ts +0 -27
  239. package/dist/lib/logger.js +0 -91
  240. package/dist/lib/observability.d.ts +0 -33
  241. package/dist/lib/observability.js +0 -373
  242. package/dist/lib/paths.d.ts +0 -35
  243. package/dist/lib/paths.js +0 -634
  244. package/dist/lib/resource-store.d.ts +0 -28
  245. package/dist/lib/resource-store.js +0 -195
  246. package/dist/lib/utils.d.ts +0 -7
  247. package/dist/lib/utils.js +0 -37
  248. package/dist/lib/zod-codecs.d.ts +0 -2
  249. package/dist/lib/zod-codecs.js +0 -18
  250. package/dist/pkg-info.d.ts +0 -7
  251. package/dist/pkg-info.js +0 -9
  252. package/dist/resources/generated-instructions.d.ts +0 -1
  253. package/dist/resources/generated-instructions.js +0 -105
  254. package/dist/resources/tool-catalog.d.ts +0 -2
  255. package/dist/resources/tool-catalog.js +0 -78
  256. package/dist/resources/tool-info.d.ts +0 -9
  257. package/dist/resources/tool-info.js +0 -206
  258. package/dist/resources/workflows.d.ts +0 -1
  259. package/dist/resources/workflows.js +0 -72
  260. package/dist/schemas.d.ts +0 -505
  261. package/dist/schemas.js +0 -676
  262. package/dist/server/bootstrap.d.ts +0 -6
  263. package/dist/server/bootstrap.js +0 -563
  264. package/dist/server/event-store.d.ts +0 -18
  265. package/dist/server/event-store.js +0 -71
  266. package/dist/server/roots-manager.d.ts +0 -28
  267. package/dist/server/roots-manager.js +0 -227
  268. package/dist/server/task-store.d.ts +0 -10
  269. package/dist/server/task-store.js +0 -91
  270. package/dist/tools/apply-patch.d.ts +0 -4
  271. package/dist/tools/apply-patch.js +0 -235
  272. package/dist/tools/calculate-hash.d.ts +0 -4
  273. package/dist/tools/calculate-hash.js +0 -187
  274. package/dist/tools/contract.d.ts +0 -50
  275. package/dist/tools/contract.js +0 -1
  276. package/dist/tools/create-directory.d.ts +0 -4
  277. package/dist/tools/create-directory.js +0 -68
  278. package/dist/tools/diff-files.d.ts +0 -4
  279. package/dist/tools/diff-files.js +0 -136
  280. package/dist/tools/edit-file.d.ts +0 -4
  281. package/dist/tools/edit-file.js +0 -256
  282. package/dist/tools/icons.d.ts +0 -15
  283. package/dist/tools/icons.js +0 -24
  284. package/dist/tools/list-directory.d.ts +0 -4
  285. package/dist/tools/list-directory.js +0 -218
  286. package/dist/tools/move-file.d.ts +0 -4
  287. package/dist/tools/move-file.js +0 -178
  288. package/dist/tools/read-multiple.d.ts +0 -4
  289. package/dist/tools/read-multiple.js +0 -207
  290. package/dist/tools/shared.d.ts +0 -172
  291. package/dist/tools/shared.js +0 -599
  292. package/dist/tools/stat-many.d.ts +0 -4
  293. package/dist/tools/stat-many.js +0 -104
  294. package/dist/tools/task-support.d.ts +0 -29
  295. package/dist/tools/task-support.js +0 -472
  296. package/dist/tools/tree.d.ts +0 -4
  297. package/dist/tools/tree.js +0 -102
  298. package/dist/tools/write-file.d.ts +0 -4
  299. package/dist/tools/write-file.js +0 -60
  300. package/dist/tools.d.ts +0 -5
  301. package/dist/tools.js +0 -54
@@ -0,0 +1,312 @@
1
+ import { statSync, watch } from 'node:fs';
2
+ import { formatUnknownErrorMessage } from './errors.js';
3
+ import { extractPath } from './file-uri.js';
4
+ import { Logger } from './observability.js';
5
+ import { parseEnvInt } from './util.js';
6
+ // Cap concurrent file watchers to avoid exhausting OS-level watch handles
7
+ // (e.g. Linux inotify, default ~8192/user). One subscription == one watcher.
8
+ export const MAX_WATCHERS = parseEnvInt('FS_MAX_WATCHERS', 256, 1, 4096);
9
+ function warnWatcherCap(uri) {
10
+ Logger.warn(`Cannot subscribe to ${uri}: MAX_WATCHERS limit (${MAX_WATCHERS}) reached.`);
11
+ }
12
+ /**
13
+ * Owns the uri → FSWatcher map and the subscription bookkeeping around it:
14
+ * notify callbacks, desired subscribe/unsubscribe state, and the watcher cap —
15
+ * plus `acquire`, the one ladder that sequences them correctly. `acquire` awaits
16
+ * path validation midway and re-checks `isStale` and `hasWatcher` itself
17
+ * afterwards, so callers do not: they take a lease and release it. The
18
+ * individual primitives stay public for the state-machine tests.
19
+ */
20
+ export function createWatcherRegistry() {
21
+ const watchers = new Map();
22
+ // A URI may have several notification sinks and several independent leases.
23
+ // Callback identity and lifetime are intentionally separate: the HTTP leg
24
+ // uses one stable bus-publishing callback for every listen stream, while each
25
+ // stream still owns one lease that must be released on close.
26
+ // ponytail: no per-subscription id in the SDK unsubscribe contract, so we
27
+ // ref-count by URI. A departed direct-notification callback can linger until
28
+ // the final lease ends; sends to a closed transport already fail harmlessly.
29
+ // The count outlives the watcher (see `dropWatcher`), so a URI whose watcher
30
+ // errored keeps one map entry per still-unreleased lease until they drain —
31
+ // no watcher slot, just bookkeeping. Per-subscription-id keying retires both
32
+ // this and the unsubscribe-by-URI over-release if that churn is observed.
33
+ const activeCallbacks = new Map();
34
+ const subscriberCounts = new Map();
35
+ const desiredState = new Map();
36
+ const debounceTimers = new Map();
37
+ let destroyed = false;
38
+ // 'unsubscribed' exists only to abort a subscribe that is mid-await
39
+ // (isStale). If no subscribe is in flight, keep no entry at all: a lingering
40
+ // 'unsubscribed' would permanently block the modern attach path (which never
41
+ // calls startSubscribe) and leak one map entry per URI ever watched. This
42
+ // must be idempotent: remove()'s teardown branch calls this directly and
43
+ // then unconditionally calls dropWatcher (which also calls this) for the
44
+ // same uri — the second call must not clobber what the first one set.
45
+ const settleDesiredState = (uri) => {
46
+ const current = desiredState.get(uri);
47
+ if (current === 'subscribing') {
48
+ desiredState.set(uri, 'unsubscribed');
49
+ }
50
+ else if (current !== 'unsubscribed') {
51
+ desiredState.delete(uri);
52
+ }
53
+ };
54
+ // Tears down the watcher itself, NOT the ref-count. The 'error' event calls
55
+ // this with leases still outstanding: clearing the count there would let the
56
+ // next release from one of those leases decrement from zero and drop a
57
+ // watcher a later subscriber re-established for the same URI. `drop` below is
58
+ // the only path that ends leases, and it is the only one that clears them.
59
+ const dropWatcher = (uri, watcher) => {
60
+ const current = watchers.get(uri);
61
+ if (current !== watcher)
62
+ return;
63
+ const timer = debounceTimers.get(uri);
64
+ if (timer) {
65
+ clearTimeout(timer);
66
+ debounceTimers.delete(uri);
67
+ }
68
+ watcher.close();
69
+ watchers.delete(uri);
70
+ activeCallbacks.delete(uri);
71
+ settleDesiredState(uri);
72
+ };
73
+ /** The last lease ended: drop the watcher and every trace of the URI. */
74
+ const drop = (uri) => {
75
+ const timer = debounceTimers.get(uri);
76
+ if (timer) {
77
+ clearTimeout(timer);
78
+ debounceTimers.delete(uri);
79
+ }
80
+ const watcher = watchers.get(uri);
81
+ if (watcher)
82
+ dropWatcher(uri, watcher);
83
+ activeCallbacks.delete(uri);
84
+ subscriberCounts.delete(uri);
85
+ settleDesiredState(uri);
86
+ };
87
+ const notifyAll = (uri) => {
88
+ const callbacks = activeCallbacks.get(uri);
89
+ if (!callbacks || callbacks.size === 0)
90
+ return;
91
+ const existing = debounceTimers.get(uri);
92
+ if (existing) {
93
+ clearTimeout(existing);
94
+ }
95
+ const timer = setTimeout(() => {
96
+ debounceTimers.delete(uri);
97
+ const cbs = activeCallbacks.get(uri);
98
+ if (!cbs)
99
+ return;
100
+ // One debounce timer per URI — fan out to every subscriber inside it, so
101
+ // a burst of changes still fires one notification round per URI, not per
102
+ // subscriber.
103
+ for (const cb of cbs) {
104
+ try {
105
+ cb(uri);
106
+ }
107
+ catch (err) {
108
+ Logger.warn(`Notify callback error for ${uri}: ${formatUnknownErrorMessage(err)}`);
109
+ }
110
+ }
111
+ }, 50);
112
+ timer.unref();
113
+ debounceTimers.set(uri, timer);
114
+ };
115
+ // The seven members below are hoisted out of the returned literal so `acquire`
116
+ // can sequence them from inside the closure. Their signatures and the shape of
117
+ // the returned object are unchanged — they are still public members, listed
118
+ // shorthand in the `return` at the bottom.
119
+ const hasWatcher = (uri) => watchers.has(uri);
120
+ const isAtCap = () => watchers.size >= MAX_WATCHERS;
121
+ /** The registry was destroyed, or this uri was unsubscribed, mid-await. */
122
+ const isStale = (uri) => destroyed || desiredState.get(uri) === 'unsubscribed';
123
+ const startSubscribe = (uri) => {
124
+ desiredState.set(uri, 'subscribing');
125
+ };
126
+ /**
127
+ * A subscribe that declared intent and then failed — bad URI, unvalidatable
128
+ * path, cap, fs.watch refusal — drops the entry entirely. Not
129
+ * `settleDesiredState`: that maps 'subscribing' onto 'unsubscribed' to abort
130
+ * an attach still mid-await, and a *finished* failure leaving 'unsubscribed'
131
+ * behind would make `isStale` true forever, so the modern attach path (which
132
+ * never calls `startSubscribe`) could never watch this uri again. Only
133
+ * clears its own 'subscribing' — a concurrent unsubscribe that already set
134
+ * 'unsubscribed' still wins.
135
+ */
136
+ const cancelSubscribe = (uri) => {
137
+ if (desiredState.get(uri) === 'subscribing')
138
+ desiredState.delete(uri);
139
+ };
140
+ const addCallback = (uri, notify) => {
141
+ let callbacks = activeCallbacks.get(uri);
142
+ if (!callbacks) {
143
+ callbacks = new Set();
144
+ activeCallbacks.set(uri, callbacks);
145
+ }
146
+ callbacks.add(notify);
147
+ desiredState.set(uri, 'subscribed');
148
+ };
149
+ const retain = (uri) => {
150
+ subscriberCounts.set(uri, (subscriberCounts.get(uri) ?? 0) + 1);
151
+ };
152
+ /**
153
+ * Ref-count by URI: only tear down the shared watcher when the last lease
154
+ * ends. Callback identity is independent from this count. A release with no
155
+ * lease outstanding (a rolled-back attach) drops the watcher outright.
156
+ */
157
+ const release = (uri) => {
158
+ const remaining = (subscriberCounts.get(uri) ?? 0) - 1;
159
+ if (remaining > 0) {
160
+ subscriberCounts.set(uri, remaining);
161
+ return;
162
+ }
163
+ drop(uri);
164
+ };
165
+ const attach = (uri, resolvedPath) => {
166
+ try {
167
+ // Watch directories recursively (children included) and files as-is.
168
+ // `fs.watch` async errors arrive via the 'error' event below, not as a
169
+ // sync throw, so no recursive-fallback try/catch is needed here — the
170
+ // outer catch handles sync throws (inotify exhaustion, path-race).
171
+ // `{ recursive: true }` is honored on macOS, Windows, and — since Node
172
+ // 20.13 — Linux; `engines.node` is >=24, so all three are covered.
173
+ const recursive = statSync(resolvedPath).isDirectory();
174
+ const watcher = watch(resolvedPath, recursive ? { recursive: true } : undefined, () => {
175
+ notifyAll(uri);
176
+ });
177
+ watcher.on('error', (err) => {
178
+ Logger.warn(`Watcher error for ${uri}: ${err.message}`);
179
+ dropWatcher(uri, watcher);
180
+ });
181
+ // Two attaches that both cleared `hasWatcher` before either finished
182
+ // validating land here for the same uri. Keep the one already wired to
183
+ // the callback set and close this one — overwriting the map entry would
184
+ // strand the first watcher's fd with nothing left holding a reference.
185
+ if (watchers.has(uri)) {
186
+ watcher.close();
187
+ return true;
188
+ }
189
+ watchers.set(uri, watcher);
190
+ return true;
191
+ }
192
+ catch (err) {
193
+ Logger.error(`Failed to create watcher for ${uri}: ${formatUnknownErrorMessage(err)}`);
194
+ return false;
195
+ }
196
+ };
197
+ return {
198
+ hasWatcher,
199
+ isAtCap,
200
+ /** Live watcher count, for pre-checking a batched listen against remaining capacity. */
201
+ size: () => watchers.size,
202
+ isStale,
203
+ startSubscribe,
204
+ cancelSubscribe,
205
+ addCallback,
206
+ retain,
207
+ release,
208
+ /**
209
+ * The one attach ladder both watcher entry points run: `resources/subscribe`
210
+ * (2025 era) and the `subscriptions/listen` filter (modern era, HTTP and
211
+ * stdio). Never throws — it reports the outcome and lets each caller decide
212
+ * what that is worth: subscribe owes its caller a precise error, and listen
213
+ * (`prepareListenWatchers`) treats the batch as all-or-nothing, releasing
214
+ * every lease it already took and rejecting the request. Idempotent per URI
215
+ * — a second call for an already-watched URI re-registers the notify
216
+ * callback (one watcher per URI).
217
+ *
218
+ * `markSubscribe` is the one branch that differs: only `resources/subscribe`
219
+ * declares desired state (what `isStale` aborts against). The listen path
220
+ * must not, or a rejected attach past that point strands a `'subscribing'`
221
+ * entry nothing settles. Whoever declares it, this function settles it:
222
+ * every failing exit past that point cancels the declaration, so no uri is
223
+ * poisoned for a later attach.
224
+ *
225
+ * Every `ok` return takes one lease; lifetime is the caller's to manage.
226
+ * HTTP releases when the listen response closes. Stdio tracks the listen
227
+ * request ID and releases on cancellation, SDK rejection, or graceful
228
+ * completion. In both legs the ref-count is by URI, so a watcher another
229
+ * stream still holds survives.
230
+ */
231
+ async acquire(pathGuard, uri, notify, { markSubscribe = false } = {}) {
232
+ // Every failing exit below routes through here, so the declaration made by
233
+ // `startSubscribe` can never outlive the attach that made it.
234
+ const fail = (result) => {
235
+ if (markSubscribe)
236
+ cancelSubscribe(uri);
237
+ return result;
238
+ };
239
+ if (hasWatcher(uri)) {
240
+ // A watcher already tracks this uri; just (re)register the callback so
241
+ // its change events reach the new subscriber. No validation or cap work
242
+ // is needed for an already-live watcher.
243
+ addCallback(uri, notify);
244
+ retain(uri);
245
+ return { ok: true };
246
+ }
247
+ // A cap hit before validation and one found after the await are the same
248
+ // condition, and both are reported the same way.
249
+ if (isAtCap()) {
250
+ warnWatcherCap(uri);
251
+ return { ok: false, reason: 'capped' };
252
+ }
253
+ if (markSubscribe)
254
+ startSubscribe(uri);
255
+ const filePath = extractPath(uri);
256
+ if (!filePath)
257
+ return fail({ ok: false, reason: 'bad-uri' });
258
+ let resolved;
259
+ try {
260
+ resolved = await pathGuard.validateExistingPath(filePath);
261
+ }
262
+ catch (error) {
263
+ return fail({ ok: false, reason: 'invalid-path', error });
264
+ }
265
+ // Re-check what the await could have changed. A stale uri is the one
266
+ // failure that must NOT cancel: an unsubscribe landed mid-await and its
267
+ // 'unsubscribed' marker is the thing that aborted this attach.
268
+ if (isStale(uri))
269
+ return { ok: false, reason: 'stale' };
270
+ if (hasWatcher(uri)) {
271
+ addCallback(uri, notify);
272
+ retain(uri);
273
+ return { ok: true };
274
+ }
275
+ if (isAtCap()) {
276
+ warnWatcherCap(uri);
277
+ return fail({ ok: false, reason: 'capped' });
278
+ }
279
+ addCallback(uri, notify);
280
+ if (!attach(uri, resolved)) {
281
+ // fs.watch threw (inotify exhaustion, or a race deleted the path): roll
282
+ // back so no dangling callback is left believing a watcher exists. No
283
+ // lease was taken yet, so `release` drops the entry outright.
284
+ release(uri);
285
+ return fail({ ok: false, reason: 'attach-failed' });
286
+ }
287
+ retain(uri);
288
+ return { ok: true };
289
+ },
290
+ attach,
291
+ destroy() {
292
+ destroyed = true;
293
+ for (const timer of debounceTimers.values()) {
294
+ clearTimeout(timer);
295
+ }
296
+ debounceTimers.clear();
297
+ for (const watcher of watchers.values()) {
298
+ try {
299
+ watcher.close();
300
+ }
301
+ catch {
302
+ /* ignore close errors so all watchers are attempted */
303
+ }
304
+ }
305
+ watchers.clear();
306
+ activeCallbacks.clear();
307
+ subscriberCounts.clear();
308
+ desiredState.clear();
309
+ },
310
+ };
311
+ }
312
+ //# sourceMappingURL=watcher-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher-registry.js","sourceRoot":"","sources":["../../src/core/watcher-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FSWatcher } from 'node:fs';\nimport { statSync, watch } from 'node:fs';\n\nimport { formatUnknownErrorMessage } from './errors.js';\nimport { extractPath } from './file-uri.js';\nimport { Logger } from './observability.js';\nimport type { PathGuard } from './path.js';\nimport { parseEnvInt } from './util.js';\n\n// Cap concurrent file watchers to avoid exhausting OS-level watch handles\n// (e.g. Linux inotify, default ~8192/user). One subscription == one watcher.\nexport const MAX_WATCHERS = parseEnvInt('FS_MAX_WATCHERS', 256, 1, 4096);\n\nfunction warnWatcherCap(uri: string): void {\n Logger.warn(`Cannot subscribe to ${uri}: MAX_WATCHERS limit (${MAX_WATCHERS}) reached.`);\n}\n\n/**\n * `ok` means a watcher is live for this uri and `notify` is registered. Every\n * other outcome names why, and only `invalid-path` carries what\n * `validateExistingPath` threw — so `error` is unreachable on a branch that has\n * none.\n */\nexport type WatcherAttachResult =\n | { ok: true }\n | { ok: false; reason: 'stale' | 'capped' | 'bad-uri' | 'attach-failed' }\n | { ok: false; reason: 'invalid-path'; error: unknown };\n\n/**\n * Owns the uri → FSWatcher map and the subscription bookkeeping around it:\n * notify callbacks, desired subscribe/unsubscribe state, and the watcher cap —\n * plus `acquire`, the one ladder that sequences them correctly. `acquire` awaits\n * path validation midway and re-checks `isStale` and `hasWatcher` itself\n * afterwards, so callers do not: they take a lease and release it. The\n * individual primitives stay public for the state-machine tests.\n */\nexport function createWatcherRegistry() {\n const watchers = new Map<string, FSWatcher>();\n // A URI may have several notification sinks and several independent leases.\n // Callback identity and lifetime are intentionally separate: the HTTP leg\n // uses one stable bus-publishing callback for every listen stream, while each\n // stream still owns one lease that must be released on close.\n // ponytail: no per-subscription id in the SDK unsubscribe contract, so we\n // ref-count by URI. A departed direct-notification callback can linger until\n // the final lease ends; sends to a closed transport already fail harmlessly.\n // The count outlives the watcher (see `dropWatcher`), so a URI whose watcher\n // errored keeps one map entry per still-unreleased lease until they drain —\n // no watcher slot, just bookkeeping. Per-subscription-id keying retires both\n // this and the unsubscribe-by-URI over-release if that churn is observed.\n const activeCallbacks = new Map<string, Set<(uri: string) => void>>();\n const subscriberCounts = new Map<string, number>();\n const desiredState = new Map<string, 'subscribed' | 'unsubscribed' | 'subscribing'>();\n const debounceTimers = new Map<string, NodeJS.Timeout>();\n let destroyed = false;\n\n // 'unsubscribed' exists only to abort a subscribe that is mid-await\n // (isStale). If no subscribe is in flight, keep no entry at all: a lingering\n // 'unsubscribed' would permanently block the modern attach path (which never\n // calls startSubscribe) and leak one map entry per URI ever watched. This\n // must be idempotent: remove()'s teardown branch calls this directly and\n // then unconditionally calls dropWatcher (which also calls this) for the\n // same uri — the second call must not clobber what the first one set.\n const settleDesiredState = (uri: string): void => {\n const current = desiredState.get(uri);\n if (current === 'subscribing') {\n desiredState.set(uri, 'unsubscribed');\n } else if (current !== 'unsubscribed') {\n desiredState.delete(uri);\n }\n };\n\n // Tears down the watcher itself, NOT the ref-count. The 'error' event calls\n // this with leases still outstanding: clearing the count there would let the\n // next release from one of those leases decrement from zero and drop a\n // watcher a later subscriber re-established for the same URI. `drop` below is\n // the only path that ends leases, and it is the only one that clears them.\n const dropWatcher = (uri: string, watcher: FSWatcher): void => {\n const current = watchers.get(uri);\n if (current !== watcher) return;\n const timer = debounceTimers.get(uri);\n if (timer) {\n clearTimeout(timer);\n debounceTimers.delete(uri);\n }\n watcher.close();\n watchers.delete(uri);\n activeCallbacks.delete(uri);\n settleDesiredState(uri);\n };\n\n /** The last lease ended: drop the watcher and every trace of the URI. */\n const drop = (uri: string): void => {\n const timer = debounceTimers.get(uri);\n if (timer) {\n clearTimeout(timer);\n debounceTimers.delete(uri);\n }\n const watcher = watchers.get(uri);\n if (watcher) dropWatcher(uri, watcher);\n activeCallbacks.delete(uri);\n subscriberCounts.delete(uri);\n settleDesiredState(uri);\n };\n\n const notifyAll = (uri: string): void => {\n const callbacks = activeCallbacks.get(uri);\n if (!callbacks || callbacks.size === 0) return;\n const existing = debounceTimers.get(uri);\n if (existing) {\n clearTimeout(existing);\n }\n const timer = setTimeout(() => {\n debounceTimers.delete(uri);\n const cbs = activeCallbacks.get(uri);\n if (!cbs) return;\n // One debounce timer per URI — fan out to every subscriber inside it, so\n // a burst of changes still fires one notification round per URI, not per\n // subscriber.\n for (const cb of cbs) {\n try {\n cb(uri);\n } catch (err) {\n Logger.warn(`Notify callback error for ${uri}: ${formatUnknownErrorMessage(err)}`);\n }\n }\n }, 50);\n timer.unref();\n debounceTimers.set(uri, timer);\n };\n\n // The seven members below are hoisted out of the returned literal so `acquire`\n // can sequence them from inside the closure. Their signatures and the shape of\n // the returned object are unchanged — they are still public members, listed\n // shorthand in the `return` at the bottom.\n\n const hasWatcher = (uri: string): boolean => watchers.has(uri);\n\n const isAtCap = (): boolean => watchers.size >= MAX_WATCHERS;\n\n /** The registry was destroyed, or this uri was unsubscribed, mid-await. */\n const isStale = (uri: string): boolean => destroyed || desiredState.get(uri) === 'unsubscribed';\n\n const startSubscribe = (uri: string): void => {\n desiredState.set(uri, 'subscribing');\n };\n\n /**\n * A subscribe that declared intent and then failed — bad URI, unvalidatable\n * path, cap, fs.watch refusal — drops the entry entirely. Not\n * `settleDesiredState`: that maps 'subscribing' onto 'unsubscribed' to abort\n * an attach still mid-await, and a *finished* failure leaving 'unsubscribed'\n * behind would make `isStale` true forever, so the modern attach path (which\n * never calls `startSubscribe`) could never watch this uri again. Only\n * clears its own 'subscribing' — a concurrent unsubscribe that already set\n * 'unsubscribed' still wins.\n */\n const cancelSubscribe = (uri: string): void => {\n if (desiredState.get(uri) === 'subscribing') desiredState.delete(uri);\n };\n\n const addCallback = (uri: string, notify: (uri: string) => void): void => {\n let callbacks = activeCallbacks.get(uri);\n if (!callbacks) {\n callbacks = new Set();\n activeCallbacks.set(uri, callbacks);\n }\n callbacks.add(notify);\n desiredState.set(uri, 'subscribed');\n };\n\n const retain = (uri: string): void => {\n subscriberCounts.set(uri, (subscriberCounts.get(uri) ?? 0) + 1);\n };\n\n /**\n * Ref-count by URI: only tear down the shared watcher when the last lease\n * ends. Callback identity is independent from this count. A release with no\n * lease outstanding (a rolled-back attach) drops the watcher outright.\n */\n const release = (uri: string): void => {\n const remaining = (subscriberCounts.get(uri) ?? 0) - 1;\n if (remaining > 0) {\n subscriberCounts.set(uri, remaining);\n return;\n }\n drop(uri);\n };\n\n const attach = (uri: string, resolvedPath: string): boolean => {\n try {\n // Watch directories recursively (children included) and files as-is.\n // `fs.watch` async errors arrive via the 'error' event below, not as a\n // sync throw, so no recursive-fallback try/catch is needed here — the\n // outer catch handles sync throws (inotify exhaustion, path-race).\n // `{ recursive: true }` is honored on macOS, Windows, and — since Node\n // 20.13 — Linux; `engines.node` is >=24, so all three are covered.\n const recursive = statSync(resolvedPath).isDirectory();\n const watcher = watch(resolvedPath, recursive ? { recursive: true } : undefined, () => {\n notifyAll(uri);\n });\n watcher.on('error', (err: Error) => {\n Logger.warn(`Watcher error for ${uri}: ${err.message}`);\n dropWatcher(uri, watcher);\n });\n // Two attaches that both cleared `hasWatcher` before either finished\n // validating land here for the same uri. Keep the one already wired to\n // the callback set and close this one — overwriting the map entry would\n // strand the first watcher's fd with nothing left holding a reference.\n if (watchers.has(uri)) {\n watcher.close();\n return true;\n }\n watchers.set(uri, watcher);\n return true;\n } catch (err) {\n Logger.error(`Failed to create watcher for ${uri}: ${formatUnknownErrorMessage(err)}`);\n return false;\n }\n };\n\n return {\n hasWatcher,\n\n isAtCap,\n\n /** Live watcher count, for pre-checking a batched listen against remaining capacity. */\n size: (): number => watchers.size,\n\n isStale,\n\n startSubscribe,\n\n cancelSubscribe,\n\n addCallback,\n\n retain,\n\n release,\n\n /**\n * The one attach ladder both watcher entry points run: `resources/subscribe`\n * (2025 era) and the `subscriptions/listen` filter (modern era, HTTP and\n * stdio). Never throws — it reports the outcome and lets each caller decide\n * what that is worth: subscribe owes its caller a precise error, and listen\n * (`prepareListenWatchers`) treats the batch as all-or-nothing, releasing\n * every lease it already took and rejecting the request. Idempotent per URI\n * — a second call for an already-watched URI re-registers the notify\n * callback (one watcher per URI).\n *\n * `markSubscribe` is the one branch that differs: only `resources/subscribe`\n * declares desired state (what `isStale` aborts against). The listen path\n * must not, or a rejected attach past that point strands a `'subscribing'`\n * entry nothing settles. Whoever declares it, this function settles it:\n * every failing exit past that point cancels the declaration, so no uri is\n * poisoned for a later attach.\n *\n * Every `ok` return takes one lease; lifetime is the caller's to manage.\n * HTTP releases when the listen response closes. Stdio tracks the listen\n * request ID and releases on cancellation, SDK rejection, or graceful\n * completion. In both legs the ref-count is by URI, so a watcher another\n * stream still holds survives.\n */\n async acquire(\n pathGuard: PathGuard,\n uri: string,\n notify: (uri: string) => void,\n { markSubscribe = false }: { markSubscribe?: boolean } = {},\n ): Promise<WatcherAttachResult> {\n // Every failing exit below routes through here, so the declaration made by\n // `startSubscribe` can never outlive the attach that made it.\n const fail = (result: WatcherAttachResult & { ok: false }): WatcherAttachResult => {\n if (markSubscribe) cancelSubscribe(uri);\n return result;\n };\n\n if (hasWatcher(uri)) {\n // A watcher already tracks this uri; just (re)register the callback so\n // its change events reach the new subscriber. No validation or cap work\n // is needed for an already-live watcher.\n addCallback(uri, notify);\n retain(uri);\n return { ok: true };\n }\n // A cap hit before validation and one found after the await are the same\n // condition, and both are reported the same way.\n if (isAtCap()) {\n warnWatcherCap(uri);\n return { ok: false, reason: 'capped' };\n }\n\n if (markSubscribe) startSubscribe(uri);\n\n const filePath = extractPath(uri);\n if (!filePath) return fail({ ok: false, reason: 'bad-uri' });\n\n let resolved: string;\n try {\n resolved = await pathGuard.validateExistingPath(filePath);\n } catch (error: unknown) {\n return fail({ ok: false, reason: 'invalid-path', error });\n }\n\n // Re-check what the await could have changed. A stale uri is the one\n // failure that must NOT cancel: an unsubscribe landed mid-await and its\n // 'unsubscribed' marker is the thing that aborted this attach.\n if (isStale(uri)) return { ok: false, reason: 'stale' };\n if (hasWatcher(uri)) {\n addCallback(uri, notify);\n retain(uri);\n return { ok: true };\n }\n if (isAtCap()) {\n warnWatcherCap(uri);\n return fail({ ok: false, reason: 'capped' });\n }\n\n addCallback(uri, notify);\n if (!attach(uri, resolved)) {\n // fs.watch threw (inotify exhaustion, or a race deleted the path): roll\n // back so no dangling callback is left believing a watcher exists. No\n // lease was taken yet, so `release` drops the entry outright.\n release(uri);\n return fail({ ok: false, reason: 'attach-failed' });\n }\n retain(uri);\n return { ok: true };\n },\n\n attach,\n\n destroy(): void {\n destroyed = true;\n for (const timer of debounceTimers.values()) {\n clearTimeout(timer);\n }\n debounceTimers.clear();\n for (const watcher of watchers.values()) {\n try {\n watcher.close();\n } catch {\n /* ignore close errors so all watchers are attempted */\n }\n }\n watchers.clear();\n activeCallbacks.clear();\n subscriberCounts.clear();\n desiredState.clear();\n },\n };\n}\n\nexport type WatcherRegistry = ReturnType<typeof createWatcherRegistry>;\n"]}
@@ -0,0 +1,121 @@
1
+ import type { Request, RequestHandler, Response } from 'express';
2
+ /**
3
+ * The one JSON-RPC error envelope every HTTP refusal goes out in — this
4
+ * module's pre-handler rejections and `transport.ts`'s. It lives here because
5
+ * `transport.ts` already imports this module and not the other way round; a
6
+ * second hand-built literal is how the two drift apart.
7
+ *
8
+ * `headers` carries what a particular refusal owes the client on top of the
9
+ * JSON content type — `WWW-Authenticate` on a 401, `Retry-After` on a 429.
10
+ * Goes out through `res.json` so every refusal keeps the `charset=utf-8` and
11
+ * `Content-Length` a hand-rolled `writeHead`/`end` pair drops.
12
+ */
13
+ export declare function sendJsonRpcError(res: Response, status: number, code: number, message: string, id?: string | number | null, headers?: Record<string, string>): void;
14
+ /**
15
+ * Pure HTTP auth and binding policy. Holds no state; all functions are
16
+ * directly testable without spinning up a server.
17
+ */
18
+ export declare function isLoopbackHttpHost(host: string): boolean;
19
+ export declare function isAllowedLocalhostOrigin(origin: string): boolean;
20
+ /**
21
+ * True if `origin` (a raw `Origin` request header) is allowed given the
22
+ * env-derived `allowedHostnames` set (hostname-form, no scheme/port). Localhost
23
+ * origins are always accepted via {@link isAllowedLocalhostOrigin}; a remote
24
+ * origin is accepted iff its parsed hostname is in the set. Both the SDK app's
25
+ * `allowedOrigins` and this OPTIONS-handler check consume hostname-form, so a
26
+ * remote origin allowed via `FS_ALLOWED_ORIGINS` is reflected
27
+ * end-to-end in `Access-Control-Allow-Origin`.
28
+ */
29
+ export declare function isOriginAllowed(origin: string, allowedHostnames: readonly string[]): boolean;
30
+ export declare function validateBearerAuthorization(apiKey: string, authHeader: unknown): authHeader is string;
31
+ /**
32
+ * Refuse to bind to a non-loopback host without an API key. Throws on
33
+ * policy violation; returns silently when allowed.
34
+ */
35
+ export declare function assertHttpBindingPolicy(host: string, apiKey: string | undefined): void;
36
+ /**
37
+ * The Host header values this bind accepts. `FS_ALLOWED_HOSTS` wins
38
+ * when set; otherwise a loopback bind takes the whole localhost hostname set (a
39
+ * client dialing http://localhost:<port> sends `Host: localhost`, which a bare
40
+ * ['127.0.0.1'] list would 403), a wildcard bind takes none, and a concrete
41
+ * non-loopback bind takes itself. An empty result means no Host validation is
42
+ * mounted — only reachable under FS_ALLOW_UNRESTRICTED_HOSTS=1.
43
+ */
44
+ export declare function resolveAllowedHosts(httpHost: string, allowedHostsEnv: string | undefined): string[];
45
+ /**
46
+ * Express's `trust proxy` setting from `FS_TRUST_PROXY`. A
47
+ * non-negative integer hop count parses to a number (Express counts hops from
48
+ * the socket); anything else (a subnet name/expression, or a negative/
49
+ * non-integer string) passes through unchanged for Express to interpret.
50
+ * `undefined` means the env var was unset or empty — leave Express's default
51
+ * (disabled) in place.
52
+ */
53
+ export declare function resolveTrustProxySetting(value: string | undefined): number | string | undefined;
54
+ /**
55
+ * Refuse to bind a wildcard host (`0.0.0.0` / `::`) without an explicit
56
+ * `FS_ALLOWED_HOSTS` list. Clients never send `Host: 0.0.0.0`, so
57
+ * defaulting the allowed-host set to the wildcard string would reject all real
58
+ * traffic. Operators who accept the risk can set
59
+ * `FS_ALLOW_UNRESTRICTED_HOSTS=1` to restore warn-and-bind.
60
+ * Loopback and concrete non-loopback hosts are unaffected.
61
+ */
62
+ export declare function assertHttpHostPolicy(host: string, allowedHosts: readonly string[], allowUnrestricted: boolean): void;
63
+ /**
64
+ * This server is a resource server with no authorization server: `API_KEY` is a
65
+ * static secret the operator hands out of band, not an issued token. So the
66
+ * metadata document deliberately omits `authorization_servers` — RFC 9728 §2
67
+ * makes it optional, and its absence is the accurate statement that a token
68
+ * cannot be obtained from an endpoint. `mcpAuthMetadataRouter` from
69
+ * `@modelcontextprotocol/express` is the tool for the IdP-backed case: it
70
+ * requires RFC 8414 authorization-server metadata, and inventing an issuer with
71
+ * endpoints that answer nothing would send clients into a flow that cannot
72
+ * complete. Adopt it if this ever moves to a real IdP.
73
+ *
74
+ * What discovery buys here: a client hitting 401 learns the resource identifier
75
+ * and that the credential goes in the Authorization header, instead of a bare
76
+ * challenge plus a 404 on the well-known path.
77
+ */
78
+ export declare function protectedResourceUrl(req: Request, hostValidated: boolean, configured: string | undefined): URL | null;
79
+ /**
80
+ * Express middleware: when `apiKey` is set, require a matching bearer token.
81
+ * No key set = open access (loopback dev mode). `apiKey` is captured once per
82
+ * app setup (passed in from startHttpServer) so the middleware and
83
+ * assertHttpBindingPolicy share one source of truth.
84
+ *
85
+ * `requireBearerAuth` + `verifyAccessToken` from `@modelcontextprotocol/express`
86
+ * is the tool for verifier-backed tokens; this middleware stays hand-rolled
87
+ * because the credential is a static operator key with no verifier, the 401 body
88
+ * must stay a JSON-RPC envelope, and the RFC 6750 bare-vs-`invalid_token`
89
+ * challenge split is implemented here. Adopt `requireBearerAuth` if this ever
90
+ * verifies issued tokens.
91
+ */
92
+ export declare function bearerAuthMiddleware(apiKey: string | undefined, hostValidated: boolean, publicUrl?: string): RequestHandler;
93
+ /**
94
+ * Env-derived CORS origins (hostname-form, no scheme/port — matches the SDK
95
+ * app's `allowedOrigins` consumer). Defaults to the SDK's loopback hostname set
96
+ * so loopback browser clients keep working; operators set
97
+ * `FS_ALLOWED_ORIGINS` to allow remote clients on non-loopback
98
+ * binds. An empty value reads as unset, matching how parseAllowedHostsEnv
99
+ * treats an all-empty list. The same set is consulted by corsPreflightHandler
100
+ * so a remote origin is reflected end-to-end in Access-Control-Allow-Origin.
101
+ */
102
+ export declare function computeAllowedOriginHostnames(originsEnv: string | undefined): string[];
103
+ /** Mounted ahead of the `/mcp` handlers so every response — not just the
104
+ * OPTIONS preflight — carries `Access-Control-Allow-Origin` for an allowed
105
+ * Origin. */
106
+ export declare function corsOriginMiddleware(allowedOriginHostnames: readonly string[]): RequestHandler;
107
+ /**
108
+ * OPTIONS preflight for `/mcp`. The allow-list carries the SEP-2243 standard
109
+ * headers (`mcp-protocol-version`, `mcp-method`, `mcp-name`): SDK clients send
110
+ * all three on every modern request POST and `createMcpHandler` requires them
111
+ * (400 / -32020), so omitting them here would fail every browser preflight —
112
+ * and the HTTP leg is modern-only, so there is no legacy path to fall back to.
113
+ * `mcp-session-id` is deliberately absent: it is 2025-era only and this
114
+ * endpoint rejects legacy traffic.
115
+ */
116
+ export declare function corsPreflightHandler(allowedOriginHostnames: readonly string[]): RequestHandler;
117
+ /**
118
+ * Fixed-window per-client-IP rate limiter.
119
+ */
120
+ export declare function createRateLimiter(max: number): RequestHandler;
121
+ //# sourceMappingURL=http-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-policy.d.ts","sourceRoot":"","sources":["../src/http-policy.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAgB,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAe/E;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,EAAE,GAAE,MAAM,GAAG,MAAM,GAAG,IAAW,EACjC,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACnC,IAAI,CAEN;AAID;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAQxD;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEhE;AAgBD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAI5F;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,OAAO,GAClB,UAAU,IAAI,MAAM,CAiBtB;AAMD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAetF;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,GAAG,SAAS,GAClC,MAAM,EAAE,CAMV;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAI/F;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,iBAAiB,EAAE,OAAO,GACzB,IAAI,CAUN;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,OAAO,EACZ,aAAa,EAAE,OAAO,EACtB,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,GAAG,GAAG,IAAI,CAcZ;AAyBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,aAAa,EAAE,OAAO,EACtB,SAAS,CAAC,EAAE,MAAM,GACjB,cAAc,CAgChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAKtF;AAyBD;;aAEa;AACb,wBAAgB,oBAAoB,CAAC,sBAAsB,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc,CAK9F;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,sBAAsB,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc,CAU9F;AAYD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CA+B7D"}