Serve tools over MCP
Serve my workspace's tools over MCP
Put my Speechify OpenAPI tool behind a hosted API with the MCP face switched on: create the API with a keyed audience in the tool’s project, add a tool route for each read operation with a clear name and description, switch on mcp\_enabled, issue a consumer key, then attach the API’s /mcp address to this coding agent and list its tools.
Follow [https://docs.speechify.ai/agents/guides/serve-tools-over-mcp.md](https://docs.speechify.ai/agents/guides/serve-tools-over-mcp.md).Hosted APIs are in beta and enabled per workspace.
Without the grant every /v1/apis endpoint here answers 402 hosted_apis_not_in_plan.
A hosted API can serve its routes as an MCP server at POST https://<slug>.apis.speechify.ai/mcp.
One address gives an MCP client the connectors, stores and runs your workspace assembled, under the same audience, keys, limits and daily caps as the routes themselves.
Every tool call runs through the same path an ordinary HTTP request to the route takes, so nothing the route enforces can be skipped over MCP.
What you end up with
- A hosted API,
acme-tools.apis.speechify.ai, that only callers holding its key can reach. - A
toolroute for each vendor read operation, and any store or run routes you want beside them. - An MCP client, such as Claude Code or Cursor, listing those routes as tools.
This page assumes an openapi tool already exists; Connect a REST API builds one.
1. Create the API
- Create it in the tool’s project. A
toolroute refuses a tool from another project with409 cross_project_reference. - Pick an audience that names its callers.
consumer_key(ack_key you mint),workspaceorowner(a Speechify API key of your workspace) anduser_token(a JWT your backend signs) all work.publicis refused withmcp_enabled, and it never serves atoolroute. - Write
nameanddescriptionfor a model. On the MCP face,nameis the server’s title anddescriptionis the instructions the client’s model reads before it chooses a tool.
mcp_enabled can also be switched on later with PATCH /v1/apis/{api_id}.
Allow up to 15 seconds for the switch to reach every server.
2. Add a tool route
operationis theidof one of the tool’soperations.- A tool route is always a
POST: its JSON body is the operation’s arguments, checked against the operation’s own argument schema. - The operation must be classified
read, and the tool’sapprovalmust be null orauto, because a route has nobody to approve a call. A route write that breaks either rule is refused with400 validation_failed, and the message names the fix. Every call checks both again: reclassifying the operation, or giving the tool any otherapproval, turns the route into a403 route_tool_not_readableuntil you change it back. - Deleting the tool, moving it to another project, or removing the operation turns the route into a
409 route_tool_unavailable, which no retry clears: point the route at a tool that has the operation. - The paths
/mcp,/openapi.jsonand/_runs, and everything under them, are reserved for the platform, so a route cannot use them. - The response is the vendor’s answer after the operation’s
responsemapping, or{"text": ...}when the vendor answered text. - Every call counts against the API’s
daily_read_capand the tool’smax_requests_per_minute.
On the MCP face the tool’s name comes from the route’s name: every run of characters other than letters, digits, _ and - becomes _, leading and trailing _ are dropped, and the result is cut to 64 characters.
A route with no name is listed under a name built from its method and path, such as post_issues_open, and a name two routes would share takes _2, _3 in route order.
The face’s tools/list is the authority, so read names from it rather than deriving them.
The route’s description is what the model reads to choose the tool, falling back to the operation’s summary when empty.
A vague route name costs you more here than anywhere else, because a model picks from names and descriptions alone.
Every other enabled route is a tool too: a store query, an aggregate, a write or a run, each taking the parameters the route binds from a request.
A file route is not listed, since a tool result cannot carry a file’s bytes.
3. Issue a key
The response carries the ck_ secret once; store it now.
rate_per_minute bounds each key, and a client over it gets 429 on its next request.
On a workspace or owner API skip this step: a Speechify API key of your workspace is the credential.
4. Attach an MCP client
The face speaks MCP over stateless streamable HTTP and takes POST only.
A client that finds the API some other way can read the address from the API’s OpenAPI document: GET https://acme-tools.apis.speechify.ai/openapi.json carries it as x-speechify-mcp.url whenever the face is on.
What a call looks like to the client
- A route that answers successfully is a tool result whose text is the route’s JSON, with the same JSON as structured content when it is an object.
- A route that refuses answers a tool result with
isError: trueand the error envelope as its text, such asroute_upstream_rate_limited. The client’s model reads the code and can wait or change its arguments. - A run or write tool takes an optional
idempotency_keyargument, sent as the route’sIdempotency-Key, so a client retry replays the first answer instead of acting twice. - A tool route is annotated read-only, and tool and run routes are annotated as reaching beyond the API, so a careful client can decide what to confirm with its user.
Errors
Where the rest lives
- Building the tool the route calls: Connect a REST API.
- Store, write and run routes, audiences and caps: Build a dashboard on the platform alone.
- Every error code: Error handling.