A coach can point an MCP client, such as an AI assistant, at Perform and let it search trainees, assign plans and create tasks. The endpoint needs nothing installed: a URL and a studio API key. Mount: /mcp, outside /v1. Router mcpRouter in src/modules/mcp/mcp.routes.ts, mounted in src/app.ts behind the same rate limiter instance as /v1. Tool definitions live in the package @perform/mcp-tools (backend/packages/mcp-tools/src/tools.ts), which a stdio build of the server shares. Production URL: https://perform-api.otherwise.co.il/mcp

How it works

The endpoint is a stateless MCP server over Streamable HTTP, built with @modelcontextprotocol/sdk.
  1. Each POST /mcp request builds a new McpServer named perform (version 1.0.0) and a new StreamableHTTPServerTransport with no session id generator. There is no session, no stream to resume and no affinity between requests.
  2. Every tool from @perform/mcp-tools is registered with its Zod input schema and annotations (readOnlyHint from the tool, destructiveHint: false, openWorldHint: true).
  3. When a tool is called, the handler does not touch the database. It builds an HTTP request and sends it to /v1/automation on the same process, at http://127.0.0.1:<PORT>/v1/automation, with the caller’s own API key.
  4. The automation lane’s data is returned to the model as pretty-printed JSON text. An error from the lane is returned as a tool result with isError: true and the lane’s message as text.
That extra local hop is deliberate. The MCP surface cannot drift from the public API, and tool calls inherit the automation lane’s validation, studio scoping, audit log and readable error sentences. Everything on Automation API applies to the matching tool.

Authentication

Send a studio API key as a bearer token. It is the same key used for the automation and partner lanes, created by an OWNER or HEAD_COACH. See Partner API for key management.
The router itself only checks that a bearer value is present and starts with pf_live_. The key is really verified when a tool makes its call to /v1/automation. So a well-formed but invalid key can connect and list tools, and every tool call then fails with invalid partner api key. A typical client configuration:
The exact configuration format depends on the MCP client.

Endpoints

POST /mcp

Handles one MCP JSON-RPC message: initialize, tools/list, tools/call and the rest of the protocol the SDK implements. Auth: studio API key. The body is a JSON-RPC 2.0 message. The response is JSON-RPC, as JSON or as a server-sent event stream, depending on what the transport negotiates with the client’s Accept header. Listing tools:
Calling a tool:
A successful tool result carries the automation lane’s data as text:
A failed tool call is still a JSON-RPC success, with the error inside the result so the model can read it and recover:
Errors:

GET /mcp

Not supported. The server is stateless, so there is no stream to open. Response: 405.
No other method is registered on /mcp.

Tools

The tool set is limited on purpose. A model acts without a person approving each step, so anything that destroys data or reaches a trainee’s phone is left out. The comment at the top of tools.ts lists what is excluded: deleting a trainee, sending a message, freezing a plan and cancelling a plan. Webhooks are not exposed either.

Read tools

limit is 1 to 200 and offset is 0 or more on every tool that takes them.

Write tools

Tool inputs are validated twice: by the tool’s own Zod schema in the MCP layer, then by the automation lane’s schema. Tool schemas use real JSON types (numbers, booleans, arrays), not the string coercion Make needs. send_form with notify: true is the one tool that can push a notification to a trainee’s device straight away. A PDF_SIGNATURE form sends nothing and returns a signing link for the coach to deliver.

Limits and operations

  • Rate limit. /mcp uses the same limiter instance as /v1: 120 requests per 60 seconds per IP by default (RATE_LIMIT_MAX, RATE_LIMIT_WINDOW_MS).
  • Internal calls are also limited. A tool call makes a second request to /v1/automation from 127.0.0.1. That request passes through the same limiter keyed by its own source address, so tool calls from every MCP user share the loopback address’s bucket. Under heavy combined use, tool calls can be throttled even when each client is under its own limit.
  • Body size. The global 24 MB JSON limit applies.
  • Audit. Writes are recorded in AuditLog by the automation lane with the API key id as the actor. Nothing marks an entry as having come through MCP.
  • Revoking access. Revoke the API key. The next tool call fails.
  • Logging. A failure inside the handler is logged as mcp request failed.

Adding a tool

  1. Add the endpoint to the automation lane first, with its schema, service function and readable errors.
  2. Add a ToolDefinition to backend/packages/mcp-tools/src/tools.ts: name, title, description, readOnly, a Zod inputSchema and a call function that returns the method, path and body.
  3. Decide what the tool must force. Anything that notifies a trainee should default to silent.
No change is needed in mcp.routes.ts. It registers every tool in the exported list.