devframe 0.9.7 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{_shared-BM3PdYli.mjs → _shared-C08SdQRl.mjs} +15 -4
- package/dist/{_shared-BJ6AmwTu.d.mts → _shared-CK3czkH4.d.mts} +25 -2
- package/dist/adapters/build.d.mts +6 -6
- package/dist/adapters/build.mjs +32 -22
- package/dist/adapters/cac.d.mts +16 -1
- package/dist/adapters/cac.mjs +8 -7
- package/dist/adapters/dev.d.mts +13 -13
- package/dist/adapters/dev.mjs +2 -2
- package/dist/adapters/embedded.d.mts +1 -1
- package/dist/adapters/initiate.d.mts +28 -26
- package/dist/adapters/initiate.mjs +1 -1
- package/dist/adapters/mcp.d.mts +28 -14
- package/dist/adapters/mcp.mjs +75 -39
- 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 +83 -51
- package/dist/client-assets-DNdhagGm.mjs +16 -0
- package/dist/constants.d.mts +8 -7
- package/dist/constants.mjs +7 -6
- package/dist/{context-BhBzOXVa.d.mts → context-4AIVRLYF.d.mts} +173 -122
- package/dist/{context-omlma_1n.mjs → context-CwPUinjn.mjs} +126 -88
- package/dist/{context-KUqAfawQ.mjs → context-DIUvJW4D.mjs} +3 -3
- package/dist/{context-HOnG-WvG.d.mts → context-rgBSPFHS.d.mts} +5 -5
- package/dist/{crypto-token-XCqTSMg9.mjs → crypto-token-TVbGGFuj.mjs} +1 -1
- package/dist/{dev-Czit5IP4.mjs → dev-Eqq5PX0k.mjs} +37 -18
- package/dist/{diagnostics-BXWW3VK-.mjs → diagnostics-BMfN8mb1.mjs} +30 -17
- package/dist/{diagnostics-CD8nlgll.mjs → diagnostics-D0EVm5M6.mjs} +2 -2
- package/dist/{events-4daw0uK9.mjs → events-BV4Dj59a.mjs} +2 -2
- package/dist/{host-h3-fRbF9yor.mjs → host-h3-BJ4siyz7.mjs} +1 -1
- package/dist/in-page-channel/index.d.mts +59 -22
- package/dist/in-page-channel/index.mjs +71 -56
- package/dist/{index-BzyopqxV.d.mts → index-pIVt92jP.d.mts} +9 -9
- package/dist/{index-DcKxIMoI.d.mts → index-pKl67H6w.d.mts} +4 -4
- package/dist/index.d.mts +11 -13
- package/dist/index.mjs +6 -7
- package/dist/{instance-registry-BjlMQbFB.mjs → instance-registry-C7-41RSp.mjs} +8 -8
- package/dist/{instance-shell-Bh4l9Vz_.d.mts → instance-shell-7Z52PE4_.d.mts} +18 -18
- package/dist/{instance-shell-CpVW4zG0.mjs → instance-shell-cdVETpfz.mjs} +227 -58
- package/dist/internal/index.d.mts +51 -21
- package/dist/internal/index.mjs +10 -9
- 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-BIFWOlBW.mjs} +26 -6
- 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 +2 -11
- package/dist/rpc/dump.mjs +1 -1
- package/dist/rpc/index.d.mts +2 -2
- package/dist/rpc/index.mjs +3 -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-BHUsDl7s.d.mts → rpc-core-Ceu6AUz8.d.mts} +4 -4
- package/dist/{rpc-core-DlHd276T.mjs → rpc-core-DVQ7xi89.mjs} +14 -2
- package/dist/{serialization-BGzEwAdr.mjs → serialization-DTZnPQ1-.mjs} +20 -18
- package/dist/{services-install-CqqQVls5.mjs → services-install-P_DV7VYL.mjs} +32 -21
- package/dist/session-DPfj6zQG.mjs +16 -0
- package/dist/{shared-state-DoXutg_U.mjs → shared-state-CaxB1q0h.mjs} +4 -3
- 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-CeQQWzM-.mjs} +4 -4
- package/dist/{static-DV_qUSRS.mjs → static-DZ8P2Kn7.mjs} +130 -135
- package/dist/{storage-BXNVhKyR.mjs → storage-Dccf_IyD.mjs} +1 -1
- 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-BOy8IoFZ.mjs} +2 -2
- package/dist/{wire-codec-0K-o5MYW.mjs → wire-codec-dcCeXcSo.mjs} +3 -3
- package/dist/{ws-client-7_lZi-B9.d.mts → ws-client-Xpxt0oPA.d.mts} +3 -3
- package/dist/{ws-server-Di2oqr4q.d.mts → ws-server-D2nv68Hg.d.mts} +13 -22
- package/package.json +6 -5
- package/dist/ws-server-BdSLrhxE.mjs +0 -267
package/dist/adapters/mcp.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
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
|
|
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-BMfN8mb1.mjs";
|
|
6
|
+
import { t as createHostContext } from "../context-CwPUinjn.mjs";
|
|
7
|
+
import { t as toAgentToolName } from "../agent-tool-name-DGzeuWcO.mjs";
|
|
7
8
|
import process from "node:process";
|
|
8
9
|
import { join } from "pathe";
|
|
9
10
|
import { homedir } from "node:os";
|
|
@@ -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-BMfN8mb1.mjs";
|
|
3
|
+
import { n as listLiveDevframeInstances, r as probeDevframeOrigin } from "../instance-registry-C7-41RSp.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
|
});
|
package/dist/client/index.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { f as RpcCacheManager, p as RpcCacheOptions } from "../index-
|
|
2
|
-
import { _ as RpcFunctionDefinition, w as RpcFunctionsCollector } from "../types-
|
|
3
|
-
import {
|
|
4
|
-
import { t as SseRpcChannelOptions } from "../sse-client-
|
|
5
|
-
import { t as WsRpcChannelOptions } from "../ws-client-
|
|
1
|
+
import { f as RpcCacheManager, p as RpcCacheOptions } from "../index-pIVt92jP.mjs";
|
|
2
|
+
import { _ as RpcFunctionDefinition, w as RpcFunctionsCollector } from "../types-Dq4i156u.mjs";
|
|
3
|
+
import { $ as DevframeServiceScopeOf, Bt as EventEmitter, Z as DevframeServiceMeta, _t as DevframeRpcClientFunctions, b as StreamSink, d as RpcSharedStateHost, ft as ScopedRpcFn, ht as SettingsForNamespace, k as SharedState, mt as ScopedSharedStates, pt as ScopedServerFunctions, rt as DevframeServicesState, st as DevframeSettings, t as ConnectionMeta, u as RpcSharedStateGetOptions, vt as DevframeRpcServerFunctions, y as StreamReader } from "../context-4AIVRLYF.mjs";
|
|
4
|
+
import { t as SseRpcChannelOptions } from "../sse-client-BQ4jS3eO.mjs";
|
|
5
|
+
import { t as WsRpcChannelOptions } from "../ws-client-Xpxt0oPA.mjs";
|
|
6
6
|
import { BirpcOptions, BirpcReturn } from "birpc";
|
|
7
7
|
//#region src/client/connection.d.ts
|
|
8
8
|
interface DevframeConnection {
|
|
@@ -50,15 +50,15 @@ declare function setupDevframeConnection(options?: SetupDevframeConnectionOption
|
|
|
50
50
|
* trust handshake, so a viewer never has to reason about the two dimensions
|
|
51
51
|
* separately.
|
|
52
52
|
*
|
|
53
|
-
* - `connecting
|
|
53
|
+
* - `connecting`: establishing the WebSocket / running the initial trust
|
|
54
54
|
* handshake. Calls issued here queue until the socket opens.
|
|
55
|
-
* - `connected
|
|
56
|
-
* - `unauthorized
|
|
55
|
+
* - `connected`: socket open and trusted; RPC calls will be served.
|
|
56
|
+
* - `unauthorized`: socket open but the server rejected trust (no valid token,
|
|
57
57
|
* or an auth-enforcing host refused it). Calls fail fast with an auth error;
|
|
58
58
|
* the UI should prompt for re-authentication or a reload.
|
|
59
|
-
* - `disconnected
|
|
59
|
+
* - `disconnected`: the socket closed (dropped mid-session, or never opened).
|
|
60
60
|
* Pending and new calls fail fast until the page reconnects.
|
|
61
|
-
* - `error
|
|
61
|
+
* - `error`: a fatal connection error (e.g. the WebSocket errored, or the
|
|
62
62
|
* connection meta could not be loaded).
|
|
63
63
|
*
|
|
64
64
|
* A `static` backend has no live socket, so it reports `connected` for its
|
|
@@ -67,9 +67,9 @@ declare function setupDevframeConnection(options?: SetupDevframeConnectionOption
|
|
|
67
67
|
type DevframeConnectionStatus = 'connecting' | 'connected' | 'unauthorized' | 'disconnected' | 'error';
|
|
68
68
|
/**
|
|
69
69
|
* What kind of failure a {@link DevframeConnectionError} describes:
|
|
70
|
-
* - `connection
|
|
71
|
-
* - `auth
|
|
72
|
-
* - `timeout
|
|
70
|
+
* - `connection`: the transport dropped, errored, or never opened.
|
|
71
|
+
* - `auth`: the server rejected trust for this client.
|
|
72
|
+
* - `timeout`: a call exceeded its {@link DevframeRpcClientOptions.callTimeout}.
|
|
73
73
|
*/
|
|
74
74
|
type DevframeConnectionErrorKind = 'connection' | 'auth' | 'timeout';
|
|
75
75
|
/**
|
|
@@ -199,7 +199,7 @@ declare function createScopedClientContext<NS extends string = string>(rpc: Devf
|
|
|
199
199
|
//#endregion
|
|
200
200
|
//#region src/client/rpc-services.d.ts
|
|
201
201
|
/**
|
|
202
|
-
* A typed handle on one advertised wire service
|
|
202
|
+
* A typed handle on one advertised wire service: the service's
|
|
203
203
|
* advertisement meta plus an RPC surface scoped to its namespace, so
|
|
204
204
|
* `handle.rpc.call('fn-name', …)` targets `<scope>:fn-name`. Service
|
|
205
205
|
* packages type the calls by augmenting `DevframeRpcServerFunctions` with
|
|
@@ -214,7 +214,7 @@ interface DevframeServiceClientHandle<NS extends string = string> extends Devfra
|
|
|
214
214
|
/**
|
|
215
215
|
* Client-side view of the server's wire-service registry, mirrored through
|
|
216
216
|
* the reactive `devframe:services` shared state. The accessors are
|
|
217
|
-
* synchronous snapshots
|
|
217
|
+
* synchronous snapshots; before the first sync lands (or on a server with
|
|
218
218
|
* no services) they read as empty. For reactive UI (e.g. hiding an
|
|
219
219
|
* "open in editor" button until the service appears), subscribe to the
|
|
220
220
|
* shared state itself via {@link DevframeServicesClient.state}.
|
|
@@ -223,14 +223,14 @@ interface DevframeServicesClient {
|
|
|
223
223
|
/** Whether the service package is advertised as installed. */
|
|
224
224
|
has: (pkg: string) => boolean;
|
|
225
225
|
/**
|
|
226
|
-
* A typed handle on an advertised service
|
|
227
|
-
* surface
|
|
226
|
+
* A typed handle on an advertised service (its meta plus a scoped RPC
|
|
227
|
+
* surface), or `undefined` while it isn't available (never throws).
|
|
228
228
|
*/
|
|
229
229
|
get: <PKG extends string>(pkg: PKG) => DevframeServiceClientHandle<DevframeServiceScopeOf<PKG>> | undefined;
|
|
230
230
|
/** Package names of every advertised service. */
|
|
231
231
|
keys: () => string[];
|
|
232
232
|
/**
|
|
233
|
-
* The mirrored `devframe:services` shared state
|
|
233
|
+
* The mirrored `devframe:services` shared state; subscribe to its
|
|
234
234
|
* `updated` event for reactivity.
|
|
235
235
|
*/
|
|
236
236
|
state: () => Promise<SharedState<DevframeServicesState>>;
|
|
@@ -257,7 +257,7 @@ interface RpcClientEvents {
|
|
|
257
257
|
*/
|
|
258
258
|
'connection:error': (error: Error) => void;
|
|
259
259
|
/**
|
|
260
|
-
* An RPC call rejected
|
|
260
|
+
* An RPC call rejected, either from the server, or because the connection
|
|
261
261
|
* was down / timed out. Useful for a global error feed or toast surface.
|
|
262
262
|
*/
|
|
263
263
|
'rpc:error': (error: Error, method: string) => void;
|
|
@@ -271,7 +271,7 @@ interface DevframeRpcClientOptions extends SetupDevframeConnectionOptions {
|
|
|
271
271
|
* Query-param name on the page URL carrying a one-time authentication code
|
|
272
272
|
* (OTP) for "magic link" auth (e.g. a link the dev server prints). When
|
|
273
273
|
* present, the client exchanges the code for a token and removes the parameter
|
|
274
|
-
* from the URL. Set `false` to disable
|
|
274
|
+
* from the URL. Set `false` to disable, e.g. integrations that drive their
|
|
275
275
|
* own authentication via `authenticateWithUrlOtp`.
|
|
276
276
|
*
|
|
277
277
|
* @default 'devframe_otp'
|
|
@@ -281,7 +281,7 @@ interface DevframeRpcClientOptions extends SetupDevframeConnectionOptions {
|
|
|
281
281
|
* Fall back to a native browser `prompt()` for the one-time authentication
|
|
282
282
|
* code when the server refuses trust and no other credential succeeds (a
|
|
283
283
|
* stored token, an injected token, or a magic-link OTP). The prompt fires
|
|
284
|
-
* only on a **top-level, unframed** page
|
|
284
|
+
* only on a **top-level, unframed** page; a framed plugin (e.g. mounted in
|
|
285
285
|
* a hub dock) never prompts, since a hub pre-authorizes it and browsers
|
|
286
286
|
* block `prompt()` in cross-origin frames anyway.
|
|
287
287
|
*
|
|
@@ -294,20 +294,20 @@ interface DevframeRpcClientOptions extends SetupDevframeConnectionOptions {
|
|
|
294
294
|
/**
|
|
295
295
|
* Which live transport to connect over:
|
|
296
296
|
*
|
|
297
|
-
* - `'auto'` (default)
|
|
297
|
+
* - `'auto'` (default) trusts the server's advertisement: its declared
|
|
298
298
|
* primary (`backend`), preferring the WebSocket when both endpoints are
|
|
299
299
|
* present. A server that couldn't bind a socket advertises SSE as its
|
|
300
300
|
* primary, so no client-side fallback probing is needed.
|
|
301
|
-
* - `'websocket'` / `'sse'`
|
|
301
|
+
* - `'websocket'` / `'sse'` pins one transport; connecting fails with a
|
|
302
302
|
* clear error when the server doesn't advertise it. Reach for
|
|
303
|
-
* `transport: 'sse'` when an intermediary silently strips WS upgrades
|
|
303
|
+
* `transport: 'sse'` when an intermediary silently strips WS upgrades,
|
|
304
304
|
* something the server cannot detect.
|
|
305
305
|
*
|
|
306
306
|
* A `static` backend ignores this option (there is no live transport).
|
|
307
307
|
*/
|
|
308
308
|
transport?: 'auto' | 'websocket' | 'sse';
|
|
309
309
|
wsOptions?: Partial<WsRpcChannelOptions>;
|
|
310
|
-
/** Channel overrides for the SSE transport
|
|
310
|
+
/** Channel overrides for the SSE transport, the `wsOptions` counterpart. */
|
|
311
311
|
sseOptions?: Partial<SseRpcChannelOptions>;
|
|
312
312
|
rpcOptions?: Partial<BirpcOptions<DevframeRpcServerFunctions, DevframeRpcClientFunctions, boolean>>;
|
|
313
313
|
cacheOptions?: boolean | Partial<RpcCacheOptions>;
|
|
@@ -315,8 +315,8 @@ interface DevframeRpcClientOptions extends SetupDevframeConnectionOptions {
|
|
|
315
315
|
* Reject a pending `rpc.call(...)` if the server hasn't answered within this
|
|
316
316
|
* many milliseconds, with a {@link DevframeConnectionError} of kind
|
|
317
317
|
* `'timeout'`. Guards against a live-but-unresponsive server hanging the UI.
|
|
318
|
-
* Omit (or `0`) to wait indefinitely. Calls always fail fast
|
|
319
|
-
* this option
|
|
318
|
+
* Omit (or `0`) to wait indefinitely. Calls always fail fast, regardless of
|
|
319
|
+
* this option, once the socket closes or trust is refused.
|
|
320
320
|
*/
|
|
321
321
|
callTimeout?: number;
|
|
322
322
|
}
|
|
@@ -344,7 +344,7 @@ interface DevframeRpcClient {
|
|
|
344
344
|
*/
|
|
345
345
|
readonly connectionError: Error | null;
|
|
346
346
|
/**
|
|
347
|
-
* The transport this client is actually connected over
|
|
347
|
+
* The transport this client is actually connected over: `'websocket'`,
|
|
348
348
|
* `'sse'`, or `'static'`. Reflects the resolution of the `transport`
|
|
349
349
|
* option against the server's advertisement.
|
|
350
350
|
*/
|
|
@@ -404,7 +404,7 @@ interface DevframeRpcClient {
|
|
|
404
404
|
sharedState: RpcSharedStateHost;
|
|
405
405
|
/**
|
|
406
406
|
* The server's advertised wire services (mirrored `devframe:services`
|
|
407
|
-
* shared state)
|
|
407
|
+
* shared state); feature-detect a capability with
|
|
408
408
|
* `rpc.services.has('@devframes/service-x')` and get a scoped, typed RPC
|
|
409
409
|
* handle with `rpc.services.get(...)`. See {@link DevframeServicesClient}.
|
|
410
410
|
*/
|
|
@@ -437,11 +437,11 @@ interface DevframeRpcClient {
|
|
|
437
437
|
* a `websocket` backend closes the underlying `WebSocket`, which the server observes as a
|
|
438
438
|
* normal disconnect. Mirrors {@link WsRpcTransport.close} on the server side.
|
|
439
439
|
*
|
|
440
|
-
* There is no corresponding "reconnect"
|
|
440
|
+
* There is no corresponding "reconnect"; a closed client is done. Discard it and call
|
|
441
441
|
* {@link getDevframeRpcClient} again to reconnect.
|
|
442
442
|
*
|
|
443
|
-
* Optional so a `DevframeRpcClientMode` implemented before this method existed
|
|
444
|
-
* transport, a hand-typed mock
|
|
443
|
+
* Optional so a `DevframeRpcClientMode` implemented before this method existed (a custom
|
|
444
|
+
* transport, a hand-typed mock) still satisfies the interface; an absent `close` is treated
|
|
445
445
|
* as nothing to close.
|
|
446
446
|
*/
|
|
447
447
|
close?: () => void;
|
|
@@ -449,7 +449,7 @@ interface DevframeRpcClient {
|
|
|
449
449
|
interface DevframeRpcClientMode {
|
|
450
450
|
/**
|
|
451
451
|
* The transport this mode speaks. Optional so a mode implemented before
|
|
452
|
-
* this field existed
|
|
452
|
+
* this field existed (a custom transport, a hand-typed mock) still
|
|
453
453
|
* satisfies the interface; an absent value reads as `'websocket'`.
|
|
454
454
|
*/
|
|
455
455
|
readonly transport?: 'websocket' | 'sse' | 'static';
|
|
@@ -472,7 +472,7 @@ interface DevframeRpcClientMode {
|
|
|
472
472
|
}
|
|
473
473
|
/**
|
|
474
474
|
* Resolve the requested `transport` option against what the server
|
|
475
|
-
* advertises. `'auto'` trusts the advertisement
|
|
475
|
+
* advertises. `'auto'` trusts the advertisement: the server's declared
|
|
476
476
|
* primary (`backend`), preferring the WebSocket when both endpoints are
|
|
477
477
|
* present; an explicit `'websocket'` / `'sse'` pins that transport and
|
|
478
478
|
* throws when the server doesn't advertise it.
|
|
@@ -502,7 +502,7 @@ declare function consumeOtpFromUrl(param?: string): string | undefined;
|
|
|
502
502
|
*
|
|
503
503
|
* Higher-level integrations (e.g. Vite DevTools) that want to drive their own
|
|
504
504
|
* authentication UI can disable `connectDevframe`'s built-in handling with
|
|
505
|
-
* `otpParam: false` and call this
|
|
505
|
+
* `otpParam: false` and call this (or {@link consumeOtpFromUrl}) themselves.
|
|
506
506
|
*/
|
|
507
507
|
declare function authenticateWithUrlOtp(rpc: Pick<DevframeRpcClient, 'isTrusted' | 'requestTrustWithCode'>, options?: {
|
|
508
508
|
param?: string;
|
|
@@ -523,7 +523,7 @@ interface WsUrlLocation {
|
|
|
523
523
|
* The object / relative-path forms connect to the page's own origin (only the
|
|
524
524
|
* `http`→`ws` protocol swap is applied), resolving the path against where
|
|
525
525
|
* `__connection.json` was loaded. This is deliberately host-agnostic so the
|
|
526
|
-
* connection survives a reverse proxy that changes the domain or port
|
|
526
|
+
* connection survives a reverse proxy that changes the domain or port, since the
|
|
527
527
|
* client trusts its own location, never a server-baked hostname. An explicit
|
|
528
528
|
* `port`/`host` (or a full `ws(s)://` URL string) opts into a cross-origin
|
|
529
529
|
* endpoint, e.g. a side-car server on its own port.
|