Potion's MCP server
Connect an MCP client to Potion at https://api.withpotion.io/mcp. Sign-in, the tools each endpoint carries, narration jobs, uploads, errors and limits.
Published 4 November 2026
Potion makes podcast feeds that a person adds to their podcast app, and narrates text into episodes for them. Its MCP server lets an assistant do all of that on the person's behalf: create a feed, narrate a script or a web article into it, add audio, and read back what is there.
The server is remote, over HTTP. There is nothing to install or run.
Endpoints
| Address | Who it is for | Narration |
|---|---|---|
https://api.withpotion.io/mcp | Any MCP client, including Claude custom connectors and ChatGPT developer mode | yes |
https://api.withpotion.io/mcp/chatgpt | The Potion app in ChatGPT's app directory | yes |
https://api.withpotion.io/mcp/claude | The Potion connector in Claude's directory, and the Claude Code plugin | no |
https://api.withpotion.io/mcp/muse | The Potion connector on Meta's Muse | yes |
Use /mcp unless you are building for one of the listings. The other three are subsets of it, each shaped by its directory's rules, and the reasons are listed under What each listing leaves out.
Connecting
- Transport: streamable HTTP. JSON-RPC requests are POSTed to the endpoint. The server sends no event stream of its own, so a GET that asks for
text/event-streamgets 405. Protocol versions 2026-07-28, 2025-06-18 and 2025-03-26 are accepted. - Sign-in: OAuth 2.1 authorization code flow with PKCE (S256 only). A client registers itself through dynamic client registration (RFC 7591) at
https://api.withpotion.io/oauth/registerand gets its client ID there; none is issued in advance. Public clients, with no secret, are supported. Redirect URIs must match exactly and be https, http on a loopback address, or a private-use scheme. - Discovery: each endpoint has its own protected-resource document (RFC 9728) at
https://api.withpotion.io/.well-known/oauth-protected-resourcefollowed by the endpoint's path, so/.well-known/oauth-protected-resource/mcp,.../mcp/chatgpt,.../mcp/claudeand.../mcp/muse. The authorization server's metadata (RFC 8414) is athttps://api.withpotion.io/.well-known/oauth-authorization-server. - What the person sees: Potion's sign-in page asks for their email address and emails them a 6-digit code. Typing it in finishes the sign-in, and creates their Potion account if it is new. There is no password.
- Scope: one scope,
account, which gives the same access as the account's own API key. - Tokens: access tokens last seven days and come with a refresh token that lasts 90 days and is replaced each time it is used. A token issued for a named endpoint, through the
resourceparameter (RFC 8707), is refused on Potion's other endpoints. - API keys: a client that sends headers can use a Potion API key instead of OAuth:
Authorization: Bearer pk_.... The API reference explains how an account gets one. - Without a token,
tools/listanswers, so a client can read the tools before anyone signs in. A tool call answers HTTP 401 with aWWW-Authenticateheader pointing at the protected-resource document.
In Claude
In claude.ai and Claude Desktop, add https://api.withpotion.io/mcp as a custom connector. In the "Add custom connector" dialog, choose Sign in now under Authentication and Register automatically under OAuth client. Claude suggests other options for both, and Potion doesn't work with them.
The Potion connector in Claude's directory, and the Claude Code plugin (/plugin marketplace add withpotion/potion, then /plugin install potion@potion), use /mcp/claude, which doesn't narrate. For narration in Claude, use the custom connector above.
In ChatGPT
Add Potion from its page in ChatGPT's app directory. It connects to /mcp/chatgpt and signs in with the same emailed code.
Tools
/mcp carries every tool:
| Tool | What it does |
|---|---|
create_feed | Creates a feed. A feed is a show: the person adds its address to their podcast app once |
list_feeds | Lists the account's feeds with their ids and subscribe addresses |
get_feed | One feed's details, including whether a podcast app has fetched it yet |
update_feed | Changes a feed's title, description, author or artwork |
delete_feed | Deletes a feed, its episodes and their audio, permanently |
add_episode | Adds an episode from an audio file at a public address, which Potion links to rather than copies |
add_episodes_batch | Adds several episodes by audio address in one call |
create_upload_link | Returns a single-use address that turns one HTTP PUT of an audio file into an episode |
list_episodes | Lists a feed's episodes, newest first |
get_episode | One episode's details, audio address and share page |
update_episode | Changes an episode's title, description, artwork, publish date or numbering |
delete_episode | Deletes an episode and any audio Potion stored for it |
render | Narrates text into an episode. Returns a job at once; the narration runs in the background |
ingest_url | Fetches a web article and extracts its text into a job, and reports its length without narrating |
start_render | Starts a job that is holding text |
append_text | Adds the next part of a long text to a job that hasn't started |
get_job | A job's progress, the remaining wait, and the episode once it exists |
list_jobs | The account's recent jobs |
list_voices | The narration voices, each with a sample to listen to |
get_account | The account's plan, limits and usage |
get_plans | Potion's plans, what each costs and what it allows |
get_upgrade_link | A Stripe Checkout page for upgrading |
get_billing_portal_link | A Stripe page for changing or cancelling a subscription |
request_account_deletion | Starts deleting the account; nothing goes until the person confirms by email |
check_confirmation | Whether a pending email confirmation has been confirmed |
start_signup | Starts an account by email, for clients that use an API key rather than OAuth |
check_signup | Whether a pending signup has been confirmed |
get_changelog | What has changed in Potion recently |
dismiss_notifications | Stops a changelog notice from being attached to later responses |
get_legal_links | Links to the terms and the privacy policy |
Every tool carries MCP annotations (read-only, destructive, idempotent, open-world), and every tool's input and output schemas are in tools/list. The same operations are available as an HTTP API, described in the API reference.
What each listing leaves out
/mcp/chatgpt leaves out these tools:
get_plans,get_upgrade_link,get_billing_portal_link: OpenAI's guidelines don't allow an app to show subscription plans or link to a checkout.create_upload_link: ChatGPT's code sandbox can't reach the internet to send the file.dismiss_notifications: this endpoint doesn't attach changelog notices.
/mcp/claude leaves out these tools:
render,ingest_url,start_render,append_text,get_job,list_jobs,list_voices: Anthropic's directory policy doesn't allow software that uses AI models to generate audio, so this endpoint doesn't narrate. Adding audio the person already has works.dismiss_notifications: this endpoint doesn't attach changelog notices.
/mcp/muse leaves out these tools:
get_upgrade_link,get_billing_portal_link: Meta's guidelines ask a connector to finish what it starts, and both of these hand the person a page to finish on.create_upload_link: held back while the listing is in Meta's review.dismiss_notifications: this endpoint doesn't attach changelog notices.
Meta's read and write classes for each Muse tool are on Potion's Muse tools page.
Narration
- Jobs.
renderreturns a job straight away.get_jobreports progress andpoll_after_seconds, the remaining wait; once the job finishes, its episode is already in the feed. The wait and the length of the audio are different numbers. - Long text goes in parts:
renderwithis_final: false, thenappend_textuntil the last part, which hasis_final: true. One job holds up to 400,000 characters. - Web articles.
ingest_urlreads public pages only. It identifies itself as PotionBot, follows robots.txt, refuses known paywalled and paywall-bypass sites, and refuses a page with fewer than 250 words of text. It stops before narrating and reports how long the audio would be;start_renderstarts it. - Spending. Narration is counted in seconds of finished audio, charged once the audio exists, and a failed job charges nothing.
start_if_under_minutesis a ceiling in minutes of audio. Onrender, a job whose estimate comes out longer is held, with nothing charged, untilstart_render; oningest_url, an article that comes in under it is narrated straight away. A scheduled run, with nobody there to confirm, should set it. - Voices. Nine, from
list_voices. The six standard voices count minute for minute; the three premium voices count five times. Each episode has one narrator. The voices have only been checked in English.
Uploading audio
create_upload_link takes the feed and the episode's details and returns an address that works once, for one hour. One HTTP PUT of the file to that address creates the episode. A refused file leaves the link usable, so the corrected file can go to the same address. A single PUT carries up to 90 MB, or less if the account's plan sets a lower file limit; larger files go through the HTTP API's multipart upload.
Cloudflare, in front of Potion, refuses Python urllib's default user agent with a bare 403 before Potion sees the request, so set a user agent of your own when sending from Python.
Feeds are unlisted, not private
A feed's address is long and random, and the feed asks podcast directories not to list it. Anyone holding the address can subscribe, and there is no password. A feed's address can't be changed; deleting the feed is the only way to stop it working. Every episode also has a public share page that plays it in a browser. Treat a feed's address like a private link, and tell the person so when the content is personal.
Errors and limits
- A refusal (a plan limit reached, the narration allowance spent, a page Potion won't read) comes back as a tool result with
isError: trueand a message saying what happened and what the person can do. On/mcp/chatgpta refusal is a successful result carrying the same message, because ChatGPT replaces an error result's text with its own. Nothing is written when a call is refused. - Invalid input comes back the same way, with a message naming the field.
- Per account: 120 reads and 60 writes a minute. Over the limit, the message says when it resets.
- A tool that isn't on an endpoint answers JSON-RPC error
-32602,Unknown tool.
Clients it has been run with
ChatGPT (the published app, and developer-mode connectors on /mcp), claude.ai custom connectors on /mcp, the Claude Code plugin, and the MCP Inspector CLI.
Questions and bugs: open an issue or email christo@withpotion.io. Terms and privacy: api.withpotion.io/legal.
Your own podcast, made by your AI
Ask your AI about anything. Potion turns it into an episode in your podcast app.
Which AI do you use?
I use another assistantFree to start. Works with the free versions.