Connecting MCP Clients to WorkingAgents with OAuth

WorkingAgents implements OAuth 2.1 with PKCE and Dynamic Client Registration. Any compliant MCP client can authenticate users without manual token management. This article covers three connection methods: automatic OAuth flow, manual OAuth, and direct bearer tokens.


How It Works

WorkingAgents publishes its OAuth configuration at:

GET https://workingagents.ai/.well-known/oauth-authorization-server

A compliant MCP client fetches this on first connection, discovers the registration, authorization, and token endpoints, and drives the full flow automatically. The user sees a browser login once. After that, the client holds a bearer token valid for 30 days.


Method 1: Automatic OAuth (Claude Code)

Claude Code supports OAuth natively. Add WorkingAgents to your MCP config:

~/.claude/mcp.json (global) or .mcp.json (project-level):

{
  "mcpServers": {
    "workingagents": {
      "type": "http",
      "url": "https://workingagents.ai/mcp",
      "auth": {
        "type": "oauth",
        "clientName": "Claude Code",
        "redirectUri": "http://localhost:3000/callback"
      }
    }
  }
}

On first use:

  1. Claude Code calls /.well-known/oauth-authorization-server to discover endpoints
  2. It registers itself at /register – no admin action needed
  3. It opens a browser window to https://workingagents.ai/authorize
  4. You log in with your WorkingAgents credentials (or Google)
  5. Claude Code exchanges the authorization code for a bearer token
  6. All subsequent MCP calls use Authorization: Bearer <token>

The token is stored by Claude Code and refreshed when it expires.


Method 2: Manual OAuth Flow

For custom agents or any HTTP client that supports OAuth 2.1.

Step 1: Register the client

POST https://workingagents.ai/register
Content-Type: application/json

{
  "client_name": "my-agent",
  "redirect_uris": ["http://localhost:3000/callback"],
  "grant_types": ["authorization_code"]
}

Response:

{
  "client_id": "wa_abc123...",
  "client_secret": "cs_xyz...",
  "redirect_uris": ["http://localhost:3000/callback"]
}

Step 2: Generate a PKCE pair

import secrets, hashlib, base64

code_verifier = secrets.token_urlsafe(64)
code_challenge = base64.urlsafe_b64encode(
    hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b"=").decode()

Step 3: Redirect the user to authorize

https://workingagents.ai/authorize
  ?client_id=wa_abc123
  &redirect_uri=http://localhost:3000/callback
  &response_type=code
  &state=<random>
  &code_challenge=<SHA256 of verifier>
  &code_challenge_method=S256

The user logs in. WorkingAgents redirects to:

http://localhost:3000/callback?code=<auth_code>&state=<your_state>

Step 4: Exchange the code for a token

POST https://workingagents.ai/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=<auth_code>
&client_id=wa_abc123
&redirect_uri=http://localhost:3000/callback
&code_verifier=<original verifier>

Response:

{
  "access_token": "...",
  "token_type": "bearer",
  "expires_in": 2592000
}

Step 5: Call the MCP server

POST https://workingagents.ai/mcp
Authorization: Bearer <access_token>
Content-Type: application/json

{"jsonrpc": "2.0", "method": "tools/list", "id": 1}

Method 3: Direct Bearer Token (Simplest)

Skip OAuth entirely. Generate a long-lived token directly in IEx on the server:

{:ok, user} = User.find_by_username("james")
secret = Plug.Crypto.KeyGenerator.generate(
  Application.get_env(:mcp, :secret_key_base),
  Application.get_env(:mcp, :cookie_salt),
  length: 32
)
token = Plug.Crypto.MessageEncryptor.encrypt(
  to_string(user.id),
  secret,
  Application.get_env(:mcp, :cookie_salt)
)

Use this token directly in any MCP client config:

Claude Code:

{
  "mcpServers": {
    "workingagents": {
      "type": "http",
      "url": "https://workingagents.ai/mcp",
      "headers": {
        "Authorization": "Bearer <token>"
      }
    }
  }
}

Python agent:

import httpx

headers = {"Authorization": "Bearer <token>"}
response = httpx.post(
    "https://workingagents.ai/mcp",
    headers=headers,
    json={"jsonrpc": "2.0", "method": "tools/list", "id": 1}
)

This token has no expiry mechanism beyond the server’s secret_key_base rotation. Use it for development, automation scripts, and single-user setups. Use OAuth for multi-user deployments.


Which Method to Use

Scenario Method
Claude Code, single user Direct bearer token
Claude Code, multiple users Automatic OAuth
Custom agent, developer-controlled Direct bearer token
Custom agent, end-user login Manual OAuth flow
Third-party tool integration Manual OAuth or bearer token

Permission Model

Regardless of how the client authenticates, the token resolves to a user ID and that user’s permission map. The tools available to an MCP client are exactly the tools the authenticated user is allowed to call. Revoking a user’s permission takes effect on the next tool call – no token reissue required.

An agent calling tools/list sees only the tools its user can access. A user without the workflow permission does not see workflow_create. A user without the Stripe connection permission does not see Stripe tools. The MCP client cannot escalate beyond the authenticated user’s permissions.