Your first call
This walkthrough ends with a working list_businesses response in your terminal. It takes about 5 minutes if you have your client ID ready.
Before you begin
Section titled “Before you begin”- A Caramel account at caramelme.com.
- An OAuth client ID. If your redirect URI is
http://localhost:3000/callbackor anotherlocalhostURL, you can self-register in step 2 — no manual setup needed. Otherwise, emailaymen@reactmotion.comwith your app name, redirect URIs, and requested scopes. curlinstalled locally.
Step 1 — Confirm the endpoints
Section titled “Step 1 — Confirm the endpoints”curl -s https://app.caramelme.com/.well-known/oauth-authorization-server | \ python3 -m json.toolYou should see a JSON document with authorization_endpoint, token_endpoint, and registration_endpoint. If this call fails, check your network connection before continuing.
Step 2 — Get a client ID
Section titled “Step 2 — Get a client ID”Skip this step if you already have a client ID issued manually.
Register a client for http://localhost:3000/callback:
curl -s -X POST https://api.caramelme.com/functions/v1/mcp-oauth/register \ -H "Content-Type: application/json" \ -d '{ "client_name": "my-first-app", "redirect_uris": ["http://localhost:3000/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "none" }'Copy the client_id from the response:
{ "client_id": "my-first-app", "redirect_uris": ["http://localhost:3000/callback"]}export CLIENT_ID="my-first-app"export REDIRECT_URI="http://localhost:3000/callback"Step 3 — Generate a PKCE pair
Section titled “Step 3 — Generate a PKCE pair”Run this Node.js snippet to get a verifier and challenge. Keep both — you’ll need them in the next two steps.
// save as pkce.mjs, run with: node pkce.mjsimport crypto from 'node:crypto';
const verifier = crypto.randomBytes(32).toString('base64url');const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');
console.log('VERIFIER: ', verifier);console.log('CHALLENGE:', challenge);node pkce.mjs# VERIFIER: 5rq1oKGcE9...# CHALLENGE: X3Lx8pMkT0...
export VERIFIER="5rq1oKGcE9..."export CHALLENGE="X3Lx8pMkT0..."Alternatively, in Python:
# save as pkce.py, run with: python3 pkce.pyimport secrets, hashlib, base64
verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b'=').decode()challenge = base64.urlsafe_b64encode( hashlib.sha256(verifier.encode()).digest()).rstrip(b'=').decode()
print('VERIFIER: ', verifier)print('CHALLENGE:', challenge)Step 4 — Authorize (get a code)
Section titled “Step 4 — Authorize (get a code)”Open this URL in a browser. Replace the placeholder values with your own:
https://api.caramelme.com/functions/v1/mcp-oauth ?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=http://localhost:3000/callback &code_challenge=YOUR_CHALLENGE &code_challenge_method=S256 &state=any-random-string &scope=meta:readOr build it in the terminal:
STATE=$(openssl rand -hex 16)echo "Open this URL in your browser:"echo "https://api.caramelme.com/functions/v1/mcp-oauth?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&code_challenge=${CHALLENGE}&code_challenge_method=S256&state=${STATE}&scope=meta%3Aread"Enter your Caramel email address. Caramel sends a magic-link email. Click the link.
The browser redirects to http://localhost:3000/callback?code=AUTH_CODE&state=…. Nothing runs at that URL yet — that’s fine. Copy the code parameter from the browser’s address bar.
export AUTH_CODE="the-code-from-the-url"Step 5 — Exchange the code for a token
Section titled “Step 5 — Exchange the code for a token”curl -s -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}" | python3 -m json.toolYou get back:
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh_token": "rt_8f3e21c4b7d94a1f8e2c6b5d4a3f2e1d", "expires_in": 3600, "token_type": "Bearer", "scope": "meta:read"}export TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."Note The authorization code is single-use. If you get
400 invalid_grant, start again at step 4 — the code either expired (codes are short-lived) or was already exchanged.
Step 6 — Call list_businesses
Section titled “Step 6 — Call list_businesses”curl -s -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": {} } }' | python3 -m json.toolYou should see the businesses your account has access to:
{ "jsonrpc": "2.0", "id": 1, "result": { "content": [ { "type": "text", "text": "{\"businesses\":[{\"id\":\"bus_01h7m9k0aabcdefghj1234567\",\"name\":\"Brewmaster Café\",\"tier\":\"growth\",\"role\":\"owner\"}]}" } ] }}Parse the result.content[0].text field — it’s a JSON string that contains the actual payload.
export BUSINESS_ID="bus_01h7m9k0aabcdefghj1234567"That’s it. You now have a working token and a business ID. Every other tool call follows the same shape — change the name and arguments fields.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause | Fix |
|---|---|---|
400 invalid_grant on code exchange | Code expired or already used | Repeat step 4 to get a fresh code |
400 invalid_grant on refresh | Refresh token rotated out | Re-run the full authorize flow |
401 unauthorized on tool call | Access token expired | Run the refresh call; see Authentication |
403 scope_required | Token scope too narrow | Re-authorize with the required scope |
Empty businesses array | No businesses linked to this account | Sign into caramelme.com and confirm at least one business exists |
-32001 JSON-RPC error | Token expired mid-session | Refresh the access token and retry the call |
Next steps
Section titled “Next steps”- Quickstart — the same flow as a one-page reference card.
- Authentication — refresh rotation, scope table, error codes, and edge cases.
- Reference — every tool in the catalog.