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.

Install
pip install pharos-mcp

For manual configuration (clients not auto-detected), add this to your client's MCP config:

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 OAuth config
# 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-secret

Flags

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

Steps 1–2: Search, then pharos_install_apps (returns pending)
# 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 consent UI (rendered inline in 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.

Steps 3–5: Click Approve, poll check_approval, call tools
# 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. 1. Publisher registers OAuth config via pharos oauth configure. This stores the auth URL, client ID, scopes, and PKCE setting in the registry.
  2. 2. User asks the chat app to find and install a server. The agent calls pharos_search_apps + pharos_install_apps.
  3. 3. Agent gets a pending approval token — not installed yet. No pharos_connect tool.
  4. 4. Client renders ui://pharos/approval/{token}. User clicks Approve. Host proxy POST /approve.
  5. 5. If OAuth: Client renders ui://pharos/oauth/{token}. User authorizes scopes. Pharos completes the OAuth dance with the provider.
  6. 6. Agent polls pharos_check_approval. Then uses pharos_list_tools and pharos_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://pharos resources in a sandboxed iframe (sandbox="allow-scripts", no same-origin)
  • ✓Handle postMessage events from the embedded HTML (for Approve/Deny and Authorize/Cancel button clicks)
  • ✓Support the ui/getToolResult JSON-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.

Next steps