Skip to content

Add remote MCP server on Cloudflare Workers (mcp-worker) - #415

Closed
MH4GF wants to merge 4 commits into
mainfrom
worktree-tq-mcp-remote-plan
Closed

Add remote MCP server on Cloudflare Workers (mcp-worker)#415
MH4GF wants to merge 4 commits into
mainfrom
worktree-tq-mcp-remote-plan

Conversation

@MH4GF

@MH4GF MH4GF commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Summary

Add a remote MCP server (mcp-worker/) that runs on Cloudflare Workers and exposes tq data over MCP Streamable HTTP, so Claude.ai (Web), Claude Desktop, and Claude Code can read the tq Turso database.

v1 scope is deliberately minimal: a single read-only tool (list_projects) to establish the server architecture before adding more tools.

Architecture

Modeled on sentry-mcp (packages/mcp-cloudflare) and the Cloudflare remote-mcp-server guide:

  • @cloudflare/workers-oauth-provider wraps the worker: /mcp is the protected API route, everything else falls through to a Hono app (/authorize, /callback, /health)
  • GitHub OAuth upstream: /authorize redirects to GitHub carrying the parsed AuthRequest in the state param; /callback exchanges the code, fetches the login, and completes the grant
  • Authorization is a single-user allowlist (login === "MH4GF"), enforced both at the OAuth callback (no grant is created for other users) and per-request in the MCP handler
  • agents/mcp createMcpHandler serves MCP statelessly: no Durable Objects; only OAUTH_KV for the provider's grant storage
  • Tools are declared with a defineTool helper (zod input schema + handler receiving { db, props }); the MCP SDK validates inputs through the schema (defaults included) before invoking the handler
  • @libsql/client/web reads the same Turso DB the local tq CLI uses; dispatch and all writes stay local

Go side is untouched.

Tests

  • bun run test — 16 tests: list_projects against in-memory libsql (empty/single/rendering/order+limit), allowlist, GitHub OAuth helpers with stubbed fetch
  • bun run typecheck — clean
  • wrangler deploy --dry-run — bundles cleanly (no DO binding errors)
  • Tests run in plain Node because agents/mcp imports cloudflare:* modules and cannot load outside workerd
  • CI: new mcp-worker job (bun install / typecheck / vitest), bun pinned to 1.3.5

Known v1 limitations (documented in mcp-worker/README.md)

  • No consent screen on /authorize + open dynamic client registration: a crafted authorize link could route a grant to an attacker-registered client if the allowlisted user completes the GitHub flow. Bounded by the allowlist (owner-only) and the read-only tool surface. Planned fix: approval dialog (Cloudflare demo pattern) before write tools land.
  • Tool errors propagate unsanitized to the (owner-only) MCP client.

Deploy & E2E

Deploy steps in mcp-worker/README.md (GitHub OAuth App, wrangler kv namespace create OAUTH_KV, 4 secrets, bun run deploy). Client setup in docs/mcp.md. Manual E2E (MCP Inspector / Claude Desktop / Claude.ai against wrangler dev) is post-merge verification since it needs GitHub OAuth App credentials.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Remote MCP server deployable to Cloudflare Workers with GitHub OAuth and a single read-only MCP tool to list projects
  • Documentation
    • Added docs and README with deployment, OAuth setup, and local development instructions
  • Tests
    • Added test suites for OAuth helpers, state signing, allowlist, and project-listing behavior
  • Chores
    • CI workflow job added to build, typecheck, and run tests for the MCP worker

MH4GF and others added 2 commits June 11, 2026 12:00
Expose tq data over MCP Streamable HTTP so Claude.ai (Web), Claude
Desktop, and Claude Code can read the tq Turso database. v1 ships a
single read-only tool: list_projects.

Architecture (modeled on sentry-mcp's packages/mcp-cloudflare and the
Cloudflare remote-mcp-server guide):

- @cloudflare/workers-oauth-provider wraps the worker; /mcp is the
  protected API route, everything else falls through to a Hono app
- GitHub OAuth upstream: /authorize redirects to GitHub carrying the
  parsed AuthRequest in the state param; /callback exchanges the code,
  fetches the login, and completes the grant
- Authorization is a single-user allowlist (login === "MH4GF"),
  enforced both at the OAuth callback (no grant is created for other
  users) and per-request in the MCP handler
- agents/mcp createMcpHandler serves MCP statelessly: no Durable
  Objects, no KV beyond OAUTH_KV (grant storage for the provider)
- Tools are declared with a defineTool helper (zod input schema +
  handler receiving { db, props }) and registered via a generic
  registerTool wrapper; the ToolCallback cast is needed because the
  SDK's conditional callback type stays deferred for generic shapes
- libsql client (@libsql/client/web) reads the same Turso DB the
  local tq CLI uses; the dispatch loop and all writes stay local

Tests run in plain Node (agents/mcp imports cloudflare:* modules and
cannot load outside workerd): list_projects against in-memory libsql,
allowlist, and GitHub OAuth helpers with stubbed fetch. CI gets an
mcp-worker job (bun install / typecheck / vitest). The setup-bun and
checkout pins in that job have no version comment annotations because
the repo write-guard blocks new comments; the SHAs are v2.2.0 and
v6.0.2 respectively.

Package manager is bun (bun.lock committed). COOKIE_ENCRYPTION_KEY
from the original plan is not needed: that secret belongs to the
demo's approval-dialog cookie flow, which v1 skips by redirecting
straight to GitHub.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- docs/mcp.md described a nonexistent tq migrate subcommand; migrations
  run automatically via PersistentPreRunE on any tq command
- wrangler.jsonc secrets comment listed COOKIE_ENCRYPTION_KEY, which the
  worker never reads (that secret belongs to the approval-dialog cookie
  flow this v1 does not implement)
- Pin bun-version 1.3.5 in CI instead of latest for reproducible runs
- Document known v1 security limitations in mcp-worker/README.md: no
  consent screen on /authorize combined with open dynamic client
  registration, and unsanitized tool errors; both bounded by the
  single-user allowlist and read-only tool surface

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces mcp-worker, a Cloudflare Workers-based remote MCP server that exposes tq database projects to Claude via GitHub OAuth authentication. It includes complete project setup, GitHub OAuth flow, MCP server wiring, a list_projects tool, comprehensive tests, user documentation, and CI integration.

Changes

MCP Worker Implementation

Layer / File(s) Summary
Project setup and configuration
mcp-worker/package.json, mcp-worker/tsconfig.json, mcp-worker/vitest.config.ts, mcp-worker/wrangler.jsonc, mcp-worker/.dev.vars.example, mcp-worker/.gitignore
TypeScript, Vitest, Wrangler, and environment templates configured for the Cloudflare Workers project, with test discovery and strict type checking enabled.
Core type definitions and database client
mcp-worker/src/types.ts, mcp-worker/src/db/client.ts
Env, AppEnv, and Props interfaces define runtime configuration for OAuth KV storage, GitHub credentials, and Turso database connection. LibSQL client factory wraps Turso configuration.
Tool definitions and allowlist
mcp-worker/src/lib/define-tool.ts, mcp-worker/src/mcp/allowlist.ts, mcp-worker/test/allowlist.test.ts
Generic ToolDef interface and defineTool helper provide type-safe tool definitions with Zod-validated inputs. isAllowedLogin predicate enforces a hard-coded allowlist check. Tests verify allowlist membership validation across representative inputs.
GitHub OAuth helpers and tests
mcp-worker/src/oauth/github.ts, mcp-worker/test/oauth.test.ts
Three functions implement GitHub OAuth flow: githubAuthorizeUrl builds authorization URLs, exchangeCodeForToken exchanges auth codes for access tokens, and fetchGithubLogin retrieves the authenticated user login. Tests verify URL construction, token exchange error handling, and Bearer token authorization.
Signed state and approval UI
mcp-worker/src/oauth/state.ts, mcp-worker/src/oauth/approval.ts
HMAC-SHA-256 based state signing and verification with base64url encoding, and an HTML approval dialog renderer that embeds the signed state for the authorization flow.
OAuth authorization flow via Hono app
mcp-worker/src/app.ts
Hono application exposes /health, /authorize, and /callback routes. The /callback route validates code and state, exchanges the code for a GitHub access token, fetches the user login, enforces allowlist checks, and completes OAuth authorization with the provider.
MCP server instantiation and handler wiring
mcp-worker/src/mcp/server.ts, mcp-worker/src/mcp/handler.ts, mcp-worker/src/index.ts
buildServer creates an MCP server with tool capabilities and registers the listProjects tool. mcpHandler enforces login allowlisting and delegates requests to the server. Top-level OAuthProvider wires OAuth flow (/authorize, /token, /register) and routes /mcp requests through mcpHandler.
MCP list_projects tool
mcp-worker/src/mcp/tools/list-projects.ts, mcp-worker/test/list-projects.test.ts
Tool queries the projects table with a validated limit parameter (1–100, default 20), ordered by descending id. Returns markdown-formatted list with project metadata or empty state message. Tests verify empty state, single-project rendering, edge cases for work_dir and dispatch status, and limit enforcement.

Documentation and CI Integration

Layer / File(s) Summary
User documentation for MCP worker
mcp-worker/README.md, docs/mcp.md
README covers architecture, OAuth/allowlist model, local dev workflow (OAuth app setup, bun dev, MCP Inspector), and production deployment (KV namespace, secrets, wrangler deploy). Integration guide documents prerequisites, step-by-step setup for Claude.ai Web/Code/Desktop clients, and directs users to README for local development.
CI job for mcp-worker
.github/workflows/ci.yml
New GitHub Actions job checks out the repository, sets up Bun 1.3.5, runs bun install --frozen-lockfile, and executes bun run typecheck and bun run test in the mcp-worker directory.

🐰 A worker in the clouds so bright,
OAuth flows and listings tight,
tq projects now reach Claude's door,
Allowlisted, signed, and tested more!

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Add remote MCP server on Cloudflare Workers (mcp-worker)' directly and clearly describes the main change: adding a new remote MCP server component deployed to Cloudflare Workers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-tq-mcp-remote-plan

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
.github/workflows/ci.yml (1)

48-55: ⚡ Quick win

Consider adding persist-credentials: false to the checkout action.

The static analysis tool zizmor flags that the checkout action does not set persist-credentials: false. While this job does not publish artifacts or require git write access, explicitly disabling credential persistence is a defense-in-depth best practice that prevents potential credential leakage.

🔒 Proposed fix
-      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
+      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
+        with:
+          persist-credentials: false
       - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 48 - 55, Update the actions/checkout
step to explicitly disable credential persistence: in the checkout action
invocation (the line using actions/checkout@de0fac2e...), add the input
persist-credentials: false so the checkout step does not leave Git credentials
available to subsequent steps; ensure the key is placed alongside any existing
inputs for that step in the workflow file.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mcp-worker/src/app.ts`:
- Around line 53-60: The grant props are persisting the GitHub accessToken
unnecessarily; change the call to c.env.OAUTH_PROVIDER.completeAuthorization to
pass only non-secret data (e.g., { login } or an empty props object) instead of
{ login, accessToken }, keep authRequest, userId, metadata and scope as-is, and
then update the Props type definition in types.ts to remove the accessToken
field so the compiler enforces no token is stored in props; ensure any code that
previously read Props.accessToken is updated to obtain tokens from a secure,
ephemeral store or the OAuth provider when needed.

In `@mcp-worker/src/index.ts`:
- Line 12: The configuration currently exposes clientRegistrationEndpoint:
"/register" which enables dynamic client registration; remove or disable this
property in the config (the clientRegistrationEndpoint entry in mcp-worker's
exported config/object) so the /register route is no longer exposed for v1
single-user allowlisted deployments; alternatively set
clientRegistrationEndpoint to null/undefined or remove any registration route
wiring that references clientRegistrationEndpoint to prevent dynamic client
onboarding until explicitly required.

In `@mcp-worker/src/mcp/server.ts`:
- Around line 21-24: The callback currently calls tool.handler(params, ctx)
directly and lets exceptions bubble to clients; wrap the call inside a try/catch
in the callback function (the one cast to ToolCallback<Input>) so any thrown
error is caught, log the original error to your internal logger (use ctx.logger
if available or console.error) and return the same response shape with a generic
failure message (e.g., { content: [{ type: "text", text: "Tool execution failed"
}] }) instead of propagating internal details from tool.handler.

In `@mcp-worker/src/mcp/tools/list-projects.ts`:
- Around line 28-31: The template is interpolating raw DB fields (row.name,
row.work_dir, and similar) into markdown; create and call a small escape
function (e.g., escapeMarkdown) that backslash-escapes markdown-significant
characters like *, _, `, [, and ] and apply it to workDir and row.name (and any
other user-controlled fields used in the template) before composing the template
literal in the map that builds the bullet lines so that the returned string uses
escaped values.

In `@mcp-worker/src/oauth/github.ts`:
- Around line 23-34: Add explicit request timeouts to the outbound fetch calls
to GITHUB_TOKEN_URL and the subsequent user info fetch (GITHUB_USER_URL) by
using an AbortController with a configurable timeout (e.g., 5–10s): create an
AbortController, pass controller.signal into fetch, start a setTimeout that
calls controller.abort() after the timeout, and clear that timer on successful
completion to avoid leaks; apply this pattern to the fetch that posts the token
exchange (the fetch using GITHUB_TOKEN_URL and body with
client_id/client_secret/code) and to the follow-up fetch for user/profile, and
ensure errors from abort are handled/translated into a clear timeout error path.

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 48-55: Update the actions/checkout step to explicitly disable
credential persistence: in the checkout action invocation (the line using
actions/checkout@de0fac2e...), add the input persist-credentials: false so the
checkout step does not leave Git credentials available to subsequent steps;
ensure the key is placed alongside any existing inputs for that step in the
workflow file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1bcd332a-7f96-4b1e-9ae4-2af20e805063

📥 Commits

Reviewing files that changed from the base of the PR and between 25e2c7d and 1540eac.

⛔ Files ignored due to path filters (1)
  • mcp-worker/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (22)
  • .github/workflows/ci.yml
  • docs/mcp.md
  • mcp-worker/.dev.vars.example
  • mcp-worker/.gitignore
  • mcp-worker/README.md
  • mcp-worker/package.json
  • mcp-worker/src/app.ts
  • mcp-worker/src/db/client.ts
  • mcp-worker/src/index.ts
  • mcp-worker/src/lib/define-tool.ts
  • mcp-worker/src/mcp/allowlist.ts
  • mcp-worker/src/mcp/handler.ts
  • mcp-worker/src/mcp/server.ts
  • mcp-worker/src/mcp/tools/list-projects.ts
  • mcp-worker/src/oauth/github.ts
  • mcp-worker/src/types.ts
  • mcp-worker/test/allowlist.test.ts
  • mcp-worker/test/list-projects.test.ts
  • mcp-worker/test/oauth.test.ts
  • mcp-worker/tsconfig.json
  • mcp-worker/vitest.config.ts
  • mcp-worker/wrangler.jsonc

Comment thread mcp-worker/src/app.ts Outdated
Comment thread mcp-worker/src/index.ts
Comment thread mcp-worker/src/mcp/server.ts
Comment thread mcp-worker/src/mcp/tools/list-projects.ts
Comment thread mcp-worker/src/oauth/github.ts
- Props now carries only the GitHub login. The access token was stored
  encrypted in the OAuth grant but no tool reads it, so persisting it
  only enlarged the secret surface in KV. Tools that need the GitHub
  API later can re-add it alongside a re-auth.
- GitHub token-exchange and user fetches now abort after 10s via
  AbortSignal.timeout, so a stalled upstream fails the auth request
  fast instead of hanging until the platform limit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MH4GF

MH4GF commented Jun 11, 2026

Copy link
Copy Markdown
Owner Author

Addressed in f5116b9:

  • Drop accessToken from grant props (app.ts) — applied. Props now carries only login.
  • Fetch timeouts for GitHub calls (oauth/github.ts) — applied via AbortSignal.timeout(10_000) on both fetches.

Skipped with reasons:

  • Remove /register (dynamic client registration) — DCR is how MCP clients (Claude.ai custom connectors, mcp-remote, Claude Code) register themselves per the MCP authorization spec; removing it breaks all client onboarding. The single-user guarantee comes from the allowlist, not from restricting registration.
  • Error boundary with generic message (server.ts) — the only principal who can invoke tools is the allowlisted owner, and seeing the real libsql error aids debugging. Documented as a known v1 limitation in mcp-worker/README.md; will revisit when multi-user lands.
  • Markdown escaping in list_projects output (list-projects.ts) — output is text content consumed by the owner's LLM client over their own data; escaping adds noise without a trust-boundary benefit here.

The previous flow redirected straight to GitHub and auto-completed the
grant in /callback. Combined with open dynamic client registration, an
attacker could register a client with their own redirect URI, lure the
allowlisted user through the GitHub flow, and receive an authorization
code at their redirect — a confused-deputy grant. The allowlist did not
bound this, since the victim is the allowlisted user.

Now /authorize GET renders a consent dialog naming the requesting client
and its redirect URI; the grant is only created after the user submits
/authorize POST. The OAuth state is HMAC-signed with OAUTH_STATE_SECRET
and verified on the POST and again at /callback, so the AuthRequest
cannot be tampered with and the consent POST cannot be forged (the
signature doubles as the CSRF token). The signed-state encoding is
UTF-8-safe, replacing the raw btoa/atob that would throw on non-Latin1
input.

Adds OAUTH_STATE_SECRET to Env, .dev.vars.example, wrangler.jsonc, and
the deploy steps. README/docs security sections rewritten from "known
limitation" to the implemented model.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
mcp-worker/src/oauth/state.ts (1)

53-76: 💤 Low value

Consider wrapping base64urlToBytes in try-catch for malformed input.

If state.slice(dot + 1) contains characters invalid for base64url decoding (e.g., non-ASCII), atob at line 16 will throw. This error would propagate up unhandled, whereas other validation failures return a clear "Malformed state" or "Invalid state signature" message.

The fix ensures consistent error messaging for all malformed input variants.

♻️ Suggested improvement
 export async function verifyState(
   state: string,
   secret: string,
 ): Promise<AuthRequest> {
   const dot = state.indexOf(".");
   if (dot === -1) {
     throw new Error("Malformed state");
   }
   const payload = state.slice(0, dot);
-  const signature = base64urlToBytes(state.slice(dot + 1));
+  let signature: Uint8Array;
+  try {
+    signature = base64urlToBytes(state.slice(dot + 1));
+  } catch {
+    throw new Error("Malformed state");
+  }
   const key = await importKey(secret);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcp-worker/src/oauth/state.ts` around lines 53 - 76, The verifyState function
needs to handle malformed base64url input consistently: wrap calls to
base64urlToBytes (both for signature: state.slice(dot + 1) and for payload
decoding before JSON.parse) in try-catch and rethrow a clear Error("Malformed
state") on any decoding/atob failure; keep the HMAC verification flow
(importKey, crypto.subtle.verify) unchanged and only convert decoding errors to
the same "Malformed state" error so all invalid-state decoding errors are
handled uniformly.
mcp-worker/src/app.ts (1)

40-73: 💤 Low value

Unhandled errors from GitHub calls expose stack traces to the client.

exchangeCodeForToken (line 54) and fetchGithubLogin (line 59) can throw on network failures, timeouts, or unexpected responses. These exceptions propagate as 500 errors with stack traces.

Per PR discussion, this is accepted for v1 since only the allowlisted owner can reach this flow. Noting for awareness when multi-user support is added.

♻️ Optional: wrap external calls for cleaner error responses
+  let accessToken: string;
+  let login: string;
+  try {
-  const accessToken = await exchangeCodeForToken({
+    accessToken = await exchangeCodeForToken({
       clientId: c.env.GITHUB_CLIENT_ID,
       clientSecret: c.env.GITHUB_CLIENT_SECRET,
       code,
     });
-  const login = await fetchGithubLogin(accessToken);
+    login = await fetchGithubLogin(accessToken);
+  } catch {
+    return c.text("GitHub authentication failed", 502);
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcp-worker/src/app.ts` around lines 40 - 73, The GitHub network calls in the
/callback handler (exchangeCodeForToken and fetchGithubLogin) can throw and
currently propagate stack traces to clients; wrap each external call (or both
together) in a try/catch inside the app.get("/callback" handler, log the caught
error details to the server logger (so debugging info is preserved) and return a
generic error response to the client (e.g., c.text("Failed to contact GitHub",
502) or c.text("Authentication service error", 502)) instead of letting the
exception propagate; ensure you reference the existing functions
exchangeCodeForToken and fetchGithubLogin and keep the existing control flow
(isAllowedLogin check and OAUTH_PROVIDER.completeAuthorization) only after
successful, caught calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@mcp-worker/src/app.ts`:
- Around line 40-73: The GitHub network calls in the /callback handler
(exchangeCodeForToken and fetchGithubLogin) can throw and currently propagate
stack traces to clients; wrap each external call (or both together) in a
try/catch inside the app.get("/callback" handler, log the caught error details
to the server logger (so debugging info is preserved) and return a generic error
response to the client (e.g., c.text("Failed to contact GitHub", 502) or
c.text("Authentication service error", 502)) instead of letting the exception
propagate; ensure you reference the existing functions exchangeCodeForToken and
fetchGithubLogin and keep the existing control flow (isAllowedLogin check and
OAUTH_PROVIDER.completeAuthorization) only after successful, caught calls.

In `@mcp-worker/src/oauth/state.ts`:
- Around line 53-76: The verifyState function needs to handle malformed
base64url input consistently: wrap calls to base64urlToBytes (both for
signature: state.slice(dot + 1) and for payload decoding before JSON.parse) in
try-catch and rethrow a clear Error("Malformed state") on any decoding/atob
failure; keep the HMAC verification flow (importKey, crypto.subtle.verify)
unchanged and only convert decoding errors to the same "Malformed state" error
so all invalid-state decoding errors are handled uniformly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f880057a-3244-4af4-be06-f0800247ce8d

📥 Commits

Reviewing files that changed from the base of the PR and between 1540eac and 67bc60f.

📒 Files selected for processing (11)
  • docs/mcp.md
  • mcp-worker/.dev.vars.example
  • mcp-worker/README.md
  • mcp-worker/src/app.ts
  • mcp-worker/src/oauth/approval.ts
  • mcp-worker/src/oauth/github.ts
  • mcp-worker/src/oauth/state.ts
  • mcp-worker/src/types.ts
  • mcp-worker/test/list-projects.test.ts
  • mcp-worker/test/state.test.ts
  • mcp-worker/wrangler.jsonc
✅ Files skipped from review due to trivial changes (3)
  • mcp-worker/wrangler.jsonc
  • mcp-worker/README.md
  • docs/mcp.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • mcp-worker/test/list-projects.test.ts
  • mcp-worker/.dev.vars.example

@MH4GF MH4GF closed this Jun 18, 2026
@MH4GF
MH4GF deleted the worktree-tq-mcp-remote-plan branch June 18, 2026 06:37
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