Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.particle.pro/llms.txt

Use this file to discover all available pages before exploring further.

The Particle Pro MCP server is an OAuth 2.1 protected resource. There are no API keys, no shared secrets, and no static bearer tokens — every request carries a short-lived JWT minted by the Particle Pro Authorization Server and bound to the MCP resource via its aud claim. This page is for client implementers. If you are using a stock MCP client (Claude Code, Cursor, VS Code, Claude Desktop) the OAuth flow is automatic — see the Quickstart.

At a glance

Protected resourcehttps://mcp.particle.pro
Resource metadatahttps://mcp.particle.pro/.well-known/oauth-protected-resource (and /mcp sub-path)
Authorization Serverhttps://api.particle.pro
AS metadatahttps://api.particle.pro/.well-known/oauth-authorization-server
JWKShttps://api.particle.pro/.well-known/jwks.json
Grant typesauthorization_code, refresh_token
PKCERequired, code_challenge_method=S256 only
Auth methodsnone (public clients), client_secret_basic, client_secret_post
Scopesmcp:read, mcp:write
Access token TTL15 minutes
Refresh token TTL30 days, rotated on every use

Discovery

Start from the resource. Fetch the protected-resource metadata document:
GET /.well-known/oauth-protected-resource HTTP/1.1
Host: mcp.particle.pro
{
  "resource": "https://mcp.particle.pro",
  "authorization_servers": ["https://api.particle.pro"],
  "bearer_methods_supported": ["header"],
  "scopes_supported": ["mcp:read", "mcp:write"],
  "resource_documentation": "https://docs.particle.pro/mcp"
}
Then fetch the Authorization Server metadata from the URL in authorization_servers:
GET /.well-known/oauth-authorization-server HTTP/1.1
Host: api.particle.pro
{
  "issuer": "https://api.particle.pro",
  "authorization_endpoint": "https://api.particle.pro/oauth/authorize",
  "token_endpoint": "https://api.particle.pro/oauth/token",
  "registration_endpoint": "https://api.particle.pro/oauth/register",
  "revocation_endpoint": "https://api.particle.pro/oauth/revoke",
  "jwks_uri": "https://api.particle.pro/.well-known/jwks.json",
  "scopes_supported": ["mcp:read", "mcp:write"],
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none", "client_secret_basic", "client_secret_post"],
  "subject_types_supported": ["public"],
  "client_id_metadata_document_supported": true,
  "service_documentation": "https://docs.particle.pro/mcp/oauth"
}
If a request to the MCP endpoint comes in unauthenticated, the server returns 401 with a WWW-Authenticate header pointing at the resource-metadata document, so a client that doesn’t pre-fetch metadata still discovers the AS:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="mcp", error="invalid_token", resource_metadata="https://mcp.particle.pro/.well-known/oauth-protected-resource"
The human-readable description (e.g. bearer token required, grant revoked or unknown) is in the response body, not the header.

Client registration

You have two ways to register a client.

Dynamic Client Registration (RFC 7591)

The simplest path. POST to the registration endpoint with at least client_name and redirect_uris:
POST /oauth/register HTTP/1.1
Host: api.particle.pro
Content-Type: application/json

{
  "client_name": "Acme Research Agent",
  "client_uri": "https://acme.example",
  "logo_uri": "https://acme.example/logo.png",
  "redirect_uris": ["http://localhost:43217/callback"],
  "token_endpoint_auth_method": "none"
}
Response (RFC 7591 § 3.2):
{
  "client_id": "c_abc123…",
  "client_name": "Acme Research Agent",
  "redirect_uris": ["http://localhost:43217/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}
Defaults applied when omitted:
  • token_endpoint_auth_method: none (i.e. public client, PKCE-only)
  • grant_types: ["authorization_code", "refresh_token"]
  • response_types: ["code"]
For confidential clients (any token_endpoint_auth_method other than none) the response includes client_secret exactly once — store it securely, the AS does not return it again.

Client ID Metadata Document (CIMD)

Agents that already publish a metadata document at an HTTPS URL can skip DCR entirely and pass the URL itself as client_id to /oauth/authorize. The AS fetches the document, validates it, and caches it. The CIMD spec is draft-ietf-oauth-client-id-metadata-document. Use this when your agent runs in many places (you don’t want to register N clients) but has a stable identity (your domain).

Authorization code with PKCE

Generate a high-entropy code_verifier, then derive its S256 challenge:
code_verifier=$(openssl rand -base64 64 | tr -d '\n' | tr '+/' '-_' | tr -d '=' | cut -c1-128)
code_challenge=$(printf "%s" "$code_verifier" | openssl dgst -sha256 -binary | base64 | tr -d '\n' | tr '+/' '-_' | tr -d '=')
Open the authorization URL in a browser:
https://api.particle.pro/oauth/authorize?
  response_type=code&
  client_id=c_abc123…&
  redirect_uri=http%3A%2F%2Flocalhost%3A43217%2Fcallback&
  scope=mcp%3Aread%20mcp%3Awrite&
  state=<random>&
  code_challenge=<code_challenge>&
  code_challenge_method=S256&
  resource=https%3A%2F%2Fmcp.particle.pro
Required parameters:
  • response_type=code (only flow supported)
  • client_id — from registration or CIMD URL
  • redirect_uri — must match a value the client registered
  • code_challenge + code_challenge_method=S256 — PKCE is mandatory
  • resource=https://mcp.particle.pro — RFC 8707 audience binding; the AS rejects any other value
scope is optional. If omitted the AS issues mcp:read mcp:write by default. Naming an unknown scope is rejected (the AS will redirect with error=invalid_scope rather than silently up-scoping). The user signs in (if not already), picks a project, and approves the requested scopes. The AS redirects back to your redirect_uri:
http://localhost:43217/callback?code=auth_xyz…&state=<your-state>
Exchange the code for tokens:
POST /oauth/token HTTP/1.1
Host: api.particle.pro
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=auth_xyz…&
redirect_uri=http%3A%2F%2Flocalhost%3A43217%2Fcallback&
code_verifier=<your-code_verifier>&
client_id=c_abc123…&
resource=https%3A%2F%2Fmcp.particle.pro
For confidential clients, authenticate one of two ways:
  • client_secret_basic — drop client_id from the form body and send client_id:client_secret URL-form-encoded in an HTTP Basic Authorization header.
  • client_secret_post — keep client_id in the form body and add a client_secret field alongside it.
Public clients (token_endpoint_auth_method=none) keep client_id in the form body and send no secret. Response:
{
  "access_token": "eyJhbGciOiJSUzI1NiIs…",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "rt_…",
  "scope": "mcp:read mcp:write"
}

Calling the MCP endpoint

Send the access token as a bearer in the Authorization header. The MCP server only accepts header-borne tokens (bearer_methods_supported: ["header"]) — no query-string tokens, no form-body tokens.
POST /mcp HTTP/1.1
Host: mcp.particle.pro
Authorization: Bearer eyJhbGciOiJSUzI1NiIs…
Content-Type: application/json

{ "jsonrpc": "2.0", "method": "tools/list", "id": 1 }
The resource server validates the token in this order:
  1. Verify the JWT signature against the rotating JWKS at https://api.particle.pro/.well-known/jwks.json.
  2. Confirm iss=https://api.particle.pro, aud=https://mcp.particle.pro, and exp is in the future.
  3. Look up the grant_id claim — the grant must still be active (not revoked, not user-disabled).
  4. Confirm the token’s sub, client_id, and project_id claims match the grant row (defense in depth against a buggy or compromised signer).
Any failure returns 401 with a WWW-Authenticate challenge.

Refresh and rotation

When the access token nears expiry, exchange the refresh token:
POST /oauth/token HTTP/1.1
Host: api.particle.pro
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&
refresh_token=rt_…&
client_id=c_abc123…&
resource=https%3A%2F%2Fmcp.particle.pro
Refresh tokens rotate on every use — the response carries a new refresh token; the old one is consumed. If the AS sees the same refresh token presented twice (replay) or two refreshes race for the same token, it revokes the entire grant chain immediately. Store the latest refresh token atomically; never retry a refresh request with the same token.

Revocation

Either side can revoke a grant:
POST /oauth/revoke HTTP/1.1
Host: api.particle.pro
Content-Type: application/x-www-form-urlencoded

token=rt_…&
client_id=c_abc123…
Users can also revoke connections from https://platform.particle.pro → Connected Applications. Once a grant is revoked, all access tokens issued from it stop validating immediately at the resource server (the grant lookup fails) and the refresh token can no longer be exchanged.

Audience binding (RFC 8707)

Every authorization request and every token request must include resource=https://mcp.particle.pro. The AS rejects mismatches with error=invalid_target. The minted access token carries aud=https://mcp.particle.pro, and the MCP server rejects any token with a different aud — even a valid token issued for some hypothetical other Particle resource would not work here. This is the standard “confused deputy” safeguard.

Scopes

Two scopes today; finer-grained scopes can be added as the surface grows:
ScopeGranted access
mcp:readRead your Particle Pro data via MCP tools.
mcp:writeTake actions on your behalf in Particle Pro via MCP tools. (All current tools are read-only — mcp:write is reserved for future write tools.)
Today every tool advertises readOnlyHint: true; mcp:write is requested by default but does not unlock additional surface yet.

Key rotation

The AS uses RS256 with a rotating JWKS. The signer publishes the “next” key well before promoting it to “active” and keeps “retired” keys live long enough for outstanding tokens to expire (15-minute access-token TTL). Validators cache the JWKS for up to 5 minutes — a fresh kid will be picked up within that window.

See also

  • Quickstart — install snippets for stock MCP clients.
  • Errors — how tool errors surface (different from REST application/problem+json).
  • auth_required — REST-side auth error code.