You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit d7a2fbb
Browse filesBrowse the repository at this point in the historyBrowse files
- Auto tool-name convention: internal ids stay colon-namespaced
(devframe:<area>:<fn>, devframes:plugin:<slug>:<fn>, command ids);
the MCP boundary derives the wire name via toAgentToolName (chars
outside [a-zA-Z0-9_-] -> '_', <=128) so every client's tool-name
pattern is satisfied. Calls resolve back to ids; collisions keep the
first registration and warn (DF0047). Documented in the agent-native
guide; exported from devframe/node.
- Validator neutrality: the git plugin's agent schemas now use
devframe/utils/simple-schema; valibot leaves its runtime deps.
- Coded diagnostics for the remaining ad-hoc throws: DF0048 (unknown
shared-state key), DF0049-DF0051 (connector call errors) — each with
a docs page; the connector projects Diagnostic code/fix/docs into its
structured error payload.
- Dedupe: one __connection.json probe primitive (probeDevframeOrigin)
behind registry liveness checks and the connector's --port probe; one
shared coerceAgentPositionalArgs behind the agent host's RPC bridge
and the hub's command tools (explicit wrap/drop fallback).
- connect.ts: IndexedInstance derives from DevframeInstanceRecord; the
lazy SDK seam is typed (ConnectSdk).
- Plan 031 accuracy: record carries origin (not host), actual DF codes,
wire-name note; plans/README.md row updated.
Copy file name to clipboardExpand all lines: docs/adapters/mcp.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -85,10 +85,10 @@ The `devframe` bin ships an MCP **connector** — a thin discovery + proxy serve
85
85
}
86
86
```
87
87
88
-
It exposes two gateway tools:
88
+
It exposes two gateway tools (the wire names of the `devframe:connect:*` ids — see [tool ids and wire names](/guide/agent-native#tool-ids-and-wire-names)):
89
89
90
-
-**`devframe:connect:list-instances`** — discover running devframe dev servers and list each one's MCP tools. Instances running without an MCP route are listed with a hint to restart with `--mcp`.
91
-
-**`devframe:connect:call-tool`** — invoke one tool on one instance (`{ port, tool, args }`) over its Streamable-HTTP endpoint.
90
+
-**`devframe_connect_list-instances`** — discover running devframe dev servers and list each one's MCP tools. Instances running without an MCP route are listed with a hint to restart with `--mcp`.
91
+
-**`devframe_connect_call-tool`** — invoke one tool on one instance (`{ port, tool, args }`) over its Streamable-HTTP endpoint.
92
92
93
93
Discovery reads the **instance registry**: every `createDevServer` (CLI `dev`, `viteDevBridge`, `@devframes/next`'s handler) writes a record to `~/.devframe/instances/<pid>-<port>.json` on boot and removes it on close; readers prune records whose liveness probe fails. In-process hosts register explicitly with `registerDevframeInstance` from `devframe/node` — see `createDevframeNextHost().mountMcp` for serving MCP on a Next app's own origin. `--port <n>` probes an explicit port besides the registry; `DEVFRAME_INSTANCES_DIR` relocates the registry and `DEVFRAME_DISABLE_INSTANCE_REGISTRY=1` opts a server out.
> Agent tool "`{id}`" is hidden from the MCP surface: its wire name "`{name}`" collides with the tool "`{existing}`".
10
+
11
+
## Cause
12
+
13
+
MCP clients constrain tool names to `^[a-zA-Z0-9_-]{1,128}$`, so the MCP adapter derives each tool's wire name from its id (runs of characters outside `[a-zA-Z0-9_-]` become a single `_`). Two registered ids sanitized to the same wire name — e.g. `demo:greet` and `demo_greet`. The first registration keeps the name; the later tool is hidden from `tools/list`.
ctx.agent.registerTool({ id: 'demo_greet', description: '…', handler }) // hidden: same wire name
20
+
```
21
+
22
+
## Fix
23
+
24
+
Rename one of the two ids so they sanitize to distinct wire names. Namespaced ids (`devframes:plugin:<slug>:<fn>`, `devframe:<area>:<fn>`) collide only when they differ solely in separator characters.
25
+
26
+
## Source
27
+
28
+
-[`packages/devframe/src/adapters/mcp/build-server.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/adapters/mcp/build-server.ts) — the `tools/list` handler reports this once per hidden tool when deduplicating wire names.
The built-in `devframe_state_read` MCP tool was called with a `key` that is not among the shared-state keys the host publishes (or that the `exposeSharedState` filter allows). The error crosses the MCP boundary as structured JSON, so the calling agent can self-correct.
14
+
15
+
## Example
16
+
17
+
```ts
18
+
// The host publishes only `my-plugin:counter`; an agent calls:
Call the `devframe_state_read` tool without arguments to list the available keys, then retry with one of them.
25
+
26
+
## Source
27
+
28
+
-[`packages/devframe/src/adapters/mcp/build-server.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/adapters/mcp/build-server.ts) — `readStateResult()` throws this when the requested key is absent from the filtered key list.
The `devframe connect` gateway tool `devframe_connect_call-tool` was invoked without a numeric `port` or a string `tool` name — the two fields that identify which instance to dial and which of its tools to call. The error crosses the MCP boundary as structured JSON, so the calling agent can self-correct.
Call `devframe_connect_list-instances` first — its result carries each instance's `port` and tool names — then retry with both fields.
24
+
25
+
## Source
26
+
27
+
-[`packages/devframe/src/cli/connect.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/cli/connect.ts) — `call()` throws this when the gateway arguments fail validation.
The `devframe connect` gateway tool `devframe_connect_call-tool` targeted a port with no live devframe instance behind it — neither the instance registry nor a direct probe of the port found one serving `__connection.json`. The instance may have stopped, restarted on a different port, or never existed. The error crosses the MCP boundary as structured JSON, so the calling agent can self-correct.
Call `devframe_connect_list-instances` for the current instance list and retry with a live port.
25
+
26
+
## Source
27
+
28
+
-[`packages/devframe/src/cli/connect.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/cli/connect.ts) — `call()` throws this when neither the registry nor the port probe finds an instance.
> The devframe instance on port `{port}` has no MCP endpoint.
10
+
11
+
## Cause
12
+
13
+
The `devframe connect` gateway tool `devframe_connect_call-tool` targeted a live devframe instance that runs without an MCP route — its `__connection.json` advertises no `mcp` entry, so there is no endpoint to proxy the tool call to. The error crosses the MCP boundary as structured JSON, so the calling agent can self-correct.
14
+
15
+
## Example
16
+
17
+
```ts
18
+
// The instance on 5173 was started without --mcp:
Restart the instance with the `--mcp` flag (or set `cli.mcp: true` on its definition) to expose its tools, then list instances again.
25
+
26
+
## Source
27
+
28
+
-[`packages/devframe/src/cli/connect.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/cli/connect.ts) — `call()` throws this when the targeted instance's record carries `mcp: null`.
Agent tools take a single object input. The MCP adapter synthesises `arg0`, `arg1`, … from positional args (`args: [A, B]`); a single object schema (`args: [v.object({ ... })]`) reads better at the agent boundary because property names are self-describing.
45
45
46
+
## Tool ids and wire names
47
+
48
+
Every agent tool has two names:
49
+
50
+
-**The id** — how the tool is registered and invoked inside devframe. Ids are colon-namespaced by convention: `devframes:plugin:<slug>:<fn>` for plugin RPCs, `devframe:<area>:<fn>` for built-ins, and command ids for hub-command-derived tools.
51
+
-**The wire name** — what MCP clients see and call. Clients constrain tool names to `^[a-zA-Z0-9_-]{1,128}$`, so the MCP adapter derives the wire name automatically: every run of characters outside `[a-zA-Z0-9_-]` becomes a single `_`, truncated to 128 characters.
The convention applies uniformly to `agent`-flagged RPCs, tools registered via `registerTool` / `registerToolProvider`, and the hub's command-derived tools — keep registering with namespaced ids and let the boundary derive the name. `toAgentToolName` (from `devframe/node`) computes the mapping when you need to predict a wire name (e.g. in a client config or a test). Calls resolve back to the id at the boundary; two ids that sanitize to the same wire name keep the first registration and hide the later one with a `DF0047` warning.
60
+
46
61
## Registering a plugin tool
47
62
48
63
For tools without a matching RPC — say, an on-demand narrative summary — register them directly:
@@ -96,7 +111,7 @@ ctx.agent.registerResource({
96
111
97
112
Every `ctx.rpc.sharedState` key is also automatically exposed to MCP as `devframe://state/<key>`. Pass `exposeSharedState: false` (or a filter function) to `createMcpServer` to opt out.
98
113
99
-
Shared state is additionally reachable through the built-in **`devframe:state:read` tool** — call it without arguments for the key list, with a `key` for that value — since many MCP clients only consume tools. It honors the same `exposeSharedState` filter as the resource projection.
114
+
Shared state is additionally reachable through the built-in **`devframe:state:read` tool**(wire name `devframe_state_read`) — call it without arguments for the key list, with a `key` for that value — since many MCP clients only consume tools. It honors the same `exposeSharedState` filter as the resource projection.
0 commit comments