MCP API Key Authentication

This flow uses pre-issued API keys (created in Marvin's UI) to authenticate with the MCP server. No browser login required — exchange your credentials for a token and start making MCP calls.


Step 1: Create an API key (one time only)#

Go to Settings → MCP in the Marvin web app and click Create token.

You'll get two values:

ValueExampleKeep secret?
Client IDcid-mrv_20672c99d4a192fb...No — this is public
Secret Keysk-mrv_443c348c0fc307fa...Yes — shown once, save immediately
Save your secret key

The secret key is shown only once. If you lose it, revoke the key and create a new one.


Step 2: Exchange credentials for an access token#

bash
curl -X POST https://app.heymarvin.com/api/v1/oauth/token \
  -d "grant_type=client_credentials" \
  -d "client_id=<YOUR_CLIENT_ID>" \
  -d "client_secret=<YOUR_CLIENT_SECRET>" \
  -d "scope=mcp:read" \
  -d "resource=https://mcp.heymarvin.com"

You'll get back:

json
{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "bearer",
  "expires_in": 3600,
  "scope": "mcp:read"
}

The access_token is a signed JWT valid for 1 hour. Exchange again when it expires.

Error responses:

StatusErrorCause
400invalid_requestMissing client_id or client_secret
401invalid_clientWrong secret, revoked key, expired key, or suspended user
400invalid_scopeRequested a scope the key doesn't have
429rate_limitedToo many exchanges (limit: 60/min per key)

Step 3: Use the token with the MCP server#

The MCP server speaks JSON-RPC over Streamable HTTP. All requests use the same pattern — POST to / with your Bearer token and a JSON-RPC body. The three headers below are always the same; only the -d body changes.

MCP server: https://mcp.heymarvin.com/

Initialize (verify your token works):

bash
curl -X POST https://mcp.heymarvin.com/ \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize",
       "params":{"protocolVersion":"2025-03-26",
       "capabilities":{},
       "clientInfo":{"name":"my-app","version":"0.1"}}}'

List available tools:

bash
curl -X POST https://mcp.heymarvin.com/ \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

Call a tool (e.g. list_projects):

bash
curl -X POST https://mcp.heymarvin.com/ \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call",
       "params":{"name":"list_projects","arguments":{}}}'

For MCP client config:

json
{
  "mcpServers": {
    "marvin": {
      "url": "https://mcp.heymarvin.com/",
      "headers": {
        "Authorization": "Bearer <ACCESS_TOKEN>"
      }
    }
  }
}

Token lifecycle#

text
Create key (Settings → MCP)            ← one time
    │
    ▼
Exchange: POST /api/v1/oauth/token      ← every ~1 hour
    │     client_id + client_secret → JWT
    ▼
Use JWT for MCP requests                ← every request
    │     Authorization: Bearer <jwt>
    │
    ├─ Token expires (1 hour)
    │     → Exchange again with same credentials
    │
    ├─ Key revoked by admin
    │     → All requests fail immediately (401)
    │
    └─ Key expired (30–180 days from creation)
          → Exchange fails, create a new key

Quick reference#

SettingValue
Token endpointPOST https://app.heymarvin.com/api/v1/oauth/token
MCP serverhttps://mcp.heymarvin.com/
Grant typeclient_credentials
Required scopemcp:read
Required resource paramhttps://mcp.heymarvin.com
Token lifetime1 hour
Rate limit (token exchange)60/min per key

Troubleshooting#

ProblemFix
401 from MCP serverCheck that your token includes scope=mcp:read and resource=https://mcp.heymarvin.com (or .eu.)
invalid_client on token exchangeSecret is wrong, key is revoked, key has expired, or user is suspended. Create a new key if unsure.
rate_limited on token exchangeYou're exchanging too often. Cache the token and reuse for its full 1-hour lifetime. Check Retry-After header.
Token works but no projects returnedThe key accesses team-shared projects only. Make sure projects are shared with the team in Marvin.
invalid_scopeYou requested a scope the key doesn't have. MCP keys use mcp:read.