Access to AO Platform Ontology from 3rd Party Products (Preview)

MCP Server - Connecting Claude via OAuth: Overview and Steps

Intended audience: Developers administrators

AO Platform: 5.0


Overview — Two Connectivity Paths

Path

Who Uses It

How OAuth Is Handled

ao-mcp-bridge (local stdio)

Individual developers on their own machine

Bridge runs locally, registers a loopback client via Dynamic Client Registration (DCR), proxies stdio → HTTPS

Claude custom connector (cloud-to-cloud, this guide)

Teams, enterprise, and individual Claude users who want the connector in their Claude account

Claude's cloud calls the MCP server directly; admin pre-registers one confidential client; each user authenticates individually via Okta

This guide covers the Claude custom connector path only.

The local token validation will have to be disabled in the AO Platform to use this feature, as we now have two applications (AO Platform server and Claude Desktop) responsible for requesting the OIDC tokens, whereas the token cache is specific to the platform.   


Video


Architecture

ao-mcp-sso.png


Claude Custom Connector OAuth Flow (cloud-to-cloud)

┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│  PHASE 1 — DISCOVERY (once per connector setup)                                  │
│                                                                                  │
│  Claude Cloud ──[1: GET /mcp (no token)]────────────────────────► ao-mcp-server      │
│               ◄──[2: HTTP 401                                │                   │
│                    WWW-Authenticate: Bearer                  │                   │
│                      resource_metadata="<MCP_RESOURCE_URL>/  │                   │
│                      .well-known/oauth-protected-            │                   │
│                      resource/mcp"]───────────────────────────────┘                   │
│                                                                                  │
│  Claude Cloud ──[3: GET /.well-known/oauth-protected-resource/mcp]─► ao-mcp-server
│               ◄──[4: { "resource": "<MCP_RESOURCE_URL>",     │                   │
│                         "authorization_servers": [           │                   │
│                           "<AUTH_SERVER_URL>" ] }]─────────────┘                   │
│                                                                                  │
│  Claude Cloud ──[5: GET /.well-known/oauth-authorization-server]─► ao-mcp-security-service
│               ◄──[6: { issuer, authorization_endpoint,       │                   │
│                         token_endpoint,                      │                   │
│                         code_challenge_methods: ["S256"],    │                   │
│                         (NO registration_endpoint) }]─────────┘                   │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│  PHASE 2 — AUTHORIZATION (once per user; repeated when token expires)            │
│                                                                                  │
│  Claude Cloud ──[7: GET /oauth/authorize                                         │
│                    ?client_id=<pre-registered>                                   │
│                    &redirect_uri=https://claude.ai/api/mcp/auth_callback         │
│                    &code_challenge=<S256-PKCE>                                   │
│                    &state=<nonce>]──────────────────────► ao-mcp-security-service    │
│                                                                   │              │
│                        ──[8: redirect to Okta /authorize]───────────┘              │
│                                          ▼                                       │
│                              ┌──────────────────────────┐                            │
│                              │  Okta login page     │ ◄── User enters credentials│
│                              └───────────┬──────────────┘                            │
│                                        │ [9: callback with IdP authorization code]
│                                        ▼                                         │
│                              ao-mcp-security-service                             │
│                              · exchanges IdP code for IdP token (server-side)    │
│                              · issues an opaque proxy code                       │
│                              · redirects to claude.ai/api/mcp/auth_callback      │
│                                        │ [10: browser → auth_callback?code=...]  │
│                                        ▼                                         │
│                                   Claude Cloud                                   │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│  PHASE 3 — TOKEN EXCHANGE                                                        │
│                                                                                  │
│  Claude Cloud ──[11: POST /oauth/token                                           │
│                     grant_type=authorization_code                                │
│                     code=<proxy code>                                            │
│                     code_verifier=<PKCE verifier>                                │
│                     client_id=<pre-registered>                                   │
│                     client_secret=<pre-registered>]──► ao-mcp-security-service   │
│               ◄──[12: { access_token, refresh_token, … }]─────────────────────────────┘
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│  PHASE 4 — TOOL CALLS (every MCP request thereafter)                             │
│                                                                                  │
│  Claude Cloud ──[13: POST /mcp                                                   │
│                     Authorization: Bearer <Okta access_token>]─► ao-mcp-server   │
│                                                                        │         │
│                     ──[14: forward Bearer token]────────────────────────────┘         │
│                                          ▼                                       │
│                                    AO Platform API                               │
│                                    · validates token (iss, aud, exp)             │
│                                    · executes tool / query                       │
│                                    · returns result                              │
│               ◄──[15: tool result]────────────── ao-mcp-server ◄─────────────────────── │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘


Credential layers explained

Layer

Credentials

Owner

Visible to user?

Okta server-side app (OIDC_CLIENT_ID / OIDC_CLIENT_SECRET)

ao-mcp-security-service ↔ Okta (server-to-server)

DevOps

Never

Claude connector client (client_id / client_secret)

Claude ↔ ao-mcp-security-service

Admin (created once via API, pasted into Claude UI)

Visible during connector setup only

User identity

Okta username + password

Each user

Yes — they log in themselves


Prerequisites

Before starting, ensure:

  1. ao-mcp-security-service is deployed at a public HTTPS URL (e.g. https://easyanswers-qa-agents-mcp-auth.apporchid.com).

  2. ao-mcp-server is deployed at a public HTTPS URL (e.g. https://easyanswers-qa-agents-mcp.apporchid.com).

  3. Okta has a server-side web app registered:

    • Grant type: Authorization Code

    • Allowed redirect URI: <AUTH_SERVER_URL>/oauth/idp-callback

    • Obtain: OIDC_ISSUER_URI, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET

  4. Anthropic egress IP 160.79.104.0/21 is allowed through any firewall protecting either service.


Step 1 — Deploy ao-mcp-security-service

Environment variables

Variable

Required

Default

Sample Value

Description

AUTH_ISSUER

Yes

http://localhost:9000

https://easyanswers-qa-agents-mcp-auth.apporchid.com

Public HTTPS URL of this service. Appears as the issuer in OAuth discovery metadata. Must exactly match the URL clients use — no trailing slash.

OIDC_ISSUER_URI (alias OKTA_ISSUER_URI)

Yes

(none)

https://dev-12345.okta.com/oauth2/aus1abc2defGHI

Issuer URI of the upstream Okta authorization server. Found in Okta Admin Console → Security → API → Authorization Servers → [server] → Issuer.

OIDC_CLIENT_ID (alias OKTA_CLIENT_ID)

Yes

(none)

0oa1abc2defGHI3jk4l5

Client ID of the server-side Okta app. Used by ao-mcp-security-service to proxy authorization to Okta. Never share with end users.

OIDC_CLIENT_SECRET (alias OKTA_CLIENT_SECRET)

Yes

(none)

AbCdEf1234567890XyZw

Client secret of the server-side Okta app. Treat like a password. Store in a secrets vault.

ADVERTISE_REGISTRATION_ENDPOINT

No

false

false

Controls whether registration_endpoint appears in OAuth discovery metadata. Must be false for Claude connector — when present, Claude attempts Dynamic Client Registration (DCR) which Okta rejects. Set to true only for the ao-mcp-bridge local DCR flow.

ADMIN_KEY

Yes (for admin API)

(empty)

9f3c1a7d2e4b8f6a... (32 hex chars)

Shared secret for POST /admin/clients. Callers must send this in the X-Admin-Key header. Generate with openssl rand -hex 32. Store in a vault — never commit to source control. When blank, the admin endpoint is effectively disabled.

DB_URL

Yes

jdbc:postgresql://localhost:5432/ao_mcp_security

jdbc:postgresql://db-host.internal:5432/ao_mcp_security

JDBC URL for the PostgreSQL database that stores registered OAuth client records.

DB_USERNAME

Yes

postgres

ao_mcp_user

PostgreSQL username with read/write access to the ao_mcp_security database.

DB_PASSWORD

Yes

postgres

(from vault)

Password for DB_USERNAME. Never hard-code; use a secrets manager.

ALLOW_LOOPBACK_REDIRECTS_ONLY

No

true

true

When true, the public DCR endpoint (POST /oauth/register) only accepts http://127.0.0.1 or http://localhost redirect URIs. Correct for production — limits DCR to ao-mcp-bridge's local flow. The POST /admin/clients endpoint is unaffected.

DB_POOL_MAX_SIZE

No

15

10

HikariCP maximum connection pool size.


Deployment checklist

  • Set ADVERTISE_REGISTRATION_ENDPOINT=false

  • Set a strong ADMIN_KEY (store in vault)

  • Confirm AUTH_ISSUER matches the public URL exactly (no trailing slash)

  • Confirm OIDC_ISSUER_URI points to the correct Okta authorization server

  • Database migrations run automatically on startup (Liquibase)


Step 2 — Deploy ao-mcp-server

Environment variables

Variable

Required

Default

Sample Value

Description

MCP_RESOURCE_URL

Yes

http://localhost:8080

https://easyanswers-qa-agents-mcp.apporchid.com

Canonical public HTTPS URL of the MCP endpoint (no /mcp path suffix, no trailing slash). Appears as the resource field in Protected Resource Metadata (PRM) and in WWW-Authenticate 401 headers. Must be the exact URL Claude uses to reach this server.

AUTH_SERVER_URL

Yes

http://localhost:9000

https://easyanswers-qa-agents-mcp-auth.apporchid.com

Public HTTPS URL of ao-mcp-security-service. Appears in the authorization_servers array of the PRM. Claude reads this to discover where to run the OAuth flow.

MCP_REQUIRE_AUTH

No

true

true

When true, every request to /mcp must carry a Bearer token or API key. Unauthenticated requests receive 401 + WWW-Authenticate. Set to false only for local no-auth development.

PLATFORM_SERVER_BASE_URL

Yes

http://localhost:8090

https://platform.apporchid.com

Base URL of the AO Platform API. The MCP server forwards all tool calls here, attaching the user's Bearer token.

PLATFORM_BEARER_TOKEN

No

(none)

(leave blank)

Optional static fallback JWT. Used when no per-request Bearer token is present (e.g., automated testing). Leave blank in production to require per-request credentials.

PLATFORM_API_KEY

No

(none)

(leave blank)

Optional static fallback API key. Leave blank in production.


Step 3 — Create the Claude Connector Client (Admin, Once)

This step registers a confidential client in ao-mcp-security-service's database. Run it once after both services are deployed.

Bash
# Generate a strong admin key (if not already done):
export ADMIN_KEY=$(openssl rand -hex 32)

# Register the Claude connector client:
curl -s -X POST https://easyanswers-qa-agents-mcp-auth.apporchid.com/admin/clients \
  -H "X-Admin-Key: $ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
    "token_endpoint_auth_method": "client_secret_basic"
  }' | python -m json.tool

Example response:

JSON
{
  "client_id": "proxy-a1b2c3d4-...",
  "client_secret": "e5f6g7h8-...",
  "client_id_issued_at": 1749984000,
  "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
  "token_endpoint_auth_method": "client_secret_basic"
}

Important: Save client_id and client_secret securely. The secret is shown only once and cannot be retrieved later (stored as BCrypt hash). Store in your secrets manager.

Lost the secret? Create a new client by re-running POST /admin/clients, then update the Claude connector Advanced settings with the new credentials.

Retrieve or audit registered clients

Bash
# Retrieve a specific client by ID (no secret returned)
curl -s https://easyanswers-qa-agents-mcp-auth.apporchid.com/admin/clients/<client_id> \
  -H "X-Admin-Key: $ADMIN_KEY" | python -m json.tool

# List all registered clients (no secrets returned)
curl -s https://easyanswers-qa-agents-mcp-auth.apporchid.com/admin/clients \
  -H "X-Admin-Key: $ADMIN_KEY" | python -m json.tool

Example list response:

JSON
[
  {
    "client_id": "proxy-a1b2c3d4-...",
    "client_id_issued_at": 1749984000,
    "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
    "token_endpoint_auth_method": "client_secret_basic"
  }
]

The client_secret field is never included in GET responses.


Step 4 — Add the Connector in Claude

Individual account (http://Claude.ai personal)

  1. Open Claude → Customize (bottom-left) → Connectors → +

  2. Paste the MCP URL: https://easyanswers-qa-agents-mcp.apporchid.com/mcp

  3. Expand Advanced settings

  4. Paste client_id and client_secret from Step 3

  5. Click Add

Team / Enterprise account

An Owner adds the connector once at the org level:

  1. Org Settings → Connectors → Add → Custom → Web

  2. Same URL and credentials as above

  3. Members then connect individually (Step 5)


Step 5 — Each User Connects (Individual)

  1. In Claude, find the connector (Connectors panel or sidebar)

  2. Click Connect

  3. A browser window opens showing the Okta login page

  4. Sign in with your own Okta credentials

  5. The connector status changes to connected

  6. AO Tools (nlq-get-data, list-ontologies, etc.) are now available in Claude chat


Verification Checklist

Run these commands to confirm all layers are working before adding the connector:

Bash
# 1. AS metadata — no registration_endpoint
curl -s https://easyanswers-qa-agents-mcp-auth.apporchid.com/.well-known/oauth-authorization-server \
  | python -m json.tool | grep registration_endpoint
# Expected: no output (field absent)

# 2. MCP endpoint — 401 with /mcp suffix in WWW-Authenticate
curl -i https://easyanswers-qa-agents-mcp.apporchid.com/mcp
# Expected headers:
#   HTTP/2 401
#   WWW-Authenticate: Bearer resource_metadata="https://easyanswers-qa-agents-mcp.apporchid.com/.well-known/oauth-protected-resource/mcp"

# 3. PRM at both paths (must return identical JSON)
curl -s https://easyanswers-qa-agents-mcp.apporchid.com/.well-known/oauth-protected-resource
curl -s https://easyanswers-qa-agents-mcp.apporchid.com/.well-known/oauth-protected-resource/mcp
# Expected both:
# {"resource":"https://easyanswers-qa-agents-mcp.apporchid.com","authorization_servers":["https://easyanswers-qa-agents-mcp-auth.apporchid.com"]}

# 4. Connector test (after adding in Claude UI)
# tools/list should return AO tools; a tool call should succeed without 401


Troubleshooting

Symptom

Most Likely Cause

Fix

mcp_registration_failed when adding connector

registration_endpoint still advertised in AS metadata

Set ADVERTISE_REGISTRATION_ENDPOINT=false and redeploy ao-mcp-security-service

unknown client on token exchange

Wrong client_id / client_secret pasted into Claude, or credentials not registered in DB

Re-run POST /admin/clients (Step 3); update Claude connector Advanced settings

401 loop after successful Okta login

Token audience (aud) mismatch — AO Platform expects a specific audience the token doesn't carry

Confirm AO Platform is configured to accept tokens from AUTH_ISSUER with the correct aud claim

Connect bounces with loopback error

Public DCR endpoint (POST /oauth/register) was called instead of admin endpoint

Always use POST /admin/clients for Claude clients; never the DCR endpoint

Cannot reach auth server / MCP server

Anthropic's cloud egress IPs are firewall-blocked

Allowlist 160.79.104.0/21 on both ao-mcp-server and ao-mcp-security-service hosts

"Invalid admin key" on Step 3

ADMIN_KEY env var not set or mismatch

Verify ADMIN_KEY env var in ao-mcp-security-service deployment matches the curl header value

Tools available but every tool call returns 401

Platform rejects the access token (invalid/expired)

Check PLATFORM_SERVER_BASE_URL is correct; confirm platform accepts tokens issued by AUTH_ISSUER


ao-mcp-bridge Reference (Existing Local Path)

The bridge is a Node.js process that runs on the developer's machine:

  • Connects Claude Desktop via stdio (not HTTPS)

  • Registers a loopback client automatically via DCR (POST /oauth/register, redirect URI = http://127.0.0.1:<random-port>)

  • Requires ADVERTISE_REGISTRATION_ENDPOINT=true in ao-mcp-security-service

Bridge and connector can coexist across different deployment environments (e.g., bridge for dev, connector for production/team). Do not set ADVERTISE_REGISTRATION_ENDPOINT=true in the same deployment used by the Claude connector.


Reference Values (QA environment)

Item

Value

MCP / resource URL

https://easyanswers-qa-agents-mcp.apporchid.com/mcp

MCP_RESOURCE_URL (no /mcp suffix)

https://easyanswers-qa-agents-mcp.apporchid.com

Authorization server (issuer)

https://easyanswers-qa-agents-mcp-auth.apporchid.com

Claude hosted callback (redirect URI)

https://claude.ai/api/mcp/auth_callback

Anthropic egress IP range

160.79.104.0/21

PKCE method

S256 (mandatory)


FAQ

  • Q: How can we disable local token validation?

  • A: There is a configuration script that will need to be run by the DevOps team.


  • Q: In the recorded video, the SSO Authentication is using Okta IdP. Can Azure AD be used?

  • A: Currently, the SSO Authentication is only validated with Okta, but it should work with Azure AD and other platform-certified OIDC providers.


  • Q: What is the default token expiry duration, and where can it be configured?

  • A: This is a configuration in IdP.


  • Q: Are refresh tokens supported in this flow? If yes, what is the refresh window?

  • A: Refresh tokens are not supported at this point. Users will be relogged in if the token expires.


  • Q: What is the expected behavior when the same user logs in from both the AO Platform and the Claude Desktop? Should the sessions be independent or shared?

  • A: Typically, sessions are independent.


  • Q: Is performance impacted when using MCP Server with SSO Authentication?

  • A: Token will be validated for every incoming request to the AO Platform server.  


  • Can the existing skills and configurations in Claude (ao-mcp-query-2.0.3.skill and claude_desktop_config) be used?

  • A: Yes.








Contact App Orchid | Disclaimer