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 inline as HTML cards in MCP Apps-compatible clients.

Who this is for

Developers building or configuring an AI chat app that supports MCP Apps UI (inline HTML rendering for tool results). This guide covers the server-side setup (registering OAuth configs with Pharos) and the user-facing flow (what the user sees in chat).

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"
    }
  }
}

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 interacts with the inline approval and OAuth cards.

Steps 1–3: Search, install, connect (returns pending)
# Step 1: Agent searches for a server
pharos_search(query="salesforce", limit=5)

→ Returns results, including:
  { "id": "my-salesforce-server", "name": "Salesforce MCP Server", ... }

# Step 2: Agent installs it
pharos_install(server_id="my-salesforce-server")
→ { "status": "installed" }

# Step 3: Agent requests connection (returns pending — NOT auto-approved)
pharos_connect(server_id="my-salesforce-server", purpose="Access Salesforce CRM data")

→ Returns:
{
  "status": "pending_approval",
  "approval_token": "pc-my-salesforce-server-1723540800-a3b1c2",
  "expires_in": 300,
  "approval_data": {
    "server": {
      "id": "my-salesforce-server",
      "display_name": "Salesforce MCP Server",
      "version": "1.0.0",
      "publisher": { "name": "io.github.yourusername", "verified": true }
    },
    "purpose": "Access Salesforce CRM data",
    "scopes": ["tools:call"],
    "capabilities": ["tools"]
  }
}

The inline approval card

When pharos_connect returns pending_approval, MCP Apps-compatible clients render an HTML approval card inline. The server attaches this via the ui://pharos/approval resource. The card shows the server name, publisher verification status, requested scopes, and purpose.

┌──────────────────────────────────────────┐
│  🔒 PHAROS — Connection Approval         │
│                                          │
│  Salesforce MCP Server v1.0.0            │
│  Publisher: io.github.yourusername ✓     │
│  Purpose: Access Salesforce CRM data     │
│                                          │
│  Requested scopes:                       │
│    → tools:call                          │
│                                          │
│  [ Deny ]          [ Approve ]           │
└──────────────────────────────────────────┘

The user clicks Approve. The client calls pharos_approve with the token. The token is HMAC-signed and expires after 5 minutes.

The inline OAuth consent screen

If the server has an OAuth config registered (via pharos oauth configure), the client also renders the OAuth consent screen inline via the ui://pharos/oauth resource. The user authorizes the OAuth scopes without leaving the chat — no browser redirect, no separate login window.

OAuth consent UI (rendered inline in chat)
# The MCP Apps client renders the OAuth consent UI inline.
# The server returns an HTML resource at ui://pharos/oauth:

🔐 PHAROS — OAuth Authorization
┌──────────────────────────────────────┐
│  Salesforce MCP Server               │
│  is requesting access to your account.│
│                                      │
│  → api                               │
│  → refresh_token                     │
│                                      │
│  [ Cancel ]    [ Authorize ]         │
└──────────────────────────────────────┘

# The user clicks "Authorize" — the client sends the approval back
# via postMessage. The server completes the OAuth flow with the
# provider and returns the connection result.

Complete the flow

After both approvals (connection + OAuth), the agent can call tools on the server. The OAuth token is managed by Pharos — the agent never sees it.

Steps 4–5: Approve connection, call tools
# Step 4: User approves the connection
pharos_approve(approval_token="pc-my-salesforce-server-1723540800-a3b1c2")

→ Returns:
{
  "status": "connected",
  "server_id": "my-salesforce-server",
  "tools_count": 12,
  "tools": [
    { "name": "query_soql", "description": "Run a SOQL query" },
    { "name": "create_record", "description": "Create a Salesforce record" },
    ...
  ]
}

# 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 + pharos_install.
  3. 3. Agent calls pharos_connect. Gets back a pending approval token — not connected yet.
  4. 4. Client renders the inline approval card ( ui://pharos/approval). User reviews and clicks Approve.
  5. 5. If OAuth: Client renders the OAuth consent screen ( ui://pharos/oauth). User authorizes scopes. Pharos completes the OAuth dance with the provider.
  6. 6. Agent calls pharos_approve with the token. Connection established. Tools available via pharos_list_tools and pharos_call_tool.

Client requirements

The inline approval and OAuth cards require an MCP Apps-compatible client — one that renders HTML resources with the text/html;profile=mcp-app content type. The client must:

  • Render HTML tool results inline in the chat (not as a separate window)
  • 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 can still use Pharos — they just get JSON responses instead of inline cards. The agent can still call pharos_approve programmatically; the user just does not get a visual approval card.

Next steps