devframe 0.7.16 → 0.8.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.
Files changed (73) hide show
  1. package/bin/devframe.mjs +8 -0
  2. package/dist/adapters/build.d.mts +1 -1
  3. package/dist/adapters/build.mjs +4 -4
  4. package/dist/adapters/cac.d.mts +1 -1
  5. package/dist/adapters/cac.mjs +1 -1
  6. package/dist/adapters/cli.d.mts +1 -1
  7. package/dist/adapters/cli.mjs +1 -1
  8. package/dist/adapters/dev.d.mts +2 -2
  9. package/dist/adapters/dev.mjs +1 -1
  10. package/dist/adapters/embedded.d.mts +1 -1
  11. package/dist/adapters/mcp.d.mts +45 -3
  12. package/dist/adapters/mcp.mjs +2 -2
  13. package/dist/agent-tool-name-C3b5vEwJ.mjs +34 -0
  14. package/dist/{cac-CPaOy3LD.mjs → cac-B-R_tDGJ.mjs} +18 -13
  15. package/dist/cli/main.d.mts +11 -0
  16. package/dist/cli/main.mjs +283 -0
  17. package/dist/client/index.d.mts +4 -4
  18. package/dist/client/index.mjs +88 -4
  19. package/dist/{context-CzqPrJSz.d.mts → context-7BbaIUSI.d.mts} +1 -1
  20. package/dist/{context-DY72brRI.mjs → context-C9Cgm1hP.mjs} +1 -1
  21. package/dist/{dev-BCeaQuGE.mjs → dev-D4M7Veo7.mjs} +22 -11
  22. package/dist/{devframe-Ckhf3kFw.d.mts → devframe-Dsjn_Xtq.d.mts} +68 -15
  23. package/dist/diagnostics-B5-qHeqD.mjs +94 -0
  24. package/dist/{diagnostics-BXwBQmoN.mjs → diagnostics-hwjXp_UV.mjs} +8 -0
  25. package/dist/{dump-Cz7yVvsB.mjs → dump-CgZShDRB.mjs} +82 -6
  26. package/dist/{build-server-C7vTKjiQ.mjs → fetch-BZyK4v6W.mjs} +225 -51
  27. package/dist/helpers/vite.d.mts +1 -1
  28. package/dist/helpers/vite.mjs +2 -2
  29. package/dist/{host-h3-CgOetlNE.mjs → host-h3-Kz7t5Xab.mjs} +83 -28
  30. package/dist/http-BghOqfTD.mjs +38 -0
  31. package/dist/{index-8dndvIxR.d.mts → index-CeBbry0R.d.mts} +2 -2
  32. package/dist/{index-DgsLFhZg.d.mts → index-Dbw1p5ch.d.mts} +27 -3
  33. package/dist/{index-YDSJgsyG.d.mts → index-vAX6bYuc.d.mts} +1 -1
  34. package/dist/index.d.mts +5 -5
  35. package/dist/instance-registry-D6fxYu36.mjs +190 -0
  36. package/dist/node/auth.d.mts +2 -2
  37. package/dist/node/hub-internals.d.mts +2 -2
  38. package/dist/node/hub-internals.mjs +1 -1
  39. package/dist/node/index.d.mts +104 -6
  40. package/dist/node/index.mjs +5 -4
  41. package/dist/recipes/common-rpc-functions.d.mts +33 -33
  42. package/dist/recipes/common-rpc-functions.mjs +6 -6
  43. package/dist/recipes/interactive-auth.d.mts +2 -2
  44. package/dist/recipes/interactive-auth.mjs +13 -13
  45. package/dist/rpc/dump.d.mts +1 -1
  46. package/dist/rpc/dump.mjs +1 -1
  47. package/dist/rpc/index.d.mts +3 -3
  48. package/dist/rpc/index.mjs +4 -4
  49. package/dist/rpc/transports/ws-client.d.mts +1 -1
  50. package/dist/rpc/transports/ws-client.mjs +1 -1
  51. package/dist/rpc/transports/ws-server.d.mts +1 -1
  52. package/dist/rpc/transports/ws-server.mjs +1 -1
  53. package/dist/{serialization-DpLXCy13.mjs → serialization-C8Mnw9hK.mjs} +1 -1
  54. package/dist/{server-DykHCza_.mjs → server-CcAPxuoT.mjs} +2 -2
  55. package/dist/{server-B0wiP_Oi.d.mts → server-VLQJouOO.d.mts} +2 -2
  56. package/dist/simple-schema-BDzLeJDk.d.mts +102 -0
  57. package/dist/simple-schema-DQPZrAaZ.mjs +154 -0
  58. package/dist/{storage-gEnj9ASQ.mjs → storage-Dzoc3NVC.mjs} +2 -66
  59. package/dist/{transports-BmDVn4SF.mjs → transports-vhizgqXM.mjs} +1 -1
  60. package/dist/types/index.d.mts +4 -4
  61. package/dist/{types-CrzNxXKq.d.mts → types-CnJSgRVa.d.mts} +33 -19
  62. package/dist/utils/agent-tool-name.d.mts +27 -0
  63. package/dist/utils/agent-tool-name.mjs +34 -0
  64. package/dist/utils/events.d.mts +1 -1
  65. package/dist/utils/shared-state.d.mts +1 -1
  66. package/dist/utils/simple-schema.d.mts +2 -0
  67. package/dist/utils/simple-schema.mjs +154 -0
  68. package/dist/utils/streaming-channel.d.mts +1 -1
  69. package/dist/{ws-client-DSXiI2h7.d.mts → ws-client-B6Tnr0ic.d.mts} +1 -1
  70. package/dist/{ws-server-9-wn7MNQ.d.mts → ws-server-D_Vjtums.d.mts} +1 -1
  71. package/package.json +18 -7
  72. package/skills/devframe/SKILL.md +1 -1
  73. package/dist/http-_pPbryTQ.mjs +0 -114
@@ -1,12 +1,13 @@
1
- import { a as diagnostics } from "./storage-gEnj9ASQ.mjs";
2
- import { n as createHostContext } from "./host-h3-CgOetlNE.mjs";
1
+ import { isAllowedOrigin } from "./rpc/transports/ws-server.mjs";
2
+ import { n as createHostContext } from "./host-h3-Kz7t5Xab.mjs";
3
+ import { t as diagnostics } from "./diagnostics-B5-qHeqD.mjs";
4
+ import { t as toAgentToolName } from "./agent-tool-name-C3b5vEwJ.mjs";
5
+ import { randomUUID } from "node:crypto";
3
6
  import { Diagnostic } from "nostics";
4
7
  import { join } from "pathe";
5
8
  import process from "node:process";
6
9
  import { homedir } from "node:os";
7
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8
- import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
9
- import { toJsonSchema } from "@valibot/to-json-schema";
10
+ import { Server, WebStandardStreamableHTTPServerTransport, isInitializeRequest } from "@modelcontextprotocol/server";
10
11
  //#region src/adapters/mcp/stringify.ts
11
12
  /**
12
13
  * JSON-coercing serializer for MCP text payloads.
@@ -87,27 +88,41 @@ const FALLBACK_OBJECT_SCHEMA = Object.freeze({
87
88
  additionalProperties: true
88
89
  });
89
90
  /**
90
- * Convert a valibot return schema to JSON Schema.
91
- * @internal
91
+ * Convert a Standard Schema to JSON Schema for the agent/MCP surface.
92
+ *
93
+ * Devframe stays validator-neutral, so conversion uses the schema's own
94
+ * [Standard JSON Schema](https://standardschema.dev/) converter
95
+ * (`~standard.jsonSchema`) when the validator provides one — zod 4 does,
96
+ * for example. Validators without a native converter (e.g. valibot) degrade
97
+ * to a permissive object schema rather than pulling in a converter library.
92
98
  */
93
- function valibotReturnToJsonSchema(schema) {
94
- if (!schema) return void 0;
95
- try {
96
- return toJsonSchema(schema);
99
+ function safeToJsonSchema(schema) {
100
+ const standard = schema["~standard"];
101
+ if (standard.jsonSchema) try {
102
+ return standard.jsonSchema.input({ target: "draft-2020-12" });
97
103
  } catch {
98
104
  return FALLBACK_OBJECT_SCHEMA;
99
105
  }
106
+ return FALLBACK_OBJECT_SCHEMA;
100
107
  }
101
108
  /**
102
- * Convert positional RPC args schemas to a single MCP-friendly object
103
- * schema. When the RPC declares `args: [v.object(...)]`, unwrap the
104
- * single-object schema directly (nicer agent UX than `{ arg0: {...} }`).
109
+ * JSON Schema for an RPC return value on the agent/MCP surface.
110
+ * @internal
111
+ */
112
+ function returnToJsonSchema(schema) {
113
+ if (!schema) return void 0;
114
+ return safeToJsonSchema(schema);
115
+ }
116
+ /**
117
+ * JSON Schema for an RPC function's positional args on the agent/MCP
118
+ * surface. Each positional arg is advertised under `arg0` / `arg1` / … —
119
+ * matching how the agent bridge coerces the incoming object payload back
120
+ * into positional arguments.
105
121
  *
106
- * Returns `undefined` when there are no args (the MCP SDK treats this
107
- * as `{ type: 'object', properties: {} }`).
122
+ * Returns `{ type: 'object', properties: {} }` when there are no args.
108
123
  * @internal
109
124
  */
110
- function valibotArgsToJsonSchema(args) {
125
+ function argsToJsonSchema(args) {
111
126
  if (!args || args.length === 0) return {
112
127
  schema: {
113
128
  type: "object",
@@ -115,13 +130,6 @@ function valibotArgsToJsonSchema(args) {
115
130
  },
116
131
  unwrapped: false
117
132
  };
118
- if (args.length === 1) {
119
- const inner = safeToJsonSchema(args[0]);
120
- if (isObjectJsonSchema(inner)) return {
121
- schema: inner,
122
- unwrapped: true
123
- };
124
- }
125
133
  const properties = {};
126
134
  const required = [];
127
135
  for (let i = 0; i < args.length; i++) {
@@ -139,16 +147,6 @@ function valibotArgsToJsonSchema(args) {
139
147
  unwrapped: false
140
148
  };
141
149
  }
142
- function safeToJsonSchema(schema) {
143
- try {
144
- return toJsonSchema(schema);
145
- } catch {
146
- return FALLBACK_OBJECT_SCHEMA;
147
- }
148
- }
149
- function isObjectJsonSchema(value) {
150
- return !!value && typeof value === "object" && value.type === "object";
151
- }
152
150
  //#endregion
153
151
  //#region src/adapters/mcp/build-server.ts
154
152
  /**
@@ -167,7 +165,7 @@ function buildMcpServerFromContext(ctx, options) {
167
165
  tools: { listChanged: true },
168
166
  resources: { listChanged: true }
169
167
  } });
170
- registerToolHandlers(server, ctx);
168
+ registerToolHandlers(server, ctx, options.exposeSharedState);
171
169
  registerResourceHandlers(server, ctx, options.exposeSharedState);
172
170
  const notify = (method) => {
173
171
  server.notification({ method }).catch(() => {});
@@ -219,7 +217,7 @@ async function createMcpServer(definition, options = {}) {
219
217
  serverVersion: options.serverVersion ?? definition.version ?? "0.0.0",
220
218
  exposeSharedState: options.exposeSharedState ?? true
221
219
  });
222
- const { startStdioTransport } = await import("./transports-BmDVn4SF.mjs");
220
+ const { startStdioTransport } = await import("./transports-vhizgqXM.mjs");
223
221
  let stop;
224
222
  try {
225
223
  stop = await startStdioTransport(server);
@@ -237,16 +235,98 @@ async function createMcpServer(definition, options = {}) {
237
235
  await stop();
238
236
  } };
239
237
  }
240
- function registerToolHandlers(server, ctx) {
241
- server.setRequestHandler(ListToolsRequestSchema, async () => {
242
- return { tools: ctx.agent.list().tools.map((tool) => projectTool(tool, ctx)) };
238
+ /**
239
+ * Id of the built-in shared-state read tool — namespaced like every other
240
+ * built-in (`devframe:<area>:<fn>`). Tool-shaped access matters because many
241
+ * MCP clients only consume tools — the parallel `devframe://state/<key>`
242
+ * resource projection stays for the clients that do read resources.
243
+ */
244
+ const READ_STATE_TOOL = "devframe:state:read";
245
+ /** Wire name of the built-in shared-state read tool: `devframe_state_read`. */
246
+ const READ_STATE_NAME = toAgentToolName(READ_STATE_TOOL);
247
+ function sharedStateFilter(exposeSharedState) {
248
+ if (exposeSharedState === false) return void 0;
249
+ return typeof exposeSharedState === "function" ? exposeSharedState : () => true;
250
+ }
251
+ function readStateToolProjection() {
252
+ return {
253
+ name: READ_STATE_NAME,
254
+ title: "Read shared state",
255
+ description: "Read this devtool's live shared state. Call without arguments to list the available keys, then with a key to get that value as JSON. Safe to call freely.",
256
+ inputSchema: {
257
+ type: "object",
258
+ properties: { key: {
259
+ type: "string",
260
+ description: "A shared-state key from the key list. Omit to list all keys."
261
+ } }
262
+ },
263
+ annotations: {
264
+ title: "Read shared state",
265
+ readOnlyHint: true,
266
+ destructiveHint: false
267
+ }
268
+ };
269
+ }
270
+ async function readStateResult(ctx, filter, key) {
271
+ const keys = ctx.rpc.sharedState.keys().filter(filter);
272
+ if (key === void 0) return { keys };
273
+ if (!keys.includes(key)) throw diagnostics.DF0048({ key });
274
+ return {
275
+ key,
276
+ value: (await ctx.rpc.sharedState.get(key)).value()
277
+ };
278
+ }
279
+ function registerToolHandlers(server, ctx, exposeSharedState) {
280
+ const stateFilter = sharedStateFilter(exposeSharedState);
281
+ const warnedCollisions = /* @__PURE__ */ new Set();
282
+ /**
283
+ * Resolve a wire tool name back to the registered {@link AgentTool}.
284
+ * Wire-name matching runs first, in manifest order — the same tool the
285
+ * list projection advertises under that name — with a raw-id fallback so
286
+ * a colon-namespaced id keeps working as a call name.
287
+ */
288
+ const resolveTool = (name) => {
289
+ return ctx.agent.list().tools.find((tool) => toAgentToolName(tool.id) === name) ?? ctx.agent.getTool(name);
290
+ };
291
+ server.setRequestHandler("tools/list", async () => {
292
+ const byName = /* @__PURE__ */ new Map();
293
+ for (const tool of ctx.agent.list().tools) {
294
+ const name = toAgentToolName(tool.id);
295
+ const existing = byName.get(name);
296
+ if (existing) {
297
+ if (!warnedCollisions.has(`${name}|${tool.id}`)) {
298
+ warnedCollisions.add(`${name}|${tool.id}`);
299
+ diagnostics.DF0047({
300
+ name,
301
+ id: tool.id,
302
+ existing: existing.id
303
+ });
304
+ }
305
+ continue;
306
+ }
307
+ byName.set(name, tool);
308
+ }
309
+ const tools = [...byName.entries()].map(([name, tool]) => projectTool(name, tool, ctx));
310
+ if (stateFilter && !byName.has(READ_STATE_NAME)) tools.push(readStateToolProjection());
311
+ return { tools };
243
312
  });
244
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
313
+ server.setRequestHandler("tools/call", async (request) => {
245
314
  const { name, arguments: args } = request.params;
246
315
  try {
247
- const tool = ctx.agent.getTool(name);
248
- const outputSchema = tool ? tool.outputSchema ?? computeOutputSchema(tool, ctx) : void 0;
249
- const result = await ctx.agent.invoke(name, args ?? {});
316
+ const tool = resolveTool(name);
317
+ if (stateFilter && !tool && (name === READ_STATE_NAME || name === READ_STATE_TOOL)) {
318
+ const key = args?.key;
319
+ const result = await readStateResult(ctx, stateFilter, key);
320
+ return {
321
+ content: [{
322
+ type: "text",
323
+ text: stringifyForMcp(result)
324
+ }],
325
+ structuredContent: result
326
+ };
327
+ }
328
+ const outputSchema = tool ? usableOutputSchema(tool.outputSchema ?? computeOutputSchema(tool, ctx)) : void 0;
329
+ const result = await ctx.agent.invoke(tool?.id ?? name, args ?? {});
250
330
  return {
251
331
  content: [{
252
332
  type: "text",
@@ -266,7 +346,7 @@ function registerToolHandlers(server, ctx) {
266
346
  });
267
347
  }
268
348
  function registerResourceHandlers(server, ctx, exposeSharedState) {
269
- server.setRequestHandler(ListResourcesRequestSchema, async () => {
349
+ server.setRequestHandler("resources/list", async () => {
270
350
  const resources = ctx.agent.list().resources.map((resource) => ({
271
351
  uri: resource.uri,
272
352
  name: resource.name,
@@ -287,7 +367,7 @@ function registerResourceHandlers(server, ctx, exposeSharedState) {
287
367
  }
288
368
  return { resources };
289
369
  });
290
- server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
370
+ server.setRequestHandler("resources/read", async (request) => {
291
371
  const { uri } = request.params;
292
372
  const parsed = parseResourceUri(uri);
293
373
  if (parsed.kind === "resource") {
@@ -306,11 +386,20 @@ function registerResourceHandlers(server, ctx, exposeSharedState) {
306
386
  throw new Error(`[devframe/mcp] unknown resource URI "${uri}"`);
307
387
  });
308
388
  }
309
- function projectTool(tool, ctx) {
389
+ /**
390
+ * MCP constrains a tool's `outputSchema` to a JSON Schema of `type:
391
+ * "object"` — clients (the SDK included) reject anything else. Non-object
392
+ * return schemas (e.g. a schema for `void` / a bare string) simply project
393
+ * no output schema; the text content still carries the result.
394
+ */
395
+ function usableOutputSchema(schema) {
396
+ return schema && typeof schema === "object" && schema.type === "object" ? schema : void 0;
397
+ }
398
+ function projectTool(name, tool, ctx) {
310
399
  const inputSchema = tool.inputSchema ?? computeInputSchema(tool, ctx);
311
- const outputSchema = tool.outputSchema ?? computeOutputSchema(tool, ctx);
400
+ const outputSchema = usableOutputSchema(tool.outputSchema ?? computeOutputSchema(tool, ctx));
312
401
  return {
313
- name: tool.id,
402
+ name,
314
403
  title: tool.title,
315
404
  description: tool.description,
316
405
  inputSchema,
@@ -323,6 +412,7 @@ function projectTool(tool, ctx) {
323
412
  };
324
413
  }
325
414
  function computeInputSchema(tool, ctx) {
415
+ if (tool.kind === "tool") return argsToJsonSchema(tool.args).schema;
326
416
  if (tool.kind !== "rpc" || !tool.rpcName) return {
327
417
  type: "object",
328
418
  properties: {}
@@ -333,13 +423,13 @@ function computeInputSchema(tool, ctx) {
333
423
  properties: {}
334
424
  };
335
425
  const args = def.args;
336
- return valibotArgsToJsonSchema(args).schema;
426
+ return argsToJsonSchema(args).schema;
337
427
  }
338
428
  function computeOutputSchema(tool, ctx) {
339
429
  if (tool.kind !== "rpc" || !tool.rpcName) return void 0;
340
430
  const def = ctx.rpc.definitions.get(tool.rpcName);
341
431
  if (!def) return void 0;
342
- return valibotReturnToJsonSchema(def.returns);
432
+ return returnToJsonSchema(def.returns);
343
433
  }
344
434
  function parseResourceUri(uri) {
345
435
  const match = uri.match(/^devframe:\/\/(resource|state)\/(.+)$/);
@@ -356,4 +446,88 @@ function parseResourceUri(uri) {
356
446
  };
357
447
  }
358
448
  //#endregion
359
- export { createMcpServer as n, buildMcpServerFromContext as t };
449
+ //#region src/adapters/mcp/fetch.ts
450
+ /**
451
+ * Build a framework-agnostic MCP Streamable-HTTP endpoint over a devframe
452
+ * context: a web-standard `Request → Response` handler any host can mount —
453
+ * h3 (see `mountMcpHttp`), a Next.js App Router route, or any other
454
+ * fetch-shaped server.
455
+ *
456
+ * Each MCP session gets its own {@link WebStandardStreamableHTTPServerTransport}
457
+ * and MCP server (built from the shared, live `ctx` via
458
+ * `buildMcpServerFromContext`), correlated by the `Mcp-Session-Id` header: an
459
+ * `initialize` POST spins up a session; later requests route to it; a `DELETE`
460
+ * (or client disconnect) tears it down. The origin gate applies devframe's
461
+ * loopback-default DNS-rebinding protection (identical semantics to the WS
462
+ * upgrade's `isAllowedOrigin`).
463
+ *
464
+ * @experimental
465
+ */
466
+ function createMcpFetchHandler(ctx, options) {
467
+ const sessions = /* @__PURE__ */ new Map();
468
+ const allowedOrigins = options.allowedOrigins;
469
+ function drop(sessionId) {
470
+ const session = sessions.get(sessionId);
471
+ if (!session) return;
472
+ sessions.delete(sessionId);
473
+ session.dispose();
474
+ }
475
+ async function createSession() {
476
+ let session;
477
+ const transport = new WebStandardStreamableHTTPServerTransport({
478
+ sessionIdGenerator: () => randomUUID(),
479
+ onsessioninitialized: (id) => {
480
+ sessions.set(id, session);
481
+ },
482
+ onsessionclosed: (id) => {
483
+ drop(id);
484
+ }
485
+ });
486
+ const { server, dispose } = buildMcpServerFromContext(ctx, {
487
+ serverName: options.serverName,
488
+ serverVersion: options.serverVersion,
489
+ exposeSharedState: options.exposeSharedState
490
+ });
491
+ session = {
492
+ transport,
493
+ dispose: async () => {
494
+ dispose();
495
+ await server.close();
496
+ }
497
+ };
498
+ transport.onclose = () => {
499
+ if (transport.sessionId) drop(transport.sessionId);
500
+ };
501
+ await server.connect(transport);
502
+ return session;
503
+ }
504
+ async function handle(req) {
505
+ const origin = req.headers.get("origin") ?? void 0;
506
+ if (allowedOrigins !== false && !isAllowedOrigin(origin, allowedOrigins ?? [])) return new Response("Forbidden: origin not allowed", { status: 403 });
507
+ const sessionId = req.headers.get("mcp-session-id") ?? void 0;
508
+ let session = sessionId ? sessions.get(sessionId) : void 0;
509
+ if (!session && req.method === "POST") {
510
+ let body;
511
+ try {
512
+ body = await req.json();
513
+ } catch {
514
+ body = void 0;
515
+ }
516
+ if (!sessionId && isInitializeRequest(body)) session = await createSession();
517
+ else return new Response(sessionId ? "Not Found: unknown MCP session" : "Bad Request: no valid session ID and not an initialize request", { status: sessionId ? 404 : 400 });
518
+ return session.transport.handleRequest(req, { parsedBody: body });
519
+ }
520
+ if (!session) return new Response(sessionId ? "Not Found: unknown MCP session" : "Bad Request: missing MCP session ID", { status: sessionId ? 404 : 400 });
521
+ return session.transport.handleRequest(req);
522
+ }
523
+ return {
524
+ fetch: handle,
525
+ dispose: async () => {
526
+ const live = [...sessions.values()];
527
+ sessions.clear();
528
+ await Promise.all(live.map((session) => session.dispose()));
529
+ }
530
+ };
531
+ }
532
+ //#endregion
533
+ export { createMcpServer as n, createMcpFetchHandler as t };
@@ -1,4 +1,4 @@
1
- import { d as McpRouteOptions, p as DevframeAuthHandler, r as DevframeDefinition } from "../devframe-Ckhf3kFw.mjs";
1
+ import { d as McpRouteOptions, p as DevframeAuthHandler, r as DevframeDefinition } from "../devframe-Dsjn_Xtq.mjs";
2
2
  //#region src/helpers/vite.d.ts
3
3
  interface ViteDevBridgeOptions {
4
4
  /**
@@ -1,8 +1,8 @@
1
1
  import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_WS_ROUTE } from "../constants.mjs";
2
- import { a as diagnostics } from "../storage-gEnj9ASQ.mjs";
2
+ import { t as diagnostics } from "../diagnostics-B5-qHeqD.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, r as resolveMcpConnectionMeta, t as createDevServer } from "../dev-BCeaQuGE.mjs";
5
+ import { n as resolveDevServerPort, r as resolveMcpConnectionMeta, t as createDevServer } from "../dev-D4M7Veo7.mjs";
6
6
  import { resolve } from "pathe";
7
7
  //#region src/helpers/vite.ts
8
8
  /**
@@ -1,8 +1,9 @@
1
1
  import { t as devframeReporter } from "./diagnostics-reporter-CsIG85Q5.mjs";
2
- import { t as diagnostics } from "./diagnostics-BXwBQmoN.mjs";
2
+ import { t as diagnostics } from "./diagnostics-hwjXp_UV.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-gEnj9ASQ.mjs";
5
+ import { t as diagnostics$1 } from "./diagnostics-B5-qHeqD.mjs";
6
+ import { i as createEventEmitter, n as createSharedState, r as nanoid, t as createStorage } from "./storage-Dzoc3NVC.mjs";
6
7
  import { defineDiagnostics } from "nostics";
7
8
  import { isatty } from "node:tty";
8
9
  import { formatWithOptions, inspect } from "node:util";
@@ -10,6 +11,43 @@ import { existsSync } from "node:fs";
10
11
  import { join } from "pathe";
11
12
  import process$1 from "node:process";
12
13
  import { homedir } from "node:os";
14
+ //#region src/node/agent-args.ts
15
+ /**
16
+ * Map the args payload an agent surface receives (MCP sends an object
17
+ * keyed `arg0`/`arg1`/…, matching the schema the adapter advertises) onto
18
+ * a handler's positional parameters. Shared by the agent host's RPC
19
+ * bridge and the hub's command-derived tools so the coercion cannot
20
+ * drift between them.
21
+ *
22
+ * - an array passes through as-is
23
+ * - `null`/`undefined` become a zero-argument call
24
+ * - with declared schemas, each schema reads its own `argN` key, in order
25
+ * - without schemas, `arg0`/`arg1`/… keys are collected when present
26
+ * - an empty object becomes a zero-argument call
27
+ * - anything else follows the {@link AgentArgsFallback}
28
+ *
29
+ * @experimental
30
+ */
31
+ function coerceAgentPositionalArgs(args, schemas, fallback = "wrap") {
32
+ if (Array.isArray(args)) return args;
33
+ if (args === void 0 || args === null) return [];
34
+ if (typeof args === "object") {
35
+ const obj = args;
36
+ if (schemas && schemas.length) return schemas.map((_, i) => obj[`arg${i}`]);
37
+ if ("arg0" in obj) {
38
+ const out = [];
39
+ let i = 0;
40
+ while (`arg${i}` in obj) {
41
+ out.push(obj[`arg${i}`]);
42
+ i++;
43
+ }
44
+ return out;
45
+ }
46
+ if (Object.keys(obj).length === 0) return [];
47
+ }
48
+ return fallback === "drop" ? [] : [args];
49
+ }
50
+ //#endregion
13
51
  //#region src/node/host-agent.ts
14
52
  /**
15
53
  * Framework-neutral host aggregating the agent-exposed surface of a
@@ -24,6 +62,7 @@ var DevframeAgentHost = class {
24
62
  events = createEventEmitter();
25
63
  tools = /* @__PURE__ */ new Map();
26
64
  resources = /* @__PURE__ */ new Map();
65
+ providers = /* @__PURE__ */ new Set();
27
66
  _rpcUnsubscribe;
28
67
  constructor(context) {
29
68
  this.context = context;
@@ -50,6 +89,19 @@ var DevframeAgentHost = class {
50
89
  }
51
90
  return existed;
52
91
  }
92
+ registerToolProvider(provider) {
93
+ this.providers.add(provider);
94
+ this.events.emit("agent:manifest:changed");
95
+ const notifyChanged = () => {
96
+ if (this.providers.has(provider)) this.events.emit("agent:manifest:changed");
97
+ };
98
+ return {
99
+ notifyChanged,
100
+ unregister: () => {
101
+ if (this.providers.delete(provider)) this.events.emit("agent:manifest:changed");
102
+ }
103
+ };
104
+ }
53
105
  registerResource(input) {
54
106
  if (this.resources.has(input.id)) throw diagnostics$1.DF0016({ id: input.id });
55
107
  const resource = {
@@ -79,15 +131,28 @@ var DevframeAgentHost = class {
79
131
  const rpcTools = this._collectRpcTools();
80
132
  const plainTools = Array.from(this.tools.values()).map((t) => t.tool);
81
133
  const resources = Array.from(this.resources.values()).map((r) => r.resource);
134
+ const seen = new Set([...rpcTools, ...plainTools].map((t) => t.id));
135
+ const providerTools = [];
136
+ for (const { tool } of this._collectProviderTools()) {
137
+ if (seen.has(tool.id)) continue;
138
+ seen.add(tool.id);
139
+ providerTools.push(tool);
140
+ }
82
141
  return {
83
- tools: [...rpcTools, ...plainTools],
142
+ tools: [
143
+ ...rpcTools,
144
+ ...plainTools,
145
+ ...providerTools
146
+ ],
84
147
  resources
85
148
  };
86
149
  }
87
150
  getTool(id) {
88
151
  const plain = this.tools.get(id);
89
152
  if (plain) return plain.tool;
90
- return this._collectRpcTools().find((t) => t.id === id);
153
+ const rpc = this._collectRpcTools().find((t) => t.id === id);
154
+ if (rpc) return rpc;
155
+ return this._collectProviderTools().find((t) => t.tool.id === id)?.tool;
91
156
  }
92
157
  getResource(id) {
93
158
  return this.resources.get(id)?.resource;
@@ -97,9 +162,11 @@ var DevframeAgentHost = class {
97
162
  if (plain?.handler) return await plain.handler(args);
98
163
  const rpcDef = this._findRpcDefinition(id);
99
164
  if (rpcDef) {
100
- const positional = this._coercePositionalArgs(args, rpcDef);
165
+ const positional = coerceAgentPositionalArgs(args, rpcDef.args, "wrap");
101
166
  return await this.context.rpc.invokeLocal(id, ...positional);
102
167
  }
168
+ const provided = this._collectProviderTools().find((t) => t.tool.id === id);
169
+ if (provided) return await provided.input.handler(args);
103
170
  throw new Error(`[devframe/agent] tool "${id}" not found`);
104
171
  }
105
172
  async read(id) {
@@ -125,11 +192,21 @@ var DevframeAgentHost = class {
125
192
  description: input.description,
126
193
  safety: input.safety ?? "action",
127
194
  tags: input.tags,
195
+ args: input.args,
128
196
  inputSchema: input.inputSchema,
129
197
  outputSchema: input.outputSchema,
130
198
  examples: input.examples
131
199
  };
132
200
  }
201
+ /** Query every registered provider, projecting inputs to serializable tools. */
202
+ _collectProviderTools() {
203
+ const out = [];
204
+ for (const provider of this.providers) for (const input of provider()) out.push({
205
+ input,
206
+ tool: this._projectTool(input)
207
+ });
208
+ return out;
209
+ }
133
210
  _collectRpcTools() {
134
211
  const out = [];
135
212
  for (const [name, def] of this.context.rpc.definitions) {
@@ -155,33 +232,11 @@ var DevframeAgentHost = class {
155
232
  const def = this.context.rpc.definitions.get(id);
156
233
  if (def?.agent) return def;
157
234
  }
158
- _coercePositionalArgs(args, def) {
159
- if (Array.isArray(args)) return args;
160
- if (args === void 0 || args === null) return [];
161
- if (args && typeof args === "object") {
162
- const obj = args;
163
- const schemas = def.args;
164
- if (schemas && schemas.length) return schemas.map((_, i) => obj[`arg${i}`]);
165
- if (hasPositionalKeys(obj)) {
166
- const out = [];
167
- let i = 0;
168
- while (`arg${i}` in obj) {
169
- out.push(obj[`arg${i}`]);
170
- i++;
171
- }
172
- return out;
173
- }
174
- }
175
- return [args];
176
- }
177
235
  };
178
236
  function inferSafety(type) {
179
237
  if (type === "static" || type === "query") return "read";
180
238
  return "action";
181
239
  }
182
- function hasPositionalKeys(obj) {
183
- return "arg0" in obj;
184
- }
185
240
  //#endregion
186
241
  //#region src/node/host-diagnostics.ts
187
242
  var DevframeDiagnosticsHost = class {
@@ -1517,4 +1572,4 @@ function createH3DevframeHost(options) {
1517
1572
  };
1518
1573
  }
1519
1574
  //#endregion
1520
- export { DevframeViewHost as a, createRpcSharedStateServerHost as c, createNodeSettings as i, DevframeDiagnosticsHost as l, createHostContext as n, DevframeServicesHostImpl as o, createScopedNodeContext as r, createRpcStreamingServerHost as s, createH3DevframeHost as t, DevframeAgentHost as u };
1575
+ export { DevframeViewHost as a, createRpcSharedStateServerHost as c, coerceAgentPositionalArgs as d, createNodeSettings as i, DevframeDiagnosticsHost as l, createHostContext as n, DevframeServicesHostImpl as o, createScopedNodeContext as r, createRpcStreamingServerHost as s, createH3DevframeHost as t, DevframeAgentHost as u };
@@ -0,0 +1,38 @@
1
+ import { t as createMcpFetchHandler } from "./fetch-BZyK4v6W.mjs";
2
+ import { defineHandler } from "h3";
3
+ //#region src/adapters/mcp/http.ts
4
+ /**
5
+ * Mount an MCP Streamable-HTTP endpoint on an h3 app at `path` — the h3
6
+ * binding over {@link createMcpFetchHandler}, which owns the sessions, the
7
+ * origin gate, and the transport plumbing.
8
+ *
9
+ * The handler is web-standard — it takes the h3 event's web `Request` and
10
+ * returns a web `Response` (an SSE `ReadableStream` body for the
11
+ * server→client stream). We copy that response onto `event.res` and return
12
+ * its body rather than returning the `Response` object directly, so a
13
+ * legitimate MCP 404 (unknown session) isn't swallowed by h3's
14
+ * "Response-with-404 falls through to the next handler" rule (which would
15
+ * otherwise hand the request to the SPA static catch-all).
16
+ *
17
+ * @experimental
18
+ */
19
+ function mountMcpHttp(app, ctx, path, options) {
20
+ const handler = createMcpFetchHandler(ctx, options);
21
+ app.use(path, defineHandler(async (event) => respond(event, await handler.fetch(event.req))));
22
+ return { dispose: handler.dispose };
23
+ }
24
+ /**
25
+ * Copy a web `Response` from the MCP transport onto the h3 event's response
26
+ * and return its body. Returning the body (a `ReadableStream` or `null`)
27
+ * rather than the `Response` object avoids h3's 404-fall-through behavior.
28
+ */
29
+ function respond(event, response) {
30
+ event.res.status = response.status;
31
+ event.res.statusText = response.statusText;
32
+ response.headers.forEach((value, key) => {
33
+ event.res.headers.set(key, value);
34
+ });
35
+ return response.body ?? "";
36
+ }
37
+ //#endregion
38
+ export { mountMcpHttp };
@@ -1,5 +1,5 @@
1
- import { W as DevframeNodeRpcSession, b as DevframeNodeContext, ht as SharedState } from "./devframe-Ckhf3kFw.mjs";
2
- import { n as InternalAnonymousAuthStorage } from "./context-CzqPrJSz.mjs";
1
+ import { W as DevframeNodeRpcSession, b as DevframeNodeContext, ht as SharedState } from "./devframe-Dsjn_Xtq.mjs";
2
+ import { n as InternalAnonymousAuthStorage } from "./context-7BbaIUSI.mjs";
3
3
  //#region src/node/auth/revoke.d.ts
4
4
  /**
5
5
  * Flip `isTrusted` to false on any live WS clients connected with `token`