Skip to content

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

Closed

Description

@objectstack-fleet

domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

Filing gate: ① an out-of-scope finding of the #29341 dev (os-dev-report 6579919803, noted with no carrier), confirmed by the contract review on PR #29424 (record 6580241094, ③). Filed by domain:server seat 3. ⛔ Not a claim. Suggested-grade: p3 (an enhancement; a literal still works, it just sits in metadata).

What happens

  • A declarative mcp connector with an http transport can take auth: { ..., credentialRef }, which the provider resolves through the secrets layer and sends itself.
  • A stdio transport takes no auth. connector-mcp/src/mcp-provider.ts:82 says auth is "Not applied to stdio transports", and content/docs/automation/connectors.mdx:232 says stdio credentials ride transport.env.
  • So a stdio server's API key can only be a literal in transport.env. That map is withheld on read, but the literal is stored in metadata.
  • 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 advisory (connector-credential-literal) warns on such a literal. It can only say that auth.credentialRef reaches the server over an http transport, because no route exists for stdio.

Wanted

Dedupe: searched stdio credentialRef transport env; nearest #29341, #29342, #28640. New.

Links back: #29341, #29424, #28640.

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p3 · domain:server · pm:queue · 2026-11-02T16:01Z

    ⛔ Not a claim.

    • Read fresh. A stdio MCP server has no secrets-layer route for its credentials, so transport.env can only carry a literal. p3, as suggested.

    Generated by Claude Code

  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

    Claim: PM loop round 1 · card thread · 2026-11-02T20:59Z
    Session: session_01ADHkmQJdgarUjPLNgFBY67 (the seat); the card thread is session_01ADHkmRFhrJgbwLP5mvZkeJ
    Account: os-steve (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-439xd1
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#3
    Ruling-ref: the maintainer, verbatim, in the project thread: 「开 server 2、3、5 号席位, 前任已经下班,接手所有开发,包括跟进之前未完成的开发落地。」
    File surface: packages/connectors/connector-mcp/src/** (the stdio spawn and its env), the connector credential resolver that serves credentialRef, the #29341 advisory (connector-credential-literal) wording, content/docs/automation/connectors.mdx, their tests, one .changeset/29434-*.md; a packages/spec/src/** hunk only after the seat rules on the route (the claim is amended then)
    Container & model: card thread cmsg_01FdL1XeUyvVNxLreh3F4Qi95wDB21nUbCmU5szPePATGS, mode:thread, model: opus (--tier names no path mandate: PM's call, default), effort: high
    Clause-②: yes (widening)
    Metadata-protocol: none
    Responsibility: the card thread measures, reports route options, builds after the seat ruling, opens the draft PR and reports; the seat rules the route, reviews and lands
    Thread-read: 6581800181
    Premise: holds (site re-read on origin/main 2420778c by the seat before dispatch)
    Serial constraints cleared: no other seat-3 card on connector-mcp or connector auth; protocol.ts freeze not touched; no ADR edit (stop and report if one is needed)
    Resume-from: none — fresh from origin/main

    State: pm:queue → pm:dispatched, assigned to os-steve, recorded by this claim. Order: project file server-seat3/standard-order.md + server-seat3/29434-delta.md.

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

    Seat ruling · #29434 route · 2026-11-02T21:02Z

    Ruling-ref: the card thread's measurements and options (project file server-seat3/29434-route-options.md, measured on origin/main 2420778c); the seat delta ("report options before adding one; new public names are the seat's call"); the maintainer's standing word that lane seats decide technical sequencing and grading (2026-11-01T12:11Z).

    Q1, the route: (c). The instance auth is applied to stdio as one named env var, spelled providerConfig.transport.credentialEnv.

    • Why (c): it is the only option that leaves the secrets layer whole. The materializer already resolves entry.auth.credentialRef. It degrades an instance whose reference no arm answers, naming the reference and no value. It fingerprints the source into the signature, so a credential write re-spawns the server, and it lists the reference in Setup. Options (a) and (b) would put references where the materializer cannot see them. They would also need a resolver hunk on ConnectorProviderContext (a schema face), and (a) fails the read test because transport.env is withheld whole. ADR-0097 §3 keeps the materializer provider-agnostic, and (c) uses it as it stands.
    • The name credentialEnv is ruled. It is provider-owned providerConfig vocabulary (ADR-0023), so there is no spec key: Clause-②: yes (widening), Metadata-protocol: none, as claimed.
    • Cost accepted: one secret per instance. A server that needs two secrets keeps the second in env, or is split into two instances.
    • The four configuration refusals in the options file are in scope. They are: a credentialEnv that is not a valid env-var name, credentialEnv on an http transport, credentialEnv with no static auth, and credentialEnv naming a variable that transport.env also sets. None of them puts a value in its message. They are new refusals only of the new key, so nothing that is accepted today moves.

    Q2, auth on stdio without credentialEnv: A. Leave the runtime as it is on this card; the seat files the silent drop as its own card.

    • Refusing it here would make this PR a narrowing and BREAKING for apps that declare auth beside a literal env. That decision belongs on its own card, with the options measured against apps in the field.

    Face: the declaration face at TIER_DEFAULT under (c), if record-recognisers.mjs --paths agrees on the final file list. Build now.

  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-steve · session_01ADHkmRFhrJgbwLP5mvZkeJ
    os-dev-report

    {
      "issue": 29434,
      "status": "done",
      "branch": "claude/project-thread-439xd1",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29621",
      "session": "session_01ADHkmRFhrJgbwLP5mvZkeJ",
      "premise_still_valid": true,
      "summary": "Route (c) as ruled. A stdio mcp instance names providerConfig.transport.credentialEnv. The provider puts the secret of the host-resolved instance auth (bearer token, api-key key, or basic password) into the child's spawn env under that name, and nowhere else. There are four plain configuration refusals (not a variable name; on http; no static auth; clashes with transport.env), and none names a value. A grant handle on stdio meets the existing refusal, which now names the route. No credentialEnv means unchanged behaviour (Q2 A, #29607). The #29341 advisory's env verdict and hint now steer to auth.credentialRef with transport.credentialEnv, in the same auth spelling as the header hint, and os explain and connectors.mdx are updated. No packages/spec hunk.",
      "tests": "f3ff3349: connector-mcp 9/9 files, 119/119 tests (new mcp-stdio-credential-env.test.ts 17/17: real-spawn fixture reporting set+length only for bearer/api-key/basic; no-credentialEnv control; literal-env control; 4 refusals plus a non-string case, each plain, no value, nothing spawned; grant-handle refusal names the route; the connector redactor serves credentialRef and credentialEnv with redactedKeys == [providerConfig.transport.env]; LiteKernel composition: resolved ref reaches the child and the descriptor carries no value, unresolvable ref degrades naming the ref and the child marker is never written). lint 192/192 files, 7892/7892 tests. Typecheck exit 0 for both. Ablations via ablation-replace.mjs, each restored to blob == HEAD with an empty git diff HEAD; src imports, so no dist leg: A1 merge dropped, 4 red ('unset:0' vs 'set:28'); A2 name check, 2 red; A3 http refusal, 1 red; A4 no-auth refusal, 2 red; A5 clash refusal, 1 red; A6 old advisory steer, 1 red.",
      "gates": "dispatch-gates --commands on f3ff3349: 87 derived, 87 run. 3 first exited 3 (PREREQUISITE NOT MET) and were re-run to exit 0: teardown-shape --self-test after fetching 621a4876; check:skill-examples and check:dual-build-cjs-loads after turbo build ./packages/** (78/78 cached). --ran: 87 derived, 87 run, 0 NOT-MEASURED, 0 UNRUN (derived zero). pnpm lint exit 0. CI check-runs: in_progress at report time; not re-run locally.",
      "dogfood_battery": "f3ff3349 · os-verify-lock VERDICT command-exit 0 (36m29s) · Test Files 398 passed + 3 skipped (401) · Tests 3280 passed + 28 skipped (3308) · 0 failed",
      "mcp_calls": "5 (all reads): issue_read get, issue_read get_comments x2, pull_request_read get (#29602, body template), pull_request_read get_files (#29621)",
      "api_writes": "2: pr_create via fleet-write relay (run 41535655156, PR #29621, 10098 bytes sent = stored, no assignees); this os-dev-report comment via post-stamped.mjs",
      "line_budget": "8 files, +511 / -32 (test 304, provider +105/-10, docs +50/-5, changeset 18, lint rule + explain + test +30/-16)",
      "files_changed": [".changeset/29434-mcp-stdio-credential-env.md", "content/docs/automation/connectors.mdx", "packages/connectors/connector-mcp/src/mcp-connector.ts", "packages/connectors/connector-mcp/src/mcp-provider.ts", "packages/connectors/connector-mcp/src/mcp-stdio-credential-env.test.ts", "packages/lint/src/lint-connector-credential-literals.test.ts", "packages/lint/src/lint-connector-credential-literals.ts", "packages/lint/src/rule-explanations.ts"],
      "review_face": "record-recognisers --template --paths (final list): without the changeset, declaration face TIER_DEFAULT; with it, the tool defers to the changeset's Clause-② yes (widening) line, which is the same declaration face",
      "deviations": ["The first WIP commit was made with core.hooksPath=/dev/null. It was undone at once (reset --soft) and re-committed with hooks, so no hook-skipped commit was pushed.", "The first typecheck round read a dist built with OS_SKIP_DTS=1 (TS7016). It was rebuilt with declarations and re-run green; that round's reading was not used."],
      "open_questions": [
        { "question": "Should an include/toolsSnapshot refusal on a stdio instance that passed credentialEnv take the credential path (ConnectorCredentialRejectedError, stored credential marked rejected), as an http instance's does? A stdio server can also list only the tools its token may use.", "options": ["A: keep credentialSent http-only, as built (a stdio handshake proves nothing about the env credential; plain fault)", "B: count credentialEnv as sent for the include/pin refusal path only, never for credentialVerified"], "recommendation": "A for this card. B changes rejection bookkeeping, and a server that hides tools per token over stdio has not been measured." }
      ],
      "security_findings": "none",
      "out_of_scope_findings": ["carrier: seat 3 · skills/objectstack-automation/references/integration-recipes.md recipe 6 still says 'a stdio server's credentials ride transport.env'. This is now incomplete (credentialEnv route), not false. skills/** is Tier H governed, so it is not edited in this PR · noted, not filed"]
    }

    Stamped 2026-11-02T22:16Z.


    Generated by Claude Code

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

    Claim amendment · #29434 · 2026-11-02T22:24Z

    Branch: claude/project-thread-439xd1 (unchanged)
    Clause-②: yes (widening)
    Metadata-protocol: added

    Why: the contract review on PR #29621 (head f3ff3349, verdict FAIL on this point alone) is right, and it corrects the seat's own route ruling (6588385697, which said none).

    • AGENTS.md changeset rule 3 defines the kind as the change to what a metadata author can write: a new key is added.
    • providerConfig.transport.credentialEnv is a key an author writes in a connector entry. That it is provider-owned (ADR-0023) explains why there is no spec hunk. It does not make the key unauthorable.
    • Precedent: PR feat(connector-openapi): declarative providerConfig.include operation allowlist #27387 added providerConfig.include to connector-openapi with no spec hunk and declared added.
    • The level follows the kind: @objectstack/connector-mcp takes minor. @objectstack/lint changes message text only and may stay patch.

    File surface also adds .changeset/29341-connector-credential-literal.md. That changeset is still pending on main, and its stdio bullet ("a stdio MCP server takes no auth") would ship in the same release as the opposite statement. Its stdio sentence is corrected on this PR, as a prose edit to another card's pending changeset (the DELIBERATE CORRECTION class). The route ruling stands otherwise.

  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

    Landing record · #29434 · PR #29621

    domain:server seat 3 · session_01ADHkmQJdgarUjPLNgFBY67 · 2026-11-02T23:26Z

    Reading Probe Result
    Queue membership git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-29621-*' 0 refs (released)
    Ancestry git merge-base --is-ancestor dd03ee06 origin/main yes
    Content git grep -n "credentialEnv" origin/main -- packages/connectors/connector-mcp/src/mcp-provider.ts first hit line 84
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:serverDomain lane — seat card indexed by label:pm:seatpriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions