Skip to main content
ReferenceFor developers

Developer reference

Raw OAuth endpoints, well-known URLs, curl-level examples. Useful if you're debugging, writing your own MCP client, or auditing the connector.

Spec compliance

The connector implements the MCP Authorization specification (2025-06-18):

  • OAuth 2.1 (RFC 9700 — BCP on OAuth 2.0 security)
  • PKCE, S256 only (mandatory)
  • RFC 7591 — Dynamic Client Registration
  • RFC 8414 — Authorization Server Metadata
  • RFC 8707 — Resource Indicators (audience binding)
  • RFC 9207 — iss parameter in the authorization response
  • RFC 9728 — OAuth 2.0 Protected Resource Metadata

Canonical URIs

PurposeURL
MCP endpointhttps://mcp.abm.dev/mcp
Resource URI (audience)https://mcp.abm.dev (exact, no trailing slash)
Issuerhttps://mcp.abm.dev
Railway fallbackhttps://mcp-server-production-883e.up.railway.app
Sign-in redirecthttps://abm.dev/sign-in

Discovery endpoints

GET /.well-known/oauth-protected-resourceRFC 9728

Declares this MCP endpoint as a protected resource and points to its authorization servers. Claude fetches this first.

GET /.well-known/oauth-authorization-serverRFC 8414

Authorization server metadata: authorization_endpoint, token_endpoint, registration_endpoint, jwks_uri, supported scopes, grant types, PKCE methods.

GET /.well-known/jwks.jsonJWKS

Public key(s) for verifying access token signatures. RS256.

Try it
curl https://mcp.abm.dev/.well-known/oauth-protected-resource | jq
curl https://mcp.abm.dev/.well-known/oauth-authorization-server | jq
curl https://mcp.abm.dev/.well-known/jwks.json | jq

OAuth endpoints

POST /register

Dynamic Client Registration. Open to anyone — no pre-registration required. Returns a client_id; client_secret is only issued when token_endpoint_auth_method isn't none.

GET /authorize

Consent + auth code issuance. Required query params: response_type=code, client_id, redirect_uri, code_challenge, code_challenge_method=S256, resource=https://mcp.abm.dev. Optional: scope, state.

POST /token

Code/refresh exchange. Supports grant_type=authorization_code (with code_verifier) and grant_type=refresh_token. Returns an RS256 JWT access token (1h) + opaque refresh token (30d).

POST /revoke

Revoke a refresh token. Always returns 200 (even for unknown tokens).

Full OAuth walk-through (curl)

1. Verify 401 behaviour

Unauthenticated request
curl -i -X POST https://mcp.abm.dev/mcp \
  -H 'Content-Type: application/json' \
  -d '{}'

# HTTP/1.1 401 Unauthorized
# WWW-Authenticate: Bearer realm="https://mcp.abm.dev",
#   error="invalid_token",
#   resource_metadata="https://mcp.abm.dev/.well-known/oauth-protected-resource"

2. Register a test client

Dynamic Client Registration
curl -X POST https://mcp.abm.dev/register \
  -H 'Content-Type: application/json' \
  -d '{
    "client_name": "Test Client",
    "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "token_endpoint_auth_method": "none"
  }'

# -> { "client_id": "abm-mcp-...", ... }

3. Generate PKCE pair

PKCE S256
CODE_VERIFIER=$(openssl rand -base64 48 | tr -d '=\n' | tr '/+' '_-')
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" \
  | openssl dgst -sha256 -binary \
  | base64 | tr -d '=\n' | tr '/+' '_-')
echo "verifier=$CODE_VERIFIER"
echo "challenge=$CODE_CHALLENGE"

4. Send the user to /authorize

Open this URL in a browser. Sign in on abm.dev, approve the consent screen, and you'll land on your redirect_uri with a ?code=... query param.

Authorization URL
https://mcp.abm.dev/authorize?\
response_type=code&\
client_id=<client_id>&\
redirect_uri=https://claude.ai/api/mcp/auth_callback&\
code_challenge=$CODE_CHALLENGE&\
code_challenge_method=S256&\
resource=https://mcp.abm.dev&\
scope=abm:read abm:write abm:linkedin abm:enrich abm:generate&\
state=<random>

5. Exchange code for token

Token exchange
curl -X POST https://mcp.abm.dev/token \
  -d "grant_type=authorization_code" \
  -d "code=<code-from-step-4>" \
  -d "code_verifier=$CODE_VERIFIER" \
  -d "client_id=<client_id>" \
  -d "redirect_uri=https://claude.ai/api/mcp/auth_callback" \
  -d "resource=https://mcp.abm.dev"

# -> {
#      "access_token": "eyJ...",
#      "token_type": "Bearer",
#      "expires_in": 3600,
#      "refresh_token": "<opaque>",
#      "scope": "abm:read abm:write abm:linkedin abm:enrich abm:generate"
#    }

6. Call the MCP endpoint

List tools
curl -X POST https://mcp.abm.dev/mcp \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

What this service never does

  • Never passes Claude's bearer token upstream to api.abm.dev. The MCP service holds its own service credentials and derives org context from the JWT's org_id claim.
  • Never issues tokens with a mismatched audience. RFC 8707 resource is validated on both /authorize and /token.
  • Never accepts plain code_challenge_method. S256 only.
  • Never persists Clerk session cookies or LinkedIn cookies on Claude's side. All secrets stay server-side and encrypted at rest.

Architecture

Request flow
  Claude.ai                         mcp.abm.dev                         api.abm.dev
  ─────────                         ────────────                        ────────────
  discover ──▶ /.well-known/*
  register ──▶ POST /register
  authorize ─▶ GET  /authorize ────▶ abm.dev/sign-in (Clerk) ─▶ consent
               POST /authorize ◀── user clicks Allow
  exchange ──▶ POST /token (PKCE)
  call     ──▶ POST /mcp  (Bearer JWT)
                              │
                              │  validate aud=mcp.abm.dev
                              │  load org_id from JWT
                              │
                              ▼
                         AbmApiClient
                              │
                              │  X-Internal-Key + x-org-id
                              │
                              ▼
                                                                ┌─── halo.* ──┐
                                                                │             │
                                                                │   Postgres  │
                                                                │             │
                                                                └─────────────┘

Source & deployment

  • Source: mcp-server/ in the abm.dev-platform monorepo.
  • Runtime: Node 20, Hono HTTP framework. Deployed as a Railway service in the abm-production project.
  • Dockerfile: mcp-server/Dockerfile.http.
  • CI sync: npm run check-routes keeps MCP tool schemas aligned with the ABM.dev Gateway controllers.

Back to the basics