MCP Integration
Connection Setup

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 integrationAdd MCP server and enter the server URL:

https://pharlo.io/_mcp

Authorize 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
ParameterDescription
client_idFrom step 2
redirect_uriMust exactly match a URI registered in step 2
scopeSpace-separated list; see Available scopes
code_challengeSHA-256 hash of code_verifier, base64url-encoded
code_challenge_methodAlways S256
stateRandom 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.

ScopeWhat it grants
assignments:readRead assignment status and history
assignments:writeCreate, update, retry, and cancel assignments
connections:readList connections and per-channel stats
connections:writeUpdate connection settings
platforms:readRead platform capabilities and payload schemas
webhooks:readRead webhook delivery logs
stats:readRead 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_uri exactly matches the URI registered in step 2 — including trailing slashes.
  • Verify code_verifier produces 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: Bearer header 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 scope parameter.
  • Check Tool Reference to see which scope each tool requires.

CORS errors from Claude.ai

  • Only https://claude.ai and https://claude.com origins 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.