BOS Platform · OAuth client reference
Discover BOS. Let the user sign in.
Start with the MCP resource URL https://dfsm.ai/mcp/apps/bos/platform. Public OAuth discovery provides the endpoints and this guide. Your client presents its Connect action; the user follows the OAuth sign-in link and signs in to their BOS account. The client completes the protocol and keeps credentials in its secure credential store.
1. Discover the connection contract
An unauthenticated protected MCP request returns HTTP 401 with a WWW-Authenticate challenge. Follow its resource_metadata URL to the protected-resource metadata, then an authorization_servers issuer to its RFC 8414 authorization-server metadata. Validate the HTTPS URLs, expected issuer and expected resource before using them.
BOS publishes Platform resource metadata and authorization-server metadata. The standard resource_documentation and service_documentation fields reference this page.
| Metadata | Client use |
|---|---|
resource | Confirm the MCP resource and OAuth token audience. |
authorization_servers, issuer | Confirm the trusted authorization server. |
registration_endpoint | Register a public OAuth client when the client has no existing registration. |
authorization_endpoint, token_endpoint, revocation_endpoint | Use the published OAuth endpoints. |
code_challenge_methods_supported, scopes_supported, grant_types_supported, token_endpoint_auth_methods_supported | Use S256, permitted scopes, authorization_code/refresh_token, and public-client authentication. |
OAuth is preferred. For a client that explicitly supports a single API key, see the optional identity API-key contract and client example.
2. Present user-operated OAuth sign-in
Register the exact callback your client owns. An HTTPS callback must be reachable by the user's browser. A native loopback callback works when the browser and callback listener share a machine. BOS redirects to the registered URI; it supplies no remote loopback relay. A client running on separate hosts must provide a browser-reachable callback.
Use response_type=code, the registered client_id and redirect_uri, scopes including mcp:tools, a random state, and PKCE code_challenge with code_challenge_method=S256. Add offline_access for refresh tokens. PKCE, state, and the registered redirect remain required for BOS public clients.
resource is the RFC 8707 audience extension. MCP-aware clients send the discovered resource explicitly. Generic OAuth clients may omit it: authorization defaults to https://dfsm.ai/mcp/apps/bos/platform. An omitted token or refresh resource uses the audience already bound to that grant. An explicit incorrect resource fails validation. Omission selects no organization, application installation or role.
The client validates callback state and issuer before exchanging the code, supplies the original PKCE verifier and exact registered redirect, and consumes the authorization code promptly. The agent surfaces Connect to the user; the user performs sign-in. Keep authorization codes, tokens, verifiers and credentials in the client protocol and secure storage.
3. Minimal client example
This JavaScript illustrates the client protocol. Run it in your client implementation with a securely persisted pending transaction and registered callback. The page is public documentation and executes no authentication code. Endpoint URLs below come from discovery. This generic-client example omits resource to use the BOS Platform default; an MCP-aware implementation sets it to the discovered Platform resource in both requests.
// Client-side protocol primitives; fetch and Web Crypto are standard APIs.
const platformResource = "https://dfsm.ai/mcp/apps/bos/platform";
const b64url = bytes => btoa(String.fromCharCode(...bytes))
.replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
const randomValue = () => b64url(crypto.getRandomValues(new Uint8Array(32)));
const httpsURL = value => {
const url = new URL(value);
if (url.protocol !== "https:" || url.username || url.password || url.hash)
throw new Error("Expected trusted HTTPS metadata URL");
return url;
};
async function jsonResponse(response) {
if (!response.ok) throw new Error(`OAuth HTTP ${response.status}`);
return response.json();
}
async function discover() {
const challenge = await fetch(platformResource, {
method: "POST", headers: {"Content-Type": "application/json"},
body: JSON.stringify({jsonrpc: "2.0", id: 1, method: "initialize",
params: {protocolVersion: "2025-11-25", capabilities: {},
clientInfo: {name: "example-client", version: "1.0"}}})
});
const metadataURL = challenge.headers.get("WWW-Authenticate")
?.match(/resource_metadata="([^"]+)"/)?.[1];
if (challenge.status !== 401 || !metadataURL)
throw new Error("Expected protected-resource discovery challenge");
const trustedOrigin = new URL(platformResource).origin;
const protectedURL = httpsURL(metadataURL);
if (protectedURL.origin !== trustedOrigin) throw new Error("Untrusted resource metadata");
const resource = await jsonResponse(await fetch(protectedURL));
if (resource.resource !== platformResource) throw new Error("Resource mismatch");
const issuer = httpsURL(resource.authorization_servers[0]);
if (issuer.origin !== trustedOrigin || issuer.pathname !== "/")
throw new Error("Unexpected BOS issuer");
const as = await jsonResponse(await fetch(new URL(
"/.well-known/oauth-authorization-server", issuer)));
if (as.issuer !== issuer.href.replace(/\/$/, "")) throw new Error("Issuer mismatch");
for (const key of ["registration_endpoint", "authorization_endpoint", "token_endpoint"])
if (httpsURL(as[key]).origin !== trustedOrigin) throw new Error("Untrusted endpoint");
if (!as.code_challenge_methods_supported.includes("S256"))
throw new Error("S256 PKCE unavailable");
return as;
}
async function prepareConnect(as, redirectURI) {
const registration = await jsonResponse(await fetch(as.registration_endpoint, {
method: "POST", headers: {"Content-Type": "application/json"},
body: JSON.stringify({client_name: "Example OAuth client",
redirect_uris: [redirectURI], token_endpoint_auth_method: "none",
grant_types: ["authorization_code", "refresh_token"], response_types: ["code"]})
}));
const verifier = randomValue();
const challenge = b64url(new Uint8Array(await crypto.subtle.digest(
"SHA-256", new TextEncoder().encode(verifier))));
const pending = {clientID: registration.client_id, redirectURI,
verifier, state: randomValue(), issuer: as.issuer};
const url = new URL(as.authorization_endpoint);
url.search = new URLSearchParams({response_type: "code", client_id: pending.clientID,
redirect_uri: redirectURI, scope: "mcp:tools offline_access", state: pending.state,
code_challenge: challenge, code_challenge_method: "S256"});
// Persist pending privately before displaying url.href in the user's Connect action.
return {pending, authorizationURL: url.href};
}
async function completeConnect(as, pending, callbackURL) {
const callback = new URL(callbackURL);
const target = new URL(pending.redirectURI);
if (callback.origin !== target.origin || callback.pathname !== target.pathname)
throw new Error("Callback target mismatch");
for (const key of ["state", "iss", "code"])
if (callback.searchParams.getAll(key).length !== 1)
throw new Error("Invalid callback parameters");
if (callback.searchParams.get("state") !== pending.state ||
callback.searchParams.get("iss") !== pending.issuer)
throw new Error("Callback state or issuer mismatch");
const tokens = await jsonResponse(await fetch(as.token_endpoint, {
method: "POST", headers: {"Content-Type": "application/x-www-form-urlencoded"},
body: new URLSearchParams({grant_type: "authorization_code",
client_id: pending.clientID, redirect_uri: pending.redirectURI,
code: callback.searchParams.get("code"), code_verifier: pending.verifier})
}));
// Delete pending; store tokens only in the client's secure credential store.
return tokens;
}
4. Discover and use authorized MCP operations
Send the access token as an Authorization: Bearer header to the discovered MCP resource. Initialize using a supported MCP protocol version, send notifications/initialized, and call tools/list. The authenticated server instructions reference this guide.
Follow the live tool schemas. For bos-identity-mcp/v2, call bos_get_context to discover current authorized contexts and bos_list_context_tools with the exact returned handle. That catalog provides operation schemas, service.describe, and readable resource URIs. Invoke an advertised read and read the exact advertised resource URI. BOS rechecks user, organization, application, installation and role authority for every operation.
Refresh through the discovered token endpoint with grant_type=refresh_token, client_id and the privately stored refresh token. Replace stored tokens with the successful response. A revoked, expired or rejected grant follows the client's user-operated OAuth reconnect flow.
Reference standards: OAuth 2.0, PKCE, resource indicators, authorization-server metadata, protected-resource metadata, and MCP authorization.