devframe 0.6.0-beta.3 → 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.
@@ -0,0 +1,338 @@
1
+ import { a as diagnostics } from "./storage-yrUZaiib.mjs";
2
+ import { n as createHostContext } from "./host-h3-Dsf-Tgwx.mjs";
3
+ import { join } from "pathe";
4
+ import process from "node:process";
5
+ import { homedir } from "node:os";
6
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
7
+ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
8
+ import { toJsonSchema } from "@valibot/to-json-schema";
9
+ //#region src/adapters/mcp/stringify.ts
10
+ /**
11
+ * JSON-coercing serializer for MCP text payloads.
12
+ *
13
+ * MCP carries tool results and resource reads as plain text over a
14
+ * JSON-RPC transport, so we cannot use the `s:`-prefixed structured-clone
15
+ * format the WS RPC transport falls back to for non-JSON values. Instead,
16
+ * we coerce common non-JSON types into JSON-friendly forms so the LLM
17
+ * client sees something useful instead of `[object Object]`.
18
+ *
19
+ * Coercions:
20
+ * - `BigInt` → `"123n"`
21
+ * - `Date` → ISO string (via the native `toJSON`)
22
+ * - `Map` → `{ __type: 'Map', entries: [[k, v], …] }`
23
+ * - `Set` → `{ __type: 'Set', entries: [v, …] }`
24
+ * - `Error` → `{ name, message, stack, cause? }` (cause recurses)
25
+ * - `Function` → `"[Function: name]"`
26
+ * - `Symbol` → `value.toString()`
27
+ * - cycles → `"[Circular]"`
28
+ */
29
+ function stringifyForMcp(value) {
30
+ if (value === void 0) return "undefined";
31
+ if (typeof value === "string") return value;
32
+ const seen = /* @__PURE__ */ new WeakSet();
33
+ return JSON.stringify(value, (_key, val) => {
34
+ if (typeof val === "bigint") return `${val}n`;
35
+ if (val instanceof Error) {
36
+ const out = {
37
+ name: val.name,
38
+ message: val.message,
39
+ stack: val.stack
40
+ };
41
+ if (val.cause !== void 0) out.cause = val.cause;
42
+ return out;
43
+ }
44
+ if (val instanceof Map) return {
45
+ __type: "Map",
46
+ entries: [...val.entries()]
47
+ };
48
+ if (val instanceof Set) return {
49
+ __type: "Set",
50
+ entries: [...val]
51
+ };
52
+ if (typeof val === "function") return `[Function: ${val.name || "anonymous"}]`;
53
+ if (typeof val === "symbol") return val.toString();
54
+ if (val !== null && typeof val === "object") {
55
+ if (seen.has(val)) return "[Circular]";
56
+ seen.add(val);
57
+ }
58
+ return val;
59
+ }, 2);
60
+ }
61
+ /**
62
+ * Format a thrown value for an MCP `isError` text payload. Surfaces the
63
+ * `Error.name`/`message`, and one level of `cause.message` so context
64
+ * isn't dropped silently.
65
+ */
66
+ function formatMcpError(error) {
67
+ if (!(error instanceof Error)) return String(error);
68
+ const cause = error.cause;
69
+ const causeText = cause instanceof Error ? ` (cause: ${cause.message})` : cause !== void 0 ? ` (cause: ${String(cause)})` : "";
70
+ return `${error.name}: ${error.message}${causeText}`;
71
+ }
72
+ //#endregion
73
+ //#region src/adapters/mcp/to-json-schema.ts
74
+ const FALLBACK_OBJECT_SCHEMA = Object.freeze({
75
+ type: "object",
76
+ additionalProperties: true
77
+ });
78
+ /**
79
+ * Convert a valibot return schema to JSON Schema.
80
+ * @internal
81
+ */
82
+ function valibotReturnToJsonSchema(schema) {
83
+ if (!schema) return void 0;
84
+ try {
85
+ return toJsonSchema(schema);
86
+ } catch {
87
+ return FALLBACK_OBJECT_SCHEMA;
88
+ }
89
+ }
90
+ /**
91
+ * Convert positional RPC args schemas to a single MCP-friendly object
92
+ * schema. When the RPC declares `args: [v.object(...)]`, unwrap the
93
+ * single-object schema directly (nicer agent UX than `{ arg0: {...} }`).
94
+ *
95
+ * Returns `undefined` when there are no args (the MCP SDK treats this
96
+ * as `{ type: 'object', properties: {} }`).
97
+ * @internal
98
+ */
99
+ function valibotArgsToJsonSchema(args) {
100
+ if (!args || args.length === 0) return {
101
+ schema: {
102
+ type: "object",
103
+ properties: {}
104
+ },
105
+ unwrapped: false
106
+ };
107
+ if (args.length === 1) {
108
+ const inner = safeToJsonSchema(args[0]);
109
+ if (isObjectJsonSchema(inner)) return {
110
+ schema: inner,
111
+ unwrapped: true
112
+ };
113
+ }
114
+ const properties = {};
115
+ const required = [];
116
+ for (let i = 0; i < args.length; i++) {
117
+ const key = `arg${i}`;
118
+ properties[key] = safeToJsonSchema(args[i]);
119
+ required.push(key);
120
+ }
121
+ return {
122
+ schema: {
123
+ type: "object",
124
+ properties,
125
+ required,
126
+ additionalProperties: false
127
+ },
128
+ unwrapped: false
129
+ };
130
+ }
131
+ function safeToJsonSchema(schema) {
132
+ try {
133
+ return toJsonSchema(schema);
134
+ } catch {
135
+ return FALLBACK_OBJECT_SCHEMA;
136
+ }
137
+ }
138
+ function isObjectJsonSchema(value) {
139
+ return !!value && typeof value === "object" && value.type === "object";
140
+ }
141
+ //#endregion
142
+ //#region src/adapters/mcp/build-server.ts
143
+ /**
144
+ * Wire an MCP {@link Server} to a devframe context. Returns the server
145
+ * plus a disposal function for the subscriptions it sets up. The
146
+ * transport is the caller's responsibility — `createMcpServer` connects
147
+ * stdio; tests can connect an {@link InMemoryTransport} instead.
148
+ *
149
+ * @internal
150
+ */
151
+ function buildMcpServerFromContext(ctx, options) {
152
+ const server = new Server({
153
+ name: options.serverName,
154
+ version: options.serverVersion
155
+ }, { capabilities: {
156
+ tools: { listChanged: true },
157
+ resources: { listChanged: true }
158
+ } });
159
+ registerToolHandlers(server, ctx);
160
+ registerResourceHandlers(server, ctx, options.exposeSharedState);
161
+ const notify = (method) => {
162
+ server.notification({ method }).catch(() => {});
163
+ };
164
+ const offManifest = ctx.agent.events.on("agent:manifest:changed", () => {
165
+ notify("notifications/tools/list_changed");
166
+ notify("notifications/resources/list_changed");
167
+ });
168
+ const offKeyAdded = ctx.rpc.sharedState.onKeyAdded(() => {
169
+ notify("notifications/resources/list_changed");
170
+ });
171
+ return {
172
+ server,
173
+ dispose: () => {
174
+ offManifest();
175
+ offKeyAdded();
176
+ }
177
+ };
178
+ }
179
+ /**
180
+ * Build an MCP server over the agent surface of a devframe definition.
181
+ * Currently supports `stdio` transport only.
182
+ *
183
+ * @experimental The agent-native surface is experimental and may change
184
+ * without a major version bump until it stabilizes.
185
+ */
186
+ async function createMcpServer(definition, options = {}) {
187
+ const transport = options.transport ?? "stdio";
188
+ if (transport !== "stdio") throw diagnostics.DF0017({
189
+ transport,
190
+ reason: "Only stdio transport is supported in this release."
191
+ });
192
+ const ctx = await createHostContext({
193
+ cwd: process.cwd(),
194
+ mode: "dev",
195
+ host: {
196
+ mountStatic: () => {},
197
+ resolveOrigin: () => "mcp://devframe",
198
+ getStorageDir: (scope) => scope === "workspace" ? join(process.cwd(), `node_modules/.${definition.id}/devframe`) : join(homedir(), `.${definition.id}/devframe`)
199
+ }
200
+ });
201
+ await definition.setup(ctx);
202
+ const { server, dispose } = buildMcpServerFromContext(ctx, {
203
+ serverName: options.serverName ?? `${definition.id} (devframe)`,
204
+ serverVersion: options.serverVersion ?? definition.version ?? "0.0.0",
205
+ exposeSharedState: options.exposeSharedState ?? true
206
+ });
207
+ const { startStdioTransport } = await import("./transports-BmDVn4SF.mjs");
208
+ let stop;
209
+ try {
210
+ stop = await startStdioTransport(server);
211
+ } catch (error) {
212
+ const reason = error instanceof Error ? error.message : String(error);
213
+ throw diagnostics.DF0017({
214
+ transport,
215
+ reason,
216
+ cause: error
217
+ });
218
+ }
219
+ options.onReady?.({ transport: "stdio" });
220
+ return { async stop() {
221
+ dispose();
222
+ await stop();
223
+ } };
224
+ }
225
+ function registerToolHandlers(server, ctx) {
226
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
227
+ return { tools: ctx.agent.list().tools.map((tool) => projectTool(tool, ctx)) };
228
+ });
229
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
230
+ const { name, arguments: args } = request.params;
231
+ try {
232
+ return { content: [{
233
+ type: "text",
234
+ text: stringifyForMcp(await ctx.agent.invoke(name, args ?? {}))
235
+ }] };
236
+ } catch (error) {
237
+ return {
238
+ isError: true,
239
+ content: [{
240
+ type: "text",
241
+ text: `Error invoking "${name}": ${formatMcpError(error)}`
242
+ }]
243
+ };
244
+ }
245
+ });
246
+ }
247
+ function registerResourceHandlers(server, ctx, exposeSharedState) {
248
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
249
+ const resources = ctx.agent.list().resources.map((resource) => ({
250
+ uri: resource.uri,
251
+ name: resource.name,
252
+ description: resource.description,
253
+ mimeType: resource.mimeType
254
+ }));
255
+ if (exposeSharedState !== false) {
256
+ const filter = typeof exposeSharedState === "function" ? exposeSharedState : () => true;
257
+ for (const key of ctx.rpc.sharedState.keys()) {
258
+ if (!filter(key)) continue;
259
+ resources.push({
260
+ uri: `devframe://state/${encodeURIComponent(key)}`,
261
+ name: key,
262
+ description: `Shared state: ${key}`,
263
+ mimeType: "application/json"
264
+ });
265
+ }
266
+ }
267
+ return { resources };
268
+ });
269
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
270
+ const { uri } = request.params;
271
+ const parsed = parseResourceUri(uri);
272
+ if (parsed.kind === "resource") {
273
+ const content = await ctx.agent.read(parsed.id);
274
+ return { contents: [{
275
+ uri,
276
+ mimeType: content.mimeType ?? "application/json",
277
+ text: content.text ?? stringifyForMcp(content.json)
278
+ }] };
279
+ }
280
+ if (parsed.kind === "state") return { contents: [{
281
+ uri,
282
+ mimeType: "application/json",
283
+ text: stringifyForMcp((await ctx.rpc.sharedState.get(parsed.key)).value())
284
+ }] };
285
+ throw new Error(`[devframe/mcp] unknown resource URI "${uri}"`);
286
+ });
287
+ }
288
+ function projectTool(tool, ctx) {
289
+ const inputSchema = tool.inputSchema ?? computeInputSchema(tool, ctx);
290
+ const outputSchema = tool.outputSchema ?? computeOutputSchema(tool, ctx);
291
+ return {
292
+ name: tool.id,
293
+ title: tool.title,
294
+ description: tool.description,
295
+ inputSchema,
296
+ ...outputSchema ? { outputSchema } : {},
297
+ annotations: {
298
+ title: tool.title,
299
+ readOnlyHint: tool.safety === "read",
300
+ destructiveHint: tool.safety === "destructive"
301
+ }
302
+ };
303
+ }
304
+ function computeInputSchema(tool, ctx) {
305
+ if (tool.kind !== "rpc" || !tool.rpcName) return {
306
+ type: "object",
307
+ properties: {}
308
+ };
309
+ const def = ctx.rpc.definitions.get(tool.rpcName);
310
+ if (!def) return {
311
+ type: "object",
312
+ properties: {}
313
+ };
314
+ const args = def.args;
315
+ return valibotArgsToJsonSchema(args).schema;
316
+ }
317
+ function computeOutputSchema(tool, ctx) {
318
+ if (tool.kind !== "rpc" || !tool.rpcName) return void 0;
319
+ const def = ctx.rpc.definitions.get(tool.rpcName);
320
+ if (!def) return void 0;
321
+ return valibotReturnToJsonSchema(def.returns);
322
+ }
323
+ function parseResourceUri(uri) {
324
+ const match = uri.match(/^devframe:\/\/(resource|state)\/(.+)$/);
325
+ if (!match) return { kind: "unknown" };
326
+ const [, kind, rest] = match;
327
+ const decoded = decodeURIComponent(rest);
328
+ if (kind === "resource") return {
329
+ kind: "resource",
330
+ id: decoded
331
+ };
332
+ return {
333
+ kind: "state",
334
+ key: decoded
335
+ };
336
+ }
337
+ //#endregion
338
+ export { createMcpServer as n, buildMcpServerFromContext as t };
@@ -1,4 +1,4 @@
1
- import { $ as ScopedServerFunctions, D as StreamReader, J as DevframeSettings, L as SharedState, O as StreamSink, bt as EventEmitter, d as ConnectionMeta, et as ScopedSharedStates, it as DevframeRpcServerFunctions, rt as DevframeRpcClientFunctions, tt as SettingsForNamespace, v as RpcSharedStateGetOptions, y as RpcSharedStateHost } from "../devframe-DgvncQy2.mjs";
1
+ import { O as StreamReader, R as SharedState, Y as DevframeSettings, at as DevframeRpcServerFunctions, b as RpcSharedStateHost, et as ScopedServerFunctions, f as ConnectionMeta, it as DevframeRpcClientFunctions, k as StreamSink, nt as SettingsForNamespace, tt as ScopedSharedStates, xt as EventEmitter, y as RpcSharedStateGetOptions } from "../devframe-BtVHhmwM.mjs";
2
2
  import { _ as RpcFunctionDefinition, w as RpcFunctionsCollector } from "../types-DZEx4ffs.mjs";
3
3
  import { C as RpcCacheManager, w as RpcCacheOptions } from "../index-Dy6GFDRf.mjs";
4
4
  import { t as WsRpcChannelOptions } from "../ws-client-D0z0pOhn.mjs";
@@ -11,6 +11,13 @@ declare const DEVFRAME_CONNECTION_META_FILENAME = "__connection.json";
11
11
  * handler here without colliding with its own routes (HMR, asset serving).
12
12
  */
13
13
  declare const DEVFRAME_WS_ROUTE = "__devframe_ws";
14
+ /**
15
+ * Route the Streamable-HTTP MCP endpoint is bound to, relative to a
16
+ * devframe's base path. Sits next to `__connection.json` and the WS route
17
+ * so an MCP client reaches it on the same origin the SPA loaded from — the
18
+ * dev server shares one port for HTTP, WS, and MCP. Opt-in via `cli.mcp`.
19
+ */
20
+ declare const DEVFRAME_MCP_ROUTE = "__mcp";
14
21
  declare const DEVFRAME_RPC_DUMP_MANIFEST_FILENAME = "__rpc-dump/index.json";
15
22
  declare const DEVFRAME_DOCK_IMPORTS_FILENAME = "__client-imports.js";
16
23
  declare const DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID = "/__devframe-client-imports.js";
@@ -53,4 +60,4 @@ declare const ANONYMOUS_RPC_PREFIX = "anonymous:";
53
60
  */
54
61
  declare function isAnonymousRpcMethod(name: string): boolean;
55
62
  //#endregion
56
- export { ANONYMOUS_RPC_PREFIX, DEVFRAME_AUTH_TOKEN_QUERY_PARAM, DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DIRNAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID, DEVFRAME_MOUNT_PATH, DEVFRAME_MOUNT_PATH_NO_TRAILING_SLASH, DEVFRAME_OTP_URL_PARAM, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY, isAnonymousRpcMethod };
63
+ export { ANONYMOUS_RPC_PREFIX, DEVFRAME_AUTH_TOKEN_QUERY_PARAM, DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DIRNAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID, DEVFRAME_MCP_ROUTE, DEVFRAME_MOUNT_PATH, DEVFRAME_MOUNT_PATH_NO_TRAILING_SLASH, DEVFRAME_OTP_URL_PARAM, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY, isAnonymousRpcMethod };
@@ -11,6 +11,13 @@ const DEVFRAME_CONNECTION_META_FILENAME = "__connection.json";
11
11
  * handler here without colliding with its own routes (HMR, asset serving).
12
12
  */
13
13
  const DEVFRAME_WS_ROUTE = "__devframe_ws";
14
+ /**
15
+ * Route the Streamable-HTTP MCP endpoint is bound to, relative to a
16
+ * devframe's base path. Sits next to `__connection.json` and the WS route
17
+ * so an MCP client reaches it on the same origin the SPA loaded from — the
18
+ * dev server shares one port for HTTP, WS, and MCP. Opt-in via `cli.mcp`.
19
+ */
20
+ const DEVFRAME_MCP_ROUTE = "__mcp";
14
21
  const DEVFRAME_RPC_DUMP_MANIFEST_FILENAME = "__rpc-dump/index.json";
15
22
  const DEVFRAME_DOCK_IMPORTS_FILENAME = "__client-imports.js";
16
23
  const DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID = "/__devframe-client-imports.js";
@@ -55,4 +62,4 @@ function isAnonymousRpcMethod(name) {
55
62
  return name.startsWith(ANONYMOUS_RPC_PREFIX);
56
63
  }
57
64
  //#endregion
58
- export { ANONYMOUS_RPC_PREFIX, DEVFRAME_AUTH_TOKEN_QUERY_PARAM, DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DIRNAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID, DEVFRAME_MOUNT_PATH, DEVFRAME_MOUNT_PATH_NO_TRAILING_SLASH, DEVFRAME_OTP_URL_PARAM, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY, isAnonymousRpcMethod };
65
+ export { ANONYMOUS_RPC_PREFIX, DEVFRAME_AUTH_TOKEN_QUERY_PARAM, DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DIRNAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID, DEVFRAME_MCP_ROUTE, DEVFRAME_MOUNT_PATH, DEVFRAME_MOUNT_PATH_NO_TRAILING_SLASH, DEVFRAME_OTP_URL_PARAM, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY, isAnonymousRpcMethod };
@@ -1,4 +1,4 @@
1
- import { L as SharedState, m as DevframeNodeContext } from "./devframe-DgvncQy2.mjs";
1
+ import { R as SharedState, h as DevframeNodeContext } from "./devframe-BtVHhmwM.mjs";
2
2
 
3
3
  //#region src/node/hub-internals/context.d.ts
4
4
  interface InternalAnonymousAuthStorage {
@@ -1,6 +1,7 @@
1
1
  import { DEVFRAME_CONNECTION_META_FILENAME } from "./constants.mjs";
2
+ import { a as diagnostics } from "./storage-yrUZaiib.mjs";
2
3
  import { n as createHostContext, t as createH3DevframeHost } from "./host-h3-Dsf-Tgwx.mjs";
3
- import { t as startHttpAndWs } from "./server-BdN41ckC.mjs";
4
+ import { t as startHttpAndWs } from "./server-CAsvfJNK.mjs";
4
5
  import { n as resolveBasePath, t as normalizeBasePath } from "./_shared-bWRzeSa0.mjs";
5
6
  import { t as open } from "./open-Deb5xmIT.mjs";
6
7
  import { mountStaticHandler } from "./utils/serve-static.mjs";
@@ -284,14 +285,40 @@ async function createDevServer(def, options = {}) {
284
285
  });
285
286
  const setupInfo = { flags };
286
287
  await def.setup(ctx, setupInfo);
288
+ const mcpConfig = resolveMcpConfig(options.mcp ?? def.cli?.mcp);
289
+ let mcpDispose;
290
+ let mcpMeta;
291
+ if (mcpConfig) {
292
+ const mcpRoute = withoutLeadingSlash(mcpConfig.path ?? "__mcp");
293
+ const mcpPath = joinURL(basePath, mcpRoute);
294
+ let mountMcpHttp;
295
+ try {
296
+ ({mountMcpHttp} = await import("./http-COX960WK.mjs"));
297
+ } catch (error) {
298
+ const reason = error instanceof Error ? error.message : String(error);
299
+ throw diagnostics.DF0017({
300
+ transport: "http",
301
+ reason,
302
+ cause: error
303
+ });
304
+ }
305
+ mcpDispose = mountMcpHttp(app, ctx, mcpPath, {
306
+ serverName: `${def.id} (devframe)`,
307
+ serverVersion: def.version ?? "0.0.0",
308
+ exposeSharedState: true,
309
+ allowedOrigins: mcpConfig.allowedOrigins
310
+ }).dispose;
311
+ mcpMeta = { path: mcpRoute };
312
+ }
287
313
  const { bindPath, wsPort, meta } = resolveWsConnection(def, options, basePath);
288
314
  const connectionMetaPath = joinURL(basePath, DEVFRAME_CONNECTION_META_FILENAME);
289
315
  app.use(connectionMetaPath, () => ({
290
316
  backend: "websocket",
291
- websocket: meta
317
+ websocket: meta,
318
+ ...mcpMeta ? { mcp: mcpMeta } : {}
292
319
  }));
293
320
  if (distDir) mountStaticHandler(app, basePath, resolve(distDir));
294
- return startHttpAndWs({
321
+ const started = await startHttpAndWs({
295
322
  context: ctx,
296
323
  host,
297
324
  port,
@@ -304,6 +331,22 @@ async function createDevServer(def, options = {}) {
304
331
  await maybeOpenBrowser(def, flags, `${info.origin}${basePath}`, options.openBrowser);
305
332
  }
306
333
  });
334
+ if (mcpDispose) {
335
+ const closeServer = started.close;
336
+ started.close = async () => {
337
+ await mcpDispose();
338
+ await closeServer();
339
+ };
340
+ }
341
+ return started;
342
+ }
343
+ /**
344
+ * Normalize the `cli.mcp` / `mcp` option (`boolean | McpRouteOptions`) into
345
+ * concrete options, or `undefined` when the MCP route is disabled.
346
+ */
347
+ function resolveMcpConfig(mcp) {
348
+ if (!mcp) return void 0;
349
+ return mcp === true ? {} : mcp;
307
350
  }
308
351
  /**
309
352
  * Resolve the three WS connection scenarios from the definition / call-site
@@ -1220,6 +1220,16 @@ interface ConnectionMeta {
1220
1220
  * URL with its protocol swapped, or a path resolved same-origin.
1221
1221
  */
1222
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
+ };
1223
1233
  /**
1224
1234
  * Names of RPC functions that have declared `jsonSerializable: true`.
1225
1235
  * Used by the WS / static client to dispatch the per-call wire
@@ -1285,6 +1295,36 @@ interface DevframeWsOptions {
1285
1295
  */
1286
1296
  url?: string;
1287
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
+ }
1288
1328
  interface DevframeCliOptions {
1289
1329
  /** Binary name; default: the devframe's `id`. */
1290
1330
  command?: string;
@@ -1311,6 +1351,22 @@ interface DevframeCliOptions {
1311
1351
  * `devtools.clientAuth` today.
1312
1352
  */
1313
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;
1314
1370
  /** Author's SPA dist directory (served as the devframe's UI). */
1315
1371
  distDir?: string;
1316
1372
  /**
@@ -1422,4 +1478,4 @@ interface DevframeDefinition {
1422
1478
  }
1423
1479
  declare function defineDevframe(d: DevframeDefinition): DevframeDefinition;
1424
1480
  //#endregion
1425
- export { ScopedServerFunctions as $, createStreamReader as A, SharedStatePatch as B, BufferedChunk as C, CliFlagsSchema as Ct, StreamReader as D, StreamErrorPayload as E, parseCliFlags as Et, ImmutableObject as F, DevframeScopedNodeContext as G, EntriesToObject as H, ImmutableSet as I, DevframeSettings as J, DevframeScopedNodeRpc as K, SharedState as L, Immutable as M, ImmutableArray as N, StreamSink as O, ImmutableMap as P, ScopedClientFunctions as Q, SharedStateEvents as R, RpcStreamingHost as S, EventsMap as St, CreateStreamSinkOptions as T, defineCliFlags as Tt, PartialWithoutId as U, createSharedState as V, Thenable as W, DevframeSettingsStore as X, DevframeSettingsRegistry as Y, ScopedBroadcastOptions as Z, RpcFunctionsHost as _, AgentToolInput as _t, DevframeDuplicationStrategy as a, DevframeRpcSharedStates as at, RpcStreamingChannel as b, EventEmitter as bt, DevframeSpaOptions as c, DevframeDiagnosticsDefinition as ct, ConnectionMeta as d, AgentHandle as dt, ScopedSharedStates as et, ConnectionMetaWebsocket as f, AgentManifest as ft, RpcBroadcastOptions as g, AgentTool as gt, DevframeNodeRpcSession as h, AgentResourceInput as ht, DevframeDeploymentKind as i, DevframeRpcServerFunctions as it, createStreamSink as j, StreamSinkEvents as k, DevframeWsOptions as l, DevframeDiagnosticsHost as lt, DevframeNodeContext as m, AgentResourceContent as mt, DevframeCliOptions as n, DevframeViewHost as nt, DevframeRuntime as o, DevframeHost as ot, DevframeCapabilities as p, AgentResource as pt, DevframeScopedStreamingHost as q, DevframeDefinition as r, DevframeRpcClientFunctions as rt, DevframeSetupInfo as s, DevframeDefineDiagnosticsOptions as st, DevframeBrowserContext as t, SettingsForNamespace as tt, defineDevframe as u, DevframeDiagnosticsLogger as ut, RpcSharedStateGetOptions as v, DevframeAgentHost as vt, CreateStreamReaderOptions as w, InferCliFlags as wt, RpcStreamingChannelOptions as x, EventUnsubscribe as xt, RpcSharedStateHost as y, DevframeAgentHostEvents as yt, SharedStateOptions as z };
1481
+ export { ScopedClientFunctions as $, StreamSinkEvents as A, SharedStateOptions as B, RpcStreamingHost as C, EventsMap as Ct, StreamErrorPayload as D, parseCliFlags as Dt, CreateStreamSinkOptions as E, defineCliFlags as Et, ImmutableMap as F, Thenable as G, createSharedState as H, ImmutableObject as I, DevframeScopedStreamingHost as J, DevframeScopedNodeContext as K, ImmutableSet as L, createStreamSink as M, Immutable as N, StreamReader as O, ImmutableArray as P, ScopedBroadcastOptions as Q, SharedState as R, RpcStreamingChannelOptions as S, EventUnsubscribe as St, CreateStreamReaderOptions as T, InferCliFlags as Tt, EntriesToObject as U, SharedStatePatch as V, PartialWithoutId as W, DevframeSettingsRegistry as X, DevframeSettings as Y, DevframeSettingsStore as Z, RpcBroadcastOptions as _, AgentTool as _t, DevframeDuplicationStrategy as a, DevframeRpcServerFunctions as at, RpcSharedStateHost as b, DevframeAgentHostEvents as bt, DevframeSpaOptions as c, DevframeDefineDiagnosticsOptions as ct, defineDevframe as d, DevframeDiagnosticsLogger as dt, ScopedServerFunctions as et, ConnectionMeta as f, AgentHandle as ft, DevframeNodeRpcSession as g, AgentResourceInput as gt, DevframeNodeContext as h, AgentResourceContent as ht, DevframeDeploymentKind as i, DevframeRpcClientFunctions as it, createStreamReader as j, StreamSink as k, DevframeWsOptions as l, DevframeDiagnosticsDefinition as lt, DevframeCapabilities as m, AgentResource as mt, DevframeCliOptions as n, SettingsForNamespace as nt, DevframeRuntime as o, DevframeRpcSharedStates as ot, ConnectionMetaWebsocket as p, AgentManifest as pt, DevframeScopedNodeRpc as q, DevframeDefinition as r, DevframeViewHost as rt, DevframeSetupInfo as s, DevframeHost as st, DevframeBrowserContext as t, ScopedSharedStates as tt, McpRouteOptions as u, DevframeDiagnosticsHost as ut, RpcFunctionsHost as v, AgentToolInput as vt, BufferedChunk as w, CliFlagsSchema as wt, RpcStreamingChannel as x, EventEmitter as xt, RpcSharedStateGetOptions as y, DevframeAgentHost as yt, SharedStateEvents as z };
@@ -1,4 +1,4 @@
1
- import { r as DevframeDefinition } from "../devframe-DgvncQy2.mjs";
1
+ import { r as DevframeDefinition } from "../devframe-BtVHhmwM.mjs";
2
2
 
3
3
  //#region src/helpers/vite.d.ts
4
4
  interface ViteDevBridgeOptions {
@@ -2,7 +2,7 @@ import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_WS_ROUTE } from "../constan
2
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-DSxKFn-v.mjs";
5
+ import { n as resolveDevServerPort, t as createDevServer } from "../dev-zC5542OV.mjs";
6
6
  import { resolve } from "pathe";
7
7
  //#region src/helpers/vite.ts
8
8
  /**