MCP server
Let AI agents such as Claude Code, Cursor and OpenAI's models use your clients' connections as tools.
The MCP server gives an AI agent your clients' connections as tools: list clients, find and run any of 89,000+ actions, call any endpoint. The agent holds an Operate As key, never a client's token. Every tool call goes through the same key limits, plan allowance and usage counting as the HTTPS API.
Connect your agent
The server is at https://api.operate.as/v1/mcp, with your key as Authorization: Bearer oa_....
claude mcp add --transport http operate-as https://api.operate.as/v1/mcp \
--header "Authorization: Bearer oa_..."Then ask "Which of my clients have Sheets connected?" and it calls list_clients.
Tools
There are thousands of actions, far more than an agent can be handed at once: a list that long fills its context and makes it worse at choosing. So the server lists a few tools that find and run the rest, and next to them the actions of the services your clients have connected.
| Tool | What it does | Arguments |
|---|---|---|
list_clients | The clients this key can use, with their connected services | None |
search_tools | The actions that best match a few words, with their inputs | query, and optionally service and limit (10, up to 50) |
get_tool | One action's inputs, with their full JSON Schemas | tool |
run_tool | Runs any action for one client | client_id, tool, and input with the action's inputs |
One per listed action, such as sheets_read_rows | Runs that action for one client | client_id, plus the action's inputs |
request | Any endpoint of a service's own API, like a pass-through request | client_id, service, method, path, query, body |
- Which actions are listed. Those of the services the key's clients have connected, up to 100 in all, smallest services first. A service with more than fits, such as GitHub's 1,200, lists only its hand-written actions; the agent finds the rest with
search_tools. Connect a new service and the list grows the next time the agent asks for it. search_toolssearches what's connected. Withoutservice, it searches every service the key's clients have connected. Passserviceto search one, connected or not.- Any action can be run.
run_tooltakes the name fromsearch_tools, with a dot (stripe.post_customers) or an underscore (stripe_post_customers), and an action can also be called directly by its underscore name even when it isn't listed. - Names use underscores in tool names, since MCP tool names can't hold dots:
sheets.read_rowsissheets_read_rows. requesttakesmethod(GET,POST,PUT,PATCH,DELETE),pathunder the service's API such as/v4/spreadsheets/1BxiM..., an object ofqueryparameters and a JSONbody.- The server sends instructions: call
list_clientsfirst, use a listed tool when one fits, otherwise find one withsearch_tools, read it withget_tooland run it withrun_tool.
Errors the agent can read
A failed tool call doesn't break the conversation with a protocol error. It's a normal tool result marked isError: true, in plain words:
{
"content": [{ "type": "text", "text": "Miller Dental's Google access ended. Send them their link to reconnect." }],
"isError": true
}So the agent can react: tell you the client must reconnect, pick another client, or fix an input and retry. When the service itself answers 400 or more, the result holds its status and data as JSON, also marked isError: true.
A missing, wrong or revoked key is refused before MCP starts, with HTTP 401, so the client reports a connection failure.
Protocol details
| Detail | How it works |
|---|---|
| Transport | Streamable HTTP, answered as plain JSON on the same POST. No server-sent event stream, so GET answers 405 Method Not Allowed |
| Protocol versions | 2025-06-18, 2025-03-26, 2024-11-05. Ask for one and you get it; otherwise 2025-06-18 |
| Batches | A JSON array of messages gets an array of answers |
Give each agent its own key
An agent decides for itself what to call, so give it only what its job needs.
| Limit | Use it to |
|---|---|
| Clients | Keep an agent that works for one client away from the rest |
| Services | Allow only, say, calendar and gmail for a booking assistant |
| Actions | Keep it read-only: sheets.read_rows without sheets.add_row, and no <service>.request so it can't use request |
The agent only sees what its key may use: list_clients, the listed tools and search_tools all leave out the rest. Calling a tool outside the key's limits returns an error result like This key can't use hubspot. Set all three limits in the dashboard's create form by choosing Restricted. See API keys.
Example conversation
- You: Add Jane Doe's cleaning on 16 October to Miller Dental's bookings sheet.
- Tool call
list_clients: Finds Miller Dental, withsheetsconnected. - Tool call
sheets_add_row:client_id,spreadsheet_id,range: "Bookings!A1",values: ["2026-10-16", "Jane Doe", "Cleaning"]. The result has"status": 200and the updated range. - Agent: Done. I added the row to Miller Dental's bookings sheet at row 12.
Had Miller Dental's Google access ended, the tool result would say so, and the agent would tell you to send them their link instead of failing silently.
When no listed tool fits, the agent searches:
- You: Open a GitHub issue on Miller Dental's website repo: the booking form breaks on Safari.
- Tool call
search_tools:query: "create github issue"findsgithub.issues_createfirst, with its inputsowner,repoandbody. - Tool call
run_tool:client_id,tool: "github.issues_create",input: {"owner": "millerdental", "repo": "website", "body": {"title": "Booking form breaks on Safari"}}. - Agent: Opened issue #214 on millerdental/website.