MCP server

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-resource

These 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.

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 https and include a path component.
  • The document's client_id must exactly equal the URL it's served from.
  • It must be valid JSON served as application/json, containing at least client_id, client_name, and redirect_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 carries io.modelcontextprotocol/protocolVersion in _meta plus matching MCP-Protocol-Version and Mcp-Method headers, and server/discover returns 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-25 and earlier: the initialize handshake. 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.

On this page