MCP Server API
The Nucleus MCP server is a standalone Cloudflare Worker at mcp.nucleus.fast. It implements the Model Context Protocol Streamable HTTP transport and OAuth 2.0 with PKCE.
See the MCP Setup guide for connection instructions.
Architecture
Section titled “Architecture”AI Client (Claude Desktop, Claude Code, Cursor…) │ OAuth 2.0 / PKCE ▼MCP Worker (mcp.nucleus.fast) — apps/mcp/ │ CF Access JWT forwarded on every call ▼Nucleus API (app.nucleus.fast/api/*) — apps/app/worker/ │ Permission checks + business logic ▼Cloudflare D1The MCP Worker never touches D1 directly. All permission enforcement is in the existing API layer.
Transport: WebStandardStreamableHTTPServerTransport (stateless). Each request creates a fresh McpServer + transport — no Durable Objects needed.
Auth: OAuth 2.0 with PKCE. The bearer token maps to a CF Access JWT stored in KV. Every API proxy call forwards that JWT as Cf-Access-Jwt-Assertion.
Workspace
Section titled “Workspace”| Property | Value |
|---|---|
| Package | @nucleus/mcp |
| Path | apps/mcp/ |
| Deployed URL | https://mcp.nucleus.fast |
| Dev port | 8790 |
OAuth Endpoints
Section titled “OAuth Endpoints”All OAuth endpoints are public (no CF Access policy on the domain — auth is handled in-band by the /oauth/authorize flow).
Discovery
Section titled “Discovery”GET /.well-known/oauth-authorization-serverGET /.well-known/oauth-protected-resourceRFC 8414 OAuth server metadata. Clients use these to discover auth URLs automatically.
Dynamic Client Registration
Section titled “Dynamic Client Registration”POST /oauth/clientsContent-Type: application/json
{ "client_name": "Claude Desktop", "redirect_uris": ["claude://mcp/auth/callback"]}Response 201:
{ "client_id": "<generated>", "client_id_issued_at": 1234567890, "redirect_uris": ["claude://mcp/auth/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "none"}Authorization
Section titled “Authorization”GET /oauth/authorize ?response_type=code &client_id=<id> &redirect_uri=<uri> &code_challenge=<S256_challenge> &code_challenge_method=S256 &state=<state>Checks for a CF_Authorization cookie. If missing, redirects to:
https://nucleusfast.cloudflareaccess.com/cdn-cgi/access/login/mcp.nucleus.fast ?redirect_url=<original_authorize_url>After Google SSO, CF Access redirects back with the JWT cookie set. The worker then:
- Validates the JWT
- Generates a one-time auth code (60s TTL in KV)
- Redirects to
redirect_uri?code=<code>&state=<state>
Token Exchange
Section titled “Token Exchange”POST /oauth/tokenContent-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=<code>&code_verifier=<pkce_verifier>&redirect_uri=<uri>&client_id=<id>Validates PKCE (SHA-256(code_verifier) == stored code_challenge), consumes the code, returns:
{ "access_token": "<token>", "token_type": "Bearer", "expires_in": 28800, "refresh_token": "<token>"}Refresh:
POST /oauth/token
grant_type=refresh_token&refresh_token=<token>Returns a new access_token.
Token Lifetimes
Section titled “Token Lifetimes”| Token | TTL | KV key |
|---|---|---|
| Auth code | 60 seconds | auth_code:<code> |
| Access token | 8 hours | token:<token> |
| Refresh token | 30 days | refresh:<token> |
MCP Endpoint
Section titled “MCP Endpoint”POST /mcpAuthorization: Bearer <access_token>Content-Type: application/jsonAccept: application/json, text/event-stream- Validates the bearer token → KV lookup → CF Access JWT
- Creates
McpServerwith all 120+ tools registered - Connects to
WebStandardStreamableHTTPServerTransport(stateless) - Returns MCP response (JSON or SSE stream)
Supports both streaming (Accept: text/event-stream) and non-streaming responses.
Query Endpoint
Section titled “Query Endpoint”The query_nucleus MCP tool calls this endpoint on the main app:
POST /api/queryAuthorization: Bearer <CF Access JWT> (or Cf-Access-Jwt-Assertion header)Content-Type: application/json
{ "question": "which projects ran over budget last quarter?"}Response:
{ "sql": "SELECT p.name, b.total_budget, SUM(te.hours * st.rate) AS actual_cost ...", "results": [...], "count": 12}Errors:
| Status | Meaning |
|---|---|
400 { error: "Generated query is not a SELECT statement" } | AI produced a non-SELECT (safety rejection) |
400 { error: "Query execution failed" } | SQL was valid but failed at runtime (schema mismatch, etc.) |
502 { error: "AI query generation failed" } | Workers AI / Kimi K2.5 unavailable |
How it works:
- Fetches compact schema from
sqlite_masterat request time - Builds a system prompt with schema + user context (user_id, person_id, access_level)
- Calls
@cf/moonshotai/kimi-k2.5via Workers AI binding - Extracts SQL from response (strips markdown fences)
- Validates SELECT-only, injects
LIMIT 200if absent - Executes against D1, returns
{ sql, results, count }
KV Namespace
Section titled “KV Namespace”Binding: MCP_SESSIONS
| Key pattern | Value | TTL |
|---|---|---|
auth_code:<code> | AuthCode JSON | 60s |
token:<token> | TokenPayload JSON | 8h |
refresh:<token> | TokenPayload JSON | 30d |
client:<id> | RegisteredClient JSON | none |
Deployment
Section titled “Deployment”Create KV namespace
Section titled “Create KV namespace”cd apps/mcpnpx wrangler kv namespace create MCP_SESSIONSUpdate wrangler.toml with the returned namespace ID (replace placeholder-create-via-wrangler).
Set secrets
Section titled “Set secrets”npx wrangler secret put CF_ACCESS_AUDGet the AUD value from the Cloudflare Access application for mcp.nucleus.fast (Zero Trust dashboard → Access → Applications → mcp.nucleus.fast → Overview).
Deploy
Section titled “Deploy”pnpm --filter @nucleus/mcp deploy# or from apps/mcp/pnpm deployLocal dev
Section titled “Local dev”pnpm --filter @nucleus/mcp dev# Worker at http://localhost:8790Note: CF Access JWT validation requires a live CF Access team domain. For local testing, temporarily bypass JWT validation with a DEV_MODE check or test against a staging deployment.
Environment Variables
Section titled “Environment Variables”| Variable | Value | Description |
|---|---|---|
NUCLEUS_API_URL | https://app.nucleus.fast | Base URL for all API proxy calls |
CF_ACCESS_TEAM_DOMAIN | nucleusfast | CF Access team domain |
CF_ACCESS_AUD | <secret> | CF Access application AUD for mcp.nucleus.fast |
MCP_SERVER_URL | https://mcp.nucleus.fast | Public MCP server URL (used in OAuth metadata) |
File Structure
Section titled “File Structure”apps/mcp/ wrangler.toml src/ index.ts # Hono entry: OAuth + MCP endpoint types.ts # Env bindings, TokenPayload, AuthCode auth/ cf-access.ts # CF Access JWT validation session.ts # KV helpers (codes, tokens, clients) authorize.ts # GET /oauth/authorize token.ts # POST /oauth/token mcp/ server.ts # createNucleusMcp() — registers all tools proxy.ts # nucleusApi() / apiToolCall() helpers tools/ # 46 tool modules (~120 tools total) me.ts # get_me, get_dashboard, get_notifications, query_nucleus search.ts tasks.ts … (43 more files) resources/ config.ts # 7 MCP resources (statuses, priorities, etc.)