Repository navigation
feat(connector-mcp): a stdio MCP server takes its credential from the secrets layer through transport.credentialEnv - #29621
Conversation
Claude-Session: https://claude.ai/code/session_01ADHkmRFhrJgbwLP5mvZkeJ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADHkmRFhrJgbwLP5mvZkeJ Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check7 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 497a3ea60655daeaa5e40912e2789cb5d7ea32a5 && git checkout 497a3ea60655daeaa5e40912e2789cb5d7ea32a5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5bd615effe96ea05b4d9744a48a345ec804de208 a76ad3f81b06d195ababc7b7ee678866ec166105 && git checkout -B drift-repro 5bd615effe96ea05b4d9744a48a345ec804de208 && git merge --no-ff a76ad3f81b06d195ababc7b7ee678866ec166105
node scripts/docs-audit/affected-docs.mjs --json 5bd615effe96ea05b4d9744a48a345ec804de208 |
|
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67 Contract-review-claim: session_01ADHkmQJdgarUjPLNgFBY67 · head f3ff334 · 2026-11-02T22:17Z |
|
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67 Contract reviewServed-tier: ① Derived judgmentsRead from: card #29434 (body, triage 6581800181, claim 6588313005, seat ruling 6588385697, dev report 6589858238), the options file server-seat3/29434-route-options.md, the PR #29621 body, its 8-file list, the net diff against
② Semver levelClause-②: yes (widening). Confirmed: it is on exactly one line-initial line in the PR body (line 8) and one in the changeset (line 6). The later PR-body mention sits inside a bullet's prose and is not a key line. The diff widens the mcp provider's accepted providerConfig, and its new refusals touch only the new key, which Must-fix: the
Fix:
Check-runs on the head (no failure; 37 not yet concluded):
③ Boundary flags
Implemented-by: VERDICT: FAIL |
…inor), and correct the pending advisory changeset's stdio bullet Claude-Session: https://claude.ai/code/session_01ADHkmRFhrJgbwLP5mvZkeJ Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67 Seat confirmation · DELIBERATE CORRECTION · 2026-11-02T22:29Z
|
|
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67 Contract-review-claim: session_01ADHkmQJdgarUjPLNgFBY67 · head a76ad3f · 2026-11-02T22:31Z |
|
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67 Contract reviewServed-tier: ① Derived judgmentsRead from: card #29434 and its five comments (triage 6581800181, claim 6588313005, seat ruling 6588385697, dev report 6589858238, claim amendment 6590026724); the PR #29621 body as it stands now; the PR comments, including the prior record 6590013700 and the seat confirmation 6590130632; the net diff against
② Semver levelClause-②: yes (widening). Confirmed as a single line-initial key line in each carrier:
Both carry The prior must-fix is resolved.
The Check Changeset no-major leg agrees on this head. There were three runs on a76ad3f (jobs 125592252770, 125593616712 and 125594571075):
The residual that the gate names is that it cannot see a second widening graded ③ Boundary flags
Implemented-by: VERDICT: PASS |
|
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67 席位核对 · ACCEPT · PR #29621 · 2026-11-02T23:03ZHead-sha: 维护者速读(席位终稿)
核对六项
|
Requested by Steve · project thread
Fixes #29434
domain:server#3 · os-steve · session_01ADHkmRFhrJgbwLP5mvZkeJ
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: follow-up #29625 — skills/objectstack-automation/references/integration-recipes.md recipe 6 still says a stdio server's credentials ride
transport.env;content/docs/automation/connectors.mdxis updated here.维护者速读
mcp连接器的 stdio 服务器现在可以从密钥层拿凭据。连接器照常声明auth: { type, credentialRef },再在providerConfig.transport.credentialEnv写上服务器读取的环境变量名,平台解析出的密钥只放进子进程的环境变量。transport.env里,存在元数据中;connectors: os validate and the save door warn on a credential-shaped literal in a connector header or env map (#28640 ruling 1A) #29341 的提示也只能劝作者改用 http。credentialEnv的 stdio 实例行为完全不变;新键写错(变量名非法、用在 http 上、没有 auth、与 env 重名)按配置错误拒绝。回滚即 revert 本 PR。packages/connectors/connector-mcp/src/mcp-provider.ts(credentialEnvOf、stdio 分支合并 env)、packages/connectors/connector-mcp/src/mcp-stdio-credential-env.test.ts、packages/lint/src/lint-connector-credential-literals.ts(提示措辞)。pnpm --filter @objectstack/connector-mcp exec vitest run src/mcp-stdio-credential-env.test.ts,预期 17/17 通过(真实子进程只回报变量「已设置」和长度)。What changes
A declarative
provider: 'mcp'instance over stdio can now take its credential from the secrets layer instead of a literal intransport.env(route (c), ruled by the seat on the card).Route. The entry declares
auth: { type: 'bearer' | 'api-key' | 'basic', credentialRef }exactly as an http instance does. The automation service resolves the reference through its existing chain (environment, then the stored connector credential, then the host resolver). Themcpprovider then puts the resolved secret (the bearer token, the api-key key, or the basic password) into the child's environment under the variable that the new provider-owned keyproviderConfig.transport.credentialEnvnames.providerConfigis provider-owned and untyped in the spec (ADR-0023), so there is nopackages/spechunk.Inherited for free. All of this keys off
entry.auth: Setup → Connector credentials lists the reference, a credential write re-materializes and re-spawns the instance, and a reference no arm answers degrades the instance naming the reference. That last case never calls the provider, so the server is never spawned.Where the value goes. It only goes into the
envhanded to the spawn (the SDK'sStdioClientTransportmerges it over its default inherited set). It never goes onto the connectordef, into a log, or into a message. A metadata read servesauth.credentialRefandtransport.credentialEnv, which are two names;transport.envstays withheld whole, as before.Refusals. These are configuration faults, thrown plain before anything is spawned, and none of them names a value:
credentialEnvthat is not an environment-variable name;credentialEnvon an http transport;credentialEnvwith no staticauth(absent, ornone);credentialEnvnaming a variable thattransport.envalso sets.A grant-bearing instance on stdio still meets the existing credential-handle refusal. That refusal's sentence now names this route (before, it pointed at "a static credential for the stdio one", which reached nothing).
Unchanged (ruling Q2 A). A stdio transport with no
credentialEnvstill does not applyauth. The silent drop is connector-mcp: an mcp instance with a stdio transport accepts a staticauth, resolves its credentialRef, and then drops the value silently, so the declared credential reaches nothing #29607. A stdio handshake does not count as the credential being sent (credentialSent/credentialVerifiedstay http-only).Advisory (connectors: os validate and the save door warn on a credential-shaped literal in a connector header or env map (#28640 ruling 1A) #29341,
connector-credential-literal). The stdio env finding now steers to the route: "move it to the connector'sauth.credentialRefwithtransport.credentialEnv". Its hint uses the sameauth: { type: 'bearer', credentialRef }spelling as the header hint. The matchingos explainparagraph is updated too.Pending release note corrected.
.changeset/29341-connector-credential-literal.md(the connectors: os validate and the save door warn on a credential-shaped literal in a connector header or env map (#28640 ruling 1A) #29341 advisory's changeset, still pending onmain) said that a stdio MCP server takes noauth, and steered authors to http. It would have shipped in the same release as this route. Its stdio bullet now names thecredentialEnvroute; this is a prose-only edit.Check Changeset/check-empty-changesetstays red on that file by design (the DELIBERATE CORRECTION class). The seat ordered the correction in the claim amendment on connector-mcp: a stdio MCP server has no secrets-layer route for its credentials, sotransport.envcan only carry a literal (the route #29341's advisory cannot point to) #29434.Docs. In
content/docs/automation/connectors.mdx, the stdio bullet now namescredentialEnv, there is a new "A stdio server's credential" section with anos:checkexample, and the grant-bearing paragraph sentence is updated.Forced test edits.
packages/lint/src/lint-connector-credential-literals.test.tshad two edits:auth;auth.credentialRefreaches it only over http");os explainfactA stdio MCP server takes no \auth`is replaced byA stdio MCP server takes the same `auth`and ``providerConfig.transport.credentialEnv` ``.One test was added in that file (the hint names the route with the shared spelling).
Tests
packages/connectors/connector-mcp/src/mcp-stdio-credential-env.test.ts(new, 17 tests). Every value in it is an obvious placeholder.credentialEnvstarts the child without the variable;transport.envreaches the child as today.credentialEnv.auth.credentialRefandtransport.credentialEnv, andredactedKeysis exactlyproviderConfig.transport.env.credential '…' is not set, and the child's start marker is never written.transport.envstill draws the advisory) and the new wording are pinned in the lint test.Ablations (each leg through
scripts/ablation-replace.mjs: anchor 1→0, blob moved, then restored to blob == HEAD with an emptygit diff HEAD; the suites importsrc, so no dist leg)env: env): 4 failed (bearer, api-key, basic, composition-resolved),expected 'unset:0' to be 'set:28'.expected a throw.credentialEnv on an http transport).TypeErrorinstead of the plain refusal;none: no throw).sets environment variable … twice).Acceptance notes
credentialEnvis a key a metadata author writes, so the kind isaddedeven with no spec hunk (AGENTS.md Post-Task Checklist step 3; precedent PR feat(connector-openapi): declarative providerConfig.include operation allowlist #27387,providerConfig.includeonconnector-openapi).@objectstack/connector-mcptakesminor.@objectstack/lintchanges only message text and stayspatch. Both lines follow the seat's claim amendment on connector-mcp: a stdio MCP server has no secrets-layer route for its credentials, sotransport.envcan only carry a literal (the route #29341's advisory cannot point to) #29434.node scripts/pm/record-recognisers.mjs --template --pathson the final file list. Without the changesets it reads the declaration face,TIER_DEFAULT. With them it refuses and defers to the changeset'sClause-②: yes (widening)line, which is the same declaration face.credentialSent/credentialVerifiedstay http-only.env, or is declared as two instances. Anapi-key'sheaderName/paramNameare http placement and are not read on stdio. Abasicusername is not a secret, so it goes inenvif the server reads it.protocol.tsand every ADR are untouched. No ADR declares or rules out a stdio credential route. ADR-0024 §3 (credentials at the process/transport boundary) and ADR-0097 §3 (credentialRefresolved by the host) both hold as written.Verification
Readings head:
f3ff3349(mergesorigin/main81cd9eb0). Every reading below was taken on that head. The one commit after it,a76ad3f8, changes only the two changesets' prose and front matter, so no test, gate or dogfood reading was re-taken. Ona76ad3f8the changeset gates ran as follows:check-adr-0087-registration,check-changeset-no-major,check:changeset-gate-self-tests,check:pm-changeset-deadline-census,check:issue-citationsandcheck:nul-bytesall exit 0.check-empty-changesetexits 1 on.changeset/29341-connector-credential-literal.md, the declared deliberate correction above.Package suites and typecheck
@objectstack/connector-mcp: Test Files 9/9, Tests 119/119; typecheck exit 0 (incl.check:test-typecheck).@objectstack/lint: Test Files 192/192, Tests 7892/7892; typecheck exit 0.Full dogfood battery (credentials card)
turbo run build --filter='@objectstack/dogfood^...'(plus the connector-mcp and lint closures) under the lock:VERDICT command-exit 0, 68/68 tasks.pnpm --filter @objectstack/dogfood testunder the lock:VERDICT command-exit 0, Test Files 398 passed and 3 skipped (401), Tests 3280 passed and 28 skipped (3308), 0 failed.Derived gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 87 families derived onf3ff3349, all 87 run.check-plugin-teardown-shape --self-test, after fetching its pinned fixture commit;check:skill-examplesandcheck:dual-build-cjs-loads, afterturbo run build --filter='./packages/**'(78/78 cache hits).--ranreconciliation:87 derived, 87 run, 0 NOT-MEASURED, 0 UNRUN(a DERIVED zero).pnpm lint(repo-wide eslint,--no-inline-config): exit 0.