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.
- package/dist/_shared-CCzf9Hub.d.mts +83 -0
- package/dist/_shared-U8D-xzrp.mjs +115 -0
- package/dist/adapters/build.d.mts +6 -6
- package/dist/adapters/build.mjs +33 -36
- package/dist/adapters/cac.d.mts +12 -1
- package/dist/adapters/cac.mjs +8 -8
- package/dist/adapters/dev.d.mts +17 -17
- package/dist/adapters/dev.mjs +2 -2
- package/dist/adapters/embedded.d.mts +1 -1
- package/dist/adapters/initiate.d.mts +31 -28
- package/dist/adapters/initiate.mjs +1 -1
- package/dist/adapters/mcp.d.mts +28 -14
- package/dist/adapters/mcp.mjs +76 -40
- package/dist/{agent-tool-name-EgfoFO8C.mjs → agent-tool-name-DGzeuWcO.mjs} +2 -2
- package/dist/cli/main.d.mts +1 -1
- package/dist/cli/main.mjs +41 -17
- package/dist/client/index.d.mts +36 -36
- package/dist/client/index.mjs +90 -52
- package/dist/{client-assets-C7SXgTWY.mjs → client-assets-DNdhagGm.mjs} +2 -2
- package/dist/constants.d.mts +8 -7
- package/dist/constants.mjs +7 -6
- package/dist/{context-omlma_1n.mjs → context-B29Oh0oA.mjs} +166 -93
- package/dist/{context-DftYyXaR.d.mts → context-BJiKJqyW.d.mts} +5 -5
- package/dist/{context-CLF4-8Pp.d.mts → context-CHW4BZOj.d.mts} +203 -134
- package/dist/{context-KUqAfawQ.mjs → context-jXO5vzjH.mjs} +3 -3
- package/dist/{crypto-token-XCqTSMg9.mjs → crypto-token-TVbGGFuj.mjs} +1 -1
- package/dist/{dev-CzkPCJwi.mjs → dev-CRYHx7iG.mjs} +73 -40
- package/dist/{diagnostics-BXWW3VK-.mjs → diagnostics-DZA6KQ_Z.mjs} +32 -19
- package/dist/{events-4daw0uK9.mjs → events-BV4Dj59a.mjs} +2 -2
- package/dist/{services-install-CqqQVls5.mjs → host-agent-CKqSTEX9.mjs} +15 -151
- package/dist/{host-h3-fRbF9yor.mjs → host-h3-BROI6SQ4.mjs} +2 -2
- package/dist/in-page-channel/index.d.mts +14 -14
- package/dist/in-page-channel/index.mjs +63 -54
- package/dist/{index-D3k0BIhe.d.mts → index-CNC390b_.d.mts} +4 -4
- package/dist/{index-BzyopqxV.d.mts → index-pIVt92jP.d.mts} +9 -9
- package/dist/index.d.mts +8 -8
- package/dist/index.mjs +2 -2
- package/dist/{instance-registry-BjlMQbFB.mjs → instance-registry-DCq762YJ.mjs} +9 -9
- package/dist/{instance-shell-Tcp5hGoq.d.mts → instance-shell-DZipaP9k.d.mts} +18 -18
- package/dist/{instance-shell-CpVW4zG0.mjs → instance-shell-DvYCwrDy.mjs} +223 -67
- package/dist/internal/index.d.mts +45 -26
- package/dist/internal/index.mjs +10 -10
- package/dist/node/auth.d.mts +2 -2
- package/dist/node/auth.mjs +2 -2
- package/dist/node/hub-internals.d.mts +2 -2
- package/dist/node/hub-internals.mjs +2 -2
- package/dist/node/index.d.mts +5 -5
- package/dist/node/index.mjs +2 -2
- package/dist/{nostics-SPGDgHEP.d.mts → nostics-0sKe5HIj.d.mts} +2 -2
- package/dist/{nostics-CzECRXpE.mjs → nostics-D0PvLZsn.mjs} +1 -1
- package/dist/origin-DGDH8BI9.d.mts +45 -0
- package/dist/origin-DZit9g29.mjs +80 -0
- package/dist/recipes/common-rpc-functions.d.mts +5 -5
- package/dist/recipes/common-rpc-functions.mjs +3 -3
- package/dist/recipes/interactive-auth.d.mts +6 -6
- package/dist/recipes/interactive-auth.mjs +6 -6
- package/dist/{remote-assets-DezQmPQU.mjs → remote-assets-DMFzLSoH.mjs} +27 -7
- package/dist/{remote-assets-B13_vRkJ.d.mts → remote-assets-eBvSKZMS.d.mts} +7 -7
- package/dist/{revoke-COLW0cOi.mjs → revoke-cqztiCX4.mjs} +1 -1
- package/dist/rpc/dump.d.mts +19 -12
- package/dist/rpc/dump.mjs +2 -2
- package/dist/rpc/index.d.mts +2 -2
- package/dist/rpc/index.mjs +2 -3
- package/dist/rpc/transports/sse-client.d.mts +1 -1
- package/dist/rpc/transports/sse-client.mjs +30 -14
- package/dist/rpc/transports/sse-server.d.mts +4 -4
- package/dist/rpc/transports/sse-server.mjs +7 -7
- package/dist/rpc/transports/ws-bun.d.mts +5 -5
- package/dist/rpc/transports/ws-bun.mjs +8 -2
- package/dist/rpc/transports/ws-client.d.mts +1 -1
- package/dist/rpc/transports/ws-client.mjs +2 -2
- package/dist/rpc/transports/ws-deno.d.mts +4 -4
- package/dist/rpc/transports/ws-deno.mjs +8 -2
- package/dist/rpc/transports/ws-server.d.mts +2 -1
- package/dist/rpc/transports/ws-server.mjs +232 -1
- package/dist/{rpc-core-DlHd276T.mjs → rpc-core-CvkU23bQ.mjs} +14 -2
- package/dist/{rpc-core-B3W3z9ff.d.mts → rpc-core-DsyDViQy.d.mts} +4 -4
- package/dist/serialization-DFg5sUt6.mjs +116 -0
- package/dist/services-install-Ben4yh52.mjs +154 -0
- package/dist/session-DPfj6zQG.mjs +16 -0
- package/dist/{shared-state--sduc6Xi.mjs → shared-state-CaxB1q0h.mjs} +2 -2
- package/dist/{simple-schema-DQPZrAaZ.mjs → simple-schema-Bi40l-P6.mjs} +2 -2
- package/dist/{sse-client-DwX021ST.d.mts → sse-client-BQ4jS3eO.d.mts} +4 -4
- package/dist/{state-CK9LjrnT.mjs → state-Mwt6--el.mjs} +17 -9
- package/dist/{storage-BXNVhKyR.mjs → storage-B70AXK64.mjs} +2 -2
- package/dist/types/index.d.mts +6 -6
- package/dist/{types-CMdPYhOx.d.mts → types-Dq4i156u.d.mts} +17 -17
- package/dist/utils/agent-tool-name.d.mts +2 -2
- package/dist/utils/agent-tool-name.mjs +2 -2
- package/dist/utils/colors.d.mts +1 -1
- package/dist/utils/crypto-token.d.mts +1 -1
- package/dist/utils/crypto-token.mjs +1 -1
- package/dist/utils/events.d.mts +1 -1
- package/dist/utils/nostics.d.mts +1 -1
- package/dist/utils/nostics.mjs +1 -1
- package/dist/utils/open.mjs +1 -1
- package/dist/utils/origin.d.mts +2 -0
- package/dist/utils/origin.mjs +80 -0
- package/dist/utils/remote-assets.d.mts +2 -2
- package/dist/utils/remote-assets.mjs +1 -1
- package/dist/utils/serve-static.d.mts +4 -4
- package/dist/utils/serve-static.mjs +56 -19
- package/dist/utils/shared-state.d.mts +1 -1
- package/dist/utils/shared-state.mjs +1 -1
- package/dist/utils/simple-schema.d.mts +5 -5
- package/dist/utils/simple-schema.mjs +2 -2
- package/dist/utils/streaming-channel.d.mts +1 -1
- package/dist/utils/streaming-channel.mjs +2 -2
- package/dist/utils/when.d.mts +5 -5
- package/dist/{validation-YrZH6atx.mjs → validation-CfV1s0SF.mjs} +2 -2
- package/dist/{wire-codec-0K-o5MYW.mjs → wire-codec-BHGt0sSs.mjs} +3 -3
- package/dist/{static-DV_qUSRS.mjs → write-BuLCrn9h.mjs} +153 -137
- package/dist/{ws-client-7_lZi-B9.d.mts → ws-client-Xpxt0oPA.d.mts} +3 -3
- package/dist/{ws-server-VyHlanzy.d.mts → ws-server-D2nv68Hg.d.mts} +13 -37
- package/package.json +3 -6
- package/skills/devframe/SKILL.md +5 -3
- package/dist/_shared-BM3PdYli.mjs +0 -68
- package/dist/_shared-mtXpcJIv.d.mts +0 -41
- package/dist/diagnostics-CD8nlgll.mjs +0 -39
- package/dist/serialization-BGzEwAdr.mjs +0 -77
- package/dist/ws-server-Y_AumUQH.mjs +0 -294
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { o as StaticAssetsSource } from "../remote-assets-
|
|
4
|
-
import { c as StartedServer, f as DevframeInstanceRecord } from "../instance-shell-
|
|
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/`)
|
|
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
|
|
23
|
-
* own client assets
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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?:
|
|
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.
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
181
|
+
* initialization; await it yourself only when you need the timing.
|
|
179
182
|
*/
|
|
180
183
|
ready: Promise<void>;
|
|
181
|
-
/** The node context, once initialized
|
|
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
|
|
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}
|
|
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-
|
|
1
|
+
import { n as getInstanceInternals, r as initDevframe } from "../dev-CRYHx7iG.mjs";
|
|
2
2
|
export { getInstanceInternals, initDevframe };
|
package/dist/adapters/mcp.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as DevframeDefinition, o as DevframeNodeContext } from "../context-
|
|
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
|
|
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)
|
|
16
|
-
* - `false
|
|
17
|
-
* - `(key) => boolean
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
83
|
-
* `Origin`-less requests, so a route-based endpoint isn't reachable by
|
|
84
|
-
*
|
|
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
|
|
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
|
|
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
|
package/dist/adapters/mcp.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { t as Diagnostic } from "../nostics-
|
|
2
|
-
import { t as DEVFRAME_EVENTS } from "../events-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { t as
|
|
6
|
-
import { t as
|
|
7
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
314
|
-
* list projection advertises under that name
|
|
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 (
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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")
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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"
|
|
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
|
|
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
|
|
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
|
|
495
|
-
* `Origin`-less requests, so a route-based endpoint isn't reachable by
|
|
496
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
16
|
+
* change; resolution back to the id happens at the boundary.
|
|
17
17
|
*
|
|
18
18
|
* ```
|
|
19
19
|
* devframe:state:read → devframe_state_read
|
package/dist/cli/main.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/cli/main.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* The `devframe` bin
|
|
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-
|
|
2
|
-
import { t as diagnostics } from "../diagnostics-
|
|
3
|
-
import { n as listLiveDevframeInstances, r as probeDevframeOrigin } from "../instance-registry-
|
|
4
|
-
import { t as toAgentToolName } from "../agent-tool-name-
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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
|
});
|