devframe 0.7.16 → 0.8.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 (80) hide show
  1. package/bin/devframe.mjs +8 -0
  2. package/dist/adapters/build.d.mts +1 -1
  3. package/dist/adapters/build.mjs +4 -4
  4. package/dist/adapters/cac.d.mts +1 -1
  5. package/dist/adapters/cac.mjs +1 -1
  6. package/dist/adapters/cli.d.mts +1 -1
  7. package/dist/adapters/cli.mjs +1 -1
  8. package/dist/adapters/dev.d.mts +2 -2
  9. package/dist/adapters/dev.mjs +1 -1
  10. package/dist/adapters/embedded.d.mts +1 -1
  11. package/dist/adapters/mcp.d.mts +51 -3
  12. package/dist/adapters/mcp.mjs +2 -2
  13. package/dist/agent-tool-name-C3b5vEwJ.mjs +34 -0
  14. package/dist/{cac-CPaOy3LD.mjs → cac-B1LM3zit.mjs} +18 -13
  15. package/dist/cli/main.d.mts +11 -0
  16. package/dist/cli/main.mjs +284 -0
  17. package/dist/client/index.d.mts +40 -11
  18. package/dist/client/index.mjs +129 -19
  19. package/dist/constants.d.mts +13 -6
  20. package/dist/constants.mjs +13 -6
  21. package/dist/{context-DY72brRI.mjs → context-CQefP2jR.mjs} +3 -2
  22. package/dist/{context-CzqPrJSz.d.mts → context-DFzmxCLa.d.mts} +1 -1
  23. package/dist/{revoke-BtQDKTp7.mjs → crypto-token-XCqTSMg9.mjs} +1 -35
  24. package/dist/{dev-BCeaQuGE.mjs → dev-BSHFZGZr.mjs} +22 -11
  25. package/dist/{devframe-Ckhf3kFw.d.mts → devframe-qgCKL683.d.mts} +77 -15
  26. package/dist/diagnostics-B5-qHeqD.mjs +94 -0
  27. package/dist/{diagnostics-BXwBQmoN.mjs → diagnostics-hwjXp_UV.mjs} +8 -0
  28. package/dist/{dump-Cz7yVvsB.mjs → dump-CgZShDRB.mjs} +82 -6
  29. package/dist/fetch-CK0S253E.mjs +534 -0
  30. package/dist/helpers/vite.d.mts +15 -14
  31. package/dist/helpers/vite.mjs +8 -8
  32. package/dist/{host-h3-CgOetlNE.mjs → host-h3-Kz7t5Xab.mjs} +83 -28
  33. package/dist/http-Dvgxujcu.mjs +38 -0
  34. package/dist/{index-8dndvIxR.d.mts → index-B7RgLw--.d.mts} +11 -6
  35. package/dist/{index-DgsLFhZg.d.mts → index-Dbw1p5ch.d.mts} +27 -3
  36. package/dist/{index-YDSJgsyG.d.mts → index-vAX6bYuc.d.mts} +1 -1
  37. package/dist/index.d.mts +5 -5
  38. package/dist/instance-registry-D6fxYu36.mjs +190 -0
  39. package/dist/node/auth.d.mts +2 -2
  40. package/dist/node/auth.mjs +2 -2
  41. package/dist/node/hub-internals.d.mts +2 -2
  42. package/dist/node/hub-internals.mjs +1 -1
  43. package/dist/node/index.d.mts +124 -6
  44. package/dist/node/index.mjs +5 -4
  45. package/dist/recipes/common-rpc-functions.d.mts +33 -33
  46. package/dist/recipes/common-rpc-functions.mjs +6 -6
  47. package/dist/recipes/interactive-auth.d.mts +2 -2
  48. package/dist/recipes/interactive-auth.mjs +23 -15
  49. package/dist/revoke-DZbJ7Cl0.mjs +35 -0
  50. package/dist/rpc/dump.d.mts +1 -1
  51. package/dist/rpc/dump.mjs +1 -1
  52. package/dist/rpc/index.d.mts +3 -3
  53. package/dist/rpc/index.mjs +4 -4
  54. package/dist/rpc/transports/ws-client.d.mts +1 -1
  55. package/dist/rpc/transports/ws-client.mjs +1 -1
  56. package/dist/rpc/transports/ws-server.d.mts +2 -2
  57. package/dist/rpc/transports/ws-server.mjs +50 -3
  58. package/dist/{serialization-DpLXCy13.mjs → serialization-C8Mnw9hK.mjs} +1 -1
  59. package/dist/{server-DykHCza_.mjs → server-CXaDDUl0.mjs} +2 -2
  60. package/dist/{server-B0wiP_Oi.d.mts → server-E9z4ZWbd.d.mts} +4 -3
  61. package/dist/simple-schema-BDzLeJDk.d.mts +102 -0
  62. package/dist/simple-schema-DQPZrAaZ.mjs +154 -0
  63. package/dist/{state-WX3HT5a3.mjs → state-BeUHDDjk.mjs} +13 -6
  64. package/dist/{storage-gEnj9ASQ.mjs → storage-Dzoc3NVC.mjs} +2 -66
  65. package/dist/{transports-BmDVn4SF.mjs → transports-vhizgqXM.mjs} +1 -1
  66. package/dist/types/index.d.mts +4 -4
  67. package/dist/{types-CrzNxXKq.d.mts → types-CnJSgRVa.d.mts} +33 -19
  68. package/dist/utils/agent-tool-name.d.mts +27 -0
  69. package/dist/utils/agent-tool-name.mjs +34 -0
  70. package/dist/utils/events.d.mts +1 -1
  71. package/dist/utils/shared-state.d.mts +1 -1
  72. package/dist/utils/simple-schema.d.mts +2 -0
  73. package/dist/utils/simple-schema.mjs +154 -0
  74. package/dist/utils/streaming-channel.d.mts +1 -1
  75. package/dist/{ws-client-DSXiI2h7.d.mts → ws-client-B6Tnr0ic.d.mts} +1 -1
  76. package/dist/{ws-server-9-wn7MNQ.d.mts → ws-server-J-zkiOyp.d.mts} +24 -3
  77. package/package.json +18 -7
  78. package/skills/devframe/SKILL.md +4 -3
  79. package/dist/build-server-C7vTKjiQ.mjs +0 -359
  80. package/dist/http-_pPbryTQ.mjs +0 -114
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import process from 'node:process'
3
+ import { runDevframeCli } from '../dist/cli/main.mjs'
4
+
5
+ runDevframeCli().catch((error) => {
6
+ console.error(error)
7
+ process.exit(1)
8
+ })
@@ -1,4 +1,4 @@
1
- import { r as DevframeDefinition } from "../devframe-Ckhf3kFw.mjs";
1
+ import { r as DevframeDefinition } from "../devframe-qgCKL683.mjs";
2
2
  //#region src/adapters/build.d.ts
3
3
  interface CreateBuildOptions {
4
4
  /** Output directory. Defaults to `dist-static`. */
@@ -1,10 +1,10 @@
1
- import { t as collectStaticRpcDump } from "../dump-Cz7yVvsB.mjs";
1
+ import { t as collectStaticRpcDump } from "../dump-CgZShDRB.mjs";
2
2
  import { n as colors } from "../diagnostics-reporter-CsIG85Q5.mjs";
3
3
  import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME } from "../constants.mjs";
4
- import { n as strictJsonStringify } from "../serialization-DpLXCy13.mjs";
4
+ import { n as strictJsonStringify } from "../serialization-C8Mnw9hK.mjs";
5
5
  import { n as structuredCloneStringify } from "../structured-clone-CbAV5rFI.mjs";
6
- import { a as diagnostics } from "../storage-gEnj9ASQ.mjs";
7
- import { n as createHostContext, t as createH3DevframeHost } from "../host-h3-CgOetlNE.mjs";
6
+ import { n as createHostContext, t as createH3DevframeHost } from "../host-h3-Kz7t5Xab.mjs";
7
+ import { t as diagnostics } from "../diagnostics-B5-qHeqD.mjs";
8
8
  import { n as resolveBasePath } from "../_shared-bWRzeSa0.mjs";
9
9
  import { existsSync } from "node:fs";
10
10
  import { dirname, resolve } from "pathe";
@@ -1,4 +1,4 @@
1
- import { Ft as parseCliFlags, Mt as CliFlagsSchema, Nt as InferCliFlags, Pt as defineCliFlags, r as DevframeDefinition } from "../devframe-Ckhf3kFw.mjs";
1
+ import { Ft as InferCliFlags, It as defineCliFlags, Lt as parseCliFlags, Pt as CliFlagsSchema, r as DevframeDefinition } from "../devframe-qgCKL683.mjs";
2
2
  import { CAC } from "cac";
3
3
  import { H3 } from "h3";
4
4
  //#region src/adapters/cac.d.ts
@@ -1,2 +1,2 @@
1
- import { n as defineCliFlags, r as parseCliFlags, t as createCac } from "../cac-CPaOy3LD.mjs";
1
+ import { n as defineCliFlags, r as parseCliFlags, t as createCac } from "../cac-B1LM3zit.mjs";
2
2
  export { createCac, defineCliFlags, parseCliFlags };
@@ -1,4 +1,4 @@
1
- import { Ft as parseCliFlags, Mt as CliFlagsSchema, Nt as InferCliFlags, Pt as defineCliFlags } from "../devframe-Ckhf3kFw.mjs";
1
+ import { Ft as InferCliFlags, It as defineCliFlags, Lt as parseCliFlags, Pt as CliFlagsSchema } from "../devframe-qgCKL683.mjs";
2
2
  import { CacHandle, CreateCacOptions, createCac } from "./cac.mjs";
3
3
  //#region src/adapters/cli.d.ts
4
4
  /** @deprecated Use `createCac` from `devframe/adapters/cac` instead. */
@@ -1,4 +1,4 @@
1
- import { n as defineCliFlags, r as parseCliFlags, t as createCac } from "../cac-CPaOy3LD.mjs";
1
+ import { n as defineCliFlags, r as parseCliFlags, t as createCac } from "../cac-B1LM3zit.mjs";
2
2
  //#region src/adapters/cli.ts
3
3
  /** @deprecated Use `createCac` from `devframe/adapters/cac` instead. */
4
4
  const createCli = createCac;
@@ -1,5 +1,5 @@
1
- import { _ as ConnectionMeta, d as McpRouteOptions, p as DevframeAuthHandler, r as DevframeDefinition, u as DevframeWsOptions } from "../devframe-Ckhf3kFw.mjs";
2
- import { n as StartedServer } from "../server-B0wiP_Oi.mjs";
1
+ import { _ as ConnectionMeta, d as McpRouteOptions, p as DevframeAuthHandler, r as DevframeDefinition, u as DevframeWsOptions } from "../devframe-qgCKL683.mjs";
2
+ import { n as StartedServer } from "../server-E9z4ZWbd.mjs";
3
3
  import { H3 } from "h3";
4
4
  //#region src/adapters/dev.d.ts
5
5
  interface CreateDevServerOptions {
@@ -1,2 +1,2 @@
1
- import { n as resolveDevServerPort, r as resolveMcpConnectionMeta, t as createDevServer } from "../dev-BCeaQuGE.mjs";
1
+ import { n as resolveDevServerPort, r as resolveMcpConnectionMeta, t as createDevServer } from "../dev-BSHFZGZr.mjs";
2
2
  export { createDevServer, resolveDevServerPort, resolveMcpConnectionMeta };
@@ -1,4 +1,4 @@
1
- import { b as DevframeNodeContext, r as DevframeDefinition } from "../devframe-Ckhf3kFw.mjs";
1
+ import { b as DevframeNodeContext, r as DevframeDefinition } from "../devframe-qgCKL683.mjs";
2
2
  //#region src/adapters/embedded.d.ts
3
3
  interface CreateEmbeddedOptions {
4
4
  /** Target context the devframe is registered into. Required. */
@@ -1,5 +1,5 @@
1
- import { r as DevframeDefinition } from "../devframe-Ckhf3kFw.mjs";
2
- import "@modelcontextprotocol/sdk/server/index.js";
1
+ import { b as DevframeNodeContext, r as DevframeDefinition } from "../devframe-qgCKL683.mjs";
2
+ import "@modelcontextprotocol/server";
3
3
  //#region src/adapters/mcp/build-server.d.ts
4
4
  interface CreateMcpServerOptions {
5
5
  /**
@@ -37,4 +37,52 @@ interface McpServerHandle {
37
37
  */
38
38
  declare function createMcpServer(definition: DevframeDefinition, options?: CreateMcpServerOptions): Promise<McpServerHandle>;
39
39
  //#endregion
40
- export { type CreateMcpServerOptions, type McpServerHandle, createMcpServer };
40
+ //#region src/adapters/mcp/fetch.d.ts
41
+ interface CreateMcpFetchHandlerOptions {
42
+ /** Name reported in the MCP handshake. */
43
+ serverName: string;
44
+ /** Version reported in the MCP handshake. */
45
+ serverVersion: string;
46
+ /** Expose shared-state keys as MCP resources — see `buildMcpServerFromContext`. */
47
+ exposeSharedState: boolean | ((key: string) => boolean);
48
+ /**
49
+ * Origin allow-list beyond the loopback default. `false` disables the
50
+ * origin gate entirely. Default: loopback-only.
51
+ *
52
+ * Unlike the WS transport, the MCP route does **not** allow `Origin`-less
53
+ * requests: a route-based endpoint is reachable by any local process, so a
54
+ * request must carry an `Origin` that passes the gate. Native clients
55
+ * (e.g. `devframe connect`) send their loopback origin explicitly.
56
+ */
57
+ allowedOrigins?: readonly string[] | false;
58
+ }
59
+ interface McpFetchHandler {
60
+ /**
61
+ * WHATWG-`fetch` handler for the MCP Streamable-HTTP endpoint. Hand every
62
+ * method (POST/GET/DELETE) on the endpoint's path to it — routing by path
63
+ * is the host's job.
64
+ */
65
+ fetch: (request: Request) => Promise<Response>;
66
+ /** Tear down every live MCP session (closes servers, drops subscriptions). */
67
+ dispose: () => Promise<void>;
68
+ }
69
+ /**
70
+ * Build a framework-agnostic MCP Streamable-HTTP endpoint over a devframe
71
+ * context: a web-standard `Request → Response` handler any host can mount —
72
+ * h3 (see `mountMcpHttp`), a Next.js App Router route, or any other
73
+ * fetch-shaped server.
74
+ *
75
+ * Each MCP session gets its own {@link WebStandardStreamableHTTPServerTransport}
76
+ * and MCP server (built from the shared, live `ctx` via
77
+ * `buildMcpServerFromContext`), correlated by the `Mcp-Session-Id` header: an
78
+ * `initialize` POST spins up a session; later requests route to it; a `DELETE`
79
+ * (or client disconnect) tears it down. The origin gate guards every request:
80
+ * loopback-default DNS-rebinding protection that — unlike the WS upgrade's
81
+ * `isAllowedOrigin` — also rejects `Origin`-less requests, so a route-based
82
+ * endpoint isn't reachable by an arbitrary local process.
83
+ *
84
+ * @experimental
85
+ */
86
+ declare function createMcpFetchHandler(ctx: DevframeNodeContext, options: CreateMcpFetchHandlerOptions): McpFetchHandler;
87
+ //#endregion
88
+ export { type CreateMcpFetchHandlerOptions, type CreateMcpServerOptions, type McpFetchHandler, type McpServerHandle, createMcpFetchHandler, createMcpServer };
@@ -1,2 +1,2 @@
1
- import { n as createMcpServer } from "../build-server-C7vTKjiQ.mjs";
2
- export { createMcpServer };
1
+ import { n as createMcpServer, t as createMcpFetchHandler } from "../fetch-CK0S253E.mjs";
2
+ export { createMcpFetchHandler, createMcpServer };
@@ -0,0 +1,34 @@
1
+ //#region src/utils/agent-tool-name.ts
2
+ /**
3
+ * Maximum tool-name length several MCP clients enforce (the Anthropic API
4
+ * pattern is `^[a-zA-Z0-9_-]{1,128}$`).
5
+ */
6
+ const MAX_TOOL_NAME_LENGTH = 128;
7
+ /**
8
+ * Derive the wire-safe agent tool name for an internal tool id.
9
+ *
10
+ * Devframe tool ids are colon-namespaced — `devframe:<area>:<fn>` for
11
+ * built-ins, `devframes:plugin:<slug>:<fn>` for plugin RPCs, and hub
12
+ * command ids for command-derived tools. MCP clients constrain tool names
13
+ * to `^[a-zA-Z0-9_-]{1,128}$`, so the agent/MCP boundary derives the wire
14
+ * name automatically: every run of characters outside `[a-zA-Z0-9_-]`
15
+ * becomes a single `_`, truncated to 128 characters. Internal ids never
16
+ * change — resolution back to the id happens at the boundary.
17
+ *
18
+ * ```
19
+ * devframe:state:read → devframe_state_read
20
+ * devframes:plugin:git:status → devframes_plugin_git_status
21
+ * ```
22
+ *
23
+ * A plain string transform with no node dependency, so browser-side UIs
24
+ * that display a tool's id (e.g. the inspect plugin's agent view) can
25
+ * import it too and show the name a client actually calls.
26
+ *
27
+ * @experimental The agent-native surface is experimental and may change
28
+ * without a major version bump until it stabilizes.
29
+ */
30
+ function toAgentToolName(id) {
31
+ return id.replace(/[^\w-]+/g, "_").slice(0, MAX_TOOL_NAME_LENGTH);
32
+ }
33
+ //#endregion
34
+ export { toAgentToolName as t };
@@ -1,9 +1,8 @@
1
1
  import { n as colors } from "./diagnostics-reporter-CsIG85Q5.mjs";
2
2
  import { createBuild } from "./adapters/build.mjs";
3
- import { n as resolveDevServerPort, t as createDevServer } from "./dev-BCeaQuGE.mjs";
3
+ import { n as resolveDevServerPort, t as createDevServer } from "./dev-BSHFZGZr.mjs";
4
4
  import process from "node:process";
5
- import cac from "cac";
6
- import { safeParse } from "valibot";
5
+ import cac$1 from "cac";
7
6
  //#region src/adapters/flags.ts
8
7
  /**
9
8
  * Identity helper that preserves the literal schema-map type — use this
@@ -29,10 +28,12 @@ function defineCliFlags(flags) {
29
28
  return flags;
30
29
  }
31
30
  /**
32
- * Best-effort probe of a valibot schema to decide whether the
33
- * corresponding CAC option takes a value. Unwraps `optional` / `nullable`
34
- * / `nullish` / `default` / `pipe` wrappers then matches on the inner
35
- * type's kind.
31
+ * Best-effort, dependency-free probe of a schema to decide whether the
32
+ * corresponding CAC option takes a value. Duck-types the `type` /
33
+ * `wrapped` / `inner` / `pipe` fields exposed by valibot and by devframe's
34
+ * built-in `s` builder, unwrapping `optional` / `nullable` / `nullish` /
35
+ * `pipe` wrappers then matching on the inner kind. Validators that don't
36
+ * expose these fields (e.g. zod) fall through to a value-taking option.
36
37
  */
37
38
  function getSchemaKind(schema) {
38
39
  let current = schema;
@@ -46,7 +47,7 @@ function getSchemaKind(schema) {
46
47
  current = current.pipe[0];
47
48
  continue;
48
49
  }
49
- return kind;
50
+ return kind ?? "unknown";
50
51
  }
51
52
  return "unknown";
52
53
  }
@@ -54,14 +55,18 @@ function getSchemaKind(schema) {
54
55
  function isBooleanFlag(schema) {
55
56
  return getSchemaKind(schema) === "boolean";
56
57
  }
57
- /** Validate and coerce the raw cac-parsed bag against a {@link CliFlagsSchema}. */
58
+ /** Validate the raw cac-parsed bag against a {@link CliFlagsSchema}. */
58
59
  function parseCliFlags(schema, raw) {
59
60
  const flags = {};
60
61
  const issues = [];
61
62
  for (const [key, fieldSchema] of Object.entries(schema)) {
62
- const result = safeParse(fieldSchema, raw[key]);
63
- if (result.success) flags[key] = result.output;
64
- else issues.push(`--${toKebab(key)}: ${result.issues.map((i) => i.message).join(", ")}`);
63
+ const result = fieldSchema["~standard"].validate(raw[key]);
64
+ if (result instanceof Promise) {
65
+ issues.push(`--${toKebab(key)}: async flag validation is not supported`);
66
+ continue;
67
+ }
68
+ if (result.issues) issues.push(`--${toKebab(key)}: ${result.issues.map((i) => i.message).join(", ")}`);
69
+ else flags[key] = result.value;
65
70
  }
66
71
  for (const [key, value] of Object.entries(raw)) if (!(key in schema) && !(key in flags)) flags[key] = value;
67
72
  return issues.length ? {
@@ -87,7 +92,7 @@ function flagKeyToOption(camel) {
87
92
  function createCac(d, options = {}) {
88
93
  const defaultPort = options.defaultPort ?? d.cli?.port ?? 9999;
89
94
  const defaultHost = d.cli?.host ?? "localhost";
90
- const cli = cac(d.cli?.command ?? d.id);
95
+ const cli = cac$1(d.cli?.command ?? d.id);
91
96
  const devCommand = cli.command("[...args]", "Start a local dev server").option("--port <port>", "Port to listen on").option("--host <host>", "Host to bind to", { default: defaultHost }).option("--open", "Open the browser on start").option("--no-open", "Do not open the browser").option("--no-auth", "Disable the interactive authentication gate").option("--mcp", "Expose an MCP server over HTTP at /__mcp (use --no-mcp to disable) [experimental]");
92
97
  if (d.cli?.flags) for (const [key, schema] of Object.entries(d.cli.flags)) {
93
98
  const optionName = flagKeyToOption(key);
@@ -0,0 +1,11 @@
1
+ //#region src/cli/main.d.ts
2
+ /**
3
+ * The `devframe` bin — the framework's own CLI, distinct from the per-app
4
+ * CLI shells authors build with `createCac(definition)`. It hosts the
5
+ * app-independent commands; today that is `connect`, the MCP connector.
6
+ *
7
+ * @experimental
8
+ */
9
+ declare function runDevframeCli(argv?: string[]): Promise<void>;
10
+ //#endregion
11
+ export { runDevframeCli };
@@ -0,0 +1,284 @@
1
+ import { t as diagnostics } from "../diagnostics-B5-qHeqD.mjs";
2
+ import { n as probeDevframeOrigin, t as listLiveDevframeInstances } from "../instance-registry-D6fxYu36.mjs";
3
+ import { t as toAgentToolName } from "../agent-tool-name-C3b5vEwJ.mjs";
4
+ import { Diagnostic } from "nostics";
5
+ import process from "node:process";
6
+ import { joinURL } from "ufo";
7
+ import { cac } from "cac";
8
+ //#region src/cli/connect.ts
9
+ const INDEX_TOOL = toAgentToolName("devframe:connect:list-instances");
10
+ const CALL_TOOL = toAgentToolName("devframe:connect:call-tool");
11
+ 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.";
12
+ const GATEWAY_TOOLS = [{
13
+ name: INDEX_TOOL,
14
+ title: "Discover running devframes",
15
+ 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.",
16
+ inputSchema: {
17
+ type: "object",
18
+ properties: {}
19
+ },
20
+ annotations: {
21
+ readOnlyHint: true,
22
+ destructiveHint: false
23
+ }
24
+ }, {
25
+ name: CALL_TOOL,
26
+ title: "Call a devframe tool",
27
+ 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.",
28
+ inputSchema: {
29
+ type: "object",
30
+ properties: {
31
+ port: {
32
+ type: "number",
33
+ description: "The instance's port, from the list-instances tool."
34
+ },
35
+ tool: {
36
+ type: "string",
37
+ description: "Tool name, from the instance's tool list."
38
+ },
39
+ args: {
40
+ type: "object",
41
+ description: "Arguments object for the tool. Omit for zero-argument tools."
42
+ }
43
+ },
44
+ required: ["port", "tool"],
45
+ additionalProperties: false
46
+ }
47
+ }];
48
+ /**
49
+ * Start the devframe MCP connector on stdio: a thin discovery + proxy server
50
+ * in the shape Vercel's next-devtools-mcp (https://github.com/vercel/next-devtools-mcp)
51
+ * validated — credit due there for the architecture this connector follows.
52
+ * It exposes two gateway tools —
53
+ * `devframe_connect_list-instances` (discover running devframe instances via
54
+ * the instance registry and list each one's MCP tools) and
55
+ * `devframe_connect_call-tool` (invoke one tool on one instance over its
56
+ * Streamable-HTTP endpoint) — and holds no domain knowledge of its own.
57
+ *
58
+ * @experimental
59
+ */
60
+ async function startConnectServer(options = {}) {
61
+ const sdk = await importSdk();
62
+ const server = new sdk.Server({
63
+ name: "devframe-connect",
64
+ version: "0.0.0"
65
+ }, { capabilities: { tools: {} } });
66
+ server.setRequestHandler("tools/list", async () => ({ tools: GATEWAY_TOOLS }));
67
+ server.setRequestHandler("tools/call", async (request) => {
68
+ const { name, arguments: args } = request.params;
69
+ try {
70
+ if (name === INDEX_TOOL) return textResult(await index(sdk, options));
71
+ if (name === CALL_TOOL) return textResult(await call(sdk, options, args ?? {}));
72
+ return errorResult({
73
+ message: `unknown tool "${name}"`,
74
+ fix: `Call ${INDEX_TOOL} or ${CALL_TOOL}.`
75
+ });
76
+ } catch (error) {
77
+ return errorResult(toErrorPayload(error));
78
+ }
79
+ });
80
+ const transport = new sdk.StdioServerTransport();
81
+ await server.connect(transport);
82
+ return { stop: async () => {
83
+ await server.close();
84
+ } };
85
+ }
86
+ async function importSdk() {
87
+ try {
88
+ const [serverMod, stdioMod, clientMod] = await Promise.all([
89
+ import("@modelcontextprotocol/server"),
90
+ import("@modelcontextprotocol/server/stdio"),
91
+ import("@modelcontextprotocol/client")
92
+ ]);
93
+ return {
94
+ Server: serverMod.Server,
95
+ StdioServerTransport: stdioMod.StdioServerTransport,
96
+ Client: clientMod.Client,
97
+ StreamableHTTPClientTransport: clientMod.StreamableHTTPClientTransport
98
+ };
99
+ } catch (error) {
100
+ const reason = error instanceof Error ? error.message : String(error);
101
+ throw diagnostics.DF0046({
102
+ reason,
103
+ cause: error
104
+ });
105
+ }
106
+ }
107
+ /** Discover instances: registry (prune-on-read) + explicit port probes. */
108
+ async function index(sdk, options) {
109
+ const { live } = await listLiveDevframeInstances({
110
+ instancesDir: options.instancesDir,
111
+ timeoutMs: options.timeoutMs
112
+ });
113
+ const records = [...live];
114
+ for (const port of options.ports ?? []) {
115
+ if (records.some((r) => r.port === port)) continue;
116
+ const probed = await probePort(port, options.timeoutMs);
117
+ if (probed) records.push(probed);
118
+ }
119
+ const instances = await Promise.all(records.map(async (record) => {
120
+ const { mcp, ...rest } = record;
121
+ const entry = {
122
+ ...rest,
123
+ mcp: null
124
+ };
125
+ if (!mcp) {
126
+ entry.hint = MCP_DISABLED_HINT;
127
+ return entry;
128
+ }
129
+ const url = `${record.origin}${mcp.path}`;
130
+ try {
131
+ entry.mcp = {
132
+ url,
133
+ tools: await listInstanceTools(sdk, url)
134
+ };
135
+ } catch (error) {
136
+ entry.mcp = {
137
+ url,
138
+ error: error instanceof Error ? error.message : String(error)
139
+ };
140
+ }
141
+ return entry;
142
+ }));
143
+ return {
144
+ instances,
145
+ ...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." } : {}
146
+ };
147
+ }
148
+ /**
149
+ * Probe an explicit port for a devframe serving `__connection.json` at `/`,
150
+ * reusing the registry's origin-candidate probe (a `localhost`-bound server
151
+ * may listen on either address family).
152
+ */
153
+ async function probePort(port, timeoutMs) {
154
+ const probed = await probeDevframeOrigin(`http://localhost:${port}`, "/", timeoutMs);
155
+ if (!probed) return null;
156
+ const mcpPath = probed.meta.mcp ? joinURL("/", probed.meta.mcp.path) : null;
157
+ return {
158
+ pid: -1,
159
+ port,
160
+ origin: probed.origin,
161
+ basePath: "/",
162
+ id: `port-${port}`,
163
+ rootDir: "",
164
+ mcp: mcpPath ? { path: mcpPath } : null,
165
+ startedAt: 0
166
+ };
167
+ }
168
+ async function listInstanceTools(sdk, url) {
169
+ return withInstanceClient(sdk, url, async (client) => {
170
+ return (await client.listTools()).tools.map((tool) => ({
171
+ name: tool.name,
172
+ description: tool.description
173
+ }));
174
+ });
175
+ }
176
+ async function call(sdk, options, args) {
177
+ if (typeof args.port !== "number" || typeof args.tool !== "string") throw diagnostics.DF0049();
178
+ const { live } = await listLiveDevframeInstances({
179
+ instancesDir: options.instancesDir,
180
+ timeoutMs: options.timeoutMs
181
+ });
182
+ const record = live.find((r) => r.port === args.port) ?? await probePort(args.port, options.timeoutMs);
183
+ if (!record) throw diagnostics.DF0050({ port: args.port });
184
+ if (!record.mcp) throw diagnostics.DF0051({ port: args.port });
185
+ return withInstanceClient(sdk, `${record.origin}${record.mcp.path}`, async (client) => {
186
+ const result = await client.callTool({
187
+ name: args.tool,
188
+ arguments: args.args ?? {}
189
+ });
190
+ return {
191
+ instance: {
192
+ id: record.id,
193
+ port: record.port
194
+ },
195
+ tool: args.tool,
196
+ isError: result.isError ?? false,
197
+ content: result.content,
198
+ ...result.structuredContent ? { structuredContent: result.structuredContent } : {}
199
+ };
200
+ });
201
+ }
202
+ async function withInstanceClient(sdk, url, fn) {
203
+ const origin = new URL(url).origin;
204
+ const transport = new sdk.StreamableHTTPClientTransport(new URL(url), { requestInit: { headers: { origin } } });
205
+ const client = new sdk.Client({
206
+ name: "devframe-connect",
207
+ version: "0.0.0"
208
+ });
209
+ await client.connect(transport);
210
+ try {
211
+ return await fn(client);
212
+ } finally {
213
+ await client.close().catch(() => {});
214
+ }
215
+ }
216
+ function textResult(value) {
217
+ return { content: [{
218
+ type: "text",
219
+ text: JSON.stringify(value, null, 2)
220
+ }] };
221
+ }
222
+ /**
223
+ * Project a thrown value into the connector's structured error payload. A
224
+ * nostics `Diagnostic` carries its code, `fix`, and docs URL across so the
225
+ * calling agent gets the actionable next step.
226
+ */
227
+ function toErrorPayload(error) {
228
+ if (error instanceof Diagnostic) return {
229
+ code: error.code,
230
+ message: error.message,
231
+ ...error.fix ? { fix: error.fix } : {},
232
+ ...error.docs ? { docs: error.docs } : {}
233
+ };
234
+ return {
235
+ message: error instanceof Error ? error.message : String(error),
236
+ ...error && typeof error === "object" && "fix" in error && typeof error.fix === "string" ? { fix: error.fix } : {}
237
+ };
238
+ }
239
+ function errorResult(error) {
240
+ return {
241
+ isError: true,
242
+ content: [{
243
+ type: "text",
244
+ text: JSON.stringify({ error }, null, 2)
245
+ }]
246
+ };
247
+ }
248
+ /** Parse the repeatable `--port` flag value(s) from cac into numbers. */
249
+ function parsePortsFlag(value) {
250
+ return (Array.isArray(value) ? value : value === void 0 ? [] : [value]).map((v) => Number(v)).filter((n) => Number.isInteger(n) && n > 0 && n < 65536);
251
+ }
252
+ /** Keep the connector process alive until the stdio transport closes it. */
253
+ function keepAlive() {
254
+ process.stdin.resume();
255
+ }
256
+ //#endregion
257
+ //#region src/cli/main.ts
258
+ /**
259
+ * The `devframe` bin — the framework's own CLI, distinct from the per-app
260
+ * CLI shells authors build with `createCac(definition)`. It hosts the
261
+ * app-independent commands; today that is `connect`, the MCP connector.
262
+ *
263
+ * @experimental
264
+ */
265
+ async function runDevframeCli(argv = process.argv) {
266
+ const cli = cac("devframe");
267
+ 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) => {
268
+ await startConnectServer({
269
+ ports: parsePortsFlag(options.port),
270
+ instancesDir: options.instancesDir,
271
+ timeoutMs: options.timeout
272
+ });
273
+ keepAlive();
274
+ });
275
+ cli.help();
276
+ cli.parse(argv, { run: false });
277
+ if (!cli.matchedCommand) {
278
+ if (!cli.options.help) cli.outputHelp();
279
+ return;
280
+ }
281
+ await cli.runMatchedCommand();
282
+ }
283
+ //#endregion
284
+ export { runDevframeCli };
@@ -1,7 +1,7 @@
1
- import { $ as DevframeRpcServerFunctions, F as ScopedSharedStates, I as SettingsForNamespace, J as RpcSharedStateHost, N as ScopedRpcFn, O as DevframeSettings, P as ScopedServerFunctions, Q as DevframeRpcClientFunctions, _ as ConnectionMeta, at as StreamReader, ht as SharedState, kt as EventEmitter, ot as StreamSink, q as RpcSharedStateGetOptions } from "../devframe-Ckhf3kFw.mjs";
2
- import { _ as RpcFunctionDefinition, w as RpcFunctionsCollector } from "../types-CrzNxXKq.mjs";
3
- import { C as RpcCacheManager, w as RpcCacheOptions } from "../index-DgsLFhZg.mjs";
4
- import { t as WsRpcChannelOptions } from "../ws-client-DSXiI2h7.mjs";
1
+ import { $ as DevframeRpcServerFunctions, F as ScopedSharedStates, I as SettingsForNamespace, J as RpcSharedStateHost, N as ScopedRpcFn, O as DevframeSettings, P as ScopedServerFunctions, Q as DevframeRpcClientFunctions, _ as ConnectionMeta, at as StreamReader, ht as SharedState, jt as EventEmitter, ot as StreamSink, q as RpcSharedStateGetOptions } from "../devframe-qgCKL683.mjs";
2
+ import { _ as RpcFunctionDefinition, w as RpcFunctionsCollector } from "../types-CnJSgRVa.mjs";
3
+ import { E as RpcCacheOptions, T as RpcCacheManager } from "../index-Dbw1p5ch.mjs";
4
+ import { t as WsRpcChannelOptions } from "../ws-client-B6Tnr0ic.mjs";
5
5
  import { BirpcOptions, BirpcReturn } from "birpc";
6
6
  //#region src/client/connection.d.ts
7
7
  interface DevframeConnection {
@@ -26,6 +26,12 @@ interface SetupDevframeConnectionOptions {
26
26
  /** Override the locally stored auth token. */
27
27
  authToken?: string;
28
28
  }
29
+ /**
30
+ * Allow an external viewer to connect by registering its browser origin with
31
+ * the Devframe host. Returns `false` if the host did not provide an origin
32
+ * registration token.
33
+ */
34
+ declare function registerDevframeViewerOrigin(connection: DevframeConnection, origin?: any): Promise<boolean>;
29
35
  /**
30
36
  * Return connection information previously prepared in this window or an
31
37
  * accessible parent window.
@@ -377,15 +383,16 @@ declare function getDevframeRpcClient(options?: DevframeRpcClientOptions): Promi
377
383
  //#endregion
378
384
  //#region src/client/otp.d.ts
379
385
  /**
380
- * Read a one-time authentication code (OTP) from the current page URL's query
381
- * string, without side effects. Returns `undefined` when the parameter is absent.
386
+ * Read a one-time authentication code (OTP) from the current page URL's
387
+ * fragment, without side effects. Returns `undefined` when the parameter is
388
+ * absent.
382
389
  */
383
390
  declare function readOtpFromUrl(param?: string): string | undefined;
384
391
  /**
385
- * Read the one-time code from the page URL and remove it from the address bar
386
- * (and the current history entry), so the single-use code isn't left in the
387
- * URL, browser history, or a `Referer`. Returns the code, or `undefined` when
388
- * absent.
392
+ * Read the one-time code from the page URL fragment and remove it from the
393
+ * address bar (and the current history entry), so the single-use code isn't
394
+ * left in the URL, browser history, or a `Referer`. Returns the code, or
395
+ * `undefined` when absent.
389
396
  */
390
397
  declare function consumeOtpFromUrl(param?: string): string | undefined;
391
398
  /**
@@ -402,6 +409,28 @@ declare function authenticateWithUrlOtp(rpc: Pick<DevframeRpcClient, 'isTrusted'
402
409
  param?: string;
403
410
  }): Promise<boolean>;
404
411
  //#endregion
412
+ //#region src/client/rpc-ws.d.ts
413
+ /** Minimal subset of `window.location` needed to resolve a WS URL. */
414
+ interface WsUrlLocation {
415
+ protocol: string;
416
+ host: string;
417
+ hostname: string;
418
+ href: string;
419
+ }
420
+ /**
421
+ * Resolve a {@link ConnectionMeta.websocket} descriptor into a concrete
422
+ * `ws(s)://` URL.
423
+ *
424
+ * The object / relative-path forms connect to the page's own origin (only the
425
+ * `http`→`ws` protocol swap is applied), resolving the path against where
426
+ * `__connection.json` was loaded. This is deliberately host-agnostic so the
427
+ * connection survives a reverse proxy that changes the domain or port — the
428
+ * client trusts its own location, never a server-baked hostname. An explicit
429
+ * `port`/`host` (or a full `ws(s)://` URL string) opts into a cross-origin
430
+ * endpoint, e.g. a side-car server on its own port.
431
+ */
432
+ declare function resolveWsUrl(websocket: ConnectionMeta['websocket'], metaBaseUrl: string, loc: WsUrlLocation): string;
433
+ //#endregion
405
434
  //#region src/client/settings.d.ts
406
435
  /**
407
436
  * Build the client-side `settings` surface for a scope namespace. Mirrors
@@ -412,4 +441,4 @@ declare function createClientSettings<T extends Record<string, any> = Record<str
412
441
  //#region src/client/index.d.ts
413
442
  declare const connectDevframe: typeof getDevframeRpcClient;
414
443
  //#endregion
415
- export { DevframeClientRpcHost, DevframeConnection, DevframeConnectionError, DevframeConnectionErrorKind, DevframeConnectionStatus, DevframeRpcClient, DevframeRpcClientCall, DevframeRpcClientCallEvent, DevframeRpcClientCallOptional, DevframeRpcClientMode, DevframeRpcClientOptions, DevframeRpcContext, DevframeScopedClientContext, DevframeScopedClientRpc, DevframeScopedClientStreamingHost, RpcClientEvents, RpcStreamingClientHost, SetupDevframeConnectionOptions, StreamingSubscribeOptions, authenticateWithUrlOtp, connectDevframe, consumeOtpFromUrl, createClientSettings, createRpcStreamingClientHost, createScopedClientContext, getDevframeConnection, getDevframeRpcClient, isCallableStatus, readOtpFromUrl, setupDevframeConnection };
444
+ export { DevframeClientRpcHost, DevframeConnection, DevframeConnectionError, DevframeConnectionErrorKind, DevframeConnectionStatus, DevframeRpcClient, DevframeRpcClientCall, DevframeRpcClientCallEvent, DevframeRpcClientCallOptional, DevframeRpcClientMode, DevframeRpcClientOptions, DevframeRpcContext, DevframeScopedClientContext, DevframeScopedClientRpc, DevframeScopedClientStreamingHost, RpcClientEvents, RpcStreamingClientHost, SetupDevframeConnectionOptions, StreamingSubscribeOptions, type WsUrlLocation, authenticateWithUrlOtp, connectDevframe, consumeOtpFromUrl, createClientSettings, createRpcStreamingClientHost, createScopedClientContext, getDevframeConnection, getDevframeRpcClient, isCallableStatus, readOtpFromUrl, registerDevframeViewerOrigin, resolveWsUrl, setupDevframeConnection };