Docs · MCP
MCP server
A single MCP endpoint any agent runtime can call. Streamable HTTP transport, OAuth 2.1 with dynamic client registration, four tools.
Quick start
The server lives at https://www.finalapproval.ai/mcp. MCP clients that implement OAuth 2.1 — including Claude Code, Cursor, and Codex — will discover it, register themselves, and prompt you to sign in with your FinalApproval account on first use.
For SDK-based agents, point the MCP client library at the URL and let it handle the OAuth handshake. See the MCP SDK docs for the current pattern.
Claude Code
Add the server with the /mcp command inside Claude Code:
/mcp add finalapproval https://www.finalapproval.ai/mcp
Claude will open the consent page in your browser. Grant approvals:write and you're connected.
Cursor
Open Cursor settings → MCP → Add server. Fill in:
- Name:
finalapproval - Transport:
http - URL:
https://www.finalapproval.ai/mcp
Save. Cursor will prompt for OAuth consent on first use.
Codex
Add an entry to your Codex MCP config (location varies by release — check codex config mcp):
{
"mcpServers": {
"finalapproval": {
"url": "https://www.finalapproval.ai/mcp"
}
}
}Custom agents
Use the MCP TypeScript SDK's Streamable HTTP transport:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const client = new Client({ name: "my-agent", version: "0.1.0" });
await client.connect(
new StreamableHTTPClientTransport(new URL("https://www.finalapproval.ai/mcp")),
);
const { content } = await client.callTool({
name: "submit_approval",
arguments: {
channel_id: "…",
agent_id: "my-agent",
title: "Deploy web@1.8.2",
body: "<p>5 commits · schema-compatible</p>",
},
});The SDK handles the OAuth 2.1 flow, including dynamic client registration. Your agent runs the consent redirect in a local browser the first time and caches the refresh token.
Tool reference
submit_approval
Create a pending approval. Returns the approval id.
{
"channel_id": "c9f1…", // UUID of the channel
"agent_id": "deploy-bot", // slug identifying your agent
"title": "string", // ≤ 200 chars
"body": "string (HTML)",// ≤ 50 KB, Tailwind classes supported
"data": { ... } // optional machine-readable payload
}wait_for_resolution
Blocks until the approval is decided. Returns early on resolution; returns {status: "still_pending"} after ~10 minutes so clients can re-invoke without keeping one HTTP connection open indefinitely.
{
"approval_id": "UUID"
}list_pending
Returns pending approvals in the authenticated user's organization. Optionally filter by channel_id.
{
"channel_id": "UUID (optional)",
"limit": 25 // 1–100, defaults to 25
}get_approval
Fetch a single approval's full state — title, body, data, status, resolution metadata.
{
"approval_id": "UUID"
}OAuth and scopes
The server issues tokens via Better Auth's OIDC provider. Metadata is at:
GET https://www.finalapproval.ai/.well-known/oauth-protected-resource GET https://www.finalapproval.ai/api/auth/.well-known/openid-configuration
There is one scope: approvals:write. It covers everything an MCP agent can do in v1 — if we split scopes later, existing tokens stay valid.
Access tokens have a 1-hour expiry and are refreshable. Your MCP client handles rotation automatically.
Rate limits and payload size
- Per-user rate limit: same as the REST API — 60 approvals/min, 5,000/day.
- Body size:
bodyHTML capped at 50 KB. Beyond that, store the payload elsewhere and link to it. - wait_for_resolution: 10-minute server-side cap per call. The MCP client should re-invoke if still pending — SDKs handle this automatically.
Security notes
- Tokens are bound to the user who granted consent. Revoke from your account settings at any time.
- The
bodyHTML is sanitized on render via DOMPurify — scripts, iframes, and inline handlers are stripped before a human ever sees them. - Channels are scoped to the user's active organization. Submitting to a channel in another org returns a generic "Channel not found" so UUID existence stays opaque across tenants.