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:
| Value | Example | Keep secret? |
|---|---|---|
| Client ID | cid-mrv_20672c99d4a192fb... | No — this is public |
| Secret Key | sk-mrv_443c348c0fc307fa... | Yes — shown once, save immediately |
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#
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:
{
"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:
| Status | Error | Cause |
|---|---|---|
| 400 | invalid_request | Missing client_id or client_secret |
| 401 | invalid_client | Wrong secret, revoked key, expired key, or suspended user |
| 400 | invalid_scope | Requested a scope the key doesn't have |
| 429 | rate_limited | Too 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):
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:
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):
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:
{
"mcpServers": {
"marvin": {
"url": "https://mcp.heymarvin.com/",
"headers": {
"Authorization": "Bearer <ACCESS_TOKEN>"
}
}
}
}
Token lifecycle#
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#
| Setting | Value |
|---|---|
| Token endpoint | POST https://app.heymarvin.com/api/v1/oauth/token |
| MCP server | https://mcp.heymarvin.com/ |
| Grant type | client_credentials |
| Required scope | mcp:read |
Required resource param | https://mcp.heymarvin.com |
| Token lifetime | 1 hour |
| Rate limit (token exchange) | 60/min per key |
Troubleshooting#
| Problem | Fix |
|---|---|
| 401 from MCP server | Check that your token includes scope=mcp:read and resource=https://mcp.heymarvin.com (or .eu.) |
invalid_client on token exchange | Secret is wrong, key is revoked, key has expired, or user is suspended. Create a new key if unsure. |
rate_limited on token exchange | You're exchanging too often. Cache the token and reuse for its full 1-hour lifetime. Check Retry-After header. |
| Token works but no projects returned | The key accesses team-shared projects only. Make sure projects are shared with the team in Marvin. |
invalid_scope | You requested a scope the key doesn't have. MCP keys use mcp:read. |