docs(adr): ADR-0100 — serve-side MCP default wiring; register the MCP tool-execution surface in the authz matrix (#3167)#3182
Closed
os-zhuang wants to merge 2 commits into
Closed
Conversation
… tool-execution surface in the authz matrix (#3167) #3167 is a decision issue whose #1 (identity admission) is the hard gate: "nothing else proceeds before it." Two findings shaped the response: 1. The issue's premise — "MCPServerPlugin is wired nowhere by default" — is stale against main. MCP is already a default-on core capability (isMcpServerEnabled() default true + serve.ts capability push + the matching route gate), and the HTTP tool surface is already fail-closed on identity (handleMcp 401 for a principal-less caller; buildMcpBridge threads the caller ExecutionContext into callData on every verb; OAuth callers run at the ADR-0090 D10 ceiling∩user intersection). 2. What was genuinely missing is exactly the gate: the MCP tool-execution surface had NO row in the ADR-0096 D4 authz conformance matrix, so "whose ExecutionContext runs a tools/call" lived only in ADR-0096 prose, not as a checked, red-CI-on-regression property. This lands: - ADR-0100 recording the four decisions grounded in the verified state: D1 identity admission (caller-principal posture, registered as the gate), D2 dev/prod boundary is identity + off-switch not a default flip, D3 HTTP-only default with stdio kept explicit-opt-in, D4 bounded default exposure (apiEnabled + ai.exposed). - Two authz-conformance rows: an enforced `mcp-tool-exec-identity` (fail-closed gate + bridge threading) and an experimental `mcp-stdio-resource-read` classifying the ADR-0096 E1 context-less stdio resource read (opt-in, never networked) with its admission requirement — registered honestly, not patched with a discouraged { isSystem: true } literal. - Three source probes pinning both halves of the HTTP admission and the stdio fall-open, so dropping the gate/threading or networking the stdio read trips CI (STALE/UNCLASSIFIED) instead of being an adversarial-review finding. Test-only + docs (packages/qa/dogfood is private) — no runtime behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155A3k1CMJQb44V4iwTLUcH
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…3167) Upgrades the `mcp-tool-exec-identity` conformance row from cited-tests-only to a real end-to-end boundary proof (ADR-0096 D4 requires one for an agent/ external-reachable surface). `showcase-mcp-identity.dogfood.test.ts` boots the showcase with MCPServerPlugin registered (via the harness `extraPlugins` — the verify harness boots the plugin set directly, not through the CLI capability push) and asserts on the real in-process HTTP stack: - anonymous POST /mcp (tools/list) → 401 (fail-closed: not 404/501, so the surface IS served and the service IS registered — the denial is identity); - an authenticated member clears the gate (not 401); - GET /mcp/skill → 200 (surface served default-on while tool-exec is gated). Wires it in as the row's `proof:` and adds the row to the test's HIGH_RISK set (so the surface must carry a proof). Adds @objectstack/mcp to the private @objectstack/dogfood dev deps (lockfile updated). Validated locally: `pnpm --filter @objectstack/dogfood test` authz-conformance + showcase-mcp-identity → 10/10 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155A3k1CMJQb44V4iwTLUcH
Contributor
Author
|
Superseded by #3202, which already registered the MCP execution-surface identity admission in the ADR-0096 matrix ( Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Decision-issue response to #3167 (default wiring for the serve-side MCP server — identity admission first). #3167 makes decision #1 (identity admission) a hard gate: "nothing else in this issue proceeds before it." This PR lands the decision record and that gate, with an end-to-end proof.
Two findings that shaped the response
The issue's premise is stale against
main. decision: default wiring for the serve-side MCP server (@objectstack/mcp) in dev — identity admission first #3167 saysMCPServerPluginis "wired nowhere by default." Verified againstmain, MCP is already a default-on core capability:isMcpServerEnabled()defaults ON (packages/types/src/env.ts),packages/cli/src/commands/serve.tspushes themcpcapability intoos serve/os devunless opted out, and the dispatcher route gate reads the same helper. The HTTP tool surface is also already fail-closed on identity —handleMcpreturns 401 for a principal-less caller (no dev-admin fallback, no fall-open), andbuildMcpBridgethreads the callerExecutionContextintocallData(..., ec)on every verb (OAuth callers run at the ADR-0090 D10 ceiling∩user intersection).What was genuinely missing is exactly the gate. The MCP tool-execution surface had no row in the ADR-0096 D4 authz conformance matrix — so "whose
ExecutionContextruns atools/call" lived only in ADR-0096 prose, not as a checked, red-CI-on-regression property.What this lands
docs/adr/0100-serve-side-mcp-default-wiring-identity-admission.md— the four decisions grounded in the verified state:user) posture, fail-closed, registered as the gate.OS_MCP_SERVER_ENABLED=falsegates registration and route), not a code-level default flip (an opt-in-for-prod amendment is documented for ratification).os devowns the process; the stdio resource path carries the ADR-0096 E1 context-less read).apiEnabledobjects,sys_*fail-closed, actions only whenai.exposed; metadata-declared exposure is the future story.authz-conformance.matrix.ts— two rows: enforcedmcp-tool-exec-identity(fail-closed 401 gate +buildMcpBridgethreading + OAuth scope gate; now carrying a real dogfoodproofand marked high-risk) and experimentalmcp-stdio-resource-readclassifying the ADR-0096 E1 context-less stdio resource read (opt-in, never networked) with its admission requirement — registered honestly, not patched with a discouraged{ isSystem: true }literal (ADR-0096 D2 retires those; thesystemContext(reason)constructor doesn't exist yet).authz-conformance.test.ts— three source probes pinning both halves of the HTTP admission and the stdio fall-open, so dropping the gate/threading or networking the stdio read trips CI (STALE / UNCLASSIFIED) instead of surfacing in an adversarial review later.showcase-mcp-identity.dogfood.test.ts— the end-to-end proof: boots the showcase withMCPServerPluginregistered and asserts on the real HTTP stack that anonymousPOST /mcp→401 (not 404/501 — the surface is served and the service is registered; the denial is identity), an authenticated member clears the gate, andGET /mcp/skill→200 (served default-on while tool-exec is gated).Scope / safety
Docs + a private test package (
@objectstack/dogfood) only — no runtime behavior change, no changeset. The identity path, OAuth admission (#2698), andai.exposed+ capability action gate are pre-existing and unchanged; this PR registers and proves them. (@objectstack/mcpadded to the dogfood dev deps; lockfile updated.)Validation
Validated locally:
pnpm --filter @objectstack/dogfood testforauthz-conformance+showcase-mcp-identity→ 10/10 pass (7 conformance ratchet + 3 dogfood-boot). The three new probes each match their source anchor exactly once and are a clean bijection with the two rows'covers; theproof:-exists and high-risk assertions pass.Open for ratification
NODE_ENV-aware opt-in-for-prod amendment.Closes #3167 pending ratification of D2.
🤖 Generated with Claude Code