Skip to content

Feature request: forward product_surface to plugin OAuth /authorize endpoints #224

@amekala

Description

@amekala

Context

We're the maintainers of the adspirer-ads-agent plugin (listed in this repo's .claude-plugin/marketplace.json, source: https://github.com/amekala/adspirer-mcp-plugin). Our plugin is a remote MCP server that runs an OAuth 2.1 flow against mcp.adspirer.com to authorize Claude clients on behalf of the user's Adspirer account.

We'd like to provide surface-aware UX for new users coming through the OAuth flow. Specifically, a first-time user installing our plugin from Claude Cowork Desktop, Claude Code Desktop, and Claude.ai Web each warrant a slightly different signup experience (different copy, different CTAs, different attribution tracking). Today we can't distinguish them.

The signal we have today

When a user clicks "Install" on our plugin, the browser receives a Claude.ai-side URL like:

https://claude.ai/api/organizations/<org-id>/mcp/start-auth/<auth-id>
  ?redirect_url=%2Fdesktop%2Fconnected%2Fcustomize%2Fplugins%2Fadspirer-ads-agent%2540knowledge-work-plugins%2Fconnectors%3F
  &open_in_browser=1
  &product_surface=claude-desktop-code

The product_surface=claude-desktop-code (or claude-desktop-cowork, etc.) is exactly the signal we need. The redirect_url path containing /desktop/connected/... is a secondary signal.

What plugin OAuth endpoints actually see

After your /api/.../mcp/start-auth/<id> endpoint processes the request and 302-redirects the browser to the plugin's /oauth/authorize endpoint, the surface signal is stripped. We see:

https://<plugin>/oauth/authorize
  ?response_type=code
  &client_id=<plugin-registered-id>
  &redirect_uri=https://claude.ai/api/mcp/auth_callback
  &code_challenge=<pkce>
  &code_challenge_method=S256
  &session_token=<short-TTL Clerk JWT>
  &scope=<space-separated>
  &state=<opaque to plugin>

No product_surface. No clue about the originating surface. Plugins authoring authorize-time UX cannot distinguish Web from Desktop, or Code-Desktop from Cowork-Desktop.

Proposal

When your server constructs the redirect URL to a plugin's /oauth/authorize, append a query parameter that exposes the surface:

https://<plugin>/oauth/authorize
  ?response_type=code
  &client_id=<plugin-registered-id>
  ... (existing params)
  &product_surface=claude-desktop-code   ← NEW

Plugin authors can opt-in to surface-aware routing. Backwards-compatible (existing plugins that ignore the param continue working unchanged).

Alternative shape (Option B in our internal analysis)

If forwarding product_surface raises concerns (e.g. privacy, or signal-mixing with OAuth-spec params), an equally-good alternative would be: register distinct client_ids per surface (e.g. claude for web, claude-desktop-code for Code Desktop, claude-desktop-cowork for Cowork Desktop). Plugins could then key their behavior off client_id directly. This is more OAuth-spec-native but a bigger change for Anthropic to roll out.

We slightly prefer the product_surface query param because it's additive and doesn't require schema migrations on the plugin side.

Use cases enabled by this

  1. Surface-specific signup funnels. Direct first-time-installing users to context-rich Web pages tuned to their entry path. We've seen this convert measurably better than a cold sign-in form.
  2. Per-surface acquisition tracking. Plugin authors can attribute new users to specific Claude surfaces (Web vs Code Desktop vs Cowork Desktop) for product analytics — same way an ad platform attributes by utm_source.
  3. Per-surface UX tuning. A Desktop installer might benefit from a "you're already connected, just authorize" flow; a Web installer benefits from a "here's how this connects with Claude.ai" flow. Surface signal lets plugins make this distinction.
  4. Better debugging. When a user reports "install failed", surface info in the OAuth flow makes triage faster.

Privacy considerations

We don't believe this raises new privacy concerns — product_surface is non-PII metadata about the Claude UX surface, not about the user. It's directly analogous to the existing User-Agent header field for distinguishing client types.

Internal context (for cross-reference)

We've documented our internal investigation in Adspirer/adstudio#347. The current workaround is a heuristic based on session-token validity + a localStorage flag that survives session expiry. It works ~95% of the time but loses the Web-vs-Desktop distinction and the Code-vs-Cowork-Desktop distinction. Native product_surface forwarding fixes both gaps cleanly.

Related PRs in this repo

Our plugin entry update (#182) was approved by @tobinsouth on Apr 26 and dismissed after a May 10 rebase to clear merge conflict — currently REVIEW_REQUIRED. Independent of this feature request, but flagging in case the same reviewers can take a look.

Effort

Likely a 1–5 line change in the start-auth redirect handler. The signal already exists internally (we can see it in URL 1) — it just needs to be passed through to URL 2.

Happy to discuss further, contribute a PR, or test in a preview environment. Thanks for considering it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions