MCP server

Connecting an assistant: the server address, sign-in, the tools, resources and prompts.

The MCP server lets an assistant work with the Affiliate Portal for a station: the REST API's operations as tools (all but the log upload and the printable documents), the week's order, changes and creatives as resources it can read and follow, and two prompts for the usual jobs. It follows the Model Context Protocol over streamable HTTP. Signing a week stays a person's act in the portal.

Server address

Remote MCP server URL
https://affiliates.radioworkflow.com/api/mcp
  • Transport: streamable HTTP. POST carries every message, GET opens a session's notification stream and DELETE ends the session.
  • Protocol versions: 2025-06-18 (the default), 2025-03-26 and 2024-11-05.
  • One message per request, at most 1 MB. Batches are refused.

Two ways in

  • A person signs in with OAuth. Add the server address in your assistant and connect. The first request answers 401 with the address of the sign-in details (https://affiliates.radioworkflow.com/.well-known/oauth-protected-resource), which the assistant follows: it opens the portal, you sign in with your emailed link if you are not signed in already, and you allow the connection. The connection acts as you, with your roles. It lasts until it goes unused for 30 days, and 90 days at most, and you can end it any time under API and MCP in the portal. It can report the affidavits you could report, and it can never sign a week.
  • Automation uses a station key. Make a key under API and MCP in the portal with the scopes the tools below need, and send it as Authorization: Bearer rwaff_k_.... A key never signs; sign_week is not listed for it.

What each tool needs:

  • list_affiliations, get_week, get_status, get_changes, list_creatives, get_week_audio, export_order, get_clearance, list_weeks: Needs view access to the affiliation; a key needs the read scope.
  • acknowledge_version: Needs an affiliation that is not read-only; a key needs the acknowledge scope.
  • apply_log_matches, report_affidavits, report_show_airings: Needs the report or sign role on a live affiliation; a key needs the report scope.
  • sign_week: Only for people with the sign role; no key can use it. It never signs anything.

Connecting your assistant

Every host that speaks MCP over HTTP can connect. Where the setting lives differs by product; these are the usual places. Enter the server address above, nothing else, unless you use a station key.

Desktop assistant apps

  1. Open the app's settings and find connectors, integrations or MCP servers.
  2. Add a remote or custom server and paste the server address.
  3. Connect. The app opens the portal in your browser; sign in and allow the connection, then return to the app.
  4. Start a new conversation and check that the station tools are listed.

Web chat apps that accept remote connectors

  1. In the app's settings, add a custom connector (it may be called a remote MCP server).
  2. Paste the server address and save.
  3. Connect and sign in to the portal when asked. The connector is then available in your conversations.

Code editors

Add an HTTP server to the editor's MCP settings, usually a JSON file named in its documentation. Without the headers entry the editor signs you in with OAuth; with it, the editor uses a station key from its environment.

An HTTP server entry
{
  "affiliates": {
    "type": "http",
    "url": "https://affiliates.radioworkflow.com/api/mcp",
    "headers": {
      "Authorization": "Bearer $AFFILIATE_API_KEY"
    }
  }
}

Command-line agents

Add the server with the agent's command for remote servers, choosing the HTTP transport and the server address; pass the Authorization header for a station key. To check a key before you connect anything, list the tools yourself:

Check a key
curl -s -X POST https://affiliates.radioworkflow.com/api/mcp \
  -H "Authorization: Bearer $AFFILIATE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Tools

The tools are the REST endpoints, generated from the same catalogue: each takes the endpoint's parameters as one object and answers its JSON as structuredContent, which matches the tool's outputSchema (the response schema on the endpoint's page). A tool is listed only when the connection may use it somewhere. upload_log and get_document are not tools: a log goes through the REST API or the portal, and a printable document is a page, not data.

ToolEffectAnnotationsKey scopeLimits
list_affiliationsreadread-only, idempotent, closed worldreadportal:auth, portal:key, key:{prefix}
get_weekreadread-only, idempotent, closed worldreadportal:auth, portal:key, key:{prefix}
get_statusreadread-only, idempotent, closed worldreadportal:auth, portal:key, key:{prefix}
get_changesreadread-only, idempotent, closed worldreadportal:auth, portal:key, key:{prefix}
list_creativesreadread-only, idempotent, closed worldreadportal:auth, portal:key, key:{prefix}
get_week_audioreadread-only, idempotent, closed worldreadportal:auth, portal:key, key:{prefix}
export_orderreadread-only, idempotent, closed worldreadportal:auth, portal:key, key:{prefix}
get_clearancereadread-only, idempotent, closed worldreadportal:auth, portal:key, key:{prefix}
acknowledge_versionwritechanges data, idempotent, closed worldacknowledgeportal:auth, portal:key, key:{prefix}, w:{prefix}
apply_log_matchesconfirmchanges data, destructive, not idempotent, closed worldreportportal:auth, portal:key, key:{prefix}, w:{prefix}
report_affidavitsconfirmchanges data, destructive, idempotent, closed worldreportportal:auth, portal:key, key:{prefix}, w:{prefix}
sign_weekreturns a linkread-only, idempotent, closed worldpeople onlyportal:auth, portal:key
list_weeksreadread-only, idempotent, closed worldreadportal:auth, portal:key, key:{prefix}
report_show_airingsconfirmchanges data, destructive, idempotent, closed worldreportportal:auth, portal:key, key:{prefix}, w:{prefix}
Tools whose description starts with Confirm record affidavits, the proof behind the network's invoices. A good host shows you each one before it runs; confirm only what you mean to report.

Limits

Each tool's description ends with its limits, the same buckets as the REST API. Over a limit the call answers rate_limited with retry_after; see Rate limits.

BucketLimit
portal:auth10 failed sign-ins a minute per network address, checked before the credential is read
portal:key60 requests a minute per credential, refilled at one a second
key:{prefix}600 calls per 10 minutes per station key over REST and MCP together, not counted for a person's connection
w:{prefix}120 recording calls a minute per credential

Results and errors

  • A success carries the answer twice, as structuredContent and as JSON text, and _meta with com.radioworkflow.affiliate/request-id (quote it when you contact the network) and com.radioworkflow.affiliate/rate-limit-remaining.
  • A refusal is isError with one text block (what happened, what to do, and the reference page) and _meta["com.radioworkflow.affiliate/error"] with code, message, http_status, retryable, retry_after when there is one, extras and docs. It carries no structuredContent. The codes are those of Errors.
  • Arguments are checked before anything is sent; a wrong one answers validation_failed naming at most four fields.
  • To repeat a recording call safely, your client sends its request id back in the call's _meta (not in its arguments) as com.radioworkflow.affiliate/request-id: the repeat returns the first result instead of recording twice.
A refused call
{
  "isError": true,
  "content": [
    {
      "type": "text",
      "text": "The week changed since you read it. Read it again, then retry. ..."
    }
  ],
  "_meta": {
    "com.radioworkflow.affiliate/request-id": "the id of this call",
    "com.radioworkflow.affiliate/rate-limit-remaining": 57,
    "com.radioworkflow.affiliate/error": {
      "code": "epoch_changed",
      "message": "The week changed since you read it. Read it again, then retry.",
      "http_status": 409,
      "retryable": false,
      "extras": {
        "epoch": 7
      },
      "docs": "https://.../docs/report-affidavits"
    }
  }
}

Resources

Each affiliation's weeks are also resources, read with the same permission and the same answer as the tool beside them. {week} is a Monday (YYYY-MM-DD) or current, the broadcast week now running in the station's time zone. The list names the three current resources of up to 50 affiliations; the templates reach every other week. Contents are application/json, with lastModified from the week's latest version when there is one. No resource carries an audio link, so the creatives in the order resource leave out audioUrl; get_week_audio returns the link to the week's audio archive.

URI templateSame answer asWhat it holds
affiliate://affiliations/{affiliation_id}/weeks/{week}/orderget_weekThe week's order as get_week answers it, without audio links: every unit with its window and affidavit, the show days, the creatives, week.epoch and each rev. Reading it records that the station viewed the version, as get_week does.
affiliate://affiliations/{affiliation_id}/weeks/{week}/changesget_changesEvery published version of the week and what changed in each, as get_changes answers it.
affiliate://affiliations/{affiliation_id}/weeks/{week}/creativeslist_creativesThe week's copy as list_creatives answers it with with_urls 0: titles, ISCIs, lengths, carts and whether the audio is ready, never an audio link. get_week_audio returns the link to the week's audio ZIP.

Subscriptions

  1. Send initialize. The answer's Mcp-Session-Id header names your session; send it on the requests that follow.
  2. Open the session's stream with GET and Accept: text/event-stream. Most hosts do this for you.
  3. Call resources/subscribe with a resource URI. A current URI follows the week that is current when you subscribe; the answer's _meta names that week's URI, and so do its notifications. The server reads the resource as you subscribe and compares each later check with that read.
  4. While the stream is open, the server checks each followed week about every 60 seconds and sends notifications/resources/updated with the URI of each resource that changed (a new version, or audio that arrived or went). Read the resource again to see what changed.
  • A session holds at most 20 subscriptions; a credential follows at most 10 affiliations and weeks at once (the order and changes of one affiliation count once), keeps at most 5 sessions (a new one ends its least recently used) and 2 open streams.
  • The server closes a stream after 280 seconds; open it again. A session unused for 30 minutes ends.
  • A session lives on one server. If a request reaches another one, or the server restarts, the stream and the subscriptions answer 404: start a new session and subscribe again. Tool calls, reads and prompts never need a session.
  • Subscriptions are best effort. To hear about every change, reliably and without a connected assistant, use webhooks.

Prompts

Two prompts walk an assistant through the usual jobs, naming the tools to call in order. Both end with the signing link: a person with the sign role signs the week in the portal.

PromptArgumentsWhat it does
file_this_weeks_affidavitsaffiliation_id, week (optional)Walks through reporting when each unit and program aired for one affiliation and week, confirming each batch with you, and ends with the link where a person with the sign role signs the week in the portal.
what_is_still_dueaffiliation_idLists what one affiliation still has to report, acknowledge or sign, and by when, without changing anything.

Troubleshooting

AnswerWhat it meansWhat to do
401The credential is missing, expired or revoked. The WWW-Authenticate header points at https://affiliates.radioworkflow.com/.well-known/oauth-protected-resource.Reconnect so the assistant signs you in again, or check the key under API and MCP in the portal.
404 on GET, DELETE or a subscriptionThe session is not on the server that answered: it ended, the server restarted, or the request reached another server.Start a new session with initialize and subscribe again. Tool calls never need a session.
409 on GETThe session already has its stream open.Use the open stream, or close it before opening another.
429A stream cap (with Retry-After: 60) or a rate limit. A tool call over a limit answers rate_limited with retry_after.Wait the time given, then retry. Streams are not needed for tool calls.
413The message is over 1 MB.Split a report into smaller batches.
503 demo_modeThis copy of the portal runs on demo data, so MCP is off.Connect to the live portal.