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.
@@ -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/adapters/build.d.ts
4
4
  interface CreateBuildOptions {
@@ -1,4 +1,4 @@
1
- import { Ct as CliFlagsSchema, Et as parseCliFlags, Tt as defineCliFlags, r as DevframeDefinition, wt as InferCliFlags } from "../devframe-DgvncQy2.mjs";
1
+ import { Dt as parseCliFlags, Et as defineCliFlags, Tt as InferCliFlags, r as DevframeDefinition, wt as CliFlagsSchema } from "../devframe-BtVHhmwM.mjs";
2
2
  import { CAC } from "cac";
3
3
  import { H3 } from "h3";
4
4
 
@@ -1,6 +1,6 @@
1
1
  import { n as colors } from "../diagnostics-reporter-CsIG85Q5.mjs";
2
2
  import { createBuild } from "./build.mjs";
3
- import { n as resolveDevServerPort, t as createDevServer } from "../dev-DSxKFn-v.mjs";
3
+ import { n as resolveDevServerPort, t as createDevServer } from "../dev-zC5542OV.mjs";
4
4
  import process from "node:process";
5
5
  import cac from "cac";
6
6
  import { safeParse } from "valibot";
@@ -82,7 +82,7 @@ function createCli(d, options = {}) {
82
82
  const defaultPort = options.defaultPort ?? d.cli?.port ?? 9999;
83
83
  const defaultHost = d.cli?.host ?? "localhost";
84
84
  const cli = cac(d.cli?.command ?? d.id);
85
- const devCommand = cli.command("[...args]", "Start a local dev server").option("--port <port>", "Port to listen on").option("--host <host>", "Host to bind to", { default: defaultHost }).option("--open", "Open the browser on start").option("--no-open", "Do not open the browser");
85
+ const devCommand = cli.command("[...args]", "Start a local dev server").option("--port <port>", "Port to listen on").option("--host <host>", "Host to bind to", { default: defaultHost }).option("--open", "Open the browser on start").option("--no-open", "Do not open the browser").option("--mcp", "Expose an MCP server over HTTP at /__mcp (use --no-mcp to disable) [experimental]");
86
86
  if (d.cli?.flags) for (const [key, schema] of Object.entries(d.cli.flags)) {
87
87
  const optionName = flagKeyToOption(key);
88
88
  const description = schema.description ?? "";
@@ -92,13 +92,16 @@ function createCli(d, options = {}) {
92
92
  devCommand.action(async (_args, rawFlags) => {
93
93
  const flags = resolveTypedFlags(d, rawFlags);
94
94
  const host = flags.host ?? defaultHost;
95
+ const port = flags.port ?? await resolveDevServerPort(d, {
96
+ host,
97
+ defaultPort
98
+ });
99
+ const mcp = flags.mcp;
95
100
  await createDevServer(d, {
96
101
  host,
97
- port: flags.port ?? await resolveDevServerPort(d, {
98
- host,
99
- defaultPort
100
- }),
102
+ port,
101
103
  flags,
104
+ mcp,
102
105
  onReady: options.onReady
103
106
  });
104
107
  });
@@ -1,5 +1,5 @@
1
- import { l as DevframeWsOptions, r as DevframeDefinition } from "../devframe-DgvncQy2.mjs";
2
- import { n as StartedServer } from "../server-DXPq8q0K.mjs";
1
+ import { l as DevframeWsOptions, r as DevframeDefinition, u as McpRouteOptions } from "../devframe-BtVHhmwM.mjs";
2
+ import { n as StartedServer } from "../server-Cfr1NrGQ.mjs";
3
3
  import { H3 } from "h3";
4
4
 
5
5
  //#region src/adapters/dev.d.ts
@@ -51,6 +51,13 @@ interface CreateDevServerOptions {
51
51
  * sources; a string opens that relative path.
52
52
  */
53
53
  openBrowser?: boolean | string;
54
+ /**
55
+ * Expose a route-based MCP server on the dev server (Streamable-HTTP).
56
+ * Overrides `def.cli?.mcp`; `undefined` falls through to it. `false`
57
+ * disables the route regardless of the definition default. See
58
+ * {@link McpRouteOptions}.
59
+ */
60
+ mcp?: boolean | McpRouteOptions;
54
61
  /**
55
62
  * Called once the WS server is bound. Devframe stays headless
56
63
  * otherwise — wire this if you want a startup banner.
@@ -1,2 +1,2 @@
1
- import { n as resolveDevServerPort, t as createDevServer } from "../dev-DSxKFn-v.mjs";
1
+ import { n as resolveDevServerPort, t as createDevServer } from "../dev-zC5542OV.mjs";
2
2
  export { createDevServer, resolveDevServerPort };
@@ -1,4 +1,4 @@
1
- import { m as DevframeNodeContext, r as DevframeDefinition } from "../devframe-DgvncQy2.mjs";
1
+ import { h as DevframeNodeContext, r as DevframeDefinition } from "../devframe-BtVHhmwM.mjs";
2
2
 
3
3
  //#region src/adapters/embedded.d.ts
4
4
  interface CreateEmbeddedOptions {
@@ -1,4 +1,4 @@
1
- import { r as DevframeDefinition } from "../devframe-DgvncQy2.mjs";
1
+ import { r as DevframeDefinition } from "../devframe-BtVHhmwM.mjs";
2
2
  //#region src/adapters/mcp/build-server.d.ts
3
3
  interface CreateMcpServerOptions {
4
4
  /**
@@ -1,338 +1,2 @@
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
1
+ import { n as createMcpServer } from "../build-server-0jWgMrz-.mjs";
338
2
  export { createMcpServer };