MCP Connection Setup
Connect the Pharlo MCP server to Claude.ai or any MCP-compatible client.
Prerequisites
A valid Pharlo key (ds_live_...) from Settings → Credentials in the console.
The ds_live_... key is your identity on the consent screen — it is not sent in request headers after authorization. The MCP server issues short-lived OAuth access tokens in exchange for it.
Claude.ai — Quick setup
Claude.ai has native MCP support. No code is required.
Open Integrations
Go to Claude.ai (opens in a new tab) → Settings → Integrations (or Connections, depending on your plan).
Add the MCP server
Click Add integration → Add MCP server and enter the server URL:
https://pharlo.io/_mcpAuthorize with your API key
Claude will open an OAuth consent screen in your browser. Enter your ds_live_... API key and click Authorize.
Start using it
Claude can now use all 17 Pharlo tools in your conversations. Try:
"List my YouTube connections and show subscriber counts."
See Workflows for ready-made prompt patterns.
MCP requests carry a 20% credit surcharge over direct REST API calls. For high-volume automation, use the REST API directly. MCP shines for interactive, conversational workflows.
Other MCP clients — Full OAuth flow
For custom AI agents or non-Claude MCP clients, implement the OAuth 2.1 + PKCE flow below. This is what Claude.ai does for you automatically.
PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks. It replaces a client secret: the client generates a random code_verifier, hashes it to a code_challenge, and later proves ownership by sending the original verifier.
Discover server metadata
Fetch the server's OAuth endpoints and capabilities:
curl https://pharlo.io/.well-known/oauth-authorization-server{
"issuer": "https://pharlo.io",
"authorization_endpoint": "https://pharlo.io/oauth/mcp/authorize",
"token_endpoint": "https://pharlo.io/oauth/mcp/token",
"registration_endpoint": "https://pharlo.io/oauth/mcp/register",
"scopes_supported": [
"assignments:read", "assignments:write",
"connections:read", "connections:write",
"platforms:read", "webhooks:read", "stats:read"
],
"code_challenge_methods_supported": ["S256"],
"grant_types_supported": ["authorization_code", "refresh_token"]
}Register your client
Register once to obtain a client_id. Public clients (SPAs, CLI tools, agents) do not use a client secret — PKCE fills that role.
curl -X POST https://pharlo.io/oauth/mcp/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "My AI Agent",
"redirect_uris": ["https://my-agent.example.com/oauth/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none",
"scope": "assignments:read assignments:write connections:read stats:read"
}'{
"client_id": "mcp_client_abc123",
"client_secret": null,
"redirect_uris": ["https://my-agent.example.com/oauth/callback"]
}Store client_id — you'll reuse it for every authorization request.
Generate a PKCE challenge
Create a fresh code_verifier for every authorization request. Never reuse verifiers.
const crypto = require('crypto');
const codeVerifier = crypto.randomBytes(32).toString('base64url');
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
// Store codeVerifier — you'll need it in step 5
console.log({ codeVerifier, codeChallenge });Build the authorization URL
Redirect the user to the authorization endpoint:
https://pharlo.io/oauth/mcp/authorize
?client_id=mcp_client_abc123
&response_type=code
&redirect_uri=https://my-agent.example.com/oauth/callback
&scope=assignments:read%20assignments:write%20connections:read%20stats:read
&code_challenge=BASE64URL_ENCODED_CHALLENGE
&code_challenge_method=S256
&state=RANDOM_CSRF_TOKEN| Parameter | Description |
|---|---|
client_id | From step 2 |
redirect_uri | Must exactly match a URI registered in step 2 |
scope | Space-separated list; see Available scopes |
code_challenge | SHA-256 hash of code_verifier, base64url-encoded |
code_challenge_method | Always S256 |
state | Random value; verify it on callback to prevent CSRF |
The user sees a consent screen and enters their ds_live_... API key to authorize.
Exchange the code for tokens
After the user authorizes, your redirect_uri receives ?code=...&state=.... Verify state matches, then exchange the code:
curl -X POST https://pharlo.io/oauth/mcp/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code\
&client_id=mcp_client_abc123\
&code=AUTH_CODE_FROM_CALLBACK\
&redirect_uri=https://my-agent.example.com/oauth/callback\
&code_verifier=ORIGINAL_VERIFIER"{
"access_token": "mcp_at_...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "mcp_rt_...",
"scope": "assignments:read assignments:write connections:read stats:read"
}Authorization codes are single-use and expire after a few minutes. If the exchange fails, restart the flow from step 4 — do not retry with the same code.
Store both tokens: access_token for requests (1-hour TTL), refresh_token to renew without re-authorizing (30-day TTL).
Make MCP requests
Pass the access token as a Bearer token on every request:
curl https://pharlo.io/_mcp \
-H "Authorization: Bearer mcp_at_..."For Anthropic SDK–based agents, pass the token via authorization_token:
import anthropic
client = anthropic.Anthropic()
response = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
betas=["mcp-client-2025-04-04"],
mcp_servers=[
{
"type": "url",
"url": "https://pharlo.io/_mcp",
"authorization_token": "mcp_at_your_access_token",
}
],
messages=[
{
"role": "user",
"content": "Show my last 5 assignment statuses.",
}
],
)
for block in response.content:
if hasattr(block, "text"):
print(block.text)Refresh the access token
Access tokens expire after 1 hour. Refresh silently without re-authorizing:
curl -X POST https://pharlo.io/oauth/mcp/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token\
&client_id=mcp_client_abc123\
&refresh_token=mcp_rt_..."The response has the same shape as the initial token exchange, with a new access_token and updated refresh_token. Always store the new refresh token — the old one is invalidated. Refresh tokens expire after 30 days; after that the user must re-authorize.
Available scopes
Request only the scopes your client actually needs.
| Scope | What it grants |
|---|---|
assignments:read | Read assignment status and history |
assignments:write | Create, update, retry, and cancel assignments |
connections:read | List connections and per-channel stats |
connections:write | Update connection settings |
platforms:read | Read platform capabilities and payload schemas |
webhooks:read | Read webhook delivery logs |
stats:read | Read all analytics and aggregated stats |
See Tool Reference to find which tools require which scopes.
Troubleshooting
invalid_grant on token exchange
- Authorization codes are single-use. If already consumed, restart from the authorization step.
- Verify
redirect_uriexactly matches the URI registered in step 2 — including trailing slashes. - Verify
code_verifierproduces the correct SHA-256 hash. A mismatch means the verifier was modified after generating the challenge.
401 on /_mcp requests
- Access token may have expired — use the refresh flow.
- Token may have been revoked — re-authorize from step 4.
- Confirm the
Authorization: Bearerheader is present and the token value has no extra whitespace.
403 insufficient_scope
- The granted scope does not cover the tool being called. Re-authorize with the missing scopes added to the
scopeparameter. - Check Tool Reference to see which scope each tool requires.
CORS errors from Claude.ai
- Only
https://claude.aiandhttps://claude.comorigins are whitelisted. This error does not appear in custom agents that call the MCP server server-side. - Ensure the MCP endpoint is served over HTTPS.