# GPT Actions vs MCP — different discovery topology, same goal

Web page: https://academy.memberpress.ai/courses/agent-discoverable-surfaces/l4/

Lesson 7 of 7 in [Agent-Discoverable Surfaces](https://academy.memberpress.ai/courses/agent-discoverable-surfaces/), 12 min.

Video: https://www.youtube.com/watch?v=pwZzLeWLwWg

**Hook**

You shipped MCP. Cursor finds you, Claude Desktop finds you, Claude Code finds you. ChatGPT users still can't reach your tool. The fix is not a second MCP server — it's a different topology.

**Concept**

Prerequisites: a public HTTP endpoint, a working sense of what `.well-known/mcp.json` does, the ability to read an OpenAPI 3.x fragment, and m3 l1 (`Rate-limit OAuth /register for AI clients`) behind you. This lesson covers the mental shift between two ways an AI agent learns your tool exists, and the single artifact that serves both.

**Two doors, opposite directions**

MCP is the front door with a sign. The agent walks up, reads `.well-known/mcp.json`, finds the endpoint, queries the schema, and starts calling operations — no human in the loop. Server-mediated discovery.

GPT Actions is the front door with a guest list. ChatGPT does not crawl for tools. A human opens the GPT builder, pastes your OpenAPI URL into the Actions panel, clicks deploy, and only then does the agent learn your address. User-mediated attachment.

Same goal. Opposite paths to the door. Ship only one and you reach half the agent population.

**One artifact, two consumers**

A single OpenAPI 3.x spec at a public URL covers both. The MCP server wraps it for the agent-mediated path. The Custom GPT imports it for the user-mediated path. Build the spec once; both ecosystems read it. That is the entire architectural shift. Everything below is mechanics.

**Walk-through**

You already have the OpenAPI URL — the same spec your MCP server exposes (in this project it lives at `workers/rag.js`'s `/openapi.json` route). The Custom GPT side has four moves.

1. Open the GPT builder at `https://chatgpt.com/gpts/editor` → Configure → Actions → Import from URL.
2. Paste the OpenAPI URL. ChatGPT validates the schema and lists every operation it found.
3. Pick the auth mode:
   - **None** — read-only public reads.
   - **API Key in headers** — do not use this for user-scoped data. The GPT creator's key gets reused across every conversation, so user A's data leaks into user B's session.
   - **OAuth 2.0** — the right answer for anything user-scoped.
4. Before testing OAuth, allowlist `https://chat.openai.com` in your OAuth provider. Custom GPT OAuth flows redirect to `https://chat.openai.com/aip/g-<gpt-id>/oauth/callback`. If your allowlist is locked to your own domain, the first user's auth attempt fails with `redirect_uri_mismatch` — the failure is invisible from your side; the user sees a generic OpenAI error page. (Redirect URI pattern current as of 2026-05-07; verify against `platform.openai.com/docs/actions` before shipping.)

Set the GPT to Public if directory discovery matters — ChatGPT's GPT store is search-indexed — or Private for invite-only. Same citation hygiene as MCP: if your endpoint returns `url` fields in its JSON, the GPT cites the source. Without them, ChatGPT summarizes without attribution.

**If you get stuck**

- **`redirect_uri_mismatch` on the first OAuth round-trip.** You forgot to allowlist `https://chat.openai.com`. Add it (or the specific callback path) to your OAuth provider's redirect list and retry.
- **The Custom GPT works for the creator but fails for every other user.** You picked API Key in headers. The creator's key is the only key the GPT carries. Switch the auth mode to OAuth and re-import.
- **ChatGPT can't see your tool at import time.** The OpenAPI URL is behind auth, returns the wrong content-type, or 404s for unauthenticated requests. Curl it from a private window; you should get `application/json` (or `application/yaml`) at a public URL with no auth required.

**Cross-reference**

m3 l1 (`Rate-limit OAuth /register for AI clients`) applies to both surfaces — every endpoint either ecosystem touches needs the same calibration, because ChatGPT user traffic shares the same egress-IP shape as Claude.ai.

**Deliverable**

You have a Custom GPT in the ChatGPT builder that imports your existing OpenAPI URL, with OAuth configured (or `none` if read-only), `https://chat.openai.com` on the OAuth allowlist, and at least one operation tested end-to-end with a real user account. You can say without checking notes: MCP is server-mediated discovery, GPT Actions is user-mediated attachment, and the OpenAPI spec is the one artifact that serves both.

**Further reading**

- ["Closing the Agent Discovery Loop"](https://sethshoultes.com/blog/closing-the-agent-discovery-loop.html) — the framing for why AI agent discovery differs from human discovery, and the shape your public surface needs to take.

## Try these prompts

- Explain in three sentences why a single OpenAPI 3.x spec at a public URL covers both an MCP server and a ChatGPT Custom GPT Action, and which two consumers read it differently.
- Walk through the exact failure mode when a Custom GPT's OAuth flow first redirects a user, given the GPT redirect URI pattern, and name the one allowlist change that fixes it.
- Compare GPT Actions' three auth modes (none, API key in headers, OAuth 2.0) for a tool that returns user-scoped data, and explain why API key in headers is brittle in that case.
