OAuth flow
Client identification, PKCE, and tokens.
The Homie MCP server implements OAuth 2.1 with PKCE (S256), the flow recommended by the MCP spec.
Discovery endpoints
GET /.well-known/oauth-authorization-server
GET /.well-known/oauth-protected-resourceThese return RFC 8414 / 9728 metadata. Your client uses them to find endpoint URLs. Our authorization-server metadata advertises client_id_metadata_document_supported: true.
1. Identify your client
Two mechanisms are supported. Prefer Client ID Metadata Documents — Dynamic Client Registration is deprecated as of MCP protocol revision 2026-07-28.
Client ID Metadata Documents (recommended)
Host a JSON document at an HTTPS URL and use that URL as your client_id — no registration call needed, and the same client_id works across authorization servers.
{
"client_id": "https://app.example.com/oauth/client-metadata.json",
"client_name": "My MCP Client",
"redirect_uris": ["http://localhost:5173/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"scope": "mcp:read mcp:write",
"token_endpoint_auth_method": "none"
}Requirements we enforce when resolving the document:
- The URL must use
httpsand include a path component. - The document's
client_idmust exactly equal the URL it's served from. - It must be valid JSON served as
application/json, containing at leastclient_id,client_name, andredirect_uris. - It must not redirect, must be under 64 KB, and must be reachable within 5 seconds.
- The host must resolve to a public address (loopback, private, link-local, and metadata-service addresses are refused).
Every redirect_uri you send in an authorization request is validated against the document's redirect_uris. Documents are cached according to their HTTP cache headers.
Dynamic client registration (deprecated)
Still supported for existing clients, since most MCP clients shipping today rely on it.
POST /oauth/register
Content-Type: application/json
{
"client_name": "My MCP Client",
"redirect_uris": ["http://localhost:5173/callback"]
}Response:
{
"client_id": "mcp_abc...",
"client_name": "My MCP Client",
"redirect_uris": ["http://localhost:5173/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"scope": "mcp:read mcp:write",
"token_endpoint_auth_method": "none"
}Clients are public and don't have a secret. PKCE is the only proof of possession — with either mechanism.
2. Authorize the user
Generate a code verifier + challenge, then redirect:
GET /oauth/authorize?
response_type=code&
client_id=<your client_id or metadata document URL>&
redirect_uri=http://localhost:5173/callback&
code_challenge=<S256 base64url>&
code_challenge_method=S256&
scope=mcp:read%20mcp:write&
state=<csrf>The user sees a consent screen showing your client name, redirect host, and the requested scopes. Request mcp:read for the twelve read tools and add mcp:write for add_prioritized_connection and modify_target_list. A read-only token cannot discover or invoke those write tools. On approve, you receive a code query param at the redirect URI.
3. Exchange the code for tokens
POST /oauth/token
Content-Type: application/json
{
"grant_type": "authorization_code",
"code": "...",
"client_id": "mcp_abc...",
"redirect_uri": "http://localhost:5173/callback",
"code_verifier": "<original verifier>"
}The token endpoint also accepts application/x-www-form-urlencoded bodies.
Response:
{
"access_token": "mcp_...",
"refresh_token": "mcpr_...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "mcp:read mcp:write"
}Protocol revisions
The server speaks both MCP protocol eras on the same /mcp endpoint, so you don't need to know which one your client implements:
2026-07-28(current): per-request version negotiation. Each request carriesio.modelcontextprotocol/protocolVersionin_metaplus matchingMCP-Protocol-VersionandMcp-Methodheaders, andserver/discoverreturns supported versions, capabilities, and usage instructions in one call. Requesting a revision we don't serve returns an error naming the ones we do, so you can retry.2025-11-25and earlier: theinitializehandshake. Still fully served — this is what MCP clients ship with today.
4. Call the MCP server
POST /mcp
Authorization: Bearer mcp_...
Content-Type: application/json
{ "jsonrpc": "2.0", "method": "initialize", "params": { ... }, "id": 1 }Without a token you get 401 Unauthorized with a WWW-Authenticate: Bearer realm="Homie MCP", resource_metadata="..." header pointing to the protected-resource metadata for discovery.
5. Refresh
POST /oauth/token
Content-Type: application/json
{
"grant_type": "refresh_token",
"refresh_token": "mcpr_...",
"client_id": "mcp_abc..."
}Each refresh rotates both the access token and refresh token; replace both stored values atomically. Reusing the previous refresh token fails. Refresh tokens expire after 30 days, and refresh also fails if the signed-in Homie user no longer belongs to the token's organization. Newly issued credentials are stored only as SHA-256 token digests. Pre-existing credentials are upgraded to digest storage when they are next used and otherwise expire through the existing token lifecycle.
Revocation
Admins revoke all tokens for the org from Settings > Integrations > Developer > MCP server > Revoke all connections. Revoked tokens stop working immediately. Removing a user from the organization also revokes that user's MCP tokens and authorization codes.