@n24q02m/hull 0.0.0-stage → 0.4.0-beta.9

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 (231) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +166 -2
  3. package/build/auth/credential-form.d.ts +163 -0
  4. package/build/auth/credential-form.d.ts.map +1 -0
  5. package/build/auth/credential-form.js +1869 -0
  6. package/build/auth/credential-form.js.map +1 -0
  7. package/build/auth/delegated-oauth-app.d.ts +90 -0
  8. package/build/auth/delegated-oauth-app.d.ts.map +1 -0
  9. package/build/auth/delegated-oauth-app.js +881 -0
  10. package/build/auth/delegated-oauth-app.js.map +1 -0
  11. package/build/auth/index.d.ts +12 -0
  12. package/build/auth/index.d.ts.map +1 -0
  13. package/build/auth/index.js +12 -0
  14. package/build/auth/index.js.map +1 -0
  15. package/build/auth/local-oauth-app.d.ts +152 -0
  16. package/build/auth/local-oauth-app.d.ts.map +1 -0
  17. package/build/auth/local-oauth-app.js +975 -0
  18. package/build/auth/local-oauth-app.js.map +1 -0
  19. package/build/auth/relay-login.d.ts +46 -0
  20. package/build/auth/relay-login.d.ts.map +1 -0
  21. package/build/auth/relay-login.js +197 -0
  22. package/build/auth/relay-login.js.map +1 -0
  23. package/build/auth/router.d.ts +18 -0
  24. package/build/auth/router.d.ts.map +1 -0
  25. package/build/auth/router.js +62 -0
  26. package/build/auth/router.js.map +1 -0
  27. package/build/auth/session-store.d.ts +49 -0
  28. package/build/auth/session-store.d.ts.map +1 -0
  29. package/build/auth/session-store.js +94 -0
  30. package/build/auth/session-store.js.map +1 -0
  31. package/build/auth/stable-sub.d.ts +27 -0
  32. package/build/auth/stable-sub.d.ts.map +1 -0
  33. package/build/auth/stable-sub.js +38 -0
  34. package/build/auth/stable-sub.js.map +1 -0
  35. package/build/auth/well-known.d.ts +4 -0
  36. package/build/auth/well-known.d.ts.map +1 -0
  37. package/build/auth/well-known.js +26 -0
  38. package/build/auth/well-known.js.map +1 -0
  39. package/build/cli/build-cli.d.ts +97 -0
  40. package/build/cli/build-cli.d.ts.map +1 -0
  41. package/build/cli/build-cli.js +309 -0
  42. package/build/cli/build-cli.js.map +1 -0
  43. package/build/cli/index.d.ts +2 -0
  44. package/build/cli/index.d.ts.map +1 -0
  45. package/build/cli/index.js +2 -0
  46. package/build/cli/index.js.map +1 -0
  47. package/build/crypto/aes.d.ts +8 -0
  48. package/build/crypto/aes.d.ts.map +1 -0
  49. package/build/crypto/aes.js +27 -0
  50. package/build/crypto/aes.js.map +1 -0
  51. package/build/crypto/ecdh.d.ts +5 -0
  52. package/build/crypto/ecdh.d.ts.map +1 -0
  53. package/build/crypto/ecdh.js +16 -0
  54. package/build/crypto/ecdh.js.map +1 -0
  55. package/build/crypto/index.d.ts +4 -0
  56. package/build/crypto/index.d.ts.map +1 -0
  57. package/build/crypto/index.js +4 -0
  58. package/build/crypto/index.js.map +1 -0
  59. package/build/crypto/kdf.d.ts +15 -0
  60. package/build/crypto/kdf.d.ts.map +1 -0
  61. package/build/crypto/kdf.js +26 -0
  62. package/build/crypto/kdf.js.map +1 -0
  63. package/build/index.d.ts +19 -0
  64. package/build/index.d.ts.map +1 -0
  65. package/build/index.js +28 -0
  66. package/build/index.js.map +1 -0
  67. package/build/lifecycle/lock.d.ts +68 -0
  68. package/build/lifecycle/lock.d.ts.map +1 -0
  69. package/build/lifecycle/lock.js +173 -0
  70. package/build/lifecycle/lock.js.map +1 -0
  71. package/build/oauth/cache.d.ts +22 -0
  72. package/build/oauth/cache.d.ts.map +1 -0
  73. package/build/oauth/cache.js +24 -0
  74. package/build/oauth/cache.js.map +1 -0
  75. package/build/oauth/index.d.ts +6 -0
  76. package/build/oauth/index.d.ts.map +1 -0
  77. package/build/oauth/index.js +6 -0
  78. package/build/oauth/index.js.map +1 -0
  79. package/build/oauth/jwt-issuer.d.ts +87 -0
  80. package/build/oauth/jwt-issuer.d.ts.map +1 -0
  81. package/build/oauth/jwt-issuer.js +205 -0
  82. package/build/oauth/jwt-issuer.js.map +1 -0
  83. package/build/oauth/pkce.d.ts +10 -0
  84. package/build/oauth/pkce.d.ts.map +1 -0
  85. package/build/oauth/pkce.js +34 -0
  86. package/build/oauth/pkce.js.map +1 -0
  87. package/build/oauth/provider.d.ts +52 -0
  88. package/build/oauth/provider.d.ts.map +1 -0
  89. package/build/oauth/provider.js +98 -0
  90. package/build/oauth/provider.js.map +1 -0
  91. package/build/oauth/user-store.d.ts +25 -0
  92. package/build/oauth/user-store.d.ts.map +1 -0
  93. package/build/oauth/user-store.js +85 -0
  94. package/build/oauth/user-store.js.map +1 -0
  95. package/build/relay/browser.d.ts +15 -0
  96. package/build/relay/browser.d.ts.map +1 -0
  97. package/build/relay/browser.js +121 -0
  98. package/build/relay/browser.js.map +1 -0
  99. package/build/relay/client.d.ts +36 -0
  100. package/build/relay/client.d.ts.map +1 -0
  101. package/build/relay/client.js +135 -0
  102. package/build/relay/client.js.map +1 -0
  103. package/build/relay/session.d.ts +12 -0
  104. package/build/relay/session.d.ts.map +1 -0
  105. package/build/relay/session.js +55 -0
  106. package/build/relay/session.js.map +1 -0
  107. package/build/relay/tool-helpers.d.ts +85 -0
  108. package/build/relay/tool-helpers.d.ts.map +1 -0
  109. package/build/relay/tool-helpers.js +103 -0
  110. package/build/relay/tool-helpers.js.map +1 -0
  111. package/build/relay/wordlist.d.ts +2 -0
  112. package/build/relay/wordlist.d.ts.map +1 -0
  113. package/build/relay/wordlist.js +7782 -0
  114. package/build/relay/wordlist.js.map +1 -0
  115. package/build/schema/types.d.ts +67 -0
  116. package/build/schema/types.d.ts.map +1 -0
  117. package/build/schema/types.js +2 -0
  118. package/build/schema/types.js.map +1 -0
  119. package/build/storage/atomic-write.d.ts +8 -0
  120. package/build/storage/atomic-write.d.ts.map +1 -0
  121. package/build/storage/atomic-write.js +26 -0
  122. package/build/storage/atomic-write.js.map +1 -0
  123. package/build/storage/backends.d.ts +74 -0
  124. package/build/storage/backends.d.ts.map +1 -0
  125. package/build/storage/backends.js +193 -0
  126. package/build/storage/backends.js.map +1 -0
  127. package/build/storage/config-file.d.ts +35 -0
  128. package/build/storage/config-file.d.ts.map +1 -0
  129. package/build/storage/config-file.js +259 -0
  130. package/build/storage/config-file.js.map +1 -0
  131. package/build/storage/credential-store.d.ts +71 -0
  132. package/build/storage/credential-store.d.ts.map +1 -0
  133. package/build/storage/credential-store.js +91 -0
  134. package/build/storage/credential-store.js.map +1 -0
  135. package/build/storage/data-dir.d.ts +58 -0
  136. package/build/storage/data-dir.d.ts.map +1 -0
  137. package/build/storage/data-dir.js +79 -0
  138. package/build/storage/data-dir.js.map +1 -0
  139. package/build/storage/encryption.d.ts +9 -0
  140. package/build/storage/encryption.d.ts.map +1 -0
  141. package/build/storage/encryption.js +27 -0
  142. package/build/storage/encryption.js.map +1 -0
  143. package/build/storage/home-dir.d.ts +11 -0
  144. package/build/storage/home-dir.d.ts.map +1 -0
  145. package/build/storage/home-dir.js +18 -0
  146. package/build/storage/home-dir.js.map +1 -0
  147. package/build/storage/index.d.ts +12 -0
  148. package/build/storage/index.d.ts.map +1 -0
  149. package/build/storage/index.js +15 -0
  150. package/build/storage/index.js.map +1 -0
  151. package/build/storage/machine-id.d.ts +4 -0
  152. package/build/storage/machine-id.d.ts.map +1 -0
  153. package/build/storage/machine-id.js +73 -0
  154. package/build/storage/machine-id.js.map +1 -0
  155. package/build/storage/mode.d.ts +23 -0
  156. package/build/storage/mode.d.ts.map +1 -0
  157. package/build/storage/mode.js +46 -0
  158. package/build/storage/mode.js.map +1 -0
  159. package/build/storage/per-plugin-store.d.ts +65 -0
  160. package/build/storage/per-plugin-store.d.ts.map +1 -0
  161. package/build/storage/per-plugin-store.js +194 -0
  162. package/build/storage/per-plugin-store.js.map +1 -0
  163. package/build/storage/resolver.d.ts +7 -0
  164. package/build/storage/resolver.d.ts.map +1 -0
  165. package/build/storage/resolver.js +38 -0
  166. package/build/storage/resolver.js.map +1 -0
  167. package/build/storage/session-lock.d.ts +19 -0
  168. package/build/storage/session-lock.d.ts.map +1 -0
  169. package/build/storage/session-lock.js +105 -0
  170. package/build/storage/session-lock.js.map +1 -0
  171. package/build/token-auth/authenticator.d.ts +70 -0
  172. package/build/token-auth/authenticator.d.ts.map +1 -0
  173. package/build/token-auth/authenticator.js +146 -0
  174. package/build/token-auth/authenticator.js.map +1 -0
  175. package/build/token-auth/cli.d.ts +38 -0
  176. package/build/token-auth/cli.d.ts.map +1 -0
  177. package/build/token-auth/cli.js +192 -0
  178. package/build/token-auth/cli.js.map +1 -0
  179. package/build/token-auth/context.d.ts +30 -0
  180. package/build/token-auth/context.d.ts.map +1 -0
  181. package/build/token-auth/context.js +32 -0
  182. package/build/token-auth/context.js.map +1 -0
  183. package/build/token-auth/http-guard.d.ts +26 -0
  184. package/build/token-auth/http-guard.d.ts.map +1 -0
  185. package/build/token-auth/http-guard.js +51 -0
  186. package/build/token-auth/http-guard.js.map +1 -0
  187. package/build/token-auth/index.d.ts +14 -0
  188. package/build/token-auth/index.d.ts.map +1 -0
  189. package/build/token-auth/index.js +14 -0
  190. package/build/token-auth/index.js.map +1 -0
  191. package/build/token-auth/limiter.d.ts +20 -0
  192. package/build/token-auth/limiter.d.ts.map +1 -0
  193. package/build/token-auth/limiter.js +41 -0
  194. package/build/token-auth/limiter.js.map +1 -0
  195. package/build/token-auth/loopback.d.ts +17 -0
  196. package/build/token-auth/loopback.d.ts.map +1 -0
  197. package/build/token-auth/loopback.js +62 -0
  198. package/build/token-auth/loopback.js.map +1 -0
  199. package/build/token-auth/py-compat.d.ts +24 -0
  200. package/build/token-auth/py-compat.d.ts.map +1 -0
  201. package/build/token-auth/py-compat.js +76 -0
  202. package/build/token-auth/py-compat.js.map +1 -0
  203. package/build/token-auth/tokens.d.ts +36 -0
  204. package/build/token-auth/tokens.d.ts.map +1 -0
  205. package/build/token-auth/tokens.js +115 -0
  206. package/build/token-auth/tokens.js.map +1 -0
  207. package/build/token-auth/users.d.ts +62 -0
  208. package/build/token-auth/users.d.ts.map +1 -0
  209. package/build/token-auth/users.js +174 -0
  210. package/build/token-auth/users.js.map +1 -0
  211. package/build/transport/cache.d.ts +19 -0
  212. package/build/transport/cache.d.ts.map +1 -0
  213. package/build/transport/cache.js +76 -0
  214. package/build/transport/cache.js.map +1 -0
  215. package/build/transport/index.d.ts +5 -0
  216. package/build/transport/index.d.ts.map +1 -0
  217. package/build/transport/index.js +4 -0
  218. package/build/transport/index.js.map +1 -0
  219. package/build/transport/local-server.d.ts +173 -0
  220. package/build/transport/local-server.d.ts.map +1 -0
  221. package/build/transport/local-server.js +457 -0
  222. package/build/transport/local-server.js.map +1 -0
  223. package/build/transport/oauth-middleware.d.ts +45 -0
  224. package/build/transport/oauth-middleware.d.ts.map +1 -0
  225. package/build/transport/oauth-middleware.js +86 -0
  226. package/build/transport/oauth-middleware.js.map +1 -0
  227. package/build/transport/streamable-http.d.ts +54 -0
  228. package/build/transport/streamable-http.d.ts.map +1 -0
  229. package/build/transport/streamable-http.js +57 -0
  230. package/build/transport/streamable-http.js.map +1 -0
  231. package/package.json +107 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/token-auth/users.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAUH,6EAA6E;AAC7E,qBAAa,UAAW,SAAQ,KAAK;IACnC,YAAY,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAGzD;CACF;AAED,wEAAwE;AACxE,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,YAAW;IAExB,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAED,MAAM,WAAW,eAAe;IAC9B,sDAAsD;IACtD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAED,yCAAyC;AACzC,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IACxC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;CACjC;AAED,yFAAyF;AACzF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;CACzC;AAqED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAiB9D;AAED,6EAA6E;AAC7E,wBAAsB,eAAe,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAKrH;AA2BD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAMtE"}
@@ -0,0 +1,174 @@
1
+ /**
2
+ * users.toml: the multi-user token table (auth mode `multi`).
3
+ *
4
+ * Same schema and validation order as core-py `hull_core.auth.users`:
5
+ *
6
+ * [users.<uid>]
7
+ * token_hash = "scrypt$..." # mint with `hull token hash`
8
+ * enabled = true
9
+ * namespace = "<uid-or-shared>" # data isolation root
10
+ * allowed_roots = ["/data/alice"] # filesystem roots tools may touch
11
+ * [users.<uid>.limits]
12
+ * rpm = 60 # optional per-minute request cap
13
+ *
14
+ * Admin is host-side only: edit the file, restart the server. There is no
15
+ * admin API and no self-service.
16
+ */
17
+ import { readFileSync, realpathSync, statSync } from 'node:fs';
18
+ import { basename, dirname, join, resolve, sep } from 'node:path';
19
+ import { parse, TomlError } from 'smol-toml';
20
+ import { pyRepr } from './py-compat.js';
21
+ import { isValidTokenHash, verifyToken } from './tokens.js';
22
+ /** Raised when users.toml is missing, malformed, or semantically invalid. */
23
+ export class UsersError extends Error {
24
+ constructor(message, options) {
25
+ super(message, options);
26
+ this.name = 'UsersError';
27
+ }
28
+ }
29
+ /** Raised by `ensurePathAllowed` (core-py raises `PermissionError`). */
30
+ export class PathNotAllowedError extends Error {
31
+ constructor(message) {
32
+ super(message);
33
+ this.code = 'EACCES';
34
+ this.name = 'PathNotAllowedError';
35
+ }
36
+ }
37
+ function isTable(value) {
38
+ // TOML tables parse to plain (null-prototype) objects; arrays and
39
+ // TomlDate values are excluded by their tags.
40
+ return Object.prototype.toString.call(value) === '[object Object]';
41
+ }
42
+ function readTomlText(path) {
43
+ let isFile = false;
44
+ try {
45
+ isFile = statSync(path).isFile();
46
+ }
47
+ catch {
48
+ isFile = false;
49
+ }
50
+ if (!isFile)
51
+ throw new UsersError(`users file not found: ${path}`);
52
+ const bytes = readFileSync(path);
53
+ try {
54
+ return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
55
+ }
56
+ catch (err) {
57
+ throw new UsersError(`users file is not valid UTF-8: ${path}`, { cause: err });
58
+ }
59
+ }
60
+ function field(entry, key, fallback) {
61
+ return Object.hasOwn(entry, key) ? entry[key] : fallback;
62
+ }
63
+ function parseUser(uid, entry) {
64
+ const who = `user ${pyRepr(uid)}`;
65
+ if (!isTable(entry))
66
+ throw new UsersError(`${who}: expected a table`);
67
+ const tokenHash = field(entry, 'token_hash', undefined);
68
+ if (typeof tokenHash !== 'string' || !isValidTokenHash(tokenHash)) {
69
+ throw new UsersError(`${who}: token_hash missing or not a scrypt encoding (mint one with \`hull token hash\`)`);
70
+ }
71
+ const namespace = field(entry, 'namespace', undefined);
72
+ if (typeof namespace !== 'string' || namespace.length === 0) {
73
+ throw new UsersError(`${who}: namespace must be a non-empty string`);
74
+ }
75
+ const enabled = field(entry, 'enabled', true);
76
+ if (typeof enabled !== 'boolean')
77
+ throw new UsersError(`${who}: enabled must be a boolean`);
78
+ const roots = field(entry, 'allowed_roots', []);
79
+ if (!Array.isArray(roots) || !roots.every((r) => typeof r === 'string')) {
80
+ throw new UsersError(`${who}: allowed_roots must be a list of strings`);
81
+ }
82
+ const limits = field(entry, 'limits', {});
83
+ if (!isTable(limits))
84
+ throw new UsersError(`${who}: limits must be a table`);
85
+ const rpmRaw = field(limits, 'rpm', undefined);
86
+ let rpm = null;
87
+ if (rpmRaw !== undefined) {
88
+ if (typeof rpmRaw !== 'bigint' || rpmRaw < 1n) {
89
+ throw new UsersError(`${who}: limits.rpm must be a positive integer`);
90
+ }
91
+ rpm = Number(rpmRaw);
92
+ }
93
+ return Object.freeze({
94
+ uid,
95
+ tokenHash,
96
+ enabled,
97
+ namespace,
98
+ allowedRoots: Object.freeze([...roots]),
99
+ limits: Object.freeze({ rpm })
100
+ });
101
+ }
102
+ /**
103
+ * Parse and validate users.toml into a `uid -> TokenUser` map.
104
+ *
105
+ * Synchronous on purpose: it runs once at server build time, and each
106
+ * `token_hash` is checked with one full scrypt derivation (like core-py
107
+ * `is_valid_encoding`) so an unusable row fails the boot, not a request.
108
+ */
109
+ export function loadUsers(path) {
110
+ const text = readTomlText(path);
111
+ let raw;
112
+ try {
113
+ raw = parse(text, { integersAsBigInt: true });
114
+ }
115
+ catch (err) {
116
+ const reason = err instanceof TomlError ? err.message : String(err);
117
+ throw new UsersError(`users file is not valid TOML: ${path}: ${reason}`, { cause: err });
118
+ }
119
+ const table = field(raw, 'users', undefined);
120
+ if (!isTable(table) || Object.keys(table).length === 0) {
121
+ throw new UsersError('users file must contain a non-empty [users] table');
122
+ }
123
+ const users = new Map();
124
+ for (const [uid, entry] of Object.entries(table))
125
+ users.set(uid, parseUser(uid, entry));
126
+ return users;
127
+ }
128
+ /** Return the first user whose `tokenHash` verifies `token`, else `null`. */
129
+ export async function findUserByToken(users, token) {
130
+ for (const user of users.values()) {
131
+ if (await verifyToken(token, user.tokenHash))
132
+ return user;
133
+ }
134
+ return null;
135
+ }
136
+ // Python `Path.resolve()` (non-strict): absolutize, follow symlinks as far
137
+ // as the path exists, keep the non-existent tail lexically.
138
+ function resolveLoose(target) {
139
+ const absolute = resolve(target);
140
+ try {
141
+ return realpathSync.native(absolute);
142
+ }
143
+ catch {
144
+ const parent = dirname(absolute);
145
+ if (parent === absolute)
146
+ return absolute;
147
+ return join(resolveLoose(parent), basename(absolute));
148
+ }
149
+ }
150
+ function comparable(p) {
151
+ return process.platform === 'win32' ? p.toLowerCase() : p;
152
+ }
153
+ function isWithin(resolved, root) {
154
+ const target = comparable(resolved);
155
+ const base = comparable(root);
156
+ if (target === base)
157
+ return true;
158
+ const prefix = base.endsWith(sep) ? base : base + sep;
159
+ return target.startsWith(prefix);
160
+ }
161
+ /**
162
+ * Enforce the caller's `allowedRoots`. An empty list grants nothing. Both
163
+ * sides are resolved first, so a symlinked escape needs its resolved target
164
+ * to sit under a root too. Throws `PathNotAllowedError` on denial.
165
+ */
166
+ export function ensurePathAllowed(scope, path) {
167
+ const resolved = resolveLoose(path);
168
+ for (const root of scope.allowedRoots) {
169
+ if (isWithin(resolved, resolveLoose(root)))
170
+ return;
171
+ }
172
+ throw new PathNotAllowedError(`path ${resolved} is outside allowed roots for user ${pyRepr(scope.uid)}`);
173
+ }
174
+ //# sourceMappingURL=users.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"users.js","sourceRoot":"","sources":["../../src/token-auth/users.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Tools-list cache with version-aware key (D10) — TS parity with core-py.
3
+ *
4
+ * Cache filename: ``<server>-<port>-<srvVersion>-<coreVersion>.tools.json``.
5
+ * A mismatch on either ``srvVersion`` (the upstream MCP server's version) or
6
+ * ``coreVersion`` (mcp-core itself) invalidates the cache, so an upgrade on
7
+ * either side never serves a stale tool surface.
8
+ *
9
+ * ``persistToolsCache`` writes via ``.tmp`` + ``rename`` (Windows-safe) and
10
+ * suppresses all filesystem errors with a debug log. This is the D10
11
+ * root-cause fix for ``Failed to persist capabilities cache`` hard-failures
12
+ * (crg #384).
13
+ */
14
+ export declare function cacheDir(): string;
15
+ export declare function cacheFilename(serverName: string, port: number, srvVersion: string, coreVersion: string): string;
16
+ export declare function atomicWrite(path: string, content: string): void;
17
+ export declare function persistToolsCache(serverName: string, port: number, srvVersion: string, coreVersion: string, tools: unknown[]): void;
18
+ export declare function loadToolsCache(serverName: string, port: number, srvVersion: string, coreVersion: string): unknown[] | null;
19
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/transport/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAUH,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAK/G;AAOD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAgB/D;AAED,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,OAAO,EAAE,GACf,IAAI,CASN;AAED,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,EAAE,GAAG,IAAI,CAUlB"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Tools-list cache with version-aware key (D10) — TS parity with core-py.
3
+ *
4
+ * Cache filename: ``<server>-<port>-<srvVersion>-<coreVersion>.tools.json``.
5
+ * A mismatch on either ``srvVersion`` (the upstream MCP server's version) or
6
+ * ``coreVersion`` (mcp-core itself) invalidates the cache, so an upgrade on
7
+ * either side never serves a stale tool surface.
8
+ *
9
+ * ``persistToolsCache`` writes via ``.tmp`` + ``rename`` (Windows-safe) and
10
+ * suppresses all filesystem errors with a debug log. This is the D10
11
+ * root-cause fix for ``Failed to persist capabilities cache`` hard-failures
12
+ * (crg #384).
13
+ */
14
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
15
+ import { homedir } from 'node:os';
16
+ import { dirname, join } from 'node:path';
17
+ // Self-namespace import lets ``vi.spyOn(cacheModule, 'cacheDir' | 'atomicWrite')``
18
+ // intercept internal calls (ESM bindings make plain references unmockable).
19
+ import * as self from './cache.js';
20
+ export function cacheDir() {
21
+ return join(homedir(), '.config', 'mcp', 'cache');
22
+ }
23
+ export function cacheFilename(serverName, port, srvVersion, coreVersion) {
24
+ const safeName = serverName.replace(/[^a-zA-Z0-9.-]/g, '_').replace(/\.\./g, '__');
25
+ const safeSrvVersion = srvVersion.replace(/[^a-zA-Z0-9.-]/g, '_').replace(/\.\./g, '__');
26
+ const safeCoreVersion = coreVersion.replace(/[^a-zA-Z0-9.-]/g, '_').replace(/\.\./g, '__');
27
+ return `${safeName}-${port}-${safeSrvVersion}-${safeCoreVersion}.tools.json`;
28
+ }
29
+ // Cache directories already chmod-tightened in this process. The directory is
30
+ // still re-created if missing (OS cleanup), but chmod runs once per directory
31
+ // so a pre-existing overly-permissive directory is tightened without paying
32
+ // the syscall on every hot-path write (TOCTOU hardening).
33
+ const initDirs = new Set();
34
+ export function atomicWrite(path, content) {
35
+ const dir = dirname(path);
36
+ if (dir) {
37
+ if (!existsSync(dir)) {
38
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
39
+ }
40
+ if (!initDirs.has(dir)) {
41
+ if (process.platform !== 'win32') {
42
+ chmodSync(dir, 0o700);
43
+ }
44
+ initDirs.add(dir);
45
+ }
46
+ }
47
+ const tmp = `${path}.tmp`;
48
+ writeFileSync(tmp, content, { encoding: 'utf-8', mode: 0o600 });
49
+ renameSync(tmp, path);
50
+ }
51
+ export function persistToolsCache(serverName, port, srvVersion, coreVersion, tools) {
52
+ const path = join(self.cacheDir(), cacheFilename(serverName, port, srvVersion, coreVersion));
53
+ const payload = JSON.stringify({ tools, srv_version: srvVersion, core_version: coreVersion });
54
+ try {
55
+ self.atomicWrite(path, payload);
56
+ }
57
+ catch (err) {
58
+ // Suppress per D10 — log only.
59
+ console.debug('Failed to persist capabilities cache for %s: %s', serverName, err);
60
+ }
61
+ }
62
+ export function loadToolsCache(serverName, port, srvVersion, coreVersion) {
63
+ const path = join(self.cacheDir(), cacheFilename(serverName, port, srvVersion, coreVersion));
64
+ if (!existsSync(path))
65
+ return null;
66
+ try {
67
+ const payload = JSON.parse(readFileSync(path, 'utf-8'));
68
+ if (payload.srv_version !== srvVersion || payload.core_version !== coreVersion)
69
+ return null;
70
+ return Array.isArray(payload.tools) ? payload.tools : null;
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ }
76
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/transport/cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export type { HttpAuthConfig } from '../token-auth/authenticator.js';
2
+ export { type HttpRoute, type HttpServerHandle, type JWTClaims, type RunHttpServerOptions, runHttpServer } from './local-server.js';
3
+ export { type AuthenticatedRequest, OAuthMiddleware, type OAuthMiddlewareOptions } from './oauth-middleware.js';
4
+ export { StreamableHTTPServer, type StreamableHTTPServerOptions } from './streamable-http.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transport/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AACpE,OAAO,EACL,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,oBAAoB,EACzB,aAAa,EACd,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,KAAK,oBAAoB,EACzB,eAAe,EACf,KAAK,sBAAsB,EAC5B,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,oBAAoB,EACpB,KAAK,2BAA2B,EACjC,MAAM,sBAAsB,CAAA"}
@@ -0,0 +1,4 @@
1
+ export { runHttpServer } from './local-server.js';
2
+ export { OAuthMiddleware } from './oauth-middleware.js';
3
+ export { StreamableHTTPServer } from './streamable-http.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/transport/index.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,aAAa,EACd,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAEL,eAAe,EAEhB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,oBAAoB,EAErB,MAAM,sBAAsB,CAAA"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Local MCP server entry point combining OAuth 2.1 AS + Streamable HTTP transport.
3
+ *
4
+ * Composes:
5
+ * 1. OAuth 2.1 AS (credential form + token exchange) -- serves /authorize,
6
+ * /token, /otp, /setup-status, /.well-known/*
7
+ * 2. MCP Streamable HTTP transport -- serves /mcp with optional Bearer auth
8
+ * 3. /health endpoint -- liveness probe
9
+ *
10
+ * For servers without credential input (e.g. godot) ``relaySchema`` may be
11
+ * omitted: only /mcp (unauthenticated) and /health are served.
12
+ *
13
+ * This is a TypeScript port of ``core-py``'s ``local_server.py``. Route layout,
14
+ * Bearer enforcement, and lifecycle semantics are kept identical.
15
+ */
16
+ import { type IncomingMessage, type ServerResponse } from 'node:http';
17
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
18
+ import type { JWTPayload } from 'jose';
19
+ import type { RelayConfigSchema } from '../auth/credential-form.js';
20
+ import { type DelegatedOAuthAppOptions } from '../auth/delegated-oauth-app.js';
21
+ import { type CredentialsCallback, type StepCallback } from '../auth/local-oauth-app.js';
22
+ import type { JWTIssuer } from '../oauth/jwt-issuer.js';
23
+ import { type HttpAuthConfig } from '../token-auth/authenticator.js';
24
+ /** Decoded JWT claims returned by JWTIssuer.verifyAccessToken. */
25
+ export type JWTClaims = JWTPayload & {
26
+ anonymous?: boolean;
27
+ };
28
+ /**
29
+ * A route a consumer registers on the same port ``runHttpServer`` serves.
30
+ *
31
+ * The built-in ``/mcp`` and ``/health`` routes are matched FIRST and cannot be
32
+ * overridden -- a consumer cannot accidentally shadow the MCP transport or the
33
+ * liveness probe. Everything else is tried against ``extraRoutes`` before the
34
+ * OAuth app, which is a catch-all for the remaining paths.
35
+ */
36
+ export interface HttpRoute {
37
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE';
38
+ path: string;
39
+ handler: (req: IncomingMessage, res: ServerResponse) => Promise<void> | void;
40
+ }
41
+ export interface RunHttpServerOptions {
42
+ /** Identifier used for JWT iss/aud and credential storage. */
43
+ serverName: string;
44
+ /** If undefined, server has NO auth (e.g., godot). */
45
+ relaySchema?: RelayConfigSchema;
46
+ /** Optional pre-created issuer, primarily for isolated tests and custom key storage. */
47
+ jwtIssuer?: JWTIssuer;
48
+ /**
49
+ * Mutually exclusive with `relaySchema`. When set, the OAuth app is the
50
+ * delegated provider (upstream redirect or device_code) instead of the local
51
+ * credential form. The `serverName` and `jwtIssuer` are supplied by this
52
+ * function; callers provide only `flow`, `upstream`, and `onTokenReceived`.
53
+ */
54
+ delegatedOAuth?: Omit<DelegatedOAuthAppOptions, 'serverName' | 'jwtIssuer'>;
55
+ /** 0 = auto-find a free port. Default: 0. */
56
+ port?: number;
57
+ /** Host to bind. Default '127.0.0.1'. */
58
+ host?: string;
59
+ /** Optional callback invoked with credentials after POST /authorize. */
60
+ onCredentialsSaved?: CredentialsCallback;
61
+ /** Optional callback invoked with step data after POST /otp. */
62
+ onStepSubmitted?: StepCallback;
63
+ /**
64
+ * Called after server ready so callers can wire background tasks (e.g.
65
+ * GDrive device code poll) to the form's ``/setup-status`` endpoint.
66
+ *
67
+ * Accepts either arity for backward compatibility:
68
+ * - Legacy 1-arg: ``hook(markComplete)`` -- success-only (older consumers).
69
+ * - New 2-arg: ``hook(markComplete, markFailed)`` -- surfaces upstream
70
+ * errors (``invalid_grant`` / ``expired_token`` / ``access_denied``)
71
+ * to the browser form so it stops polling and shows the error.
72
+ *
73
+ * Prefer the 2-arg form for new code. Arity is detected via
74
+ * ``Function.prototype.length``.
75
+ */
76
+ setupCompleteHook?: ((markComplete: (key?: string) => void) => void) | ((markComplete: (key?: string) => void, markFailed: (key?: string, error?: string) => void) => void);
77
+ /**
78
+ * Optional renderer used in place of the default credential form on GET
79
+ * /authorize. Passed through to ``createLocalOAuthApp``.
80
+ */
81
+ customCredentialFormHtml?: (schema: RelayConfigSchema, options: {
82
+ submitUrl: string;
83
+ prefill?: Record<string, string>;
84
+ }) => string;
85
+ /** Forwarded to the local OAuth app; see LocalOAuthAppOptions.stableSubEnabled. */
86
+ stableSubEnabled?: boolean;
87
+ /**
88
+ * Optional middleware invoked after JWT verification and before the MCP
89
+ * transport handles the request. Called with verified claims and a ``next``
90
+ * function that invokes the MCP transport. Consumers use this to wrap the
91
+ * request in AsyncLocalStorage (e.g., for per-user token lookup).
92
+ */
93
+ authScope?: (claims: JWTClaims, next: () => Promise<void>) => Promise<void>;
94
+ /**
95
+ * When `true`, skip Bearer token validation on the MCP endpoint and treat
96
+ * the caller as anonymous. Intended for deployments behind an external
97
+ * auth boundary (reverse proxy, API gateway like agentgateway/Zitadel) that
98
+ * already enforces authentication. The deployer is responsible for ensuring
99
+ * the network in front of this server is locked down — anyone who can
100
+ * reach `/mcp` directly will get tool access.
101
+ *
102
+ * Wire via env var: `authDisabled: process.env.MCP_AUTH_DISABLE === '1'`.
103
+ *
104
+ * When set, `authScope` (if provided) receives anonymous claims
105
+ * `{ sub: 'anonymous', anonymous: true }`.
106
+ */
107
+ authDisabled?: boolean;
108
+ /**
109
+ * Whether to auto-open the setup URL in the user's browser when the stored
110
+ * config is not yet complete. Default ``true`` -- first-run setup for the
111
+ * servers built on this function depends on it.
112
+ *
113
+ * Set ``false`` when the consumer already hands the setup URL to the user by
114
+ * another route (a tool-call result, a log line, its own UI). Otherwise there
115
+ * are two entry points into the SAME temporary server, so the user starts two
116
+ * consent flows: whichever finishes first resolves the flow and closes the
117
+ * server, and the other tab's redirect then lands on a dead port
118
+ * (ERR_CONNECTION_REFUSED). The user is left with one "Setup complete" page
119
+ * and one connection error, unable to tell which one counted.
120
+ */
121
+ openBrowser?: boolean;
122
+ /**
123
+ * Extra HTTP routes served on the same port, matched by exact pathname and
124
+ * method. Tried after ``/mcp`` and ``/health`` (which always win) and before
125
+ * the OAuth app, whose handler is a catch-all for every other path -- so a
126
+ * route registered here is the only way for a consumer to own an endpoint
127
+ * inside this process. Used by servers that need their own OAuth callback,
128
+ * e.g. adding a second upstream account to an already-authenticated subject.
129
+ */
130
+ extraRoutes?: HttpRoute[];
131
+ /**
132
+ * Token-table HTTP auth, the core-py `[server] auth` modes:
133
+ *
134
+ * - `{ mode: 'no-auth' }` -- no Authorization header; every request runs as
135
+ * `localAuthContext()`. Refuses (throws `ServerConfigError`) any `host`
136
+ * that is not loopback.
137
+ * - `{ mode: 'token', tokenHash, rpm? }` -- one shared bearer token checked
138
+ * against a `scrypt$...` hash; caller `uid` is `"shared"`.
139
+ * - `{ mode: 'multi', usersFile?, users? }` -- users.toml table (default
140
+ * `~/.hull/users.toml`); 401 wrong token, 403 disabled user, 429 over the
141
+ * user's `limits.rpm`.
142
+ *
143
+ * The guard runs before routing for every path except `/health` (so `/mcp`
144
+ * and `extraRoutes` both see the caller via `currentUser()`), and rejects
145
+ * with core-py's JSON body and `WWW-Authenticate: Bearer` on 401. The config
146
+ * is validated before the port is bound. Mutually exclusive with the
147
+ * OAuth/relay options (`relaySchema`, `delegatedOAuth`, `jwtIssuer`,
148
+ * `authDisabled`, `authScope`).
149
+ */
150
+ auth?: HttpAuthConfig;
151
+ }
152
+ export interface HttpServerHandle {
153
+ /** Actual TCP port bound. Non-zero even when ``options.port`` was 0. */
154
+ port: number;
155
+ /** Host bound. */
156
+ host: string;
157
+ /** Cleanly close transport + http server. */
158
+ close: () => Promise<void>;
159
+ }
160
+ /**
161
+ * Start an HTTP server with optional local OAuth AS + MCP Streamable HTTP transport.
162
+ *
163
+ * Behavior:
164
+ * - If ``relaySchema`` is provided, serves OAuth routes (/authorize, /token,
165
+ * /otp, /setup-status, /.well-known/*) AND /mcp with Bearer auth.
166
+ * - If ``relaySchema`` is undefined (e.g., godot), serves ONLY /mcp without
167
+ * auth (plus /health).
168
+ * - Binds to ``host:port``. Port 0 auto-assigns via the OS.
169
+ * - Returns a handle for lifecycle management; the server runs in the
170
+ * background until ``close()`` is called.
171
+ */
172
+ export declare function runHttpServer(serverFactory: () => McpServer, options: RunHttpServerOptions): Promise<HttpServerHandle>;
173
+ //# sourceMappingURL=local-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-server.d.ts","sourceRoot":"","sources":["../../src/transport/local-server.ts"],"names":[],"mappings":"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"}