{ "openapi": "3.1.0", "info": { "title": "MCPJam API", "version": "1.0.0-preview", "description": "Programmatic access to MCP servers saved in your MCPJam projects — live diagnostics (validate, inspect, export) and operations: call tools, render prompts, run eval suites asynchronously and poll their results, and import OAuth tokens.\n\n**The API is in preview**: the surface may change while we finish the design. Error `code` values are stable; error `message` strings are not. Write clients that ignore unknown response fields.", "contact": { "name": "MCPJam", "url": "https://github.com/MCPJam/inspector/issues" } }, "servers": [ { "url": "https://app.mcpjam.com/api/v1", "description": "Hosted MCPJam" } ], "security": [ { "bearerAuth": [] } ], "tags": [ { "name": "Hosts", "description": "Project hosts: named model + capability profiles you run chats and eval suites against." }, { "name": "Environments", "description": "Project environments: named, live-editable execution bundles (one host, an optional standalone server group, optionally pinned skills and plugin versions) that eval suites and journeys run against. Distinct from Sandbox images, which are Computer base images. Reads require project membership; every write requires project admin." }, { "name": "Sandbox images", "description": "Custom Computer images: a digest-pinned Dockerfile built into an immutable image your project's computers boot from." }, { "name": "Server diagnostics", "description": "Connect-level health checks against a saved MCP server." }, { "name": "Primitives", "description": "The server's MCP primitives: tools, prompts, and resources." }, { "name": "Export", "description": "Full-server snapshots for diffing and CI." }, { "name": "Execution", "description": "Run the server's primitives: call tools, render prompts." }, { "name": "Eval runs", "description": "Asynchronous eval suite runs: create with 202, poll status, iterations, and traces." }, { "name": "OAuth", "description": "Bring-your-own OAuth: import externally obtained tokens for a server." }, { "name": "Chatboxes", "description": "Read-only access to the chatboxes published from a project: listing, settings, attached servers, and share links." }, { "name": "Catalog", "description": "Discover the resources the other routes operate on: your account, projects, servers, eval suites, and chat sessions." }, { "name": "Tunnels", "description": "Relay tunnels that expose local MCP servers through a public URL, registered as first-class project servers (the `mcpjam tunnel` CLI flow)." }, { "name": "Agent", "description": "Headless agent turns over the public API: send a message history, the server runs one assistant turn with project-scoped workspace tools (eval reads + suite creation) on a pinned hosted model, and returns the reply plus created-resource references." } ], "paths": { "/host-catalog": { "get": { "operationId": "getHostCompatCatalog", "tags": [ "Catalog" ], "summary": "Get the host-compat catalog", "description": "The versioned host-compatibility catalog backing `mcpjam compat` verdicts. Public and unauthenticated: static host facts and creation config with no project or user scope. Always returns a catalog — `source` is `live` when the backend publish was reachable and `bundled` when serving the SDK's built-in fallback.", "security": [], "responses": { "200": { "description": "The latest catalog envelope.", "content": { "application/json": { "schema": { "type": "object", "required": [ "schemaVersion", "version", "contentHash", "publishedAt", "catalog", "source" ], "properties": { "schemaVersion": { "type": "integer", "description": "Catalog document schema version (currently 2)." }, "version": { "type": "integer", "description": "Monotonic backend publish version; 0 when `source` is `bundled`." }, "contentHash": { "type": "string", "description": "SHA-256 of the canonical catalog JSON; empty when `source` is `bundled`." }, "publishedAt": { "type": "integer", "description": "Publish time (ms epoch); 0 when `source` is `bundled`." }, "source": { "type": "string", "enum": [ "live", "bundled" ], "description": "Whether this envelope came from the backend publish or the SDK's bundled fallback." }, "catalog": { "type": "object", "required": [ "hostsById" ], "properties": { "hostsById": { "type": "object", "description": "Canonical host catalog keyed by host style. Each entry is the full host object: compare/display facts plus HostConfigInputV2 creation fields.", "additionalProperties": { "type": "object", "required": [ "id", "label", "provenance", "rendersMcpApps", "hostStyle" ], "properties": { "id": { "type": "string" }, "label": { "type": "string" }, "provenance": { "type": "string", "enum": [ "observed", "probe", "vendor-doc", "assumed" ] }, "rendersMcpApps": { "type": "boolean" }, "supportedProtocolVersions": { "type": "array", "items": { "type": "string" } }, "verifiedAt": { "type": "integer", "description": "When this host's facts were last verified (ms epoch)." }, "imageSupport": { "type": "object", "additionalProperties": true }, "hostStyle": { "type": "string" }, "mcpProfile": { "type": "object", "description": "Host MCP profile. MCP Apps capabilities and OpenAI compat live here.", "additionalProperties": true } }, "additionalProperties": true } } } } } } } } } } } }, "/harness/{harnessId}/builtin-tools": { "get": { "operationId": "listHarnessBuiltinTools", "tags": [ "Harness" ], "summary": "List a harness's native built-in tools", "description": "The native tools an agent harness (e.g. `claude-code`) runs INSIDE its sandbox — Bash, Read, Edit, Glob, Grep, WebSearch, and the like. Display-only: these execute via the harness's own agent loop and are NOT callable through MCPJam. Static published-package metadata; no project scope.", "parameters": [ { "name": "harnessId", "in": "path", "required": true, "schema": { "type": "string", "enum": [ "claude-code" ] }, "description": "The harness id." } ], "responses": { "200": { "description": "The harness's built-in tools.", "content": { "application/json": { "schema": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "type": "object", "required": [ "key", "name" ], "properties": { "key": { "type": "string" }, "name": { "type": "string" }, "commonName": { "type": "string" }, "toolUseKind": { "type": "string" }, "description": { "type": "string" }, "inputSchema": { "type": "object", "additionalProperties": true } } } } } } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/images": { "get": { "operationId": "listSandboxImages", "tags": [ "Sandbox images" ], "summary": "List a project's sandbox images", "description": "The custom Computer images (digest-pinned Dockerfiles built into immutable images) saved in the project — your own personal drafts plus the project-shared ones.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "responses": { "200": { "description": "The project's sandbox images.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImagePage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "post": { "operationId": "createSandboxImage", "tags": [ "Sandbox images" ], "summary": "Create a sandbox image", "description": "Create a personal-draft sandbox image from a blueprint and respond `201` with its detail. Build it (`POST .../build`) before a computer can boot from it. Guest callers are denied (a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImageCreateRequest" }, "example": { "name": "scraper", "blueprint": "base: debian:bookworm-slim@sha256:\ninitialize:\n - name: Install tooling\n run: apt-get update && apt-get install -y git\nknowledge:\n - name: Test notes\n contents: Run `make test` before pushing.\n" } } } }, "responses": { "201": { "description": "The sandbox image was created.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImage" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/images/{imageId}": { "get": { "operationId": "getSandboxImage", "tags": [ "Sandbox images" ], "summary": "Get a sandbox image", "description": "One sandbox image's blueprint, sharing, and latest build. An image that exists but belongs to a different project reads as `404 NOT_FOUND`.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/imageId" } ], "responses": { "200": { "description": "The sandbox image.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "patch": { "operationId": "updateSandboxImage", "tags": [ "Sandbox images" ], "summary": "Update a sandbox image", "description": "Edit a sandbox image's name and/or Dockerfile. Re-build it for the changes to take effect on a computer. Guest callers are denied (a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/imageId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImageUpdateRequest" }, "example": { "name": "scraper (v2)" } } } }, "responses": { "200": { "description": "The updated sandbox image.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImage" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "delete": { "operationId": "deleteSandboxImage", "tags": [ "Sandbox images" ], "summary": "Delete a sandbox image", "description": "Permanently delete a sandbox image. Computers booted from it fall back to the base image. Deleting a project-shared image requires project admin. Bodyless — any field is rejected. Guest callers are denied (a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/imageId" } ], "responses": { "200": { "description": "The sandbox image was deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImageDeleted" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/images/{imageId}/builds": { "get": { "operationId": "listSandboxImageBuilds", "tags": [ "Sandbox images" ], "summary": "List a sandbox image's builds", "description": "A sandbox image's builds, newest first, each with its status and capped log preview.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/imageId" } ], "responses": { "200": { "description": "The sandbox image's builds.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImageBuildPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/images/{imageId}/build": { "post": { "operationId": "buildSandboxImage", "tags": [ "Sandbox images" ], "summary": "Build a sandbox image", "description": "Trigger a build of the sandbox image's image and respond `202`. The build runs asynchronously — poll the builds list for status. Bodyless. Guest callers are denied (a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/imageId" } ], "requestBody": { "$ref": "#/components/requestBodies/emptyBody" }, "responses": { "202": { "description": "The build was accepted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImageBuildStarted" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/images/{imageId}/promote": { "post": { "operationId": "promoteSandboxImage", "tags": [ "Sandbox images" ], "summary": "Share a sandbox image with the project", "description": "Promote a personal-draft sandbox image to a project-shared one. Requires project admin. Bodyless. Guest callers are denied (a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/imageId" } ], "requestBody": { "$ref": "#/components/requestBodies/emptyBody" }, "responses": { "200": { "description": "The now-shared sandbox image.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImage" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/images/{imageId}/use": { "post": { "operationId": "useSandboxImage", "tags": [ "Sandbox images" ], "summary": "Boot your computer from a sandbox image", "description": "Attach the sandbox image to the caller's computer, which re-provisions it from the pinned image (installed files are wiped). The image must have a `ready` build, and the computer provider must match the build's. Bodyless. Guest callers are denied (a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/imageId" } ], "requestBody": { "$ref": "#/components/requestBodies/emptyBody" }, "responses": { "200": { "description": "The sandbox image was attached; the computer is re-provisioning.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ComputerAttached" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/computer/reset": { "post": { "operationId": "resetComputer", "tags": [ "Sandbox images" ], "summary": "Reset your computer to its image", "description": "Reset the caller's computer back to its current image, wiping mutable state. Bodyless. Guest callers are denied (a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "$ref": "#/components/requestBodies/emptyBody" }, "responses": { "200": { "description": "The reset was requested.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ComputerReset" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/hosts": { "get": { "operationId": "listHosts", "tags": [ "Hosts" ], "summary": "List a project's hosts", "description": "The hosts saved in the project — the named model + capability profiles you attach to chats and eval suites. Returns the `id`s the host detail/update/delete routes take.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "responses": { "200": { "description": "The project's hosts.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HostPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "post": { "operationId": "createHost", "tags": [ "Hosts" ], "summary": "Create a host (from a template or a full config)", "description": "Creates a host in the project and responds `201` with the new host's detail. Seed the host config one of two ways: pass `template` (a built-in template id, optional `theme`) to seed server-side from the live backend host catalog, falling back to the bundled SDK catalog snapshot if the live catalog is unavailable, OR pass `config` (a full host config v2). Exactly one of `template` or `config` is required.\n\nGuest callers are denied (host creation is a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HostCreateRequest" }, "example": { "name": "Claude", "template": "claude", "theme": "dark" } } } }, "responses": { "201": { "description": "The host was created.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HostDetail" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/hosts/{hostId}": { "get": { "operationId": "getHost", "tags": [ "Hosts" ], "summary": "Get a host", "description": "One host's full settings, including its resolved host config. A host that exists but belongs to a different project reads as `404 NOT_FOUND`.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/hostId" } ], "responses": { "200": { "description": "The host settings.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HostDetail" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "patch": { "operationId": "updateHost", "tags": [ "Hosts" ], "summary": "Update a host", "description": "Edit a host's display name and/or its host config. Only the fields you pass change. Guest callers are denied (a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/hostId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HostUpdateRequest" }, "example": { "name": "Claude (renamed)" } } } }, "responses": { "200": { "description": "The updated host settings.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HostDetail" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "delete": { "operationId": "deleteHost", "tags": [ "Hosts" ], "summary": "Delete a host", "description": "Permanently delete a host from the project. Pass `{ \"force\": true }` to delete a host that is still referenced (e.g. by an eval suite). Guest callers are denied (a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/hostId" } ], "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "properties": { "force": { "type": "boolean", "description": "Delete even if the host is still referenced." } } } } } }, "responses": { "200": { "description": "The host was deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HostDeleted" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/servers/{serverId}/validate": { "post": { "operationId": "validateServer", "tags": [ "Server diagnostics" ], "summary": "Validate a server", "description": "Connects to the server, initializes the MCP session, and returns a connection snapshot (server info, negotiated capabilities). The same check the hosted inspector runs when you connect a server.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "$ref": "#/components/requestBodies/emptyBody" }, "responses": { "200": { "description": "Server connected and initialized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ValidateResult" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/servers/{serverId}/doctor": { "post": { "operationId": "runDoctor", "tags": [ "Server diagnostics" ], "summary": "Run the doctor", "description": "Runs the full doctor workflow — probe → connect → initialize → capabilities → primitives — and returns a step-by-step report. The richest signal for \"is this server healthy, and why not.\"", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "$ref": "#/components/requestBodies/emptyBody" }, "responses": { "200": { "description": "Doctor report. `status` summarizes the outcome; per-step detail is in `checks`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DoctorReport" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/servers/{serverId}/check-oauth": { "post": { "operationId": "checkOAuth", "tags": [ "Server diagnostics" ], "summary": "Check OAuth requirement", "description": "Lightweight, no MCP connection: reports whether the server is configured to require an OAuth grant.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "$ref": "#/components/requestBodies/emptyBody" }, "responses": { "200": { "description": "OAuth requirement for the server.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CheckOAuthResult" }, "example": { "useOAuth": true, "serverUrl": "https://mcp.example.com/mcp" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/servers/{serverId}/tools": { "post": { "operationId": "listTools", "tags": [ "Primitives" ], "summary": "List tools", "description": "Returns the server's tools as a collection page. Each item is the MCP tool definition (`name`, `description`, `inputSchema`, ...).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "$ref": "#/components/requestBodies/cursorBody" }, "responses": { "200": { "description": "One page of tools.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ToolPage" }, "example": { "items": [ { "name": "create_issue", "description": "Create a new issue in the tracker", "inputSchema": { "type": "object", "properties": { "title": { "type": "string" } } } } ] } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "422": { "$ref": "#/components/responses/FeatureNotSupported" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/servers/{serverId}/prompts": { "post": { "operationId": "listPrompts", "tags": [ "Primitives" ], "summary": "List prompts", "description": "Returns the server's prompts as a collection page.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "$ref": "#/components/requestBodies/cursorBody" }, "responses": { "200": { "description": "One page of prompts.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PromptPage" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "422": { "$ref": "#/components/responses/FeatureNotSupported" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/servers/{serverId}/resources": { "post": { "operationId": "listResources", "tags": [ "Primitives" ], "summary": "List resources", "description": "Returns the server's resources as a collection page.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "$ref": "#/components/requestBodies/cursorBody" }, "responses": { "200": { "description": "One page of resources.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResourcePage" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "422": { "$ref": "#/components/responses/FeatureNotSupported" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/servers/{serverId}/resources/read": { "post": { "operationId": "readResource", "tags": [ "Primitives" ], "summary": "Read a resource", "description": "Reads a single resource by URI and returns its contents.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "uri" ], "properties": { "uri": { "type": "string", "description": "The resource URI, exactly as returned by the resources list.", "example": "file:///readme.md" } } } } } }, "responses": { "200": { "description": "The resource contents.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResourceContents" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "422": { "$ref": "#/components/responses/FeatureNotSupported" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/servers/{serverId}/export": { "post": { "operationId": "exportServer", "tags": [ "Export" ], "summary": "Export a server snapshot", "description": "Lists tools, resources, and prompts in one call and returns a single JSON snapshot — handy for diffing a server's surface over time in CI.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "$ref": "#/components/requestBodies/emptyBody" }, "responses": { "200": { "description": "Full server snapshot.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExportSnapshot" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/servers/{serverId}/tools/call": { "post": { "operationId": "callTool", "tags": [ "Execution" ], "summary": "Call a tool", "description": "Executes a tool on the server and returns the MCP `CallToolResult` directly. Tool-level failures (`isError: true` in the result) are **successful calls** — the server answered; only transport/auth errors use the error envelope.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "toolName" ], "properties": { "toolName": { "type": "string", "description": "Tool name, exactly as returned by the tools list." }, "parameters": { "type": "object", "description": "Tool arguments matching the tool's `inputSchema`. Defaults to `{}`.", "additionalProperties": true } } }, "example": { "toolName": "create_issue", "parameters": { "title": "Bug: login fails" } } } } }, "responses": { "200": { "description": "The MCP `CallToolResult`. Check `isError` for tool-level failures.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CallToolResult" }, "example": { "content": [ { "type": "text", "text": "Issue created: #42" } ] } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "422": { "$ref": "#/components/responses/FeatureNotSupported" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/servers/{serverId}/prompts/get": { "post": { "operationId": "getPrompt", "tags": [ "Execution" ], "summary": "Render a prompt", "description": "Renders a prompt with arguments and returns the MCP `GetPromptResult` directly (`description?`, `messages`).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "promptName" ], "properties": { "promptName": { "type": "string", "description": "Prompt name, exactly as returned by the prompts list." }, "arguments": { "type": "object", "description": "Prompt arguments. String, number, and boolean values.", "additionalProperties": { "type": [ "string", "number", "boolean" ] } } } }, "example": { "promptName": "summarize", "arguments": { "style": "bullet" } } } } }, "responses": { "200": { "description": "The MCP `GetPromptResult`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GetPromptResult" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "422": { "$ref": "#/components/responses/FeatureNotSupported" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/eval-runs": { "post": { "operationId": "createEvalRun", "tags": [ "Eval runs" ], "summary": "Create an eval run (async)", "description": "Creates a suite run from an existing `suiteId` (rerun) and/or inline `tests`, then **detaches execution and responds `202` immediately** with the `runId`. Validation and quota errors surface on this request; poll `GET /eval-runs/{runId}` for progress. The run appears live in the hosted UI Runs tab, tagged `source: \"api\"`.\n\nA bare `suiteId` with no inline tests reruns the suite as configured. Per-organization concurrency is capped (default 2 concurrent runs); exceeding it returns `429` with `details.reason: \"CONCURRENT_RUN_LIMIT\"`.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalRunCreateRequest" }, "example": { "suiteName": "smoke", "serverIds": [ "srv_abc123" ], "tests": [ { "title": "echo works", "runs": 1, "model": "anthropic/claude-haiku-4.5", "provider": "anthropic", "steps": [ { "id": "s1", "kind": "prompt", "prompt": "Use the echo tool to say hi" }, { "id": "s2", "kind": "assert", "assertion": { "type": "toolCalledWith", "toolName": "echo", "args": { "args": { "text": "hi" } } } } ] } ] } } } }, "responses": { "202": { "description": "Run created; execution continues in the background.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalRunCreated" }, "example": { "runId": "run_abc123", "suiteId": "suite_def456", "status": "running", "caseUpsert": { "committed": [ { "name": "echo works" } ], "failed": [] } } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/eval-runs/{runId}": { "get": { "operationId": "getEvalRun", "tags": [ "Eval runs" ], "summary": "Get run status", "description": "Run status, result, and summary. Poll until `status` is terminal (`completed`, `failed`, or `cancelled`).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/runId" } ], "responses": { "200": { "description": "The run.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalRun" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/eval-runs/{runId}/iterations": { "get": { "operationId": "listEvalRunIterations", "tags": [ "Eval runs" ], "summary": "List run iterations", "description": "Per-iteration results: actual tool calls, structured token usage, and latency. Cursor-paginated.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/runId" }, { "name": "limit", "in": "query", "required": false, "description": "Page size, 1–200. Defaults to 50.", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 } }, { "name": "cursor", "in": "query", "required": false, "description": "Opaque cursor from a previous response's `nextCursor`.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "One page of iterations.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalIterationPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/eval-runs/{runId}/iterations/{iterationId}/trace": { "get": { "operationId": "getEvalIterationTrace", "tags": [ "Eval runs" ], "summary": "Get an iteration trace", "description": "Full trace envelope for one iteration: conversation messages, expected-vs-actual tool call analysis, and spans. The shape is rich and may evolve — treat it as an open document. Returns `404` with `details.reason: \"TRACE_NOT_AVAILABLE\"` when the iteration finished without a stored trace.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/runId" }, { "$ref": "#/components/parameters/iterationId" } ], "responses": { "200": { "description": "The trace envelope.", "content": { "application/json": { "schema": { "type": "object", "description": "Trace document: `messages`, `prompts` (per-prompt expected/actual tool-call analysis), `spans`, and more.", "additionalProperties": true } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/eval-runs/{runId}/iterations/{iterationId}/steps": { "get": { "operationId": "getEvalRunSteps", "tags": [ "Eval runs" ], "summary": "Get an iteration's step results", "description": "One row per authored test step, in order, with `status` (`ok`/`fail`/`skipped`/`pending`), a `reason`, and any `evidence` (screenshots, replay-video offset, widget tool calls). The fastest way to see which step failed and why. Unlike `/trace`, a missing trace is not a `404` — step verdicts still return, just without evidence.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/runId" }, { "$ref": "#/components/parameters/iterationId" } ], "responses": { "200": { "description": "The ordered step results as a page envelope.", "content": { "application/json": { "schema": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "description": "Authored steps in order with their verdict.", "items": { "type": "object", "required": [ "stepId", "stepIndex", "kind", "status" ], "properties": { "stepId": { "type": "string" }, "stepIndex": { "type": "integer" }, "kind": { "type": "string", "enum": [ "prompt", "toolCall", "interact", "assert" ] }, "status": { "type": "string", "enum": [ "ok", "fail", "skipped", "pending" ] }, "reason": { "type": [ "string", "null" ] }, "evidence": { "type": "object", "description": "Optional, omitted when the step produced none.", "additionalProperties": true } }, "additionalProperties": true } }, "nextCursor": { "type": "string" } }, "additionalProperties": true } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/eval-runs/{runId}/cancel": { "post": { "operationId": "cancelEvalRun", "tags": [ "Eval runs" ], "summary": "Cancel a run", "description": "Request cancellation of an in-flight run; marks the run and its pending/running iterations `cancelled`. A no-op success when the run is already cancelled; returns `409` when the run already reached a terminal status (`completed`/`failed`/`timed_out`).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/runId" } ], "responses": { "200": { "description": "The cancelled run.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalRun" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "description": "The run already finished and cannot be cancelled." }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/eval-suites/{suiteId}/runs": { "get": { "operationId": "listEvalSuiteRuns", "tags": [ "Eval runs" ], "summary": "List a suite's runs", "description": "Recent runs for a suite, newest first.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/suiteId" }, { "name": "limit", "in": "query", "required": false, "description": "Maximum runs to return, 1–100. Defaults to 25.", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 } } ], "responses": { "200": { "description": "Recent runs, newest first.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalRunPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/servers/{serverId}/oauth/import-tokens": { "post": { "operationId": "importOAuthTokens", "tags": [ "OAuth" ], "summary": "Import OAuth tokens", "description": "Stores OAuth tokens you obtained yourself (e.g. via the SDK's `runOAuthLogin` — interactive loopback, headless, or client-credentials) for this server, scoped to your user, project, and server. Subsequent API calls against the server inject the stored access token automatically, and `401`s from the server trigger a server-side refresh — no further caller involvement.\n\nThis closes the loop after an `OAUTH_REQUIRED` error.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ImportTokensRequest" }, "example": { "serverUrl": "https://mcp.example.com/mcp", "clientInformation": { "clientId": "client_123" }, "tokens": { "access_token": "at_…", "refresh_token": "rt_…", "expires_in": 3600 } } } } }, "responses": { "200": { "description": "Tokens stored.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ImportTokensResult" }, "example": { "imported": true, "expiresAt": 1781142000000 } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/me": { "get": { "operationId": "getMe", "tags": [ "Catalog" ], "summary": "Get the authenticated account", "description": "The user behind the API key (keys act as their creator, scoped to one organization).", "responses": { "200": { "description": "The account.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Me" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects": { "get": { "operationId": "listProjects", "tags": [ "Catalog" ], "summary": "List projects", "description": "Projects the caller can access. API keys only ever see projects inside the key's organization.", "parameters": [ { "name": "organizationId", "in": "query", "required": false, "description": "Filter to one organization.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "The caller's projects.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/servers": { "get": { "operationId": "listProjectServers", "tags": [ "Catalog" ], "summary": "List a project's servers", "description": "The MCP servers saved in the project — the `serverId`s every other route takes. STDIO command/args/env and raw headers are never exposed.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "responses": { "200": { "description": "The project's servers.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectServerPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/eval-suites": { "get": { "operationId": "listEvalSuites", "tags": [ "Catalog" ], "summary": "List a project's eval suites", "description": "Eval suites in the project with latest-run summaries and pass-rate trends — the `suiteId`s the eval-run routes take.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "responses": { "200": { "description": "The project's eval suites.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalSuitePage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "post": { "operationId": "createEvalSuite", "tags": [ "Eval runs" ], "summary": "Create an eval suite (author-only, does not run)", "description": "Creates a runnable eval suite — the suite record plus its test cases — and responds `201` **synchronously**, WITHOUT executing anything. Use this to author a suite, then run it later with `POST /eval-runs` (passing the returned `suiteId`).\n\nThis is distinct from `POST /eval-runs`, which creates a run and **detaches execution**, responding `202` with a `runId`. There is no concurrency cap here (no run is started).\n\nThe body uses an ergonomic authoring shape: a suite-level default `model` (and optional `provider`) applies to every test unless the test overrides it; `provider` is derived from a `provider/model` id when neither is supplied. Each test's case body is an ordered `steps` array (prompt / toolCall / interact / assert).\n\nGuest callers are denied (suite creation is a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalSuiteCreateRequest" }, "example": { "name": "smoke", "serverIds": [ "srv_abc123" ], "model": "anthropic/claude-haiku-4.5", "tests": [ { "title": "lists files", "steps": [ { "id": "s1", "kind": "prompt", "prompt": "List the files in the project root." }, { "id": "s2", "kind": "assert", "assertion": { "type": "toolCalledWith", "toolName": "list_files", "args": { "args": {} } } } ] } ] } } } }, "responses": { "201": { "description": "The suite was created.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalSuiteCreated" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/chat-sessions": { "get": { "operationId": "listChatSessions", "tags": [ "Catalog" ], "summary": "List chat sessions", "description": "Chat sessions visible to the caller (personal + project-shared merged). Top-level rather than project-nested because `projectId` is an optional filter, not an owning scope.", "parameters": [ { "name": "projectId", "in": "query", "required": false, "description": "Filter to one project.", "schema": { "type": "string" } }, { "name": "status", "in": "query", "required": false, "description": "`active` (default) or `archived`.", "schema": { "type": "string", "enum": [ "active", "archived" ], "default": "active" } }, { "name": "limit", "in": "query", "required": false, "description": "Page size, 1–200. Defaults to 50.", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 } }, { "name": "before", "in": "query", "required": false, "description": "Cursor: the previous page's `nextCursor` (a `lastActivityAt` timestamp).", "schema": { "type": "string" } } ], "responses": { "200": { "description": "One page of chat sessions, newest activity first.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChatSessionPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/chatboxes": { "get": { "operationId": "listChatboxes", "tags": [ "Chatboxes" ], "summary": "List a project's chatboxes", "description": "The chatboxes published from this project — name, access mode, attached servers, and share link. Read-only.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "responses": { "200": { "description": "The project's chatboxes.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChatboxPage" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/chatboxes/{chatboxId}": { "get": { "operationId": "getChatbox", "tags": [ "Chatboxes" ], "summary": "Get a chatbox", "description": "One chatbox's full read-only settings: model, system prompt, tool-approval policy, attached servers, and share link. A chatbox that exists but belongs to a different project reads as `404 NOT_FOUND`.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/chatboxId" } ], "responses": { "200": { "description": "The chatbox settings.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChatboxDetail" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/tunnels": { "post": { "operationId": "createTunnel", "tags": [ "Tunnels" ], "summary": "Create (or revive) a relay tunnel for a named project server", "description": "Registers a server record named `name` if missing, mints a relay tunnel grant for it, and **persists the tunnel bearer URL (including the plaintext `?k=` secret) onto the server record's `url`** so evals and chatboxes can target the tunnel like any remote server. The plaintext persistence is a deliberate trade-off of the current tunnel MVP — the backend otherwise stores only a hash of the secret — mitigated by rotation: **every call rotates the secret, revokes the previous grant at the edge (disconnecting any live tunnel session for the server), and updates the stored URL**, so re-calling this route is also the rotation/recovery path.\n\nThe caller hosts the tunnel itself: connect a WebSocket to `relayWsUrl` (subprotocol `mcpjam-tunnel.v1`, `Authorization: Bearer `) and serve the relayed requests — this is what `mcpjam tunnel` does. When the host disconnects, the server record stays and calls to the public URL fail fast at the edge.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TunnelCreateRequest" }, "example": { "name": "everything" } } } }, "responses": { "201": { "description": "Tunnel grant minted and the server record updated.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TunnelGrant" }, "example": { "serverId": "srv_abc123", "name": "everything", "existed": false, "slug": "calm-otter", "url": "https://calm-otter.tunnels.mcpjam.com/api/mcp/adapter-http/srv_abc123?k=…", "connectToken": "ct_…", "connectTokenExpiresAt": 1767225600000, "relayWsUrl": "wss://tunnels.mcpjam.com/agent", "secretVersion": 3 } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/tunnels/{serverId}/close": { "post": { "operationId": "closeTunnel", "tags": [ "Tunnels" ], "summary": "Revoke a tunnel's live grant", "description": "Revokes the grant at the control plane and edge: the public URL stops working immediately and any live tunnel session is disconnected. The server record — including its now-dead `url` — is intentionally left untouched, so the next create revives the tunnel with the same slug.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "responses": { "200": { "description": "Grant revoked; the server record is unchanged.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TunnelClosed" }, "example": { "serverId": "srv_abc123", "status": "closed" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/environments": { "get": { "operationId": "listEnvironments", "tags": [ "Environments" ], "summary": "List a project's environments", "description": "The project environments saved in the project. Archived environments are excluded unless `includeArchived=true` — you need that to find one to restore.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "name": "includeArchived", "in": "query", "required": false, "description": "Include archived environments.", "schema": { "type": "boolean", "default": false } } ], "responses": { "200": { "description": "The project's environments.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironmentPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "post": { "operationId": "createEnvironment", "tags": [ "Environments" ], "summary": "Create an environment", "description": "Create a project environment from a host plus, optionally, a standalone server group, a pinned skill selection, and pinned plugin versions. The name must be unique among the project's live environments. Requires project admin.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironmentCreateRequest" } } } }, "responses": { "201": { "description": "The created environment.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironment" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "409": { "$ref": "#/components/responses/Conflict" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/environments/{environmentId}": { "get": { "operationId": "getEnvironment", "tags": [ "Environments" ], "summary": "Show one environment", "description": "Read one environment, including the `revision` you pass back as `expectedRevision` when writing to it.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/environmentId" } ], "responses": { "200": { "description": "The environment.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironment" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "patch": { "operationId": "updateEnvironment", "tags": [ "Environments" ], "summary": "Update an environment", "description": "Edit an environment. Only the fields you send change; send `null` for `serverAttachmentId`, `skillSelection`, or `pluginVersionIds` to clear them. Requires `expectedRevision` — the revision you last read. If the environment changed since, the write is rejected with 409 rather than overwriting the concurrent edit. Requires project admin.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/environmentId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironmentUpdateRequest" } } } }, "responses": { "200": { "description": "The updated environment.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironment" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "409": { "$ref": "#/components/responses/Conflict" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/environments/{environmentId}/resolve": { "get": { "operationId": "resolveEnvironment", "tags": [ "Environments" ], "summary": "Preview what an environment resolves to", "description": "Resolve an environment to the exact execution inputs a run would use right now: the host's current config, the closed server set (including servers contributed by pinned plugin versions), and the resolved plugin versions. Returns 409 when the environment cannot currently produce a runnable configuration — for example a pinned plugin was disabled or deleted; `details.code` carries the specific reason (`ENV_PLUGIN_UNAVAILABLE`, `ENV_NO_SERVERS`, `ENV_HOST_MISSING`, …).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/environmentId" } ], "responses": { "200": { "description": "The resolved execution inputs.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironmentResolved" } } } }, "409": { "$ref": "#/components/responses/Conflict" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/environments/{environmentId}/archive": { "post": { "operationId": "archiveEnvironment", "tags": [ "Environments" ], "summary": "Archive an environment", "description": "Archive an environment. It stops being selectable for runs and frees its name for a new one, but the row is kept and can be restored — this is why archive is a sub-action rather than a DELETE. Requires `expectedRevision` — the revision you last read. If the environment changed since, the write is rejected with 409 rather than overwriting the concurrent edit. Requires project admin.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/environmentId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironmentRevisionRequest" } } } }, "responses": { "200": { "description": "The updated environment.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironment" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "409": { "$ref": "#/components/responses/Conflict" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/environments/{environmentId}/restore": { "post": { "operationId": "restoreEnvironment", "tags": [ "Environments" ], "summary": "Restore an archived environment", "description": "Restore an archived environment. Returns 409 if another live environment took its name in the meantime. Plugin pins whose version row no longer exists at all are dropped on the way back to live — compare the returned `pluginVersionIds` against what you archived to detect that. Requires `expectedRevision` — the revision you last read. If the environment changed since, the write is rejected with 409 rather than overwriting the concurrent edit. Requires project admin.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/environmentId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironmentRevisionRequest" } } } }, "responses": { "200": { "description": "The updated environment.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironment" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "409": { "$ref": "#/components/responses/Conflict" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/images/validate": { "post": { "operationId": "validateSandboxImageBlueprint", "summary": "Validate a blueprint", "description": "Lint blueprint YAML without saving it. Returns 200 with `ok: false` + structured errors for an invalid blueprint; the authoritative rejection still happens at create/update/build.", "tags": [ "Sandbox images" ], "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImageBlueprintValidateRequest" }, "example": { "blueprint": "base: debian:bookworm-slim@sha256:\ninitialize:\n - name: Install tooling\n run: apt-get update && apt-get install -y git\nknowledge:\n - name: Test notes\n contents: Run `make test` before pushing.\n" } } } }, "responses": { "200": { "description": "Lint result", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImageBlueprintValidateResult" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/agent": { "post": { "operationId": "runAgentTurn", "tags": [ "Agent" ], "summary": "Run one headless agent turn", "description": "Runs ONE assistant turn over the supplied message history and responds synchronously with the final assistant text, the operations it invoked, and references to any resources it created (currently eval suites).\n\nThe caller owns conversation state: resend the full history each turn. The model is pinned server-side (hosted catalog) and billed to the project. Tools available to the turn are read operations plus atomic `create_eval_suite`; run/cancel and generation operations are deliberately excluded — starting a run stays an explicit caller action via `POST /eval-runs`.\n\nEvery tool invocation is hard-clamped to the path `projectId`. Turns are capped at 4 concurrent per organization (`429 RATE_LIMITED`; enforced per server instance) and ~90s wall clock (`504 TIMEOUT`). Guest callers are denied. Requires a hosted MCPJam deployment (`422 FEATURE_NOT_SUPPORTED` otherwise).\n\nWhen a turn fails or times out AFTER a create operation already persisted a resource, the error body's `details.createdResources` carries the created references — check it before retrying, or a retry will create duplicates.\n\n**Retry policy:** this operation is NOT idempotent — a turn may persist resources (eval suites) even when the response is lost, and there is no idempotency key yet. Do not blind-retry: deduplicate on your own trigger identity (e.g. the Slack event id) and, when a retry is unavoidable, first list suites to check whether the previous attempt already created one.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AgentTurnRequest" }, "example": { "messages": [ { "role": "user", "content": "Create an eval suite for my weather server with one case that checks get_forecast is called for \"forecast for Paris\"." } ] } } } }, "responses": { "200": { "description": "The completed turn.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AgentTurnResponse" }, "example": { "reply": "Created the suite \"weather smoke\" with 1 case. It is ready to run.", "toolCalls": [ { "operation": "list_project_servers" }, { "operation": "create_eval_suite" } ], "createdResources": [ { "type": "eval_suite", "id": "ts_abc123", "name": "weather smoke", "url": "https://app.mcpjam.com/evals/suite/ts_abc123" } ], "usage": { "inputTokens": 2450, "outputTokens": 312 } } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "422": { "$ref": "#/components/responses/FeatureNotSupported" }, "429": { "$ref": "#/components/responses/RateLimited" }, "504": { "$ref": "#/components/responses/Timeout" }, "500": { "$ref": "#/components/responses/InternalError" } } } } }, "components": { "securitySchemes": { "bearerAuth": { "type": "http", "scheme": "bearer", "description": "MCPJam API key (`sk_…`). Create one at [Settings → API keys](https://app.mcpjam.com/settings/api-keys). Guest sessions cannot use the API, and API keys cannot manage other API keys." } }, "parameters": { "projectId": { "name": "projectId", "in": "path", "required": true, "description": "ID of the hosted project that contains the server.", "schema": { "type": "string" } }, "serverId": { "name": "serverId", "in": "path", "required": true, "description": "ID of the server inside the project.", "schema": { "type": "string" } }, "runId": { "name": "runId", "in": "path", "required": true, "description": "Eval run ID, as returned by `POST /eval-runs`.", "schema": { "type": "string" } }, "iterationId": { "name": "iterationId", "in": "path", "required": true, "description": "Iteration ID, as returned by the run's iterations list.", "schema": { "type": "string" } }, "suiteId": { "name": "suiteId", "in": "path", "required": true, "description": "Eval suite ID, as returned by `POST /eval-runs`.", "schema": { "type": "string" } }, "chatboxId": { "name": "chatboxId", "in": "path", "required": true, "description": "Chatbox ID, as returned by the project's chatbox list.", "schema": { "type": "string" } }, "hostId": { "name": "hostId", "in": "path", "required": true, "description": "Host ID, as returned by the project's host list.", "schema": { "type": "string" } }, "imageId": { "name": "imageId", "in": "path", "required": true, "description": "Sandbox image ID, as returned by the project's image list.", "schema": { "type": "string" } }, "environmentId": { "name": "environmentId", "in": "path", "required": true, "description": "Project environment ID, as returned by the project's environment list.", "schema": { "type": "string" } } }, "requestBodies": { "emptyBody": { "required": false, "description": "Accepts an empty JSON object.", "content": { "application/json": { "schema": { "type": "object", "properties": {} }, "example": {} } } }, "cursorBody": { "required": false, "description": "Optional pagination cursor.", "content": { "application/json": { "schema": { "type": "object", "properties": { "cursor": { "type": "string", "description": "Opaque pagination cursor from a previous response's `nextCursor`. Don't parse it." } } }, "example": {} } } } }, "schemas": { "Error": { "type": "object", "required": [ "code", "message" ], "properties": { "code": { "type": "string", "description": "Stable, machine-readable error code. New codes may be added over time; treat unknown codes as non-retryable failures unless the HTTP status says otherwise.", "enum": [ "UNAUTHORIZED", "FORBIDDEN", "NOT_FOUND", "CONFLICT", "VALIDATION_ERROR", "RATE_LIMITED", "FEATURE_NOT_SUPPORTED", "SERVER_UNREACHABLE", "TIMEOUT", "OAUTH_REQUIRED", "INTERNAL_ERROR" ] }, "message": { "type": "string", "description": "Human-readable description. May change between releases — don't match on it." }, "details": { "type": "object", "description": "Optional, unstructured context bag.", "additionalProperties": true } } }, "ValidateResult": { "type": "object", "required": [ "success", "status" ], "properties": { "success": { "type": "boolean", "const": true }, "status": { "type": "string", "const": "connected" }, "initInfo": { "type": [ "object", "null" ], "description": "MCP initialization snapshot: server info, negotiated protocol version, and capabilities. `null` if the server connected but initialization data was unavailable.", "additionalProperties": true } } }, "DoctorReport": { "type": "object", "description": "Step-by-step health report from the probe → connect → initialize → capabilities workflow.", "required": [ "generatedAt", "status", "connection", "checks" ], "properties": { "generatedAt": { "type": "string", "format": "date-time", "description": "When the report was generated." }, "status": { "type": "string", "enum": [ "ready", "oauth_required", "partial", "error" ], "description": "Overall outcome. `partial` means the server connected but some primitive listings failed." }, "probe": { "type": [ "object", "null" ], "description": "Transport-level probe result (HTTP reachability, protocol hints), or `null` when skipped.", "additionalProperties": true }, "connection": { "type": "object", "required": [ "status", "detail" ], "properties": { "status": { "type": "string", "enum": [ "connected", "error", "skipped" ] }, "detail": { "type": "string" } } }, "initInfo": { "type": [ "object", "null" ], "additionalProperties": true }, "capabilities": { "type": [ "object", "null" ], "description": "The server's negotiated MCP capabilities.", "additionalProperties": true }, "tools": { "type": "array", "items": { "$ref": "#/components/schemas/Tool" } }, "resources": { "type": "array", "items": { "$ref": "#/components/schemas/Resource" } }, "resourceTemplates": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "prompts": { "type": "array", "items": { "$ref": "#/components/schemas/Prompt" } }, "checks": { "type": "object", "description": "Per-step status. Each check is `{ status: ok | error | skipped, detail }`.", "properties": { "probe": { "$ref": "#/components/schemas/DoctorCheck" }, "connection": { "$ref": "#/components/schemas/DoctorCheck" }, "initialization": { "$ref": "#/components/schemas/DoctorCheck" }, "capabilities": { "$ref": "#/components/schemas/DoctorCheck" }, "tools": { "$ref": "#/components/schemas/DoctorCheck" }, "resources": { "$ref": "#/components/schemas/DoctorCheck" }, "resourceTemplates": { "$ref": "#/components/schemas/DoctorCheck" }, "prompts": { "$ref": "#/components/schemas/DoctorCheck" } } }, "error": { "oneOf": [ { "type": "null" }, { "type": "object", "required": [ "code", "message" ], "properties": { "code": { "type": "string" }, "message": { "type": "string" }, "details": {} } } ], "description": "Terminal error, or `null` when the workflow completed." } }, "additionalProperties": true }, "DoctorCheck": { "type": "object", "required": [ "status", "detail" ], "properties": { "status": { "type": "string", "enum": [ "ok", "error", "skipped" ] }, "detail": { "type": "string" } } }, "CheckOAuthResult": { "type": "object", "required": [ "useOAuth", "serverUrl" ], "properties": { "useOAuth": { "type": "boolean", "description": "Whether the server is configured to require an OAuth grant." }, "serverUrl": { "type": [ "string", "null" ], "description": "The server's URL, or `null` for non-HTTP transports." } } }, "Tool": { "type": "object", "description": "MCP tool definition, as returned by the server.", "required": [ "name" ], "properties": { "name": { "type": "string" }, "description": { "type": "string" }, "inputSchema": { "type": "object", "description": "JSON Schema for the tool's arguments.", "additionalProperties": true }, "outputSchema": { "type": "object", "description": "JSON Schema for the tool's structured output, when declared.", "additionalProperties": true } }, "additionalProperties": true }, "Prompt": { "type": "object", "description": "MCP prompt definition, as returned by the server.", "required": [ "name" ], "properties": { "name": { "type": "string" }, "description": { "type": "string" }, "arguments": { "type": "array", "items": { "type": "object", "required": [ "name" ], "properties": { "name": { "type": "string" }, "description": { "type": "string" }, "required": { "type": "boolean" } }, "additionalProperties": true } } }, "additionalProperties": true }, "Resource": { "type": "object", "description": "MCP resource descriptor, as returned by the server.", "required": [ "uri" ], "properties": { "uri": { "type": "string" }, "name": { "type": "string" }, "description": { "type": "string" }, "mimeType": { "type": "string" } }, "additionalProperties": true }, "ResourceContents": { "type": "object", "description": "MCP read-resource result.", "required": [ "contents" ], "properties": { "contents": { "type": "array", "items": { "type": "object", "required": [ "uri" ], "properties": { "uri": { "type": "string" }, "mimeType": { "type": "string" }, "text": { "type": "string", "description": "Present for text resources." }, "blob": { "type": "string", "description": "Base64-encoded payload, present for binary resources." } }, "additionalProperties": true } } }, "additionalProperties": true }, "ToolPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Tool" } }, "nextCursor": { "type": "string", "description": "Opaque cursor for the next page. Omitted on the last page." } } }, "PromptPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Prompt" } }, "nextCursor": { "type": "string", "description": "Opaque cursor for the next page. Omitted on the last page." } } }, "ResourcePage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Resource" } }, "nextCursor": { "type": "string", "description": "Opaque cursor for the next page. Omitted on the last page." } } }, "CallToolResult": { "type": "object", "description": "MCP tool execution result, returned verbatim from the server.", "required": [ "content" ], "properties": { "content": { "type": "array", "description": "Content blocks (`text`, `image`, `resource`, ...).", "items": { "type": "object", "additionalProperties": true } }, "structuredContent": { "type": "object", "description": "Structured output matching the tool's `outputSchema`, when declared.", "additionalProperties": true }, "isError": { "type": "boolean", "description": "`true` when the **tool** reported a failure. The HTTP call still succeeds — the server answered." } }, "additionalProperties": true }, "GetPromptResult": { "type": "object", "description": "MCP prompt render result, returned verbatim from the server.", "required": [ "messages" ], "properties": { "description": { "type": "string" }, "messages": { "type": "array", "items": { "type": "object", "required": [ "role", "content" ], "properties": { "role": { "type": "string", "enum": [ "user", "assistant" ] }, "content": { "description": "MCP prompt message content block.", "additionalProperties": true } }, "additionalProperties": true } } }, "additionalProperties": true }, "EvalTestStep": { "type": "object", "description": "One authored test step (the unified test model). `kind` discriminates: `prompt` is a user message (model turn); `toolCall` is a deterministic, model-free tool call; `interact` is one pure widget action; `assert` is an assertion (a `Predicate` like `toolCalledWith` / `widgetRendered`, or a DOM `WidgetAssertion`).", "required": [ "id", "kind" ], "properties": { "id": { "type": "string", "minLength": 1 }, "kind": { "type": "string", "enum": [ "prompt", "toolCall", "interact", "assert" ] }, "prompt": { "type": "string", "description": "User message (`kind: prompt`)." }, "serverName": { "type": "string", "description": "Server that owns the tool (`kind: toolCall`)." }, "toolName": { "type": "string", "description": "Tool name (`kind: toolCall` / `interact`)." }, "arguments": { "type": "object", "description": "Tool-call arguments (`kind: toolCall`).", "additionalProperties": true }, "action": { "type": "object", "description": "Widget action (`kind: interact`).", "additionalProperties": true }, "assertion": { "type": "object", "description": "Predicate or widget assertion (`kind: assert`).", "additionalProperties": true } }, "additionalProperties": true }, "EvalTestCase": { "type": "object", "description": "Inline eval test case. The case body is an ordered `steps` array (the unified test model).", "required": [ "title", "runs", "model", "provider", "steps" ], "properties": { "title": { "type": "string" }, "steps": { "type": "array", "minItems": 1, "description": "Ordered test steps. The first `prompt` step is the case query; `toolCalledWith` asserts are the expected tool calls; a single model-free `toolCall` step is a render-check.", "items": { "$ref": "#/components/schemas/EvalTestStep" } }, "runs": { "type": "integer", "minimum": 1, "maximum": 10, "description": "Iterations to execute for this case." }, "model": { "type": "string", "description": "Model ID. Hosted-catalog ids use `provider/name` form (e.g. `anthropic/claude-haiku-4.5`) and run on org credits; provider-native ids require a matching `modelApiKeys` entry (BYOK). Unknown models are rejected with VALIDATION_ERROR at create time." }, "provider": { "type": "string", "description": "Model provider, e.g. `anthropic`, `openai`." }, "isNegativeTest": { "type": "boolean", "description": "When `true`, the case passes if NO tools are called." }, "expectedOutput": { "type": "string" }, "advancedConfig": { "type": "object", "description": "Optional `system`, `temperature`, `toolChoice` overrides.", "additionalProperties": true } }, "additionalProperties": true }, "EvalSuiteCreateRequest": { "type": "object", "description": "Author-only suite-create body. A suite-level default `model` (and optional `provider`) applies to every test unless the test overrides it.", "required": [ "name", "serverIds", "model", "tests" ], "properties": { "name": { "type": "string", "minLength": 1, "description": "Suite name." }, "description": { "type": "string" }, "serverIds": { "type": "array", "minItems": 1, "description": "Servers (by canonical project ID) the suite's cases run against.", "items": { "type": "string" } }, "serverNames": { "type": "array", "description": "Optional display names, parallel to `serverIds`.", "items": { "type": "string" } }, "model": { "type": "string", "minLength": 1, "description": "Suite-level default model id (e.g. `anthropic/claude-haiku-4.5`). Used for any test that omits `model`." }, "provider": { "type": "string", "description": "Optional suite-level default provider. When omitted, the provider is derived from a `provider/model` id." }, "passCriteria": { "type": "object", "properties": { "minimumPassRate": { "type": "number" } } }, "tags": { "type": "array", "description": "Accepted for forward-compat; not persisted today (no-op).", "items": { "type": "string" } }, "tests": { "type": "array", "minItems": 1, "maxItems": 100, "description": "Test cases to create in the suite.", "items": { "type": "object", "required": [ "title", "steps" ], "properties": { "title": { "type": "string", "minLength": 1 }, "steps": { "type": "array", "minItems": 1, "description": "Ordered test steps (the unified test model). The first `prompt` step is the case query; `toolCalledWith` asserts are the expected tool calls; a single model-free `toolCall` step is a render-check.", "items": { "$ref": "#/components/schemas/EvalTestStep" } }, "runs": { "type": "integer", "minimum": 1, "maximum": 10, "description": "Iterations to execute for this case. Defaults to 1." }, "model": { "type": "string", "description": "Per-case model override. Defaults to the suite-level `model`." }, "provider": { "type": "string", "description": "Per-case provider override." }, "expectedOutput": { "type": "string" }, "isNegativeTest": { "type": "boolean", "description": "When `true`, the case passes if NO tools are called." }, "scenario": { "type": "string" }, "advancedConfig": { "type": "object", "description": "Optional `system`, `temperature`, `toolChoice` overrides.", "additionalProperties": true } }, "additionalProperties": true } } }, "additionalProperties": false }, "EvalSuiteCreated": { "type": "object", "required": [ "suiteId", "name", "caseUpsert" ], "properties": { "suiteId": { "type": "string" }, "name": { "type": "string" }, "servers": { "type": "array", "description": "The servers attached to the suite. `name` is present when supplied.", "items": { "type": "object", "required": [ "id" ], "properties": { "id": { "type": "string" }, "name": { "type": "string" } } } }, "caseUpsert": { "type": "object", "description": "Per-case create outcomes. Partial failures don't abort the suite; an all-failed new suite is rejected with VALIDATION_ERROR.", "properties": { "committed": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "name": { "type": "string" } } } }, "failed": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "error": { "type": "string" } } } } } } } }, "EvalRunCreateRequest": { "type": "object", "description": "Three valid shapes: `suiteId` (rerun an existing suite, optionally upserting inline `tests` into it), `suiteName` + `tests` + `serverIds` (create a new suite and run it), or `suiteName` + `tests` + `environmentId` (create a new suite and run it against a project environment, which supplies the servers). Inline `tests` alone — without a `suiteId` or a `suiteName` — are rejected with `VALIDATION_ERROR`.", "anyOf": [ { "required": [ "suiteId" ] }, { "required": [ "suiteName", "tests", "serverIds" ], "properties": { "tests": { "minItems": 1 } } }, { "required": [ "suiteName", "tests", "environmentId" ], "properties": { "tests": { "minItems": 1 } } } ], "properties": { "suiteId": { "type": "string", "description": "Existing suite to rerun. A bare `suiteId` with no `tests` reruns the suite exactly as configured." }, "suiteName": { "type": "string", "description": "Name for a new suite. Required (non-empty) when no `suiteId` is given." }, "suiteDescription": { "type": "string" }, "tests": { "type": "array", "maxItems": 100, "description": "Inline test cases to upsert into the suite before running.", "items": { "$ref": "#/components/schemas/EvalTestCase" } }, "serverIds": { "type": "array", "minItems": 1, "description": "Servers (by ID) the run connects to. Required when creating a new suite unless `environmentId` is given (the environment supplies the closed server set, and any `serverIds` sent alongside it are ignored); optional on reruns — when omitted, the run connects the suite's saved server selection (the set its snapshot references). A rerun of a suite with no saved selection is rejected with `VALIDATION_ERROR` (`details.reason: \"NO_SAVED_SERVER_SELECTION\"`).", "items": { "type": "string" } }, "modelApiKeys": { "type": "object", "description": "Optional per-provider model API keys (e.g. `{ \"anthropic\": \"sk-ant-…\" }`). Falls back to your organization's configured providers when omitted.", "additionalProperties": { "type": "string" } }, "notes": { "type": "string" }, "passCriteria": { "type": "object", "properties": { "minimumPassRate": { "type": "number" } } }, "iterationOverride": { "type": "integer", "minimum": 1, "maximum": 10, "description": "Override the per-case `runs` count for this run only." }, "environmentId": { "type": "string", "description": "Run against a project environment. The environment supplies the closed server set (so `serverIds` is not required and is ignored if sent), and the run is pinned to the revision resolved at launch — if the environment changes in between, the run is rejected with 409 rather than executing against a different configuration." } }, "additionalProperties": true }, "EvalRunCreated": { "type": "object", "required": [ "runId", "suiteId", "status", "caseUpsert" ], "properties": { "runId": { "type": "string" }, "suiteId": { "type": "string" }, "status": { "type": "string", "const": "running" }, "servers": { "type": "array", "description": "The servers the run connects to — explicit or derived from the suite's saved selection. `name` is present when known (always, on the derived path).", "items": { "type": "object", "required": [ "id" ], "properties": { "id": { "type": "string" }, "name": { "type": "string" } } } }, "caseUpsert": { "type": "object", "description": "Per-case upsert outcomes for inline `tests`. Partial failures don't abort the run.", "properties": { "committed": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "name": { "type": "string" } } } }, "failed": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "error": { "type": "string" } } } } } } } }, "EvalRun": { "type": "object", "required": [ "id", "suiteId", "status", "source", "createdAt" ], "properties": { "id": { "type": "string" }, "suiteId": { "type": "string" }, "runNumber": { "type": [ "integer", "null" ] }, "status": { "type": "string", "description": "Poll until terminal: `completed`, `failed`, or `cancelled`.", "enum": [ "pending", "running", "completed", "failed", "cancelled" ] }, "result": { "type": [ "string", "null" ], "description": "Pass/fail verdict once terminal.", "enum": [ "passed", "failed", null ] }, "summary": { "type": [ "object", "null" ], "properties": { "total": { "type": "integer" }, "passed": { "type": "integer" }, "failed": { "type": "integer" }, "passRate": { "type": "number" } } }, "source": { "type": "string", "description": "Run origin. API-created runs are `api`.", "enum": [ "ui", "api", "sdk" ] }, "notes": { "type": [ "string", "null" ] }, "createdAt": { "type": "number", "description": "Epoch milliseconds." }, "completedAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds, `null` until terminal." } } }, "EvalRunPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/EvalRun" } } } }, "EvalIteration": { "type": "object", "required": [ "id", "iterationNumber", "status" ], "properties": { "id": { "type": "string" }, "testCaseId": { "type": [ "string", "null" ] }, "title": { "type": [ "string", "null" ] }, "iterationNumber": { "type": "integer" }, "status": { "type": "string", "enum": [ "pending", "running", "completed", "failed", "cancelled" ] }, "result": { "type": [ "string", "null" ], "enum": [ "passed", "failed", null ] }, "model": { "type": [ "string", "null" ] }, "provider": { "type": [ "string", "null" ] }, "startedAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds." }, "durationMs": { "type": [ "number", "null" ], "description": "Wall-clock duration; `null` until terminal." }, "tokensUsed": { "type": [ "number", "null" ] }, "usage": { "type": [ "object", "null" ], "description": "Structured token usage (input/output/cached/reasoning) when available.", "additionalProperties": true }, "actualToolCalls": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "expectedToolCalls": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "error": { "type": [ "string", "null" ] } } }, "EvalIterationPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/EvalIteration" } }, "nextCursor": { "type": "string", "description": "Opaque cursor for the next page. Omitted on the last page." } } }, "ImportTokensRequest": { "type": "object", "required": [ "serverUrl", "tokens" ], "properties": { "serverUrl": { "type": "string", "description": "The MCP server URL the tokens were obtained for." }, "oauthResourceUrl": { "type": "string", "description": "OAuth protected-resource URL, when it differs from `serverUrl`." }, "clientInformation": { "type": "object", "description": "OAuth client used to obtain the tokens. Optional, but without it server-side refresh cannot run — always include it when you provide a `refresh_token`.", "required": [ "clientId" ], "properties": { "clientId": { "type": "string" }, "clientSecret": { "type": "string", "format": "password" } } }, "tokens": { "type": "object", "required": [ "access_token" ], "properties": { "access_token": { "type": "string", "format": "password" }, "refresh_token": { "type": "string", "format": "password" }, "expires_in": { "type": "number", "description": "Seconds until the access token expires." }, "token_type": { "type": "string" }, "scope": { "type": "string" }, "id_token": { "type": "string", "format": "password" } } } } }, "Me": { "type": "object", "required": [ "id", "email", "name" ], "properties": { "id": { "type": "string" }, "email": { "type": "string" }, "name": { "type": "string" }, "imageUrl": { "type": [ "string", "null" ] }, "profilePictureUrl": { "type": [ "string", "null" ] }, "plan": { "type": [ "string", "null" ] }, "createdAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds." }, "updatedAt": { "type": [ "number", "null" ] } } }, "Project": { "type": "object", "required": [ "id", "name" ], "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "icon": { "type": [ "string", "null" ] }, "organizationId": { "type": [ "string", "null" ] }, "visibility": { "type": [ "string", "null" ] }, "role": { "type": "string", "description": "Caller's role on the project, when resolved." }, "createdAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds." }, "updatedAt": { "type": [ "number", "null" ] } } }, "ProjectPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Project" } } } }, "ProjectServer": { "type": "object", "description": "A saved MCP server, projected toward the hosted (HTTP) shape. STDIO command/args/env and raw headers are never exposed.", "required": [ "id", "name", "enabled", "transportType", "useOAuth", "hasClientSecret" ], "properties": { "id": { "type": "string" }, "projectId": { "type": [ "string", "null" ] }, "name": { "type": "string" }, "enabled": { "type": "boolean" }, "transportType": { "type": "string" }, "url": { "type": [ "string", "null" ], "description": "Endpoint for HTTP-transport servers; `null` for stdio." }, "useOAuth": { "type": "boolean" }, "hasClientSecret": { "type": "boolean" }, "oauthScopes": { "type": "array", "items": { "type": "string" } }, "createdAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds." }, "updatedAt": { "type": [ "number", "null" ] } } }, "ProjectServerPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/ProjectServer" } } } }, "EvalSuite": { "type": "object", "required": [ "id", "totals", "passRateTrend" ], "properties": { "id": { "type": "string" }, "name": { "type": [ "string", "null" ] }, "projectId": { "type": [ "string", "null" ] }, "createdAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds." }, "updatedAt": { "type": [ "number", "null" ] }, "latestRun": { "type": [ "object", "null" ], "properties": { "id": { "type": [ "string", "null" ] }, "status": { "type": [ "string", "null" ] }, "passRate": { "type": [ "number", "null" ] }, "passed": { "type": [ "integer", "null" ] }, "failed": { "type": [ "integer", "null" ] }, "createdAt": { "type": [ "number", "null" ] } } }, "totals": { "type": "object", "properties": { "passed": { "type": "integer" }, "failed": { "type": "integer" }, "runs": { "type": "integer" } } }, "passRateTrend": { "type": "array", "description": "Recent runs' pass rates, oldest first.", "items": { "type": "number" } } } }, "EvalSuitePage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/EvalSuite" } } } }, "ChatSession": { "type": "object", "required": [ "id" ], "properties": { "id": { "type": "string" }, "title": { "type": [ "string", "null" ] }, "status": { "type": [ "string", "null" ] }, "projectId": { "type": [ "string", "null" ] }, "visibility": { "type": [ "string", "null" ], "description": "`private` or `project`." }, "lastActivityAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds." }, "createdAt": { "type": [ "number", "null" ] }, "isPinned": { "type": "boolean" }, "isUnread": { "type": "boolean" } } }, "ChatSessionPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/ChatSession" } }, "nextCursor": { "type": "string", "description": "Pass as `before` to fetch the next page. Omitted on the last page." } } }, "ImportTokensResult": { "type": "object", "required": [ "imported" ], "properties": { "imported": { "type": "boolean", "const": true }, "expiresAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds when the stored access token expires, or `null` if unknown." } } }, "ExportSnapshot": { "type": "object", "required": [ "serverId", "exportedAt", "tools", "resources", "prompts" ], "properties": { "serverId": { "type": "string" }, "exportedAt": { "type": "string", "format": "date-time" }, "tools": { "type": "array", "items": { "$ref": "#/components/schemas/Tool" } }, "resources": { "type": "array", "items": { "$ref": "#/components/schemas/Resource" } }, "prompts": { "type": "array", "items": { "$ref": "#/components/schemas/Prompt" } } } }, "ChatboxLink": { "type": "object", "description": "Share link for the chatbox. The URL embeds the access token; visible to any caller who can read the chatbox (the same audience that sees it in the hosted UI).", "required": [ "path", "url" ], "properties": { "path": { "type": "string", "description": "App-relative share path." }, "url": { "type": "string", "description": "Absolute share URL." } } }, "ChatboxServer": { "type": "object", "description": "A server attached to the chatbox. Chatboxes attach HTTP servers only, so `url` is the server's endpoint.", "required": [ "id", "name", "useOAuth" ], "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "url": { "type": [ "string", "null" ], "description": "HTTP endpoint of the server." }, "useOAuth": { "type": "boolean" } } }, "Chatbox": { "type": "object", "description": "Summary of a published chatbox, as returned by the list endpoint.", "required": [ "id", "name", "serverCount", "serverNames" ], "properties": { "id": { "type": "string" }, "projectId": { "type": [ "string", "null" ] }, "name": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "mode": { "type": [ "string", "null" ], "description": "Who can use the chatbox.", "enum": [ "project_members", "invited_only", "anyone_with_link", null ] }, "hostStyle": { "type": [ "string", "null" ], "description": "Chat surface style the chatbox renders (e.g. `claude`, `chatgpt`)." }, "hostId": { "type": [ "string", "null" ], "description": "The named host the chatbox publishes (hosts and chatboxes are 1:1)." }, "hostName": { "type": [ "string", "null" ] }, "serverCount": { "type": "integer" }, "serverNames": { "type": "array", "items": { "type": "string" } }, "link": { "oneOf": [ { "$ref": "#/components/schemas/ChatboxLink" }, { "type": "null" } ] }, "createdAt": { "type": [ "number", "null" ], "description": "Unix epoch milliseconds." }, "updatedAt": { "type": [ "number", "null" ], "description": "Unix epoch milliseconds." } } }, "ChatboxDetail": { "type": "object", "description": "A chatbox's full read-only settings: the summary fields plus the host execution config and resolved servers.", "required": [ "id", "name", "serverCount", "serverNames", "requireToolApproval", "servers" ], "properties": { "id": { "type": "string" }, "projectId": { "type": [ "string", "null" ] }, "name": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "mode": { "type": [ "string", "null" ], "description": "Who can use the chatbox.", "enum": [ "project_members", "invited_only", "anyone_with_link", null ] }, "hostStyle": { "type": [ "string", "null" ], "description": "Chat surface style the chatbox renders (e.g. `claude`, `chatgpt`)." }, "hostId": { "type": [ "string", "null" ], "description": "The named host the chatbox publishes (hosts and chatboxes are 1:1)." }, "hostName": { "type": [ "string", "null" ] }, "serverCount": { "type": "integer" }, "serverNames": { "type": "array", "items": { "type": "string" } }, "link": { "oneOf": [ { "$ref": "#/components/schemas/ChatboxLink" }, { "type": "null" } ] }, "createdAt": { "type": [ "number", "null" ], "description": "Unix epoch milliseconds." }, "updatedAt": { "type": [ "number", "null" ], "description": "Unix epoch milliseconds." }, "modelId": { "type": [ "string", "null" ], "description": "Model the chatbox chats with." }, "systemPrompt": { "type": [ "string", "null" ] }, "temperature": { "type": [ "number", "null" ] }, "requireToolApproval": { "type": "boolean" }, "servers": { "type": "array", "items": { "$ref": "#/components/schemas/ChatboxServer" } } } }, "ChatboxPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Chatbox" } } } }, "TunnelCreateRequest": { "type": "object", "required": [ "name" ], "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 128, "description": "Server name to register the tunnel under. Reusing an existing server's name points that record at the tunnel: its `url` is overwritten and stdio records are converted to `transportType: \"http\"`." } } }, "TunnelGrant": { "type": "object", "description": "Everything the caller needs to host the tunnel connection. **Treat the whole object as a credential**: `url` embeds the plaintext `?k=` bearer secret and `connectToken` authenticates the relay WebSocket.", "required": [ "serverId", "name", "existed", "slug", "url", "connectToken", "relayWsUrl" ], "properties": { "serverId": { "type": "string", "description": "The registered project server's ID." }, "name": { "type": "string" }, "existed": { "type": "boolean", "description": "True when a server record with this name already existed (its config now points at the tunnel)." }, "previousUrl": { "type": "string", "description": "The record's previous URL, present when it existed with a different one (it has been overwritten)." }, "previousTransportType": { "type": "string", "description": "The record's previous transport, present when it existed (a `stdio` record has been converted to `http`)." }, "slug": { "type": "string", "description": "Stable public subdomain slug; survives re-creates for the same server." }, "url": { "type": "string", "description": "Public tunnel URL including the `?k=` bearer secret. Also persisted on the server record's `url`." }, "connectToken": { "type": "string", "description": "Bearer for the relay edge WebSocket handshake." }, "connectTokenExpiresAt": { "type": "number", "description": "Epoch-ms expiry of `connectToken`." }, "relayWsUrl": { "type": "string", "description": "Relay edge WebSocket endpoint to connect to." }, "secretVersion": { "type": "number", "description": "Monotonic secret version; each create bumps it." } } }, "TunnelClosed": { "type": "object", "required": [ "serverId", "status" ], "properties": { "serverId": { "type": "string" }, "status": { "type": "string", "enum": [ "closed" ] } } }, "Host": { "type": "object", "description": "A host in a project, as returned by the host list.", "required": [ "id", "name", "hostConfigId", "modelId", "serverCount", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "hostConfigId": { "type": "string", "description": "ID of the content-addressed host config this host points at." }, "modelId": { "type": "string", "description": "Resolved model id, e.g. `anthropic/claude-haiku-4.5`." }, "serverCount": { "type": "integer", "description": "Number of MCP servers attached to the host config." }, "createdAt": { "type": "number", "description": "Unix epoch milliseconds." }, "updatedAt": { "type": "number", "description": "Unix epoch milliseconds." } } }, "HostPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Host" } } } }, "HostDetail": { "type": "object", "description": "A host plus its resolved host config (model, capabilities, host context, MCP profile).", "required": [ "id", "name", "config" ], "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "config": { "type": "object", "additionalProperties": true, "description": "Host config v2 DTO. Opaque object; shape mirrors the inspector's host editor." } } }, "HostCreateRequest": { "type": "object", "description": "Create a host from a built-in template OR a full host config. Exactly one of `template` or `config` is required.", "required": [ "name" ], "properties": { "name": { "type": "string", "description": "Display name for the new host." }, "template": { "type": "string", "description": "Built-in template id to seed the host config from.", "enum": [ "mcpjam", "claude", "claude-code", "chatgpt", "mistral", "cursor", "codex", "copilot", "vscode", "agentcore", "n8n", "perplexity" ] }, "theme": { "type": "string", "enum": [ "light", "dark" ], "description": "Theme stamped into the seeded config (template only)." }, "config": { "type": "object", "additionalProperties": true, "minProperties": 1, "description": "Full host config v2, used verbatim (alternative to `template`)." } }, "oneOf": [ { "required": [ "template" ] }, { "required": [ "config" ] } ] }, "HostUpdateRequest": { "type": "object", "description": "Edit a host. Provide at least one of `name` or `config`; only the fields you pass change.", "properties": { "name": { "type": "string", "description": "New display name." }, "config": { "type": "object", "additionalProperties": true, "minProperties": 1, "description": "Replacement host config v2." } }, "anyOf": [ { "required": [ "name" ] }, { "required": [ "config" ] } ] }, "HostDeleted": { "type": "object", "required": [ "id", "deleted" ], "properties": { "id": { "type": "string" }, "deleted": { "type": "boolean", "enum": [ true ] } } }, "SandboxImageBuild": { "type": "object", "description": "One build of a sandbox image's image.", "required": [ "id", "status", "provider", "baseImageDigests", "createdAt" ], "properties": { "id": { "type": "string" }, "status": { "type": "string", "enum": [ "queued", "building", "ready", "failed" ] }, "provider": { "type": "string", "enum": [ "e2b", "stub" ] }, "e2bBuildId": { "type": "string" }, "baseImageDigests": { "type": "array", "items": { "type": "string" }, "description": "Resolved sha256 digest of every FROM base (the reproducibility pin)." }, "logPreview": { "type": "string", "description": "Capped, ANSI-stripped tail of the build log." }, "error": { "type": "string" }, "createdAt": { "type": "number" }, "startedAt": { "type": "number" }, "finishedAt": { "type": "number" } } }, "SandboxImage": { "type": "object", "description": "A project's custom Computer image: a digest-pinned blueprint plus its latest build.", "required": [ "id", "projectId", "name", "blueprint", "contentHash", "sharing", "isOwner", "currentBuild", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string" }, "projectId": { "type": "string" }, "name": { "type": "string" }, "contentHash": { "type": "string" }, "sharing": { "type": "string", "enum": [ "user", "project" ], "description": "`user` = a personal draft; `project` = shared with the whole project." }, "isOwner": { "type": "boolean" }, "currentBuild": { "oneOf": [ { "$ref": "#/components/schemas/SandboxImageBuild" }, { "type": "null" } ] }, "createdAt": { "type": "number" }, "updatedAt": { "type": "number" }, "blueprint": { "type": "string", "description": "The image's blueprint YAML (base / initialize / maintenance / knowledge). `base` must be an allowlisted official image pinned by sha256 digest." } } }, "SandboxImagePage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/SandboxImage" } }, "nextCursor": { "type": "string" } } }, "SandboxImageBuildPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/SandboxImageBuild" } }, "nextCursor": { "type": "string" } } }, "SandboxImageCreateRequest": { "type": "object", "additionalProperties": false, "description": "Create a sandbox image from a blueprint YAML document: an allowlisted, `@sha256`-digest-pinned `base` image plus `initialize` steps baked into the image, and optional `maintenance`/`knowledge` delivered to the agent at runtime.", "required": [ "name", "blueprint" ], "properties": { "name": { "type": "string", "minLength": 1, "description": "Display name for the new sandbox image; must be non-empty after trimming." }, "blueprint": { "type": "string", "minLength": 1, "description": "Blueprint YAML defining the image. Validated server-side; use the validate endpoint for fast feedback." } } }, "SandboxImageUpdateRequest": { "type": "object", "additionalProperties": false, "description": "Edit a sandbox image. Provide at least one of `name` or `blueprint`; only the fields you pass change.", "properties": { "name": { "type": "string", "minLength": 1, "description": "New display name; must be non-empty after trimming." }, "blueprint": { "type": "string", "minLength": 1, "description": "Replacement blueprint YAML. Edits to maintenance/knowledge take effect at the next chat turn without a rebuild; base/initialize edits require a new build." } }, "anyOf": [ { "required": [ "name" ] }, { "required": [ "blueprint" ] } ] }, "SandboxImageDeleted": { "type": "object", "required": [ "id", "deleted" ], "properties": { "id": { "type": "string" }, "deleted": { "type": "boolean", "enum": [ true ] } } }, "SandboxImageBuildStarted": { "type": "object", "description": "A build was accepted (202). It runs in the background — poll the builds list for status.", "required": [ "id", "buildId", "reused" ], "properties": { "id": { "type": "string", "description": "The sandbox image id." }, "buildId": { "type": "string" }, "reused": { "type": "boolean", "description": "True when an identical prior build was reused instead of rebuilding." } } }, "ComputerAttached": { "type": "object", "required": [ "imageId", "computerId", "status" ], "properties": { "imageId": { "type": "string" }, "computerId": { "type": "string" }, "status": { "type": "string", "description": "The computer's lifecycle status after attaching (it re-provisions)." } } }, "ComputerReset": { "type": "object", "required": [ "projectId", "reset" ], "properties": { "projectId": { "type": "string" }, "reset": { "type": "boolean" } } }, "EnvironmentSkillSelection": { "type": "object", "description": "An explicit pinned skill selection. Cannot be empty — clear the field instead (send `null` on update) to mean \"no pinned skills\".", "required": [ "mode", "skillIds" ], "properties": { "mode": { "type": "string", "enum": [ "explicit" ] }, "skillIds": { "type": "array", "minItems": 1, "items": { "type": "string" }, "description": "Project-shared skill IDs. Skills carrying supporting files or extra frontmatter, and plugin-component skills, cannot be pinned." } } }, "ProjectEnvironment": { "type": "object", "description": "A project environment: a named, live-editable execution bundle that eval suites and journeys run against.", "required": [ "id", "projectId", "name", "hostId", "revision", "archived", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string" }, "projectId": { "type": "string" }, "name": { "type": "string", "description": "Unique among the project's live (non-archived) environments." }, "description": { "type": "string" }, "hostId": { "type": "string", "description": "The host this environment runs against." }, "serverAttachmentId": { "type": "string", "description": "Present only when the environment pins a standalone server group; otherwise the host config's own servers apply." }, "skillSelection": { "$ref": "#/components/schemas/EnvironmentSkillSelection" }, "pluginVersionIds": { "type": "array", "items": { "type": "string" }, "description": "Pinned plugin VERSION IDs. Narrow by design: the plugin must be installed and enabled, the version must be `ready`, at most one version per plugin may be pinned, and none of its skills may carry supporting files. Not a general-purpose plugin list." }, "sandboxImageId": { "type": "string", "description": "Sandbox-image pin: a project-shared image (see the images endpoints) that eval runs in this environment boot a fresh sandbox from. Absent when unpinned.", "minLength": 1, "pattern": ".*\\S.*" }, "revision": { "type": "integer", "description": "Optimistic-concurrency counter. Pass this back as `expectedRevision` on the next write; if it no longer matches, the write is rejected with 409 instead of overwriting a concurrent edit." }, "archived": { "type": "boolean", "description": "Archived environments cannot be edited or launched until restored." }, "archivedAt": { "type": "number", "description": "Unix epoch milliseconds. Present only when archived." }, "createdAt": { "type": "number", "description": "Unix epoch milliseconds." }, "updatedAt": { "type": "number", "description": "Unix epoch milliseconds." } } }, "ProjectEnvironmentPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/ProjectEnvironment" } } } }, "ProjectEnvironmentCreateRequest": { "type": "object", "description": "Unknown fields are rejected.", "required": [ "name", "hostId" ], "additionalProperties": false, "properties": { "name": { "type": "string", "minLength": 1 }, "description": { "type": "string" }, "hostId": { "type": "string" }, "serverAttachmentId": { "type": "string" }, "skillSelection": { "$ref": "#/components/schemas/EnvironmentSkillSelection" }, "pluginVersionIds": { "type": "array", "minItems": 1, "items": { "type": "string" }, "description": "Pinned plugin VERSION IDs. Narrow by design: the plugin must be installed and enabled, the version must be `ready`, at most one version per plugin may be pinned, and none of its skills may carry supporting files. Not a general-purpose plugin list." }, "sandboxImageId": { "type": "string", "minLength": 1, "description": "Optional sandbox-image pin. Must be a project-shared image; personal drafts are rejected — promote them first.", "pattern": ".*\\S.*" } } }, "ProjectEnvironmentUpdateRequest": { "type": "object", "description": "Only the fields you send change. `serverAttachmentId`, `skillSelection`, and `pluginVersionIds` are three-state: omit to leave unchanged, send `null` to CLEAR, send a value to set. An empty array is rejected — it is not a way to clear. Send at least one field besides `expectedRevision`. Unknown fields are rejected.", "required": [ "expectedRevision" ], "additionalProperties": false, "properties": { "expectedRevision": { "type": "integer", "minimum": 0, "description": "The `revision` you last read. If the environment changed since, the write is rejected with 409 rather than clobbering the other edit." }, "name": { "type": "string", "minLength": 1 }, "description": { "type": "string", "description": "Send an empty string to clear the description." }, "hostId": { "type": "string" }, "serverAttachmentId": { "type": "string", "nullable": true, "description": "`null` clears the pin and falls back to the host config's servers." }, "skillSelection": { "allOf": [ { "$ref": "#/components/schemas/EnvironmentSkillSelection" } ], "nullable": true, "description": "`null` clears the pinned skill selection." }, "pluginVersionIds": { "type": "array", "minItems": 1, "items": { "type": "string" }, "nullable": true, "description": "Pinned plugin VERSION IDs. Narrow by design: the plugin must be installed and enabled, the version must be `ready`, at most one version per plugin may be pinned, and none of its skills may carry supporting files. Not a general-purpose plugin list. `null` clears all pins." }, "sandboxImageId": { "type": [ "string", "null" ], "description": "New sandbox-image pin, or null to clear it. Omit to leave unchanged.", "minLength": 1, "pattern": ".*\\S.*" } } }, "ProjectEnvironmentRevisionRequest": { "type": "object", "description": "The optimistic-concurrency precondition. Unknown fields are rejected.", "required": [ "expectedRevision" ], "additionalProperties": false, "properties": { "expectedRevision": { "type": "integer", "minimum": 0, "description": "The `revision` you last read for this environment." } } }, "ProjectEnvironmentResolved": { "type": "object", "description": "What an environment resolves to right now — the same resolution an eval run performs.", "required": [ "environment", "hostId", "hostName", "hostConfigId", "selectedServerIds", "effectiveServerIds", "pluginVersions", "servers" ], "properties": { "environment": { "type": "object", "required": [ "id", "name", "revision" ], "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "revision": { "type": "integer", "description": "The revision this resolution reflects." } } }, "hostId": { "type": "string" }, "hostName": { "type": "string" }, "hostConfigId": { "type": "string", "description": "The host's config at resolve time — hosts rotate configs live, so this can change without the environment's `revision` changing." }, "serverAttachmentId": { "type": "string" }, "sandboxImageId": { "type": "string", "description": "The environment's sandbox-image pin, when set.", "minLength": 1, "pattern": ".*\\S.*" }, "selectedServerIds": { "type": "array", "items": { "type": "string" }, "description": "The closed non-plugin server set." }, "effectiveServerIds": { "type": "array", "items": { "type": "string" }, "description": "`selectedServerIds` plus the servers contributed by pinned plugin versions — the set a run actually connects. Identical to `selectedServerIds` when no plugins are pinned." }, "pluginVersions": { "type": "array", "description": "The resolved plugin version pins, in pin order.", "items": { "type": "object", "required": [ "pluginId", "pluginVersionId", "name", "bundleHash" ], "properties": { "pluginId": { "type": "string" }, "pluginVersionId": { "type": "string" }, "name": { "type": "string" }, "bundleHash": { "type": "string" } } } }, "servers": { "type": "array", "description": "Connectable projection of `effectiveServerIds`, healed to the current live servers.", "items": { "type": "object", "required": [ "serverId", "name" ], "properties": { "serverId": { "type": "string" }, "name": { "type": "string" } } } } } }, "SandboxImageBlueprintValidateRequest": { "type": "object", "additionalProperties": false, "required": [ "blueprint" ], "properties": { "blueprint": { "type": "string", "description": "Blueprint YAML to lint." } } }, "SandboxImageBlueprintValidateResult": { "type": "object", "description": "Lint result. `ok: true` carries the resolved base digest; `ok: false` carries structured errors. Always returned with HTTP 200 — an invalid blueprint is a successful lint.", "required": [ "ok" ], "properties": { "ok": { "type": "boolean" }, "baseImageDigest": { "type": "string", "description": "Resolved sha256 digest of `base` (present when ok)." }, "errors": { "type": "array", "items": { "type": "object", "required": [ "path", "message" ], "properties": { "path": { "type": "string", "description": "Location of the violation, e.g. `initialize[0].run` (empty for document-level errors)." }, "message": { "type": "string" } } } } }, "oneOf": [ { "required": [ "baseImageDigest" ], "properties": { "ok": { "const": true } } }, { "required": [ "errors" ], "properties": { "ok": { "const": false } } } ] }, "AgentTurnRequest": { "type": "object", "required": [ "messages" ], "properties": { "messages": { "type": "array", "minItems": 1, "maxItems": 50, "description": "The conversation so far, oldest first. The caller owns state and resends the full history each turn. Each message content is capped at 8,000 characters AND 8,192 UTF-8 bytes; the whole history is additionally capped at 98,304 UTF-8 bytes (96 KB).", "items": { "type": "object", "required": [ "role", "content" ], "properties": { "role": { "type": "string", "enum": [ "user", "assistant" ] }, "content": { "type": "string", "minLength": 1, "maxLength": 8000, "description": "Plain text. Max 8,000 characters and 8,192 UTF-8 bytes." } } } } } }, "AgentTurnResponse": { "type": "object", "required": [ "reply", "toolCalls", "createdResources", "usage" ], "properties": { "reply": { "type": "string", "description": "The assistant's final text for this turn." }, "toolCalls": { "type": "array", "items": { "type": "object", "required": [ "operation" ], "properties": { "operation": { "type": "string", "description": "Platform operation name invoked during the turn." } } } }, "createdResources": { "type": "array", "description": "Resources the turn created, with app deep links.", "items": { "type": "object", "required": [ "type", "id", "url" ], "properties": { "type": { "type": "string", "enum": [ "eval_suite" ] }, "id": { "type": "string" }, "name": { "type": "string" }, "url": { "type": "string", "format": "uri" } } } }, "usage": { "type": "object", "required": [ "inputTokens", "outputTokens" ], "properties": { "inputTokens": { "type": "integer" }, "outputTokens": { "type": "integer" } } } } } }, "responses": { "InternalError": { "description": "Something failed on MCPJam's side.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "code": "INTERNAL_ERROR", "message": "Unexpected internal error" } } } }, "ValidationError": { "description": "Malformed body or parameters.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "code": "VALIDATION_ERROR", "message": "Invalid JSON body" } } } }, "Unauthorized": { "description": "Missing, invalid, revoked, or orphaned key (`UNAUTHORIZED`) — or the **target MCP server** needs an OAuth grant (`OAUTH_REQUIRED`), which is a property of the server, not your key.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "examples": { "badKey": { "summary": "Invalid or revoked key", "value": { "code": "UNAUTHORIZED", "message": "Invalid API key" } }, "oauthRequired": { "summary": "Target server needs an OAuth grant", "value": { "code": "OAUTH_REQUIRED", "message": "Server requires OAuth authorization" } } } } } }, "Forbidden": { "description": "Key is valid but not allowed to do this.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "code": "FORBIDDEN", "message": "You do not have access to this project" } } } }, "NotFound": { "description": "Unknown project, server, or resource.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "code": "NOT_FOUND", "message": "Server not found" } } } }, "FeatureNotSupported": { "description": "The target server doesn't support this MCP capability.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "code": "FEATURE_NOT_SUPPORTED", "message": "Server does not support resources" } } } }, "RateLimited": { "description": "Per-key rate limit exceeded (60 requests/minute sustained, bursts up to 10). Honor `Retry-After` and back off with jitter.", "headers": { "Retry-After": { "description": "Seconds to wait before retrying.", "schema": { "type": "integer" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "code": "RATE_LIMITED", "message": "API key rate limit exceeded. Slow down and retry." } } } }, "ServerUnreachable": { "description": "Could not connect to the target MCP server.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "code": "SERVER_UNREACHABLE", "message": "Failed to connect to server" } } } }, "Timeout": { "description": "The target MCP server connected but didn't respond in time.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "code": "TIMEOUT", "message": "Request to server timed out" } } } }, "Conflict": { "description": "The resource is not in a state that accepts this write — a stale `expectedRevision`, a duplicate name, or an environment that cannot currently be launched. The request was well-formed; re-read the resource and retry.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "code": "CONFLICT", "message": "Environment changed since you loaded it (expected revision 3, current 5). Reload and retry." } } } } } } }