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