devframe 0.9.20 → 0.10.1
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/_shared-B2PZsp-J.mjs +191 -0
- package/dist/{_shared-BIUet_Jh.d.mts → _shared-D1ebaX_J.d.mts} +13 -14
- package/dist/adapters/build.d.mts +3 -4
- package/dist/adapters/build.mjs +4 -5
- package/dist/adapters/cac.d.mts +3 -4
- package/dist/adapters/cac.mjs +3 -3
- package/dist/adapters/dev.d.mts +8 -8
- package/dist/adapters/dev.mjs +2 -2
- package/dist/adapters/embedded.d.mts +1 -1
- package/dist/adapters/initiate.d.mts +8 -8
- package/dist/adapters/initiate.mjs +1 -1
- package/dist/adapters/mcp.d.mts +4 -142
- package/dist/adapters/mcp.mjs +1 -597
- package/dist/agentic-DLqHMOyN.d.mts +30 -0
- package/dist/browser-agent-Pu-FLk6I.mjs +26 -0
- package/dist/browser-agent-rpc-BXhoSh1z.mjs +70 -0
- package/dist/cli/main.mjs +12 -260
- package/dist/client/index.d.mts +6 -6
- package/dist/client/index.mjs +12 -87
- package/dist/constants.d.mts +2 -1
- package/dist/constants.mjs +2 -1
- package/dist/{context-BLHBDQcZ.d.mts → context--tVkJw3W.d.mts} +127 -47
- package/dist/{context-BLXXjoBW.mjs → context-BqpgC5tq.mjs} +55 -5
- package/dist/{context-jXO5vzjH.mjs → context-CHpyLX8W.mjs} +1 -1
- package/dist/{context-BA5FPz7K.d.mts → context-D3qBv0IX.d.mts} +1 -1
- package/dist/{dev-BdKKOH8S.mjs → dev-BTbLJekh.mjs} +16 -24
- package/dist/{diagnostics-DZA6KQ_Z.mjs → diagnostics-Cx4_VjlG.mjs} +11 -3
- package/dist/dist-CO98PBnQ.mjs +265 -0
- package/dist/{host-agent-C3MBpNCd.mjs → host-agent-mIbQ6Qi5.mjs} +14 -13
- package/dist/import-runtime-module-BzlEQ9PI.mjs +24 -0
- package/dist/in-page-channel/index.d.mts +16 -12
- package/dist/in-page-channel/index.mjs +42 -14
- package/dist/index-Bia1vKjL.d.mts +218 -0
- package/dist/index-CeEtxDmK.d.mts +78 -0
- package/dist/{index-r6NstVmM.d.mts → index-D-2IdY1I.d.mts} +2 -2
- package/dist/{index-CiA6C705.d.mts → index-DuEm0sDH.d.mts} +1 -1
- package/dist/index.d.mts +4 -11
- package/dist/index.mjs +1 -10
- package/dist/{instance-registry-DCq762YJ.mjs → instance-registry-DtvYxc0Y.mjs} +1 -1
- package/dist/{instance-shell-C6Vg97lg.d.mts → instance-shell-CXnpSuK7.d.mts} +31 -5
- package/dist/{instance-shell-yfrM3_vK.mjs → instance-shell-wC2sfEU_.mjs} +7 -7
- package/dist/interactive-auth-BCa_iamv.mjs +384 -0
- package/dist/internal/index.d.mts +66 -22
- package/dist/internal/index.mjs +143 -8
- package/dist/mrmime-40h5OOIp.mjs +448 -0
- package/dist/node/auth.d.mts +2 -2
- package/dist/node/hub-internals.d.mts +2 -2
- package/dist/node/hub-internals.mjs +2 -2
- package/dist/node/index.d.mts +2 -4
- package/dist/node/index.mjs +2 -2
- package/dist/{nostics-0sKe5HIj.d.mts → nostics-DWvukRwa.d.mts} +11 -2
- package/dist/{origin-D_eBKEzr.mjs → origin-DZit9g29.mjs} +1 -21
- package/dist/recipes/interactive-auth.d.mts +2 -2
- package/dist/recipes/interactive-auth.mjs +1 -231
- package/dist/{remote-assets-D0StJVLp.mjs → remote-assets-B9MeWmW6.mjs} +2 -2
- package/dist/rpc/client.d.mts +1 -1
- package/dist/rpc/client.mjs +1 -1
- package/dist/rpc/dump.d.mts +2 -1
- package/dist/rpc/index.d.mts +3 -2
- package/dist/rpc/server.d.mts +1 -1
- package/dist/rpc/server.mjs +1 -1
- package/dist/rpc/transports/sse-client.d.mts +1 -1
- package/dist/rpc/transports/sse-server.d.mts +3 -3
- package/dist/rpc/transports/sse-server.mjs +1 -1
- package/dist/rpc/transports/ws-bun.d.mts +2 -2
- package/dist/rpc/transports/ws-bun.mjs +1 -1
- package/dist/rpc/transports/ws-client.d.mts +1 -1
- package/dist/rpc/transports/ws-deno.d.mts +2 -2
- package/dist/rpc/transports/ws-deno.mjs +1 -1
- package/dist/rpc/transports/ws-server.d.mts +1 -1
- package/dist/rpc/transports/ws-server.mjs +1 -1
- package/dist/{rpc-core-BlL-Ip8n.d.mts → rpc-core-B6n9MQsc.d.mts} +4 -4
- package/dist/{rpc-core-CvkU23bQ.mjs → rpc-core-BXyMjbKG.mjs} +1 -1
- package/dist/{services-install-Ben4yh52.mjs → services-install-D5YUwFXW.mjs} +17 -1
- package/dist/{sse-client-BcbYwXdD.d.mts → sse-client-DNdscfu_.d.mts} +2 -2
- package/dist/{storage-B70AXK64.mjs → storage-CHD1IkqU.mjs} +1 -1
- package/dist/tool-input-BAQXvvqL.mjs +393 -0
- package/dist/types/index.d.mts +4 -4
- package/dist/{types-D2vGd7-2.d.mts → types-BmDbfHCx.d.mts} +2 -3
- package/dist/utils/debounce.d.mts +50 -0
- package/dist/utils/debounce.mjs +88 -0
- package/dist/utils/events.d.mts +1 -1
- package/dist/utils/launch-editor.d.mts +8 -0
- package/dist/utils/launch-editor.mjs +36 -1
- package/dist/utils/nostics.d.mts +2 -2
- package/dist/utils/remote-assets.mjs +1 -1
- package/dist/utils/serve-static.mjs +1 -1
- package/dist/utils/shared-state.d.mts +1 -1
- package/dist/utils/simple-schema.d.mts +3 -2
- package/dist/utils/streaming-channel.d.mts +1 -1
- package/dist/utils/url.d.mts +31 -0
- package/dist/utils/url.mjs +58 -0
- package/dist/{ws-client-CNpP6Ium.d.mts → ws-client-6Mrz6eC4.d.mts} +2 -2
- package/dist/{ws-server-BB8jhxeT.d.mts → ws-server-y2R76aSW.d.mts} +2 -2
- package/package.json +10 -11
- package/dist/_shared-U8D-xzrp.mjs +0 -115
- package/dist/agent-tool-name-DGzeuWcO.mjs +0 -31
- package/dist/client-assets-DNdhagGm.mjs +0 -16
- package/dist/nostics-DD6gsDZA.mjs +0 -108
- package/dist/recipes/common-rpc-functions.d.mts +0 -70
- package/dist/recipes/common-rpc-functions.mjs +0 -115
- package/dist/simple-schema-Bi40l-P6.mjs +0 -154
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { nanoid } from "./utils/nanoid.mjs";
|
|
2
|
+
import { n as onBrowserAgentToolsChanged, t as listBrowserAgentTools } from "./browser-agent-Pu-FLk6I.mjs";
|
|
3
|
+
//#region src/client/client-id.ts
|
|
4
|
+
const CLIENT_ID_STORAGE_KEY = "devframe:client-id";
|
|
5
|
+
let memoryClientId;
|
|
6
|
+
/**
|
|
7
|
+
* This browser tab's stable client id: one nanoid per tab, persisted in
|
|
8
|
+
* `sessionStorage` so it survives page reloads and RPC reconnects. The node
|
|
9
|
+
* side uses it to tell connected tabs apart across reconnects (see #394).
|
|
10
|
+
*
|
|
11
|
+
* Tab duplication copies `sessionStorage`, so two tabs can briefly share an id
|
|
12
|
+
* until per-tab disambiguation lands with the wider tab-metadata work.
|
|
13
|
+
*/
|
|
14
|
+
function resolveClientId(win = globalThis.window) {
|
|
15
|
+
try {
|
|
16
|
+
const storage = win?.sessionStorage;
|
|
17
|
+
if (storage) {
|
|
18
|
+
let id = storage.getItem(CLIENT_ID_STORAGE_KEY);
|
|
19
|
+
if (!id) {
|
|
20
|
+
id = nanoid();
|
|
21
|
+
storage.setItem(CLIENT_ID_STORAGE_KEY, id);
|
|
22
|
+
}
|
|
23
|
+
return id;
|
|
24
|
+
}
|
|
25
|
+
} catch {}
|
|
26
|
+
memoryClientId ??= nanoid();
|
|
27
|
+
return memoryClientId;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/client/browser-agent-rpc.ts
|
|
31
|
+
/** Mirror this document's browser-agent registry over its existing RPC connection. */
|
|
32
|
+
function setupBrowserAgentRpcBridge(rpc) {
|
|
33
|
+
rpc.client.register({
|
|
34
|
+
name: "devframe:agent:invoke-client-tool",
|
|
35
|
+
type: "action",
|
|
36
|
+
jsonSerializable: true,
|
|
37
|
+
handler: async (id, args) => {
|
|
38
|
+
const tool = listBrowserAgentTools().find((tool) => tool.id === id);
|
|
39
|
+
if (!tool) throw new Error(`[devframe/agent] browser tool "${id}" not found`);
|
|
40
|
+
return await tool.invoke(args);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
let queued = false;
|
|
44
|
+
let disposed = false;
|
|
45
|
+
let lastSyncedCount = 0;
|
|
46
|
+
const sync = () => {
|
|
47
|
+
if (queued || disposed) return;
|
|
48
|
+
queued = true;
|
|
49
|
+
queueMicrotask(async () => {
|
|
50
|
+
queued = false;
|
|
51
|
+
if (disposed) return;
|
|
52
|
+
const manifests = listBrowserAgentTools().map(({ invoke: _, ...manifest }) => manifest);
|
|
53
|
+
if (manifests.length === 0 && lastSyncedCount === 0) return;
|
|
54
|
+
lastSyncedCount = manifests.length;
|
|
55
|
+
await rpc.callOptional("devframe:agent:sync-client-tools", resolveClientId(), manifests).catch(() => {});
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
const stopTools = onBrowserAgentToolsChanged(sync);
|
|
59
|
+
const stopConnection = rpc.events.on("connection:status", (status) => {
|
|
60
|
+
if (status === "connected") sync();
|
|
61
|
+
});
|
|
62
|
+
sync();
|
|
63
|
+
return () => {
|
|
64
|
+
disposed = true;
|
|
65
|
+
stopTools();
|
|
66
|
+
stopConnection();
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
export { setupBrowserAgentRpcBridge };
|
package/dist/cli/main.mjs
CHANGED
|
@@ -1,118 +1,19 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import { t as
|
|
3
|
-
import { n as listLiveDevframeInstances, r as probeDevframeOrigin } from "../instance-registry-DCq762YJ.mjs";
|
|
4
|
-
import { t as toAgentToolName } from "../agent-tool-name-DGzeuWcO.mjs";
|
|
1
|
+
import { t as diagnostics } from "../diagnostics-Cx4_VjlG.mjs";
|
|
2
|
+
import { t as importRuntimeModule } from "../import-runtime-module-BzlEQ9PI.mjs";
|
|
5
3
|
import process from "node:process";
|
|
6
|
-
import { joinURL } from "ufo";
|
|
7
4
|
import { cac } from "cac";
|
|
8
|
-
//#region src/cli/
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
*/
|
|
13
|
-
function resolveAuthToken(authToken, record) {
|
|
14
|
-
return typeof authToken === "function" ? authToken(record) : authToken;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Build the request headers the connector sends to one instance's MCP route:
|
|
18
|
-
* the instance's own (loopback) `origin` so the route's origin gate accepts
|
|
19
|
-
* this native client, plus `Authorization: Bearer <token>` when a bearer is
|
|
20
|
-
* configured. The bearer appears **only** here, never in the connection URL,
|
|
21
|
-
* the registry records, or the indexed results. Exported for focused tests.
|
|
22
|
-
*/
|
|
23
|
-
function buildInstanceRequestHeaders(url, token) {
|
|
24
|
-
const headers = { origin: new URL(url).origin };
|
|
25
|
-
if (token) headers.authorization = `Bearer ${token}`;
|
|
26
|
-
return headers;
|
|
5
|
+
//#region src/cli/main.ts
|
|
6
|
+
/** Parse the repeatable `--port` flag value(s) from cac into numbers. */
|
|
7
|
+
function parsePortsFlag(value) {
|
|
8
|
+
return (Array.isArray(value) ? value : value === void 0 ? [] : [value]).map((v) => Number(v)).filter((n) => Number.isInteger(n) && n > 0 && n < 65536);
|
|
27
9
|
}
|
|
28
|
-
const INDEX_TOOL = toAgentToolName("devframe:connect:list-instances");
|
|
29
|
-
const CALL_TOOL = toAgentToolName("devframe:connect:call-tool");
|
|
30
|
-
const MCP_DISABLED_HINT = "This instance runs without an MCP route. Restart it with the --mcp flag (or set `cli.mcp: true` on its definition) to expose its tools, then list instances again.";
|
|
31
|
-
const GATEWAY_TOOLS = [{
|
|
32
|
-
name: INDEX_TOOL,
|
|
33
|
-
title: "Discover running devframes",
|
|
34
|
-
description: "Discover every running devframe dev server on this machine and list each one's MCP tools. Call this FIRST, before assuming which devtools are available; the result names the instance (id, project root, origin) and the port to pass to the call tool. Safe to call freely.",
|
|
35
|
-
inputSchema: {
|
|
36
|
-
type: "object",
|
|
37
|
-
properties: {}
|
|
38
|
-
},
|
|
39
|
-
annotations: {
|
|
40
|
-
readOnlyHint: true,
|
|
41
|
-
destructiveHint: false
|
|
42
|
-
}
|
|
43
|
-
}, {
|
|
44
|
-
name: CALL_TOOL,
|
|
45
|
-
title: "Call a devframe tool",
|
|
46
|
-
description: "Invoke one MCP tool on one running devframe instance discovered via the list-instances tool. Pass the instance's port, the tool name, and the tool's arguments object.",
|
|
47
|
-
inputSchema: {
|
|
48
|
-
type: "object",
|
|
49
|
-
properties: {
|
|
50
|
-
port: {
|
|
51
|
-
type: "number",
|
|
52
|
-
description: "The instance's port, from the list-instances tool."
|
|
53
|
-
},
|
|
54
|
-
tool: {
|
|
55
|
-
type: "string",
|
|
56
|
-
description: "Tool name, from the instance's tool list."
|
|
57
|
-
},
|
|
58
|
-
args: {
|
|
59
|
-
type: "object",
|
|
60
|
-
description: "Arguments object for the tool. Omit for zero-argument tools."
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
required: ["port", "tool"],
|
|
64
|
-
additionalProperties: false
|
|
65
|
-
}
|
|
66
|
-
}];
|
|
67
10
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* validated; credit is due there for the architecture this connector follows.
|
|
71
|
-
* It exposes two gateway tools:
|
|
72
|
-
* `devframe_connect_list-instances` (discover running devframe instances via
|
|
73
|
-
* the instance registry and list each one's MCP tools) and
|
|
74
|
-
* `devframe_connect_call-tool` (invoke one tool on one instance over its
|
|
75
|
-
* Streamable-HTTP endpoint), and holds no domain knowledge of its own.
|
|
11
|
+
* Load the connector from the optional `@devframes/agentic` peer, mapping a
|
|
12
|
+
* failed load (typically: the peer is not installed) to a thrown `DF0046`.
|
|
76
13
|
*/
|
|
77
|
-
async function
|
|
78
|
-
const sdk = await importSdk();
|
|
79
|
-
const server = new sdk.Server({
|
|
80
|
-
name: "devframe-connect",
|
|
81
|
-
version: "0.0.0"
|
|
82
|
-
}, { capabilities: { tools: {} } });
|
|
83
|
-
server.setRequestHandler("tools/list", async () => ({ tools: GATEWAY_TOOLS }));
|
|
84
|
-
server.setRequestHandler("tools/call", async (request) => {
|
|
85
|
-
const { name, arguments: args } = request.params;
|
|
86
|
-
try {
|
|
87
|
-
if (name === INDEX_TOOL) return textResult(await index(sdk, options));
|
|
88
|
-
if (name === CALL_TOOL) return textResult(await call(sdk, options, args ?? {}));
|
|
89
|
-
return errorResult({
|
|
90
|
-
message: `unknown tool "${name}"`,
|
|
91
|
-
fix: `Call ${INDEX_TOOL} or ${CALL_TOOL}.`
|
|
92
|
-
});
|
|
93
|
-
} catch (error) {
|
|
94
|
-
return errorResult(toErrorPayload(error));
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
const transport = new sdk.StdioServerTransport();
|
|
98
|
-
await server.connect(transport);
|
|
99
|
-
return { stop: async () => {
|
|
100
|
-
await server.close();
|
|
101
|
-
} };
|
|
102
|
-
}
|
|
103
|
-
async function importSdk() {
|
|
14
|
+
async function importConnect() {
|
|
104
15
|
try {
|
|
105
|
-
|
|
106
|
-
import("@modelcontextprotocol/server"),
|
|
107
|
-
import("@modelcontextprotocol/server/stdio"),
|
|
108
|
-
import("@modelcontextprotocol/client")
|
|
109
|
-
]);
|
|
110
|
-
return {
|
|
111
|
-
Server: serverMod.Server,
|
|
112
|
-
StdioServerTransport: stdioMod.StdioServerTransport,
|
|
113
|
-
Client: clientMod.Client,
|
|
114
|
-
StreamableHTTPClientTransport: clientMod.StreamableHTTPClientTransport
|
|
115
|
-
};
|
|
16
|
+
return await importRuntimeModule("@devframes/agentic/connect");
|
|
116
17
|
} catch (error) {
|
|
117
18
|
const reason = error instanceof Error ? error.message : String(error);
|
|
118
19
|
throw diagnostics.DF0046({
|
|
@@ -121,156 +22,6 @@ async function importSdk() {
|
|
|
121
22
|
});
|
|
122
23
|
}
|
|
123
24
|
}
|
|
124
|
-
/** Discover instances: registry (prune-on-read) + explicit port probes. */
|
|
125
|
-
async function index(sdk, options) {
|
|
126
|
-
const { live } = await listLiveDevframeInstances({
|
|
127
|
-
instancesDir: options.instancesDir,
|
|
128
|
-
timeoutMs: options.timeoutMs
|
|
129
|
-
});
|
|
130
|
-
const records = [...live];
|
|
131
|
-
for (const port of options.ports ?? []) {
|
|
132
|
-
if (records.some((r) => r.port === port)) continue;
|
|
133
|
-
const probed = await probePort(port, options.timeoutMs);
|
|
134
|
-
if (probed) records.push(probed);
|
|
135
|
-
}
|
|
136
|
-
const instances = await Promise.all(records.map(async (record) => {
|
|
137
|
-
const { mcp, ...rest } = record;
|
|
138
|
-
const entry = {
|
|
139
|
-
...rest,
|
|
140
|
-
mcp: null
|
|
141
|
-
};
|
|
142
|
-
if (!mcp) {
|
|
143
|
-
entry.hint = MCP_DISABLED_HINT;
|
|
144
|
-
return entry;
|
|
145
|
-
}
|
|
146
|
-
const url = `${record.origin}${mcp.path}`;
|
|
147
|
-
try {
|
|
148
|
-
entry.mcp = {
|
|
149
|
-
url,
|
|
150
|
-
tools: await listInstanceTools(sdk, url, resolveAuthToken(options.authToken, record))
|
|
151
|
-
};
|
|
152
|
-
} catch (error) {
|
|
153
|
-
entry.mcp = {
|
|
154
|
-
url,
|
|
155
|
-
error: error instanceof Error ? error.message : String(error)
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
return entry;
|
|
159
|
-
}));
|
|
160
|
-
return {
|
|
161
|
-
instances,
|
|
162
|
-
...instances.length === 0 ? { hint: "No running devframe instances found. Start a devframe dev server (with --mcp for tools), or pass --port <n> to devframe connect if the instance predates the registry." } : {}
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Probe an explicit port for a devframe serving `__connection.json` at `/`,
|
|
167
|
-
* reusing the registry's origin-candidate probe (a `localhost`-bound server
|
|
168
|
-
* may listen on either address family).
|
|
169
|
-
*/
|
|
170
|
-
async function probePort(port, timeoutMs) {
|
|
171
|
-
const probed = await probeDevframeOrigin(`http://localhost:${port}`, "/", timeoutMs);
|
|
172
|
-
if (!probed) return null;
|
|
173
|
-
const mcpPath = probed.meta.mcp ? joinURL("/", probed.meta.mcp.path) : null;
|
|
174
|
-
return {
|
|
175
|
-
pid: -1,
|
|
176
|
-
port,
|
|
177
|
-
origin: probed.origin,
|
|
178
|
-
basePath: "/",
|
|
179
|
-
id: `port-${port}`,
|
|
180
|
-
rootDir: "",
|
|
181
|
-
mcp: mcpPath ? { path: mcpPath } : null,
|
|
182
|
-
startedAt: 0
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
async function listInstanceTools(sdk, url, token) {
|
|
186
|
-
return withInstanceClient(sdk, url, token, async (client) => {
|
|
187
|
-
return (await client.listTools()).tools.map((tool) => ({
|
|
188
|
-
name: tool.name,
|
|
189
|
-
description: tool.description
|
|
190
|
-
}));
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
async function call(sdk, options, args) {
|
|
194
|
-
if (typeof args.port !== "number" || typeof args.tool !== "string") throw diagnostics.DF0049();
|
|
195
|
-
const { live } = await listLiveDevframeInstances({
|
|
196
|
-
instancesDir: options.instancesDir,
|
|
197
|
-
timeoutMs: options.timeoutMs
|
|
198
|
-
});
|
|
199
|
-
const record = live.find((r) => r.port === args.port) ?? await probePort(args.port, options.timeoutMs);
|
|
200
|
-
if (!record) throw diagnostics.DF0050({ port: args.port });
|
|
201
|
-
if (!record.mcp) throw diagnostics.DF0051({ port: args.port });
|
|
202
|
-
return withInstanceClient(sdk, `${record.origin}${record.mcp.path}`, resolveAuthToken(options.authToken, record), async (client) => {
|
|
203
|
-
const result = await client.callTool({
|
|
204
|
-
name: args.tool,
|
|
205
|
-
arguments: args.args ?? {}
|
|
206
|
-
});
|
|
207
|
-
return {
|
|
208
|
-
instance: {
|
|
209
|
-
id: record.id,
|
|
210
|
-
port: record.port
|
|
211
|
-
},
|
|
212
|
-
tool: args.tool,
|
|
213
|
-
isError: result.isError ?? false,
|
|
214
|
-
content: result.content,
|
|
215
|
-
...result.structuredContent ? { structuredContent: result.structuredContent } : {}
|
|
216
|
-
};
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
async function withInstanceClient(sdk, url, token, fn) {
|
|
220
|
-
const transport = new sdk.StreamableHTTPClientTransport(new URL(url), { requestInit: { headers: buildInstanceRequestHeaders(url, token) } });
|
|
221
|
-
const client = new sdk.Client({
|
|
222
|
-
name: "devframe-connect",
|
|
223
|
-
version: "0.0.0"
|
|
224
|
-
}, { versionNegotiation: { mode: "auto" } });
|
|
225
|
-
await client.connect(transport);
|
|
226
|
-
try {
|
|
227
|
-
return await fn(client);
|
|
228
|
-
} finally {
|
|
229
|
-
await client.close().catch(() => {});
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
function textResult(value) {
|
|
233
|
-
return { content: [{
|
|
234
|
-
type: "text",
|
|
235
|
-
text: JSON.stringify(value, null, 2)
|
|
236
|
-
}] };
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Project a thrown value into the connector's structured error payload. A
|
|
240
|
-
* nostics `Diagnostic` carries its code, `fix`, and docs URL across so the
|
|
241
|
-
* calling agent gets the actionable next step.
|
|
242
|
-
*/
|
|
243
|
-
function toErrorPayload(error) {
|
|
244
|
-
if (error instanceof Diagnostic) return {
|
|
245
|
-
code: error.code,
|
|
246
|
-
message: error.message,
|
|
247
|
-
...error.fix ? { fix: error.fix } : {},
|
|
248
|
-
...error.docs ? { docs: error.docs } : {}
|
|
249
|
-
};
|
|
250
|
-
return {
|
|
251
|
-
message: error instanceof Error ? error.message : String(error),
|
|
252
|
-
...error && typeof error === "object" && "fix" in error && typeof error.fix === "string" ? { fix: error.fix } : {}
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
function errorResult(error) {
|
|
256
|
-
return {
|
|
257
|
-
isError: true,
|
|
258
|
-
content: [{
|
|
259
|
-
type: "text",
|
|
260
|
-
text: JSON.stringify({ error }, null, 2)
|
|
261
|
-
}]
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
/** Parse the repeatable `--port` flag value(s) from cac into numbers. */
|
|
265
|
-
function parsePortsFlag(value) {
|
|
266
|
-
return (Array.isArray(value) ? value : value === void 0 ? [] : [value]).map((v) => Number(v)).filter((n) => Number.isInteger(n) && n > 0 && n < 65536);
|
|
267
|
-
}
|
|
268
|
-
/** Keep the connector process alive until the stdio transport closes it. */
|
|
269
|
-
function keepAlive() {
|
|
270
|
-
process.stdin.resume();
|
|
271
|
-
}
|
|
272
|
-
//#endregion
|
|
273
|
-
//#region src/cli/main.ts
|
|
274
25
|
/**
|
|
275
26
|
* The `devframe` bin is the framework's own CLI, distinct from the per-app
|
|
276
27
|
* CLI shells authors build with `createCac(definition)`. It hosts the
|
|
@@ -279,6 +30,7 @@ function keepAlive() {
|
|
|
279
30
|
async function runDevframeCli(argv = process.argv) {
|
|
280
31
|
const cli = cac("devframe");
|
|
281
32
|
cli.command("connect", "Run the devframe MCP connector on stdio (discovers running devframe dev servers and proxies their tools)").option("--port <port>", "Probe an explicit port besides the instance registry (repeatable)").option("--instances-dir <dir>", "Override the instance registry directory (default: ~/.devframe/instances, or $DEVFRAME_INSTANCES_DIR)").option("--timeout <ms>", "Probe timeout per instance in milliseconds", { default: 1e3 }).action(async (options) => {
|
|
33
|
+
const { startConnectServer } = await importConnect();
|
|
282
34
|
await startConnectServer({
|
|
283
35
|
ports: parsePortsFlag(options.port),
|
|
284
36
|
instancesDir: options.instancesDir,
|
|
@@ -290,7 +42,7 @@ async function runDevframeCli(argv = process.argv) {
|
|
|
290
42
|
*/
|
|
291
43
|
authToken: process.env.DEVFRAME_MCP_AUTH_TOKEN
|
|
292
44
|
});
|
|
293
|
-
|
|
45
|
+
process.stdin.resume();
|
|
294
46
|
});
|
|
295
47
|
cli.help();
|
|
296
48
|
cli.parse(argv, { run: false });
|
package/dist/client/index.d.mts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { t as
|
|
5
|
-
import { t as
|
|
6
|
-
import {
|
|
1
|
+
import { i as BirpcReturn, r as BirpcOptions } from "../index-Bia1vKjL.mjs";
|
|
2
|
+
import { f as RpcCacheManager, p as RpcCacheOptions } from "../index-DuEm0sDH.mjs";
|
|
3
|
+
import { h as RpcFunctionDefinition, w as RpcFunctionsCollector } from "../types-BmDbfHCx.mjs";
|
|
4
|
+
import { Ct as DevframeRpcClientFunctions, Kt as EventEmitter, at as DevframeServiceScopeOf, b as StreamSink, bt as ScopedSharedStates, d as RpcSharedStateHost, k as SharedState, lt as DevframeServicesState, pt as DevframeSettings, rt as DevframeServiceMeta, t as ConnectionMeta, u as RpcSharedStateGetOptions, vt as ScopedRpcFn, wt as DevframeRpcServerFunctions, xt as SettingsForNamespace, y as StreamReader, yt as ScopedServerFunctions } from "../context--tVkJw3W.mjs";
|
|
5
|
+
import { t as SseRpcChannelOptions } from "../sse-client-DNdscfu_.mjs";
|
|
6
|
+
import { t as WsRpcChannelOptions } from "../ws-client-6Mrz6eC4.mjs";
|
|
7
7
|
//#region src/client/connection.d.ts
|
|
8
8
|
export interface DevframeConnection {
|
|
9
9
|
/** Server-advertised transport and serialization metadata. */
|
package/dist/client/index.mjs
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { n as DEVFRAME_EVENTS, t as
|
|
1
|
+
import { a as defineDiagnostics, i as createBirpc, n as argsToJsonSchema, o as DEVFRAME_EVENTS, r as resolveAgentSafety, t as toolInputToRpcArgs } from "../tool-input-BAQXvvqL.mjs";
|
|
2
2
|
import { t as hash } from "../hash-DdPnc4k3.mjs";
|
|
3
3
|
import { createEventEmitter } from "../utils/events.mjs";
|
|
4
|
+
import { withBase, withProtocol } from "../utils/url.mjs";
|
|
4
5
|
import { t as createSharedState } from "../shared-state-eQVYHNqA.mjs";
|
|
5
6
|
import { i as structuredCloneStringify, n as structuredCloneParse, t as structuredCloneDeserialize } from "../structured-clone-CgtQ61SA.mjs";
|
|
6
7
|
import { createStreamReader, createStreamSink } from "../utils/streaming-channel.mjs";
|
|
7
8
|
import { toAgentToolName } from "../utils/agent-tool-name.mjs";
|
|
8
|
-
import { withBase, withProtocol } from "ufo";
|
|
9
|
-
import { createBirpc } from "birpc";
|
|
10
9
|
//#region src/constants.ts
|
|
11
10
|
/** Devframe runtime routes and static output conventions. */
|
|
12
11
|
const DEVFRAME_CONNECTION_META_FILENAME = "__connection.json";
|
|
@@ -1670,83 +1669,6 @@ function createScopedClientContext(rpc, namespace) {
|
|
|
1670
1669
|
};
|
|
1671
1670
|
}
|
|
1672
1671
|
//#endregion
|
|
1673
|
-
//#region src/adapters/mcp/to-json-schema.ts
|
|
1674
|
-
const FALLBACK_OBJECT_SCHEMA = Object.freeze({
|
|
1675
|
-
type: "object",
|
|
1676
|
-
additionalProperties: true
|
|
1677
|
-
});
|
|
1678
|
-
/**
|
|
1679
|
-
* Convert a Standard Schema to JSON Schema for the agent/MCP surface.
|
|
1680
|
-
*
|
|
1681
|
-
* Devframe stays validator-neutral, so conversion uses the schema's own
|
|
1682
|
-
* [Standard JSON Schema](https://standardschema.dev/) converter
|
|
1683
|
-
* (`~standard.jsonSchema`) when the validator provides one; zod 4 does,
|
|
1684
|
-
* for example. Validators without a native converter (e.g. valibot) degrade
|
|
1685
|
-
* to a permissive object schema rather than pulling in a converter library.
|
|
1686
|
-
*/
|
|
1687
|
-
function safeToJsonSchema(schema) {
|
|
1688
|
-
const standard = schema["~standard"];
|
|
1689
|
-
if (standard.jsonSchema) try {
|
|
1690
|
-
return standard.jsonSchema.input({ target: "draft-2020-12" });
|
|
1691
|
-
} catch {
|
|
1692
|
-
return FALLBACK_OBJECT_SCHEMA;
|
|
1693
|
-
}
|
|
1694
|
-
return FALLBACK_OBJECT_SCHEMA;
|
|
1695
|
-
}
|
|
1696
|
-
/**
|
|
1697
|
-
* JSON Schema for an RPC function's positional args on the agent/MCP
|
|
1698
|
-
* surface. Each positional arg is advertised under `arg0` / `arg1` / …,
|
|
1699
|
-
* matching how the agent bridge coerces the incoming object payload back
|
|
1700
|
-
* into positional arguments.
|
|
1701
|
-
*
|
|
1702
|
-
* Returns `{ type: 'object', properties: {} }` when there are no args.
|
|
1703
|
-
* @internal
|
|
1704
|
-
*/
|
|
1705
|
-
function argsToJsonSchema(args) {
|
|
1706
|
-
if (!args || args.length === 0) return {
|
|
1707
|
-
type: "object",
|
|
1708
|
-
properties: {}
|
|
1709
|
-
};
|
|
1710
|
-
const properties = {};
|
|
1711
|
-
const required = [];
|
|
1712
|
-
for (let i = 0; i < args.length; i++) {
|
|
1713
|
-
const key = `arg${i}`;
|
|
1714
|
-
properties[key] = safeToJsonSchema(args[i]);
|
|
1715
|
-
required.push(key);
|
|
1716
|
-
}
|
|
1717
|
-
return {
|
|
1718
|
-
type: "object",
|
|
1719
|
-
properties,
|
|
1720
|
-
required,
|
|
1721
|
-
additionalProperties: false
|
|
1722
|
-
};
|
|
1723
|
-
}
|
|
1724
|
-
//#endregion
|
|
1725
|
-
//#region src/tool-input.ts
|
|
1726
|
-
/**
|
|
1727
|
-
* Convert an object-shaped tool input into positional arguments.
|
|
1728
|
-
*
|
|
1729
|
-
* Tool schemas expose positional parameters as `arg0`, `arg1`, and so on.
|
|
1730
|
-
* Arrays pass through for callers that already provide positional arguments.
|
|
1731
|
-
*/
|
|
1732
|
-
function collectPositionalArgs(input, argumentCount) {
|
|
1733
|
-
if (Array.isArray(input)) return input;
|
|
1734
|
-
if (input === void 0 || input === null) return [];
|
|
1735
|
-
if (typeof input !== "object") return void 0;
|
|
1736
|
-
const record = input;
|
|
1737
|
-
if (argumentCount != null) return Array.from({ length: argumentCount }, (_, index) => record[`arg${index}`]);
|
|
1738
|
-
if ("arg0" in record) {
|
|
1739
|
-
const positional = [];
|
|
1740
|
-
while (`arg${positional.length}` in record) positional.push(record[`arg${positional.length}`]);
|
|
1741
|
-
return positional;
|
|
1742
|
-
}
|
|
1743
|
-
return Object.keys(record).length === 0 ? [] : void 0;
|
|
1744
|
-
}
|
|
1745
|
-
/** Convert tool input for an RPC, preserving an untyped payload as arg 0. */
|
|
1746
|
-
function toolInputToRpcArgs(input, argumentCount) {
|
|
1747
|
-
return collectPositionalArgs(input, argumentCount) ?? [input];
|
|
1748
|
-
}
|
|
1749
|
-
//#endregion
|
|
1750
1672
|
//#region src/client/webmcp.ts
|
|
1751
1673
|
/**
|
|
1752
1674
|
* The page's WebMCP model context, when the browser (or a polyfill)
|
|
@@ -1787,14 +1709,15 @@ function registerWebMcpTools(clientRpc, options = {}) {
|
|
|
1787
1709
|
return;
|
|
1788
1710
|
}
|
|
1789
1711
|
const controller = new AbortController();
|
|
1712
|
+
const safety = resolveAgentSafety(def.type, agent);
|
|
1790
1713
|
const result = modelContext.registerTool({
|
|
1791
1714
|
name,
|
|
1792
1715
|
description: agent.description,
|
|
1793
1716
|
inputSchema: argsToJsonSchema(def.args),
|
|
1794
1717
|
annotations: {
|
|
1795
1718
|
title: agent.title ?? def.name,
|
|
1796
|
-
readOnlyHint:
|
|
1797
|
-
destructiveHint:
|
|
1719
|
+
readOnlyHint: safety === "read",
|
|
1720
|
+
destructiveHint: safety === "destructive"
|
|
1798
1721
|
},
|
|
1799
1722
|
execute: (args) => executeRpcTool(def, clientRpc.context, args)
|
|
1800
1723
|
}, { signal: controller.signal });
|
|
@@ -1824,11 +1747,6 @@ function registerWebMcpTools(clientRpc, options = {}) {
|
|
|
1824
1747
|
registered.clear();
|
|
1825
1748
|
};
|
|
1826
1749
|
}
|
|
1827
|
-
function resolveSafety(def, agent) {
|
|
1828
|
-
if (agent.safety) return agent.safety;
|
|
1829
|
-
const type = def.type ?? "query";
|
|
1830
|
-
return type === "static" || type === "query" ? "read" : "action";
|
|
1831
|
-
}
|
|
1832
1750
|
async function executeRpcTool(def, context, args) {
|
|
1833
1751
|
try {
|
|
1834
1752
|
const positional = toolInputToRpcArgs(args, def.args?.length);
|
|
@@ -1903,6 +1821,8 @@ async function getDevframeRpcClient(options = {}) {
|
|
|
1903
1821
|
const context = { rpc: void 0 };
|
|
1904
1822
|
const clientRpc = new RpcFunctionsCollectorBase(context);
|
|
1905
1823
|
const disposeWebMcp = options.webmcp === false ? void 0 : registerWebMcpTools(clientRpc);
|
|
1824
|
+
let disposeBrowserAgentBridge;
|
|
1825
|
+
let closed = false;
|
|
1906
1826
|
async function fetchJsonFromBases(path) {
|
|
1907
1827
|
const candidates = [resolvedBaseURL, ...bases.filter((base) => base !== resolvedBaseURL)].filter((x) => x != null);
|
|
1908
1828
|
const errors = [];
|
|
@@ -1957,7 +1877,9 @@ async function getDevframeRpcClient(options = {}) {
|
|
|
1957
1877
|
}
|
|
1958
1878
|
/** Release authentication and transport resources even if another disposer fails. */
|
|
1959
1879
|
function closeRpcClient() {
|
|
1880
|
+
closed = true;
|
|
1960
1881
|
try {
|
|
1882
|
+
disposeBrowserAgentBridge?.();
|
|
1961
1883
|
disposeWebMcp?.();
|
|
1962
1884
|
} finally {
|
|
1963
1885
|
try {
|
|
@@ -2068,6 +1990,9 @@ async function getDevframeRpcClient(options = {}) {
|
|
|
2068
1990
|
}, () => {
|
|
2069
1991
|
bootstrapAuthSettled = true;
|
|
2070
1992
|
});
|
|
1993
|
+
if (connectionMeta.mcp) import("../browser-agent-rpc-BXhoSh1z.mjs").then(({ setupBrowserAgentRpcBridge }) => {
|
|
1994
|
+
if (!closed) disposeBrowserAgentBridge = setupBrowserAgentRpcBridge(rpc);
|
|
1995
|
+
}).catch(() => {});
|
|
2071
1996
|
if (authChannel) authChannel.onmessage = (event) => {
|
|
2072
1997
|
if (event.data?.type === "auth-update" && event.data.authToken) rpc.requestTrustWithToken(event.data.authToken);
|
|
2073
1998
|
};
|
package/dist/constants.d.mts
CHANGED
|
@@ -113,7 +113,8 @@ export declare const DEVFRAME_SSE_SESSION_HEADER = "x-birpc-session";
|
|
|
113
113
|
* Route the Streamable-HTTP MCP endpoint is bound to, relative to a
|
|
114
114
|
* devframe's base path. Sits next to `__connection.json` and the WS route
|
|
115
115
|
* so an MCP client reaches it on the same origin the SPA loaded from; the
|
|
116
|
-
* dev server shares one port for HTTP, WS, and MCP. Opt-in via
|
|
116
|
+
* dev server shares one port for HTTP, WS, and MCP. Opt-in via the host's
|
|
117
|
+
* `mcp` setting.
|
|
117
118
|
*/
|
|
118
119
|
export declare const DEVFRAME_MCP_ROUTE = "__mcp";
|
|
119
120
|
export declare const DEVFRAME_RPC_DUMP_MANIFEST_FILENAME = "__rpc-dump/index.json";
|
package/dist/constants.mjs
CHANGED
|
@@ -36,7 +36,8 @@ const DEVFRAME_SSE_SESSION_HEADER = "x-birpc-session";
|
|
|
36
36
|
* Route the Streamable-HTTP MCP endpoint is bound to, relative to a
|
|
37
37
|
* devframe's base path. Sits next to `__connection.json` and the WS route
|
|
38
38
|
* so an MCP client reaches it on the same origin the SPA loaded from; the
|
|
39
|
-
* dev server shares one port for HTTP, WS, and MCP. Opt-in via
|
|
39
|
+
* dev server shares one port for HTTP, WS, and MCP. Opt-in via the host's
|
|
40
|
+
* `mcp` setting.
|
|
40
41
|
*/
|
|
41
42
|
const DEVFRAME_MCP_ROUTE = "__mcp";
|
|
42
43
|
const DEVFRAME_RPC_DUMP_MANIFEST_FILENAME = "__rpc-dump/index.json";
|