MCP overview
Connect an assistant with OAuth or an API key, inspect tool schemas, and control workspace permissions.
Skysay speaks MCP (Model Context Protocol). An MCP-capable client points
at the hosted endpoint, connects with OAuth or a scoped API key, and the Skysay tools appear in
tools/list — ready to call. The client setup recipes configure this endpoint;
the REST API exposes the same capabilities.
The endpoint
POST https://api.skysay.ai/mcp
Authorization: Bearer sky_your_scoped_keyStandard JSON-RPC: tools/list to discover, tools/call to invoke.
Choose the profile for the connection:
| Profile | URL | What it can do |
|---|---|---|
| Full integration | https://api.skysay.ai/mcp | All supported tools, including separately approved wallet funding. |
| OpenAI connector | https://api.skysay.ai/mcp/openai | Calling and the other supported tools, without wallet top-ups. |
| Read-only directory connector | https://api.skysay.ai/mcp/read-only | Five workspace read tools, no calls or writes. |
These are server-enforced profiles, not permissions selected by a client's name. Directory approval and publication are separate from endpoint availability.
Connect with OAuth
For an assistant that supports remote MCP authorization, enter
https://api.skysay.ai/mcp as the server URL and choose OAuth. Sign into
Skysay, check the client and workspace shown on the consent screen, and approve
the permissions you want to grant. Adding a custom connection does not require
Skysay to be listed in that assistant's public directory.
The initial permissions read workspace, agent, call metadata, coverage, number and onboarding information, and evaluate policy. Reading conversation content or performing supported writes requires additional consent. Check each consent screen: some permissions allow actions that contact people or spend your Skysay balance. A connection cannot access a different workspace by passing its organization ID.
Manage and revoke connections from Connected apps on the API keys page in the Skysay workspace. Revocation invalidates the connection's tokens. Existing API keys continue to work independently and must be revoked separately.
Verified email signup during OAuth
A supported MCP client opens Skysay's existing verified email OTP or supported social sign-in. Complete verification in the browser, then review the client, workspace and permissions and choose Allow access or Deny. Verification alone grants no MCP access. PKCE returns to the originating registered client. No arbitrary HTTP tool, custom bearer header or API key is needed for signup. Reconnecting uses the same workspace without another welcome grant.
Unverified contact email never grants access to a stored phone identity or links its workspace. Existing phone-owned API keys remain valid until revoked, but phone browser sign-in is deferred with SMS signup. Cross-account linking is not provided. Use your verified email/social account's own workspace. Expired or used connection requests must be restarted from the client.
The single $5 welcome credit has no expiry and applies only to authenticated
metered browser testing in Skysay. Number setup/rental, PSTN and SMS require
purchased funds and ordinary readiness and metering. Optional approved funding
is available on the full /mcp resource without a phone identity through create_wallet_topup with
explicit wallet:write consent. The permission is offered unticked and approves
no purchase. Each payment still needs approval of the exact amount, wallet
currency and purpose. The tool returns a direct Stripe hosted-checkout URL, or
accepts an owner-approved shared payment token from a compatible host/wallet.
It uses the same billing service as the retained payment API; the historical
headless URL does not enable signup. See payment paths and limits.
Client connection support and directory permissions remain separate. The public Claude listing remains read-only; calling requires a separately allowed full connection with consented calling scopes. OAuth does not itself approve payments, number purchases or billable calls. See Billing.
create_wallet_topup is absent from the OpenAI and public read-only connectors. MCP tool
availability does not establish native payment support in ChatGPT, Claude,
Codex or Grok. This server returns ordinary tool results and does not negotiate
or send MCP elicitation requests. A checkout link opens Stripe in a browser;
JSON seller/approval metadata is not a native payment card. Actual entirely
in-chat and default-card merchant acceptance remain unproven.
OAuth client configuration
| Setting | Value |
|---|---|
| MCP resource | https://api.skysay.ai/mcp |
| Authorization server | https://api.skysay.ai |
| Resource discovery | https://api.skysay.ai/.well-known/oauth-protected-resource/mcp |
| Authorization server discovery | https://api.skysay.ai/.well-known/oauth-authorization-server |
| Authorization endpoint | https://api.skysay.ai/oauth/authorize |
| Token endpoint | https://api.skysay.ai/oauth/token |
| Dynamic registration endpoint | https://api.skysay.ai/oauth/register |
| Revocation endpoint | https://api.skysay.ai/oauth/revoke |
Use the authorization-code flow with PKCE S256 and public-client token
authentication (none); no client secret is required. Both Client ID Metadata
Documents (CIMD) and dynamic client registration (DCR) are supported. CIMD
documents may advertise supported authentication methods as a list containing
none, even when their legacy preference is a different method. Redirect URIs
must match the client's metadata or registration. Authorization responses
include the issuer (iss).
Access tokens expire after one hour. Refresh tokens rotate, expire after
30 days without renewal, and have a maximum family lifetime of 90 days.
Reconnect when the client asks you to sign in again. An unauthenticated MCP
request returns a 401 discovery challenge; a supported tool that needs more
consent returns a 403 scope challenge. Only the scopes advertised by the
authorization server can be granted through OAuth; the API-key catalogue is
broader. Skysay does not currently provide OpenID Connect UserInfo or the
openid and email scopes for ChatGPT Enterprise workspace-domain restrictions.
Permissions, consent and reconnecting
The resource discovery document lists every scope an OAuth client can be
granted in scopes_supported, including calls:write (place calls). A client
may ask for any of them; if it asks for nothing, it gets the read set.
The consent screen shows each permission as a checkbox. What is ticked when you press Allow access is exactly what the app gets:
- what the app asked for starts ticked;
- what you already allowed it starts ticked, under Already allowed;
- Place calls is offered unticked under You can also allow when the app did not ask for it, so an assistant that never completes a scope step-up can still be allowed to place calls.
- Add purchased funds (
wallet:write) is also offered unticked on a full connection when neither requested nor already allowed. It enablescreate_wallet_topup; it approves no payment. Each purchase needs separate owner approval of the exact amount, currency and purpose. It is unavailable on the OpenAI and read-only connectors; see funding paths and limits. - Transcripts (
recordings:read) is offered unticked on a full connection when neither requested nor already allowed. This is the existing combined permission for call and simulation transcripts, recording details and evidence that quotes conversation content—not a separate transcript scope. Leaving it unticked does not grant content access.
Connecting the same app again never takes a permission away on its own: approval keeps everything already allowed and adds what was asked for, so tokens issued earlier keep working. A permission is removed only when you untick it (or disconnect the app under Connected apps).
When a tool needs a permission the connection lacks, the 403 scope challenge
names it in WWW-Authenticate and in the body:
{
"error": "InsufficientMcpScope",
"message": "additional consent is required for this tool: calls:write",
"missing_scopes": ["calls:write"]
}Reconnect and tick the named permission. If a transcript request repeatedly asks you to reconnect, check that the consent screen shows the intended workspace and Transcripts is selected before approving. Some clients repeat their initial permission request instead of requesting the missing scope; the optional checkbox lets you approve it on the full connector. Already-approved access is retained only for the same user, workspace, OAuth client and resource. A newly authorized connection can use its approved scopes; an older, narrower token does not gain permissions merely because another connection was authorized.
The read-only connector below does not offer transcript or recording access.
OpenAI connector: calling without wallet top-ups
Use https://api.skysay.ai/mcp/openai for the OpenAI connector profile. It keeps
calling enabled: approve Place calls (calls:write) for the intended
workspace, then explicitly approve each call's caller, recipient, purpose and
expected cost. Normal number readiness, destination policy and available
purchased funds still apply. The $5 welcome credit remains for browser calls
only, not phone-number rental, PSTN calls or SMS.
This profile does not list or execute create_wallet_topup. It does not offer
wallet:write at consent and refuses attempts to request it. Hiding a tool in
the client is not the security boundary: even a broad API key cannot invoke
the top-up tool on this endpoint.
Its resource discovery document is
https://api.skysay.ai/.well-known/oauth-protected-resource/mcp/openai, and its
OAuth resource is https://api.skysay.ai/mcp/openai. Use this resource throughout
authorization, token exchange and refresh. The authorization server and PKCE
flow are the same as above. Tokens for /mcp/openai, /mcp and /mcp/read-only
are not interchangeable; switching profiles requires a new authorization for
the target resource. Creating that connection does not modify or revoke an
existing full connection.
Other integrations using /mcp, including Grok, retain their existing payment
tools and consent rules. This profile does not change Claude's read-only
directory listing, and its existence is not a claim of OpenAI directory approval
or native in-chat payment support.
Read-only connector for Claude
Use https://api.skysay.ai/mcp/read-only for a connection that can inspect
workspace data without generating audio or performing writes. It exposes
exactly five tools:
account_overview: a short workspace summary — wallet, usage totals, and for each collection its total with the newest few items.list_agents: list your agents, one page at a time.get_agent: read one agent's configuration.list_numbers: list your phone numbers, one page at a time.list_calls: inspect call metadata one page at a time, without recordings or transcripts.
Each of the four collection tools answers with at most 25,000 characters, so a large workspace is read in pages rather than in one oversized answer; see Bounded results and paging.
All other tool calls are refused at this endpoint, even with a broad API key.
OAuth consent accepts only account:read, agents:read, calls:read, and
numbers:read. Its discovery document is
https://api.skysay.ai/.well-known/oauth-protected-resource/mcp/read-only,
and its OAuth resource is https://api.skysay.ai/mcp/read-only. Tokens for
this resource cannot be used at /mcp, and tokens for /mcp cannot be used
at the read-only endpoint. Both connections use the same sign-in, PKCE, consent,
refresh and revocation flow described above.
Try “Show my Skysay workspace overview,” “List my Skysay agents,” or “Show my Skysay phone numbers.” A new workspace may return empty lists; the connector will not create sample data or provision numbers to fill them.
Connect from the workspace
The workspace's API keys page creates scoped keys. Its server entry is connection information, not a universal client configuration. Use the Claude Code recipe or Codex recipe for the required transport and credential fields; other clients have their own setup and OAuth support. Do not paste a key value into client configuration; use the recipe's environment-variable reference or its OAuth flow.
Use https://api.skysay.ai/mcp for the full resource, or
https://api.skysay.ai/mcp/read-only for five read tools and no writes. The
OpenAI profile at https://api.skysay.ai/mcp/openai keeps calling but excludes
wallet funding. API keys
and OAuth grants are independent. Revoking one does not revoke the other.
Results, errors and tool metadata
Tool calls return a text content block containing JSON and the same data in
result.structuredContent. A tool that answers with a plain list uses
{ "items": [...] } in structured content; the paged collection tools
(list_agents, list_calls, list_numbers) answer with a named array and a
pagination object instead. A refused tool returns result.isError: true and
structuredContent.error with type and message. Unexpected failures use
internal_error and a request_id to quote to support. An unknown tool, or a
params, name or arguments that is not the right shape, remains a JSON-RPC
error (-32602); anything that goes wrong once the tool runs is a refused tool
result.
Every tool has a title and explicit boolean readOnlyHint, destructiveHint,
openWorldHint and idempotentHint annotations. Creating a new draft record is
a write, but not inherently destructive; spending funds, sending messages and
ending calls are consequential actions. A fixed provider handling bounded
workspace data is not open-world merely because it is remote. These hints do
not grant permissions or replace approval. Required scopes are in
_meta["ai.skysay/required_scopes"]; alternatives are in
_meta["ai.skysay/alternative_required_scopes"]. The catalogue lists all tools,
even when your credential lacks their scopes. A call whose credential lacks a
tool's scopes fails closed with a refused tool result naming the missing scopes.
organization_id is optional on all tools and defaults to your credential's
workspace. A different workspace is refused. Streamable HTTP negotiates
2025-11-25, 2025-06-18 or 2025-03-26 through initialize. Notifications receive
202 with no body; ping is supported; GET and HEAD return 405, with no SSE stream.
The endpoint also speaks MCP 2026-07-28, which has no handshake. A client using it
sends MCP-Protocol-Version: 2026-07-28 and Mcp-Method headers on every request
(plus Mcp-Name for tools/call and resources/read), with the protocol version and
client capabilities in params._meta. server/discover lists the supported revision;
every result carries resultType, and list and read results carry ttlMs and
cacheScope. A request that revision does not define, such as ping, returns 404 with
-32601; a header that disagrees with the body returns 400 with -32020, and an
unsupported version returns 400 with -32022 and the supported list. Requests without
that header keep the handshake-era behavior only when their body does not explicitly
claim a modern version. A modern body with a missing or legacy version header is
rejected before any tool runs; removing the header cannot bypass modern validation.
Non-browser clients may omit Origin. If a client supplies it, either MCP endpoint
accepts only the origins of the deployment's configured MCP resource URL and OAuth
consent URL; an invalid, duplicate or unapproved origin returns HTTP 403 before the
request body is read. The hosted defaults are https://api.skysay.ai and
https://skysay.ai. Self-hosted deployments derive these origins from
SKYSAY_MCP_RESOURCE_URL and SKYSAY_OAUTH_CONSENT_URL, never from a request's Host or
forwarded headers.
These refusals follow the official Streamable HTTP transport specification: supplied origins are validated and contradictory version headers/body claims are refused before tools run.
Bounded results and paging
account_overview, list_agents, list_calls, list_numbers,
list_contact_lists and list_contacts grow with your workspace, so each
answers with at most 25,000 characters of result text, however large the
workspace is. list_contact_lists takes limit 1 to 50 (default 20) and
list_contacts 1 to 100 (default 25); both page with cursor the same way.
The three list tools return one newest-first page:
{
"calls": [ ... ],
"pagination": {
"limit": 10,
"returned": 7,
"has_more": true,
"next_cursor": "<opaque>",
"omitted": []
}
}limittakes 1 to 50.list_callsandlist_numbersdefault to 10,list_agentsto 50. A larger value is lowered to 50 and a smaller one raised to 1; the page reports the limit it used.- A page holds at most
limititems and at most 25,000 characters, so it can hold fewer.returnedsays how many it holds. Usehas_more, neverreturned < limit, to decide whether to continue. - Pass
next_cursorback ascursorto read the next page. It is meaningful only whilehas_moreis true, and it is opaque: pass it back exactly as given. Leavecursorout for the first page. - An item too large to fit any page on its own is not returned. It is named in
omittedas{ "id", "reason": "exceeds_result_size_budget", "read_with" }, and the next page continues after it.read_withnames the tool that reads that one item on this connection (get_agentfor an agent,get_callfor a call on the full/mcpendpoint), or isnullwhen the connection has none. - A listing is not a snapshot: an item created while you page through it may not appear, and nothing is repeated or skipped because of it.
list_callsitems are the same call recordsGET /v1/callsreturns, withoutruntime_config(the agent configuration the call ran with).get_callreturns the complete record.
account_overview is a summary, not a page. It returns wallet; usage with
its totals and up to 10 line items; and, for agents, numbers,
number_requests, calls and conversations, a section with:
total: how many there are;preview: up to 5 of the newest, with identifying fields only (for example an agent'sid,name,modeandstatus; a call'sid,direction,status, numbers andcreated_at; a conversation's numbers,status,last_message_atandmessage_count);returned,has_more(totalis larger thanreturned) andomitted;list_tool: the tool on this connection that pages through the complete records, ornullwhen there is none (number requests; conversations on the read-only connector). It needs its own scope.
The overview never includes SMS message content. Read messages with
list_conversations (sms:read).
Migration for custom MCP clients
Read result.structuredContent instead of result.content[0].json, inspect
result.isError and structuredContent.error.type instead of error.type, and
read scope metadata from _meta instead of annotations.required_scopes.
Standard MCP clients already understand these result types.
Since the bounded results above:
list_callsandlist_numbersreturn{ calls | numbers, pagination }instead of a list (structuredContent.items). Follownext_cursorwhilehas_moreis true to read everything.account_overviewreturns sections withtotalandpreviewinstead of complete lists of agents, numbers, number requests, calls and conversations; itsusagecarries totals and up to 10 line items.list_callsitems no longer carryruntime_config; useget_call.list_agentspages can now stop beforelimitand can name items inpagination.omitted.limitmust be an integer (10,10.0or"10"). A boolean, a fraction or other text is refused instead of being coerced. Acursormust be one this tool returned.account_overview,list_callsandlist_numbersare refused for a credential that belongs to no workspace, aslist_agentsalready was.
Prefix scopes
Some scopes are grouped by prefix — for example sms:* covers both sms:read
and sms:send. Grant the specific leaf scope for least privilege, or the prefix
when an agent legitimately needs the whole group.
Capabilities that a deployment can switch off
A few tools belong to capabilities an operator enables per deployment — the
Simulations Lab is one. tools/list publishes them
regardless, because it is a catalog of what Skysay can do rather than an
inventory of what your instance has switched on. Calling one where the
capability is off returns the same not-found the equivalent REST route returns.
When a tool call is refused
A refused call is a tool result with isError: true, so the assistant reads
it and can correct itself. structuredContent.error names the refusal's
type and message. When the same request over REST would answer with a
structured body, that body is under error.body: for example, the issues
of a refused configuration, or the current revision after a conflict. A
missing MCP scopes or workspace refusal carries no body.
Configure an agent from MCP
An organization-scoped key with agents:read and agents:write can complete
the normal customer configuration loop without switching to REST:
- Call
get_agent_catalogto discover selectable STT, LLM, and TTS options plus conversation engines. It accepts eitheragents:readorcoverage:read; usemetered_only: truewhen you need only component-metered choices. Passvoice_frontend: "gpt_live"oncreate_agentorset_agent_voice_stackto select GPT-Live 1; omit it to keep Standard. - Call
create_agent, then uselist_agentsfor a bounded newest-first page orget_agentfor one current safe agent projection. - Use
update_agentfor the ordinary mutable fields andset_agent_voice_stackto publish a selected stack. Read the published configuration back withget_agent_voice_stack. - Use
preview_voiceonly as an explicit Listen action. It can contact a provider or validate a BYOK credential, is rate-limited per workspace, and may write safe preview-rate or voice-availability state. It is not a read-only or free preview.
list_agents is deliberately a bounded MCP collection: it returns the same
safe agent items as REST inside { agents, pagination }, with up to 50 agents
per page (the default and maximum), and fewer when the page reaches its
25,000-character limit. Follow pagination.next_cursor only while
pagination.has_more is true. REST also returns a named bounded page; its limits
and migration contract are documented in REST endpoints.
list_audio_environments is the same catalogue as
GET /v1/audio-environments, with the same account:read scope. It is a
read-only, provider-independent discovery call: it returns Off plus the
currently packaged versioned presets and never changes an agent or starts a
preview. Use its exact preset ID and asset version when configuring output
ambience or a Simulation Lab caller condition; do not maintain a client-side
copy of the catalogue.
The key's organization is authoritative. You may omit the redundant
organization_id input from create, list, and preview calls; if supplied, it
must match the key's workspace. Agent IDs, optional project filters, saved
voice pins, preview rate limits, credential ownership, and provider/BYOK
validation stay in the same services as the REST workflow.
Browser sessions stay a browser workflow
MCP intentionally does not expose browser-session create or settle. Those
commands require calls:write plus a browser-owned LiveKit/audio participant
and that client's later settlement signal. Use the browser or the REST
browser-session workflow for an audio test.
For local stdio MCP, set both SKYSAY_MCP_SCOPES and
SKYSAY_MCP_ORGANIZATION_ID before calling any tool. An absent organization fails closed; tools/list remains
schema discovery only.
Tool reference
Every tool, its scope, and what it does.
Scopes & permissions
The full scope catalog and fail-closed rules.
Superseded: /docs/agents
This page replaces the old /docs/agents stub. Its MCP-endpoint, OpenAPI, and
scoped-key material now lives here and in
Authentication & keys.
Content fields
Conversation content needs its content scope wherever it appears, over MCP and REST. Tools that combine content with other data stay callable at their base scope and omit the content fields:
account_overview(account:read) previews conversations without any message, so it never carries a message'sbody,metadata,errororidempotency_key, whatever the scopes.send_sms(sms:send) andsend_agent_template_message(sms:send+agents:read) return the message ID and delivery status;body,metadata,errorandidempotency_keycome back only withsms:read. This includes a retry that replays an existingidempotency_key.get_simulation_run(agents:read) returns status, verdicts and pagination. Withoutrecordings:readit omits each attempt'stranscript,recording_refandfacts, assertionactualvalues, rubricrationaleand linked-evidencenote, because each can quote the conversation.
Content-bearing webhook subscriptions
Creating or changing a webhook subscription for sms.received requires
sms:read. Subscribing to call.extraction.ready
requires recordings:read, because extracted results can quote the conversation.
Changing any existing webhook destination requires both sms:read and
recordings:read: queued deliveries and concurrent subscription changes can
contain either kind of content. Removing a subscription does not make its queued
content safe to redirect. Agent names and lifecycle-only subscriptions
remain editable with agents:write.
Over MCP, a call lacking one of these content scopes receives a refused tool
result naming the missing scope, before anything is written.