feat(mcp)!: stdio transport requires an API-key principal — fail-closed, no unscoped bridge (ADR-0101, #3246)#3253
Merged
Conversation
…ed, no unscoped bridge (ADR-0101, #3246) Closes the platform's last identity-less execution surface: the long-lived MCP stdio transport, which previously bridged the raw metadata service + data engine with no ExecutionContext (record_by_id read via dataEngine.findOne, bypassing RLS/FLS/tenant). - plugin.ts start(): when stdio auto-start is requested, resolve OS_MCP_STDIO_API_KEY through the SAME @objectstack/core chain as HTTP/REST (resolveStdioExecutionContext → resolveAuthzContext → resolveApiKeyPrincipal), build the caller ExecutionContext, and thread it (re-resolved per call) into a principal-bound record reader. FAIL-CLOSED: no key / no objectql / an unknown|revoked|expired key throws and refuses to start stdio. No `system` bypass — full authority is a minted admin/service key. - mcp-server-runtime.ts bridgeResources: record_by_id now takes a principal-bound getRecord reader (ql.find(obj,{where:{id},context})); registered only when a reader is supplied — no unscoped fallback. Dropped the raw IDataEngine bridge. - authz matrix: mcp-stdio-authority experimental → enforced; probe/bite retargeted from bridgeResources(unscoped-stdio) → stdio-principal-bound. - ADR-0101 Proposed → Accepted; env-var docs add OS_MCP_STDIO_API_KEY. Tests: mcp 83/83 (4 new fail-closed cases: no key / no objectql / unresolvable key refuse to start; disabled stdio needs no key); dogfood authz-conformance 9/9. BREAKING (stdio auto-start only): OS_MCP_STDIO_ENABLED/autoStart now requires OS_MCP_STDIO_API_KEY; keyless stdio no longer starts. HTTP surface unaffected. Refs #3246, #3167, ADR-0101, ADR-0096. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115eg8dAaCfWaDYYAm3ma36
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 11 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115eg8dAaCfWaDYYAm3ma36
os-zhuang
marked this pull request as ready for review
July 19, 2026 10:37
6 tasks
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.
What & why
实现 ADR-0101 / #3246 v1:关闭平台最后一个无身份执行面——长驻 MCP stdio 传输。此前
record_by_idresource 走裸dataEngine.findOne(...),无 ExecutionContext → 绕过 RLS/FLS/租户。改动
plugin.tsstart():stdio auto-start 时,从OS_MCP_STDIO_API_KEY经与 HTTP/REST 同一条@objectstack/core链(resolveStdioExecutionContext→resolveAuthzContext→resolveApiKeyPrincipal)解析出 caller EC,逐调用重解析后线进 principal-bound 的 record reader。FAIL-CLOSED:无 key / 无 objectql / key 无法解析(unknown|revoked|expired|owner-less)→ 抛错拒启 stdio。无system旁路——满权=mint 一个 admin/service key。mcp-server-runtime.tsbridgeResources:record_by_id改用 principal-boundgetRecord(ql.find(obj,{where:{id},context})),仅在提供 reader 时注册——无裸 fallback。移除裸IDataEngine桥。mcp-stdio-authorityexperimental→enforced;探针/bite 从bridgeResources(unscoped-stdio)改为stdio-principal-bound。OS_MCP_STDIO_API_KEY。身份模型(三方对齐)
stdio 现在与 HTTP 共用同一条 key→EC 链,scope 由该身份权限决定——对齐 MCP 规范(stdio 后端凭据从 env 供给)、Postgres/GitHub MCP server(按凭据 scope)、Anthropic agent-identity(admin 预置 scoped service 身份)。详见 ADR-0101。
验证(本地)
@objectstack/mcp83/83(含 4 个新 fail-closed 用例:无 key / 无 objectql / 无法解析 key 各自拒启;未启用 stdio 则不需 key);authz-conformance9/9(矩阵升 enforced + 新探针/bite (g) + baseline sound + proof-file-exists);turbo build(mcp/verify 闭包,含 tsc)绿。OS_MCP_STDIO_ENABLED=true/autoStart现在要求OS_MCP_STDIO_API_KEY;无 key 不再启动(fail-closed)。默认开的 HTTP 面、以及任何不启用 stdio auto-start 的部署,不受影响。changeset 标@objectstack/mcpminor + body 内注明 breaking——评审时如需 major 可调。后续(v2,#3246 未阻塞项)
os dev用 dev-admin 自动 mint 本地 scoped dev key 并打印;命名 service 身份 / 权限集 + key 轮换。Refs #3246、ADR-0101、#3167、ADR-0096。
🤖 Generated with Claude Code
https://claude.ai/code/session_0115eg8dAaCfWaDYYAm3ma36
Generated by Claude Code