# Oppy MCP — Connect Guide (canonical)

Published: 2026-04-23
Updated: 2026-08-19
Source: https://docs.oppy.pro/n/oppy-mcp-oauth-setup
Account: Oppy Inc

---

# Oppy MCP — Connect Guide

**Endpoint:** `https://mcp.oppy.pro/mcp` · **Transport:** Streamable HTTP (JSON-RPC 2.0)
_Canonical connect documentation. Verified against production August 19, 2026._

Oppy is an AI workforce platform: AI teammates (&quot;oppies&quot;) that qualify leads, book appointments, answer questions, and keep the record straight across SMS, email, voice, and chat. This one endpoint reaches all of it — contacts, conversations, calendars, boards, notes, files, knowledge bases, the oppies themselves, and the audit stream.

Works with any MCP-compatible client, from any vendor. Nothing here is specific to one model or lab.

&gt; **Reading this as an agent?** Fetch the machine-readable version: `curl -H &quot;Accept: text/markdown&quot; https://mcp.oppy.pro/`

---

## Quick reference

| | |
|---|---|
| MCP endpoint | `https://mcp.oppy.pro/mcp` |
| Transport | Streamable HTTP, JSON-RPC 2.0. Stateless — every request is independent |
| Auth | OAuth 2.1 (recommended) or `Authorization: Bearer &lt;api-key&gt;` |
| Protected-resource metadata | `https://mcp.oppy.pro/.well-known/oauth-protected-resource/mcp` |
| Authorization server | `https://io.oppy.pro` |
| Scope | `mcp` |
| Health | `https://mcp.oppy.pro/health` |
| Docs (human) | `https://mcp.oppy.pro` |
| Data boundary | Everything is scoped to your Oppy account. You only ever see your own data |

---

## Choose an auth method

**Sign in with OAuth if a person is connecting their own client.** Nothing secret to paste or store, every action attributed to that person in their own audit trail, revocable any time, and access expires on its own so a forgotten client doesn&#39;t stay connected. Needs a browser once, at setup.

**Use a bearer API key if no human is present.** Right for scheduled jobs, CI, and always-on agents: no browser, stable across restarts, works anywhere a static header is accepted. The cost is a long-lived secret you store and rotate yourself, shared by everyone using it — so actions attribute to the key, not a person.

---

## 1. OAuth 2.1

Most clients handle this end to end once you give them the URL. Add `https://mcp.oppy.pro/mcp` as a remote/HTTP MCP server, sign in to Oppy when the browser opens, approve, done.

### The flow, for clients implementing it directly

1. **Probe.** `POST https://mcp.oppy.pro/mcp` without credentials → `401` with a `WWW-Authenticate` header pointing at the protected-resource metadata URL. An unauthenticated `tools/call` also returns `_meta[&quot;mcp/www_authenticate&quot;]`.
2. **Discover the resource.** `GET https://mcp.oppy.pro/.well-known/oauth-protected-resource/mcp` (RFC 9728; the root path `/.well-known/oauth-protected-resource` also resolves). It returns the `authorization_servers` array — follow it rather than hardcoding endpoints.
3. **Discover the authorization server.** `GET https://io.oppy.pro/.well-known/oauth-authorization-server`.
4. **Register.** Dynamic client registration is open — `POST` to the `registration_endpoint`. No pre-provisioning, no manual client ID request.
5. **Authorize + exchange.** Authorization code with PKCE `S256`. Pass `resource=https://mcp.oppy.pro/mcp` through *both* the authorization request and the token request. Request scope `mcp`.
6. **Call.** Send the access token as `Authorization: Bearer &lt;access_token&gt;`. Refresh with the `refresh_token` grant.

### Endpoints

As advertised by the authorization server at the time of writing. Discover them; don&#39;t hardcode.

| Endpoint | URL |
|---|---|
| Issuer | `https://io.oppy.pro` |
| Authorization | `https://io.oppy.pro/api/oauth/authorize` |
| Token | `https://io.oppy.pro/api/oauth/token` |
| Dynamic client registration | `https://io.oppy.pro/api/oauth/register` |
| Revocation | `https://io.oppy.pro/api/oauth/revoke` |

**Supported:** `authorization_code` and `refresh_token` grants · `code` response type · PKCE `S256` · client auth `none`, `client_secret_basic`, `client_secret_post`. Public clients should use `none` + PKCE.

The access token is an Oppy-issued token bound to the user, account, scope, and MCP resource. Internal API keys are never exposed as OAuth tokens.

---

## 2. Bearer API key

1. Create a key in your Oppy account settings → API keys.
2. Point the client at `https://mcp.oppy.pro/mcp`.
3. Send `Authorization: Bearer &lt;your-key&gt;` on every request.

Store it in a secret manager, never in a repo, and rotate it on a schedule you decide. A key carries account-admin reach — treat it like a password. Delete it to revoke; there is no automatic expiry.

```bash
curl -sS https://mcp.oppy.pro/mcp \
  -H &quot;Authorization: Bearer $OPPY_API_KEY&quot; \
  -H &quot;Content-Type: application/json&quot; \
  -d &#39;{&quot;jsonrpc&quot;:&quot;2.0&quot;,&quot;id&quot;:1,&quot;method&quot;:&quot;tools/list&quot;}&#39;
```

---

## Client setup

### ChatGPT / Codex
Add **Oppy** from the plugins directory, or add `https://mcp.oppy.pro/mcp` as a remote MCP server. Sign in when prompted.

### Claude — desktop, web, and mobile
Settings → Connectors → Add custom connector → `https://mcp.oppy.pro/mcp`. Sign in when the browser opens.

### Claude Code
```json
{
  &quot;mcpServers&quot;: {
    &quot;oppy&quot;: {
      &quot;type&quot;: &quot;http&quot;,
      &quot;url&quot;: &quot;https://mcp.oppy.pro/mcp&quot;,
      &quot;headers&quot;: { &quot;Authorization&quot;: &quot;Bearer ${OPPY_API_KEY}&quot; }
    }
  }
}
```
Export `OPPY_API_KEY` in your shell profile. Verify with `claude mcp list`, then try a `manage_boards` list call. Or use `claude mcp add --transport http oppy https://mcp.oppy.pro/mcp` and let OAuth handle it.

### Cursor
Add to `.cursor/mcp.json` using the same shape as Claude Code, or add it as an HTTP server in settings and sign in with OAuth.

### Anything else
Any client that speaks streamable-HTTP MCP works. Give it the URL and either auth method.

---

## What the tools look like

**21 tools, dispatched by `action`.** Rather than one tool per operation, each tool covers a domain and takes an `action` — `manage_contacts` with `action: &quot;list&quot;`, `action: &quot;create&quot;`, `action: &quot;bulk_update&quot;`, and so on. Read the tool&#39;s own description for its action list; several also expose `action: &quot;help&quot;` with a `topic` parameter for in-context reference.

| Domain | Tool |
|---|---|
| People | `manage_contacts` |
| Threads | `manage_conversations` |
| Scheduling | `manage_calendar` (events, analytics), `manage_calendars` (connections, assignment) |
| Pipeline | `manage_boards` (boards → lists → items) |
| Written record | `manage_notes`, `manage_notes_bulk`, `note_audio` |
| Files | `manage_files` |
| Knowledge | `manage_indexes` |
| The workforce | `manage_channels` (the oppies), `manage_loops` (standing instructions on a schedule) |
| Agent-to-agent | `list_teammates`, `ask_oppy` |
| Automation | `manage_automation_rules` |
| Audit | `manage_events`, `manage_feedbacks` |
| Web | `manage_browser`, `extract_data` |
| Generation | `generate_content` |
| Telephony | `manage_twilio` |

### Conventions worth knowing before your first call

**Start broad when searching.** Every list action defaults to something useful. Call with no filters first, then narrow. Thin results almost always mean over-filtering, not empty data.

**Control the payload.** Most list actions accept `fields` (comma-separated include list, or `*`), `exclude_fields`, `truncate` (e.g. `{description: 100}`), and `count_only`. On large lists, narrow `fields` — it&#39;s the difference between a clean response and one truncated mid-JSON.

**Paginate per tool.** Most use `page` / `per_page`. `manage_conversations` uses `limit` plus a `latest_message_before` cursor — there is no page or offset. `manage_events` timeline uses `before_timestamp`.

**Read before you write.** List responses may be partial: `manage_boards` `item_list` marks any shortened field in a `truncated_fields` array. Never write a list record straight back — `item_get` the full record first, or you&#39;ll silently truncate the stored value.

**Confirmation gates are deliberate.** `manage_channels` create and archive require `confirmed_by_user: true`. Board and list deletes require `confirm_delete`. Hard-deleting notes requires `confirm`. Bulk contact writes accept `dry_run: true` for a preview. A prompt-shrink guard rejects system-message edits that cut more than 70% unless you pass `force`. These exist because a wrong call here is expensive — don&#39;t route around them, use them.

**A record you can&#39;t reach reads as &quot;not found.&quot;** Scoping is enforced before existence. If something seems missing, check scope and permissions before reporting a bug — and read the `help` topic where one exists.

**Run browser sessions one at a time.** `manage_browser` is for sequential automation. Lead with DOM extraction (`snapshot`, `evaluate`, `content`, `locator_text`) rather than screenshots — it&#39;s faster, cheaper, and more reliable. Screenshot only for final visual verification or when there&#39;s no DOM to read.

---

## Permissions, attribution, and safety

**An agent gets exactly the access its authenticating user has.** Connecting through an assistant never widens permissions.

**Every action is attributed.** It lands in the same shared timeline as work done by hand, so delegating doesn&#39;t lose the thread of who did what. `manage_events` reads that stream back.

**Some tools act in the real world.** Three are read-only: `list_teammates`, `manage_twilio`, `extract_data`. The rest write. Several reach outside Oppy entirely:

- `manage_conversations` sends real SMS, email, and voice to real people. A sent message cannot be unsent.
- `manage_calendar` emails external attendees and can delete meetings off their calendars.
- `manage_loops` fires unattended on a schedule with full tool access, including outbound comms.
- `manage_automation_rules` posts to third-party webhooks and can trigger outbound conversations.
- `manage_browser` drives a real browser and can submit forms on third-party sites.
- `manage_notes`, `manage_notes_bulk`, and `manage_indexes` can publish pages to the open internet via `visibility: public` or `public_unlisted`.

Tool annotations (`readOnlyHint`, `openWorldHint`, `destructiveHint`) reflect this honestly, so a well-behaved client can gate on them.

**Two habits that prevent nearly every bad outcome:**

1. **Draft first when the audience is a person.** For outbound messaging, produce the draft and hold it for review rather than sending on a single instruction.
2. **Count before you act in bulk.** `count` or `dry_run` first, show the number, then proceed. &quot;Delete everyone who hasn&#39;t replied&quot; deserves a number and a confirmation, not a mutation.

---

## Troubleshooting

**`401` from `/mcp`.** Expected before auth. The response carries `WWW-Authenticate` pointing at the protected-resource metadata. With a bearer key, a 401 means the key is missing, revoked, or malformed.

**`403`.** The credential authenticated but its scope is too narrow for that action.

**&quot;No authorization support detected.&quot;** Confirm these return 200 without credentials: `https://mcp.oppy.pro/.well-known/oauth-protected-resource`, `.../oauth-protected-resource/mcp`, and `https://io.oppy.pro/.well-known/oauth-authorization-server`.

**Sign-in succeeds but tool calls fail.** Check the token carries scope `mcp`, that its bound resource matches `https://mcp.oppy.pro/mcp` exactly, and that the client completed the PKCE exchange.

**Empty results.** Usually the credential is keyed to a different account than expected, or the filters are too narrow. List with no filters to confirm.

**A tool returns a 404-shaped error for every action.** That feature may be disabled on this backend. Read the error message — it says so — before assuming a bug.

**OAuth loop in a desktop client.** Clear the client&#39;s stored MCP credentials and reconnect.

---

## Known gap: enterprise workspace domain restrictions

The authorization server does not yet advertise `openid` or `email` scopes and publishes no UserInfo endpoint. Platforms that gate connections on a verified email domain — including ChatGPT workspace domain restrictions — require both. Until that ships, enterprise admins cannot restrict Oppy to their own domain.

---

### Change history

- **2026-08-19** — Rewritten as the canonical connect guide. Corrected the stale banner claiming OAuth was non-functional (it shipped 2026-08-06). Documented the authorization server at `https://io.oppy.pro`, the full OAuth flow, bearer auth, per-client setup, the 21-tool inventory, the API conventions agents need, and honest safety guidance. Absorbed the former separate developer-setup note.
- **2026-07-23** — Marked OAuth non-functional pending auth consolidation.
- **2026-04-22** — Initial guide.
