Skip to content

feat(mcp): switch anonymous sign-in nudge to elicitation#2716

Open
fahreddinozcan wants to merge 3 commits into
masterfrom
feat/mcp-auth-elicit
Open

feat(mcp): switch anonymous sign-in nudge to elicitation#2716
fahreddinozcan wants to merge 3 commits into
masterfrom
feat/mcp-auth-elicit

Conversation

@fahreddinozcan
Copy link
Copy Markdown
Contributor

@fahreddinozcan fahreddinozcan commented Jun 3, 2026

Summary

Replaces the in-tool-result sign-in nudge introduced in #2604 with an MCP elicitation/create form request. The previous markdown block instructed the assistant to relay the message to the user and offer to run the setup command, which some agents (notably Claude Code) flagged as prompt injection on the tool result. Elicitations are delivered out-of-band to the client UI, so they bypass that path entirely.

What changes

  • maybeElicitAuthSignIn(server, ctx) is called from resolve-library-id and query-docs after the response is built. The tool result text is now clean (no markdown nudge).
  • Fires only when the backend has set ctx.shouldPrompt via X-Context7-Auth-Prompt: 1 and the caller is anonymous — same trigger condition as before, just a different surface.
  • Gated on getClientCapabilities().elicitation. Clients that don't advertise elicitation see no nudge — safe no-op.
  • The dialog presents a single-select radio with two options:
    • "I'll run the command to sign in" — user copies the npx ctx7 setup ... command from the dialog and runs it in their terminal.
    • "Continue anonymously with smaller limits" — suppresses further nudges for the lifetime of the MCP process, keyed per sessionId / clientIp. Decline / Cancel produce the same suppression.
  • The command itself is shown in the dialog message; the server does not attempt to drive the client to execute it (no agent-side text injection of any kind).
  • Fire-and-forget — wrapped in void ... .catch(...). Never blocks or alters the surrounding tool response.

Deployment dependency

This PR is dormant until the backend re-enables the prompt header. The corresponding one-line flip lives in context7app at lib/api/v2/anonymousSignInPrompt.ts (set ENABLED = true). Without that, ctx.shouldPrompt is never set and maybeElicitAuthSignIn is a no-op.

Known limitations

  • The MCP protocol fixes the response action enum at accept / decline / cancel and gives the client full control over button labels. There is no way for the server to rename Accept/Decline to "Close" or "OK", or to render a single-button dialog (spec ref). The two-option radio mitigates the confusion because the user has already chosen intent before clicking Accept.
  • "Continue anonymously" suppression is in-memory and resets on editor reload (each reload starts a fresh MCP child process). Durable cross-session suppression would require a new backend endpoint — punted as a follow-up.

Test plan

  • pnpm exec tsc --noEmit in packages/mcp — passes
  • pnpm exec vitest run in packages/mcp — 12/12 passes
  • Manual: point Claude Code at a local build with ENABLED = true in the backend, make 5 anonymous calls, confirm elicitation dialog appears with the npx ctx7 setup command and the two-option radio
  • Manual: confirm tool result text contains no markdown nudge
  • Manual: confirm picking "Continue anonymously" + Accept suppresses subsequent dialogs in the same session
  • Manual: confirm Decline / Cancel also suppress subsequent dialogs in the same session

Replace the in-result markdown nudge with an MCP `elicitation/create`
form request. The previous text-injection approach instructed the
assistant to relay the message to the user, which some agents flagged
as prompt injection. Elicitations are delivered out-of-band to the
client UI, bypassing that surface entirely.

- `maybeElicitAuthSignIn` fires after each tool response when the
  backend has set `ctx.shouldPrompt` (via `X-Context7-Auth-Prompt: 1`)
  and the caller is anonymous.
- Gated on the client advertising the `elicitation` capability;
  no-op otherwise.
- Includes a "Don't show this again" checkbox; opting out suppresses
  further nudges for the lifetime of the MCP process, keyed per
  session id / client IP.
- Fire-and-forget: the elicitation never blocks or fails the
  surrounding tool response.
Replace the "Don't show this again" checkbox with a single-select
radio between "I'll run the command to sign in" and "Continue
anonymously with smaller limits". The radio makes the user's intent
explicit and softens the protocol-fixed Accept/Decline labels —
Accept now just submits the choice.

Picking "Continue anonymously" (or declining/cancelling outright)
suppresses further nudges for the lifetime of the MCP process.
The command itself stays in the dialog message for the user to
copy; the server does not attempt to drive the client to execute it.
Switch the elicitation's choice field from `oneOf` with separate
`const`/`title` entries to the simpler `enum: [...]` shape. Cursor's
elicitation UI does not render the `oneOf`-with-titles pattern
correctly — it falls back to a plain text input with the const string
as the default value. The flat enum form is rendered as a proper
dropdown / radio across the clients we tested.

The user-facing strings are now also the enum const values, so the
elicitation response surfaces the chosen label directly. Suppression
logic compares against the same string constants.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant