devframe 0.9.19 → 0.10.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 (98) hide show
  1. package/dist/_shared-B2PZsp-J.mjs +191 -0
  2. package/dist/{_shared-BIUet_Jh.d.mts → _shared-Dy1UBMUN.d.mts} +13 -14
  3. package/dist/adapters/build.d.mts +3 -4
  4. package/dist/adapters/build.mjs +4 -5
  5. package/dist/adapters/cac.d.mts +3 -4
  6. package/dist/adapters/cac.mjs +3 -3
  7. package/dist/adapters/dev.d.mts +8 -8
  8. package/dist/adapters/dev.mjs +2 -2
  9. package/dist/adapters/embedded.d.mts +1 -1
  10. package/dist/adapters/initiate.d.mts +8 -8
  11. package/dist/adapters/initiate.mjs +1 -1
  12. package/dist/adapters/mcp.d.mts +4 -123
  13. package/dist/adapters/mcp.mjs +1 -589
  14. package/dist/agentic-D4QQQUjE.d.mts +30 -0
  15. package/dist/cli/main.mjs +12 -260
  16. package/dist/client/index.d.mts +6 -6
  17. package/dist/client/index.mjs +3 -4
  18. package/dist/constants.d.mts +2 -1
  19. package/dist/constants.mjs +2 -1
  20. package/dist/{context-BA5FPz7K.d.mts → context-B4BKPuaa.d.mts} +1 -1
  21. package/dist/{context-BLXXjoBW.mjs → context-C141q8H4.mjs} +5 -5
  22. package/dist/{context-jXO5vzjH.mjs → context-CHpyLX8W.mjs} +1 -1
  23. package/dist/{context-BLHBDQcZ.d.mts → context-CTAxz9ec.d.mts} +108 -47
  24. package/dist/{dev-DbWecpjA.mjs → dev-DM1mIlgi.mjs} +16 -24
  25. package/dist/{diagnostics-DZA6KQ_Z.mjs → diagnostics-Cx4_VjlG.mjs} +11 -3
  26. package/dist/dist-Bw3EbMj4.mjs +305 -0
  27. package/dist/dist-CO98PBnQ.mjs +265 -0
  28. package/dist/{host-agent-C3MBpNCd.mjs → host-agent-ZSKgzMVu.mjs} +2 -7
  29. package/dist/import-runtime-module-BzlEQ9PI.mjs +24 -0
  30. package/dist/in-page-channel/index.d.mts +2 -7
  31. package/dist/in-page-channel/index.mjs +1 -4
  32. package/dist/index-Bia1vKjL.d.mts +218 -0
  33. package/dist/{index-r6NstVmM.d.mts → index-CcPSKbxb.d.mts} +2 -2
  34. package/dist/index-CeEtxDmK.d.mts +78 -0
  35. package/dist/{index-CiA6C705.d.mts → index-DuEm0sDH.d.mts} +1 -1
  36. package/dist/index.d.mts +4 -11
  37. package/dist/index.mjs +1 -10
  38. package/dist/{instance-registry-DCq762YJ.mjs → instance-registry-DtvYxc0Y.mjs} +1 -1
  39. package/dist/{instance-shell-C6Vg97lg.d.mts → instance-shell-DOZSxst6.d.mts} +31 -5
  40. package/dist/{instance-shell-370Oee9K.mjs → instance-shell-wC2sfEU_.mjs} +6 -6
  41. package/dist/interactive-auth-BCa_iamv.mjs +384 -0
  42. package/dist/internal/index.d.mts +66 -22
  43. package/dist/internal/index.mjs +143 -8
  44. package/dist/mrmime-40h5OOIp.mjs +448 -0
  45. package/dist/node/auth.d.mts +2 -2
  46. package/dist/node/hub-internals.d.mts +2 -2
  47. package/dist/node/hub-internals.mjs +2 -2
  48. package/dist/node/index.d.mts +2 -4
  49. package/dist/node/index.mjs +2 -2
  50. package/dist/{nostics-0sKe5HIj.d.mts → nostics-DWvukRwa.d.mts} +11 -2
  51. package/dist/{origin-DGDH8BI9.d.mts → origin-CY6BBHOy.d.mts} +14 -1
  52. package/dist/recipes/interactive-auth.d.mts +2 -2
  53. package/dist/recipes/interactive-auth.mjs +1 -231
  54. package/dist/{remote-assets-D0StJVLp.mjs → remote-assets-B9MeWmW6.mjs} +2 -2
  55. package/dist/rpc/client.d.mts +1 -1
  56. package/dist/rpc/client.mjs +1 -1
  57. package/dist/rpc/dump.d.mts +2 -1
  58. package/dist/rpc/index.d.mts +3 -2
  59. package/dist/rpc/server.d.mts +1 -1
  60. package/dist/rpc/server.mjs +1 -1
  61. package/dist/rpc/transports/sse-client.d.mts +1 -1
  62. package/dist/rpc/transports/sse-server.d.mts +3 -3
  63. package/dist/rpc/transports/ws-bun.d.mts +2 -2
  64. package/dist/rpc/transports/ws-client.d.mts +1 -1
  65. package/dist/rpc/transports/ws-deno.d.mts +2 -2
  66. package/dist/rpc/transports/ws-server.d.mts +2 -2
  67. package/dist/{rpc-core-CvkU23bQ.mjs → rpc-core-BXyMjbKG.mjs} +1 -1
  68. package/dist/{rpc-core-BlL-Ip8n.d.mts → rpc-core-rLrwFEcw.d.mts} +4 -4
  69. package/dist/{services-install-Ben4yh52.mjs → services-install-D5YUwFXW.mjs} +17 -1
  70. package/dist/{sse-client-BcbYwXdD.d.mts → sse-client-DNdscfu_.d.mts} +2 -2
  71. package/dist/{storage-B70AXK64.mjs → storage-CHD1IkqU.mjs} +1 -1
  72. package/dist/types/index.d.mts +4 -4
  73. package/dist/{types-D2vGd7-2.d.mts → types-BmDbfHCx.d.mts} +2 -3
  74. package/dist/utils/debounce.d.mts +50 -0
  75. package/dist/utils/debounce.mjs +88 -0
  76. package/dist/utils/events.d.mts +1 -1
  77. package/dist/utils/launch-editor.d.mts +8 -0
  78. package/dist/utils/launch-editor.mjs +36 -1
  79. package/dist/utils/nostics.d.mts +2 -2
  80. package/dist/utils/origin.d.mts +2 -2
  81. package/dist/utils/origin.mjs +21 -1
  82. package/dist/utils/remote-assets.mjs +1 -1
  83. package/dist/utils/serve-static.mjs +1 -1
  84. package/dist/utils/shared-state.d.mts +1 -1
  85. package/dist/utils/simple-schema.d.mts +3 -2
  86. package/dist/utils/streaming-channel.d.mts +1 -1
  87. package/dist/utils/url.d.mts +31 -0
  88. package/dist/utils/url.mjs +58 -0
  89. package/dist/{ws-client-CNpP6Ium.d.mts → ws-client-6Mrz6eC4.d.mts} +2 -2
  90. package/dist/{ws-server-BB8jhxeT.d.mts → ws-server-y2R76aSW.d.mts} +2 -2
  91. package/package.json +10 -11
  92. package/dist/_shared-U8D-xzrp.mjs +0 -115
  93. package/dist/agent-tool-name-DGzeuWcO.mjs +0 -31
  94. package/dist/client-assets-DNdhagGm.mjs +0 -16
  95. package/dist/nostics-DD6gsDZA.mjs +0 -108
  96. package/dist/recipes/common-rpc-functions.d.mts +0 -70
  97. package/dist/recipes/common-rpc-functions.mjs +0 -115
  98. package/dist/simple-schema-Bi40l-P6.mjs +0 -154
@@ -1,590 +1,2 @@
1
- import { t as Diagnostic } from "../nostics-D0PvLZsn.mjs";
2
- import { t as DEVFRAME_EVENTS } from "../events-BV4Dj59a.mjs";
3
- import { t as isAllowedOrigin } from "../origin-DZit9g29.mjs";
4
- import { r as timingSafeEqual } from "../crypto-token-TVbGGFuj.mjs";
5
- import { t as diagnostics } from "../diagnostics-DZA6KQ_Z.mjs";
6
- import { t as createHostContext } from "../context-BLXXjoBW.mjs";
7
- import { t as toAgentToolName } from "../agent-tool-name-DGzeuWcO.mjs";
8
- import { join } from "pathe";
9
- import process from "node:process";
10
- import { homedir } from "node:os";
11
- import { defineHandler } from "h3";
12
- import { Server, createMcpHandler } from "@modelcontextprotocol/server";
13
- //#region src/adapters/mcp/stringify.ts
14
- /**
15
- * JSON-coercing serializer for MCP text payloads.
16
- *
17
- * MCP carries tool results and resource reads as plain text over a
18
- * JSON-RPC transport, so we cannot use the `s:`-prefixed structured-clone
19
- * format the WS RPC transport falls back to for non-JSON values. Instead,
20
- * we coerce common non-JSON types into JSON-friendly forms so the LLM
21
- * client sees something useful instead of `[object Object]`.
22
- *
23
- * Coercions:
24
- * - `BigInt` → `"123n"`
25
- * - `Date` → ISO string (via the native `toJSON`)
26
- * - `Map` → `{ __type: 'Map', entries: [[k, v], …] }`
27
- * - `Set` → `{ __type: 'Set', entries: [v, …] }`
28
- * - `Error` → `{ name, message, stack, cause? }` (cause recurses)
29
- * - `Function` → `"[Function: name]"`
30
- * - `Symbol` → `value.toString()`
31
- * - cycles → `"[Circular]"`
32
- */
33
- function stringifyForMcp(value) {
34
- if (value === void 0) return "undefined";
35
- if (typeof value === "string") return value;
36
- const seen = /* @__PURE__ */ new WeakSet();
37
- return JSON.stringify(value, (_key, val) => {
38
- if (typeof val === "bigint") return `${val}n`;
39
- if (val instanceof Error) {
40
- const out = {
41
- name: val.name,
42
- message: val.message,
43
- stack: val.stack
44
- };
45
- if (val.cause !== void 0) out.cause = val.cause;
46
- return out;
47
- }
48
- if (val instanceof Map) return {
49
- __type: "Map",
50
- entries: [...val.entries()]
51
- };
52
- if (val instanceof Set) return {
53
- __type: "Set",
54
- entries: [...val]
55
- };
56
- if (typeof val === "function") return `[Function: ${val.name || "anonymous"}]`;
57
- if (typeof val === "symbol") return val.toString();
58
- if (val !== null && typeof val === "object") {
59
- if (seen.has(val)) return "[Circular]";
60
- seen.add(val);
61
- }
62
- return val;
63
- }, 2);
64
- }
65
- /**
66
- * Format a thrown value for an MCP `isError` text payload.
67
- *
68
- * A nostics `Diagnostic` (every coded devframe error) becomes structured
69
- * JSON (`{ error: { code, message, fix?, docs? } }`), so an agent receives
70
- * the actionable next step (`fix`) and the docs URL instead of a bare
71
- * message string. Other errors surface `Error.name`/`message`, plus one
72
- * level of `cause.message` so context isn't dropped silently.
73
- */
74
- function formatMcpError(error) {
75
- if (error instanceof Diagnostic) return JSON.stringify({ error: {
76
- code: error.code,
77
- message: error.message,
78
- ...error.fix ? { fix: error.fix } : {},
79
- ...error.docs ? { docs: error.docs } : {}
80
- } }, null, 2);
81
- if (!(error instanceof Error)) return String(error);
82
- const cause = error.cause;
83
- const causeText = cause instanceof Error ? ` (cause: ${cause.message})` : cause !== void 0 ? ` (cause: ${String(cause)})` : "";
84
- return `${error.name}: ${error.message}${causeText}`;
85
- }
86
- //#endregion
87
- //#region src/adapters/mcp/to-json-schema.ts
88
- const FALLBACK_OBJECT_SCHEMA = Object.freeze({
89
- type: "object",
90
- additionalProperties: true
91
- });
92
- /**
93
- * Convert a Standard Schema to JSON Schema for the agent/MCP surface.
94
- *
95
- * Devframe stays validator-neutral, so conversion uses the schema's own
96
- * [Standard JSON Schema](https://standardschema.dev/) converter
97
- * (`~standard.jsonSchema`) when the validator provides one; zod 4 does,
98
- * for example. Validators without a native converter (e.g. valibot) degrade
99
- * to a permissive object schema rather than pulling in a converter library.
100
- */
101
- function safeToJsonSchema(schema) {
102
- const standard = schema["~standard"];
103
- if (standard.jsonSchema) try {
104
- return standard.jsonSchema.input({ target: "draft-2020-12" });
105
- } catch {
106
- return FALLBACK_OBJECT_SCHEMA;
107
- }
108
- return FALLBACK_OBJECT_SCHEMA;
109
- }
110
- /**
111
- * JSON Schema for an RPC return value on the agent/MCP surface.
112
- * @internal
113
- */
114
- function returnToJsonSchema(schema) {
115
- if (!schema) return void 0;
116
- return safeToJsonSchema(schema);
117
- }
118
- /**
119
- * JSON Schema for an RPC function's positional args on the agent/MCP
120
- * surface. Each positional arg is advertised under `arg0` / `arg1` / …,
121
- * matching how the agent bridge coerces the incoming object payload back
122
- * into positional arguments.
123
- *
124
- * Returns `{ type: 'object', properties: {} }` when there are no args.
125
- * @internal
126
- */
127
- function argsToJsonSchema(args) {
128
- if (!args || args.length === 0) return {
129
- type: "object",
130
- properties: {}
131
- };
132
- const properties = {};
133
- const required = [];
134
- for (let i = 0; i < args.length; i++) {
135
- const key = `arg${i}`;
136
- properties[key] = safeToJsonSchema(args[i]);
137
- required.push(key);
138
- }
139
- return {
140
- type: "object",
141
- properties,
142
- required,
143
- additionalProperties: false
144
- };
145
- }
146
- //#endregion
147
- //#region src/adapters/mcp/build-server.ts
148
- /**
149
- * Build a fresh MCP {@link Server} over a devframe context, registering its
150
- * tool and resource handlers. This is a pure factory: it sets up no
151
- * long-lived subscriptions and holds no per-connection state, so it is safe
152
- * to call once per request under `createMcpHandler` or once per connection
153
- * under `serveStdio`. Change notifications are published separately: over
154
- * HTTP through the handler's `notify` bus (see `createMcpFetchHandler`), and
155
- * on stdio through the connection's own `send*ListChanged` calls (see
156
- * {@link bridgeListChanged}, wired by `serveStdio`).
157
- *
158
- * @internal
159
- */
160
- function buildMcpServerFromContext(ctx, options) {
161
- const server = new Server({
162
- name: options.serverName,
163
- version: options.serverVersion
164
- }, { capabilities: {
165
- tools: { listChanged: true },
166
- resources: { listChanged: true }
167
- } });
168
- registerToolHandlers(server, ctx, options.exposeSharedState);
169
- registerResourceHandlers(server, ctx, options.exposeSharedState);
170
- return server;
171
- }
172
- /**
173
- * Publish devframe's `list_changed` events through a set of typed sinks:
174
- * `tools()` for tool-list changes and `resources()` for resource-list
175
- * changes (shared-state keys are surfaced as resources). Returns an
176
- * unsubscribe function.
177
- *
178
- * The HTTP path passes the handler's `notify` bus sugar; the stdio path
179
- * passes the pinned server's `send*ListChanged` methods, which `serveStdio`
180
- * routes onto the connection's active `subscriptions/listen` streams.
181
- *
182
- * @internal
183
- */
184
- function bridgeListChanged(ctx, sinks) {
185
- const offManifest = ctx.agent.events.on(DEVFRAME_EVENTS.bus.agentManifestChanged, () => {
186
- sinks.tools();
187
- sinks.resources();
188
- });
189
- const offKeyAdded = ctx.rpc.sharedState.onKeyAdded(() => {
190
- sinks.resources();
191
- });
192
- return () => {
193
- offManifest();
194
- offKeyAdded();
195
- };
196
- }
197
- /**
198
- * Build an MCP server over the agent surface of a devframe definition.
199
- * Currently supports `stdio` transport only.
200
- */
201
- async function createMcpServer(definition, options = {}) {
202
- const transport = options.transport ?? "stdio";
203
- if (transport !== "stdio") throw diagnostics.DF0017({
204
- transport,
205
- reason: "Only stdio transport is supported in this release."
206
- });
207
- const ctx = await createHostContext({
208
- cwd: process.cwd(),
209
- mode: "dev",
210
- host: {
211
- mountStatic: () => {},
212
- resolveOrigin: () => "mcp://devframe",
213
- getStorageDir: (scope) => {
214
- if (scope === "workspace") return join(process.cwd(), ".devframe");
215
- if (scope === "project") return join(process.cwd(), `node_modules/.${definition.id}/devframe`);
216
- return join(homedir(), `.${definition.id}/devframe`);
217
- }
218
- },
219
- importMetaUrl: definition.importMetaUrl
220
- });
221
- for (const input of definition.services ?? []) ctx.services.install(input, { resolveFrom: definition.importMetaUrl });
222
- await ctx.services.ready();
223
- await definition.setup(ctx);
224
- const buildOptions = {
225
- serverName: options.serverName ?? `${definition.id} (devframe)`,
226
- serverVersion: options.serverVersion ?? definition.version ?? "0.0.0",
227
- exposeSharedState: options.exposeSharedState ?? true
228
- };
229
- let handle;
230
- try {
231
- const { serveStdio } = await import("@modelcontextprotocol/server/stdio");
232
- handle = serveStdio(() => {
233
- const server = buildMcpServerFromContext(ctx, buildOptions);
234
- const unbridge = bridgeListChanged(ctx, {
235
- tools: () => {
236
- server.sendToolListChanged().catch(() => {});
237
- },
238
- resources: () => {
239
- server.sendResourceListChanged().catch(() => {});
240
- }
241
- });
242
- const priorOnClose = server.onclose;
243
- server.onclose = () => {
244
- unbridge();
245
- priorOnClose?.();
246
- };
247
- return server;
248
- });
249
- } catch (error) {
250
- const reason = error instanceof Error ? error.message : String(error);
251
- throw diagnostics.DF0017({
252
- transport,
253
- reason,
254
- cause: error
255
- });
256
- }
257
- options.onReady?.({ transport: "stdio" });
258
- return { async stop() {
259
- await handle.close();
260
- } };
261
- }
262
- /**
263
- * Id of the built-in shared-state read tool, namespaced like every other
264
- * built-in (`devframe:<area>:<fn>`). Tool-shaped access matters because many
265
- * MCP clients only consume tools; the parallel `devframe://state/<key>`
266
- * resource projection stays for the clients that do read resources.
267
- */
268
- const READ_STATE_TOOL = "devframe:state:read";
269
- /** Wire name of the built-in shared-state read tool: `devframe_state_read`. */
270
- const READ_STATE_NAME = toAgentToolName(READ_STATE_TOOL);
271
- function sharedStateFilter(exposeSharedState) {
272
- if (exposeSharedState === false) return void 0;
273
- return typeof exposeSharedState === "function" ? exposeSharedState : () => true;
274
- }
275
- function readStateToolProjection() {
276
- return {
277
- name: READ_STATE_NAME,
278
- title: "Read shared state",
279
- 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.",
280
- inputSchema: {
281
- type: "object",
282
- properties: { key: {
283
- type: "string",
284
- description: "A shared-state key from the key list. Omit to list all keys."
285
- } }
286
- },
287
- annotations: {
288
- title: "Read shared state",
289
- readOnlyHint: true,
290
- destructiveHint: false
291
- }
292
- };
293
- }
294
- async function readStateResult(ctx, filter, key) {
295
- const keys = ctx.rpc.sharedState.keys().filter(filter);
296
- if (key === void 0) return { keys };
297
- if (!keys.includes(key)) throw diagnostics.DF0048({ key });
298
- return {
299
- key,
300
- value: (await ctx.rpc.sharedState.get(key)).value()
301
- };
302
- }
303
- function registerToolHandlers(server, ctx, exposeSharedState) {
304
- const stateFilter = sharedStateFilter(exposeSharedState);
305
- const warnedCollisions = /* @__PURE__ */ new Set();
306
- /**
307
- * Resolve a wire tool name back to the registered {@link AgentTool}.
308
- * Wire-name matching runs first, in manifest order (the same tool the
309
- * list projection advertises under that name), with a raw-id fallback so
310
- * a colon-namespaced id keeps working as a call name.
311
- */
312
- const resolveTool = (name) => {
313
- return ctx.agent.list().tools.find((tool) => toAgentToolName(tool.id) === name) ?? ctx.agent.getTool(name);
314
- };
315
- server.setRequestHandler("tools/list", async () => {
316
- const byName = /* @__PURE__ */ new Map();
317
- for (const tool of ctx.agent.list().tools) {
318
- const name = toAgentToolName(tool.id);
319
- const existing = byName.get(name);
320
- if (existing) {
321
- if (!warnedCollisions.has(`${name}|${tool.id}`)) {
322
- warnedCollisions.add(`${name}|${tool.id}`);
323
- diagnostics.DF0047({
324
- name,
325
- id: tool.id,
326
- existing: existing.id
327
- });
328
- }
329
- continue;
330
- }
331
- byName.set(name, tool);
332
- }
333
- const tools = [...byName.entries()].map(([name, tool]) => projectTool(name, tool, ctx));
334
- if (stateFilter && !byName.has(READ_STATE_NAME)) tools.push(readStateToolProjection());
335
- return { tools };
336
- });
337
- server.setRequestHandler("tools/call", async (request) => {
338
- const { name, arguments: args } = request.params;
339
- try {
340
- const tool = resolveTool(name);
341
- if (stateFilter && !tool && (name === READ_STATE_NAME || name === READ_STATE_TOOL)) {
342
- const key = args?.key;
343
- const result = await readStateResult(ctx, stateFilter, key);
344
- return {
345
- content: [{
346
- type: "text",
347
- text: stringifyForMcp(result)
348
- }],
349
- structuredContent: result
350
- };
351
- }
352
- const outputSchema = tool ? usableOutputSchema(tool.outputSchema ?? computeOutputSchema(tool, ctx)) : void 0;
353
- const result = await ctx.agent.invoke(tool?.id ?? name, args ?? {});
354
- return {
355
- content: [{
356
- type: "text",
357
- text: stringifyForMcp(result)
358
- }],
359
- ...outputSchema ? { structuredContent: result } : {}
360
- };
361
- } catch (error) {
362
- return {
363
- isError: true,
364
- content: [{
365
- type: "text",
366
- text: `Error invoking "${name}": ${formatMcpError(error)}`
367
- }]
368
- };
369
- }
370
- });
371
- }
372
- function registerResourceHandlers(server, ctx, exposeSharedState) {
373
- const stateFilter = sharedStateFilter(exposeSharedState);
374
- server.setRequestHandler("resources/list", async () => {
375
- const resources = ctx.agent.list().resources.map((resource) => ({
376
- uri: resource.uri,
377
- name: resource.name,
378
- description: resource.description,
379
- mimeType: resource.mimeType
380
- }));
381
- if (stateFilter) for (const key of ctx.rpc.sharedState.keys()) {
382
- if (!stateFilter(key)) continue;
383
- resources.push({
384
- uri: `devframe://state/${encodeURIComponent(key)}`,
385
- name: key,
386
- description: `Shared state: ${key}`,
387
- mimeType: "application/json"
388
- });
389
- }
390
- return { resources };
391
- });
392
- server.setRequestHandler("resources/read", async (request) => {
393
- const { uri } = request.params;
394
- const parsed = parseResourceUri(uri);
395
- if (parsed.kind === "resource") {
396
- const content = await ctx.agent.read(parsed.id);
397
- return { contents: [{
398
- uri,
399
- mimeType: content.mimeType ?? "application/json",
400
- text: content.text ?? stringifyForMcp(content.json)
401
- }] };
402
- }
403
- if (parsed.kind === "state") {
404
- if (!stateFilter || !stateFilter(parsed.key)) throw diagnostics.DF0048({ key: parsed.key });
405
- return { contents: [{
406
- uri,
407
- mimeType: "application/json",
408
- text: stringifyForMcp((await ctx.rpc.sharedState.get(parsed.key)).value())
409
- }] };
410
- }
411
- throw new Error(`[devframe/mcp] unknown resource URI "${uri}"`);
412
- });
413
- }
414
- /**
415
- * MCP constrains a tool's `outputSchema` to a JSON Schema of `type:
416
- * "object"`; clients (the SDK included) reject anything else. Non-object
417
- * return schemas (e.g. a schema for `void` / a bare string) simply project
418
- * no output schema; the text content still carries the result.
419
- */
420
- function usableOutputSchema(schema) {
421
- return schema && typeof schema === "object" && schema.type === "object" ? schema : void 0;
422
- }
423
- function projectTool(name, tool, ctx) {
424
- const inputSchema = tool.inputSchema ?? computeInputSchema(tool, ctx);
425
- const outputSchema = usableOutputSchema(tool.outputSchema ?? computeOutputSchema(tool, ctx));
426
- return {
427
- name,
428
- title: tool.title,
429
- description: tool.description,
430
- inputSchema,
431
- ...outputSchema ? { outputSchema } : {},
432
- annotations: {
433
- title: tool.title,
434
- readOnlyHint: tool.safety === "read",
435
- destructiveHint: tool.safety === "destructive"
436
- }
437
- };
438
- }
439
- function computeInputSchema(tool, ctx) {
440
- if (tool.kind === "tool") return argsToJsonSchema(tool.args);
441
- if (tool.kind !== "rpc" || !tool.rpcName) return {
442
- type: "object",
443
- properties: {}
444
- };
445
- const def = ctx.rpc.definitions.get(tool.rpcName);
446
- if (!def) return {
447
- type: "object",
448
- properties: {}
449
- };
450
- const args = def.args;
451
- return argsToJsonSchema(args);
452
- }
453
- function computeOutputSchema(tool, ctx) {
454
- if (tool.kind !== "rpc" || !tool.rpcName) return void 0;
455
- const def = ctx.rpc.definitions.get(tool.rpcName);
456
- if (!def) return void 0;
457
- return returnToJsonSchema(def.returns);
458
- }
459
- function parseResourceUri(uri) {
460
- const match = uri.match(/^devframe:\/\/(resource|state)\/(.+)$/);
461
- if (!match) return { kind: "unknown" };
462
- const [, kind, rest] = match;
463
- const decoded = decodeURIComponent(rest);
464
- if (kind === "resource") return {
465
- kind: "resource",
466
- id: decoded
467
- };
468
- return {
469
- kind: "state",
470
- key: decoded
471
- };
472
- }
473
- //#endregion
474
- //#region src/adapters/mcp/fetch.ts
475
- /**
476
- * Parse exactly one `Authorization: Bearer <token>` credential, returning the
477
- * token or `undefined` for a missing, malformed, empty, or multi-credential
478
- * header. The token itself is never logged. `\S+` rejects the whitespace that
479
- * a second credential (fetch merges duplicate headers as `a, b`) or an empty
480
- * value would introduce.
481
- */
482
- function parseBearerToken(header) {
483
- if (!header) return void 0;
484
- const match = /^Bearer (\S+)$/i.exec(header.trim());
485
- return match ? match[1] : void 0;
486
- }
487
- /**
488
- * Resolve the identity gate for one request. `false` is the origin-only
489
- * opt-out; a callback delegates identity; a string requires a constant-time
490
- * bearer match. Never reveals whether a supplied token was close to correct.
491
- */
492
- async function isAuthorized(req, authorization) {
493
- if (authorization === false) return true;
494
- if (typeof authorization === "function") return await authorization(req) === true;
495
- const token = parseBearerToken(req.headers.get("authorization"));
496
- if (token === void 0) return false;
497
- return timingSafeEqual(token, authorization);
498
- }
499
- /**
500
- * Build a framework-agnostic MCP endpoint over a devframe context: a
501
- * web-standard `Request → Response` handler any host can mount: h3 (see
502
- * `mountMcpHttp`), a Next.js App Router route, or any other fetch-shaped
503
- * server.
504
- *
505
- * The endpoint is **stateless**: it serves the 2026-07-28 revision per request
506
- * through the SDK's {@link createMcpHandler}, which builds a fresh MCP server
507
- * (from the shared, live `ctx` via `buildMcpServerFromContext`) for each
508
- * request: no `Mcp-Session-Id` registry, no session-local routing, no
509
- * GET/DELETE teardown protocol. 2025-era clients are still served through the
510
- * SDK's default stateless legacy path. `list_changed` events reach modern
511
- * `subscriptions/listen` streams through the handler's `notify` bus.
512
- *
513
- * The origin gate guards every request: loopback-default DNS-rebinding
514
- * protection that (unlike the WS upgrade's `isAllowedOrigin`) also rejects
515
- * `Origin`-less requests, so a route-based endpoint isn't reachable by a
516
- * browser or a remote host (a disallowed origin gets `403`). It trusts
517
- * same-machine callers by default. When that isn't your trust boundary, add
518
- * an optional identity gate ({@link CreateMcpFetchHandlerOptions.authorization}),
519
- * checked after the origin gate: a bearer/callback check that proves *who* is
520
- * calling (a missing/invalid credential gets `401` with a
521
- * `WWW-Authenticate: Bearer` challenge).
522
- */
523
- function createMcpFetchHandler(ctx, options) {
524
- const allowedOrigins = options.allowedOrigins;
525
- const authorization = options.authorization ?? false;
526
- const handler = createMcpHandler(() => buildMcpServerFromContext(ctx, {
527
- serverName: options.serverName,
528
- serverVersion: options.serverVersion,
529
- exposeSharedState: options.exposeSharedState
530
- }));
531
- const unbridge = bridgeListChanged(ctx, {
532
- tools: () => {
533
- handler.notify.toolsChanged();
534
- },
535
- resources: () => {
536
- handler.notify.resourcesChanged();
537
- }
538
- });
539
- async function handle(req) {
540
- const origin = req.headers.get("origin") ?? void 0;
541
- if (allowedOrigins !== false && (origin === void 0 || !isAllowedOrigin(origin, allowedOrigins ?? []))) return new Response("Forbidden", { status: 403 });
542
- if (!await isAuthorized(req, authorization)) return new Response("Unauthorized", {
543
- status: 401,
544
- headers: { "WWW-Authenticate": "Bearer" }
545
- });
546
- return handler.fetch(req);
547
- }
548
- return {
549
- fetch: handle,
550
- dispose: async () => {
551
- unbridge();
552
- await handler.close();
553
- }
554
- };
555
- }
556
- //#endregion
557
- //#region src/adapters/mcp/http.ts
558
- /**
559
- * Mount a stateless MCP endpoint on an h3 app at `path`: the h3 binding over
560
- * {@link createMcpFetchHandler}, which owns the per-request serving, the
561
- * origin gate, and the transport plumbing.
562
- *
563
- * The handler is web-standard: it takes the h3 event's web `Request` and
564
- * returns a web `Response` (an SSE `ReadableStream` body for a
565
- * `subscriptions/listen` stream). We copy that response onto `event.res` and
566
- * return its body rather than returning the `Response` object directly, so an
567
- * MCP error response (e.g. a 4xx) isn't swallowed by h3's "Response-with-404
568
- * falls through to the next handler" rule (which would otherwise hand the
569
- * request to the SPA static catch-all).
570
- */
571
- function mountMcpHttp(app, ctx, path, options) {
572
- const handler = createMcpFetchHandler(ctx, options);
573
- app.use(path, defineHandler(async (event) => respond(event, await handler.fetch(event.req))));
574
- return { dispose: handler.dispose };
575
- }
576
- /**
577
- * Copy a web `Response` from the MCP transport onto the h3 event's response
578
- * and return its body. Returning the body (a `ReadableStream` or `null`)
579
- * rather than the `Response` object avoids h3's 404-fall-through behavior.
580
- */
581
- function respond(event, response) {
582
- event.res.status = response.status;
583
- event.res.statusText = response.statusText;
584
- response.headers.forEach((value, key) => {
585
- event.res.headers.set(key, value);
586
- });
587
- return response.body ?? "";
588
- }
589
- //#endregion
1
+ import { createMcpFetchHandler, createMcpServer, mountMcpHttp } from "@devframes/agentic/mcp";
590
2
  export { createMcpFetchHandler, createMcpServer, mountMcpHttp };
@@ -0,0 +1,30 @@
1
+ import { B as DevframeDefinition, F as CreateMcpServerOptions, L as McpFetchHandler, P as CreateMcpFetchHandlerOptions, R as McpServerHandle, o as DevframeNodeContext } from "./context-CTAxz9ec.mjs";
2
+ import { H3 } from "h3";
3
+ //#region src/node/agentic.d.ts
4
+ /**
5
+ * The h3-bound half of the MCP adapter contract (the SDK-neutral half lives
6
+ * in `types/mcp.ts`, which must stay lib-neutral; h3's declarations are not).
7
+ */
8
+ interface MountMcpHttpOptions extends CreateMcpFetchHandlerOptions {}
9
+ interface MountedMcpHttp {
10
+ /** Tear down the MCP handler (aborts in-flight exchanges, drops the change bridge). */
11
+ dispose: () => Promise<void>;
12
+ }
13
+ /** The surface `@devframes/agentic/mcp` exports, as devframe's loaders consume it. */
14
+ interface AgenticMcpModule {
15
+ /** Build an MCP server over the agent surface of a devframe definition (stdio). */
16
+ createMcpServer: (definition: DevframeDefinition, options?: CreateMcpServerOptions) => Promise<McpServerHandle>;
17
+ /** Build a framework-agnostic `Request → Response` MCP endpoint over a devframe context. */
18
+ createMcpFetchHandler: (ctx: DevframeNodeContext, options: CreateMcpFetchHandlerOptions) => McpFetchHandler;
19
+ /** Mount a stateless MCP endpoint on an h3 app at `path`. */
20
+ mountMcpHttp: (app: H3, ctx: DevframeNodeContext, path: string, options: MountMcpHttpOptions) => MountedMcpHttp;
21
+ }
22
+ /**
23
+ * Load the MCP adapter from `@devframes/agentic/mcp`, mapping a failed load
24
+ * (typically: the optional peer is not installed) to a thrown `DF0079`.
25
+ * Loads through `importRuntimeModule`, so the adapter and the MCP SDK behind
26
+ * it never enter a consumer's bundle graph.
27
+ */
28
+ declare function importAgenticMcp(specifier?: string): Promise<AgenticMcpModule>;
29
+ //#endregion
30
+ export { importAgenticMcp as i, MountMcpHttpOptions as n, MountedMcpHttp as r, AgenticMcpModule as t };