Skip to content

feat(agent): agent-native wave phases 2+3 — core surface, instance registry, devframe connect - #145

Open
antfubot wants to merge 4 commits into
feat/agent-mcp-wave-phase-1from
feat/agent-mcp-wave-phase-3
Open

feat(agent): agent-native wave phases 2+3 — core surface, instance registry, devframe connect#145
antfubot wants to merge 4 commits into
feat/agent-mcp-wave-phase-1from
feat/agent-mcp-wave-phase-3

Conversation

@antfubot

@antfubot antfubot commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Why

Phases 2 and 3 of plan 031 (stacked on #142; supersedes #144, folded in here). Phase 2 builds the core agent surface the wave needs; phase 3 ships the flagship: the connector half of the architecture next-devtools-mcp validated. Vercel hand-rolled ~14 KB of Next-specific port/process discovery; devframe owns the framework, so dev servers announce themselves and one generic connector serves every devframe-built tool.

Phase 2 — core surface

  • createMcpFetchHandler(ctx, options) — the MCP Streamable-HTTP endpoint as a framework-agnostic web-standard Request → Response handler (sessions, origin gate, teardown). mountMcpHttp is now 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 same exposeSharedState filter as the resource projection. Many MCP clients only consume tools; resources stay for those that don't.
  • 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 the agent host applies to agent-flagged RPCs. The commands host registers one provider deriving tools from its commands map — single source of truth, no mirrored registrations. Commands opt in via an agent field (description-as-prompt, safety, optional positional valibot args, single object unwrapped); agent on a handler-less group throws DF8404; the field never crosses the wire. notifyChanged() drives tools/list_changed.
  • devframe/utils/valibot-json-schema — the valibot→JSON-Schema conversion promoted to a public util so SDK-free hosts can convert schemas.
  • Schema-typed handlers may be async — the schema-typed definition branch types handlers as Thenable<InferReturnType<RS>> (explicit RS ⇒ no inference risk; sync handlers stay assignable). Unblocks schema-typing genuinely-async RPCs — the limitation plan 001 hit.
  • git agent surfacestatus/log/show/branches/diff agent-flagged with args/returns schemas (safety: 'read'). Write ops 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 (CLI dev, viteDevBridge, the Next handler side-car) 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 { message, fix } payloads; a missing SDK peer throws DF0043. --port probes beside the registry.
  • In-process Next MCPDevframeNextHost.mountMcp(ctx, path) serves the fetch handler on the Next app's own origin: the literal /_next/mcp shape on devframe primitives. The hub example mounts /__hub/__mcp, advertises it in its connection meta, registers the instance, and agent-flags its ping command; the catch-all route exports POST/DELETE (MCP methods).
  • MCP conformance fix — non-object outputSchema projections are dropped (a v.void() returns schema produced type: \"null\", which SDK clients reject on tools/list).
  • Proof, in CI — two e2e gates: devframe-connect.spec.ts (files-inspector: discovery → gateway-tool round-trip → actionable error payloads) and minimal-next-devframe-hub-dev.spec.ts (connector discovers the hub inside the Next dev server and calls its agent-flagged command through the in-process endpoint). Hermetic per-suite registry dirs; unit runs stay out of the global registry.

All tool names follow the devframe:<area>:<fn> convention, matching the built-in RPC families.

Verification

Full gate: pnpm lint && pnpm test && pnpm typecheck && pnpm build — 905 unit tests (provider laziness/collision/notify semantics, registry atomicity/dedup/prune, outputSchema guard, commands bridge against the real agent host) and 17/17 Playwright e2e including both connector gates.


This PR was created with the help of an agent.

antfubot added 3 commits July 28, 2026 08:33
…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
@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
- 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
@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.

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.

1 participant