Repository navigation
fix(connector-mcp): an include a credentialed MCP server does not list degrades the instance as a credential refusal, not the boot - #28357
Conversation
…kes the credential path When providerConfig.include names a tool the MCP server's tools/list does not carry and the handshake carried the instance's credential, the refusal is raised as ConnectorCredentialRejectedError (status 403), naming both causes the answer cannot tell apart: the credential's scopes hide the tool, or the declaration names a tool the server does not have. An instance whose handshake carried no credential keeps the plain configuration fault. Claude-Session: https://claude.ai/code/session_017xRBcVBKAFk1ntXSjvPt7U Co-authored-by: Claude <noreply@anthropic.com>
…ialed server hides, with docs and changeset The three probes as permanent tests, on the real provider and materializer and the real credential store (ObjectQL over SQLite): an environment credential degrades the instance instead of failing the boot and recovers once the scopes cover the tools; a stored credential is marked rejected and held, and a better-scoped value recovers it without a restart; with no credential the boot still fails as a declaration fault. Claude-Session: https://claude.ai/code/session_017xRBcVBKAFk1ntXSjvPt7U Co-authored-by: Claude <noreply@anthropic.com>
…e rejected row also cover an mcp listing that withholds an included tool ConnectorCredentialRejectedError's TSDoc and its status member, and the sys_connector_credential status description, named only a 401 / 403. The mcp provider now also raises the marker (status 403) for a server that accepted the credential and did not list a tool the instance's include names, so each text names that case too. Comments only in spec. Claude-Session: https://claude.ai/code/session_017xRBcVBKAFk1ntXSjvPt7U Co-authored-by: Claude <noreply@anthropic.com>
…p-scope-hidden-tools
…nnected and the server starting The Credentials paragraph said a classic token whose scopes hide an included tool stops the server from starting when set as the environment variable. The mcp provider now degrades the instance on either path. It also notes that GitHub's server keeps read tools that work on public repositories, these two among them, listed whatever the scopes. Claude-Session: https://claude.ai/code/session_017xRBcVBKAFk1ntXSjvPt7U Co-authored-by: Claude <noreply@anthropic.com>
…s the declaration fault Pins the boundary the plain branch's comment now states: only the instance's auth takes the credential path; a header credential is part of the declaration and is fixed by editing it. Claude-Session: https://claude.ai/code/session_017xRBcVBKAFk1ntXSjvPt7U Co-authored-by: Claude <noreply@anthropic.com>
…rom source in tests The store-backed test imports the three; check:test-source-alias requires an anchored source alias for each rather than a wider unaliased ledger. The registry log is quieted through OS_REGISTRY_LOG. Claude-Session: https://claude.ai/code/session_017xRBcVBKAFk1ntXSjvPt7U Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 4 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 168 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 62127e0b0f6af3e2df5a7bc1ab272bb6fd60105e && git checkout 62127e0b0f6af3e2df5a7bc1ab272bb6fd60105e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9212035719b9420c11bc19f9b3192ccf06416bdc b6312fe6f3108fe588197fd5256ba041077789cf && git checkout -B drift-repro 9212035719b9420c11bc19f9b3192ccf06416bdc && git merge --no-ff b6312fe6f3108fe588197fd5256ba041077789cf
node scripts/docs-audit/affected-docs.mjs --json 9212035719b9420c11bc19f9b3192ccf06416bdc
|
|
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (HTTP 429 rate_limit, the tier's usage limit reached, 2026-10-31T06:14Z) · head b6312fe Why a review at that tier is owed: the diff edits |
|
domain:server#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: |
|
epic:#27293#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract reviewServed-tier: Inputs: card #28311 (body, the dev note 6538998021, triage 6539260047 which routes the credentialed case to the credential path, the claim), PR #28357 (body, the docs-drift comment, the seat's hand-off 6539986174 naming the question this record must answer, the file list of 10 files, the diff of 569 lines against ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Verdict reasoning. The marker's contract on Implemented-by: VERDICT: PASS |
Fixes #28311
Clause-②: no
Metadata-protocol: none
维护者速读
tools/list没有列出include点名的工具时,不再按“声明错误”让整个服务启动失败,而是按“凭据问题”把该实例降级,存储的凭据标为rejected;没有凭据的实例行为完全不变。auth凭据且工具缺失”这一种情况的归类,复用已有的ConnectorCredentialRejectedError与rejected状态,没有新类型、新状态。回滚即还原mcp-provider.ts里的if (credentialSent)分支。packages/connectors/connector-mcp/src/mcp-provider.ts(if (credentialSent)分支与 403 的理由)、packages/connectors/connector-mcp/src/mcp-include-credential-scope.test.ts(环境变量路径与存储路径两条启动测试)、content/docs/automation/connectors.mdx。pnpm --filter @objectstack/connector-mcp exec vitest run src/mcp-include-credential-scope.test.ts,预期Tests 9 passed (9)。What changes
@objectstack/connector-mcp(mcp-provider.ts). WhenproviderConfig.includenames a tool the server'stools/listdoes not carry, the refusal now depends on whether the handshake carried the instance's credential (anhttptransport with the instanceauthsent as a header — the same predicatecredentialVerifiedalready used):ConnectorCredentialRejectedError, status403. The message names the missing tools, the tools the server lists for that credential, and both causes with the fix for each (the credential lacks the scopes those tools need, or the declaration names a tool the server does not have). The materializer's existing credential path then applies: degraded, boot not failed; a stored credential markedrejectedand held; an environment or vault credential retried on the backoff.auth, anapi-keysent as a query parameter, astdiotransport, or a credential written intoproviderConfig.transport.headers, which is part of the declaration): the plain configuration fault, byte-identical message, fatal at boot as before.content/docs/automation/connectors.mdx: theincludeparagraph, the "credential the upstream refuses" paragraph and two rows of the failure-mode table.packages/apps/connector-github/README.md: the Credentials paragraph no longer says an environment-variable token stops the server from starting.ConnectorCredentialRejectedError/CONNECTOR_CREDENTIAL_REJECTEDTSDoc inpackages/spec(comments only, no type change) and thesys_connector_credential.statusfield description now name the second way the marker is raised.src/mcp-include-credential-scope.test.ts(new): the three probes the card was filed from, made permanent on the real provider, the real materializer and the real credential store (ObjectQL over SQLite), plus controls. For the store,connector-mcpgains three devDependencies (@objectstack/objectql,@objectstack/driver-sql,@objectstack/platform-objects, all already in its build closure) and avitest.config.tsthat reads them from source.@objectstack/connector-mcppatch,@objectstack/service-automationpatch (the field description).Tests
At head
b6312fe6:pnpm --filter @objectstack/connector-mcp test:Test Files 6 passed (6),Tests 65 passed (65);typecheckexit 0, test-typecheck ledger unchanged (1 file(s) / 5 error(s), the new file adds none).pnpm --filter @objectstack/connector-app-github test(validate + vitest):Validation passed,Test Files 2 passed (2),Tests 10 passed (10);typecheckexit 0.@objectstack/service-automation(the description edit), atc7628191, no later commit touches it:Test Files 237 passed (237),Tests 2985 passed (2985);typecheckexit 0.auth,api-keyas a query parameter, a credential intransport.headers,stdiowithauth, and a credential whose listing carries every entry (materializes,credentialVerified); through the materializer — environment credential (boot not failed, degraded, listedrejected/env, not held, recovers on a reconcile once the same token's scopes cover the tools), stored credential (degraded with the credential reason, rowrejectedwith that reason, held ascredential, a reconcile dials nothing, a better-scoped value recovers it withlastVerifiedAtset; neither token in any log line; no "Fix its declaration" line), and no credential (boot still fails with the declaration fault).a51f1291:node scripts/ablation-replace.mjsreplacedif (credentialSent) {withif (false as boolean) {(anchor 1 to 0, blob05b010b670deto2dd4cd2773e3):Tests 3 failed | 6 passed (9), the three credential-path cases red, every control green; restored, blob equals HEAD,git diff HEADempty. The test imports the provider by relative path fromsrc, so no build sits between the mutation and the run.Acceptance notes
github/github-mcp-serveratf2a07cd1: the scope filter is applied to the inventory beforetools/list(pkg/http/handler.goPATScopeFilter,pkg/github/scope_filter.go), the result carries no field about hidden tools, and the HTTP layer sets no scope header on that answer. The only scope signal isWWW-Authenticate: Bearer error="insufficient_scope"with403on atools/call(pkg/http/middleware/scope_challenge.go). So the message names both causes, and status403follows that same convention (RFC 6750 section 3.1); the message itself quotes no HTTP status.f2a07cd1,search_issuesandissue_readare declared withscopes.PublicRead(scopes.Repo), whose visibility check returns true for any scopes (pkg/scopes/scopes.go,PublicRead), matching GitHub'sdocs/scope-filtering.md"Public Repository Access". The defect is reachable with GitHub through other tools (write tools,notifications, project and org tools) and with any server that filters by credential. The README paragraph now says so.rejectedstate is reused; no new state. It clears when the value is set again or a handshake lists every entry (markVerified). A credentialed instance whoseincludehas a real typo now marks its stored credentialrejectedtoo; the message names that cause, and editing the declaration re-attempts it and clears the mark on success.connector-mcp's own tests, which run without the app;packages/apps/connector-github/test/boot.test.tsis unchanged.packages/services/service-automation/src/plugin.tsis not touched. Its hold log line ("its upstream refused its credential") reads acceptably for this case and is left alone.origin/mainwas merged atefdddc90, after feat(apps): connector-github, GitHub's remote MCP server as an open-edition connector app (E4 batch 1, GitHub half) #28306 landed. Main has moved since; GitHub's merge of the newer commits is clean (git merge-treeexit 0), and none of them touches these packages.Verification
Gate families derived for this diff: 114 run, 0 NOT MEASURED
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackatb6312fe6: 114 commands for 10 paths, 569 changed lines (+533 / -36) (under the 3000 threshold). Every command was run with its exit code captured before any pipe. 112 exited 0 on the first pass.pnpm --filter @objectstack/spec run check:skill-examplesandpnpm check:dual-build-cjs-loadsexited 3 (PREREQUISITE NOT MET: unbuiltclient-reactand siblings). Re-run once the dists existed, both exited 0 (277 prose examples type-check across 3 surface(s);113 published require entry point(s) across 70 package(s) load).--ranover the final record:114 derived famil(ies) accounted for — 114 run, 0 NOT-MEASURED.Not run locally, by rule
test:repo,pnpm lintand the other repo-wide scans belong to CI. Dogfood does not apply: no!in the title and no(narrowing)arm.Generated by Claude Code