SOIT Docs
Capabilities

MCP Server

Serve a workspace's governed tools to Claude Code, Cursor, and other MCP clients at /mcp.

Since v1.3.0, SOIT is itself an MCP server. It serves each workspace's tools to MCP clients — Claude Code, Cursor, the official SDKs, and any other client of the streamable HTTP transport — at /mcp. A client connects with a SOIT API key; the tools it is offered are the ones that key may invoke, and every call it makes is a governed run, the same as a direct tool call.

This page is about MCP clients calling SOIT. For SOIT calling external MCP servers as plugin tools, see MCP and Skills.

Connecting

SettingValue
URLThe API address plus /mcp, for example https://soit.example.com/mcp
TransportStreamable HTTP
CredentialAuthorization: Bearer sk_…, a SOIT API key

The key names the workspace. A signed-in session works too, with the workspace in X-Workspace-Id. Listing and calling tools take a credential that may write in the workspace (a Dev, Admin, or Owner, and a key with the write scope); a read-only credential is offered no tools. Settings › API lists where clients connect, with the Claude Code command.

Claude Code

claude mcp add --transport http soit http://localhost:9200/mcp \
  --header "Authorization: Bearer $SOIT_API_KEY"

Or share it with a project in .mcp.json, which Claude Code reads from the project root and expands ${SOIT_API_KEY} in:

{
  "mcpServers": {
    "soit": {
      "type": "http",
      "url": "http://localhost:9200/mcp",
      "headers": {
        "Authorization": "Bearer ${SOIT_API_KEY}"
      }
    }
  }
}

Cursor

Put this in .cursor/mcp.json (one project) or ~/.cursor/mcp.json (every project). Cursor expands ${env:SOIT_API_KEY}:

{
  "mcpServers": {
    "soit": {
      "url": "http://localhost:9200/mcp",
      "headers": {
        "Authorization": "Bearer ${env:SOIT_API_KEY}"
      }
    }
  }
}

Other clients

examples/mcp/ in the SOIT repository includes a Python client built on the official MCP Python SDK (pip install mcp) that lists the tools and calls one. Keep the key out of files you commit.

Tools

tools/list offers every tool of the workspace the credential may invoke: built-ins, installed plugin tools, and the tools of enabled MCP servers. A key with Allowed tools set sees only those.

MCP clients and model APIs accept only letters, digits, _, and - in a tool name, so each SOIT ref is given a name:

SOIT refMCP tool name
tool:function:time_nowfunction_time_now
mcp_tool:github:create_issuegithub_create_issue

A name that would collide with another, or run past 64 characters, carries a short digest of its ref. Each tool's _meta holds its ref as ai.soit/tool_ref.

tools/call runs the tool through the tool gateway SOIT's agents use: secrets, egress policy, rate limits, budgets, audit, and cost apply, and the call is a run with mode=tool whose id comes back in the result's _meta as ai.soit/run_id. A result that is a JSON object is also returned as structuredContent.

Refusals the model can act on — arguments that do not match the tool's schema, a blocked address, a spent budget — come back as a tool result with isError: true and the reason, so the model can correct itself. An unknown tool name is the protocol error -32602.

Approval

A tool whose policy requires approval does not run on the first call. The result is an error that says so, with the approval id (ai.soit/approval_id) and run id, and the request appears in Govern › Approvals. Once a reviewer decides, the same call with the same arguments runs the tool or reports the rejection. Calling again before the decision returns the same pending approval; arguments that differ make a new call, with a new approval. Who decides, deadlines, and delegation are described in Approvals.

Protocol revisions

The endpoint is stateless whichever revision a client speaks:

  • Every POST /mcp carries one JSON-RPC message and is answered on its own, with application/json. No session is kept, no Mcp-Session-Id is issued, and one a client sends is ignored, so any API instance can answer any request.
  • GET /mcp (a server-to-client stream) and DELETE /mcp (ending a session) answer 405: the server sends no notifications and makes no requests of the client.
  • Notifications are accepted with 202; batches are refused.
  • A request naming a version SOIT does not support, in its MCP-Protocol-Version header or its _meta, is refused with 400 and UnsupportedProtocolVersion (-32022), whose data lists the supported versions.
RevisionBehavior
2026-07-28 (stateless)No handshake. Each request declares io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities in params._meta (without them: 400, -32602) and repeats itself in the MCP-Protocol-Version and Mcp-Method headers, plus Mcp-Name for tools/call. A missing or disagreeing header is refused with 400 and HeaderMismatch (-32020). server/discover returns the supported versions, capabilities, and instructions.
2024-11-05 to 2025-11-25Clients open with initialize, which answers with the client's version when supported and otherwise the latest of these; ping is answered. Results keep those revisions' shapes.

In the 2026-07-28 revision every result carries resultType: "complete" and the server's identity in _meta (io.modelcontextprotocol/serverInfo). server/discover and tools/list say how long they may be reused (ttlMs) and that only the caller may cache them (cacheScope: "private"), because the tool list is the credential's own.

Authorization

A request without a working credential is answered 401 with a challenge that names the endpoint's OAuth 2.1 protected resource metadata (RFC 9728):

WWW-Authenticate: Bearer resource_metadata="https://soit.example.com/.well-known/oauth-protected-resource/mcp"

The metadata, also served at /.well-known/oauth-protected-resource, names the resource and says that bearer tokens go in the Authorization header. SOIT authenticates MCP clients with its API keys, which clients send as a configured header, and does not issue OAuth tokens itself. A deployment that puts an OAuth authorization server in front of SOIT lists it in MCP_AUTHORIZATION_SERVERS, and the metadata publishes it for clients that discover their authorization that way.

Deployment settings

SettingPurpose
MCP_RESOURCE_URLThe endpoint's public URL, named in the metadata and the challenge. Empty derives it from the request; behind a proxy that rewrites the host, set it. The production compose file sets it from SOIT_PUBLIC_HOSTNAME.
MCP_AUTHORIZATION_SERVERSOAuth authorization servers to publish, as a JSON list. Defaults to none.
MCP_ALLOWED_ORIGINSBrowser origins, besides the endpoint's own, that may call it. A request from any other Origin is refused with 403, which keeps a web page from reaching the endpoint through a user's browser. Clients that are not browsers send no Origin. Defaults to none.

A proxy in front of the API must route /mcp and /.well-known/oauth-protected-resource* to the API as well as /api/* and /v1/*; the production reference's Caddyfile does. /mcp and /.well-known/* answer their protocols' own shapes, not SOIT's response envelope.

From v1.4.0, a process started with SOIT_ROLE=gateway serves /mcp and its discovery metadata too, along with /v1 and /api/v1/tools, so MCP traffic can run on processes of its own; see Running the gateway on its own.

Known limitations

As of v1.4.0:

  • Only tools are served: no resources, prompts, sampling, elicitation, subscriptions, or progress notifications. Tool results are returned whole.
  • The tool list is read when asked; the server sends no list_changed notification when tools are installed or removed.
  • A call that needs approval runs only when the client calls again; nothing runs it on the client's behalf.

In v1.3.0 a key's calls-per-minute and daily limits counted model calls only. From v1.4.0 they count MCP tools/call too; a refusal is a tool result whose _meta carries ai.soit/retry_after and ai.soit/quota and whose text says when to retry. See Per-key limits.

On this page