feat(agent): agent-native MCP wave — bridges, core surface, instance registry, devframe connect - #145
feat(agent): agent-native MCP wave — bridges, core surface, instance registry, devframe connect#145antfubot wants to merge 6 commits into
Conversation
|
Follow-up commit
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.) |
|
Follow-up commit
Gates rerun: 905 unit tests (incl. new |
…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).
b4b9bb0 to
ef2598e
Compare
✅ Deploy Preview for devfra ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Rebased onto Conflicts resolved during the rebase (
Full gate rerun clean post-rebase: 911 unit tests, typecheck, lint, build, and 17/17 Playwright e2e. |
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.
|
Follow-up commit cac only auto-prints help for an explicit 4 new unit tests cover bare invocation, |
Why
The full agent-native MCP wave (plan 031). Vercel's
next-devtools-mcp0.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/mcpshape on devframe primitives via@devframes/next.Supersedes #142 (phase 1) and #144 (phase 2), both folded in here.
Phase 1 — bridges & structured errors
viteDevBridgeand@devframes/next'screateDevframeNextHandlergain anmcpoption (forwarded tocreateDevServer) and advertise the side-car endpoint in their__connection.json—ConnectionMeta['mcp']gains an optionalportfor side-car origins. Shared resolverresolveMcpConnectionMeta.formatMcpErroremits{ error: { code, message, fix?, docs? } }for nosticsDiagnostics, so agents get the actionable next step and docs URL instead of a flattened string.Phase 2 — core surface
createMcpFetchHandler(ctx, options)— the MCP Streamable-HTTP endpoint as a framework-agnostic web-standardRequest → Responsehandler;mountMcpHttpis a thin h3 wrapper over it.devframe:state:readtool — tool-shaped shared-state access (no key → key list, key → JSON value), honoring theexposeSharedStatefilter alongside the resource projection.ctx.agent.registerToolProvider(() => AgentToolInput[]): a tool source queried atlist/getTool/invoketime, the same on-demand projection applied toagent-flagged RPCs. The commands host registers one provider deriving tools from its commands map — single source of truth, nothing mirrored. Commands opt in via anagentfield (description,safety, optional valibotargs);agenton a handler-less group throws DF8404; the field never crosses the wire.Thenable<InferReturnType<RS>>.status/log/show/branches/diffagent-flagged with args/returns schemas (safety: 'read'); writes stay agent-invisible. Cross-refs plan 029.Phase 3 — instance registry, connector, in-process Next MCP
devframe/node):registerDevframeInstancewrites an atomic record to~/.devframe/instances/<pid>-<port>.json; readers prune dead records on failed__connection.jsonprobes, dedup same-port ghosts (newest wins), and adopt a dialable origin for family-ambiguouslocalhostbinds.createDevServerregisters automatically and unregisters on close; in-process hosts call it explicitly.DEVFRAME_INSTANCES_DIR/DEVFRAME_DISABLE_INSTANCE_REGISTRYoverride. Failures degrade to coded warnings (DF0042).devframebin +connect— the package's first bin.devframe connectruns 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---mcpfunnel hint) anddevframe:connect:call-tool(proxy one tool call over Streamable-HTTP). Errors carry{ message, fix }; a missing SDK peer throws DF0043.--portprobes beside the registry.DevframeNextHost.mountMcp(ctx, path)serves the fetch handler on the Next app's own origin (the/_next/mcpshape). The hub example mounts/__hub/__mcp, advertises it, registers the instance, and agent-flags its ping command.outputSchemaprojections are dropped (av.void()returns schema producedtype: null, which SDK clients reject).API surface
All wave tool names follow the
devframe:<area>:<fn>convention. The public surface is deliberately minimal — only members with a consumer outside their own package are exported (createMcpFetchHandler,registerToolProvider,registerDevframeInstance+ its two types,resolveMcpConnectionMeta, 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 build— 905 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).This PR was created with the help of an agent.