Skip to content

Add OAuth 2.1 for Cowork connector support#2

Open
sergical wants to merge 2 commits intomainfrom
feat/oauth-cowork
Open

Add OAuth 2.1 for Cowork connector support#2
sergical wants to merge 2 commits intomainfrom
feat/oauth-cowork

Conversation

@sergical
Copy link
Copy Markdown
Member

Summary

  • Adds /internal as OAuth 2.1-protected MCP endpoint for Claude Desktop/Cowork users
  • Google SSO authentication restricted to @sentry.io emails
  • OAuth users share a team Plausible API key (stored as Cloudflare secret)
  • /mcp remains unchanged for direct Bearer token CLI users (backward compatible)
  • Uses @cloudflare/workers-oauth-provider for OAuth 2.1 server with KV-backed token storage

Architecture

OAuthProvider
├── /.well-known/*       → OAuth metadata (automatic)
├── /authorize           → Redirects to Google SSO
├── /callback            → Validates @sentry.io email, issues grant
├── /token, /register    → Token exchange & client registration (automatic)
├── /internal (OAuth)    → MCP endpoint using shared PLAUSIBLE_API_KEY
└── /mcp (Bearer token)  → MCP endpoint using caller's own API key

Deploy checklist

  • Create KV namespace: wrangler kv namespace create plausible-mcp-oauth
  • Update wrangler.toml KV id with actual namespace ID
  • Add Cloudflare secrets: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, PLAUSIBLE_API_KEY
  • Google Cloud OAuth app configured with redirect URI https://plausible-mcp.sentry.dev/callback

Test plan

  • OAuth metadata endpoints return correct JSON
  • /mcp direct Bearer flow still works (error messages verified)
  • Full OAuth flow tested locally: register client → /authorize → Google SSO → callback → auth code issued
  • End-to-end Cowork connector test after deploy
  • Verify CLI users unaffected

🤖 Generated with Claude Code

sergical and others added 2 commits April 10, 2026 19:23
Adds /internal as an OAuth-protected MCP endpoint for Claude Desktop/Cowork
users, authenticated via Google SSO restricted to @sentry.io emails. Uses
a shared team Plausible API key for OAuth users.

- New src/auth-handler.ts: Google OAuth flow with @sentry.io domain check
- worker.ts: OAuthProvider wraps /internal (OAuth) while /mcp stays as
  direct Bearer token for CLI users (backward compatible)
- wrangler.toml: KV namespace for OAuth state, pinned dev port
- New dependency: @cloudflare/workers-oauth-provider

Deploy requires: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, PLAUSIBLE_API_KEY
secrets and a KV namespace for OAUTH_KV.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wrap internalApiHandler and defaultHandler with Sentry.withSentry()
since OAuthProvider export can't be wrapped directly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sergical sergical marked this pull request as ready for review April 10, 2026 23:32
Comment thread wrangler.toml
# Local dev uses --local which simulates KV automatically
[[kv_namespaces]]
binding = "OAUTH_KV"
id = "PLACEHOLDER_REPLACE_BEFORE_DEPLOY"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: A placeholder KV namespace ID in wrangler.toml combined with non-defensive code in auth-handler.ts will cause runtime crashes, as env.OAUTH_KV will be uninitialized.
Severity: HIGH

Suggested Fix

Replace the placeholder ID in wrangler.toml with a valid KV namespace ID before deployment. As a defensive measure, add a runtime check in auth-handler.ts to verify that env.OAUTH_KV is defined before it is used, and throw a clear error if it is not.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: wrangler.toml#L18

Potential issue: The `wrangler.toml` file contains a placeholder value for the
`OAUTH_KV` namespace ID. The code in `auth-handler.ts` directly accesses `env.OAUTH_KV`
to perform `put`, `get`, and `delete` operations without checking if the binding is
valid or defined. If the worker is deployed with this placeholder, the `env.OAUTH_KV`
binding will likely be invalid or `undefined`. Consequently, any attempt to initiate the
OAuth flow will trigger a runtime error, such as `TypeError: Cannot read property 'put'
of undefined`, when the code tries to access the non-existent KV namespace, breaking the
authentication flow.

Did we get this right? 👍 / 👎 to inform future reviews.

Comment thread src/auth-handler.ts
Comment on lines +106 to +108
if (user.hd !== "sentry.io" || !user.email.endsWith("@sentry.io")) {
return new Response("Access restricted to @sentry.io accounts", {
status: 403,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The authentication logic incorrectly uses an || operator, which will deny access to legitimate users with @sentry.io emails if they use personal Google accounts.
Severity: MEDIUM

Suggested Fix

Change the logical operator from || (OR) to && (AND) in the if condition. A simpler and more robust fix would be to remove the check for user.hd and only validate that user.email.endsWith("@sentry.io") is true.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/auth-handler.ts#L106-L108

Potential issue: The authentication logic at `src/auth-handler.ts:106` incorrectly uses
an `||` (OR) operator to validate a user's domain. The condition `user.hd !==
"sentry.io" || !user.email.endsWith("@sentry.io")` will incorrectly deny access to users
with a valid `@sentry.io` email address if they authenticate using a personal Google
account, because the `user.hd` field will be `undefined`. An `undefined` `hd` field
causes the first part of the condition to evaluate to `true`, leading to an access
denied error for a legitimate user.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a2f4dfa. Configure here.

Comment thread src/worker.ts
defaultSiteId: env.PLAUSIBLE_DEFAULT_SITE_ID,
});

const response = await createMcpHandler(server)(request, env, ctx);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

MCP handler path mismatch causes /internal endpoint 404

High Severity

createMcpHandler(server) defaults to matching the /mcp path, but OAuthProvider routes requests to internalApiHandler at the /internal path (configured via apiRoute: "/internal"). Since /internal doesn't match the default /mcp route, the MCP handler will always return 404 for OAuth-authenticated requests. The createMcpHandler call needs a { route: "/internal" } option to match the actual request path.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a2f4dfa. Configure here.

Comment thread src/worker.ts
const response = await createMcpHandler(server)(request, env, ctx);
return corsResponse(response);
},
} satisfies ExportedHandler<Env>);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

OAuth endpoint missing rate limiting unlike Bearer endpoint

Medium Severity

The internalApiHandler for the OAuth-protected /internal endpoint has no rate limiting, while handleDirectMcp applies per-IP rate limiting via env.RATE_LIMITER. Since /internal uses a shared PLAUSIBLE_API_KEY, unthrottled usage by any authenticated user could exhaust Plausible API rate limits, impacting all OAuth users.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a2f4dfa. Configure here.

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