Whimful
For developers

Integrate with Whimful's MCP server.

Whimful exposes its accounts, sites, and analytics as a Model Context Protocol server at https://mcp.whimful.com. This guide shows how to register as a client and mint OAuth access tokens on behalf of your users.

The shortcut

Already using an MCP client? Just plug it in.

If you're building on top of an MCP-aware host (Claude, Cursor, custom agents using the MCP SDK), there's nothing to register manually. Point the client at our server URL — it handles discovery, registration, consent, and token exchange for you.

Server URL

https://mcp.whimful.com

The first time a user connects, the client performs RFC 7591 dynamic registration, opens the Whimful consent page in their browser, and stores the resulting access token. Read on if you need to drive the OAuth flow yourself.

Approval required before tokens issue

Anyone can register a client at /oauth/register — that part is open. But Whimful gates which redirect URIs are allowed to mint access tokens. Until your callback URL is on Whimful's allowlist, the consent screen will refuse the request and the token endpoint will return invalid_grant.

Email developers@whimful.com with your client name and the exact redirect URI(s) you need approved. We typically turn these around within a business day.

Custom integration

Drive the OAuth flow yourself.

Whimful implements OAuth 2.1 with the authorization-code + PKCE flow for public clients. The full integration is four steps.

1

Discover server metadata

All endpoints are advertised at the standard well-known URLs (RFC 8414, RFC 9728). Hard-coding them is fine for now, but discovery is the future-proof path.

curl https://mcp.whimful.com/.well-known/oauth-authorization-server

Response

{
  "issuer": "https://mcp.whimful.com",
  "authorization_endpoint": "https://whimful.com/oauth/authorize",
  "token_endpoint": "https://mcp.whimful.com/oauth/token",
  "registration_endpoint": "https://mcp.whimful.com/oauth/register",
  "scopes_supported": ["sites:read", "sites:write", "reports:read"],
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"]
}
2

Register your client

POST to the registration endpoint with the redirect URIs you'll use. We issue a client_id immediately. We don't issue client secrets — every client must be public and use PKCE.

curl -X POST https://mcp.whimful.com/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "Acme Agent",
    "redirect_uris": ["https://app.acme.com/oauth/callback"]
  }'

Response (201)

{
  "client_id": "client_abc123…",
  "client_name": "Acme Agent",
  "redirect_uris": ["https://app.acme.com/oauth/callback"],
  "grant_types": ["authorization_code"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none",
  "client_id_issued_at": 1769990400
}
Redirect URI rules. Must be HTTPS, or http://localhost / http://127.0.0.1 for local development. Matching is exact — register every URI you intend to use. The client_name shows up on the Whimful consent screen, so make it recognisable to your users.
3

Send the user to consent

Generate a PKCE pair, then redirect the user's browser to the authorization endpoint. After they sign in to Whimful and approve, we redirect back to your callback with a one-shot code.

PKCE (RFC 7636)

// 1. Random verifier (43–128 chars, URL-safe)
const code_verifier = base64url(crypto.randomBytes(32));

// 2. SHA-256 hash, base64url-encoded
const code_challenge = base64url(sha256(code_verifier));

Authorization URL

https://whimful.com/oauth/authorize
  ?client_id=client_abc123…
  &redirect_uri=https://app.acme.com/oauth/callback
  &response_type=code
  &code_challenge=<BASE64URL(SHA256(verifier))>
  &code_challenge_method=S256
  &scope=sites:read+reports:read
  &state=<random_state>

Callback

GET https://app.acme.com/oauth/callback?code=…&state=…
Verify state matches what you sent before exchanging the code. Codes are single-use and expire after 10 minutes.
4

Exchange the code for a token

POST to the token endpoint with the code, the original redirect URI, your client ID, and the original PKCE verifier. The body is application/x-www-form-urlencoded.

curl -X POST https://mcp.whimful.com/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=authorization_code" \
  --data-urlencode "code=${CODE}" \
  --data-urlencode "redirect_uri=https://app.acme.com/oauth/callback" \
  --data-urlencode "client_id=client_abc123…" \
  --data-urlencode "code_verifier=${VERIFIER}"

Response (200)

{
  "access_token": "wmcp_…",
  "token_type": "Bearer",
  "expires_in": 31536000,
  "scope": "sites:read reports:read"
}
Tokens are valid for 365 days. Refresh tokens are not yet issued — when a token expires, run the consent flow again. Store the access token like any other secret; it gives full access to the granted scopes for that user.
Using the token

Call MCP tools with the access token.

Once you have an access token, talk to the MCP server like any other Streamable HTTP MCP endpoint. Send the token as a Bearer credential.

Example: list the user's accounts

curl -X POST https://mcp.whimful.com \
  -H "Authorization: Bearer wmcp_…" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": { "name": "list_accounts", "arguments": {} }
  }'

If you're using the official MCP SDK, point a StreamableHTTPClientTransport at https://mcp.whimful.com with an Authorization header. The server is stateless per request, so no session bookkeeping is needed.

Scopes

Three scopes.

Request the narrowest set of scopes your integration needs. The user sees each scope on the consent screen and can deny the whole request, but not individual scopes.

sites:read

List sites and read metadata across the user's accounts.

sites:write

Create sites and run domain verification on behalf of the user.

reports:read

Run pre-defined BigQuery analytics reports against the user's data.

Token lifecycle

Handling expiry & errors.

Errors follow the OAuth 2.1 conventions and arrive as JSON with an `error` and `error_description`.

401 Unauthorized

The bearer token is missing, malformed, expired, or revoked. Drop it and run the consent flow again.

403 Forbidden

The token is valid but the requested tool needs a scope the user didn't grant. Re-run the consent flow with the additional scope in the scope parameter.

400 invalid_grant on /oauth/token

The most common causes are: code already redeemed (single-use), code older than 10 minutes, redirect_uri doesn't match the original /authorize call, PKCE code_verifier doesn't hash to the original challenge, or your redirect URI hasn't been approved yet (see "Approval required" above).

Need a hand integrating?

Email developers@whimful.com with what you're building. We'll help you get the OAuth flow wired up and your first MCP call green.

Get in touch