devframe 0.9.8 → 0.9.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 (121) hide show
  1. package/dist/_shared-CCzf9Hub.d.mts +83 -0
  2. package/dist/_shared-U8D-xzrp.mjs +115 -0
  3. package/dist/adapters/build.d.mts +6 -6
  4. package/dist/adapters/build.mjs +33 -36
  5. package/dist/adapters/cac.d.mts +12 -1
  6. package/dist/adapters/cac.mjs +8 -8
  7. package/dist/adapters/dev.d.mts +17 -17
  8. package/dist/adapters/dev.mjs +2 -2
  9. package/dist/adapters/embedded.d.mts +1 -1
  10. package/dist/adapters/initiate.d.mts +31 -28
  11. package/dist/adapters/initiate.mjs +1 -1
  12. package/dist/adapters/mcp.d.mts +28 -14
  13. package/dist/adapters/mcp.mjs +76 -40
  14. package/dist/{agent-tool-name-EgfoFO8C.mjs → agent-tool-name-DGzeuWcO.mjs} +2 -2
  15. package/dist/cli/main.d.mts +1 -1
  16. package/dist/cli/main.mjs +41 -17
  17. package/dist/client/index.d.mts +36 -36
  18. package/dist/client/index.mjs +90 -52
  19. package/dist/{client-assets-C7SXgTWY.mjs → client-assets-DNdhagGm.mjs} +2 -2
  20. package/dist/constants.d.mts +8 -7
  21. package/dist/constants.mjs +7 -6
  22. package/dist/{context-omlma_1n.mjs → context-B29Oh0oA.mjs} +166 -93
  23. package/dist/{context-DftYyXaR.d.mts → context-BJiKJqyW.d.mts} +5 -5
  24. package/dist/{context-CLF4-8Pp.d.mts → context-CHW4BZOj.d.mts} +203 -134
  25. package/dist/{context-KUqAfawQ.mjs → context-jXO5vzjH.mjs} +3 -3
  26. package/dist/{crypto-token-XCqTSMg9.mjs → crypto-token-TVbGGFuj.mjs} +1 -1
  27. package/dist/{dev-CzkPCJwi.mjs → dev-CRYHx7iG.mjs} +73 -40
  28. package/dist/{diagnostics-BXWW3VK-.mjs → diagnostics-DZA6KQ_Z.mjs} +32 -19
  29. package/dist/{events-4daw0uK9.mjs → events-BV4Dj59a.mjs} +2 -2
  30. package/dist/{services-install-CqqQVls5.mjs → host-agent-CKqSTEX9.mjs} +15 -151
  31. package/dist/{host-h3-fRbF9yor.mjs → host-h3-BROI6SQ4.mjs} +2 -2
  32. package/dist/in-page-channel/index.d.mts +14 -14
  33. package/dist/in-page-channel/index.mjs +63 -54
  34. package/dist/{index-D3k0BIhe.d.mts → index-CNC390b_.d.mts} +4 -4
  35. package/dist/{index-BzyopqxV.d.mts → index-pIVt92jP.d.mts} +9 -9
  36. package/dist/index.d.mts +8 -8
  37. package/dist/index.mjs +2 -2
  38. package/dist/{instance-registry-BjlMQbFB.mjs → instance-registry-DCq762YJ.mjs} +9 -9
  39. package/dist/{instance-shell-Tcp5hGoq.d.mts → instance-shell-DZipaP9k.d.mts} +18 -18
  40. package/dist/{instance-shell-CpVW4zG0.mjs → instance-shell-DvYCwrDy.mjs} +223 -67
  41. package/dist/internal/index.d.mts +45 -26
  42. package/dist/internal/index.mjs +10 -10
  43. package/dist/node/auth.d.mts +2 -2
  44. package/dist/node/auth.mjs +2 -2
  45. package/dist/node/hub-internals.d.mts +2 -2
  46. package/dist/node/hub-internals.mjs +2 -2
  47. package/dist/node/index.d.mts +5 -5
  48. package/dist/node/index.mjs +2 -2
  49. package/dist/{nostics-SPGDgHEP.d.mts → nostics-0sKe5HIj.d.mts} +2 -2
  50. package/dist/{nostics-CzECRXpE.mjs → nostics-D0PvLZsn.mjs} +1 -1
  51. package/dist/origin-DGDH8BI9.d.mts +45 -0
  52. package/dist/origin-DZit9g29.mjs +80 -0
  53. package/dist/recipes/common-rpc-functions.d.mts +5 -5
  54. package/dist/recipes/common-rpc-functions.mjs +3 -3
  55. package/dist/recipes/interactive-auth.d.mts +6 -6
  56. package/dist/recipes/interactive-auth.mjs +6 -6
  57. package/dist/{remote-assets-DezQmPQU.mjs → remote-assets-DMFzLSoH.mjs} +27 -7
  58. package/dist/{remote-assets-B13_vRkJ.d.mts → remote-assets-eBvSKZMS.d.mts} +7 -7
  59. package/dist/{revoke-COLW0cOi.mjs → revoke-cqztiCX4.mjs} +1 -1
  60. package/dist/rpc/dump.d.mts +19 -12
  61. package/dist/rpc/dump.mjs +2 -2
  62. package/dist/rpc/index.d.mts +2 -2
  63. package/dist/rpc/index.mjs +2 -3
  64. package/dist/rpc/transports/sse-client.d.mts +1 -1
  65. package/dist/rpc/transports/sse-client.mjs +30 -14
  66. package/dist/rpc/transports/sse-server.d.mts +4 -4
  67. package/dist/rpc/transports/sse-server.mjs +7 -7
  68. package/dist/rpc/transports/ws-bun.d.mts +5 -5
  69. package/dist/rpc/transports/ws-bun.mjs +8 -2
  70. package/dist/rpc/transports/ws-client.d.mts +1 -1
  71. package/dist/rpc/transports/ws-client.mjs +2 -2
  72. package/dist/rpc/transports/ws-deno.d.mts +4 -4
  73. package/dist/rpc/transports/ws-deno.mjs +8 -2
  74. package/dist/rpc/transports/ws-server.d.mts +2 -1
  75. package/dist/rpc/transports/ws-server.mjs +232 -1
  76. package/dist/{rpc-core-DlHd276T.mjs → rpc-core-CvkU23bQ.mjs} +14 -2
  77. package/dist/{rpc-core-B3W3z9ff.d.mts → rpc-core-DsyDViQy.d.mts} +4 -4
  78. package/dist/serialization-DFg5sUt6.mjs +116 -0
  79. package/dist/services-install-Ben4yh52.mjs +154 -0
  80. package/dist/session-DPfj6zQG.mjs +16 -0
  81. package/dist/{shared-state--sduc6Xi.mjs → shared-state-CaxB1q0h.mjs} +2 -2
  82. package/dist/{simple-schema-DQPZrAaZ.mjs → simple-schema-Bi40l-P6.mjs} +2 -2
  83. package/dist/{sse-client-DwX021ST.d.mts → sse-client-BQ4jS3eO.d.mts} +4 -4
  84. package/dist/{state-CK9LjrnT.mjs → state-Mwt6--el.mjs} +17 -9
  85. package/dist/{storage-BXNVhKyR.mjs → storage-B70AXK64.mjs} +2 -2
  86. package/dist/types/index.d.mts +6 -6
  87. package/dist/{types-CMdPYhOx.d.mts → types-Dq4i156u.d.mts} +17 -17
  88. package/dist/utils/agent-tool-name.d.mts +2 -2
  89. package/dist/utils/agent-tool-name.mjs +2 -2
  90. package/dist/utils/colors.d.mts +1 -1
  91. package/dist/utils/crypto-token.d.mts +1 -1
  92. package/dist/utils/crypto-token.mjs +1 -1
  93. package/dist/utils/events.d.mts +1 -1
  94. package/dist/utils/nostics.d.mts +1 -1
  95. package/dist/utils/nostics.mjs +1 -1
  96. package/dist/utils/open.mjs +1 -1
  97. package/dist/utils/origin.d.mts +2 -0
  98. package/dist/utils/origin.mjs +80 -0
  99. package/dist/utils/remote-assets.d.mts +2 -2
  100. package/dist/utils/remote-assets.mjs +1 -1
  101. package/dist/utils/serve-static.d.mts +4 -4
  102. package/dist/utils/serve-static.mjs +56 -19
  103. package/dist/utils/shared-state.d.mts +1 -1
  104. package/dist/utils/shared-state.mjs +1 -1
  105. package/dist/utils/simple-schema.d.mts +5 -5
  106. package/dist/utils/simple-schema.mjs +2 -2
  107. package/dist/utils/streaming-channel.d.mts +1 -1
  108. package/dist/utils/streaming-channel.mjs +2 -2
  109. package/dist/utils/when.d.mts +5 -5
  110. package/dist/{validation-YrZH6atx.mjs → validation-CfV1s0SF.mjs} +2 -2
  111. package/dist/{wire-codec-0K-o5MYW.mjs → wire-codec-BHGt0sSs.mjs} +3 -3
  112. package/dist/{static-DV_qUSRS.mjs → write-BuLCrn9h.mjs} +153 -137
  113. package/dist/{ws-client-7_lZi-B9.d.mts → ws-client-Xpxt0oPA.d.mts} +3 -3
  114. package/dist/{ws-server-VyHlanzy.d.mts → ws-server-D2nv68Hg.d.mts} +13 -37
  115. package/package.json +3 -6
  116. package/skills/devframe/SKILL.md +5 -3
  117. package/dist/_shared-BM3PdYli.mjs +0 -68
  118. package/dist/_shared-mtXpcJIv.d.mts +0 -41
  119. package/dist/diagnostics-CD8nlgll.mjs +0 -39
  120. package/dist/serialization-BGzEwAdr.mjs +0 -77
  121. package/dist/ws-server-Y_AumUQH.mjs +0 -294
@@ -1,7 +1,7 @@
1
- import { d as DevframeRpcConnection, n as WsOriginRegistry, u as DevframeNodeRpcSessionMeta } from "../ws-server-VyHlanzy.mjs";
2
- import { Ct as DevframeAuthHandler, F as DevframeDefinition, G as McpRouteOptions, U as DevframeSseOptions, W as DevframeWsOptions, bt as DevframeStorageScope, o as DevframeNodeContext, s as DevframeNodeRpcSession, t as ConnectionMeta } from "../context-CLF4-8Pp.mjs";
3
- import { o as StaticAssetsSource } from "../remote-assets-B13_vRkJ.mjs";
4
- import { c as StartedServer, f as DevframeInstanceRecord } from "../instance-shell-Tcp5hGoq.mjs";
1
+ import { c as DevframeNodeRpcSessionMeta, l as DevframeRpcConnection, n as WsOriginRegistry } from "../ws-server-D2nv68Hg.mjs";
2
+ import { F as DevframeDefinition, St as DevframeStorageScope, Tt as DevframeAuthHandler, U as DevframeSseOptions, W as DevframeWsOptions, o as DevframeNodeContext, q as McpSetting, s as DevframeNodeRpcSession, t as ConnectionMeta } from "../context-CHW4BZOj.mjs";
3
+ import { o as StaticAssetsSource } from "../remote-assets-eBvSKZMS.mjs";
4
+ import { c as StartedServer, f as DevframeInstanceRecord } from "../instance-shell-DZipaP9k.mjs";
5
5
  import { Buffer } from "node:buffer";
6
6
  import { IncomingMessage, Server, ServerResponse } from "node:http";
7
7
  import { Duplex } from "node:stream";
@@ -9,7 +9,7 @@ import { H3 } from "h3";
9
9
  //#region src/adapters/initiate.d.ts
10
10
  interface InitDevframeOptions {
11
11
  /**
12
- * Mount base the handler answers under (e.g. `/__my-tool/`) — required so
12
+ * Mount base the handler answers under (e.g. `/__my-tool/`), required so
13
13
  * the mount path is explicit at the call site. A handler is by definition
14
14
  * mounted *inside* a host app's origin; the resolved value is echoed back
15
15
  * as {@link DevframeInstance.base} so route/middleware code references it
@@ -19,8 +19,8 @@ interface InitDevframeOptions {
19
19
  base: string;
20
20
  /**
21
21
  * Override the definition's `clientAssets` (or deprecated `cli.distDir`).
22
- * When neither is set — or `false` is passed to suppress the definition's
23
- * own client assets — the handler runs in **bridge mode**: only
22
+ * When neither is set (or `false` is passed to suppress the definition's
23
+ * own client assets), the handler runs in **bridge mode**: only
24
24
  * `__connection.json`, the WS endpoint, and the MCP route (when enabled) are
25
25
  * served; the SPA is hosted elsewhere.
26
26
  */
@@ -36,20 +36,20 @@ interface InitDevframeOptions {
36
36
  */
37
37
  server?: Server;
38
38
  /**
39
- * Explicit control over how the browser reaches the RPC WebSocket —
39
+ * Explicit control over how the browser reaches the RPC WebSocket,
40
40
  * advertised in `__connection.json`. The **local binding** resolves in
41
41
  * precedence order `ws.port` (pinned side-car) > `server` (shared upgrade)
42
42
  * > `ws.sidecar` (auto-port side-car) > none (the host drives the socket
43
43
  * through {@link DevframeInstance.attach}). `ws.url` controls the
44
44
  * advertisement* only: the browser dials it verbatim (a tunnel/relay),
45
- * while the local binding keeps following the options above — and on its
45
+ * while the local binding keeps following the options above; on its
46
46
  * own it means an external server owns both the transport and its auth.
47
- * Pass `false` to serve no WebSocket at all — clients connect over the
47
+ * Pass `false` to serve no WebSocket at all; clients connect over the
48
48
  * SSE endpoint instead (`backend: 'sse'`).
49
49
  */
50
50
  ws?: DevframeWsOptions | false;
51
51
  /**
52
- * SSE RPC endpoint control — enabled by default at `<base>__sse` as the
52
+ * SSE RPC endpoint control, enabled by default at `<base>__sse` as the
53
53
  * more portable transport alongside the WebSocket. Pass `false` to
54
54
  * disable, or a {@link DevframeSseOptions} to rename the route.
55
55
  */
@@ -67,21 +67,24 @@ interface InitDevframeOptions {
67
67
  * (derived from the first request, or `origin`). Pass a
68
68
  * {@link DevframeAuthHandler} for a custom scheme, or `false` to opt out
69
69
  * for a single-user localhost setup that owns the trust boundary another
70
- * way. Ignored for the `ws.url` tier — the server behind that URL owns auth.
70
+ * way. Ignored for the `ws.url` tier, since the server behind that URL owns auth.
71
71
  */
72
72
  auth?: boolean | DevframeAuthHandler;
73
73
  /**
74
74
  * Expose a route-based MCP server (Streamable-HTTP) at `<base>__mcp` and
75
75
  * advertise it in `__connection.json`. Overrides `def.cli?.mcp`;
76
- * `undefined` falls through to it. See {@link McpRouteOptions}.
76
+ * `undefined` falls through to it, then to the `'auto'` default (mount
77
+ * once the agent surface is non-empty). See {@link McpSetting}.
77
78
  */
78
- mcp?: boolean | McpRouteOptions;
79
+ mcp?: McpSetting;
79
80
  /**
80
81
  * Public origin the host app is reachable at (e.g. `http://localhost:3000`),
81
- * or a getter for hosts that resolve it late. When omitted (or the getter
82
- * returns a falsy value), it is derived lazily from the first request the
83
- * handler serves — used for the auth banner's magic link and absolute dock
84
- * URLs.
82
+ * or a getter for hosts that resolve it late. Backs the auth banner's magic
83
+ * link and absolute dock URLs. When omitted (or the getter returns a falsy
84
+ * value), it is derived from a served request, but only when that request's
85
+ * own origin is loopback or exactly matches an `allowedOrigins` entry; a raw
86
+ * inbound `Host`/URL authority and forwarded headers are never adopted. Set
87
+ * this explicitly for a non-loopback deployment (proxy, LAN, public host).
85
88
  */
86
89
  origin?: string | (() => string);
87
90
  /**
@@ -104,7 +107,7 @@ interface InitDevframeOptions {
104
107
  allowedOrigins?: readonly string[] | WsOriginRegistry | false;
105
108
  /**
106
109
  * h3 app to mount the handler's routes on. When omitted a fresh internal
107
- * app is created — the common middleware case. An adapter that owns the
110
+ * app is created, the common middleware case. An adapter that owns the
108
111
  * whole server (e.g. `createDevServer`) passes its own app so callers can
109
112
  * compose custom routes ahead of devframe's.
110
113
  */
@@ -138,19 +141,19 @@ interface DevframeInstance {
138
141
  /**
139
142
  * The normalized mount base this instance answers under (leading and
140
143
  * trailing slash, e.g. `/__my-tool/`). Reference it when wiring the mount
141
- * — route guards, middleware path checks — instead of repeating the
144
+ * (route guards, middleware path checks) instead of repeating the
142
145
  * string.
143
146
  */
144
147
  base: string;
145
148
  /**
146
- * Web-standard request handler — mount it on a catch-all route under
149
+ * Web-standard request handler; mount it on a catch-all route under
147
150
  * {@link DevframeInstance.base} (Next.js route handler, SvelteKit
148
151
  * `+server.ts`, Hono `c.req.raw`, Nitro `toWebRequest(event)`, …).
149
152
  * Requests outside the base 404.
150
153
  */
151
154
  handler: (request: Request) => Promise<Response>;
152
155
  /**
153
- * Connect/Express-style middleware over the same surface — for
156
+ * Connect/Express-style middleware over the same surface, for
154
157
  * `viteServer.middlewares.use(handler.nodeMiddleware)` or any other
155
158
  * node middleware stack. Mount it un-prefixed: paths outside the base
156
159
  * call `next()` so the rest of the stack keeps working.
@@ -158,7 +161,7 @@ interface DevframeInstance {
158
161
  nodeMiddleware: (req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) => void;
159
162
  /**
160
163
  * Route a host server's `upgrade` events to the RPC socket, returning a
161
- * detach function — the manual counterpart to the `server` option, for
164
+ * detach function, the manual counterpart to the `server` option, for
162
165
  * hosts that get their `node:http` server only after the instance exists.
163
166
  * Available on the default tier; a configured transport (`server`,
164
167
  * `ws.port`, `ws.sidecar`, `ws.url`) already owns the socket and reports
@@ -175,10 +178,10 @@ interface DevframeInstance {
175
178
  /**
176
179
  * Resolves once `def.setup` has run and the WebSocket binding is live.
177
180
  * `handler`/`nodeMiddleware` await it internally, so hosts never race
178
- * initialization — await it yourself only when you need the timing.
181
+ * initialization; await it yourself only when you need the timing.
179
182
  */
180
183
  ready: Promise<void>;
181
- /** The node context, once initialized — for advanced wiring (own WS transport, extra RPC registration). */
184
+ /** The node context, once initialized, for advanced wiring (own WS transport, extra RPC registration). */
182
185
  context: Promise<DevframeNodeContext>;
183
186
  /**
184
187
  * The `ConnectionMeta` this handler serves at `<base>__connection.json`.
@@ -204,7 +207,7 @@ interface DevframeInstanceInternals {
204
207
  /** @internal */
205
208
  declare function getInstanceInternals(handler: object): DevframeInstanceInternals;
206
209
  /**
207
- * Serve a devframe through one framework-agnostic, web-standard handler —
210
+ * Serve a devframe through one framework-agnostic, web-standard handler:
208
211
  * the SPA, `__connection.json` discovery, the WebSocket RPC endpoint, the
209
212
  * auth gate, and the optional MCP route, all under a single mount base.
210
213
  * Mount `handler` on any framework's catch-all route (or `nodeMiddleware` on
@@ -212,10 +215,10 @@ declare function getInstanceInternals(handler: object): DevframeInstanceInternal
212
215
  *
213
216
  * The factory is synchronous and kicks off initialization eagerly;
214
217
  * `handler`/`nodeMiddleware` await readiness internally. Nothing binds a port
215
- * on its own: the WebSocket resolves in precedence order — `ws.port` (pinned
218
+ * on its own: the WebSocket resolves in precedence order: `ws.port` (pinned
216
219
  * side-car) > `server` (shared upgrade at `<base>__ws`) > `ws.sidecar`
217
220
  * (auto-port side-car) > the host driving upgrades itself through
218
- * {@link DevframeInstance.attach} — while `ws.url`, when set, overrides the
221
+ * {@link DevframeInstance.attach}. Meanwhile `ws.url`, when set, overrides the
219
222
  * advertised* endpoint (the tunnel pattern) and on its own hands the whole
220
223
  * transport to an external server. `__connection.json` reflects whichever
221
224
  * combination is active.
@@ -1,2 +1,2 @@
1
- import { n as getInstanceInternals, r as initDevframe } from "../dev-CzkPCJwi.mjs";
1
+ import { n as getInstanceInternals, r as initDevframe } from "../dev-CRYHx7iG.mjs";
2
2
  export { getInstanceInternals, initDevframe };
@@ -1,4 +1,4 @@
1
- import { F as DevframeDefinition, o as DevframeNodeContext } from "../context-CLF4-8Pp.mjs";
1
+ import { F as DevframeDefinition, G as McpAuthorization, o as DevframeNodeContext } from "../context-CHW4BZOj.mjs";
2
2
  import { H3 } from "h3";
3
3
  import "@modelcontextprotocol/server";
4
4
  //#region src/adapters/mcp/build-server.d.ts
@@ -6,15 +6,15 @@ interface CreateMcpServerOptions {
6
6
  /**
7
7
  * Transport to use. `createMcpServer` itself runs `'stdio'` (a standalone
8
8
  * process with its own host context); the Streamable-HTTP transport is
9
- * served route-based by the dev server instead — see `mountMcpHttp` and
9
+ * served route-based by the dev server instead; see `mountMcpHttp` and
10
10
  * the `mcp` option on `createDevServer` / `createCac`'s `--mcp` flag.
11
11
  */
12
12
  transport?: 'stdio';
13
13
  /**
14
14
  * Expose shared-state keys as MCP resources.
15
- * - `true` (default) — every key the host publishes
16
- * - `false` — none
17
- * - `(key) => boolean` — filter
15
+ * - `true` (default): every key the host publishes
16
+ * - `false`: none
17
+ * - `(key) => boolean`: filter
18
18
  */
19
19
  exposeSharedState?: boolean | ((key: string) => boolean);
20
20
  /** Override the name reported in the MCP handshake. */
@@ -41,8 +41,17 @@ interface CreateMcpFetchHandlerOptions {
41
41
  serverName: string;
42
42
  /** Version reported in the MCP handshake. */
43
43
  serverVersion: string;
44
- /** Expose shared-state keys as MCP resources — see `buildMcpServerFromContext`. */
44
+ /** Expose shared-state keys as MCP resources; see `buildMcpServerFromContext`. */
45
45
  exposeSharedState: boolean | ((key: string) => boolean);
46
+ /**
47
+ * Optional identity check, layered on top of the origin gate and checked
48
+ * **after** it: a bearer token string (matched in constant time against
49
+ * `Authorization: Bearer <token>`), a `(request) => boolean` callback, or
50
+ * `false` (the default) for origin-only, trusting same-machine callers. A
51
+ * callback governs identity only and cannot relax the origin gate. See
52
+ * {@link McpAuthorization}.
53
+ */
54
+ authorization?: McpAuthorization;
46
55
  /**
47
56
  * Origin allow-list beyond the loopback default. `false` disables the
48
57
  * origin gate entirely. Default: loopback-only.
@@ -57,7 +66,7 @@ interface CreateMcpFetchHandlerOptions {
57
66
  interface McpFetchHandler {
58
67
  /**
59
68
  * WHATWG-`fetch` handler for the MCP endpoint. Hand every method
60
- * (POST/GET/DELETE) on the endpoint's path to it — routing by path is the
69
+ * (POST/GET/DELETE) on the endpoint's path to it; routing by path is the
61
70
  * host's job.
62
71
  */
63
72
  fetch: (request: Request) => Promise<Response>;
@@ -66,22 +75,27 @@ interface McpFetchHandler {
66
75
  }
67
76
  /**
68
77
  * Build a framework-agnostic MCP endpoint over a devframe context: a
69
- * web-standard `Request → Response` handler any host can mount — h3 (see
78
+ * web-standard `Request → Response` handler any host can mount: h3 (see
70
79
  * `mountMcpHttp`), a Next.js App Router route, or any other fetch-shaped
71
80
  * server.
72
81
  *
73
82
  * The endpoint is **stateless**: it serves the 2026-07-28 revision per request
74
83
  * through the SDK's {@link createMcpHandler}, which builds a fresh MCP server
75
84
  * (from the shared, live `ctx` via `buildMcpServerFromContext`) for each
76
- * request — no `Mcp-Session-Id` registry, no session-local routing, no
85
+ * request: no `Mcp-Session-Id` registry, no session-local routing, no
77
86
  * GET/DELETE teardown protocol. 2025-era clients are still served through the
78
87
  * SDK's default stateless legacy path. `list_changed` events reach modern
79
88
  * `subscriptions/listen` streams through the handler's `notify` bus.
80
89
  *
81
90
  * The origin gate guards every request: loopback-default DNS-rebinding
82
- * protection that — unlike the WS upgrade's `isAllowedOrigin` — also rejects
83
- * `Origin`-less requests, so a route-based endpoint isn't reachable by an
84
- * arbitrary local process.
91
+ * protection that (unlike the WS upgrade's `isAllowedOrigin`) also rejects
92
+ * `Origin`-less requests, so a route-based endpoint isn't reachable by a
93
+ * browser or a remote host (a disallowed origin gets `403`). It trusts
94
+ * same-machine callers by default. When that isn't your trust boundary, add
95
+ * an optional identity gate ({@link CreateMcpFetchHandlerOptions.authorization}),
96
+ * checked after the origin gate: a bearer/callback check that proves *who* is
97
+ * calling (a missing/invalid credential gets `401` with a
98
+ * `WWW-Authenticate: Bearer` challenge).
85
99
  */
86
100
  declare function createMcpFetchHandler(ctx: DevframeNodeContext, options: CreateMcpFetchHandlerOptions): McpFetchHandler;
87
101
  //#endregion
@@ -92,11 +106,11 @@ interface MountedMcpHttp {
92
106
  dispose: () => Promise<void>;
93
107
  }
94
108
  /**
95
- * Mount a stateless MCP endpoint on an h3 app at `path` — the h3 binding over
109
+ * Mount a stateless MCP endpoint on an h3 app at `path`: the h3 binding over
96
110
  * {@link createMcpFetchHandler}, which owns the per-request serving, the
97
111
  * origin gate, and the transport plumbing.
98
112
  *
99
- * The handler is web-standard — it takes the h3 event's web `Request` and
113
+ * The handler is web-standard: it takes the h3 event's web `Request` and
100
114
  * returns a web `Response` (an SSE `ReadableStream` body for a
101
115
  * `subscriptions/listen` stream). We copy that response onto `event.res` and
102
116
  * return its body rather than returning the `Response` object directly, so an
@@ -1,11 +1,12 @@
1
- import { t as Diagnostic } from "../nostics-CzECRXpE.mjs";
2
- import { t as DEVFRAME_EVENTS } from "../events-4daw0uK9.mjs";
3
- import { i as isAllowedOrigin } from "../ws-server-Y_AumUQH.mjs";
4
- import { t as diagnostics } from "../diagnostics-BXWW3VK-.mjs";
5
- import { t as createHostContext } from "../context-omlma_1n.mjs";
6
- import { t as toAgentToolName } from "../agent-tool-name-EgfoFO8C.mjs";
7
- import process from "node:process";
1
+ import { t as Diagnostic } from "../nostics-D0PvLZsn.mjs";
2
+ import { t as DEVFRAME_EVENTS } from "../events-BV4Dj59a.mjs";
3
+ import { t as isAllowedOrigin } from "../origin-DZit9g29.mjs";
4
+ import { r as timingSafeEqual } from "../crypto-token-TVbGGFuj.mjs";
5
+ import { t as diagnostics } from "../diagnostics-DZA6KQ_Z.mjs";
6
+ import { t as createHostContext } from "../context-B29Oh0oA.mjs";
7
+ import { t as toAgentToolName } from "../agent-tool-name-DGzeuWcO.mjs";
8
8
  import { join } from "pathe";
9
+ import process from "node:process";
9
10
  import { homedir } from "node:os";
10
11
  import { defineHandler } from "h3";
11
12
  import { Server, createMcpHandler } from "@modelcontextprotocol/server";
@@ -65,7 +66,7 @@ function stringifyForMcp(value) {
65
66
  * Format a thrown value for an MCP `isError` text payload.
66
67
  *
67
68
  * A nostics `Diagnostic` (every coded devframe error) becomes structured
68
- * JSON — `{ error: { code, message, fix?, docs? } }` — so an agent receives
69
+ * JSON (`{ error: { code, message, fix?, docs? } }`), so an agent receives
69
70
  * the actionable next step (`fix`) and the docs URL instead of a bare
70
71
  * message string. Other errors surface `Error.name`/`message`, plus one
71
72
  * level of `cause.message` so context isn't dropped silently.
@@ -93,7 +94,7 @@ const FALLBACK_OBJECT_SCHEMA = Object.freeze({
93
94
  *
94
95
  * Devframe stays validator-neutral, so conversion uses the schema's own
95
96
  * [Standard JSON Schema](https://standardschema.dev/) converter
96
- * (`~standard.jsonSchema`) when the validator provides one — zod 4 does,
97
+ * (`~standard.jsonSchema`) when the validator provides one; zod 4 does,
97
98
  * for example. Validators without a native converter (e.g. valibot) degrade
98
99
  * to a permissive object schema rather than pulling in a converter library.
99
100
  */
@@ -116,7 +117,7 @@ function returnToJsonSchema(schema) {
116
117
  }
117
118
  /**
118
119
  * JSON Schema for an RPC function's positional args on the agent/MCP
119
- * surface. Each positional arg is advertised under `arg0` / `arg1` / … —
120
+ * surface. Each positional arg is advertised under `arg0` / `arg1` / …,
120
121
  * matching how the agent bridge coerces the incoming object payload back
121
122
  * into positional arguments.
122
123
  *
@@ -152,7 +153,7 @@ function argsToJsonSchema(args) {
152
153
  //#region src/adapters/mcp/build-server.ts
153
154
  /**
154
155
  * Build a fresh MCP {@link Server} over a devframe context, registering its
155
- * tool and resource handlers. This is a pure factory — it sets up no
156
+ * tool and resource handlers. This is a pure factory: it sets up no
156
157
  * long-lived subscriptions and holds no per-connection state, so it is safe
157
158
  * to call once per request under `createMcpHandler` or once per connection
158
159
  * under `serveStdio`. Change notifications are published separately: over
@@ -265,9 +266,9 @@ async function createMcpServer(definition, options = {}) {
265
266
  } };
266
267
  }
267
268
  /**
268
- * Id of the built-in shared-state read tool — namespaced like every other
269
+ * Id of the built-in shared-state read tool, namespaced like every other
269
270
  * built-in (`devframe:<area>:<fn>`). Tool-shaped access matters because many
270
- * MCP clients only consume tools — the parallel `devframe://state/<key>`
271
+ * MCP clients only consume tools; the parallel `devframe://state/<key>`
271
272
  * resource projection stays for the clients that do read resources.
272
273
  */
273
274
  const READ_STATE_TOOL = "devframe:state:read";
@@ -310,8 +311,8 @@ function registerToolHandlers(server, ctx, exposeSharedState) {
310
311
  const warnedCollisions = /* @__PURE__ */ new Set();
311
312
  /**
312
313
  * Resolve a wire tool name back to the registered {@link AgentTool}.
313
- * Wire-name matching runs first, in manifest order — the same tool the
314
- * list projection advertises under that name — with a raw-id fallback so
314
+ * Wire-name matching runs first, in manifest order (the same tool the
315
+ * list projection advertises under that name), with a raw-id fallback so
315
316
  * a colon-namespaced id keeps working as a call name.
316
317
  */
317
318
  const resolveTool = (name) => {
@@ -375,6 +376,7 @@ function registerToolHandlers(server, ctx, exposeSharedState) {
375
376
  });
376
377
  }
377
378
  function registerResourceHandlers(server, ctx, exposeSharedState) {
379
+ const stateFilter = sharedStateFilter(exposeSharedState);
378
380
  server.setRequestHandler("resources/list", async () => {
379
381
  const resources = ctx.agent.list().resources.map((resource) => ({
380
382
  uri: resource.uri,
@@ -382,17 +384,14 @@ function registerResourceHandlers(server, ctx, exposeSharedState) {
382
384
  description: resource.description,
383
385
  mimeType: resource.mimeType
384
386
  }));
385
- if (exposeSharedState !== false) {
386
- const filter = typeof exposeSharedState === "function" ? exposeSharedState : () => true;
387
- for (const key of ctx.rpc.sharedState.keys()) {
388
- if (!filter(key)) continue;
389
- resources.push({
390
- uri: `devframe://state/${encodeURIComponent(key)}`,
391
- name: key,
392
- description: `Shared state: ${key}`,
393
- mimeType: "application/json"
394
- });
395
- }
387
+ if (stateFilter) for (const key of ctx.rpc.sharedState.keys()) {
388
+ if (!stateFilter(key)) continue;
389
+ resources.push({
390
+ uri: `devframe://state/${encodeURIComponent(key)}`,
391
+ name: key,
392
+ description: `Shared state: ${key}`,
393
+ mimeType: "application/json"
394
+ });
396
395
  }
397
396
  return { resources };
398
397
  });
@@ -407,17 +406,20 @@ function registerResourceHandlers(server, ctx, exposeSharedState) {
407
406
  text: content.text ?? stringifyForMcp(content.json)
408
407
  }] };
409
408
  }
410
- if (parsed.kind === "state") return { contents: [{
411
- uri,
412
- mimeType: "application/json",
413
- text: stringifyForMcp((await ctx.rpc.sharedState.get(parsed.key)).value())
414
- }] };
409
+ if (parsed.kind === "state") {
410
+ if (!stateFilter || !stateFilter(parsed.key)) throw diagnostics.DF0048({ key: parsed.key });
411
+ return { contents: [{
412
+ uri,
413
+ mimeType: "application/json",
414
+ text: stringifyForMcp((await ctx.rpc.sharedState.get(parsed.key)).value())
415
+ }] };
416
+ }
415
417
  throw new Error(`[devframe/mcp] unknown resource URI "${uri}"`);
416
418
  });
417
419
  }
418
420
  /**
419
421
  * MCP constrains a tool's `outputSchema` to a JSON Schema of `type:
420
- * "object"` — clients (the SDK included) reject anything else. Non-object
422
+ * "object"`; clients (the SDK included) reject anything else. Non-object
421
423
  * return schemas (e.g. a schema for `void` / a bare string) simply project
422
424
  * no output schema; the text content still carries the result.
423
425
  */
@@ -477,26 +479,56 @@ function parseResourceUri(uri) {
477
479
  //#endregion
478
480
  //#region src/adapters/mcp/fetch.ts
479
481
  /**
482
+ * Parse exactly one `Authorization: Bearer <token>` credential, returning the
483
+ * token or `undefined` for a missing, malformed, empty, or multi-credential
484
+ * header. The token itself is never logged. `\S+` rejects the whitespace that
485
+ * a second credential (fetch merges duplicate headers as `a, b`) or an empty
486
+ * value would introduce.
487
+ */
488
+ function parseBearerToken(header) {
489
+ if (!header) return void 0;
490
+ const match = /^Bearer (\S+)$/i.exec(header.trim());
491
+ return match ? match[1] : void 0;
492
+ }
493
+ /**
494
+ * Resolve the identity gate for one request. `false` is the origin-only
495
+ * opt-out; a callback delegates identity; a string requires a constant-time
496
+ * bearer match. Never reveals whether a supplied token was close to correct.
497
+ */
498
+ async function isAuthorized(req, authorization) {
499
+ if (authorization === false) return true;
500
+ if (typeof authorization === "function") return await authorization(req) === true;
501
+ const token = parseBearerToken(req.headers.get("authorization"));
502
+ if (token === void 0) return false;
503
+ return timingSafeEqual(token, authorization);
504
+ }
505
+ /**
480
506
  * Build a framework-agnostic MCP endpoint over a devframe context: a
481
- * web-standard `Request → Response` handler any host can mount — h3 (see
507
+ * web-standard `Request → Response` handler any host can mount: h3 (see
482
508
  * `mountMcpHttp`), a Next.js App Router route, or any other fetch-shaped
483
509
  * server.
484
510
  *
485
511
  * The endpoint is **stateless**: it serves the 2026-07-28 revision per request
486
512
  * through the SDK's {@link createMcpHandler}, which builds a fresh MCP server
487
513
  * (from the shared, live `ctx` via `buildMcpServerFromContext`) for each
488
- * request — no `Mcp-Session-Id` registry, no session-local routing, no
514
+ * request: no `Mcp-Session-Id` registry, no session-local routing, no
489
515
  * GET/DELETE teardown protocol. 2025-era clients are still served through the
490
516
  * SDK's default stateless legacy path. `list_changed` events reach modern
491
517
  * `subscriptions/listen` streams through the handler's `notify` bus.
492
518
  *
493
519
  * The origin gate guards every request: loopback-default DNS-rebinding
494
- * protection that — unlike the WS upgrade's `isAllowedOrigin` — also rejects
495
- * `Origin`-less requests, so a route-based endpoint isn't reachable by an
496
- * arbitrary local process.
520
+ * protection that (unlike the WS upgrade's `isAllowedOrigin`) also rejects
521
+ * `Origin`-less requests, so a route-based endpoint isn't reachable by a
522
+ * browser or a remote host (a disallowed origin gets `403`). It trusts
523
+ * same-machine callers by default. When that isn't your trust boundary, add
524
+ * an optional identity gate ({@link CreateMcpFetchHandlerOptions.authorization}),
525
+ * checked after the origin gate: a bearer/callback check that proves *who* is
526
+ * calling (a missing/invalid credential gets `401` with a
527
+ * `WWW-Authenticate: Bearer` challenge).
497
528
  */
498
529
  function createMcpFetchHandler(ctx, options) {
499
530
  const allowedOrigins = options.allowedOrigins;
531
+ const authorization = options.authorization ?? false;
500
532
  const handler = createMcpHandler(() => buildMcpServerFromContext(ctx, {
501
533
  serverName: options.serverName,
502
534
  serverVersion: options.serverVersion,
@@ -512,7 +544,11 @@ function createMcpFetchHandler(ctx, options) {
512
544
  });
513
545
  async function handle(req) {
514
546
  const origin = req.headers.get("origin") ?? void 0;
515
- if (allowedOrigins !== false && (origin === void 0 || !isAllowedOrigin(origin, allowedOrigins ?? []))) return new Response("Forbidden: origin required", { status: 403 });
547
+ if (allowedOrigins !== false && (origin === void 0 || !isAllowedOrigin(origin, allowedOrigins ?? []))) return new Response("Forbidden", { status: 403 });
548
+ if (!await isAuthorized(req, authorization)) return new Response("Unauthorized", {
549
+ status: 401,
550
+ headers: { "WWW-Authenticate": "Bearer" }
551
+ });
516
552
  return handler.fetch(req);
517
553
  }
518
554
  return {
@@ -526,11 +562,11 @@ function createMcpFetchHandler(ctx, options) {
526
562
  //#endregion
527
563
  //#region src/adapters/mcp/http.ts
528
564
  /**
529
- * Mount a stateless MCP endpoint on an h3 app at `path` — the h3 binding over
565
+ * Mount a stateless MCP endpoint on an h3 app at `path`: the h3 binding over
530
566
  * {@link createMcpFetchHandler}, which owns the per-request serving, the
531
567
  * origin gate, and the transport plumbing.
532
568
  *
533
- * The handler is web-standard — it takes the h3 event's web `Request` and
569
+ * The handler is web-standard: it takes the h3 event's web `Request` and
534
570
  * returns a web `Response` (an SSE `ReadableStream` body for a
535
571
  * `subscriptions/listen` stream). We copy that response onto `event.res` and
536
572
  * return its body rather than returning the `Response` object directly, so an
@@ -7,13 +7,13 @@ const MAX_TOOL_NAME_LENGTH = 128;
7
7
  /**
8
8
  * Derive the wire-safe agent tool name for an internal tool id.
9
9
  *
10
- * Devframe tool ids are colon-namespaced — `devframe:<area>:<fn>` for
10
+ * Devframe tool ids are colon-namespaced: `devframe:<area>:<fn>` for
11
11
  * built-ins, `devframes:plugin:<slug>:<fn>` for plugin RPCs, and hub
12
12
  * command ids for command-derived tools. MCP clients constrain tool names
13
13
  * to `^[a-zA-Z0-9_-]{1,128}$`, so the agent/MCP boundary derives the wire
14
14
  * name automatically: every run of characters outside `[a-zA-Z0-9_-]`
15
15
  * becomes a single `_`, truncated to 128 characters. Internal ids never
16
- * change — resolution back to the id happens at the boundary.
16
+ * change; resolution back to the id happens at the boundary.
17
17
  *
18
18
  * ```
19
19
  * devframe:state:read → devframe_state_read
@@ -1,6 +1,6 @@
1
1
  //#region src/cli/main.d.ts
2
2
  /**
3
- * The `devframe` bin — the framework's own CLI, distinct from the per-app
3
+ * The `devframe` bin is the framework's own CLI, distinct from the per-app
4
4
  * CLI shells authors build with `createCac(definition)`. It hosts the
5
5
  * app-independent commands; today that is `connect`, the MCP connector.
6
6
  */
package/dist/cli/main.mjs CHANGED
@@ -1,18 +1,37 @@
1
- import { t as Diagnostic } from "../nostics-CzECRXpE.mjs";
2
- import { t as diagnostics } from "../diagnostics-BXWW3VK-.mjs";
3
- import { n as listLiveDevframeInstances, r as probeDevframeOrigin } from "../instance-registry-BjlMQbFB.mjs";
4
- import { t as toAgentToolName } from "../agent-tool-name-EgfoFO8C.mjs";
1
+ import { t as Diagnostic } from "../nostics-D0PvLZsn.mjs";
2
+ import { t as diagnostics } from "../diagnostics-DZA6KQ_Z.mjs";
3
+ import { n as listLiveDevframeInstances, r as probeDevframeOrigin } from "../instance-registry-DCq762YJ.mjs";
4
+ import { t as toAgentToolName } from "../agent-tool-name-DGzeuWcO.mjs";
5
5
  import process from "node:process";
6
6
  import { joinURL } from "ufo";
7
7
  import { cac } from "cac";
8
8
  //#region src/cli/connect.ts
9
+ /**
10
+ * Resolve the per-record bearer from the {@link ConnectServerOptions.authToken}
11
+ * option. Exported for focused tests of the credential resolution.
12
+ */
13
+ function resolveAuthToken(authToken, record) {
14
+ return typeof authToken === "function" ? authToken(record) : authToken;
15
+ }
16
+ /**
17
+ * Build the request headers the connector sends to one instance's MCP route:
18
+ * the instance's own (loopback) `origin` so the route's origin gate accepts
19
+ * this native client, plus `Authorization: Bearer <token>` when a bearer is
20
+ * configured. The bearer appears **only** here, never in the connection URL,
21
+ * the registry records, or the indexed results. Exported for focused tests.
22
+ */
23
+ function buildInstanceRequestHeaders(url, token) {
24
+ const headers = { origin: new URL(url).origin };
25
+ if (token) headers.authorization = `Bearer ${token}`;
26
+ return headers;
27
+ }
9
28
  const INDEX_TOOL = toAgentToolName("devframe:connect:list-instances");
10
29
  const CALL_TOOL = toAgentToolName("devframe:connect:call-tool");
11
30
  const MCP_DISABLED_HINT = "This instance runs without an MCP route. Restart it with the --mcp flag (or set `cli.mcp: true` on its definition) to expose its tools, then list instances again.";
12
31
  const GATEWAY_TOOLS = [{
13
32
  name: INDEX_TOOL,
14
33
  title: "Discover running devframes",
15
- description: "Discover every running devframe dev server on this machine and list each one's MCP tools. Call this FIRST, before assuming which devtools are available — the result names the instance (id, project root, origin) and the port to pass to the call tool. Safe to call freely.",
34
+ description: "Discover every running devframe dev server on this machine and list each one's MCP tools. Call this FIRST, before assuming which devtools are available; the result names the instance (id, project root, origin) and the port to pass to the call tool. Safe to call freely.",
16
35
  inputSchema: {
17
36
  type: "object",
18
37
  properties: {}
@@ -48,12 +67,12 @@ const GATEWAY_TOOLS = [{
48
67
  /**
49
68
  * Start the devframe MCP connector on stdio: a thin discovery + proxy server
50
69
  * in the shape Vercel's next-devtools-mcp (https://github.com/vercel/next-devtools-mcp)
51
- * validated — credit due there for the architecture this connector follows.
52
- * It exposes two gateway tools —
70
+ * validated; credit is due there for the architecture this connector follows.
71
+ * It exposes two gateway tools:
53
72
  * `devframe_connect_list-instances` (discover running devframe instances via
54
73
  * the instance registry and list each one's MCP tools) and
55
74
  * `devframe_connect_call-tool` (invoke one tool on one instance over its
56
- * Streamable-HTTP endpoint) — and holds no domain knowledge of its own.
75
+ * Streamable-HTTP endpoint), and holds no domain knowledge of its own.
57
76
  */
58
77
  async function startConnectServer(options = {}) {
59
78
  const sdk = await importSdk();
@@ -128,7 +147,7 @@ async function index(sdk, options) {
128
147
  try {
129
148
  entry.mcp = {
130
149
  url,
131
- tools: await listInstanceTools(sdk, url)
150
+ tools: await listInstanceTools(sdk, url, resolveAuthToken(options.authToken, record))
132
151
  };
133
152
  } catch (error) {
134
153
  entry.mcp = {
@@ -163,8 +182,8 @@ async function probePort(port, timeoutMs) {
163
182
  startedAt: 0
164
183
  };
165
184
  }
166
- async function listInstanceTools(sdk, url) {
167
- return withInstanceClient(sdk, url, async (client) => {
185
+ async function listInstanceTools(sdk, url, token) {
186
+ return withInstanceClient(sdk, url, token, async (client) => {
168
187
  return (await client.listTools()).tools.map((tool) => ({
169
188
  name: tool.name,
170
189
  description: tool.description
@@ -180,7 +199,7 @@ async function call(sdk, options, args) {
180
199
  const record = live.find((r) => r.port === args.port) ?? await probePort(args.port, options.timeoutMs);
181
200
  if (!record) throw diagnostics.DF0050({ port: args.port });
182
201
  if (!record.mcp) throw diagnostics.DF0051({ port: args.port });
183
- return withInstanceClient(sdk, `${record.origin}${record.mcp.path}`, async (client) => {
202
+ return withInstanceClient(sdk, `${record.origin}${record.mcp.path}`, resolveAuthToken(options.authToken, record), async (client) => {
184
203
  const result = await client.callTool({
185
204
  name: args.tool,
186
205
  arguments: args.args ?? {}
@@ -197,9 +216,8 @@ async function call(sdk, options, args) {
197
216
  };
198
217
  });
199
218
  }
200
- async function withInstanceClient(sdk, url, fn) {
201
- const origin = new URL(url).origin;
202
- const transport = new sdk.StreamableHTTPClientTransport(new URL(url), { requestInit: { headers: { origin } } });
219
+ async function withInstanceClient(sdk, url, token, fn) {
220
+ const transport = new sdk.StreamableHTTPClientTransport(new URL(url), { requestInit: { headers: buildInstanceRequestHeaders(url, token) } });
203
221
  const client = new sdk.Client({
204
222
  name: "devframe-connect",
205
223
  version: "0.0.0"
@@ -254,7 +272,7 @@ function keepAlive() {
254
272
  //#endregion
255
273
  //#region src/cli/main.ts
256
274
  /**
257
- * The `devframe` bin — the framework's own CLI, distinct from the per-app
275
+ * The `devframe` bin is the framework's own CLI, distinct from the per-app
258
276
  * CLI shells authors build with `createCac(definition)`. It hosts the
259
277
  * app-independent commands; today that is `connect`, the MCP connector.
260
278
  */
@@ -264,7 +282,13 @@ async function runDevframeCli(argv = process.argv) {
264
282
  await startConnectServer({
265
283
  ports: parsePortsFlag(options.port),
266
284
  instancesDir: options.instancesDir,
267
- timeoutMs: options.timeout
285
+ timeoutMs: options.timeout,
286
+ /**
287
+ * The bearer for authenticated instance MCP routes comes from the
288
+ * environment, never a CLI flag: command-line arguments are visible to
289
+ * any process on the machine (`ps`, `/proc`), which would defeat it.
290
+ */
291
+ authToken: process.env.DEVFRAME_MCP_AUTH_TOKEN
268
292
  });
269
293
  keepAlive();
270
294
  });