Connect an MCP client
The Caramel MCP server lets AI clients call Caramel tools — list businesses, submit forms, upsert contacts, and more — directly from a chat or coding assistant. The server speaks Streamable HTTP + JSON-RPC 2.0.
| Field | Value |
|---|---|
| Server URL | https://app.caramelme.com/api/functions/caramel-mcp |
| Transport | Streamable HTTP, JSON-RPC 2.0, POST-only |
| Authentication | OAuth 2.0 + PKCE (S256), magic-link login |
| Access token lifetime | 1 hour |
| Refresh token lifetime | 30 days, rotates on use |
Before you begin
Section titled “Before you begin”- Create a Caramel account at caramelme.com. The free Starter tier is enough to test the connection and call read-only tools.
- Confirm your client can handle an OAuth redirect. Claude, Cursor, Windsurf, and Lovable handle this for you. Custom server-side clients need to host a redirect handler.
Connect from Claude
Section titled “Connect from Claude”Claude.ai, Claude Desktop, and Claude Code support MCP natively. No code required.
- Open connector or integration settings in your Claude client.
- Add a new MCP server.
- Enter the URL:
https://app.caramelme.com/api/functions/caramel-mcp - Click Connect. Your browser opens Caramel’s login page.
- Enter your Caramel account email. Caramel sends a magic link.
- Click the link. The browser returns to Claude and the connection is live.
To verify, ask: “What can Caramel do?” or “List my Caramel businesses.” Claude calls caramel.v1.meta.capabilities and lists the available tools.
Note Dynamic Client Registration is pre-approved for Claude.ai and loopback redirect URIs (
http://localhost:<port>/callback). Claude self-registers with no manual step.
Connect from Cursor, Windsurf, or Lovable
Section titled “Connect from Cursor, Windsurf, or Lovable”All three use the same MCP server settings flow.
- Open Workspace Settings → Connectors → Add MCP server.
- Enter the URL:
https://app.caramelme.com/api/functions/caramel-mcp - Click Connect and complete the magic-link OAuth flow.
Once connected, describe what you want in plain language. The assistant writes the JSON-RPC tool calls for you.
Connect a custom client (OAuth by hand)
Section titled “Connect a custom client (OAuth by hand)”Use this path if you’re building your own MCP client or calling the server from server-side code.
Step 1 — Discover OAuth endpoints
Section titled “Step 1 — Discover OAuth endpoints”curl https://app.caramelme.com/.well-known/oauth-authorization-serverResponse (abridged):
{ "issuer": "https://api.caramelme.com", "authorization_endpoint": "https://api.caramelme.com/functions/v1/mcp-oauth", "token_endpoint": "https://api.caramelme.com/functions/v1/mcp-oauth", "registration_endpoint": "https://api.caramelme.com/functions/v1/mcp-oauth/register", "response_types_supported": ["code"], "grant_types_supported": ["authorization_code", "refresh_token"], "code_challenge_methods_supported": ["S256"], "token_endpoint_auth_methods_supported": ["none"]}Step 2 — Register your client (Dynamic Client Registration)
Section titled “Step 2 — Register your client (Dynamic Client Registration)”POST your redirect URI to get a client_id. Skip this step if you were issued one manually.
curl -X POST https://api.caramelme.com/functions/v1/mcp-oauth/register \ -H "Content-Type: application/json" \ -d '{ "client_name": "Your app", "redirect_uris": ["http://localhost:3000/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "none" }'Response (201 Created):
{ "client_id": "claude-caramel", "redirect_uris": ["http://localhost:3000/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "none"}Registration only accepts redirect URIs on the server allow-list: https://claude.ai/api/mcp/auth_callback and http://localhost:<port>/callback. Any other hosted redirect URI returns 400 invalid_redirect_uri — email aymen@reactmotion.com to have it added.
Step 3 — Build the authorize URL (PKCE S256)
Section titled “Step 3 — Build the authorize URL (PKCE S256)”Generate a code verifier and challenge, then redirect the user:
async function buildAuthorizeUrl(clientId, redirectUri, scope) { // Generate verifier — store in server-side session, NOT localStorage const verifier = base64UrlEncode(crypto.getRandomValues(new Uint8Array(32))); const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier)); const challenge = base64UrlEncode(new Uint8Array(hash)); const state = crypto.randomUUID();
session.set('pkce_verifier', verifier); session.set('oauth_state', state);
const url = new URL('https://api.caramelme.com/functions/v1/mcp-oauth'); url.searchParams.set('response_type', 'code'); url.searchParams.set('client_id', clientId); url.searchParams.set('redirect_uri', redirectUri); url.searchParams.set('code_challenge', challenge); url.searchParams.set('code_challenge_method', 'S256'); url.searchParams.set('state', state); url.searchParams.set('scope', scope); return url.toString();}
function base64UrlEncode(bytes) { return btoa(String.fromCharCode(...bytes)) .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');}The user enters their email, receives a magic link, and is redirected to REDIRECT_URI?code=AUTH_CODE&state=….
Important Always verify the returned
statematches what you sent. A mismatch is a CSRF attempt — discard the code.
Step 4 — Exchange the code for tokens
Section titled “Step 4 — Exchange the code for tokens”curl -X POST https://api.caramelme.com/functions/v1/mcp-oauth \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=$AUTH_CODE" \ -d "redirect_uri=$REDIRECT_URI" \ -d "client_id=$CLIENT_ID" \ -d "code_verifier=$VERIFIER"Response:
{ "access_token": "eyJ…", "refresh_token": "rt_…", "expires_in": 3600, "token_type": "Bearer", "scope": "meta:read forms:write audience:write"}Step 5 — Call MCP tools
Section titled “Step 5 — Call MCP tools”The server accepts JSON-RPC 2.0 POST requests. Pass the access token as a Bearer header.
Smoke test — list your businesses:
curl -X POST https://app.caramelme.com/api/functions/caramel-mcp \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "method": "tools/call", "id": 1, "params": { "name": "caramel.v1.business.list", "arguments": {} } }'Discover all available tools:
curl -X POST https://app.caramelme.com/api/functions/caramel-mcp \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "method": "tools/call", "id": 2, "params": { "name": "caramel.v1.meta.capabilities", "arguments": {} } }'Tip Fetch the tool list at runtime via
caramel.v1.meta.capabilitiesrather than hardcoding it. New tools appear automatically.
Step 6 — Refresh before expiry
Section titled “Step 6 — Refresh before expiry”curl -X POST https://api.caramelme.com/functions/v1/mcp-oauth \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token" \ -d "refresh_token=$REFRESH_TOKEN" \ -d "client_id=$CLIENT_ID"Important Refresh tokens rotate on every use. Persist the new
refresh_tokenfrom the response and discard the previous one. Using a stale refresh token invalidates the session — the user must re-authorize.
Scopes
Section titled “Scopes”Request only what you need.
| Scope | What it allows |
|---|---|
meta:read | Capability discovery and usage counters (always granted) |
forms:read | List forms and read form schemas |
forms:write | Submit form data |
audience:read | List and read contacts (shipping soon) |
audience:write | Upsert contacts |
messaging:send | Trigger sends (coming soon) |
provisioning:write | Domain and sender management |
A tool call that needs a scope your token lacks returns 403 scope_required. There is no in-place upgrade — re-run the authorize flow with the additional scope added.
Error reference
Section titled “Error reference”| Signal | Meaning | Action |
|---|---|---|
JSON-RPC error code -32001 | Access token expired | Refresh the token and retry the call once |
HTTP 401 on refresh | Refresh token invalid or revoked | Re-run the full authorize flow |
400 invalid_redirect_uri | URI not on allow-list | Email aymen@reactmotion.com to have it added |
403 tier_required | Business tier too low | Upgrade at caramelme.com/billing |
Some error responses use the misspelled key messsage (three s’s) instead of message. Normalize both in your client:
const message = body.message ?? body.messsage ?? 'Unknown error';Token storage checklist
Section titled “Token storage checklist”- Encrypt refresh tokens at rest. Never log them.
- Store PKCE verifiers and
statein server-side session storage (encrypted cookies, etc.) — notlocalStorage. - Discard verifiers and state parameters after a single use.
- Treat access tokens with the same care as refresh tokens if you persist them.
Next steps
Section titled “Next steps”- Build with Lovable — use the MCP connection inside a Lovable app.
- Connect a ChatGPT Custom GPT — use the REST surface instead of MCP.
- Tool reference — every tool name, parameters, and error codes.
- Rate limits — per-bearer caps and backoff guidance.