Skip to content

feat(connector-mcp): a stdio MCP server takes its credential from the secrets layer through transport.credentialEnv - #29621

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-439xd1
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-439xd1

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

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.mdx is updated here.

维护者速读

  • 改了什么:mcp 连接器的 stdio 服务器现在可以从密钥层拿凭据。连接器照常声明 auth: { type, credentialRef },再在 providerConfig.transport.credentialEnv 写上服务器读取的环境变量名,平台解析出的密钥只放进子进程的环境变量。
  • 为什么改:以前 stdio 服务器的 API key 只能以明文写在 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。
  • 风险与回滚:只是新增一个作者可写的可选键(Metadata-protocol: added,connector-mcp 升 minor),没写 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 in transport.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). The mcp provider 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 key providerConfig.transport.credentialEnv names. providerConfig is provider-owned and untyped in the spec (ADR-0023), so there is no packages/spec hunk.

  • 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 env handed to the spawn (the SDK's StdioClientTransport merges it over its default inherited set). It never goes onto the connector def, into a log, or into a message. A metadata read serves auth.credentialRef and transport.credentialEnv, which are two names; transport.env stays withheld whole, as before.

  • Refusals. These are configuration faults, thrown plain before anything is spawned, and none of them names a value:

    • a credentialEnv that is not an environment-variable name;
    • a credentialEnv on an http transport;
    • a credentialEnv with no static auth (absent, or none);
    • a credentialEnv naming a variable that transport.env also 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 credentialEnv still does not apply auth. The silent drop is connector-mcp: an mcp instance with a stdio transport accepts a static auth, 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 / credentialVerified stay 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's auth.credentialRef with transport.credentialEnv". Its hint uses the same auth: { type: 'bearer', credentialRef } spelling as the header hint. The matching os explain paragraph 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 on main) said that a stdio MCP server takes no auth, and steered authors to http. It would have shipped in the same release as this route. Its stdio bullet now names the credentialEnv route; this is a prose-only edit. Check Changeset / check-empty-changeset stays 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, so transport.env can only carry a literal (the route #29341's advisory cannot point to) #29434.

  • Docs. In content/docs/automation/connectors.mdx, the stdio bullet now names credentialEnv, there is a new "A stdio server's credential" section with an os:check example, and the grant-bearing paragraph sentence is updated.

Forced test edits. packages/lint/src/lint-connector-credential-literals.test.ts had two edits:

  • the stdio env message pin now names the new route (was: "a stdio server takes no auth; auth.credentialRef reaches it only over http");
  • the os explain fact A 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.

  • Real spawns through the SDK. The fixture child reports only whether the variable is set and its length, never the value.
    • bearer, api-key and basic each reach the child in the named variable, and the materialization carries no value;
    • control: the same instance with no credentialEnv starts the child without the variable;
    • control: a literal in transport.env reaches the child as today.
  • Refusals. Each of the four is thrown plain and names no value, and nothing is spawned (the not-a-name case is run with both a malformed string and a non-string). A grant handle on stdio meets the handle refusal, which now names credentialEnv.
  • Metadata read. The connector redactor serves auth.credentialRef and transport.credentialEnv, and redactedKeys is exactly providerConfig.transport.env.
  • Through the automation service (LiteKernel + AutomationServicePlugin + ConnectorMcpPlugin, a real stdio child):
    • a reference resolved by the host reaches the spawned server, and the served descriptor carries no value;
    • an unresolvable reference degrades the instance with credential '…' is not set, and the child's start marker is never written.
  • Advisory. The control (a literal in transport.env still 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 empty git diff HEAD; the suites import src, so no dist leg)
  • A1, the env merge dropped (env: env): 4 failed (bearer, api-key, basic, composition-resolved), expected 'unset:0' to be 'set:28'.
  • A2, the variable-name check dropped: 2 failed (not-a-name, not-a-string), expected a throw.
  • A3, the http refusal dropped: 1 failed (credentialEnv on an http transport).
  • A4, the no-auth refusal dropped: 2 failed (no auth: TypeError instead of the plain refusal; none: no throw).
  • A5, the clash refusal dropped: 1 failed (sets environment variable … twice).
  • A6, the old advisory steer restored: 1 failed (the stdio message pin, old vs new sentence).

Acceptance notes

  • Declaration and level: credentialEnv is a key a metadata author writes, so the kind is added even with no spec hunk (AGENTS.md Post-Task Checklist step 3; precedent PR feat(connector-openapi): declarative providerConfig.include operation allowlist #27387, providerConfig.include on connector-openapi). @objectstack/connector-mcp takes minor. @objectstack/lint changes only message text and stays patch. Both lines follow the seat's claim amendment on connector-mcp: a stdio MCP server has no secrets-layer route for its credentials, so transport.env can only carry a literal (the route #29341's advisory cannot point to) #29434.
  • Review face: node scripts/pm/record-recognisers.mjs --template --paths on the final file list. Without the changesets it reads the declaration face, TIER_DEFAULT. With them it refuses and defers to the changeset's Clause-②: yes (widening) line, which is the same declaration face.
  • Tool-list refusals over stdio stay on the plain path. The seat confirmed option A: credentialSent / credentialVerified stay http-only.
  • One secret per instance. A server that reads two secrets keeps the second in env, or is declared as two instances. An api-key's headerName / paramName are http placement and are not read on stdio. A basic username is not a secret, so it goes in env if the server reads it.
  • protocol.ts and 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 (credentialRef resolved by the host) both hold as written.

Verification

Readings head: f3ff3349 (merges origin/main 81cd9eb0). 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. On a76ad3f8 the 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-citations and check:nul-bytes all exit 0. check-empty-changeset exits 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)
  • Closure build 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 test under 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 on f3ff3349, all 87 run.
  • Three first answered exit 3 (PREREQUISITE NOT MET) and were re-run after their prerequisite, then exited 0:
    • check-plugin-teardown-shape --self-test, after fetching its pinned fixture commit;
    • check:skill-examples and check:dual-build-cjs-loads, after turbo run build --filter='./packages/**' (78/78 cache hits).
  • --ran reconciliation: 87 derived, 87 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero).
  • pnpm lint (repo-wide eslint, --no-inline-config): exit 0.
  • CI families are not re-run locally; the PR check-runs are the record.

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

7 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. ⚠️ 1 changed file(s) yielded no anchor (packages/connectors/connector-mcp/src/mcp-connector.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/connectors/connector-mcp/src/mcp-connector.ts) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 101 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 5bd615effe96ea05b4d9744a48a345ec804de208 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 497a3ea60655daeaa5e40912e2789cb5d7ea32a5 — the merge of head a76ad3f81b06d195ababc7b7ee678866ec166105 into base 5bd615effe96ea05b4d9744a48a345ec804de208, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

Contract-review-claim: session_01ADHkmQJdgarUjPLNgFBY67 · head f3ff334 · 2026-11-02T22:17Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

Contract review

Served-tier: TIER_DEFAULT
Head-sha: f3ff334946100fc174eb62213d5e862744ec18ae
Local-runs: none

① Derived judgments

Read 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 origin/main, and the check-runs on the head.

  • The new provider-owned key providerConfig.transport.credentialEnv on an mcp stdio transport: right. It is route (c) as ruled. The spec diff is empty (git diff origin/main...pr29621 -- packages/spec prints nothing). packages/spec/authorable-surface/integration.json stops at Connector:providerConfig, which is opaque, so no baseline moves. The key is read only in normalizeTransport's stdio arm, through credentialEnvOf.
  • The resolved static credential reaches only the child's environment: right. I traced every path the value takes.
    • secretOf(ctx.auth) returns it into credentialEnvOf, which returns { name, value }.
    • The stdio arm merges it into the returned transport's env copy. The source t.env is spread, never mutated.
    • That transport reaches the discovery transport (stdio is never rewrapped), then createMcpConnector, then defaultMcpClientFactory, then the SDK StdioClientTransport({ env }).
    • def is built only from name, label, description and tool descriptors, with authentication: { type: 'none' }. The transport is never copied onto it, and the materialization result carries no transport.
    • No log call exists in the provider. No refusal message interpolates the value: the four new ones and the reworded handle refusal name the connector, the key path, and at most the validated variable name.
    • The two pre-existing catch-alls (ConnectorCredentialRejectedError and ConnectorUpstreamUnavailableError) append the SDK error's message. A spawn or handshake error does not carry the child env, and those paths are unchanged from the literal-env case on main.
    • The new test pins this: no value in the serialized materialization, nor in the served descriptor through LiteKernel.
  • The four configuration refusals: right. Each is a plain Error, thrown in normalizeTransport before the stdio policy check and before any spawn. The test asserts nothing is spawned.
    • Not-a-name (also a non-string): it does not echo the offending value, which is correct, since a mistyped secret is the likely bad input there.
    • On http: the check runs before the url check.
    • No static auth (absent, or none).
    • A clash with an own key of transport.env.
    • The no-auth and clash refusals name the variable, but only after it has passed ENV_NAME_RE, so it is a name. A grant-bearing instance skips placement (borrowsCredential), so secretOf is never reached for a grant kind, and the instance then meets the handle refusal.
  • A stdio instance with no credentialEnv is unchanged, byte for byte: right (Q2 A, connector-mcp: an mcp instance with a stdio transport accepts a static auth, resolves its credentialRef, and then drops the value silently, so the declared credential reaches nothing #29607). With the key undefined, credentialEnvOf returns undefined before any check, and env is the same isStringRecord(t.env) ? t.env : undefined expression as on main. The only other change on that path is the handle refusal's sentence, which is message text. credentialSent and credentialVerified stay http-only, and the test pins credentialVerified undefined on stdio.
  • The materializer's behaviour for auth.credentialRef applies unchanged: right. Nothing under service-automation is in the diff.
    • credentialRefsOf(entry.auth) drives the Setup listing (plugin.ts ~L4451).
    • It drives the signature fingerprint |credential: (~L2928–2931), so a credential write re-spawns the instance.
    • degradeConnectorForCredential names the reference before the factory runs.
    • All three are transport-agnostic. The composition test shows a degrade naming the reference, and the child's start marker is never written.
  • Redactor read: right. CONNECTOR_CREDENTIAL_MAP_PATHS still withholds providerConfig.transport.env whole. credentialEnv is not a URL and not a map, so it is served as stored, and it is a name by validation. auth.credentialRef is a reference. The test pins redactedKeys to exactly that one path. Nothing new leaks.
  • Reworded grant-handle refusal, and 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 stdio verdict and hint: right.
    • STEER_ENV now names both halves, auth.credentialRef with transport.credentialEnv.
    • ENV_ROUTE uses the same auth: { type: 'bearer', credentialRef } spelling as HEADER_ROUTE, and the new test pins the shared substring and the absence of the old http steer.
    • The os explain paragraph in rule-explanations.ts matches.
    • The handle refusal now names a route that reaches something, so the earlier dead-end sentence is gone.
  • Docs edit (content/docs/automation/connectors.mdx): right. The stdio bullet now lists credentialEnv?. The new section "A stdio server's credential" carries an os:check example, the one-secret cost, the basic-username note, the four boot failures, the unchanged no-key case, and the advisory cross-link. The grant-bearing paragraph is updated. Check Documentation Links is green.
  • mcp-connector.ts: right. The change is a comment only.

② Semver level

Clause-②: 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 main never read.

Must-fix: the Metadata-protocol kind is wrong, and so is the level that follows from it. The PR body and the changeset both declare none, and the changeset grades @objectstack/connector-mcp: patch and @objectstack/lint: patch.

  • AGENTS.md (changeset rule 3) defines the kind as "the change to what a metadata author can write — a new key, accepted value or metadata type is added", and says a kind other than none takes at least minor on a package the diff moves.
  • credentialEnv is a new key that a metadata author writes in a connector entry, with new meaning and new refusals.
  • scripts/check-changeset-no-major.mjs says that a change the ratchets cannot see leaves "the declaration — not the diff" as the carrier. authorable-surface stops at providerConfig, which is why Check Changeset went green on none.
  • Direct precedent: PR feat(connector-openapi): declarative providerConfig.include operation allowlist #27387 added providerConfig.include to connector-openapi with no spec hunk, documented it on this same docs page, and declared added with that package at minor. PR feat(connector-mcp,spec): toolsSnapshot pins each MCP tool; a mismatch degrades through CONNECTOR_UPSTREAM_CHANGED #28515 (toolsSnapshot) also declared added.
  • "providerConfig is provider-owned (ADR-0023)" explains why there is no spec hunk. It does not make the key unauthorable. The claim and the seat ruling carried none forward, but a seat ruling does not override the maintainer's ruling A table.

Fix:

  • Change the kind to added on both the PR-body line and the changeset line.
  • Grade @objectstack/connector-mcp minor. @objectstack/lint may stay patch (its change is message text; the gate's rule is at least one package at minor), or move it to its own none/patch changeset.
  • Keep the ② line as it is: added beside a widening is consistent.

Check-runs on the head (no failure; 37 not yet concluded):

  • success (16): Auto Label, Build UI Deps, Check Changeset, Check Documentation Links, Check PR Size, Dogfood Regression Gate, Flag docs affected by code changes, Governed Surface Queue Guard, Lint · ESLint, Lint · PM dispatch-gates, Lint · scoped · query-options erasure, PR-body guards, Plan Test Core shards, Spec property liveness, Type Check · source gates, filter.
  • skipped (8): opt-in or matrix-template jobs.
  • in_progress (37): Build Core, Build Docs, Build & E2E UI, Live E2E UI, Lint · CI, release & engine gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped · slot lookup & corpus, Lint · scoped · tooling self-tests, Test Core 1–8 of 8, Test UI 1–16 of 16, Type Check UI, Type Check · consumer gates, Type Check · debt ledger, Type Check · workspace.

③ Boundary flags

  • open_questions (include/pin refusals on a stdio credentialEnv instance): confirm A.
    • In the code, credentialSent (http and sendsInstanceAuth) decides two things: whether an include miss becomes ConnectorCredentialRejectedError, which marks a stored credential rejected and parks it until rewritten, and whether pin drift with missing tools goes to pinnedToolsHiddenError.
    • A stdio tools/list gives no evidence that the server read its environment, let alone judged the credential. Taking the credential path would mark a correct stored credential rejected on a mere include typo.
    • The plain fault is the honest one. It also matches the accepted fix(connector-mcp): an include a credentialed MCP server does not list degrades the instance as a credential refusal, not the boot #28357 deviation for borrowed tokens. B stays unmeasured, and it needs a card only if a stdio server that filters tools by token turns up.
  • Out-of-scope note (integration-recipes.md, recipe 6): confirmed stale after this PR, and correctly left out.
    • On the head, line 158 reads "mcp (http only; a stdio server's credentials ride transport.env)".
    • "http only" stays true for the recipe's grant-bearing kinds. The parenthetical's claim about stdio credentials is now incomplete: a static credentialRef reaches stdio through transport.credentialEnv.
    • skills/** is a governed Tier H surface, and editing it would move this PR off the declaration face. Leaving it out is right.
    • The dev marked it "noted, not filed", so the seat owes it a carrier: a skills-lane card. The recipe 1/6 stdio wording and its eval should be checked there.
  • Deviations: both closed.
    • The hook-skipped WIP commit was reset and re-committed with hooks. The PR's commits are d8cb294, 4a09c75 and the merge f3ff334, so no hook-skipped commit was pushed.
    • The first typecheck round read a dist built without declarations, so it was discarded and re-run. That reading was not used, and CI typecheck is still in progress on the head.
  • Flag beyond the dev report (non-blocking, worth fixing in the same revision): .changeset/29341-connector-credential-literal.md is still pending on main. It tells @objectstack/lint readers that "a stdio MCP server takes no auth" and that the warning steers to http. It will ship in the same release as this changeset, which says the opposite. Suggest amending its stdio bullet here, or noting the supersession in this changeset.
  • Security findings: none beyond the hardening note in ①. The must-fix is in ②.

Implemented-by: claude/project-thread-439xd1
Reviewed-by: session_01ADHkmQJdgarUjPLNgFBY67

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>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

Seat confirmation · DELIBERATE CORRECTION · 2026-11-02T22:29Z

Check Changeset refuses on .changeset/29341-connector-credential-literal.md (check-empty-changeset, #17712). This is the DELIBERATE CORRECTION class, not a collision, and the seat confirms it.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

Contract-review-claim: session_01ADHkmQJdgarUjPLNgFBY67 · head a76ad3f · 2026-11-02T22:31Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

Contract review

Served-tier: TIER_DEFAULT
Head-sha: a76ad3f81b06d195ababc7b7ee678866ec166105
Local-runs: none

① Derived judgments

Read 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 origin/main; the delta since the failed head; and the check-runs on both heads.

  • The delta since the failed head is changesets only, so the prior ① judgments carry. git diff f3ff334946100fc174eb62213d5e862744ec18ae pr29621 touches exactly two files, 3 lines added and 3 removed:
    • In .changeset/29434-mcp-stdio-credential-env.md: the front-matter line '@objectstack/connector-mcp': patch becomes minor, and the body line Metadata-protocol: none becomes added.
    • In .changeset/29341-connector-credential-literal.md: one bullet, the stdio transport.env bullet, is rewritten (judged in ③).
    • Nothing under packages/** or content/** and no test changed. The one commit after the failed head, a76ad3f, is a changeset chore.
    • The net diff against origin/main is now 9 files, +512/−33. That is the prior 8 files plus the 29341 changeset. main has not moved any of the touched code, docs or changeset paths since the PR's merge base 81cd9eb.
  • So these judgments from the prior record stand unchanged:
    • the providerConfig.transport.credentialEnv route (c), with an empty spec diff;
    • the resolved static credential reaching only the child's spawn env (never on def, in a log, or in a message);
    • the four configuration refusals, which echo no value;
    • a stdio instance with no credentialEnv unchanged byte for byte (Q2 A);
    • the materializer's credentialRef behaviour inherited unchanged;
    • the redactor serving two names, with transport.env withheld whole;
    • the reworded grant-handle refusal and the advisory's steer, hint and os explain text;
    • the connectors.mdx section;
    • the comment-only mcp-connector.ts change.
  • The public surface is unchanged since the prior record. It is still one new provider-owned authorable key plus message text. The changeset body still describes it accurately: route, value handling, the four refusals, the unchanged no-key case, and the @objectstack/lint steer.
  • The code gates have not concluded on either head. A changeset-only delta proves no code by itself.
    • The CI run on the failed head was cancelled, because the a76ad3f push superseded it.
    • On that head, Test Core 1–8, Test UI 1–16, Type Check UI and Type Check · workspace all read cancelled.
    • The rollups that read those cancelled lanes:
    • Concluded green on the failed head, and still valid for identical code: Build Core, Build Docs, Build & E2E UI, Live E2E UI, Lint & Repo Gates and every Lint lane, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Dogfood Regression Gate.
    • The prior record's code judgment rests on reading the source, together with the dev's local readings on the failed head:
      • connector-mcp 119/119;
      • lint 7892/7892;
      • typecheck exit 0;
      • dogfood 0 failed;
      • 87/87 derived gates.
    • CI's own proof of the code is pending on this head.

② Semver level

Clause-②: yes (widening). Confirmed as a single line-initial key line in each carrier:

  • PR body, line 8: the one line-initial occurrence. The later mention in the "Review face" bullet is bullet prose, not a key line.
  • .changeset/29434-mcp-stdio-credential-env.md, line 6.

Both carry Metadata-protocol: added (PR body line 9, changeset line 7).

The prior must-fix is resolved.

  • The kind is now added, which matches AGENTS.md changeset rule 3: credentialEnv is a new key that a metadata author writes. The precedent is PR feat(connector-openapi): declarative providerConfig.include operation allowlist #27387, providerConfig.include, which has no spec hunk.
  • @objectstack/connector-mcp is graded minor, which is what that kind requires on a package whose src/** this diff moves.
  • @objectstack/lint stays at patch, and that is correct. Its change is message text only: the stdio env finding's steer and hint, and an os explain paragraph. Its accept set, finding positions and severity are unchanged. The gate requires that at least one moved package carry the level, and connector-mcp carries it.
  • No package is graded major.

The Check Changeset no-major leg agrees on this head. There were three runs on a76ad3f (jobs 125592252770, 125593616712 and 125594571075):

  • Run 1 (the push event) refused on the level axis. It read the PR body before the body was edited, so it saw the old kind none beside the new minor. That reading is stale.
  • Runs 2 and 3 (the body-edited events) both print:
    • "✓ This diff introduces no major bump."
    • "✓ LEVEL AXIS: … metadata-protocol added … @objectstack/connector-mcp: minor", which accounts for the declared widening. They list @objectstack/lint: patch as not refused.
    • check-adr-0087-registration is green, with no declared-breaking changeset.
    • Protocol lockstep holds.

The residual that the gate names is that it cannot see a second widening graded patch. It does not apply here: the only authorable change is credentialEnv, and it lives in connector-mcp.

③ Boundary flags

  • The edit to .changeset/29341-connector-credential-literal.md is prose only, and correct.
    • The diff against origin/main replaces exactly one line, the stdio transport.env bullet.
    • The front matter (@objectstack/lint: patch), its own ② line, its Metadata-protocol: none, the header bullet and every other paragraph are byte-identical. none is right for connectors: os validate and the save door warn on a credential-shaped literal in a connector header or env map (#28640 ruling 1A) #29341's warning-only advisory.
    • The new sentence states the route this PR ships and nothing more: move the entry to auth: { type: 'bearer' | 'api-key' | 'basic', credentialRef }, and name the variable in providerConfig.transport.credentialEnv; the mcp provider hands the resolved credential to the stdio server in that variable.
    • That matches the provider's placement for those three auth types, and it matches the advisory's STEER_ENV and ENV_ROUTE text on this head.
    • The old sentence ("a stdio MCP server takes no auth… only over an http transport") would have shipped false in the same release. Restoring it would be wrong.
    • The edit is inside the amended claim surface (6590026724), and the seat confirmed it on the PR (6590130632).
  • The check-empty-changeset red is the only failure on this head, and its check is not required.
    • In runs 2 and 3, the sole ##[error] lines are the finding: random changeset filenames collide silently across parallel agents — a round overwrote a sibling PR's minor changeset and every gate stayed green #17712 refusal on that one file and its exit code. That is the DELIBERATE CORRECTION class.
    • Run 1 adds only the stale level refusal judged in ②.
    • Every other concluded check-run on this head is success or skipped: Lint & Repo Gates, Governed Surface Queue Guard, every Lint lane, Type Check · source gates, Type Check · debt ledger, Spec property liveness, Check Documentation Links, PR-body guards (×3), Dogfood Regression Gate, Build UI Deps, Plan Test Core shards, filter, Auto Label, Check PR Size, and Flag docs affected by code changes.
    • Still running at 22:34Z: Build Core, Build Docs, Build & E2E UI, Live E2E UI, Test Core 1–8, Test UI 1–16, Type Check UI, Type Check · workspace, and Type Check · consumer gates.
    • The main ruleset's required contexts are TypeScript Type Check, Test Core, Build Core, Lint & Repo Gates, Governed Surface Queue Guard, Lint UI, Build & E2E UI, Type Check UI, Test UI and Live E2E UI. Check Changeset is not among them, so its expected red does not block the merge.
    • The required code contexts must still conclude green on this sha before landing.
  • The PR body's Skills-docs: follow-up #29625 line is correct.
  • The include/pin open question is settled as option A. credentialSent and credentialVerified stay http-only, and the code on this head is unchanged. The PR body's acceptance notes record the seat's confirmation of A.
  • The advisory hardening note has a carrier. It is now item 2 of connector-mcp: an mcp instance with a stdio transport accepts a static auth, resolves its credentialRef, and then drops the value silently, so the declared credential reaches nothing #29607, filing addendum 6590066946: run the credential-literal predicate on the credentialEnv string, as a warning only. It stays non-blocking for this PR.
  • The prior record's flag on the pending 29341 changeset is resolved by the edit above.
  • Security findings: none new. The delta carries no code.

Implemented-by: claude/project-thread-439xd1
Reviewed-by: session_01ADHkmQJdgarUjPLNgFBY67

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

席位核对 · ACCEPT · PR #29621 · 2026-11-02T23:03Z

Head-sha: a76ad3f81b06d195ababc7b7ee678866ec166105

维护者速读(席位终稿)

核对六项

  1. 形态:draft → main,Fixes #29434。9 个文件,+512/−33,都在认领及其修订 6590026724 的申报面内。.changeset/29341-connector-credential-literal.md 只改了 stdio 那一条 bullet,纯 prose:新路线让它原来的说法在同一版本内失真,所以一并更正。
  2. 前提实测:首轮 declaration 面复审(TIER_DEFAULT,6590013700,head f3ff3349)FAIL,唯一原因是申报写成 Metadata-protocol: none。这是席位裁定的错,规则是新增的 provider 自有 providerConfig 键算 added 并升 minor,先例 feat(connector-openapi): declarative providerConfig.include operation allowlist #27387。认领已修订,dev 改了申报。重审(6590229589,head a76ad3f8,独立会话)PASS。复审之后的 delta(f3ff3349..a76ad3f8)只有两份 changeset 共 3 行,无代码、无测试改动。席位按 diff 核过。
  3. 门禁:本 head 全部 check-run 已读:58 success、12 skipped、3 failure,失败的 3 个都是 Check Changeset。必需项全部 success:TypeScript Type Check、Test Core(1–8/8)、Build Core、Lint & Repo Gates、Governed Surface Queue Guard、Build & E2E UI、Type Check UI、Test UI、Live E2E UI。Lint UI 按路径跳过。Check Changeset 是 check-empty-changeset 对 29341 那份 changeset 的拒绝,属 DELIBERATE CORRECTION 一类,席位已在本 PR 确认(6590130632),它不是必需项。PR 上的 Dogfood 分片按设计跳过,dogfood 唯一读数是 dev 在 f3ff3349 上本地跑的整套:3280 条通过,0 失败。之后只改了 changeset,不需重跑。dev 其他读数:connector-mcp 119/119,lint 7892/7892,typecheck exit 0,派生门禁 87/87。
  4. 接受的偏差:改了别卡待发的 29341 changeset(见第 1 项,已按 DELIBERATE CORRECTION 确认)。mcp-connector.ts 只改了注释。
  5. 开放问题裁定:路线选 (c) providerConfig.transport.credentialEnv;Q2 选 A,即不写这个键时行为不变。裁定见卡上。skills 配方 6 的措辞另立 skills(objectstack-automation): integration-recipes recipe 6 still says a stdio MCP server's credentials ride transport.env; a static credentialRef now reaches stdio through transport.credentialEnv (#29434) #29625(受管路径),dev 的凭据加固发现挂到 connector-mcp: an mcp instance with a stdio transport accepts a static auth, resolves its credentialRef, and then drops the value silently, so the declared credential reaches nothing #29607。
  6. 落地条款:Clause-②: yes (widening),Metadata-protocol: added,PR 正文与 changeset 都有。semver:@objectstack/connector-mcp 升 minor,@objectstack/lint 升 patch。check-governed-merges --pr 29621:NOT governed。落地授权,维护者原话:「按照 pm-dispatch 规则复核过的都授权」(2026-10-25T11:35Z)。

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 2, 2026 23:04
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit dd03ee0 Nov 2, 2026
71 of 74 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-439xd1 branch November 2, 2026 23:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

1 participant