Skip to content

feat!: hard-fail elicitation when client cannot prompt for confirmation#1208

Open
manjunathshiva wants to merge 1 commit into
mongodb-js:mainfrom
manjunathshiva:feat/mcp06-elicitation-fail-closed
Open

feat!: hard-fail elicitation when client cannot prompt for confirmation#1208
manjunathshiva wants to merge 1 commit into
mongodb-js:mainfrom
manjunathshiva:feat/mcp06-elicitation-fail-closed

Conversation

@manjunathshiva

Copy link
Copy Markdown

Per OWASP MCP Top 10 (2025) item MCP06 - Intent Flow Subversion. Tools listed in confirmationRequiredTools no longer execute silently against MCP clients that don't advertise the elicitation capability; they refuse with a structured error that names the missing capability.

Before this change, Elicitation.requestConfirmation() returned a bare true whenever the client didn't support elicitation, which meant a confirmation-gated operation (drop-database, atlas-create-db-user, etc.) would proceed against an older client without the user ever seeing a confirmation prompt. The user never had the chance to consent, so silently proceeding is a policy violation: no consent, no execute.

Changes:

src/elicitation.ts

  • requestConfirmation() return type changes from Promise<boolean> to a tagged union ConfirmationResult: { ok: true } | { ok: false; reason: 'declined' | 'no-elicitation-support' }. The new type is exported so callers can distinguish "user said no" from "client cannot ask". Both block execution; the two cases want different error messages and different audit-log severities.

src/tools/tool.ts

  • ToolBase.verifyConfirmed() now returns ConfirmationResult.
  • ToolBase.invoke() inspects the reason. For 'no-elicitation-support' it logs a warning via the new LogId.toolBlockedNoElicitation and returns an actionable error telling the user/operator to either use an elicitation-capable client or remove the tool from confirmationRequiredTools. For 'declined' it preserves the previous user-decline message and debug-level log.

src/common/logging/loggingDefinitions.ts

  • New LogId.toolBlockedNoElicitation (1_003_005) for audit-trail visibility into MCP06 refusals.

src/lib.ts and src/web.ts

  • ConfirmationResult is now exported from both entry points so embedders that wrap requestConfirmation see the same type the server uses.

Tests:

  • tests/unit/elicitation.test.ts: every assertion against requestConfirmation now compares against the tagged shape. New test explicitly verifies the OWASP MCP06 fail-closed behaviour.
  • tests/unit/toolBase.test.ts: verifyConfirmed return-type updated; two new tests cover the 'declined' and 'no-elicitation-support' propagation paths.
  • tests/unit/toolContext.test.ts and the streams unit-test mocks updated to resolve { ok: true } instead of bare booleans.

api-extractor reports regenerated.

BREAKING CHANGE: Elicitation.requestConfirmation() return type changes from Promise to Promise. Downstream code that awaited the old boolean must switch to inspecting .ok (and optionally .reason). Likewise ToolBase.verifyConfirmed() now returns ConfirmationResult.

Operationally: MCP clients that DON'T advertise the elicitation capability will see refusals when invoking the eight tools currently in confirmationRequiredTools (drop-database, drop-collection, delete-many, drop-index, atlas-create-access-list, atlas-create-db-user, atlas-streams-manage,
atlas-streams-teardown). Clients that DO advertise the capability are unaffected. The error message points operators at the resolution paths (upgrade the client, or remove the tool from the list).

Proposed changes

Checklist

Per OWASP MCP Top 10 (2025) item MCP06 - Intent Flow Subversion.
Tools listed in `confirmationRequiredTools` no longer execute silently
against MCP clients that don't advertise the `elicitation` capability;
they refuse with a structured error that names the missing capability.

Before this change, Elicitation.requestConfirmation() returned a bare
`true` whenever the client didn't support elicitation, which meant a
confirmation-gated operation (drop-database, atlas-create-db-user,
etc.) would proceed against an older client without the user ever
seeing a confirmation prompt. The user never had the chance to
consent, so silently proceeding is a policy violation: no consent, no
execute.

Changes:

src/elicitation.ts
- requestConfirmation() return type changes from `Promise<boolean>` to
  a tagged union ConfirmationResult: `{ ok: true } | { ok: false;
  reason: 'declined' | 'no-elicitation-support' }`. The new type is
  exported so callers can distinguish "user said no" from "client
  cannot ask". Both block execution; the two cases want different
  error messages and different audit-log severities.

src/tools/tool.ts
- ToolBase.verifyConfirmed() now returns ConfirmationResult.
- ToolBase.invoke() inspects the reason. For 'no-elicitation-support'
  it logs a warning via the new LogId.toolBlockedNoElicitation and
  returns an actionable error telling the user/operator to either use
  an elicitation-capable client or remove the tool from
  confirmationRequiredTools. For 'declined' it preserves the previous
  user-decline message and debug-level log.

src/common/logging/loggingDefinitions.ts
- New LogId.toolBlockedNoElicitation (1_003_005) for audit-trail
  visibility into MCP06 refusals.

src/lib.ts and src/web.ts
- ConfirmationResult is now exported from both entry points so
  embedders that wrap requestConfirmation see the same type the
  server uses.

Tests:
- tests/unit/elicitation.test.ts: every assertion against
  requestConfirmation now compares against the tagged shape. New test
  explicitly verifies the OWASP MCP06 fail-closed behaviour.
- tests/unit/toolBase.test.ts: verifyConfirmed return-type updated;
  two new tests cover the 'declined' and 'no-elicitation-support'
  propagation paths.
- tests/unit/toolContext.test.ts and the streams unit-test mocks
  updated to resolve `{ ok: true }` instead of bare booleans.

api-extractor reports regenerated.

BREAKING CHANGE: Elicitation.requestConfirmation() return type changes
from Promise<boolean> to Promise<ConfirmationResult>. Downstream code
that awaited the old boolean must switch to inspecting `.ok` (and
optionally `.reason`). Likewise ToolBase.verifyConfirmed() now returns
ConfirmationResult.

Operationally: MCP clients that DON'T advertise the `elicitation`
capability will see refusals when invoking the eight tools currently
in `confirmationRequiredTools` (drop-database, drop-collection,
delete-many, drop-index, atlas-create-access-list,
atlas-create-db-user, atlas-streams-manage,
atlas-streams-teardown). Clients that DO advertise the capability are
unaffected. The error message points operators at the resolution
paths (upgrade the client, or remove the tool from the list).
@manjunathshiva manjunathshiva requested a review from a team as a code owner May 27, 2026 09:08
@manjunathshiva manjunathshiva requested review from fmenezes and removed request for a team May 27, 2026 09:08
@fmenezes

Copy link
Copy Markdown
Collaborator

LGTM

@fmenezes

Copy link
Copy Markdown
Collaborator

tests are failing, make sure to adjust CI

@fmenezes fmenezes added the no-title-validation Add this label to disable the title check for this PR. label May 27, 2026

@nirinchev nirinchev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced we want to go that route. While I understand the intention, it will be a breaking behavior for many users with clients that don't support elicitation. We'll have to discuss that internally to decide whether the benefits outweigh the disruptiveness.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-pr-activity no-title-validation Add this label to disable the title check for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants