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.
- package/dist/adapters/build.d.mts +1 -1
- package/dist/adapters/cli.d.mts +1 -1
- package/dist/adapters/cli.mjs +9 -6
- package/dist/adapters/dev.d.mts +9 -2
- package/dist/adapters/dev.mjs +1 -1
- package/dist/adapters/embedded.d.mts +1 -1
- package/dist/adapters/mcp.d.mts +1 -1
- package/dist/adapters/mcp.mjs +1 -337
- package/dist/build-server-0jWgMrz-.mjs +338 -0
- package/dist/client/index.d.mts +1 -1
- package/dist/constants.d.mts +8 -1
- package/dist/constants.mjs +8 -1
- package/dist/{context-B_I9fMBc.d.mts → context-UX9qUDK7.d.mts} +1 -1
- package/dist/{dev-DSxKFn-v.mjs → dev-zC5542OV.mjs} +46 -3
- package/dist/{devframe-DgvncQy2.d.mts → devframe-BtVHhmwM.d.mts} +57 -1
- package/dist/helpers/vite.d.mts +1 -1
- package/dist/helpers/vite.mjs +1 -1
- package/dist/http-COX960WK.mjs +114 -0
- package/dist/{index-B4lNDq5v.d.mts → index-ByaMT1Xp.d.mts} +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/node/auth.d.mts +1 -1
- package/dist/node/hub-internals.d.mts +2 -2
- package/dist/node/index.d.mts +2 -2
- package/dist/node/index.mjs +1 -1
- package/dist/recipes/interactive-auth.d.mts +2 -2
- package/dist/{server-BdN41ckC.mjs → server-CAsvfJNK.mjs} +21 -17
- package/dist/{server-DXPq8q0K.d.mts → server-Cfr1NrGQ.d.mts} +14 -3
- package/dist/types/index.d.mts +2 -2
- package/dist/utils/events.d.mts +1 -1
- package/dist/utils/shared-state.d.mts +1 -1
- package/dist/utils/streaming-channel.d.mts +1 -1
- package/package.json +1 -1
|
@@ -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 };
|
package/dist/client/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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";
|
package/dist/constants.d.mts
CHANGED
|
@@ -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 };
|
package/dist/constants.mjs
CHANGED
|
@@ -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,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-
|
|
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
|
-
|
|
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 {
|
|
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 };
|
package/dist/helpers/vite.d.mts
CHANGED
package/dist/helpers/vite.mjs
CHANGED
|
@@ -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-
|
|
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
|
/**
|