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

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,457 @@
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 { randomUUID } from 'node:crypto';
17
+ import * as fs from 'node:fs';
18
+ import { createServer } from 'node:http';
19
+ import * as os from 'node:os';
20
+ import * as path from 'node:path';
21
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
22
+ import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
23
+ import { isSchemaComplete } from '../auth/credential-form.js';
24
+ import { createDelegatedOAuthApp } from '../auth/delegated-oauth-app.js';
25
+ import { createLocalOAuthApp } from '../auth/local-oauth-app.js';
26
+ import { jsonResponse } from '../auth/router.js';
27
+ import { refreshLockTimestamp, sweepStaleLocks, writeLockFile } from '../lifecycle/lock.js';
28
+ import { tryOpenBrowser } from '../relay/browser.js';
29
+ import { readStoredConfig } from '../storage/credential-store.js';
30
+ import { Authenticator } from '../token-auth/authenticator.js';
31
+ import { guardRequest } from '../token-auth/http-guard.js';
32
+ import { assertNoAuthLoopback } from '../token-auth/loopback.js';
33
+ import { extractBearerToken } from './oauth-middleware.js';
34
+ /**
35
+ * Start an HTTP server with optional local OAuth AS + MCP Streamable HTTP transport.
36
+ *
37
+ * Behavior:
38
+ * - If ``relaySchema`` is provided, serves OAuth routes (/authorize, /token,
39
+ * /otp, /setup-status, /.well-known/*) AND /mcp with Bearer auth.
40
+ * - If ``relaySchema`` is undefined (e.g., godot), serves ONLY /mcp without
41
+ * auth (plus /health).
42
+ * - Binds to ``host:port``. Port 0 auto-assigns via the OS.
43
+ * - Returns a handle for lifecycle management; the server runs in the
44
+ * background until ``close()`` is called.
45
+ */
46
+ export async function runHttpServer(serverFactory, options) {
47
+ const host = options.host ?? '127.0.0.1';
48
+ const wantedPort = options.port ?? 0;
49
+ // Token-table auth replaces the OAuth/relay stack entirely; combining them
50
+ // would leave two competing Bearer schemes on /mcp.
51
+ let tokenAuth = null;
52
+ if (options.auth !== undefined) {
53
+ const conflicting = ['relaySchema', 'delegatedOAuth', 'jwtIssuer', 'authDisabled', 'authScope'].filter((key) => options[key] !== undefined && options[key] !== false);
54
+ if (conflicting.length > 0) {
55
+ throw new Error(`\`auth\` is mutually exclusive with ${conflicting.map((key) => `\`${key}\``).join(', ')}`);
56
+ }
57
+ tokenAuth = new Authenticator(options.auth);
58
+ if (tokenAuth.mode === 'no-auth')
59
+ assertNoAuthLoopback(host);
60
+ }
61
+ // Edge auth deployment warning (per spec 2026-05-01-stdio-pure-http-multiuser
62
+ // §4.2.1). When ``PUBLIC_URL`` points to a non-localhost host but
63
+ // ``MCP_RELAY_PASSWORD`` is empty, the relay form is reachable from the
64
+ // public Internet without authentication — that's the wedge this gate
65
+ // closes. Operators running single-user dev on localhost intentionally
66
+ // skip the password; everyone else gets a startup warning so the misconfig
67
+ // doesn't pass silently.
68
+ const publicUrl = process.env.PUBLIC_URL ?? '';
69
+ const relayPassword = process.env.MCP_RELAY_PASSWORD ?? '';
70
+ const isLocalhost = /^https?:\/\/(localhost|127\.0\.0\.1)/.test(publicUrl);
71
+ if (tokenAuth === null && publicUrl && !isLocalhost && !relayPassword) {
72
+ console.warn('[hull] WARNING: HTTP mode public deployment without MCP_RELAY_PASSWORD — relay form is open to Internet');
73
+ }
74
+ let oauthApp = null;
75
+ let jwtIssuer = null;
76
+ if (options.relaySchema && options.delegatedOAuth) {
77
+ throw new Error('`relaySchema` and `delegatedOAuth` are mutually exclusive');
78
+ }
79
+ if (options.delegatedOAuth) {
80
+ oauthApp = await createDelegatedOAuthApp({
81
+ serverName: options.serverName,
82
+ flow: options.delegatedOAuth.flow,
83
+ upstream: options.delegatedOAuth.upstream,
84
+ onTokenReceived: options.delegatedOAuth.onTokenReceived,
85
+ sessionKv: options.delegatedOAuth.sessionKv,
86
+ jwtIssuer: options.jwtIssuer
87
+ });
88
+ jwtIssuer = oauthApp.jwtIssuer;
89
+ }
90
+ else if (options.relaySchema) {
91
+ oauthApp = await createLocalOAuthApp({
92
+ serverName: options.serverName,
93
+ relaySchema: options.relaySchema,
94
+ onCredentialsSaved: options.onCredentialsSaved,
95
+ onStepSubmitted: options.onStepSubmitted,
96
+ customCredentialFormHtml: options.customCredentialFormHtml,
97
+ stableSubEnabled: options.stableSubEnabled,
98
+ jwtIssuer: options.jwtIssuer
99
+ });
100
+ jwtIssuer = oauthApp.jwtIssuer;
101
+ }
102
+ // MCP per-session pattern (mirrors Python's StreamableHTTPSessionManager):
103
+ //
104
+ // - Each MCP client gets its own ``StreamableHTTPServerTransport`` keyed by
105
+ // Mcp-Session-Id, plus its own ``McpServer`` instance.
106
+ // - The first POST (initialize) has no session header; we mint a UUID via
107
+ // ``sessionIdGenerator`` and register the transport in ``onsessioninitialized``.
108
+ // - Subsequent POSTs (notifications/initialized, tools/list, tools/call)
109
+ // carry Mcp-Session-Id and are routed to the same transport, so the SDK's
110
+ // ``_initialized`` flag is in the right state for ``validateSession`` /
111
+ // ``validateProtocolVersion`` to accept the request.
112
+ // - Anything else -- an ID we never issued, or no ID on a request that
113
+ // cannot open a session -- is refused without building anything. Only
114
+ // ``initialize`` may open a session, which is what keeps the count of
115
+ // live ``McpServer`` instances tied to sessions rather than to requests.
116
+ //
117
+ // Stateless mode (sessionIdGenerator: undefined) was an earlier attempt; it
118
+ // returned HTTP 500 on the SDK-mandatory ``notifications/initialized`` POST
119
+ // because each request landed on a fresh transport with ``_initialized=false``
120
+ // and the SDK had no session manager to bridge them. Per-session routing is
121
+ // the same architecture the SDK examples + Python core-py use.
122
+ const transports = new Map();
123
+ const servers = new Map();
124
+ /** JSON-RPC error envelope, shaped like core-py's session manager replies. */
125
+ function jsonRpcError(res, status, code, message) {
126
+ jsonResponse(res, status, {
127
+ jsonrpc: '2.0',
128
+ id: 'server-error',
129
+ error: { code, message }
130
+ });
131
+ }
132
+ /**
133
+ * Buffer and parse the body of a request that carries no session ID.
134
+ *
135
+ * Only the bootstrap path pays this cost. Once a session exists the request
136
+ * stream is handed to the SDK untouched, as before -- we need the body here
137
+ * solely to tell an ``initialize`` apart from a request that has no business
138
+ * opening a session, and the SDK cannot re-read a stream we consumed, so the
139
+ * parsed value is passed on to ``handleRequest``.
140
+ *
141
+ * The size ceiling matches the SDK transport's own ``maximumMessageSize``
142
+ * default; without one, this buffer is an unbounded allocation reachable
143
+ * before any session exists.
144
+ */
145
+ async function readBootstrapBody(req) {
146
+ const MAX_BYTES = 4 * 1024 * 1024;
147
+ const chunks = [];
148
+ let size = 0;
149
+ for await (const chunk of req) {
150
+ const buf = chunk;
151
+ size += buf.length;
152
+ if (size > MAX_BYTES) {
153
+ return { ok: false, status: 413, code: -32600, message: 'Request body too large' };
154
+ }
155
+ chunks.push(buf);
156
+ }
157
+ try {
158
+ return { ok: true, body: JSON.parse(Buffer.concat(chunks).toString('utf8')) };
159
+ }
160
+ catch {
161
+ return { ok: false, status: 400, code: -32700, message: 'Parse error' };
162
+ }
163
+ }
164
+ async function handleSessionRequest(req, res) {
165
+ const sessionHeader = req.headers['mcp-session-id'];
166
+ const incomingSessionId = Array.isArray(sessionHeader) ? sessionHeader[0] : sessionHeader;
167
+ if (incomingSessionId) {
168
+ const known = transports.get(incomingSessionId);
169
+ if (!known) {
170
+ // 404 is the spec's signal for "that session is gone" and the status
171
+ // core-py returns. A client that lost its session to a restart reads
172
+ // it as "open a new one"; a 400 would read as "your request is
173
+ // malformed" and it would retry the dead ID.
174
+ jsonRpcError(res, 404, -32600, 'Session not found');
175
+ return;
176
+ }
177
+ await known.handleRequest(req, res);
178
+ return;
179
+ }
180
+ // No session ID. GET (opens the server-to-client SSE stream) and DELETE
181
+ // (closes a session) both presuppose one, so only a POST can get further.
182
+ if (req.method !== 'POST') {
183
+ jsonRpcError(res, 400, -32600, 'Bad Request: Mcp-Session-Id header is required');
184
+ return;
185
+ }
186
+ const parsed = await readBootstrapBody(req);
187
+ if (!parsed.ok) {
188
+ jsonRpcError(res, parsed.status, parsed.code, parsed.message);
189
+ return;
190
+ }
191
+ if (!isInitializeRequest(parsed.body)) {
192
+ jsonRpcError(res, 400, -32600, 'Bad Request: no valid session ID provided');
193
+ return;
194
+ }
195
+ const server = serverFactory();
196
+ let transport;
197
+ transport = new StreamableHTTPServerTransport({
198
+ sessionIdGenerator: () => randomUUID(),
199
+ onsessioninitialized: (sessionId) => {
200
+ transports.set(sessionId, transport);
201
+ servers.set(sessionId, server);
202
+ },
203
+ onsessionclosed: (sessionId) => {
204
+ transports.delete(sessionId);
205
+ servers.delete(sessionId);
206
+ server.close().catch(() => {
207
+ /* best-effort cleanup */
208
+ });
209
+ }
210
+ });
211
+ await server.connect(transport);
212
+ await transport.handleRequest(req, res, parsed.body);
213
+ }
214
+ // Derive the RFC 9728 protected-resource-metadata URL for the Bearer
215
+ // challenge. Mirrors ``local-oauth-app.ts``'s ``getBaseUrl`` PUBLIC_URL-first
216
+ // convention: the deployed servers (oci-vm-prod behind CF Tunnel -> Caddy)
217
+ // set ``PUBLIC_URL`` so the advertised metadata URL is the public HTTPS host
218
+ // rather than the internal HTTP socket address. When unset, fall back to the
219
+ // request Host + ``X-Forwarded-Proto`` (or the socket ``encrypted`` flag).
220
+ function resourceMetadataUrl(req) {
221
+ const envPublicUrl = process.env.PUBLIC_URL;
222
+ let base;
223
+ if (envPublicUrl !== undefined && envPublicUrl.length > 0) {
224
+ base = envPublicUrl.replace(/\/+$/, '');
225
+ }
226
+ else {
227
+ const reqHost = req.headers.host ?? 'localhost';
228
+ const encrypted = req.socket.encrypted === true;
229
+ const forwardedProto = req.headers['x-forwarded-proto'];
230
+ let protocol = encrypted ? 'https' : 'http';
231
+ if (typeof forwardedProto === 'string' && forwardedProto.length > 0) {
232
+ const commaIdx = forwardedProto.indexOf(',');
233
+ protocol = (commaIdx !== -1 ? forwardedProto.substring(0, commaIdx) : forwardedProto).trim();
234
+ }
235
+ base = `${protocol}://${reqHost}`;
236
+ }
237
+ return `${base}/.well-known/oauth-protected-resource`;
238
+ }
239
+ async function mcpHandler(req, res) {
240
+ // Bearer auth bypass for deployments behind an external auth boundary.
241
+ // Caller (reverse proxy, API gateway) is trusted to enforce auth upstream.
242
+ if (jwtIssuer && options.authDisabled) {
243
+ const anonymousClaims = { sub: 'anonymous', anonymous: true };
244
+ if (options.authScope) {
245
+ await options.authScope(anonymousClaims, async () => {
246
+ await handleSessionRequest(req, res);
247
+ });
248
+ return;
249
+ }
250
+ await handleSessionRequest(req, res);
251
+ return;
252
+ }
253
+ // Bearer auth if configured.
254
+ if (jwtIssuer) {
255
+ const authHeader = req.headers.authorization;
256
+ const token = extractBearerToken(authHeader);
257
+ if (!token) {
258
+ res.writeHead(401, {
259
+ 'WWW-Authenticate': `Bearer resource_metadata="${resourceMetadataUrl(req)}"`
260
+ });
261
+ res.end();
262
+ return;
263
+ }
264
+ let claims;
265
+ try {
266
+ claims = await jwtIssuer.verifyAccessToken(token);
267
+ }
268
+ catch {
269
+ res.writeHead(401, {
270
+ 'WWW-Authenticate': `Bearer resource_metadata="${resourceMetadataUrl(req)}", error="invalid_token"`
271
+ });
272
+ res.end();
273
+ return;
274
+ }
275
+ if (options.authScope) {
276
+ await options.authScope(claims, async () => {
277
+ await handleSessionRequest(req, res);
278
+ });
279
+ return;
280
+ }
281
+ }
282
+ // Delegate to per-session MCP transport.
283
+ await handleSessionRequest(req, res);
284
+ }
285
+ async function dispatch(req, res, pathname) {
286
+ // Route /mcp to MCP transport (with optional Bearer auth).
287
+ if (pathname === '/mcp') {
288
+ await mcpHandler(req, res);
289
+ return;
290
+ }
291
+ // Consumer-registered routes. After the built-ins (which win) and before
292
+ // the OAuth app, whose handler is a catch-all for the remaining paths.
293
+ if (options.extraRoutes) {
294
+ for (const route of options.extraRoutes) {
295
+ if (route.path === pathname && route.method === req.method) {
296
+ await route.handler(req, res);
297
+ return;
298
+ }
299
+ }
300
+ }
301
+ // Route everything else to OAuth app if present.
302
+ if (oauthApp) {
303
+ await oauthApp.handler(req, res);
304
+ return;
305
+ }
306
+ // No OAuth app and no match -- 404.
307
+ jsonResponse(res, 404, { error: 'not_found' });
308
+ }
309
+ const handler = async (req, res) => {
310
+ const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
311
+ const pathname = url.pathname;
312
+ // Liveness probe. Always available, never behind token auth.
313
+ if (pathname === '/health') {
314
+ jsonResponse(res, 200, { status: 'ok', server: options.serverName });
315
+ return;
316
+ }
317
+ // Token-table auth runs before any routing and binds the caller's
318
+ // AuthContext for the rest of the request (read via currentUser()).
319
+ if (tokenAuth !== null) {
320
+ await guardRequest(tokenAuth, req, res, () => dispatch(req, res, pathname));
321
+ return;
322
+ }
323
+ await dispatch(req, res, pathname);
324
+ };
325
+ const httpServer = createServer((req, res) => {
326
+ handler(req, res).catch(() => {
327
+ if (!res.headersSent) {
328
+ res.writeHead(500, { 'Content-Type': 'application/json' });
329
+ res.end(JSON.stringify({ error: 'internal_error' }));
330
+ }
331
+ });
332
+ });
333
+ await new Promise((resolve, reject) => {
334
+ httpServer.once('error', reject);
335
+ httpServer.listen(wantedPort, host, () => {
336
+ httpServer.removeListener('error', reject);
337
+ resolve();
338
+ });
339
+ });
340
+ const addr = httpServer.address();
341
+ const actualPort = addr.port;
342
+ // Sweep stale locks for our server name BEFORE writing our own. Without
343
+ // this, abnormal-exit residue (Windows OOM, taskkill, signal) can pile up
344
+ // dozens of `<server>-<port>.lock` files — see 2026-04-28 wet-mcp 11-stale-lock incident.
345
+ const swept = sweepStaleLocks(options.serverName);
346
+ if (swept > 0) {
347
+ console.error(`[runHttpServer] cleaned ${swept} stale lock(s) for ${options.serverName}`);
348
+ }
349
+ const proxyToken = jwtIssuer ? await jwtIssuer.issueAccessToken('proxy', 31536000) : '';
350
+ const lockDirPath = path.join(os.homedir(), '.config', 'mcp', 'locks');
351
+ fs.mkdirSync(lockDirPath, { recursive: true, mode: 0o700 });
352
+ if (process.platform !== 'win32') {
353
+ fs.chmodSync(lockDirPath, 0o700);
354
+ }
355
+ const lockFile = writeLockFile(options.serverName, actualPort, proxyToken, lockDirPath);
356
+ // Refresh the lock timestamp hourly so the 24h TTL sweep does not kill
357
+ // long-running daemons. Cancelled in `close()`.
358
+ const lockRefreshInterval = setInterval(() => refreshLockTimestamp(lockFile), 3600 * 1000);
359
+ if (typeof lockRefreshInterval.unref === 'function') {
360
+ lockRefreshInterval.unref();
361
+ }
362
+ // Auto-open the credential form in the user's browser when no creds exist
363
+ // yet. We open the root URL ("/") which auto-bootstraps PKCE and redirects
364
+ // to /authorize with valid params; opening /authorize directly returns
365
+ // invalid_request because it requires client_id/redirect_uri/state/
366
+ // code_challenge. See `local-oauth-app.ts` root handler docstring.
367
+ // Best-effort: any failure surfaces via tryOpenBrowser's ASCII fallback banner.
368
+ // ``openBrowser: false`` opts out entirely -- see the option's docstring for
369
+ // why a consumer that already published the URL must not get a second tab.
370
+ if (oauthApp && options.openBrowser !== false) {
371
+ try {
372
+ const existingConfig = await readStoredConfig(options.serverName);
373
+ // Use schema completeness instead of "config === null" so peer-share
374
+ // paths writing partial entries (e.g. wet inheriting CRG cloud keys)
375
+ // do not suppress the relay form when required fields are missing.
376
+ const configComplete = options.relaySchema
377
+ ? isSchemaComplete(existingConfig, options.relaySchema)
378
+ : existingConfig !== null;
379
+ if (!configComplete && process.env.NODE_ENV !== 'test') {
380
+ const setupUrl = `http://${host}:${actualPort}/`;
381
+ await tryOpenBrowser(setupUrl);
382
+ }
383
+ }
384
+ catch {
385
+ /* best-effort: never crash startup on browser-open failure */
386
+ }
387
+ }
388
+ // Invoke setup hook. Supports legacy 1-arg (success-only) and new 2-arg
389
+ // (success + failure) signatures via Function.prototype.length so upstream
390
+ // errors (invalid_grant / expired_token / access_denied) propagate to the
391
+ // browser form instead of leaving the spinner waiting forever.
392
+ if (options.setupCompleteHook && oauthApp) {
393
+ const markSetupFailed = 'markSetupFailed' in oauthApp && typeof oauthApp.markSetupFailed === 'function'
394
+ ? oauthApp.markSetupFailed
395
+ : undefined;
396
+ if (options.setupCompleteHook.length >= 2 && markSetupFailed !== undefined) {
397
+ ;
398
+ options.setupCompleteHook(oauthApp.markSetupComplete, markSetupFailed);
399
+ }
400
+ else {
401
+ ;
402
+ options.setupCompleteHook(oauthApp.markSetupComplete);
403
+ }
404
+ }
405
+ return {
406
+ port: actualPort,
407
+ host,
408
+ close: async () => {
409
+ if (oauthApp && 'shutdown' in oauthApp) {
410
+ await oauthApp.shutdown();
411
+ }
412
+ // Drain per-session transports + servers so their open SSE streams
413
+ // and tool-call timers don't keep the event loop alive after the
414
+ // HTTP server closes.
415
+ for (const transport of transports.values()) {
416
+ try {
417
+ await transport.close();
418
+ }
419
+ catch {
420
+ /* best-effort cleanup */
421
+ }
422
+ }
423
+ for (const server of servers.values()) {
424
+ try {
425
+ await server.close();
426
+ }
427
+ catch {
428
+ /* best-effort cleanup */
429
+ }
430
+ }
431
+ transports.clear();
432
+ servers.clear();
433
+ if (lockRefreshInterval !== null) {
434
+ clearInterval(lockRefreshInterval);
435
+ }
436
+ return new Promise((resolve, reject) => {
437
+ httpServer.close(async (err) => {
438
+ if (lockFile) {
439
+ try {
440
+ await fs.promises.unlink(lockFile);
441
+ }
442
+ catch {
443
+ /* best-effort cleanup */
444
+ }
445
+ }
446
+ if (err) {
447
+ reject(err);
448
+ }
449
+ else {
450
+ resolve();
451
+ }
452
+ });
453
+ });
454
+ }
455
+ };
456
+ }
457
+ //# sourceMappingURL=local-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-server.js","sourceRoot":"","sources":["../../src/transport/local-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * OAuth 2.1 Bearer token validation middleware for Streamable HTTP.
3
+ *
4
+ * Validates `Authorization: Bearer <token>` headers on incoming requests
5
+ * against a `JWTIssuer`. On missing or invalid token it writes a 401
6
+ * with `WWW-Authenticate: Bearer resource_metadata="..."` per RFC 6750 +
7
+ * RFC 9728 (OAuth 2.1 protected resource metadata discovery).
8
+ *
9
+ * Success attaches the validated claims to `AuthenticatedRequest.user` so
10
+ * downstream handlers can read the subject without re-validating.
11
+ */
12
+ import type { IncomingMessage, ServerResponse } from 'node:http';
13
+ import type { JWTIssuer } from '../oauth/jwt-issuer.js';
14
+ export interface OAuthMiddlewareOptions {
15
+ jwtIssuer: JWTIssuer;
16
+ resourceMetadataUrl: string;
17
+ /**
18
+ * When `true`, skip Bearer token validation and attach an anonymous user
19
+ * claim. Intended for deployments behind an external auth boundary
20
+ * (reverse proxy, API gateway like agentgateway/Zitadel) where the caller
21
+ * has already verified identity. Logs a warning on construction; the
22
+ * deployer is responsible for ensuring upstream auth is in place.
23
+ */
24
+ authDisabled?: boolean;
25
+ }
26
+ export interface AuthenticatedRequest extends IncomingMessage {
27
+ user?: Record<string, unknown>;
28
+ }
29
+ export declare function extractBearerToken(authHeader: string | undefined): string | null;
30
+ export declare class OAuthMiddleware {
31
+ private readonly _issuer;
32
+ private readonly _resourceMetadataUrl;
33
+ private readonly _authDisabled;
34
+ constructor(options: OAuthMiddlewareOptions);
35
+ /**
36
+ * Validate the request. Returns `true` if the request should proceed
37
+ * (and attaches `req.user` on success). Returns `false` if the
38
+ * middleware has already written a 401 response.
39
+ *
40
+ * When constructed with `authDisabled: true`, this short-circuits to
41
+ * `true` and attaches `{ sub: 'anonymous', anonymous: true }` to `req.user`.
42
+ */
43
+ validate(req: AuthenticatedRequest, res: ServerResponse): Promise<boolean>;
44
+ }
45
+ //# sourceMappingURL=oauth-middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-middleware.d.ts","sourceRoot":"","sources":["../../src/transport/oauth-middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAEhE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAEvD,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,SAAS,CAAA;IACpB,mBAAmB,EAAE,MAAM,CAAA;IAC3B;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,oBAAqB,SAAQ,eAAe;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B;AAiBD,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAsBhF;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAW;IACnC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAQ;IAC7C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,YAAY,OAAO,EAAE,sBAAsB,EAU1C;IAED;;;;;;;OAOG;IACG,QAAQ,CAAC,GAAG,EAAE,oBAAoB,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAkB/E;CACF"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * OAuth 2.1 Bearer token validation middleware for Streamable HTTP.
3
+ *
4
+ * Validates `Authorization: Bearer <token>` headers on incoming requests
5
+ * against a `JWTIssuer`. On missing or invalid token it writes a 401
6
+ * with `WWW-Authenticate: Bearer resource_metadata="..."` per RFC 6750 +
7
+ * RFC 9728 (OAuth 2.1 protected resource metadata discovery).
8
+ *
9
+ * Success attaches the validated claims to `AuthenticatedRequest.user` so
10
+ * downstream handlers can read the subject without re-validating.
11
+ */
12
+ function writeChallenge(res, resourceMetadataUrl, error) {
13
+ const params = [`resource_metadata="${resourceMetadataUrl}"`];
14
+ if (error)
15
+ params.push(`error="${error}"`);
16
+ res.writeHead(401, {
17
+ 'WWW-Authenticate': `Bearer ${params.join(', ')}`,
18
+ 'Content-Type': 'application/json'
19
+ });
20
+ res.end(JSON.stringify({
21
+ error: error ?? 'unauthorized',
22
+ error_description: error ? 'The access token is missing or invalid' : 'Authentication required'
23
+ }));
24
+ }
25
+ export function extractBearerToken(authHeader) {
26
+ if (!authHeader)
27
+ return null;
28
+ // Fast path for strictly "Bearer " or "bearer " (exact case & single space)
29
+ if (authHeader.startsWith('Bearer ') || authHeader.startsWith('bearer ')) {
30
+ const token = authHeader.substring(7).trim();
31
+ return token.length > 0 ? token : null;
32
+ }
33
+ // Fallback for tricky cases (mixed case, leading whitespace, etc.)
34
+ const trimmed = authHeader.trim();
35
+ if (trimmed.length <= 6)
36
+ return null;
37
+ const prefix = trimmed.substring(0, 6);
38
+ if (prefix.toLowerCase() !== 'bearer')
39
+ return null;
40
+ const char6 = trimmed.charCodeAt(6);
41
+ // Check if character 6 is whitespace (space=32, tab=9, newline=10, return=13)
42
+ if (char6 !== 32 && char6 !== 9 && char6 !== 10 && char6 !== 13)
43
+ return null;
44
+ const token = trimmed.substring(7).trim();
45
+ return token.length > 0 ? token : null;
46
+ }
47
+ export class OAuthMiddleware {
48
+ constructor(options) {
49
+ this._issuer = options.jwtIssuer;
50
+ this._resourceMetadataUrl = options.resourceMetadataUrl;
51
+ this._authDisabled = options.authDisabled === true;
52
+ if (this._authDisabled) {
53
+ console.warn('[hull OAuthMiddleware] auth_disabled=true — Bearer token validation skipped. ' +
54
+ 'Caller must enforce authentication at the network boundary (e.g. reverse proxy, API gateway).');
55
+ }
56
+ }
57
+ /**
58
+ * Validate the request. Returns `true` if the request should proceed
59
+ * (and attaches `req.user` on success). Returns `false` if the
60
+ * middleware has already written a 401 response.
61
+ *
62
+ * When constructed with `authDisabled: true`, this short-circuits to
63
+ * `true` and attaches `{ sub: 'anonymous', anonymous: true }` to `req.user`.
64
+ */
65
+ async validate(req, res) {
66
+ if (this._authDisabled) {
67
+ req.user = { sub: 'anonymous', anonymous: true };
68
+ return true;
69
+ }
70
+ const token = extractBearerToken(req.headers.authorization);
71
+ if (!token) {
72
+ writeChallenge(res, this._resourceMetadataUrl);
73
+ return false;
74
+ }
75
+ try {
76
+ const claims = await this._issuer.verifyAccessToken(token);
77
+ req.user = claims;
78
+ return true;
79
+ }
80
+ catch {
81
+ writeChallenge(res, this._resourceMetadataUrl, 'invalid_token');
82
+ return false;
83
+ }
84
+ }
85
+ }
86
+ //# sourceMappingURL=oauth-middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-middleware.js","sourceRoot":"","sources":["../../src/transport/oauth-middleware.ts"],"names":[],"mappings":"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"}