Skip to content

feat(agent)!: agent-native MCP wave — bridges, core surface, instance registry, devframe connect - #145

Merged
antfu merged 11 commits into
mainfrom
feat/agent-mcp-wave-phase-3
Aug 3, 2026
Merged

feat(agent)!: agent-native MCP wave — bridges, core surface, instance registry, devframe connect#145
antfu merged 11 commits into
mainfrom
feat/agent-mcp-wave-phase-3

Conversation

@antfubot

@antfubot antfubot commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Why

The full agent-native MCP wave (plan 031). Vercel's next-devtools-mcp 0.4.0 validated an architecture devframe was already positioned for: the real MCP endpoint lives inside the framework, and a thin external connector just discovers and proxies it. This wave builds both halves — the embedded surface and the connector — proven end-to-end, including the literal /_next/mcp shape on devframe primitives via @devframes/next.

Supersedes #142 (phase 1) and #144 (phase 2), both folded in here.

Breaking change

MCP tool names are now sanitized wire names, not the raw colon-namespaced ids. MCP clients constrain tool names to ^[a-zA-Z0-9_-]{1,128}$; every tool this wave ships uses a colon-namespaced id (devframe:<area>:<fn>, devframes:plugin:<slug>:<fn>), which several strict clients (including the Anthropic API) reject outright. The MCP boundary now derives a wire-safe name automatically (toAgentToolName — every run of characters outside [a-zA-Z0-9_-] becomes _) and resolves calls back to the id:

Id Old (rejected by strict clients) Wire name (this PR)
devframe:state:read devframe:state:read devframe_state_read
devframe:connect:list-instances devframe:connect:list-instances devframe_connect_list-instances
devframe:connect:call-tool devframe:connect:call-tool devframe_connect_call-tool
devframes:plugin:git:status (and the other 4 git tools) devframes:plugin:git:status devframes_plugin_git_status

Anything hardcoding the colon form as a literal MCP tool name (an agent client's tool allow-list, a saved prompt, a test) needs the sanitized name instead — this PR ships before any of these names went out in a release, but the rename is breaking relative to earlier previews of this branch (#142/#144) and worth flagging for anyone who already wired against them. See tool ids and wire names in the agent-native guide.

Phase 1 — bridges & structured errors

  • Bridge MCP forwarding. viteDevBridge and @devframes/next's createDevframeNextHandler gain an mcp option (forwarded to createDevServer) and advertise the side-car endpoint in their __connection.jsonConnectionMeta['mcp'] gains an optional port for side-car origins. Shared resolver resolveMcpConnectionMeta.
  • Structured diagnostic errors. formatMcpError emits { error: { code, message, fix?, docs? } } for nostics Diagnostics, so agents get the actionable next step and docs URL instead of a flattened string.
  • Docs. Agent-native guide documents the conventions: description-as-prompt, gateway tools, structured errors.

Phase 2 — core surface

  • createMcpFetchHandler(ctx, options) — the MCP Streamable-HTTP endpoint as a framework-agnostic web-standard Request → Response handler; mountMcpHttp is a thin h3 wrapper over it.
  • Built-in devframe:state:read tool — tool-shaped shared-state access (no key → key list, key → JSON value), honoring the exposeSharedState filter alongside the resource projection.
  • Hub commands → agent surface, as a lazy provider. New core API ctx.agent.registerToolProvider(() => AgentToolInput[]): a tool source queried at list/getTool/invoke time, the same on-demand projection applied to agent-flagged RPCs. The commands host registers one provider deriving tools from its commands map — single source of truth, nothing mirrored. Commands opt in via an agent field (description, safety, optional valibot args); agent on a handler-less group throws DF8404; the field never crosses the wire.
  • Schema-typed handlers may be async — the schema-typed definition branch types handlers as Thenable<InferReturnType<RS>>.
  • git agent surfacestatus/log/show/branches/diff agent-flagged with args/returns schemas (safety: 'read'); writes stay agent-invisible. Cross-refs plan 029.

Phase 3 — instance registry, connector, in-process Next MCP

  • Instance registry (devframe/node): registerDevframeInstance writes an atomic record to ~/.devframe/instances/<pid>-<port>.json; readers prune dead records on failed __connection.json probes, dedup same-port ghosts (newest wins), and adopt a dialable origin for family-ambiguous localhost binds. createDevServer registers automatically and unregisters on close; in-process hosts call it explicitly. DEVFRAME_INSTANCES_DIR / DEVFRAME_DISABLE_INSTANCE_REGISTRY override. Failures degrade to coded warnings (DF0042).
  • devframe bin + connect — the package's first bin. devframe connect runs a stdio MCP connector exposing two gateway tools: devframe_connect_list-instances (discover live instances + list their MCP tools; MCP-less instances carry a restart-with---mcp funnel hint) and devframe_connect_call-tool (proxy one tool call over Streamable-HTTP). Errors carry { code, message, fix, docs } (coded diagnostics DF0049–DF0051); a missing SDK peer throws DF0046. --port probes beside the registry.
  • In-process Next MCPDevframeNextHost.mountMcp(ctx, path) serves the fetch handler on the Next app's own origin (the /_next/mcp shape). The hub example mounts /__hub/__mcp, advertises it, registers the instance, and agent-flags its ping command.
  • MCP conformance fix — non-object outputSchema projections are dropped (a v.void() returns schema produced type: null, which SDK clients reject).

Tool ids vs wire names

Internal tool ids follow the devframe:<area>:<fn> / devframes:plugin:<slug>:<fn> convention. MCP clients constrain tool names to ^[a-zA-Z0-9_-]{1,128}$, so the MCP boundary now derives the wire name automatically (toAgentToolName, from devframe/utils/agent-tool-name — a plain string transform, safe to import client-side too): runs of unsafe characters become _devframe:state:read ships as devframe_state_read, devframes:plugin:git:status as devframes_plugin_git_status. Calls resolve back to ids at the boundary; sanitize-collisions keep the first registration and warn (DF0047). The convention applies uniformly to agent-flagged RPCs, registered tools, providers, and hub-command tools, and is documented in the agent-native guide.

API surface

The public surface is deliberately minimal — only members with a consumer outside their own package are exported (createMcpFetchHandler, registerToolProvider, registerDevframeInstance + its two types, resolveMcpConnectionMeta, toAgentToolName (devframe/utils/agent-tool-name), coerceAgentPositionalArgs, and the feature options). valibot→JSON-Schema conversion and the registry read/probe/prune helpers stay internal.

Verification

Full gate: pnpm lint && pnpm test && pnpm typecheck && pnpm build1031 unit tests and 17/17 Playwright e2e, including two connector gates: files-inspector (discovery → gateway-tool round-trip → actionable errors) and minimal-next-devframe-hub (connector discovers the hub inside the Next dev server and calls its agent-flagged command through the in-process endpoint).

Post-review hardening

A two-axis review (standards + spec) surfaced findings that are now addressed:

  • Validator neutrality — the git plugin's agent schemas moved from a runtime valibot dependency to the built-in devframe/utils/simple-schema.
  • Coded diagnostics everywhere — the remaining ad-hoc throw new Errors (connector call errors, unknown shared-state key) became DF0048–DF0051 with docs pages; the connector projects code/fix/docs into its structured error payload.
  • Dedupe — one __connection.json probe primitive shared by registry liveness checks and the --port probe; one coerceAgentPositionalArgs (exported) shared by the agent host's RPC bridge and the hub's command tools.
  • TypingIndexedInstance derives from DevframeInstanceRecord; the connector's lazy SDK seam is typed.
  • Plan accuracy — plan 031 now records the origin field, the actual DF codes, and the wire-name convention; the plans/README.md row is updated.
  • Inspect plugin reflects the real tool name — the agent view was displaying each tool's internal (colon-namespaced) id as if it were what an MCP client calls; it now shows the sanitized wire name (toAgentToolName) as the primary label, with the internal id noted underneath only when they differ.

This PR was created with the help of an agent.

@antfubot

Copy link
Copy Markdown
Collaborator Author

Follow-up commit d1d9373 (also addresses feedback that applies to #144's commands bridge):

  • Lazy tool providers replace imperative sync. New core API ctx.agent.registerToolProvider(() => AgentToolInput[]) — a tool source queried at list/getTool/invoke time, the same on-demand projection the agent host already applies to agent-flagged RPC definitions. The hub commands host now registers one provider deriving tools from its commands map (single source of truth); the registerAgentTools/unregisterAgentTools mirror and its handle bookkeeping are gone. handle.notifyChanged() still drives MCP tools/list_changed.
  • Tool names follow the devframe:<area>:<fn> convention (matching devframe:agent:list-tools etc.): read_statedevframe:state:read, devframe_indexdevframe:connect:list-instances, devframe_calldevframe:connect:call-tool.

Gates rerun: 905 unit tests, typecheck, lint, and the three connector e2e specs all green. (tsnapi flagged the removed bridge methods as a narrowing — allowed deliberately since they only ever existed in this unmerged stack.)

@antfubot antfubot changed the title feat(agent): agent-native wave phase 3 — instance registry, devframe connect, in-process Next MCP feat(agent): agent-native wave phases 2+3 — core surface, instance registry, devframe connect Jul 29, 2026
@antfubot
antfubot changed the base branch from feat/agent-mcp-wave-phase-2 to feat/agent-mcp-wave-phase-1 July 29, 2026 08:39
@antfubot

Copy link
Copy Markdown
Collaborator Author

Follow-up commit b4b9bb0 shrinks the public API surface added by this wave:

  • devframe/utils/valibot-json-schema subpath removed. AgentToolInput gains optional valibot args — the same shape RPC definitions carry ("schemas are carried by the definition; consumers convert on demand"). The agent host derives the JSON-Schema input internally, the hub passes command schemas through untouched, and valibot→JSON-Schema conversion is an implementation detail again. Net: −1 public subpath (−2 exported functions), +1 optional field on an existing @experimental type.
  • Instance registry trimmed from 9 exports to 3. devframe/node now exports only registerDevframeInstance + DevframeInstanceRecord / DevframeInstanceRegistration — what custom hosts actually need. The read/probe/prune/list helpers and env-var consts stay internal to the connector (same package, relative imports).

Gates rerun: 905 unit tests (incl. new args-projection coverage), typecheck, lint, 3/3 connector e2e.

@antfubot antfubot changed the title feat(agent): agent-native wave phases 2+3 — core surface, instance registry, devframe connect feat(agent): agent-native MCP wave — bridges, core surface, instance registry, devframe connect Jul 30, 2026
@antfubot
antfubot changed the base branch from feat/agent-mcp-wave-phase-1 to main July 30, 2026 01:25
@antfubot
antfubot force-pushed the feat/agent-mcp-wave-phase-3 branch from b4b9bb0 to ef2598e Compare July 30, 2026 03:45
@netlify

netlify Bot commented Jul 30, 2026

Copy link
Copy Markdown

Deploy Preview for devfra ready!

Name Link
🔨 Latest commit f38b0c6
🔍 Latest deploy log https://app.netlify.com/projects/devfra/deploys/6a703ac04ce238000873fdbc
😎 Deploy Preview https://deploy-preview-145--devfra.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@antfubot

Copy link
Copy Markdown
Collaborator Author

Rebased onto main and retargeted from the phase-1 branch — phase 1 (#142) is already merged there, so this PR is now the clean phases 2+3 delta on top of it, with no phase-1 duplication.

Conflicts resolved during the rebase (main moved 8 commits, incl. #143's example-dir rename and #141's openHelperscommonRpcFunctions deprecation):

  • tests/__snapshots__/tsnapi/devframe/recipes/open-helpers.snapshot.d.ts — took main's deprecated-alias version; later regenerated cleanly (non-breaking) once phase 2's Thenable<InferReturnType<RS>> handler-return widening applied to it and common-rpc-functions.
  • examples/{minimal-next-devframe-hub → next-devframe-hub}/... (route handler + test) — git's rename detection carried my edits into the renamed path; merged main's ensureNextDevframeHub rename with my POST/DELETE/GET handler addition.
  • Follow-up fixups (ef2598e) to fully align with chore: rename minimal-* example dirs, reorganize docs nav #143's rename: the MCP serverName, registry instance id, e2e spec (renamed minimal-next-devframe-hub-dev.spec.tsnext-devframe-hub-dev.spec.ts) all now use example:next-devframe-hub; same fix for files-inspector's example:files-inspector id/namespace in devframe-connect.spec.ts; playwright.config.ts's stale cwd.

Full gate rerun clean post-rebase: 911 unit tests, typecheck, lint, build, and 17/17 Playwright e2e.

@antfubot

Copy link
Copy Markdown
Collaborator Author

Follow-up commit 158eb99: bare devframe (no subcommand) now shows help instead of silently exiting 0.

cac only auto-prints help for an explicit -h/--help flag — an unmatched command (bare invocation, or an unrecognized subcommand like devframe bogus) left matchedCommand unset with no fallback, so devframe alone did nothing. Now checks cli.matchedCommand after parse and falls back to cli.outputHelp(), guarded against cli.options.help so --help itself isn't printed twice (cac already handles that case internally).

4 new unit tests cover bare invocation, --help, an unrecognized subcommand, and a real subcommand's own help — asserting help prints exactly once in each case. Full gate green: 915 tests, typecheck, lint, build.

…ommands bridge, git agent tools

- createMcpFetchHandler: framework-agnostic web-standard MCP endpoint
  extracted from mountMcpHttp (now a thin h3 wrapper); exported from
  devframe/adapters/mcp for custom hosts (Next App Router, etc.)
- built-in read_state(key?) MCP tool over shared state, honoring the
  exposeSharedState filter alongside the resource projection
- hub commands gain opt-in agent exposure: agent field (description,
  safety, valibot args) projects handler-bearing commands into ctx.agent;
  DF8404 rejects agent exposure on group-only commands
- valibot→JSON-Schema conversion moved to devframe/utils/valibot-json-schema
  (public) so SDK-free hosts can convert schemas
- rpc: schema-typed handlers may be async — Thenable<InferReturnType<RS>>
  in the schema-typed definition branch
- git plugin: status/log/show/branches/diff agent-flagged with valibot
  args/returns schemas (read-only surface; writes stay private)
- docs: hub commands-as-tools, read_state, custom-host mounting; DF8404 page
…connect, in-process Next MCP

- instance registry: registerDevframeInstance/readDevframeInstances/
  probeDevframeInstance/listLiveDevframeInstances in devframe/node —
  atomic same-dir writes, prune-on-read, ghost dedup per (port, basePath),
  dialable-origin adoption for family-ambiguous localhost binds;
  createDevServer registers automatically and unregisters on close;
  DEVFRAME_INSTANCES_DIR / DEVFRAME_DISABLE_INSTANCE_REGISTRY overrides
- first devframe bin: `devframe connect` runs the stdio MCP connector —
  devframe_index (discover instances + their tools, funnel hints for
  MCP-less servers) and devframe_call (proxy one tool call over
  Streamable-HTTP); errors carry actionable fix payloads; missing SDK
  peer throws coded DF0043
- @devframes/next: DevframeNextHost.mountMcp serves MCP in-process on the
  Next app's own origin (the /_next/mcp shape); hub example wires it,
  advertises it in connection meta, registers the instance, and
  agent-flags its ping command; catch-all route exports POST/DELETE
- mcp adapter: drop non-object outputSchema projections (MCP requires
  type object; a v.void() returns schema broke SDK clients)
- e2e: devframe-connect (files-inspector round-trip incl. gateway tool)
  and minimal-next-devframe-hub (in-process discovery + command call);
  hermetic per-suite registries; vitest keeps unit runs out of the
  global registry
- diagnostics DF0042/DF0043 + docs pages; connect/registry docs in the
  MCP adapter page
- ctx.agent.registerToolProvider(() => AgentToolInput[]): a lazy tool
  source queried at list/getTool/invoke time — the same on-demand
  projection applied to agent-flagged RPCs; earlier sources win on id
  collision; handle.notifyChanged() drives tools/list_changed
- hub commands host derives its agent projection through one provider:
  the commands map is the single source of truth, replacing the
  registerAgentTools/unregisterAgentTools mirror and its handle map
- built-in and connector tool names follow the devframe:<area>:<fn>
  convention: read_state -> devframe:state:read, devframe_index ->
  devframe:connect:list-instances, devframe_call ->
  devframe:connect:call-tool
- drop the devframe/utils/valibot-json-schema subpath: AgentToolInput
  gains valibot args (the same shape RPC definitions carry); the agent
  host derives the JSON-Schema input internally, and the hub passes
  schemas through untouched — conversion is an implementation detail
  again
- devframe/node exports only registerDevframeInstance (+ its two types)
  from the instance registry; the read/probe/prune helpers stay internal
  to the connector
…space

main renamed examples/minimal-next-devframe-hub -> examples/next-devframe-hub
and its RPC/command/instance ids to the example:next-devframe-hub
convention (#143); this wave's additions (MCP serverName, registry
instance id, e2e spec file + assertions, playwright cwd) now match.
Same fix for files-inspector's example:files-inspector id/namespace.

Also regenerates the recipes/common-rpc-functions + recipes/open-helpers
dts snapshots for phase 2's Thenable<InferReturnType<RS>> handler-return
widening (non-breaking — allowed to update without --allow-breaking).
Bare `devframe` (no subcommand) silently exited 0 — cac only shows help
automatically for an explicit -h/--help flag, not an unmatched command.
Check matchedCommand after parse() and fall back to outputHelp(), guarding
against the already-handled --help case so it doesn't print twice.
main landed two breaking changes this branch didn't know about:
- deps!: migrate MCP adapter to @modelcontextprotocol/sdk v2 (#156) —
  the monolithic package split into @modelcontextprotocol/server +
  @modelcontextprotocol/client; setRequestHandler moved from imported
  schema constants to method-string form.
- feat(rpc)!: support Standard Schema for RPC definitions (#157) —
  RpcArgsSchema/RpcReturnSchema key off StandardSchemaV1 instead of
  valibot's GenericSchema; @valibot/to-json-schema dropped; single-arg
  JSON-Schema unwrapping removed (always arg0/arg1 now).

Adaptations:
- connect.ts: import from @modelcontextprotocol/server(+/stdio) and
  @modelcontextprotocol/client (dynamic, still peer-optional); handler
  registration uses 'tools/list'/'tools/call' method strings.
- devframe's package.json: @modelcontextprotocol/client added as an
  optional peer (connect.ts uses it to dial discovered instances) and
  bundled correctly via tsdown's onlyBundle (client pulls in
  @modelcontextprotocol/core, pkce-challenge, eventsource[-parser],
  jose — all now declared).
- Renumbered DF0042 (registry write failure) and DF0043 (missing MCP
  SDK) to DF0045/DF0046 — both collided with codes main allocated to
  unrelated diagnostics (capabilities.build:false; RPC arg/return
  validation) while this branch was in flight.
- AgentTool/AgentToolInput.args and DevframeCommandAgentOptions.args
  retyped from valibot's GenericSchema[] to StandardSchemaV1[].
  host-agent.ts no longer eagerly converts args to inputSchema (that
  module is gone); a kind: 'tool' entry now carries args raw, mirroring
  how an RPC-backed tool defers to ctx.rpc.definitions — the MCP
  adapter's computeInputSchema converts either on demand.
- hub's commands→agent bridge: coercePositionalArgs no longer detects
  a single-object schema to unwrap (that convention is gone project-
  wide); always maps arg0/arg1/... positionally, matching RPC-backed
  tool coercion.
- Tests updated for the new args-carried-raw contract, plus a new
  end-to-end MCP-adapter test proving Standard Schema args convert to
  JSON Schema over the real wire (arg0-keyed, not unwrapped).
- Removed the now-fully-redundant devframe/utils/valibot-json-schema
  module and its registrations (superseded by the upstream
  to-json-schema.ts, which already covers every validator via
  ~standard.jsonSchema, degrading to a permissive object schema for
  validators without one — e.g. valibot).

Verified: 1020 unit tests, typecheck (21/21), lint, full build, and
17/17 Playwright e2e (incl. both connector round-trips through the
real stdio/HTTP MCP v2 pipeline) all green.
@antfubot
antfubot force-pushed the feat/agent-mcp-wave-phase-3 branch from 158eb99 to 2549d78 Compare August 3, 2026 02:48
@antfubot

antfubot commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto main again — it had moved 15 commits ahead, including two breaking changes that landed directly on top of this wave's surface area:

Both required real adaptation, not just textual conflict resolution — see 2549d78 for the full breakdown. Highlights:

  • connect.ts and the MCP adapter now speak SDK v2 throughout (including a fix tsdown caught at build time: @modelcontextprotocol/client's transitive deps — @modelcontextprotocol/core, pkce-challenge, eventsource/-parser, jose — needed declaring since connect.ts uses it to dial discovered instances, not just @modelcontextprotocol/server for hosting).
  • Two diagnostic codes (DF0042, DF0043) collided with codes main allocated to unrelated diagnostics while this branch was in flight — renumbered to DF0045/DF0046.
  • AgentTool/AgentToolInput/DevframeCommandAgentOptions's args field retyped from valibot's GenericSchema[] to StandardSchemaV1[]. Since the single-object unwrap convention is gone everywhere, the hub's commands→agent bridge and host-agent.ts's tests were updated to match the new always-arg0/arg1 contract — and host-agent.ts no longer eagerly converts args to inputSchema (that module doesn't exist anymore); a kind: 'tool' entry now carries args raw, mirroring exactly how an RPC-backed tool already deferred to ctx.rpc.definitions — conversion happens once, in the MCP adapter, for both.
  • Removed the now-fully-redundant devframe/utils/valibot-json-schema module (this wave's own addition) — superseded by upstream's validator-neutral to-json-schema.ts.

Verified: 1020 unit tests, typecheck (21/21), lint, full build, and all 17 Playwright e2e specs green — including both connector round-trips running the real stdio↔HTTP MCP v2 pipeline end-to-end, not just mocked.

registerDevframeInstance builds its file path with pathe's join, which
always normalizes to forward slashes; the test compared that against
node:path's join, which uses the platform-native separator — a real
mismatch on Windows (backslash vs forward slash), not a flake. All
three windows-latest CI jobs failed on this exact assertion.

Switch the test to pathe's join too, matching the implementation.
… findings

- 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.
Resolves conflicts from #158 (knip integration):
- packages/hub/package.json: keep our @standard-schema/spec addition,
  take main's removal of the (knip-flagged unused) birpc dependency
- pnpm-lock.yaml: regenerated via `pnpm install --lockfile-only`

Also fixes two knip findings the merge exposed in this branch's own
new surface (unchecked by knip before it landed on main):
- instance-registry.ts: DEVFRAME_INSTANCES_DIR_ENV /
  DEVFRAME_DISABLE_INSTANCE_REGISTRY_ENV / resolveInstancesDir /
  probeDevframeInstance drop `export` — module-internal per the
  barrel's existing "read/probe/prune helpers stay internal" comment,
  nothing outside the file used them
- packages/next: DevframeNextHostMcpOptions (the `mountMcp` options
  type) now re-exported from the package barrel, alongside its sibling
  option types — a real gap, not a knip false positive
@antfubot antfubot changed the title feat(agent): agent-native MCP wave — bridges, core surface, instance registry, devframe connect feat(agent)!: agent-native MCP wave — bridges, core surface, instance registry, devframe connect Aug 3, 2026
…pect plugin display

BREAKING CHANGE: MCP tool names are now the sanitized wire name derived
from each tool's colon-namespaced id (toAgentToolName), not the id
itself — devframe:state:read ships as devframe_state_read,
devframe:connect:list-instances as devframe_connect_list-instances,
devframes:plugin:git:status as devframes_plugin_git_status, etc.
Anything hardcoding the colon form as a literal MCP tool name needs
the sanitized name instead. Marked and explained in the PR
title/description since this renames every tool this wave ships.

- Move `toAgentToolName` from devframe/node to devframe/utils/agent-tool-name:
  it's a plain string transform with no node dependency, so browser-side
  UIs can import it too — not node-specific. Wires the package.json
  exports map, tsdown client entries + check-client-dist list, and the
  tsconfig.base.json / alias.ts cross-package path aliases the same way
  every other devframe/utils/* entry already is.
- Split its test file accordingly: toAgentToolName tests move to
  utils/agent-tool-name.test.ts; coerceAgentPositionalArgs tests move
  to node/__tests__/agent-args.test.ts.
- inspect plugin's AgentView now reflects the real tool name: the agent
  view was displaying each tool's internal (colon-namespaced) id as if
  it were what an MCP client calls. It now shows the sanitized wire
  name as the primary label, with the internal id noted underneath
  only when they differ (storybook fixture updated to demonstrate the
  distinction).
@antfu
antfu merged commit a126fc6 into main Aug 3, 2026
11 of 12 checks passed
@antfu
antfu deleted the feat/agent-mcp-wave-phase-3 branch August 3, 2026 07:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants