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.
Files changed (102) hide show
  1. package/dist/_shared-B2PZsp-J.mjs +191 -0
  2. package/dist/{_shared-BIUet_Jh.d.mts → _shared-D1ebaX_J.d.mts} +13 -14
  3. package/dist/adapters/build.d.mts +3 -4
  4. package/dist/adapters/build.mjs +4 -5
  5. package/dist/adapters/cac.d.mts +3 -4
  6. package/dist/adapters/cac.mjs +3 -3
  7. package/dist/adapters/dev.d.mts +8 -8
  8. package/dist/adapters/dev.mjs +2 -2
  9. package/dist/adapters/embedded.d.mts +1 -1
  10. package/dist/adapters/initiate.d.mts +8 -8
  11. package/dist/adapters/initiate.mjs +1 -1
  12. package/dist/adapters/mcp.d.mts +4 -142
  13. package/dist/adapters/mcp.mjs +1 -597
  14. package/dist/agentic-DLqHMOyN.d.mts +30 -0
  15. package/dist/browser-agent-Pu-FLk6I.mjs +26 -0
  16. package/dist/browser-agent-rpc-BXhoSh1z.mjs +70 -0
  17. package/dist/cli/main.mjs +12 -260
  18. package/dist/client/index.d.mts +6 -6
  19. package/dist/client/index.mjs +12 -87
  20. package/dist/constants.d.mts +2 -1
  21. package/dist/constants.mjs +2 -1
  22. package/dist/{context-BLHBDQcZ.d.mts → context--tVkJw3W.d.mts} +127 -47
  23. package/dist/{context-BLXXjoBW.mjs → context-BqpgC5tq.mjs} +55 -5
  24. package/dist/{context-jXO5vzjH.mjs → context-CHpyLX8W.mjs} +1 -1
  25. package/dist/{context-BA5FPz7K.d.mts → context-D3qBv0IX.d.mts} +1 -1
  26. package/dist/{dev-BdKKOH8S.mjs → dev-BTbLJekh.mjs} +16 -24
  27. package/dist/{diagnostics-DZA6KQ_Z.mjs → diagnostics-Cx4_VjlG.mjs} +11 -3
  28. package/dist/dist-CO98PBnQ.mjs +265 -0
  29. package/dist/{host-agent-C3MBpNCd.mjs → host-agent-mIbQ6Qi5.mjs} +14 -13
  30. package/dist/import-runtime-module-BzlEQ9PI.mjs +24 -0
  31. package/dist/in-page-channel/index.d.mts +16 -12
  32. package/dist/in-page-channel/index.mjs +42 -14
  33. package/dist/index-Bia1vKjL.d.mts +218 -0
  34. package/dist/index-CeEtxDmK.d.mts +78 -0
  35. package/dist/{index-r6NstVmM.d.mts → index-D-2IdY1I.d.mts} +2 -2
  36. package/dist/{index-CiA6C705.d.mts → index-DuEm0sDH.d.mts} +1 -1
  37. package/dist/index.d.mts +4 -11
  38. package/dist/index.mjs +1 -10
  39. package/dist/{instance-registry-DCq762YJ.mjs → instance-registry-DtvYxc0Y.mjs} +1 -1
  40. package/dist/{instance-shell-C6Vg97lg.d.mts → instance-shell-CXnpSuK7.d.mts} +31 -5
  41. package/dist/{instance-shell-yfrM3_vK.mjs → instance-shell-wC2sfEU_.mjs} +7 -7
  42. package/dist/interactive-auth-BCa_iamv.mjs +384 -0
  43. package/dist/internal/index.d.mts +66 -22
  44. package/dist/internal/index.mjs +143 -8
  45. package/dist/mrmime-40h5OOIp.mjs +448 -0
  46. package/dist/node/auth.d.mts +2 -2
  47. package/dist/node/hub-internals.d.mts +2 -2
  48. package/dist/node/hub-internals.mjs +2 -2
  49. package/dist/node/index.d.mts +2 -4
  50. package/dist/node/index.mjs +2 -2
  51. package/dist/{nostics-0sKe5HIj.d.mts → nostics-DWvukRwa.d.mts} +11 -2
  52. package/dist/{origin-D_eBKEzr.mjs → origin-DZit9g29.mjs} +1 -21
  53. package/dist/recipes/interactive-auth.d.mts +2 -2
  54. package/dist/recipes/interactive-auth.mjs +1 -231
  55. package/dist/{remote-assets-D0StJVLp.mjs → remote-assets-B9MeWmW6.mjs} +2 -2
  56. package/dist/rpc/client.d.mts +1 -1
  57. package/dist/rpc/client.mjs +1 -1
  58. package/dist/rpc/dump.d.mts +2 -1
  59. package/dist/rpc/index.d.mts +3 -2
  60. package/dist/rpc/server.d.mts +1 -1
  61. package/dist/rpc/server.mjs +1 -1
  62. package/dist/rpc/transports/sse-client.d.mts +1 -1
  63. package/dist/rpc/transports/sse-server.d.mts +3 -3
  64. package/dist/rpc/transports/sse-server.mjs +1 -1
  65. package/dist/rpc/transports/ws-bun.d.mts +2 -2
  66. package/dist/rpc/transports/ws-bun.mjs +1 -1
  67. package/dist/rpc/transports/ws-client.d.mts +1 -1
  68. package/dist/rpc/transports/ws-deno.d.mts +2 -2
  69. package/dist/rpc/transports/ws-deno.mjs +1 -1
  70. package/dist/rpc/transports/ws-server.d.mts +1 -1
  71. package/dist/rpc/transports/ws-server.mjs +1 -1
  72. package/dist/{rpc-core-BlL-Ip8n.d.mts → rpc-core-B6n9MQsc.d.mts} +4 -4
  73. package/dist/{rpc-core-CvkU23bQ.mjs → rpc-core-BXyMjbKG.mjs} +1 -1
  74. package/dist/{services-install-Ben4yh52.mjs → services-install-D5YUwFXW.mjs} +17 -1
  75. package/dist/{sse-client-BcbYwXdD.d.mts → sse-client-DNdscfu_.d.mts} +2 -2
  76. package/dist/{storage-B70AXK64.mjs → storage-CHD1IkqU.mjs} +1 -1
  77. package/dist/tool-input-BAQXvvqL.mjs +393 -0
  78. package/dist/types/index.d.mts +4 -4
  79. package/dist/{types-D2vGd7-2.d.mts → types-BmDbfHCx.d.mts} +2 -3
  80. package/dist/utils/debounce.d.mts +50 -0
  81. package/dist/utils/debounce.mjs +88 -0
  82. package/dist/utils/events.d.mts +1 -1
  83. package/dist/utils/launch-editor.d.mts +8 -0
  84. package/dist/utils/launch-editor.mjs +36 -1
  85. package/dist/utils/nostics.d.mts +2 -2
  86. package/dist/utils/remote-assets.mjs +1 -1
  87. package/dist/utils/serve-static.mjs +1 -1
  88. package/dist/utils/shared-state.d.mts +1 -1
  89. package/dist/utils/simple-schema.d.mts +3 -2
  90. package/dist/utils/streaming-channel.d.mts +1 -1
  91. package/dist/utils/url.d.mts +31 -0
  92. package/dist/utils/url.mjs +58 -0
  93. package/dist/{ws-client-CNpP6Ium.d.mts → ws-client-6Mrz6eC4.d.mts} +2 -2
  94. package/dist/{ws-server-BB8jhxeT.d.mts → ws-server-y2R76aSW.d.mts} +2 -2
  95. package/package.json +10 -11
  96. package/dist/_shared-U8D-xzrp.mjs +0 -115
  97. package/dist/agent-tool-name-DGzeuWcO.mjs +0 -31
  98. package/dist/client-assets-DNdhagGm.mjs +0 -16
  99. package/dist/nostics-DD6gsDZA.mjs +0 -108
  100. package/dist/recipes/common-rpc-functions.d.mts +0 -70
  101. package/dist/recipes/common-rpc-functions.mjs +0 -115
  102. 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 Diagnostic } from "../nostics-D0PvLZsn.mjs";
2
- import { t as diagnostics } from "../diagnostics-DZA6KQ_Z.mjs";
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/connect.ts
9
- /**
10
- * Resolve the per-record bearer from the {@link ConnectServerOptions.authToken}
11
- * option. Exported for focused tests of the credential resolution.
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
- * Start the devframe MCP connector on stdio: a thin discovery + proxy server
69
- * in the shape Vercel's next-devtools-mcp (https://github.com/vercel/next-devtools-mcp)
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 startConnectServer(options = {}) {
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
- const [serverMod, stdioMod, clientMod] = await Promise.all([
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
- keepAlive();
45
+ process.stdin.resume();
294
46
  });
295
47
  cli.help();
296
48
  cli.parse(argv, { run: false });
@@ -1,9 +1,9 @@
1
- import { f as RpcCacheManager, p as RpcCacheOptions } from "../index-CiA6C705.mjs";
2
- import { E as RpcFunctionsCollector, _ as RpcFunctionDefinition } from "../types-D2vGd7-2.mjs";
3
- import { Q as DevframeServiceMeta, Vt as EventEmitter, b as StreamSink, ct as DevframeSettings, d as RpcSharedStateHost, et as DevframeServiceScopeOf, gt as SettingsForNamespace, ht as ScopedSharedStates, it as DevframeServicesState, k as SharedState, mt as ScopedServerFunctions, pt as ScopedRpcFn, t as ConnectionMeta, u as RpcSharedStateGetOptions, vt as DevframeRpcClientFunctions, y as StreamReader, yt as DevframeRpcServerFunctions } from "../context-BLHBDQcZ.mjs";
4
- import { t as SseRpcChannelOptions } from "../sse-client-BcbYwXdD.mjs";
5
- import { t as WsRpcChannelOptions } from "../ws-client-CNpP6Ium.mjs";
6
- import { BirpcOptions, BirpcReturn } from "birpc";
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. */
@@ -1,12 +1,11 @@
1
- import { n as DEVFRAME_EVENTS, t as defineDiagnostics } from "../nostics-DD6gsDZA.mjs";
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: resolveSafety(def, agent) === "read",
1797
- destructiveHint: resolveSafety(def, agent) === "destructive"
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
  };
@@ -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 `cli.mcp`.
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";
@@ -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 `cli.mcp`.
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";