Repository navigation
fix(connector-mcp)!: refuse an unreadable or unknown providerConfig.include instead of exposing every tool - #27985
Conversation
…nclude instead of exposing every tool A declarative mcp instance read an include that was not an array as "no allowlist" and exposed every tool the server lists, dropped non-string entries, and ignored an entry naming a tool the server does not list. include now keeps the openapi provider's meaning: any shape other than an array of non-empty tool names is refused before connecting, and an entry the server's tools/list does not carry is refused after listing (the connection closed first). Both are plain configuration faults. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6bhgUmmMVhd9t2Y2dExvC
…ape and an unknown tool Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6bhgUmmMVhd9t2Y2dExvC
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 2 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 055c30915d41654851883507bb1528287e23cb84 && git checkout 055c30915d41654851883507bb1528287e23cb84
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4a5396b81e0666dc4c71d13394a32e59ed411a48 6840f48cab7173ab94431a1a8c3b9b5af8bcc201 && git checkout -B drift-repro 4a5396b81e0666dc4c71d13394a32e59ed411a48 && git merge --no-ff 6840f48cab7173ab94431a1a8c3b9b5af8bcc201
node scripts/docs-audit/affected-docs.mjs --json 4a5396b81e0666dc4c71d13394a32e59ed411a48 |
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F Contract-review-claim: session_01L6bhgUCizoYWQVJWjhdR6F · head 6840f48 · 2026-10-30T06:25Z |
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F Contract reviewServed-tier: Read at 2026-10-30T06:37Z: card #27402 (body and all 7 comments, above all triage 6489254710, claim 6515507103 with amendments 6515533290 and 6515646431, os-dev-report 6516451168), PR #27985 (body, 4-file list, both comments, Contract-review-claim 6516467804), the net diff of the head against main (4 files, +230/−3), the head's check-runs, and the cited code at the head. Adversarial read; the seat's conclusions were not taken as inputs. ① Derived judgmentsFail-closed, every shape traced through the factory at the head. The predicate is: refuse when No leaked client on the refusal path. Shape refusal: no client. Unknown-entry refusal: Fidelity to openapi, read side by side. Shape predicate: identical ( Materializer and the "rejected with the reason" clause. Spec. Tests. Every refusal is pinned through the real Gates and dogfood. Check-runs on the head at this read: 33 success, 14 skipped, 6 in progress (Test Core shards 1, 2, 3, 5, 6, 8). Green: Lint & Repo Gates, TypeScript Type Check, Build Core, Governed Surface Queue Guard, Test UI, Live E2E UI, Lint · PM dispatch-gates, Spec property liveness, Check Changeset, Dogfood Regression Gate, PR-body guards. The dogfood battery is on record at this head: os-dev-report 6516451168 names the head's short id with command-exit 0, 357 files passed / 3 skipped, 2875 tests passed / 20 skipped; the head's merge commit was made at 2026-10-30T05:57Z and the report posted at 2026-10-30T06:24Z, which fits the 1435 s hold it reports. Their conclusions are the gate verdicts; nothing was re-run here. ② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: PASS |
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F 维护者速读(席位终稿)
席位意见: ACCEPT。收窄与 openapi 逐项对齐,所有探过的形状都失败即关闭,契约复核 PASS。 席位核对ACCEPT · head
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
Requested by Sales · project thread
Fixes #27402
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: updated here —
content/docs/automation/connectors.mdx(theprovider: 'mcp'section now says what theopenapisection already says: a shape other than an array of tool names, or an entry the server does not list, fails the boot and never falls back to every tool; added to the file surface by claim amendment 6515646431). The skill's MCP recipe (skills/objectstack-automation/references/integration-recipes.md§2) already writesinclude: ['search_issues'], which stays valid; nothing underskills/**is touched.维护者速读
provider: 'mcp'连接器实例的providerConfig.include(工具白名单)现在会响亮拒收两种错误写法:不是"非空字符串数组"的形状(如字符串'web_search')在连接前拒收;点名了 MCP 服务端tools/list里没有的工具,在列出后拒收(先关连接)。两者都是配置错误:启动时失败,重载时跳过该条目。include: 'web_search'会被当成"没有白名单",实例把服务端的全部工具(包括delete_everything这类破坏性工具)暴露给流程和智能体,且没有任何报错;写错工具名也被静默忽略。这是白名单失效放行(p1),并让include与openapiprovider(feat(connector-openapi): declarative providerConfig.include operation allowlist #27387)一个含义。include写成非数组或列了服务端没有的工具,升级后启动会失败并在报错里点名条目与服务端工具清单;改成工具名数组或删掉错的条目即可。回滚 = revert 本 PR。packages/connectors/connector-mcp/src/mcp-provider.ts(createMcpProviderFactory里的形状拒收与未知条目拒收两段);mcp-provider.test.ts末尾#27402一组;.changeset/27402-mcp-include-refuses.md。pnpm --filter @objectstack/connector-mcp exec vitest run src/mcp-provider.test.ts,应见 34/34 通过,其中providerConfig.include refuses loudly (#27402)一组 10 条全绿。What changes
createMcpProviderFactory(the factory the boot materializer calls for everyprovider: 'mcp'instance) now validatesproviderConfig.includewith theopenapiprovider's meaning (#26709 triage 6487372008, #27387):includeabsent ⇒ every tool, as before. Otherwise it must be an array of non-empty strings; anything else (a string, an object,null, a non-string or empty entry) throws a plainError:connector-mcp provider: connector 'NAME' providerConfig.include must be an array of tool names (strings): each the name of a tool the MCP server lists in tools/list.No client is created.tools/list. The allowlist ridescreateMcpConnector's ownincludepredicate, which records every tool name the server lists. An entry nothing matched closes the connection and throws a plainErrornaming the entries and up to 20 of the server's tools:... providerConfig.include names 'nope', which the MCP server does not list. Each entry must be the name of a tool from the server's tools/list; the server lists 2 tool(s): 'web_search', 'delete_everything'.(same shape and sample size as theopenapirefusal).ConnectorUpstreamUnavailableErrornorConnectorCredentialRejectedError): fatal at boot, a skipped entry on reload. The instance never falls back to exposing every tool.Nothing outside
createMcpProviderFactorymoves (its JSDoc gains one paragraph).McpProviderConfig.includekeeps itsunknowntype;createMcpConnector(hand-wired, host-code predicate) is unchanged.Tests
packages/connectors/connector-mcp/src/mcp-provider.test.ts, new groupmcp provider: providerConfig.include refuses loudly (#27402), against a fake server listingweb_search+delete_everything(the card's measurement):include: 'web_search',['web_search', 7],['nope']; plus['web_search', ''], an object,null, and a partial['web_search', 'nope', 'nope'](no partial instance). Each asserts a configuration fault (not upstream-unavailable, not credential-rejected), the message's first sentence, and that the shape refusals never connect / the unknown-entry refusal closes the client.includeexposes every tool.LiteKernelwithAutomationServicePlugin+ConnectorMcpPluginand a declared instance withinclude: 'web_search'failsbootstrap()withfailed to materialize connector instance 'vendor_mcp' via provider 'mcp'+ the include message, never connects, and registers no action under the name.Red first (fix stashed, base ed0e71d): 8 failed / 26 passed
Green and ablation (scripts/ablation-replace.mjs, each leg restored blob == HEAD ae66f3def905, git diff HEAD empty)
vitest run src/mcp-provider.test.ts34/34;pnpm --filter @objectstack/connector-mcp test5 files / 56 tests passed;typecheckexit 0 (test layer: 1 pinned debt signature held, unchanged);eslint srcexit 0.false && cfg.include !== undefined &&): 6 failed (the five shape cases + the kernel boot case).if (false && unknown.length > 0)): 2 failed (the unknown and the partial case).await bundle.close()deleted: 1 failed (the unknown-entry case'sclosedassertion)../mcp-provider.jsfrom source (nodisthop), so no rebuild leg applies.Acceptance notes
openapi: the boot fails naming the instance and the fault; on a reload the entry is skipped and logged, and an instance already serving keeps its previous (valid) bundle. No new instance state is added (that would be outside the file surface).include: []stays accepted and exposes no tools, as foropenapi.skills/objectstack-automation/references/integration-recipes.md:63andskills/objectstack-automation/evals/integrations-declared.json:7still say theopenapiprovider has noincludekey, which feat(connector-openapi): declarative providerConfig.include operation allowlist #27387 made false.git merge-treeagainst every open PR touchingpackages/connectors/connector-mcp/**orcontent/docs/automation/connectors.mdx: only chore: version packages #26517 (CHANGELOG/package.json), clean.Verification
Dogfood battery at the final head
6840f48c(after mergingorigin/mainonce; closure built underos-verify-lock, thenpnpm --filter @objectstack/dogfood test, no shard, no path):os-verify-lock: VERDICT command-exit 0· Test Files 357 passed | 3 skipped (360) · Tests 2875 passed | 20 skipped (2895) · no failures. The same run re-ran@objectstack/connector-mcp's suite on that head: 5 files / 56 tests passed. (An earlier battery at the pre-merge head6504a2e9read the same counts.)The derived gates below were run at
6504a2e9; the merge brought onlymain's own files, so the change set is the same four paths, anddispatch-gates --ranre-reconciled on6840f48c: 87 derived, 87 run, 0 NOT-MEASURED.Derived gates: 87 derived, 87 run, 0 NOT-MEASURED (dispatch-gates --ran, a derived zero: every row carries an exit code)
All 87 exit 0. Two first answered exit 3 (
PREREQUISITE NOT MET, not a verdict) before their dists existed —check:skill-examples(client-react.d.ts) andcheck:dual-build-cjs-loads(8 dists); after building those packages both reran exit 0 (275 prose examples type-check; cjs-loads floors held).Docs family readings for the
connectors.mdxparagraph:check:docs✅ 232 generated files in sync ·check:doc-anchors✅ 822 links resolve ·check:doc-authoring✓ baseline held ·check:doc-frontmatter✓ ·check:docs-single-h1✓ 472 pages ·check:docs-redirectsOK ·check:docs-spec-enumerationsOK ·check:docs-transcript-drift✓ ·check:doc-security-posture✅ ·check:doc-formula-expressionsexit 0 ·check-doc-route-spelling --advisory✓ ·docs-audit/check-affected-docsexit 0 ·check-issue-citations✅ none added.ADR-0087:
check-adr-0087-registration --base origin/main✓ 1 declared-breaking changeset carrying a disposition (not-required (no-migration-prescription));check-changeset-no-major✓ no major.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01L6bhgUmmMVhd9t2Y2dExvC