Build a client

Protocol reference

Transport, discovery, registration and the token exchange, for anyone building or debugging an MCP client.

You do not need this page to use the MCP server from an existing client. It is for people building their own client, or checking why one does not connect.

Endpoints

Everything the server exposes, all on https://api.olee.ai except the approval page:

MethodPathAuthenticationPurpose
POST/mcpBearerMCP messages, JSON-RPC 2.0
GET/mcpNoneA short description of the server
GET/.well-known/oauth-protected-resourceNoneProtected resource metadata, RFC 9728
GET/.well-known/oauth-protected-resource/mcpNoneThe same document, at the path-inserted location
GET/.well-known/oauth-authorization-serverNoneAuthorization server metadata, RFC 8414
POST/oauth/registerNoneDynamic client registration, RFC 7591
POST/oauth/tokenPKCEAuthorization code exchange
GEThttps://oleon.io/mcp-authorizeBrowser sessionWhere a person approves the connection

Transport

The server implements the MCP Streamable HTTP transport in stateless mode.

  • Send every message as a POST to /mcp.
  • Send Content-Type: application/json and Accept: application/json, text/event-stream. A request that does not accept both is refused with 406.
  • No Mcp-Session-Id is issued. Each request is authenticated and answered on its own, so the server never pushes notifications and there is no stream to resume.
  • Replies arrive as a server-sent event stream carrying the JSON-RPC response.

The server advertises the tools capability only, and returns usage instructions for the model in its initialize result.

Initialize
curl https://api.olee.ai/mcp \
  -H "Authorization: Bearer $OLEON_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-06-18",
      "capabilities": {},
      "clientInfo": { "name": "my-client", "version": "1.0.0" }
    }
  }'
Response
event: message
data: {"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{"listChanged":true}},"serverInfo":{"name":"oleon-workspace","version":"0.1.0"},"instructions":"Read access to an Oleon Workspace account…"},"jsonrpc":"2.0","id":1}

Calling a tool follows the same pattern:

Call a tool
curl https://api.olee.ai/mcp \
  -H "Authorization: Bearer $OLEON_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": { "name": "list_orders", "arguments": { "project_slug": "acme-store", "status": "new", "limit": 10 } }
  }'

GET /mcp needs no credential and describes the server:

JSON
{
  "name": "oleon-workspace",
  "version": "0.1.0",
  "transport": "streamable-http",
  "endpoint": "https://api.olee.ai/mcp",
  "note": "POST JSON-RPC here with an Authorization: Bearer header."
}

Versioning and change

Three versions matter here, and they move independently of each other.

VersionWhere it appearsWhat it tells you
Protocol versioninitialize, as protocolVersionThe MCP revision the server speaks, currently 2025-06-18. Send the revision your client implements; the server answers with the one it will use.
Server versioninitialize, as serverInfo.versionWhich build you reached, currently 0.1.0.
Tool settools/listThe tools available to the credential you sent.

Treat tools/list as the source of truth rather than this page. Call it after initialize and work from what comes back. A client that hardcodes a tool list copied out of documentation is a client that breaks the day a tool is added.

What changes without notice

  • A new tool.
  • A new optional argument on an existing tool.
  • A new field in a result, including a new key in _meta.
  • A field you have seen before being absent. Empty and null fields are left out of results entirely, so treat every field as optional and never assume a key is present because it was last time.

Ignore what you do not recognise and none of these reaches you as a break.

What we give notice for

Removing a tool, removing or renaming an argument, changing what an argument means, or changing a default. Account holders are told by email before it takes effect, the same way a material change to the terms is announced.

The server is at 0.1.0 and the tool set is still growing. If you are building something other people will depend on, tell us through support, so a change that affects you reaches you directly rather than as a broadcast.

Authorization

OAuth 2.1 authorization code flow with PKCE, for public clients. There are no client secrets, no scopes and no refresh tokens: the credential issued stands for the person who approved it, reaches what they can reach, and lasts until they revoke it.

1. Discover

A POST /mcp without a valid credential answers 401 with a pointer to the resource metadata:

HTTP
HTTP/2 401
WWW-Authenticate: Bearer resource_metadata="https://api.olee.ai/.well-known/oauth-protected-resource"
GET /.well-known/oauth-protected-resource
{
  "resource": "https://api.olee.ai/mcp",
  "authorization_servers": ["https://api.olee.ai"],
  "bearer_methods_supported": ["header"],
  "resource_name": "oleon-workspace"
}
GET /.well-known/oauth-authorization-server
{
  "issuer": "https://api.olee.ai",
  "authorization_endpoint": "https://oleon.io/mcp-authorize",
  "token_endpoint": "https://api.olee.ai/oauth/token",
  "registration_endpoint": "https://api.olee.ai/oauth/register",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"]
}

The authorization endpoint is on oleon.io, not on the API. That is where people sign in, so passkeys and two-factor authentication work during approval.

2. Register

Terminal
curl https://api.olee.ai/oauth/register \
  -H "Content-Type: application/json" \
  -d '{ "client_name": "My Client", "redirect_uris": ["http://127.0.0.1:33418/callback"] }'
redirect_urisstring[]required

From 1 to 10 addresses. Each must use https, or plain http on a loopback host (localhost, 127.0.0.1 or [::1]). Custom schemes are refused.

client_namestring

Up to 200 characters. Shown to the person on the approval page.

A successful registration answers 201:

JSON
{
  "client_id": "…",
  "redirect_uris": ["http://127.0.0.1:33418/callback"],
  "client_name": "My Client",
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code"],
  "response_types": ["code"]
}

A rejected one answers 400 with invalid_client_metadata or invalid_redirect_uri. Registration is limited to 30 per hour from one address.

3. Send the person to approve

Open the authorization endpoint in the person's browser:

https://oleon.io/mcp-authorize
  ?response_type=code
  &client_id=CLIENT_ID
  &redirect_uri=http%3A%2F%2F127.0.0.1%3A33418%2Fcallback
  &code_challenge=BASE64URL_SHA256_OF_VERIFIER
  &code_challenge_method=S256
  &state=RANDOM_STATE

redirect_uri must match one registered for the client exactly, and code_challenge_method must be S256. If the person is signed out, they sign in first and return to the same request.

OutcomeRedirect
The person presses Connectredirect_uri?code=CODE&state=RANDOM_STATE
The person presses Cancelredirect_uri?error=access_denied&state=RANDOM_STATE

A code can be exchanged once, within five minutes.

4. Exchange the code

Terminal
curl https://api.olee.ai/oauth/token \
  -d grant_type=authorization_code \
  -d code=CODE \
  -d redirect_uri=http://127.0.0.1:33418/callback \
  -d client_id=CLIENT_ID \
  -d code_verifier=VERIFIER

The body may be form encoded, as above, or JSON. A successful exchange answers 200:

JSON
{
  "access_token": "mcp_sk_…",
  "token_type": "Bearer"
}

There is no expires_in and no refresh_token. Send the token as Authorization: Bearer on every POST /mcp. When it stops working, the person revoked it or lost access, and a 401 sends your client back to step 1.

StatusErrorCause
400unsupported_grant_typegrant_type was not authorization_code
400invalid_requestA required field was missing
401invalid_clientThe client_id is not registered
400invalid_grantThe code is unknown, used, expired, or was issued for another client or redirect_uri, or the PKCE verifier does not match

invalid_grant deliberately does not say which of those happened. Token requests are limited to 60 per minute from one address.