Skip to content

Commit c4b5897

Browse files
committed
docs(plans): add plan 008 — default-on MCP behind a non-empty agent surface
1 parent f2632b2 commit c4b5897

2 files changed

Lines changed: 168 additions & 0 deletions

File tree

plans/008-default-on-mcp.md

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# Plan 008: Serve MCP by default when a devframe exposes an agent surface
2+
3+
> **Executor instructions**: Follow this plan step by step. Run every verification command and confirm the expected result before moving on. If a STOP condition occurs, stop and report it instead of weakening the dependency or security posture. Update this plan's row in `plans/README.md` when complete.
4+
>
5+
> **Drift check (run first)**: `git diff --stat f2632b2c..HEAD -- packages/devframe/src/adapters packages/devframe/src/types/devframe.ts packages/devframe/src/node packages/hub/src/node/initiate.ts packages/vite/src packages/next/src starter examples/hub-vite examples/hub-next tests/optional-mcp-bundles.test.ts docs/content`
6+
> Stop if the MCP route options, the agent host manifest, or the plan 002 authorization contract have materially changed.
7+
8+
## Status
9+
10+
- **Priority**: P2
11+
- **Effort**: M
12+
- **Risk**: MED
13+
- **Depends on**: `plans/002-authenticate-mcp-http.md`, `plans/003-enforce-mcp-state-policy.md`
14+
- **Category**: product
15+
- **Planned at**: commit `f2632b2c`, 2026-09-02
16+
17+
## Why this matters
18+
19+
Devframe's positioning is "one tool, two views — for the human and for the coding agent". Today the agent view takes three separate opt-ins: flag functions with `agent`, enable `cli.mcp`, and install `@modelcontextprotocol/server`. The first opt-in is the meaningful authoring decision; the other two are plumbing. Making the MCP endpoint light up automatically once an author flags their first function turns the agent view into a default feature of every devframe — while a devframe that never flags anything, and a user who sets `mcp: false`, keep paying exactly zero cost.
20+
21+
## Constraints this plan must not break
22+
23+
- **Validator neutrality**: `@modelcontextprotocol/server@2` depends on `zod@4` via `@modelcontextprotocol/core`. The SDK must stay an **optional peer** of `devframe`; it never enters runtime `dependencies`.
24+
- **Zero cost when off**: the `importRuntimeModule('devframe/adapters/mcp')` lazy load and the `tests/optional-mcp-bundles.test.ts` guard stay authoritative — `mcp: false` (and an empty agent surface) must load no MCP code and add no bundle weight.
25+
- **Plan 002's authorization contract**: no route mounts without an explicit authorization policy or the `DEVFRAME_MCP_AUTH_TOKEN` bearer. Default-on never means unauthenticated-on.
26+
- **Per-function default-deny**: the `agent` field on `defineRpcFunction` remains the only way a function reaches an agent.
27+
28+
## Current state
29+
30+
- `packages/devframe/src/types/devframe.ts``cli.mcp?: boolean | McpRouteOptions`, omitted means off.
31+
- `packages/devframe/src/adapters/initiate.ts` — mounts the route only when `mcp` is truthy, via `importRuntimeModule`.
32+
- `packages/devframe/src/adapters/cac.ts` — tri-state `--mcp` flag (`undefined`/`true`/`false`).
33+
- `packages/devframe/src/node/host-agent.ts``DevframeAgentHost` already knows whether the agent surface is non-empty (agent-flagged RPCs, `registerTool()` entries, tool providers).
34+
- `packages/hub/src/node/initiate.ts``initHub({ mcp })`, aggregate endpoint, off by default.
35+
- After plan 002: `mcp: true` requires `DEVFRAME_MCP_AUTH_TOKEN` (or an explicit `authorization` policy) and the Next hub kit defaults to disabled.
36+
- `examples/hub-vite` has no MCP wiring; `examples/hub-next` does (a parity violation of the "Hub example parity" rule in `AGENTS.md`).
37+
38+
## Target behavior: `mcp: 'auto'` as the new default
39+
40+
Add `'auto'` to the `mcp` option and make it the default where `mcp` is omitted, for both `initDevframe` and `initHub`. Under `'auto'`, the route mounts if and only if **all three** hold:
41+
42+
1. **The agent surface is non-empty** — at least one agent-flagged RPC, `ctx.agent.registerTool()` entry, or registered tool provider.
43+
2. **`@modelcontextprotocol/server` resolves** as an installed peer.
44+
3. **An authorization policy is available**`DEVFRAME_MCP_AUTH_TOKEN` is set (plan 002's shorthand path). `'auto'` never mounts with `authorization: false`.
45+
46+
When condition 1 holds but 2 or 3 fails, emit **one** structured diagnostic (new sequential `DF00xx`, `method: 'warn'`, deduplicated per process) naming the single missing piece — "install `@modelcontextprotocol/server`" or "set `DEVFRAME_MCP_AUTH_TOKEN`" — so the author discovers the agent view exists. When condition 1 fails, `'auto'` is silent and loads nothing: indistinguishable from today's off.
47+
48+
Explicit values keep today's meaning: `false` never mounts and never diagnoses; `true`/object follow plan 002's contract exactly (missing token on explicit `true` stays a coded startup failure — the author asked for it). The `--mcp`/`--no-mcp` tri-state flag overrides the definition as it does now, with `--no-mcp` forcing off over `'auto'`.
49+
50+
The product layer makes the default tangible:
51+
52+
- `starter/` ships `@modelcontextprotocol/server` in its `package.json` (real version, per starter conventions), an `agent`-flagged example RPC, and a README line about `DEVFRAME_MCP_AUTH_TOKEN`.
53+
- `examples/hub-vite` gains aggregate-MCP wiring matching `examples/hub-next` (explicit env-backed authorization in both), closing the parity gap in the same PR.
54+
- Framework kits inherit `'auto'` by forwarding an omitted `mcp` instead of coercing it to off; `@devframes/next/hub` keeps plan 002's explicit-opt-in stance only if 002 landed it that way — otherwise it adopts `'auto'` like the rest.
55+
56+
## Commands you will need
57+
58+
| Purpose | Command | Expected on success |
59+
|---|---|---|
60+
| Adapter tests | `pnpm exec vitest run packages/devframe/src/adapters/__tests__/initiate.test.ts packages/devframe/src/adapters/mcp/__tests__` | all tests pass |
61+
| Hub/kit tests | `pnpm exec vitest run packages/hub/src/node/__tests__/initiate.test.ts packages/vite/test/single.test.ts packages/next/test/handler.test.ts` | all tests pass |
62+
| Bundle guard | `pnpm exec vitest run tests/optional-mcp-bundles.test.ts` | all tests pass |
63+
| API snapshots | `pnpm build && pnpm exec vitest run tests/exports.test.ts -u` | only intended public snapshots change |
64+
| Full verification | `pnpm lint && pnpm knip && pnpm test && pnpm typecheck && pnpm build` | every command exits 0 |
65+
66+
## Scope
67+
68+
**In scope**:
69+
70+
- `packages/devframe/src/types/devframe.ts` (`'auto'` on the `mcp` union)
71+
- `packages/devframe/src/adapters/initiate.ts`, `_shared.ts`, `cac.ts`
72+
- `packages/devframe/src/adapters/__tests__/initiate.test.ts`
73+
- `packages/devframe/src/node/host-agent.ts` (surface-emptiness query, if not already exposed)
74+
- `packages/devframe/src/node/diagnostics.ts` + one new `docs/content/6.errors/DF00xx.md`
75+
- `packages/hub/src/node/initiate.ts` + `__tests__/initiate.test.ts`
76+
- `packages/vite/src/single.ts`, `packages/vite/src/hub.ts`, `packages/next/src/handler.ts`, `packages/next/src/hub.ts` (forward omitted `mcp` as `'auto'`)
77+
- `starter/` (SDK dependency, flagged example RPC, README)
78+
- `examples/hub-vite` (MCP parity with `examples/hub-next`) and both hub example READMEs
79+
- `tests/optional-mcp-bundles.test.ts` (add: `'auto'` with empty surface bundles/loads no MCP code)
80+
- `tests/__snapshots__/tsnapi/**` (affected snapshots only)
81+
- Docs: `1.guide/index.md`, `1.guide/15.agent-native.md`, `2.adapters/7.mcp.md`, `2.adapters/index.md`, `3.frameworks/*.md`, `8.references/4.node-api.md`, `8.references/6.hub-api.md` — positive framing ("serves MCP once you flag a function for agents"), no "opt-in" language left behind
82+
- `plans/README.md` status row
83+
84+
**Out of scope**:
85+
86+
- Vendoring or reimplementing any part of the MCP protocol.
87+
- Changing the authorization model (plan 002 owns it) or state exposure policy (plan 003 owns it).
88+
- Moving `@modelcontextprotocol/*` out of `peerDependencies`.
89+
- The stdio transport and `devframe connect` (unchanged semantics).
90+
91+
## Git workflow
92+
93+
- Branch: `feat/default-on-mcp`.
94+
- Commit style: `feat(devframe): serve MCP by default when an agent surface exists`.
95+
- Do not push/open a PR unless instructed by the operator.
96+
97+
## Steps
98+
99+
### Step 1: Teach the option `'auto'`
100+
101+
Extend the `mcp` union with `'auto'` in `types/devframe.ts` and normalize the omitted case to `'auto'` in `initiate.ts` and `initHub`. Expose a cheap `hasAgentSurface()` (or equivalent) on the agent host if the manifest cannot already answer it without building tools.
102+
103+
**Verify**: `pnpm --filter devframe typecheck` → exit 0.
104+
105+
### Step 2: Implement the three-condition mount
106+
107+
In `initiate.ts` (and the hub aggregate), resolve `'auto'`: check surface non-emptiness first (free, no import), then attempt the runtime SDK import, then the plan 002 authorization resolution. Mount only when all three pass; emit the new deduplicated diagnostic when only the surface condition passes. `false` and `--no-mcp` short-circuit before any check.
108+
109+
**Verify**: `pnpm exec vitest run packages/devframe/src/adapters/__tests__/initiate.test.ts` → all tests pass, including new cases for each cell of the matrix (empty surface / missing SDK / missing token / all present).
110+
111+
### Step 3: Keep the zero-cost guarantee provable
112+
113+
Extend `tests/optional-mcp-bundles.test.ts`: a bundle of an `'auto'`-defaulted devframe with an empty agent surface must contain no `@modelcontextprotocol/*` resolution, and running it must not import the MCP adapter.
114+
115+
**Verify**: `pnpm exec vitest run tests/optional-mcp-bundles.test.ts` → all tests pass.
116+
117+
### Step 4: Product layer and example parity
118+
119+
Update `starter/` (SDK dep — synced by `scripts/sync-starter-version.ts` conventions — flagged RPC, README token note) and bring `examples/hub-vite` to MCP parity with `examples/hub-next`, both using explicit env-backed authorization per plan 002. Update both example READMEs in the same change.
120+
121+
**Verify**: `pnpm exec vitest run packages/vite/test/single.test.ts examples/hub-next/tests` → all tests pass.
122+
123+
### Step 5: Docs and snapshots
124+
125+
Rewrite the scoped docs pages with positive framing per the documentation style rules: describe what mounts and when, lead with the flag-a-function flow, keep lookup details (the `'auto'` condition matrix) on the reference pages. Refresh only the intentionally-changed tsnapi snapshots.
126+
127+
**Verify**: `pnpm lint && pnpm knip && pnpm test && pnpm typecheck && pnpm build` → every command exits 0.
128+
129+
## Test plan
130+
131+
- Omitted `mcp` + empty surface → no route, no import, no diagnostic.
132+
- Omitted `mcp` + flagged RPC + SDK + token → route mounts, bearer required.
133+
- Omitted `mcp` + flagged RPC, SDK missing → no route, one warn diagnostic naming the SDK.
134+
- Omitted `mcp` + flagged RPC + SDK, token missing → no route, one warn diagnostic naming the env var.
135+
- `mcp: false` / `--no-mcp` + flagged RPC → no route, no diagnostic.
136+
- Explicit `mcp: true`, token missing → plan 002's coded startup failure (unchanged).
137+
- Hub aggregate under `'auto'` → mounts only when at least one mounted devframe has a non-empty surface and conditions 2–3 hold.
138+
- Bundle guard: `'auto'` + empty surface bundles clean.
139+
140+
## Done criteria
141+
142+
- [ ] Omitting `mcp` serves the agent view exactly when a surface exists, the SDK resolves, and authorization is configured.
143+
- [ ] `mcp: false` and empty-surface `'auto'` load zero MCP code, proven by the bundle guard.
144+
- [ ] `@modelcontextprotocol/*` remain optional peers of `devframe`; no workspace package gains them as runtime dependencies.
145+
- [ ] `'auto'` never mounts an unauthenticated route.
146+
- [ ] Starter demonstrates the flag-a-function flow out of the box; hub examples are at MCP parity.
147+
- [ ] Docs contain no "opt-in"/"optional peer" framing for the default flow; the condition matrix lives on a reference page.
148+
- [ ] Full verification passes; only in-scope files and `plans/README.md` changed.
149+
150+
## STOP conditions
151+
152+
- Plan 002 or 003 has not landed (`plans/README.md` rows not DONE).
153+
- The `'auto'` check cannot determine surface non-emptiness without importing the MCP adapter or SDK.
154+
- Satisfying `'auto'` would require moving `@modelcontextprotocol/*` into runtime `dependencies` or otherwise introducing `zod` as a devframe dependency.
155+
- The authorization contract from plan 002 would need loosening (e.g. `'auto'` mounting with `authorization: false`) to make the default useful.
156+
- API snapshot changes include unrelated exports.
157+
158+
## Maintenance notes
159+
160+
Every future transport or kit that gains an `mcp` option must route the omitted case through the same `'auto'` resolution rather than re-inventing a default. Reviewers should check that new built-in devframes flag functions deliberately — under `'auto'`, an `agent` field is what turns the endpoint on for users with a token configured.

plans/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ Generated by the improve skill on 2026-09-01 at commit `2d978f84`. Execute in th
1616

1717
Status values: TODO | IN PROGRESS | DONE | BLOCKED (with reason) | REJECTED (with rationale)
1818

19+
## Product Plans
20+
21+
Planned outside the security audit; same executor rules apply.
22+
23+
| Plan | Title | Priority | Effort | Depends on | Status |
24+
|---|---|---|---|---|---|
25+
| 008 | Serve MCP by default when a devframe exposes an agent surface | P2 | M | 002, 003 | TODO |
26+
1927
## Dependency Notes
2028

2129
- Plan 001 lands first because the release path should stop following mutable privileged workflow code before security fixes are published.

0 commit comments

Comments
 (0)