devframe 0.7.15 → 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.
- package/bin/devframe.mjs +8 -0
- package/dist/adapters/build.d.mts +8 -1
- package/dist/adapters/build.mjs +5 -3
- package/dist/adapters/cac.d.mts +1 -1
- package/dist/adapters/cac.mjs +1 -1
- package/dist/adapters/cli.d.mts +1 -1
- package/dist/adapters/cli.mjs +1 -1
- package/dist/adapters/dev.d.mts +2 -2
- package/dist/adapters/dev.mjs +1 -1
- package/dist/adapters/embedded.d.mts +1 -1
- package/dist/adapters/mcp.d.mts +45 -3
- package/dist/adapters/mcp.mjs +2 -2
- package/dist/agent-tool-name-C3b5vEwJ.mjs +34 -0
- package/dist/{cac-CjF0haJ6.mjs → cac-B-R_tDGJ.mjs} +19 -14
- package/dist/cli/main.d.mts +11 -0
- package/dist/cli/main.mjs +283 -0
- package/dist/client/index.d.mts +45 -9
- package/dist/client/index.mjs +266 -97
- package/dist/constants.d.mts +7 -1
- package/dist/constants.mjs +7 -1
- package/dist/{context-C_bqRR0i.d.mts → context-7BbaIUSI.d.mts} +1 -1
- package/dist/{context-C1Gr2BGB.mjs → context-C9Cgm1hP.mjs} +1 -1
- package/dist/{dev-OJFr4rx8.mjs → dev-D4M7Veo7.mjs} +22 -11
- package/dist/{devframe-BADsX91-.d.mts → devframe-Dsjn_Xtq.d.mts} +78 -15
- package/dist/diagnostics-B5-qHeqD.mjs +94 -0
- package/dist/{diagnostics-BXwBQmoN.mjs → diagnostics-hwjXp_UV.mjs} +8 -0
- package/dist/{dump-Cz7yVvsB.mjs → dump-CgZShDRB.mjs} +82 -6
- package/dist/{build-server-cANnMwg0.mjs → fetch-BZyK4v6W.mjs} +225 -51
- package/dist/helpers/vite.d.mts +1 -1
- package/dist/helpers/vite.mjs +2 -2
- package/dist/{host-h3-SjwRTwkE.mjs → host-h3-Kz7t5Xab.mjs} +83 -28
- package/dist/http-BghOqfTD.mjs +38 -0
- package/dist/{index-Brcd2V4t.d.mts → index-CeBbry0R.d.mts} +2 -2
- package/dist/{index-DgsLFhZg.d.mts → index-Dbw1p5ch.d.mts} +27 -3
- package/dist/{index-YDSJgsyG.d.mts → index-vAX6bYuc.d.mts} +1 -1
- package/dist/index.d.mts +5 -5
- package/dist/instance-registry-D6fxYu36.mjs +190 -0
- package/dist/node/auth.d.mts +2 -2
- package/dist/node/hub-internals.d.mts +2 -2
- package/dist/node/hub-internals.mjs +1 -1
- package/dist/node/index.d.mts +104 -6
- package/dist/node/index.mjs +5 -4
- package/dist/recipes/common-rpc-functions.d.mts +33 -33
- package/dist/recipes/common-rpc-functions.mjs +6 -6
- package/dist/recipes/interactive-auth.d.mts +2 -2
- package/dist/recipes/interactive-auth.mjs +13 -13
- package/dist/rpc/dump.d.mts +1 -1
- package/dist/rpc/dump.mjs +1 -1
- package/dist/rpc/index.d.mts +3 -3
- package/dist/rpc/index.mjs +4 -4
- package/dist/rpc/transports/ws-client.d.mts +1 -1
- package/dist/rpc/transports/ws-client.mjs +1 -1
- package/dist/rpc/transports/ws-server.d.mts +1 -1
- package/dist/rpc/transports/ws-server.mjs +1 -1
- package/dist/{serialization-DpLXCy13.mjs → serialization-C8Mnw9hK.mjs} +1 -1
- package/dist/{server-BFsuZI9e.mjs → server-CcAPxuoT.mjs} +2 -2
- package/dist/{server-Bu5iB3aV.d.mts → server-VLQJouOO.d.mts} +2 -2
- package/dist/simple-schema-BDzLeJDk.d.mts +102 -0
- package/dist/simple-schema-DQPZrAaZ.mjs +154 -0
- package/dist/{storage-D_Xy9v1l.mjs → storage-Dzoc3NVC.mjs} +2 -62
- package/dist/{transports-BmDVn4SF.mjs → transports-vhizgqXM.mjs} +1 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/{types-CrzNxXKq.d.mts → types-CnJSgRVa.d.mts} +33 -19
- package/dist/utils/agent-tool-name.d.mts +27 -0
- package/dist/utils/agent-tool-name.mjs +34 -0
- package/dist/utils/events.d.mts +1 -1
- package/dist/utils/shared-state.d.mts +1 -1
- package/dist/utils/simple-schema.d.mts +2 -0
- package/dist/utils/simple-schema.mjs +154 -0
- package/dist/utils/streaming-channel.d.mts +1 -1
- package/dist/{ws-client-DSXiI2h7.d.mts → ws-client-B6Tnr0ic.d.mts} +1 -1
- package/dist/{ws-server-9-wn7MNQ.d.mts → ws-server-D_Vjtums.d.mts} +1 -1
- package/package.json +18 -34
- package/skills/devframe/SKILL.md +1 -1
- package/dist/http-B2P55ZXg.mjs +0 -114
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as createHostContext } from "./host-h3-
|
|
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/
|
|
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
|
|
91
|
-
*
|
|
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
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
return
|
|
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
|
-
*
|
|
103
|
-
*
|
|
104
|
-
|
|
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 `
|
|
107
|
-
* as `{ type: 'object', properties: {} }`).
|
|
122
|
+
* Returns `{ type: 'object', properties: {} }` when there are no args.
|
|
108
123
|
* @internal
|
|
109
124
|
*/
|
|
110
|
-
function
|
|
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-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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(
|
|
313
|
+
server.setRequestHandler("tools/call", async (request) => {
|
|
245
314
|
const { name, arguments: args } = request.params;
|
|
246
315
|
try {
|
|
247
|
-
const tool =
|
|
248
|
-
|
|
249
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 };
|
package/dist/helpers/vite.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { d as McpRouteOptions, p as DevframeAuthHandler, r as DevframeDefinition } from "../devframe-
|
|
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
|
/**
|
package/dist/helpers/vite.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_WS_ROUTE } from "../constants.mjs";
|
|
2
|
-
import {
|
|
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-
|
|
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-
|
|
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 {
|
|
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: [
|
|
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
|
-
|
|
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 =
|
|
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-
|
|
2
|
-
import { n as InternalAnonymousAuthStorage } from "./context-
|
|
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`
|