Agent MCP + MCP Apps OAuth Guide
Set up an AI chat app so users can discover, install, approve, and OAuth-authorize MCP servers — all without leaving the chat. The approval and OAuth flows render as sandboxed iframe resources in MCP Apps-compatible clients — not as HTML stuffed into the tool payload.
Who this is for
Developers building or configuring an AI chat app that supports MCP Apps UI (sandboxed iframe resources, not inline HTML in the tool payload). This guide covers the server-side setup (registering OAuth configs with Pharos) and the user-facing flow (what the user sees in chat). Set PHAROS_MCP_APPS=true.
Install the Pharos MCP Server
The MCP server runs alongside your chat app as a stdio process. One command installs it and auto-configures supported clients.
pip install pharos-mcpFor manual configuration (clients not auto-detected), add this to your client's MCP config:
{
"mcpServers": {
"pharos": {
"command": "pharos-mcp",
"env": {
"PHAROS_MCP_APPS": "true"
}
}
}
}Register OAuth providers
If you publish an MCP server that requires OAuth (Salesforce, Google Drive, Slack, etc.), register your OAuth provider details with the Pharos registry. This lets Pharos handle the OAuth flow inline — the user authorizes without leaving the chat.
# Register your OAuth provider with Pharos
pharos oauth configure my-salesforce-server \
--auth-url https://login.salesforce.com/services/oauth2/authorize \
--client-id 3MVG9...your-client-id \
--scopes api,refresh_token \
--pkce
# With a server-side secret (optional — for confidential clients)
pharos oauth configure my-google-drive-server \
--auth-url https://accounts.google.com/o/oauth2/v2/auth \
--client-id your-client-id.apps.googleusercontent.com \
--scopes openid,email,profile,drive.file \
--pkce \
--client-secret your-client-secretFlags
--auth-url— OAuth authorization endpoint URL (required)--client-id— Your registered client ID (required)--scopes— Comma-separated scopes to request--pkce— Require PKCE (default: true, recommended for public clients)--client-secret— For confidential clients (server-side secret handling)
The user flow in chat
Here is the exact sequence that happens when a user asks your chat app to work with an OAuth-enabled MCP server. The agent chains tool calls; the user only clicks Approve/Deny and Authorize in the sandboxed iframes.
# Step 1: Agent searches (Apps mode — LibreChat)
pharos_search_apps(query="salesforce", limit=5)
→ Compact JSON + ui://pharos/results/{token}
{ "id": "my-salesforce-server", "name": "Salesforce MCP Server", ... }
# Step 2: Agent starts install — does NOT finish until you click
pharos_install_apps(server_id="my-salesforce-server")
→ Returns:
{
"status": "pending_approval",
"approval_token": "ia-my-salesforce-server-1723540800-a3b1c2",
"ui": "ui://pharos/approval/ia-my-salesforce-server-1723540800-a3b1c2"
}
# There is no pharos_connect or pharos_approve MCP tool.The approval iframe
When pharos_install_apps returns pending_approval, the host renders ui://pharos/approval/{token} in a sandboxed iframe (sandbox="allow-scripts", no same-origin). The card shows the package name and Approve / Deny. There is no pharos_approve MCP tool.
┌──────────────────────────────────────────┐ │ PHAROS — Install Approval │ │ │ │ Salesforce MCP Server v1.0.0 │ │ Publisher: io.github.yourusername │ │ │ │ [ Deny ] [ Approve ] │ └──────────────────────────────────────────┘
The user clicks Approve. The button postMessages the host proxy, which calls POST /approve — invisible to the model. The agent polls pharos_check_approval.
The OAuth consent resource
If the server has an OAuth config registered (via pharos oauth configure), the host also renders ui://pharos/oauth/{token}. The OAuth card is still a UI resource. Install approval is the click path on ui://pharos/approval/{token}— not an MCP approve tool. The user authorizes without leaving the chat.
# OAuth is still a UI resource — not HTML stuffed into the tool JSON.
# Host renders ui://pharos/oauth/{token} in a sandboxed iframe.
PHAROS — OAuth Authorization
┌──────────────────────────────────────┐
│ Salesforce MCP Server │
│ is requesting access to your account.│
│ │
│ → api │
│ → refresh_token │
│ │
│ [ Cancel ] [ Authorize ] │
└──────────────────────────────────────┘
# Click Authorize → postMessage → host proxy.
# Separate from install approval (ui://pharos/approval/{token}).Complete the flow
After the install click (and OAuth if required), the agent can call tools on the server. The OAuth token is managed by Pharos — the agent never sees it.
# Step 3: You click Approve in the sandboxed iframe
# Button postMessages the host → POST /approve (not an MCP tool)
# Step 4: Agent polls until the click is recorded
pharos_check_approval(approval_token="ia-my-salesforce-server-1723540800-a3b1c2")
→ { "status": "installed", "server_id": "my-salesforce-server" }
# If the server needs OAuth, the host also renders
# ui://pharos/oauth/{token}. You click Authorize there.
# Step 5: Agent calls a tool — the OAuth token is already handled
pharos_call_tool(
server_id="my-salesforce-server",
tool_name="query_soql",
arguments={"query": "SELECT Name, Email FROM Contact LIMIT 5"}
)
→ { "result": [{ "Name": "Jane Doe", "Email": "[email protected]" }, ...] }How the pieces fit
- 1. Publisher registers OAuth config via
pharos oauth configure. This stores the auth URL, client ID, scopes, and PKCE setting in the registry. - 2. User asks the chat app to find and install a server. The agent calls
pharos_search_apps+pharos_install_apps. - 3. Agent gets a pending approval token — not installed yet. No
pharos_connecttool. - 4. Client renders
ui://pharos/approval/{token}. User clicks Approve. Host proxyPOST /approve. - 5. If OAuth: Client renders
ui://pharos/oauth/{token}. User authorizes scopes. Pharos completes the OAuth dance with the provider. - 6. Agent polls
pharos_check_approval. Then usespharos_list_toolsandpharos_call_tool.
Client requirements
Approval and OAuth cards require an MCP Apps-compatible client — one that renders HTML resources with the text/html;profile=mcp-app content type as sandboxed iframes. The client must:
- ✓Render
ui://pharosresources in a sandboxed iframe (sandbox="allow-scripts", no same-origin) - ✓Handle
postMessageevents from the embedded HTML (for Approve/Deny and Authorize/Cancel button clicks) - ✓Support the
ui/getToolResultJSON-RPC method to receive tool result data for rendering
Clients without MCP Apps support should use CLI mode (omit PHAROS_MCP_APPS) and get compact JSON only. There is no pharos_approve tool the agent can call to skip the click. End-user chatbots require a physical Approve click.