devframe 0.6.0-beta.2 → 0.6.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 (60) hide show
  1. package/dist/adapters/build.d.mts +1 -1
  2. package/dist/adapters/build.mjs +2 -2
  3. package/dist/adapters/cli.d.mts +1 -1
  4. package/dist/adapters/cli.mjs +9 -6
  5. package/dist/adapters/dev.d.mts +9 -2
  6. package/dist/adapters/dev.mjs +1 -1
  7. package/dist/adapters/embedded.d.mts +1 -1
  8. package/dist/adapters/mcp.d.mts +1 -1
  9. package/dist/adapters/mcp.mjs +1 -337
  10. package/dist/build-server-0jWgMrz-.mjs +338 -0
  11. package/dist/client/index.d.mts +2 -2
  12. package/dist/client/index.mjs +56 -22
  13. package/dist/constants.d.mts +31 -1
  14. package/dist/constants.mjs +33 -1
  15. package/dist/{context-3x_bgBgZ.mjs → context-CKJheEtf.mjs} +2 -2
  16. package/dist/{context-BEVByy_Z.d.mts → context-UX9qUDK7.d.mts} +1 -1
  17. package/dist/{dev-BaX8fLH2.mjs → dev-zC5542OV.mjs} +49 -6
  18. package/dist/{devframe-D-gr9sBx.d.mts → devframe-BtVHhmwM.d.mts} +76 -4
  19. package/dist/{dump-DoXkj4ku.mjs → dump-D09PZb7T.mjs} +14 -4
  20. package/dist/{hash-DFc5WwhO.mjs → hash-PAfnyu2k.mjs} +1 -1
  21. package/dist/helpers/vite.d.mts +1 -1
  22. package/dist/helpers/vite.mjs +2 -2
  23. package/dist/{host-h3-C5SZlk03.mjs → host-h3-Dsf-Tgwx.mjs} +37 -11
  24. package/dist/http-COX960WK.mjs +114 -0
  25. package/dist/index-ByaMT1Xp.d.mts +107 -0
  26. package/dist/{index-CxaPKDXX.d.mts → index-Dy6GFDRf.d.mts} +1 -0
  27. package/dist/index.d.mts +2 -2
  28. package/dist/{launch-editor-DPfltGA4.mjs → launch-editor-DIzaS5EG.mjs} +58 -43
  29. package/dist/node/auth.d.mts +2 -64
  30. package/dist/node/auth.mjs +2 -100
  31. package/dist/node/hub-internals.d.mts +2 -2
  32. package/dist/node/hub-internals.mjs +1 -1
  33. package/dist/node/index.d.mts +3 -3
  34. package/dist/node/index.mjs +3 -3
  35. package/dist/recipes/interactive-auth.d.mts +53 -0
  36. package/dist/recipes/interactive-auth.mjs +132 -0
  37. package/dist/recipes/open-helpers.mjs +2 -2
  38. package/dist/{revoke-ENfxWkFK.mjs → revoke-BtQDKTp7.mjs} +1 -1
  39. package/dist/rpc/dump.mjs +1 -1
  40. package/dist/rpc/index.d.mts +2 -2
  41. package/dist/rpc/index.mjs +4 -1
  42. package/dist/rpc/transports/ws-client.mjs +21 -8
  43. package/dist/{server-Dl6TU5LE.mjs → server-CAsvfJNK.mjs} +51 -15
  44. package/dist/{server-rX781kz-.d.mts → server-Cfr1NrGQ.d.mts} +52 -10
  45. package/dist/{shared-state-D4PPieA0.mjs → shared-state-QtMas0NG.mjs} +34 -4
  46. package/dist/state-WX3HT5a3.mjs +101 -0
  47. package/dist/{storage-l2ezeend.mjs → storage-yrUZaiib.mjs} +55 -6
  48. package/dist/types/index.d.mts +2 -2
  49. package/dist/utils/crypto-token.mjs +1 -1
  50. package/dist/utils/events.d.mts +1 -1
  51. package/dist/utils/hash.mjs +1 -1
  52. package/dist/utils/launch-editor.mjs +1 -1
  53. package/dist/utils/open.mjs +1 -1
  54. package/dist/utils/serve-static.mjs +2 -1
  55. package/dist/utils/shared-state.d.mts +1 -1
  56. package/dist/utils/shared-state.mjs +1 -1
  57. package/dist/utils/streaming-channel.d.mts +1 -1
  58. package/dist/utils/when.d.mts +1 -1
  59. package/package.json +10 -9
  60. package/dist/{open-Dusa2Zzd.mjs → open-Deb5xmIT.mjs} +1 -1
@@ -1,12 +1,13 @@
1
1
  import { DEVFRAME_CONNECTION_META_FILENAME } from "./constants.mjs";
2
- import { n as createHostContext, t as createH3DevframeHost } from "./host-h3-C5SZlk03.mjs";
3
- import { t as startHttpAndWs } from "./server-Dl6TU5LE.mjs";
2
+ import { a as diagnostics } from "./storage-yrUZaiib.mjs";
3
+ import { n as createHostContext, t as createH3DevframeHost } from "./host-h3-Dsf-Tgwx.mjs";
4
+ import { t as startHttpAndWs } from "./server-CAsvfJNK.mjs";
4
5
  import { n as resolveBasePath, t as normalizeBasePath } from "./_shared-bWRzeSa0.mjs";
5
- import { t as open } from "./open-Dusa2Zzd.mjs";
6
+ import { t as open } from "./open-Deb5xmIT.mjs";
6
7
  import { mountStaticHandler } from "./utils/serve-static.mjs";
7
8
  import { resolve } from "pathe";
8
- import { networkInterfaces } from "node:os";
9
9
  import process$1 from "node:process";
10
+ import { networkInterfaces } from "node:os";
10
11
  import { H3 } from "h3";
11
12
  import { createServer } from "node:net";
12
13
  import { joinURL, withBase as withBase$1, withLeadingSlash, withoutLeadingSlash } from "ufo";
@@ -284,14 +285,40 @@ async function createDevServer(def, options = {}) {
284
285
  });
285
286
  const setupInfo = { flags };
286
287
  await def.setup(ctx, setupInfo);
288
+ const mcpConfig = resolveMcpConfig(options.mcp ?? def.cli?.mcp);
289
+ let mcpDispose;
290
+ let mcpMeta;
291
+ if (mcpConfig) {
292
+ const mcpRoute = withoutLeadingSlash(mcpConfig.path ?? "__mcp");
293
+ const mcpPath = joinURL(basePath, mcpRoute);
294
+ let mountMcpHttp;
295
+ try {
296
+ ({mountMcpHttp} = await import("./http-COX960WK.mjs"));
297
+ } catch (error) {
298
+ const reason = error instanceof Error ? error.message : String(error);
299
+ throw diagnostics.DF0017({
300
+ transport: "http",
301
+ reason,
302
+ cause: error
303
+ });
304
+ }
305
+ mcpDispose = mountMcpHttp(app, ctx, mcpPath, {
306
+ serverName: `${def.id} (devframe)`,
307
+ serverVersion: def.version ?? "0.0.0",
308
+ exposeSharedState: true,
309
+ allowedOrigins: mcpConfig.allowedOrigins
310
+ }).dispose;
311
+ mcpMeta = { path: mcpRoute };
312
+ }
287
313
  const { bindPath, wsPort, meta } = resolveWsConnection(def, options, basePath);
288
314
  const connectionMetaPath = joinURL(basePath, DEVFRAME_CONNECTION_META_FILENAME);
289
315
  app.use(connectionMetaPath, () => ({
290
316
  backend: "websocket",
291
- websocket: meta
317
+ websocket: meta,
318
+ ...mcpMeta ? { mcp: mcpMeta } : {}
292
319
  }));
293
320
  if (distDir) mountStaticHandler(app, basePath, resolve(distDir));
294
- return startHttpAndWs({
321
+ const started = await startHttpAndWs({
295
322
  context: ctx,
296
323
  host,
297
324
  port,
@@ -304,6 +331,22 @@ async function createDevServer(def, options = {}) {
304
331
  await maybeOpenBrowser(def, flags, `${info.origin}${basePath}`, options.openBrowser);
305
332
  }
306
333
  });
334
+ if (mcpDispose) {
335
+ const closeServer = started.close;
336
+ started.close = async () => {
337
+ await mcpDispose();
338
+ await closeServer();
339
+ };
340
+ }
341
+ return started;
342
+ }
343
+ /**
344
+ * Normalize the `cli.mcp` / `mcp` option (`boolean | McpRouteOptions`) into
345
+ * concrete options, or `undefined` when the MCP route is disabled.
346
+ */
347
+ function resolveMcpConfig(mcp) {
348
+ if (!mcp) return void 0;
349
+ return mcp === true ? {} : mcp;
307
350
  }
308
351
  /**
309
352
  * Resolve the three WS connection scenarios from the definition / call-site
@@ -1,5 +1,5 @@
1
1
  import { _ as RpcFunctionDefinition } from "./types-DZEx4ffs.mjs";
2
- import { S as RpcFunctionsCollectorBase } from "./index-CxaPKDXX.mjs";
2
+ import { S as RpcFunctionsCollectorBase } from "./index-Dy6GFDRf.mjs";
3
3
  import { t as DevframeNodeRpcSessionMeta } from "./ws-server-Bc2wBHl-.mjs";
4
4
  import { CAC } from "cac";
5
5
  import { GenericSchema, InferOutput } from "valibot";
@@ -471,9 +471,13 @@ interface DevframeRpcServerFunctions {
471
471
  * by the host adapter (e.g. Vite DevTools); the standalone server registers
472
472
  * an auto-trust noop when `auth: false`.
473
473
  *
474
+ * Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
475
+ * reachable before the connection is trusted — this is the *only* rule the
476
+ * pre-trust gate applies, no per-method allowlist.
477
+ *
474
478
  * @internal
475
479
  */
476
- 'devframe:anonymous:auth': (params: {
480
+ 'anonymous:devframe:auth': (params: {
477
481
  authToken: string;
478
482
  ua: string;
479
483
  origin: string;
@@ -485,15 +489,27 @@ interface DevframeRpcServerFunctions {
485
489
  * node-issued bearer token, returning the token on success or `null`. The
486
490
  * handler is provided by the host adapter on top of `exchangeTempAuthCode`.
487
491
  *
492
+ * Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
493
+ * reachable before the connection is trusted.
494
+ *
488
495
  * @internal
489
496
  */
490
- 'devframe:auth:exchange': (params: {
497
+ 'anonymous:devframe:auth:exchange': (params: {
491
498
  code: string;
492
499
  ua: string;
493
500
  origin: string;
494
501
  }) => Promise<{
495
502
  authToken: string | null;
496
503
  }>;
504
+ /**
505
+ * Self-revoke: the caller asks the server to revoke its own bearer token
506
+ * (if any) and drop to untrusted. Requires an already-trusted caller, so
507
+ * unlike the two handshake methods above it does **not** carry the
508
+ * `anonymous:` prefix. Registered by `recipes/interactive-auth`.
509
+ *
510
+ * @internal
511
+ */
512
+ 'devframe:auth:revoke': () => Promise<void>;
497
513
  /**
498
514
  * Subscribe a client to a shared-state key. Wired by
499
515
  * `RpcSharedStateHost`; do not register manually.
@@ -1204,6 +1220,16 @@ interface ConnectionMeta {
1204
1220
  * URL with its protocol swapped, or a path resolved same-origin.
1205
1221
  */
1206
1222
  websocket?: number | string | ConnectionMetaWebsocket;
1223
+ /**
1224
+ * Present when the dev server exposes a route-based MCP endpoint
1225
+ * (`cli.mcp`). Advertises the MCP Streamable-HTTP route so in-browser
1226
+ * tooling (e.g. an MCP inspector) can discover it without guessing the
1227
+ * path. `path` is relative to `__connection.json`'s location, like the
1228
+ * WebSocket `path`.
1229
+ */
1230
+ mcp?: {
1231
+ path: string;
1232
+ };
1207
1233
  /**
1208
1234
  * Names of RPC functions that have declared `jsonSerializable: true`.
1209
1235
  * Used by the WS / static client to dispatch the per-call wire
@@ -1269,6 +1295,36 @@ interface DevframeWsOptions {
1269
1295
  */
1270
1296
  url?: string;
1271
1297
  }
1298
+ /**
1299
+ * Configuration for the route-based MCP server mounted alongside the dev
1300
+ * server (opt-in via {@link DevframeCliOptions.mcp}). The endpoint speaks
1301
+ * the MCP Streamable-HTTP transport over the same origin as the SPA,
1302
+ * exposing the definition's `ctx.agent` tools + shared-state resources to
1303
+ * external MCP clients connected to the *running* server.
1304
+ *
1305
+ * @experimental The agent-native surface is experimental and may change
1306
+ * without a major version bump until it stabilizes.
1307
+ */
1308
+ interface McpRouteOptions {
1309
+ /**
1310
+ * Route segment the MCP endpoint binds to, relative to the SPA base.
1311
+ * Default: `__mcp` (i.e. `/__mcp` standalone, `/__<id>/__mcp` hosted).
1312
+ */
1313
+ path?: string;
1314
+ /**
1315
+ * Extra `Origin` header values to accept beyond the loopback default
1316
+ * (`localhost`/`127.0.0.1`/`::1` and any `Origin`-less native client).
1317
+ * Add your LAN/tunnel origin here when reaching the endpoint from another
1318
+ * host, mirroring the WS transport's origin gate. Pass `false` to disable
1319
+ * origin checking entirely (not recommended). Default: loopback-only.
1320
+ *
1321
+ * This is the endpoint's DNS-rebinding protection — the shared
1322
+ * `isAllowedOrigin` gate the WS upgrade already uses, applied as external
1323
+ * middleware (the approach the MCP SDK now recommends over its own
1324
+ * deprecated `allowedHosts`/`allowedOrigins` transport flags).
1325
+ */
1326
+ allowedOrigins?: readonly string[] | false;
1327
+ }
1272
1328
  interface DevframeCliOptions {
1273
1329
  /** Binary name; default: the devframe's `id`. */
1274
1330
  command?: string;
@@ -1295,6 +1351,22 @@ interface DevframeCliOptions {
1295
1351
  * `devtools.clientAuth` today.
1296
1352
  */
1297
1353
  auth?: boolean;
1354
+ /**
1355
+ * Expose a route-based MCP server alongside the dev server, speaking the
1356
+ * MCP Streamable-HTTP transport at `/__mcp` (relative to the base path).
1357
+ * It surfaces the same `ctx.agent` tools + shared-state resources as the
1358
+ * stdio `mcp` command, but against the live, running server.
1359
+ *
1360
+ * - `false` / omitted (default) — no MCP route is mounted.
1361
+ * - `true` — mount at the default `__mcp` route with the loopback-only
1362
+ * origin gate.
1363
+ * - {@link McpRouteOptions} — customise the route path / allowed origins.
1364
+ *
1365
+ * The `--mcp` / `--no-mcp` CLI flags override this per run.
1366
+ *
1367
+ * @experimental
1368
+ */
1369
+ mcp?: boolean | McpRouteOptions;
1298
1370
  /** Author's SPA dist directory (served as the devframe's UI). */
1299
1371
  distDir?: string;
1300
1372
  /**
@@ -1406,4 +1478,4 @@ interface DevframeDefinition {
1406
1478
  }
1407
1479
  declare function defineDevframe(d: DevframeDefinition): DevframeDefinition;
1408
1480
  //#endregion
1409
- export { ScopedServerFunctions as $, createStreamReader as A, SharedStatePatch as B, BufferedChunk as C, CliFlagsSchema as Ct, StreamReader as D, StreamErrorPayload as E, parseCliFlags as Et, ImmutableObject as F, DevframeScopedNodeContext as G, EntriesToObject as H, ImmutableSet as I, DevframeSettings as J, DevframeScopedNodeRpc as K, SharedState as L, Immutable as M, ImmutableArray as N, StreamSink as O, ImmutableMap as P, ScopedClientFunctions as Q, SharedStateEvents as R, RpcStreamingHost as S, EventsMap as St, CreateStreamSinkOptions as T, defineCliFlags as Tt, PartialWithoutId as U, createSharedState as V, Thenable as W, DevframeSettingsStore as X, DevframeSettingsRegistry as Y, ScopedBroadcastOptions as Z, RpcFunctionsHost as _, AgentToolInput as _t, DevframeDuplicationStrategy as a, DevframeRpcSharedStates as at, RpcStreamingChannel as b, EventEmitter as bt, DevframeSpaOptions as c, DevframeDiagnosticsDefinition as ct, ConnectionMeta as d, AgentHandle as dt, ScopedSharedStates as et, ConnectionMetaWebsocket as f, AgentManifest as ft, RpcBroadcastOptions as g, AgentTool as gt, DevframeNodeRpcSession as h, AgentResourceInput as ht, DevframeDeploymentKind as i, DevframeRpcServerFunctions as it, createStreamSink as j, StreamSinkEvents as k, DevframeWsOptions as l, DevframeDiagnosticsHost as lt, DevframeNodeContext as m, AgentResourceContent as mt, DevframeCliOptions as n, DevframeViewHost as nt, DevframeRuntime as o, DevframeHost as ot, DevframeCapabilities as p, AgentResource as pt, DevframeScopedStreamingHost as q, DevframeDefinition as r, DevframeRpcClientFunctions as rt, DevframeSetupInfo as s, DevframeDefineDiagnosticsOptions as st, DevframeBrowserContext as t, SettingsForNamespace as tt, defineDevframe as u, DevframeDiagnosticsLogger as ut, RpcSharedStateGetOptions as v, DevframeAgentHost as vt, CreateStreamReaderOptions as w, InferCliFlags as wt, RpcStreamingChannelOptions as x, EventUnsubscribe as xt, RpcSharedStateHost as y, DevframeAgentHostEvents as yt, SharedStateOptions as z };
1481
+ export { ScopedClientFunctions as $, StreamSinkEvents as A, SharedStateOptions as B, RpcStreamingHost as C, EventsMap as Ct, StreamErrorPayload as D, parseCliFlags as Dt, CreateStreamSinkOptions as E, defineCliFlags as Et, ImmutableMap as F, Thenable as G, createSharedState as H, ImmutableObject as I, DevframeScopedStreamingHost as J, DevframeScopedNodeContext as K, ImmutableSet as L, createStreamSink as M, Immutable as N, StreamReader as O, ImmutableArray as P, ScopedBroadcastOptions as Q, SharedState as R, RpcStreamingChannelOptions as S, EventUnsubscribe as St, CreateStreamReaderOptions as T, InferCliFlags as Tt, EntriesToObject as U, SharedStatePatch as V, PartialWithoutId as W, DevframeSettingsRegistry as X, DevframeSettings as Y, DevframeSettingsStore as Z, RpcBroadcastOptions as _, AgentTool as _t, DevframeDuplicationStrategy as a, DevframeRpcServerFunctions as at, RpcSharedStateHost as b, DevframeAgentHostEvents as bt, DevframeSpaOptions as c, DevframeDefineDiagnosticsOptions as ct, defineDevframe as d, DevframeDiagnosticsLogger as dt, ScopedServerFunctions as et, ConnectionMeta as f, AgentHandle as ft, DevframeNodeRpcSession as g, AgentResourceInput as gt, DevframeNodeContext as h, AgentResourceContent as ht, DevframeDeploymentKind as i, DevframeRpcClientFunctions as it, createStreamReader as j, StreamSink as k, DevframeWsOptions as l, DevframeDiagnosticsDefinition as lt, DevframeCapabilities as m, AgentResource as mt, DevframeCliOptions as n, SettingsForNamespace as nt, DevframeRuntime as o, DevframeRpcSharedStates as ot, ConnectionMetaWebsocket as p, AgentManifest as pt, DevframeScopedNodeRpc as q, DevframeDefinition as r, DevframeViewHost as rt, DevframeSetupInfo as s, DevframeHost as st, DevframeBrowserContext as t, ScopedSharedStates as tt, McpRouteOptions as u, DevframeDiagnosticsHost as ut, RpcFunctionsHost as v, AgentToolInput as vt, BufferedChunk as w, CliFlagsSchema as wt, RpcStreamingChannel as x, EventEmitter as xt, RpcSharedStateGetOptions as y, DevframeAgentHost as yt, SharedStateEvents as z };
@@ -5,7 +5,7 @@ import { createHash } from "node:crypto";
5
5
  function serialize(o) {
6
6
  return typeof o == "string" ? `'${o}'` : new c().serialize(o);
7
7
  }
8
- const c = /* @__PURE__ */ function() {
8
+ const c = /*@__PURE__*/ function() {
9
9
  class o {
10
10
  #t = /* @__PURE__ */ new Map();
11
11
  compare(t, r) {
@@ -501,14 +501,24 @@ async function getRpcResolvedSetupResult(definition, context) {
501
501
  if (!definition.setup) return {};
502
502
  if (typeof context === "object" && context !== null) {
503
503
  definition.__cache ??= /* @__PURE__ */ new WeakMap();
504
- let promise = definition.__cache.get(context);
504
+ const cache = definition.__cache;
505
+ let promise = cache.get(context);
505
506
  if (!promise) {
506
507
  promise = Promise.resolve(definition.setup(context));
507
- definition.__cache.set(context, promise);
508
+ promise.catch(() => {
509
+ if (cache.get(context) === promise) cache.delete(context);
510
+ });
511
+ cache.set(context, promise);
508
512
  }
509
513
  return await promise;
510
514
  }
511
- definition.__promise ??= Promise.resolve(definition.setup(context));
515
+ if (!definition.__promise) {
516
+ const promise = Promise.resolve(definition.setup(context));
517
+ promise.catch(() => {
518
+ if (definition.__promise === promise) definition.__promise = void 0;
519
+ });
520
+ definition.__promise = promise;
521
+ }
512
522
  return await definition.__promise;
513
523
  }
514
524
  async function getRpcHandler(definition, context) {
@@ -2,7 +2,7 @@
2
2
  function serialize(o) {
3
3
  return typeof o == "string" ? `'${o}'` : new c().serialize(o);
4
4
  }
5
- const c = /* @__PURE__ */ function() {
5
+ const c = /*@__PURE__*/ function() {
6
6
  class o {
7
7
  #t = /* @__PURE__ */ new Map();
8
8
  compare(t, r) {
@@ -1,4 +1,4 @@
1
- import { r as DevframeDefinition } from "../devframe-D-gr9sBx.mjs";
1
+ import { r as DevframeDefinition } from "../devframe-BtVHhmwM.mjs";
2
2
 
3
3
  //#region src/helpers/vite.d.ts
4
4
  interface ViteDevBridgeOptions {
@@ -1,8 +1,8 @@
1
1
  import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_WS_ROUTE } from "../constants.mjs";
2
- import { a as diagnostics } from "../storage-l2ezeend.mjs";
2
+ import { a as diagnostics } from "../storage-yrUZaiib.mjs";
3
3
  import { n as resolveBasePath, t as normalizeBasePath } from "../_shared-bWRzeSa0.mjs";
4
4
  import { serveStaticNodeMiddleware } from "../utils/serve-static.mjs";
5
- import { n as resolveDevServerPort, t as createDevServer } from "../dev-BaX8fLH2.mjs";
5
+ import { n as resolveDevServerPort, t as createDevServer } from "../dev-zC5542OV.mjs";
6
6
  import { resolve } from "pathe";
7
7
  //#region src/helpers/vite.ts
8
8
  /**
@@ -2,14 +2,14 @@ import { t as devframeReporter } from "./diagnostics-reporter-CsIG85Q5.mjs";
2
2
  import { t as diagnostics } from "./diagnostics-BXwBQmoN.mjs";
3
3
  import { RpcFunctionsCollectorBase } from "./rpc/index.mjs";
4
4
  import { defineRpcFunction } from "./index.mjs";
5
- import { a as diagnostics$1, i as createEventEmitter, n as createSharedState, r as nanoid, t as createStorage } from "./storage-l2ezeend.mjs";
5
+ import { a as diagnostics$1, i as createEventEmitter, n as createSharedState, r as nanoid, t as createStorage } from "./storage-yrUZaiib.mjs";
6
6
  import { defineDiagnostics } from "nostics";
7
7
  import { isatty } from "node:tty";
8
8
  import { formatWithOptions, inspect } from "node:util";
9
9
  import { existsSync } from "node:fs";
10
10
  import { join } from "pathe";
11
- import { homedir } from "node:os";
12
11
  import process$1 from "node:process";
12
+ import { homedir } from "node:os";
13
13
  //#region src/node/host-agent.ts
14
14
  /**
15
15
  * Framework-neutral host aggregating the agent-exposed surface of a
@@ -203,11 +203,18 @@ var DevframeDiagnosticsHost = class {
203
203
  }
204
204
  };
205
205
  //#endregion
206
- //#region ../../node_modules/.pnpm/obug@2.1.1/node_modules/obug/dist/core.js
206
+ //#region ../../node_modules/.pnpm/obug@2.1.3/node_modules/obug/dist/core.js
207
+ /**
208
+ * Coerce `value`.
209
+ */
207
210
  function coerce(value) {
208
211
  if (value instanceof Error) return value.stack || value.message;
209
212
  return value;
210
213
  }
214
+ /**
215
+ * Selects a color for a debug namespace
216
+ * @return An ANSI color code for the given namespace
217
+ */
211
218
  function selectColor(colors, namespace) {
212
219
  let hash = 0;
213
220
  for (let i = 0; i < namespace.length; i++) {
@@ -216,6 +223,10 @@ function selectColor(colors, namespace) {
216
223
  }
217
224
  return colors[Math.abs(hash) % colors.length];
218
225
  }
226
+ /**
227
+ * Checks if the given string matches a namespace template, honoring
228
+ * asterisks as wildcards.
229
+ */
219
230
  function matchesTemplate(search, template) {
220
231
  let searchIndex = 0;
221
232
  let templateIndex = 0;
@@ -270,8 +281,8 @@ function createDebug$1(namespace, options) {
270
281
  options.formatArgs.call(debug, diff, args);
271
282
  debug.log(...args);
272
283
  };
273
- debug.extend = function(namespace$1, delimiter = ":") {
274
- return createDebug$1(this.namespace + delimiter + namespace$1, {
284
+ debug.extend = function(namespace, delimiter = ":") {
285
+ return createDebug$1(this.namespace + delimiter + namespace, {
275
286
  useColors: this.useColors,
276
287
  color: this.color,
277
288
  formatArgs: this.formatArgs,
@@ -302,21 +313,24 @@ function createDebug$1(namespace, options) {
302
313
  }
303
314
  let names = [];
304
315
  let skips = [];
305
- function enable(namespaces$1) {
306
- globalNamespaces = namespaces$1;
316
+ function enable(namespaces) {
317
+ globalNamespaces = namespaces;
307
318
  names = [];
308
319
  skips = [];
309
320
  const split = globalNamespaces.trim().replace(/\s+/g, ",").split(",").filter(Boolean);
310
321
  for (const ns of split) if (ns[0] === "-") skips.push(ns.slice(1));
311
322
  else names.push(ns);
312
323
  }
324
+ /**
325
+ * Returns true if the given mode name is enabled, false otherwise.
326
+ */
313
327
  function enabled(name) {
314
328
  for (const skip of skips) if (matchesTemplate(name, skip)) return false;
315
329
  for (const ns of names) if (matchesTemplate(name, ns)) return true;
316
330
  return false;
317
331
  }
318
332
  //#endregion
319
- //#region ../../node_modules/.pnpm/obug@2.1.1/node_modules/obug/dist/node.js
333
+ //#region ../../node_modules/.pnpm/obug@2.1.3/node_modules/obug/dist/node.js
320
334
  const colors = process.stderr.getColorDepth && process.stderr.getColorDepth() > 2 ? [
321
335
  20,
322
336
  21,
@@ -412,7 +426,10 @@ const inspectOpts = Object.keys(process.env).filter((key) => /^debug_/i.test(key
412
426
  else value = Number(value);
413
427
  obj[prop] = value;
414
428
  return obj;
415
- }, {});
429
+ }, Object.create(null));
430
+ /**
431
+ * Is stdout a TTY? Colored output is enabled when `true`.
432
+ */
416
433
  function useColors() {
417
434
  return "colors" in inspectOpts ? Boolean(inspectOpts.colors) : isatty(process.stderr.fd);
418
435
  }
@@ -420,9 +437,12 @@ function getDate() {
420
437
  if (inspectOpts.hideDate) return "";
421
438
  return `${(/* @__PURE__ */ new Date()).toISOString()} `;
422
439
  }
440
+ /**
441
+ * Adds ANSI color escape codes if enabled.
442
+ */
423
443
  function formatArgs(diff, args) {
424
- const { namespace: name, useColors: useColors$1 } = this;
425
- if (useColors$1) {
444
+ const { namespace: name, useColors } = this;
445
+ if (useColors) {
426
446
  const c = this.color;
427
447
  const colorCode = `\u001B[3${c < 8 ? c : `8;5;${c}`}`;
428
448
  const prefix = ` ${colorCode};1m${name} \u001B[0m`;
@@ -437,10 +457,16 @@ const defaultOptions = {
437
457
  useColors: useColors(),
438
458
  formatArgs,
439
459
  formatters: {
460
+ /**
461
+ * Map %o to `util.inspect()`, all on a single line.
462
+ */
440
463
  o(v) {
441
464
  this.inspectOpts.colors = this.useColors;
442
465
  return inspect(v, this.inspectOpts).split("\n").map((str) => str.trim()).join(" ");
443
466
  },
467
+ /**
468
+ * Map %O to `util.inspect()`, allowing multiple lines if needed.
469
+ */
444
470
  O(v) {
445
471
  this.inspectOpts.colors = this.useColors;
446
472
  return inspect(v, this.inspectOpts);
@@ -0,0 +1,114 @@
1
+ import { isAllowedOrigin } from "./rpc/transports/ws-server.mjs";
2
+ import { t as buildMcpServerFromContext } from "./build-server-0jWgMrz-.mjs";
3
+ import { randomUUID } from "node:crypto";
4
+ import { defineHandler } from "h3";
5
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
6
+ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
7
+ //#region src/adapters/mcp/http.ts
8
+ /**
9
+ * Mount an MCP Streamable-HTTP endpoint on an h3 app at `path`.
10
+ *
11
+ * Each MCP session gets its own {@link WebStandardStreamableHTTPServerTransport}
12
+ * and MCP server (built from the shared, live `ctx` via
13
+ * `buildMcpServerFromContext`), correlated by the `Mcp-Session-Id` header:
14
+ * an `initialize` POST spins up a session; later requests route to it; a
15
+ * `DELETE` (or client disconnect) tears it down.
16
+ *
17
+ * The transport is web-standard — its `handleRequest` takes the h3 event's
18
+ * web `Request` and returns a web `Response` (an SSE `ReadableStream` body
19
+ * for the server→client stream). We copy that response onto `event.res` and
20
+ * return its body rather than returning the `Response` object directly, so a
21
+ * legitimate MCP 404 (unknown session) isn't swallowed by h3's
22
+ * "Response-with-404 falls through to the next handler" rule (which would
23
+ * otherwise hand the request to the SPA static catch-all).
24
+ *
25
+ * @experimental
26
+ */
27
+ function mountMcpHttp(app, ctx, path, options) {
28
+ const sessions = /* @__PURE__ */ new Map();
29
+ const allowedOrigins = options.allowedOrigins;
30
+ function drop(sessionId) {
31
+ const session = sessions.get(sessionId);
32
+ if (!session) return;
33
+ sessions.delete(sessionId);
34
+ session.dispose();
35
+ }
36
+ async function createSession() {
37
+ let session;
38
+ const transport = new WebStandardStreamableHTTPServerTransport({
39
+ sessionIdGenerator: () => randomUUID(),
40
+ onsessioninitialized: (id) => {
41
+ sessions.set(id, session);
42
+ },
43
+ onsessionclosed: (id) => {
44
+ drop(id);
45
+ }
46
+ });
47
+ const { server, dispose } = buildMcpServerFromContext(ctx, {
48
+ serverName: options.serverName,
49
+ serverVersion: options.serverVersion,
50
+ exposeSharedState: options.exposeSharedState
51
+ });
52
+ session = {
53
+ transport,
54
+ dispose: async () => {
55
+ dispose();
56
+ await server.close();
57
+ }
58
+ };
59
+ transport.onclose = () => {
60
+ if (transport.sessionId) drop(transport.sessionId);
61
+ };
62
+ await server.connect(transport);
63
+ return session;
64
+ }
65
+ app.use(path, defineHandler(async (event) => {
66
+ const req = event.req;
67
+ const origin = req.headers.get("origin") ?? void 0;
68
+ if (allowedOrigins !== false && !isAllowedOrigin(origin, allowedOrigins ?? [])) {
69
+ event.res.status = 403;
70
+ return "Forbidden: origin not allowed";
71
+ }
72
+ const sessionId = req.headers.get("mcp-session-id") ?? void 0;
73
+ let session = sessionId ? sessions.get(sessionId) : void 0;
74
+ if (!session && req.method === "POST") {
75
+ let body;
76
+ try {
77
+ body = await req.json();
78
+ } catch {
79
+ body = void 0;
80
+ }
81
+ if (!sessionId && isInitializeRequest(body)) session = await createSession();
82
+ else {
83
+ event.res.status = sessionId ? 404 : 400;
84
+ return sessionId ? "Not Found: unknown MCP session" : "Bad Request: no valid session ID and not an initialize request";
85
+ }
86
+ return respond(event, await session.transport.handleRequest(req, { parsedBody: body }));
87
+ }
88
+ if (!session) {
89
+ event.res.status = sessionId ? 404 : 400;
90
+ return sessionId ? "Not Found: unknown MCP session" : "Bad Request: missing MCP session ID";
91
+ }
92
+ return respond(event, await session.transport.handleRequest(req));
93
+ }));
94
+ return { dispose: async () => {
95
+ const live = [...sessions.values()];
96
+ sessions.clear();
97
+ await Promise.all(live.map((session) => session.dispose()));
98
+ } };
99
+ }
100
+ /**
101
+ * Copy a web `Response` from the MCP transport onto the h3 event's response
102
+ * and return its body. Returning the body (a `ReadableStream` or `null`)
103
+ * rather than the `Response` object avoids h3's 404-fall-through behavior.
104
+ */
105
+ function respond(event, response) {
106
+ event.res.status = response.status;
107
+ event.res.statusText = response.statusText;
108
+ response.headers.forEach((value, key) => {
109
+ event.res.headers.set(key, value);
110
+ });
111
+ return response.body ?? "";
112
+ }
113
+ //#endregion
114
+ export { mountMcpHttp };
@@ -0,0 +1,107 @@
1
+ import { R as SharedState, g as DevframeNodeRpcSession, h as DevframeNodeContext } from "./devframe-BtVHhmwM.mjs";
2
+ import { v as RpcFunctionDefinitionAny } from "./types-DZEx4ffs.mjs";
3
+ import { n as InternalAnonymousAuthStorage } from "./context-UX9qUDK7.mjs";
4
+ import { Peer } from "crossws";
5
+
6
+ //#region src/node/auth/handler.d.ts
7
+ /**
8
+ * A ready-made pre-auth RPC handler, as produced by
9
+ * `devframe/recipes/interactive-auth`'s `createInteractiveAuth`. Bundles
10
+ * everything a host adapter needs to wire an authenticated server:
11
+ * the handshake RPC functions, the resolver gate, the connect-time trust
12
+ * hook, and the startup banner.
13
+ *
14
+ * `startHttpAndWs` accepts one of these directly via its `auth` option —
15
+ * see {@link https://devfra.me | devframe}'s server docs — or a host can
16
+ * wire the four pieces itself against a lower-level transport.
17
+ */
18
+ interface DevframeAuthHandler {
19
+ /**
20
+ * `anonymous:devframe:auth` + `anonymous:devframe:auth:exchange` (the
21
+ * handshake) and `devframe:auth:revoke` (self-revoke) — register these on
22
+ * the RPC host (e.g. `rpcHost.register(fn)` for each).
23
+ */
24
+ rpcFunctions: RpcFunctionDefinitionAny[];
25
+ /**
26
+ * Resolver gate: whether `methodName` is callable given `session`'s
27
+ * current trust state. Defaults to allowing any `anonymous:`-prefixed
28
+ * method (see `isAnonymousRpcMethod`) plus anything once the session is
29
+ * trusted.
30
+ */
31
+ authorize: (methodName: string, session: DevframeNodeRpcSession) => boolean;
32
+ /**
33
+ * Connect-time trust: reads a bearer token off the peer's upgrade request
34
+ * (an `Authorization: Bearer <token>` header, or a static/pre-shared
35
+ * token from `clientAuthTokens`) and, when valid, marks the session
36
+ * trusted immediately — before the client's own handshake call.
37
+ */
38
+ onConnect: (peer: Peer, session: DevframeNodeRpcSession) => void;
39
+ /**
40
+ * Print the current one-time code and its magic-link URL. Devframe stays
41
+ * headless — call this yourself once the server is listening. Safe to
42
+ * call repeatedly; it only prints once per code.
43
+ */
44
+ printBanner: () => void;
45
+ }
46
+ //#endregion
47
+ //#region src/node/auth/revoke.d.ts
48
+ /**
49
+ * Flip `isTrusted` to false on any live WS clients connected with `token`
50
+ * and broadcast the `auth:revoked` event so they can react.
51
+ *
52
+ * Shared between persisted-auth revocation and remote-dock token revocation.
53
+ */
54
+ declare function revokeActiveConnectionsForToken(context: DevframeNodeContext, token: string): Promise<void>;
55
+ /**
56
+ * Revoke an auth token: remove from storage and notify all connected clients
57
+ * using this token that they are no longer trusted.
58
+ */
59
+ declare function revokeAuthToken(context: DevframeNodeContext, storage: SharedState<InternalAnonymousAuthStorage>, token: string): Promise<void>;
60
+ //#endregion
61
+ //#region src/node/auth/state.d.ts
62
+ /**
63
+ * The current one-time authentication code. Display this to the user (e.g. in
64
+ * the dev-server terminal) so they can type it into the browser to authenticate.
65
+ */
66
+ declare function getTempAuthCode(): string;
67
+ /**
68
+ * Rotate the authentication code, resetting its expiry window and failed-attempt
69
+ * counter. Call this when a new authentication flow begins (e.g. when an
70
+ * untrusted client starts authenticating) so the displayed code is freshly
71
+ * valid for its full TTL.
72
+ */
73
+ declare function refreshTempAuthCode(): string;
74
+ /**
75
+ * Build a "magic link" authentication URL that embeds a one-time code (OTP) as
76
+ * a query parameter. Opening it authenticates the client without typing — print
77
+ * it on startup (devframe stays headless, so the host prints its own banner).
78
+ * Defaults to the current code; the link is subject to the same TTL.
79
+ */
80
+ declare function buildOtpAuthUrl(baseUrl: string, code?: string): string;
81
+ /**
82
+ * Re-authenticate a connection that presents a previously-issued bearer token.
83
+ * Returns `true` and marks the session trusted when the token is known.
84
+ *
85
+ * Used by the `anonymous:devframe:auth` handler so a client that already
86
+ * authenticated (token persisted in the browser) is trusted on reconnect
87
+ * without entering the code again.
88
+ */
89
+ declare function verifyAuthToken(token: string, session: DevframeNodeRpcSession, storage: SharedState<InternalAnonymousAuthStorage>): boolean;
90
+ /**
91
+ * Exchange a one-time authentication code for a fresh, node-issued bearer token.
92
+ *
93
+ * On success this mints a high-entropy token, records it in the trusted store,
94
+ * marks the calling session trusted, rotates the code, and returns the token
95
+ * for the client to persist. Returns `null` on any failure.
96
+ *
97
+ * Because the code is short and human-typed, verification is hardened against
98
+ * brute force: it enforces a time-to-live, compares in constant time, and
99
+ * rotates the code after {@link TEMP_AUTH_MAX_ATTEMPTS} failed attempts so an
100
+ * attacker cannot keep guessing against the same code.
101
+ */
102
+ declare function exchangeTempAuthCode(code: string, session: DevframeNodeRpcSession, info: {
103
+ ua: string;
104
+ origin: string;
105
+ }, storage: SharedState<InternalAnonymousAuthStorage>): string | null;
106
+ //#endregion
107
+ export { verifyAuthToken as a, DevframeAuthHandler as c, refreshTempAuthCode as i, exchangeTempAuthCode as n, revokeActiveConnectionsForToken as o, getTempAuthCode as r, revokeAuthToken as s, buildOtpAuthUrl as t };
@@ -16,6 +16,7 @@ declare class RpcCacheManager {
16
16
  constructor(options: RpcCacheOptions);
17
17
  updateOptions(options: Partial<RpcCacheOptions>): void;
18
18
  cached<T>(m: string, a: unknown[]): T | undefined;
19
+ has(m: string, a: unknown[]): boolean;
19
20
  apply(req: {
20
21
  m: string;
21
22
  a: unknown[];