# Append a session note Source: https://docs.mcpjam.com/api-reference/agent-browsers/append-a-session-note /reference/openapi.json post /browser-sessions/note Records a note without driving the browser. commandId provides idempotency. Request bodies are limited to 64,000 characters. # Close a browser session Source: https://docs.mcpjam.com/api-reference/agent-browsers/close-a-browser-session /reference/openapi.json post /browser-sessions/close Closes the durable session and releases its desktop. Use a new runKey for another browser. Request bodies are limited to 64,000 characters. # Execute a browser command Source: https://docs.mcpjam.com/api-reference/agent-browsers/execute-a-browser-command /reference/openapi.json post /browser-sessions/command Returns an executed, refused or unknown result. Reusing commandId with the same payload returns its receipt without replay; pending receipts return unknown. Page content is untrusted data. Request bodies are limited to 64,000 characters. # Fetch a browser screenshot Source: https://docs.mcpjam.com/api-reference/agent-browsers/fetch-a-browser-screenshot /reference/openapi.json post /browser-sessions/artifact Returns screenshot as base64 PNG, or null when absent. artifactId is the command ID from the result. Request bodies are limited to 64,000 characters. # List your browser sessions Source: https://docs.mcpjam.com/api-reference/agent-browsers/list-your-browser-sessions /reference/openapi.json post /browser-sessions/sessions Returns a sessions array of sessions owned by the caller in this project. Request bodies are limited to 64,000 characters. # Open a cloud browser session Source: https://docs.mcpjam.com/api-reference/agent-browsers/open-a-cloud-browser-session /reference/openapi.json post /browser-sessions/session Returns session identity, engine, bootId and runKey. Same runKey and policy reuse the session; omit runKey for a new isolated browser. Request bodies are limited to 64,000 characters. # Read command receipts Source: https://docs.mcpjam.com/api-reference/agent-browsers/read-command-receipts /reference/openapi.json post /browser-sessions/trace Returns entries ordered by sequence, optionally filtered by commandId. Screenshot payloads are fetched separately. Request bodies are limited to 64,000 characters. # Run one headless agent turn Source: https://docs.mcpjam.com/api-reference/agent/run-one-headless-agent-turn /reference/openapi.json post /projects/{projectId}/agent Runs ONE assistant turn over the supplied message history and responds synchronously with the final assistant text, the operations it invoked, references to any resources it created, and any actions awaiting human approval. The caller owns conversation state: resend the full history each turn. The model is pinned server-side (hosted catalog) and billed to the project. Tools are TIERED. Read operations and non-spending writes execute directly. Three categories are PROPOSAL-ONLY: operations that SPEND once (running a suite or a case, generating cases, cancelling a run), operations whose spend RECURS (setting a suite's schedule), and operations whose effects MCPJam cannot describe or undo because they leave MCPJam entirely (`call_server_tool`, which runs a tool on the caller's own MCP server). For each, the turn validates against the operation's real schema, persists a proposal, and returns it in `proposedActions` for a person to approve; `confirmSeverity` says which hazard the approver should be warned about. MCPJam-side deletions are excluded from the surface entirely — a proposal makes spend deliberate, but it does not make a deletion recoverable. Proposals are only offered when the request supplies a `conversationId`; without somewhere to collect a click there is nothing to approve, so the tools are withheld rather than offered and then refused. Every 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). When a turn fails or times out AFTER work already persisted, the error body's `details.createdResources` and `details.proposedActions` carry what survived — check them before retrying. **Retry policy:** send `idempotencyKey` as a STABLE identity for the triggering event (not a fresh uuid per attempt). Every write the turn performs derives its own key from it, so a retried turn's mutations land on the rows the first attempt created instead of duplicating them. The key makes a retry SAFE; it does not make one free — dedupe at your own trigger so most retries never re-run the turn at all. A retry whose model authors materially different arguments hashes differently and is correctly treated as a different write. # Export project traces as OTLP/JSON Source: https://docs.mcpjam.com/api-reference/catalog/export-project-traces-as-otlpjson /reference/openapi.json get /trace-exports/otlp Returns an OTLP `ExportTraceServiceRequest`. Pagination rides in RESPONSE HEADERS rather than the body so the payload stays a valid OTLP document: read `x-mcpjam-next-cursor` and feed it back as `cursor`, and treat `x-mcpjam-export-complete: true` as the end of the stream. # Get the authenticated account Source: https://docs.mcpjam.com/api-reference/catalog/get-the-authenticated-account /reference/openapi.json get /me The user behind the API key (keys act as their creator, scoped to one organization). # Get the host-compat catalog Source: https://docs.mcpjam.com/api-reference/catalog/get-the-host-compat-catalog /reference/openapi.json get /host-catalog 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. # List a project's eval suites Source: https://docs.mcpjam.com/api-reference/catalog/list-a-projects-eval-suites /reference/openapi.json get /projects/{projectId}/eval-suites Eval suites in the project with latest-run summaries and pass-rate trends — the `suiteId`s the eval-run routes take. # List a project's servers Source: https://docs.mcpjam.com/api-reference/catalog/list-a-projects-servers /reference/openapi.json get /projects/{projectId}/servers The MCP servers saved in the project — the `serverId`s every other route takes. STDIO command/args/env and raw headers are never exposed. # List chat sessions Source: https://docs.mcpjam.com/api-reference/catalog/list-chat-sessions /reference/openapi.json get /chat-sessions 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. # List organizations Source: https://docs.mcpjam.com/api-reference/catalog/list-organizations /reference/openapi.json get /organizations The organizations the caller belongs to — the source of the `organizationId` that `GET /projects` filters by and `POST /projects` accepts. An API key is bound to one organization and only ever sees that one. Read-only: organization, member, role and billing changes are not part of the public API. # List projects Source: https://docs.mcpjam.com/api-reference/catalog/list-projects /reference/openapi.json get /projects Projects the caller can access. API keys only ever see projects inside the key's organization. # Read the organization's spend budget Source: https://docs.mcpjam.com/api-reference/catalog/read-the-organizations-spend-budget /reference/openapi.json get /organizations/{organizationId}/spend-budget The ceiling on what this organization may spend on MCPJam-billed work — chat, evals, judges and computer time — in the current billing window, and how much of it has been spent. Any member may read it: a member who cannot raise the ceiling still needs to know it exists, because it is what refused their run. `capUsd: null` means the organization is uncapped, which is the default. Runs that use your own provider keys are never counted against the budget and never blocked by it. Neither is the free daily allowance — it is not credits. # Remove the organization's spend budget Source: https://docs.mcpjam.com/api-reference/catalog/remove-the-organizations-spend-budget /reference/openapi.json delete /organizations/{organizationId}/spend-budget Leave the organization uncapped. **Requires organization owner or admin.** The window's spend counter survives: it is a record of spend, not of the budget, and a budget set again later measures against the spend already made this period. # Search or list unified sessions Source: https://docs.mcpjam.com/api-reference/catalog/search-or-list-unified-sessions /reference/openapi.json get /projects/{projectId}/sessions The unified, cross-surface sessions feed for one project: Playground, user-testing, eval, and swarm transcripts in one list, newest first. Project-NESTED (unlike `/chat-sessions`) because `projectId` owns the scope here rather than filtering it. Supplying `q` at all switches to relevance-ordered search; `scope` then chooses what is searched. Row-level visibility applies in every mode: a caller never receives another member's private Playground session. The response echoes the honored `scope`. A deployment predating that parameter omits the echo — clients requesting a non-default scope MUST treat its absence as "unsupported" rather than accepting the results. # Set the organization's spend budget Source: https://docs.mcpjam.com/api-reference/catalog/set-the-organizations-spend-budget /reference/openapi.json put /organizations/{organizationId}/spend-budget Set or replace the ceiling. **Requires organization owner or admin.** When the budget is reached, new MCPJam-billed work — chat turns, eval runs, computer starts — is refused for the whole organization until the window resets or an owner or admin raises the cap. Work already in flight finishes, and running computers keep going until they hibernate. `capUsd` is stored as whole credits (1 credit = 1 cent), so it is rounded to the cent; the response reports what was kept. Changing the cap mid-window does not reset the spend counter, and does not re-announce a threshold the organization was already told about. # The MCPJam-provided model catalog Source: https://docs.mcpjam.com/api-reference/catalog/the-mcpjam-provided-model-catalog /reference/openapi.json get /models PUBLIC: this route carries no user or project data and requires no bearer token, matching the upstream catalog it proxies. Mounted before authentication for that reason. # Add a note to the API session browser Source: https://docs.mcpjam.com/api-reference/chat-sessions/add-a-note-to-the-api-session-browser /reference/openapi.json post /chat-sessions/{sessionId}/browser/note Same-user, API-origin session browser operation. sessionId is the public chatSessions id. Screenshot evidence is returned as URLs; browser identity persists across turns. # Close the API session browser Source: https://docs.mcpjam.com/api-reference/chat-sessions/close-the-api-session-browser /reference/openapi.json post /chat-sessions/{sessionId}/browser/close Same-user, API-origin session browser operation. sessionId is the public chatSessions id. Screenshot evidence is returned as URLs; browser identity persists across turns. # Command the API session browser Source: https://docs.mcpjam.com/api-reference/chat-sessions/command-the-api-session-browser /reference/openapi.json post /chat-sessions/{sessionId}/browser/command Same-user, API-origin session browser operation. sessionId is the public chatSessions id. Screenshot evidence is returned as URLs; browser identity persists across turns. # Create an API session shell and open its browser Source: https://docs.mcpjam.com/api-reference/chat-sessions/create-an-api-session-shell-and-open-its-browser /reference/openapi.json post /chat-sessions/browser Same-user, API-origin session browser operation. sessionId is the public chatSessions id. Screenshot evidence is returned as URLs; browser identity persists across turns. # Open the API session browser Source: https://docs.mcpjam.com/api-reference/chat-sessions/open-the-api-session-browser /reference/openapi.json post /chat-sessions/{sessionId}/browser/open Same-user, API-origin session browser operation. sessionId is the public chatSessions id. Screenshot evidence is returned as URLs; browser identity persists across turns. # Read a chat session's execution trace Source: https://docs.mcpjam.com/api-reference/chat-sessions/read-a-chat-sessions-execution-trace /reference/openapi.json get /chat-sessions/{sessionId}/trace Per-turn execution spans: per-tool-call latency, token usage, and indices into the transcript. Same span shape as the eval iteration trace. **Incremental by default** — returns the LATEST turn, not the whole session, because a session's spans are the largest thing this API emits and the turn you just took is the one you are debugging. Reach older turns with `turnId` or `afterPromptIndex`; use `includeSpans=false` for cheap per-turn summaries. A turn whose spans could not be read reports `spansUnavailable: true` rather than an empty `spans` array: "made no calls" and "could not fetch" are opposite conclusions. # Read a chat session's messages Source: https://docs.mcpjam.com/api-reference/chat-sessions/read-a-chat-sessions-messages /reference/openapi.json get /chat-sessions/{sessionId} Session metadata plus a bounded window of its raw messages. `index` on each message is its ABSOLUTE position in the stored transcript, not its position in the returned page: trace spans reference messages positionally, so this read and `/trace` join on that number. A transcript that could not be read reports `transcriptUnavailable: true` and a **null** `messageCount` — never 0, which would claim the conversation is empty. # Read an API session browser screenshot URL Source: https://docs.mcpjam.com/api-reference/chat-sessions/read-an-api-session-browser-screenshot-url /reference/openapi.json post /chat-sessions/{sessionId}/browser/artifact Same-user, API-origin session browser operation. sessionId is the public chatSessions id. Screenshot evidence is returned as URLs; browser identity persists across turns. # Read the API session browser command trace Source: https://docs.mcpjam.com/api-reference/chat-sessions/read-the-api-session-browser-command-trace /reference/openapi.json post /chat-sessions/{sessionId}/browser/trace Same-user, API-origin session browser operation. sessionId is the public chatSessions id. Screenshot evidence is returned as URLs; browser identity persists across turns. # Send one agent Playground message Source: https://docs.mcpjam.com/api-reference/chat-sessions/send-one-agent-playground-message /reference/openapi.json post /chat-sessions/messages Send one message to a project's MCP servers and get the model's reply plus the telemetry a participant in the conversation could not see: which tools ran, with what arguments, what each returned, per-call latency, and token usage. **Spends model credits per call.** `idempotencyKey` is required and must be STABLE for the triggering intent — a fresh key per HTTP attempt deduplicates nothing, so a timeout-and-retry would run and bill the turn twice. With a stable key, a retry replays the completed turn. Omit `sessionId` to start a session; pass the one this returns to continue it. Configuration (`modelId`, target, `systemPrompt`, `toolMode`) pins on the FIRST turn — a continuation that resends any of it is refused with `details.reason: "CONFIG_ON_CONTINUATION"`. Only sessions created through this endpoint may be continued through it (`CONTINUATION_NOT_ALLOWED`): appending to a human's live Playground session would interleave two writers on one transcript. `toolMode` defaults to `read_only`, which advertises only tools the server annotated `readOnlyHint: true`. That hint is server-asserted, so `read_only` is a policy this host applies, not a guarantee it can verify. `auto` advertises everything and **may cause real external side effects** through arbitrary third-party tools. Pass `hostId` — or target an `environmentId`, which pins its own host — to run the turn AS a saved host. That is what selects the ENGINE: a host declaring an agent harness runs the real runtime, and one that cannot run here is refused BEFORE it spends (422, `details.reason: "HARNESS_UNAVAILABLE"`, with `details.kind` naming the rule that refused) rather than quietly downgraded to the emulated engine. Every response names what actually ran in `engine`. `hostId` is per-turn and cannot be pinned, so a continuation of a session that named ONLY a host must re-send it — omitting it is a 400 with `details.reason: "HOST_TARGET_REQUIRED"`, never a quiet fall back to the emulated engine. # Create a client (from a template or a full config) Source: https://docs.mcpjam.com/api-reference/clients/create-a-client-from-a-template-or-a-full-config /reference/openapi.json post /projects/{projectId}/clients Creates a client in the project and responds `201` with the new client's detail. Seed the config one of two ways: pass `template` (a built-in template id, optional `theme`) to seed server-side from the live backend client-template catalog, falling back to the bundled SDK catalog snapshot if the live catalog is unavailable, OR pass `config` (a full client config v2). Exactly one of `template` or `config` is required. Guest callers are denied (client creation is a write). # Delete a client Source: https://docs.mcpjam.com/api-reference/clients/delete-a-client /reference/openapi.json delete /projects/{projectId}/clients/{client} Permanently deletes the client. Refused while a live environment, an active legacy journey or an eval suite still holds it — the error names what to detach first. Guest callers are denied (a write). # Duplicate a client Source: https://docs.mcpjam.com/api-reference/clients/duplicate-a-client /reference/openapi.json post /projects/{projectId}/clients/{client}/duplicate Creates a new client carrying the selected client's current config, and responds `201` with the copy's detail. Purely additive — the source is untouched. # Get a client Source: https://docs.mcpjam.com/api-reference/clients/get-a-client /reference/openapi.json get /projects/{projectId}/clients/{client} One client's full settings, including its resolved config, its `configId` (the concurrency token every write takes), and `impact` — what a config edit would follow. The path segment accepts a client **name or ID**. A client that exists but belongs to a different project reads as `404 NOT_FOUND`. # List a project's clients Source: https://docs.mcpjam.com/api-reference/clients/list-a-projects-clients /reference/openapi.json get /projects/{projectId}/clients The clients saved in the project — the named, reusable configurations that define how MCPJam connects to and talks to your MCP servers. Returns the `id`s and `configId`s the detail and write routes take. Clients that exist only as the private backing of a User Testing scenario are omitted unless `includePrivateBacking=true`. # Replace a client's servers Source: https://docs.mcpjam.com/api-reference/clients/replace-a-clients-servers /reference/openapi.json post /projects/{projectId}/clients/{client}/servers Replaces the client's required and optional saved-server attachments without round-tripping the whole config. Composed server-side from the client's current config, so every other setting is preserved — and `expectedConfigId` is required so two concurrent server replacements cannot silently lose one. # Update a client Source: https://docs.mcpjam.com/api-reference/clients/update-a-client /reference/openapi.json patch /projects/{projectId}/clients/{client} Edit a client's display name and/or its config. Two ways to change the config: `config` replaces it wholesale, and `set` changes named fields over the config read inside the write transaction — prefer `set`, because a whole-config round-trip composed from a stale read reverts whatever landed in between. Config edits require `expectedConfigId` and renames require `expectedName`. A stale token is `409 CONFLICT` whose `details` carries the current value: re-read the client and retry. A config edit that resolves to byte-identical settings writes nothing at all. Guest callers are denied (a write). # Fetch a bounded conformance report projection Source: https://docs.mcpjam.com/api-reference/conformance-runs/fetch-a-bounded-conformance-report-projection /reference/openapi.json get /projects/{projectId}/conformance-runs/{runId}/report Failing checks only, failed before could-not-run, capped at 50. `totalCases` is every case; `totalFailingCases` is the failing population `truncated` is computed from. `pending` on a check means this profile reported it but did not score it. `404` when the run has no stored report. # Finalize an incrementally ingested conformance run Source: https://docs.mcpjam.com/api-reference/conformance-runs/finalize-an-incrementally-ingested-conformance-run /reference/openapi.json post /projects/{projectId}/conformance-ingest/runs/finalize How conformance runs executed OUTSIDE the platform (local CLI, GitHub Actions) reach the Conformance history. Authenticate like any other `/api/v1` route (typically an `sk_` key). The `{projectId}` segment always wins over any `projectId` in the body. The literal `default` resolves to the key org's Default project. STATUS AND BODY PASS THROUGH VERBATIM. Recomputes the pooled outcome from uploaded cases. Idempotent. # Get a persisted conformance run Source: https://docs.mcpjam.com/api-reference/conformance-runs/get-a-persisted-conformance-run /reference/openapi.json get /projects/{projectId}/conformance-runs/{runId} Status, outcome, and score are three different answers. A run whose `projectId` is not the path's is `404`. Per-suite `profileId` / `pending` are present when the backend has stored them. # Ingest a completed conformance run in one call Source: https://docs.mcpjam.com/api-reference/conformance-runs/ingest-a-completed-conformance-run-in-one-call /reference/openapi.json post /projects/{projectId}/conformance-ingest/report How conformance runs executed OUTSIDE the platform (local CLI, GitHub Actions) reach the Conformance history. Authenticate like any other `/api/v1` route (typically an `sk_` key). The `{projectId}` segment always wins over any `projectId` in the body. The literal `default` resolves to the key org's Default project. STATUS AND BODY PASS THROUGH VERBATIM. The whole-run variant: submit a finished composite report. This is what the SDK reporter uses by default. # Keep a long-running uploaded conformance run alive Source: https://docs.mcpjam.com/api-reference/conformance-runs/keep-a-long-running-uploaded-conformance-run-alive /reference/openapi.json post /projects/{projectId}/conformance-ingest/runs/heartbeat How conformance runs executed OUTSIDE the platform (local CLI, GitHub Actions) reach the Conformance history. Authenticate like any other `/api/v1` route (typically an `sk_` key). The `{projectId}` segment always wins over any `projectId` in the body. The literal `default` resolves to the key org's Default project. STATUS AND BODY PASS THROUGH VERBATIM. # List persisted conformance runs Source: https://docs.mcpjam.com/api-reference/conformance-runs/list-persisted-conformance-runs /reference/openapi.json get /projects/{projectId}/conformance-runs Newest first. Optionally narrow to one saved server. `pending` counts checks the run's profile reported but did not score. # Open a conformance run for incremental ingestion Source: https://docs.mcpjam.com/api-reference/conformance-runs/open-a-conformance-run-for-incremental-ingestion /reference/openapi.json post /projects/{projectId}/conformance-ingest/runs/start How conformance runs executed OUTSIDE the platform (local CLI, GitHub Actions) reach the Conformance history. Authenticate like any other `/api/v1` route (typically an `sk_` key). The `{projectId}` segment always wins over any `projectId` in the body. The literal `default` resolves to the key org's Default project. STATUS AND BODY PASS THROUGH VERBATIM. Opens (or idempotently reuses) a run that suite reports are appended to. # Start a persisted conformance run Source: https://docs.mcpjam.com/api-reference/conformance-runs/start-a-persisted-conformance-run /reference/openapi.json post /projects/{projectId}/servers/{serverId}/conformance-runs Grades the saved server against the protocol, apps, and tasks suites and returns `202` with a run id. The target comes from the saved server the path names: a body cannot supply a URL. OAuth is not startable here. Poll the run detail for the verdict. Status, outcome, and score are three different answers. # Upload one suite report onto a started conformance run Source: https://docs.mcpjam.com/api-reference/conformance-runs/upload-one-suite-report-onto-a-started-conformance-run /reference/openapi.json post /projects/{projectId}/conformance-ingest/runs/reports How conformance runs executed OUTSIDE the platform (local CLI, GitHub Actions) reach the Conformance history. Authenticate like any other `/api/v1` route (typically an `sk_` key). The `{projectId}` segment always wins over any `projectId` in the body. The literal `default` resolves to the key org's Default project. STATUS AND BODY PASS THROUGH VERBATIM. One requested suite per call. Content-hash conflicts 409. # Cancel an in-flight readiness run Source: https://docs.mcpjam.com/api-reference/directory-readiness/cancel-an-in-flight-readiness-run /reference/openapi.json post /projects/{projectId}/readiness-runs/{runId}/cancel The executing node learns about this on its next heartbeat and aborts the run in flight, which matters more than the row's status: the thing being stopped is traffic to somebody else's server. # Fetch a readiness run's full report Source: https://docs.mcpjam.com/api-reference/directory-readiness/fetch-a-readiness-runs-full-report /reference/openapi.json get /projects/{projectId}/readiness-runs/{runId}/report Every finding, with its class, provenance, publisher-documentation citation and remediation. AI-derived findings carry `provenance` of `llm` and are confined to the experience lane as `heuristic` or `manual-review`; they can never decide a lane. `404` when the run has no stored report: it may be in flight, it may have failed, or its report may have aged past retention. # List readiness runs Source: https://docs.mcpjam.com/api-reference/directory-readiness/list-readiness-runs /reference/openapi.json get /projects/{projectId}/readiness-runs # Read one readiness run Source: https://docs.mcpjam.com/api-reference/directory-readiness/read-one-readiness-run /reference/openapi.json get /projects/{projectId}/readiness-runs/{runId} Lane statuses, coverage and the observation axis. The full report — every finding with its citation and provenance — is a separate fetch. # Start a Claude directory-readiness run Source: https://docs.mcpjam.com/api-reference/directory-readiness/start-a-claude-directory-readiness-run /reference/openapi.json post /projects/{projectId}/servers/{serverId}/readiness-runs/claude Grades the saved server against Anthropic's connector-directory requirements and returns `202` with a run id. The target comes from the saved server the path names: a body cannot supply a URL, an actor, an organization, a model or a cost. Poll the run detail for the verdict. Deterministic grading is free. # Start an OpenAI plugin-directory readiness run Source: https://docs.mcpjam.com/api-reference/directory-readiness/start-an-openai-plugin-directory-readiness-run /reference/openapi.json post /projects/{projectId}/servers/{serverId}/readiness-runs/openai Grades the saved server against OpenAI's plugin-directory requirements and returns `202` with a run id. `submissionMode` is required and never inferred: inferring it would read a forgotten package as `mcp-only` and report the package lane not-applicable, turning a missing input into a clean bill of health. Hosted runs grade the two wire-safe shapes; the package shapes need an upload this API cannot receive and are available from the CLI. # Archive an environment Source: https://docs.mcpjam.com/api-reference/environments/archive-an-environment /reference/openapi.json post /projects/{projectId}/environments/{environmentId}/archive 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. # Check what this deployment's environment surface supports Source: https://docs.mcpjam.com/api-reference/environments/check-what-this-deployments-environment-surface-supports /reference/openapi.json get /projects/{projectId}/environments/capabilities Report which environment features this MCPJam deployment accepts. Call it before sending a model override: clients ship independently of the platform, and a field an older deployment does not know is a hard validation error there rather than a silently ignored one. A deployment too old to answer reports `false` for everything, which is the correct assumption. # Create an environment Source: https://docs.mcpjam.com/api-reference/environments/create-an-environment /reference/openapi.json post /projects/{projectId}/environments 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. # Get or create an unnamed environment for a composed stack Source: https://docs.mcpjam.com/api-reference/environments/get-or-create-an-unnamed-environment-for-a-composed-stack /reference/openapi.json post /projects/{projectId}/environments/ensure-adhoc GET-OR-CREATE an UNNAMED, content-addressed environment for a composed stack — a host plus an optional server group, model override, computer image and pinned skills. Distinct from `POST /environments`, which mints a NAMED row that lands in the project's environment list forever. A composed stack is a throwaway: the caller wants to run this exact combination, not to add a permanent entry someone else has to reason about. Deduplicated by a server-side fingerprint of the stack, so the same stack always returns the same environment. `created` says which happened — the status is `200` either way, because get-or-create cannot honestly answer `201` on the dedupe path. A deployment that predates ad-hoc environments answers `400` with `details.reason: "ADHOC_UNAVAILABLE"`. Requires project membership (admin only when the stack pins plugin versions). Promote one to a named environment with `POST /environments/{environmentId}/name`. # List a project's environments Source: https://docs.mcpjam.com/api-reference/environments/list-a-projects-environments /reference/openapi.json get /projects/{projectId}/environments The project environments saved in the project. Archived environments are excluded unless `includeArchived=true` — you need that to find one to restore. # List a project's server groups Source: https://docs.mcpjam.com/api-reference/environments/list-a-projects-server-groups /reference/openapi.json get /projects/{projectId}/server-groups A server group is an immutable snapshot of a set of saved servers. Pinning one on an environment is what keeps a run off its host's live server list, so a composed run keeps testing the servers it was composed against even after the shared host is edited. # Preview what an environment resolves to Source: https://docs.mcpjam.com/api-reference/environments/preview-what-an-environment-resolves-to /reference/openapi.json get /projects/{projectId}/environments/{environmentId}/resolve 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`, …). # Promote an unnamed environment Source: https://docs.mcpjam.com/api-reference/environments/promote-an-unnamed-environment /reference/openapi.json post /projects/{projectId}/environments/{environmentId}/name Give an UNNAMED (ad-hoc) environment a name, promoting it IN PLACE — the same environment, now a permanent entry in the project's list, with the same id every existing run still points at. The ONLY promotion path. `PATCH /environments/{environmentId}` renames an already-named environment and is admin-gated; this is member-gated and refuses a row that already has a name (`409`). The platform keeps them apart on purpose: routing promotion through the rename would either open it to members or leave ad-hoc rows unnameable. Promotion also drops the content fingerprint, because a named environment is mutable: keeping it would let a later identical composition deduplicate onto a row that has since been edited into something else. # Restore an archived environment Source: https://docs.mcpjam.com/api-reference/environments/restore-an-archived-environment /reference/openapi.json post /projects/{projectId}/environments/{environmentId}/restore 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. # Show one environment Source: https://docs.mcpjam.com/api-reference/environments/show-one-environment /reference/openapi.json get /projects/{projectId}/environments/{environmentId} Read one environment, including the `revision` you pass back as `expectedRevision` when writing to it. # Snapshot servers into a server group Source: https://docs.mcpjam.com/api-reference/environments/snapshot-servers-into-a-server-group /reference/openapi.json post /projects/{projectId}/server-groups Creates a server group holding exactly the given servers and responds `201` with its detail. Groups are create-only — there is no update route, because an editable group would reintroduce the drift that pinning exists to prevent. Names are unique per project, so a clash responds `409`. Servers must belong to this project and must not be plugin-managed. # Update an environment Source: https://docs.mcpjam.com/api-reference/environments/update-an-environment /reference/openapi.json patch /projects/{projectId}/environments/{environmentId} 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. # Append iterations to an open run Source: https://docs.mcpjam.com/api-reference/eval-runs/append-iterations-to-an-open-run /reference/openapi.json post /projects/{projectId}/eval-ingest/runs/iterations Appends one batch of iteration results to a run opened by `runs/start`. How eval runs executed OUTSIDE the platform (local dev, CI) reach the Evals dashboard. Authenticate like any other `/api/v1` route (typically an `sk_` key); the gateway swaps in a delegated org-scoped token so the backend's fail-closed org scoping applies. The `{projectId}` segment declares where results land and always wins over any `projectId` in the body. The literal `default` resolves to the key org's Default project — the zero-config CI case. STATUS AND BODY PASS THROUGH VERBATIM: success shapes are the legacy `{ ok: true, ... }` envelopes the SDK reporter parses, not the v1 resource envelope. # Cancel a run Source: https://docs.mcpjam.com/api-reference/eval-runs/cancel-a-run /reference/openapi.json post /projects/{projectId}/eval-runs/{runId}/cancel Request cancellation of an in-flight run; marks the run and its pending/running iterations `cancelled`. A run held in `grading` for its gating judge CAN be cancelled — it lands `cancelled` with no verdict, which is the honest outcome for a run nobody waited out. A no-op success when the run is already cancelled; returns `409` when the run already reached a terminal status (`completed`/`failed`/`timed_out`). # Close an open ingestion run Source: https://docs.mcpjam.com/api-reference/eval-runs/close-an-open-ingestion-run /reference/openapi.json post /projects/{projectId}/eval-ingest/runs/finalize Marks the run complete and computes its rollups. Until this is called the run stays open in the dashboard. How eval runs executed OUTSIDE the platform (local dev, CI) reach the Evals dashboard. Authenticate like any other `/api/v1` route (typically an `sk_` key); the gateway swaps in a delegated org-scoped token so the backend's fail-closed org scoping applies. The `{projectId}` segment declares where results land and always wins over any `projectId` in the body. The literal `default` resolves to the key org's Default project — the zero-config CI case. STATUS AND BODY PASS THROUGH VERBATIM: success shapes are the legacy `{ ok: true, ... }` envelopes the SDK reporter parses, not the v1 resource envelope. # Compare a run against a baseline Source: https://docs.mcpjam.com/api-reference/eval-runs/compare-a-run-against-a-baseline /reference/openapi.json get /projects/{projectId}/eval-runs/{runId}/compare Compare this run against a baseline run: per-case status (`regressed`, `fixed`, `new_case`, `removed_case`, `changed`), per-scorer pass-rate and mean deltas from the evaluation contract, and whether the evaluation config changed. Omit `baseRunId` to compare against the nearest earlier **completed** run in the same suite. Returns `404` with `details.reason` = `BASELINE_NOT_FOUND` when there is no comparable predecessor — that is an incomplete comparison, not a failing one. A scorer whose `definitionChanged` is `true` was graded by a different definition on each side, so its delta is not a regression. # Create an eval case Source: https://docs.mcpjam.com/api-reference/eval-runs/create-an-eval-case /reference/openapi.json post /projects/{projectId}/eval-suites/{suiteId}/cases Adds one case to the suite. Both `title` and a non-empty `steps` array are required — `steps` is optional on the shared case shape so PATCH can be partial, but a case persisted without executable steps could never run. When `models` is omitted the suite's configured model is used. # Create an eval run (async) Source: https://docs.mcpjam.com/api-reference/eval-runs/create-an-eval-run-async /reference/openapi.json post /projects/{projectId}/eval-runs 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"`. A 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"`. For a suite with attached project environments, pass `environmentId` to choose which one the run uses; the `202` echoes the resolved `environment` triple, and `GET /eval-runs/{runId}` reports the same triple for the life of the run. To make the run readable as something more specific than `API` in the Runs table, send `x-mcpjam-launcher` (and `x-mcpjam-ci` from a CI job). Both are labels: `source` is stamped `api` either way. # Create an eval suite (author-only, does not run) Source: https://docs.mcpjam.com/api-reference/eval-runs/create-an-eval-suite-author-only-does-not-run /reference/openapi.json post /projects/{projectId}/eval-suites 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`). This 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). The 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). Guest callers are denied (suite creation is a write). # Create several eval cases Source: https://docs.mcpjam.com/api-reference/eval-runs/create-several-eval-cases /reference/openapi.json post /projects/{projectId}/eval-suites/{suiteId}/cases/batch Adds up to 100 cases to the suite in one call — the bulk form of the single create, and the way to import a suite or convert a repo's test files without a round trip per case. Every entry needs a `title` and a non-empty `steps` array. Cases are validated together and reported individually: a `failed` entry does not roll back its committed siblings, so the response is `201` even when some entries were refused — read `failed` rather than branching on the status alone. Send an `Idempotency-Key` header to make a retry land on the same rows. # Delete an eval case Source: https://docs.mcpjam.com/api-reference/eval-runs/delete-an-eval-case /reference/openapi.json delete /projects/{projectId}/eval-suites/{suiteId}/cases/{caseId} Removes the case from the suite. Past runs that executed it are not affected. A case in a CI-owned suite (see `EvalSuiteDetail.managedBy`) refuses this with `409`; send the suite file's `suite.id` as `declaredSuiteId` to delete as the file. # Delete an eval suite Source: https://docs.mcpjam.com/api-reference/eval-runs/delete-an-eval-suite /reference/openapi.json delete /projects/{projectId}/eval-suites/{suiteId} Deletes the suite and its cases. Past runs are not deleted. A CI-owned suite (see `EvalSuiteDetail.managedBy`) refuses this with `409`; send its file's `suite.id` as `declaredSuiteId` to delete as the file. # Enable, repoint, or disable a suite's schedule Source: https://docs.mcpjam.com/api-reference/eval-runs/enable-repoint-or-disable-a-suites-schedule /reference/openapi.json patch /projects/{projectId}/eval-suites/{suiteId}/schedule A schedule fires exactly ONE run per interval, so an environment-based suite must pin exactly one of its attached environments. Omitting `environmentId` on a single-environment suite means that environment; omitting it on a multi-environment suite is a VALIDATION_ERROR. Disabling preserves the existing pin. Responds with the full updated suite. # Generate eval cases from the suite's tools Source: https://docs.mcpjam.com/api-reference/eval-runs/generate-eval-cases-from-the-suites-tools /reference/openapi.json post /projects/{projectId}/eval-suites/{suiteId}/cases/generate Discovers the suite's server tools over a live MCP connection, generates cases against them, and persists them — the only edit route that connects to a server, and the only one that SPENDS ORG CREDITS. Synchronous: connect, generate, persist, disconnect, respond. An environment-based suite generates against that environment's closed server set, so the cases match the tools its runs will actually see. Pass `x-mcpjam-idempotency-key` to make a retry safe: drafts are recorded backend-side BEFORE any case is persisted, so a replay reuses them instead of spending credits again, and each case is persisted under a derived per-item key so the loop is resumable. # Get a run's decision summary Source: https://docs.mcpjam.com/api-reference/eval-runs/get-a-runs-decision-summary /reference/openapi.json get /projects/{projectId}/eval-runs/{runId}/decision-summary The canonical, versioned run decision contract: the verdict and where it came from, the counts with the population they count, the run's own verdict decision when it has one, and one page of per-trial diagnostics carrying the user-value chain, the first failed stage, the failure category, evidence scoped to that stage, and one next action. ADDITIVE and composed: it is the same two reads a caller could make by hand (`GET …/eval-runs/{runId}` and `GET …/iterations`), assembled once here so every client shares one reading of a run instead of inventing its own. `cursor` and `limit` page the DIAGNOSTICS using the same cursors the iterations endpoint issues, and `diagnostics.complete` says honestly whether the page you got is the run's whole non-passing set. # Get a run's gate waiver Source: https://docs.mcpjam.com/api-reference/eval-runs/get-a-runs-gate-waiver /reference/openapi.json get /projects/{projectId}/eval-runs/{runId}/gate-waivers The waiver in force over this run, or `null`. Available to anyone who can VIEW the run, not only to those who can grant one — a waiver its readers cannot see is not a visible waiver. A client gating on this run does not need this endpoint: `GET /eval-runs/{runId}` already carries `gateWaiver`, so a gate can fold one in without a second round trip. # Get an iteration trace Source: https://docs.mcpjam.com/api-reference/eval-runs/get-an-iteration-trace /reference/openapi.json get /projects/{projectId}/eval-runs/{runId}/iterations/{iterationId}/trace 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. # Get an iteration's step results Source: https://docs.mcpjam.com/api-reference/eval-runs/get-an-iterations-step-results /reference/openapi.json get /projects/{projectId}/eval-runs/{runId}/iterations/{iterationId}/steps 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. # Get an upload URL for a run artifact Source: https://docs.mcpjam.com/api-reference/eval-runs/get-an-upload-url-for-a-run-artifact /reference/openapi.json post /projects/{projectId}/eval-ingest/artifacts/upload-url Mints a short-lived URL for uploading a widget blob or other artifact referenced by an ingested run. How eval runs executed OUTSIDE the platform (local dev, CI) reach the Evals dashboard. Authenticate like any other `/api/v1` route (typically an `sk_` key); the gateway swaps in a delegated org-scoped token so the backend's fail-closed org scoping applies. The `{projectId}` segment declares where results land and always wins over any `projectId` in the body. The literal `default` resolves to the key org's Default project — the zero-config CI case. STATUS AND BODY PASS THROUGH VERBATIM: success shapes are the legacy `{ ok: true, ... }` envelopes the SDK reporter parses, not the v1 resource envelope. # Get one eval case Source: https://docs.mcpjam.com/api-reference/eval-runs/get-one-eval-case /reference/openapi.json get /projects/{projectId}/eval-suites/{suiteId}/cases/{caseId} A case that belongs to this suite and project. A case in another suite is a 404, not a 403. # Get one eval suite's configuration Source: https://docs.mcpjam.com/api-reference/eval-runs/get-one-eval-suites-configuration /reference/openapi.json get /projects/{projectId}/eval-suites/{suiteId} The suite's full settings — attached environments, hosts, execution config, judge, schedule. The list route returns run rollups instead; this is the shape the edit routes read and write. # Get one run's stage analytics Source: https://docs.mcpjam.com/api-reference/eval-runs/get-one-runs-stage-analytics /reference/openapi.json get /projects/{projectId}/eval-runs/{runId}/stage-analytics ONE run's materialized stage-analytics document: where its trials fell out of the six-stage user-value chain (connection, discovery, selection, tool call, response, user value), overall and sliced marginally by intent, model and host. The same document the suite listing returns, addressed by run. Use this rather than paging `/eval-suites/{suiteId}/stage-analytics` to reach a known run: that listing is newest-first, so walking it costs work proportional to how long ago the run finished and cannot answer once the run falls outside the pages read. Counts are returned; RATES are not. A zero denominator is `notMeasured`, never `0`, and every excluded observation is counted under a named class so a denominator can never shrink silently. `404` covers BOTH "this run has no document" and "this run is not visible to you", and the two are deliberately not distinguished — separating them would confirm that a run exists in a project you cannot see. NOT backfilled either: a run that completed before this shipped has no document, and that absence means UNMEASURED, never a funnel of zeros. # Get one run's suite quality-gate report Source: https://docs.mcpjam.com/api-reference/eval-runs/get-one-runs-suite-quality-gate-report /reference/openapi.json get /projects/{projectId}/eval-runs/{runId}/gate ONE run's stored suite quality-gate report (`SuiteGateReportV1`): passed, failed, non_gateable, or not_configured. `not_configured` is a 200 report — the suite has no active conditions — never a missing route. A 404 means the run is not visible. A deployment that predates the evaluator answers 501 FEATURE_NOT_SUPPORTED; that is a fact about the deployment, not proof the suite has no policy. A run waiver never covers this report. # Get run status Source: https://docs.mcpjam.com/api-reference/eval-runs/get-run-status /reference/openapi.json get /projects/{projectId}/eval-runs/{runId} Run status, result, and summary. Poll until `status` is terminal (`completed`, `failed`, `cancelled` or `timed_out`). `grading` is not terminal — every trial has finished and the run is held for its gating judge, with `result` still `pending` — so keep polling. # Get the pre-run disclosure for a suite launch plan Source: https://docs.mcpjam.com/api-reference/eval-runs/get-the-pre-run-disclosure-for-a-suite-launch-plan /reference/openapi.json get /projects/{projectId}/eval-suites/{suiteId}/run-disclosure What happens to a run's content BEFORE you launch it: which models it calls and where they route, which LLM analyzers/judges can fire and where their evidence goes, capture/retention/region facts, and the subprocessors engaged. Read-only — never launches or gates a run. Keyed by the SAME destination-affecting subset a launch selects (`caseIds`/`environmentId`/`environmentIds`); pass the selectors you would pass to a run so what this discloses is what that run would do. `run_eval_suite` already fetches this itself and returns it on the receipt's `disclosure` field — call this route directly when you need the disclosure BEFORE deciding to launch. A deployment that predates this contract answers 422 `FEATURE_NOT_SUPPORTED` with `details.reason: "contract_unavailable"`, never a partial payload — this is a GUARANTEE only when the deployment's missing-function error reaches this route unredacted. Production Convex can redact that same failure to a generic "Server Error" indistinguishable from a genuine handler crash on a suite the caller CAN see; this route has no independent way to tell the two apart in that one case, so it answers 502 instead of guessing 422 — a caller cannot rely on this code alone to detect an old production deployment, and a 502 does not imply the contract is available either. `projectId` is positional only, for REST consistency with sibling routes — this endpoint authorizes per-suite, not per-project, so any `projectId` returns the same disclosure for a `suiteId` you can reach. # Ingest a completed eval run in one call Source: https://docs.mcpjam.com/api-reference/eval-runs/ingest-a-completed-eval-run-in-one-call /reference/openapi.json post /projects/{projectId}/eval-ingest/report The whole-run variant: submit a finished run and its iterations in a single request. This is what the SDK reporter uses by default. How eval runs executed OUTSIDE the platform (local dev, CI) reach the Evals dashboard. Authenticate like any other `/api/v1` route (typically an `sk_` key); the gateway swaps in a delegated org-scoped token so the backend's fail-closed org scoping applies. The `{projectId}` segment declares where results land and always wins over any `projectId` in the body. The literal `default` resolves to the key org's Default project — the zero-config CI case. STATUS AND BODY PASS THROUGH VERBATIM: success shapes are the legacy `{ ok: true, ... }` envelopes the SDK reporter parses, not the v1 resource envelope. # Launch one run per target as a group (async) Source: https://docs.mcpjam.com/api-reference/eval-runs/launch-one-run-per-target-as-a-group-async /reference/openapi.json post /projects/{projectId}/eval-run-groups Launches ONE run per target — attached project environments, or attached named hosts — under a single **server-minted** `runGroupId`, then detaches execution and responds `202` with a per-target receipt. This is the only endpoint with grouped-launch semantics. `POST /eval-runs` also accepts a `runGroupId`, but purely as a display label: it groups sibling rows in the UI and gives N separate launches no group treatment. Here the server bounds the fan-out (max 10 targets), validates EVERY target before launching any of them (an unattached target, a heterogeneous target list, or a target whose host selects an unavailable harness returns `400` with zero runs started), and holds exactly ONE organization concurrency slot for the whole group — released only when the last sibling finishes. Targets fan out along ONE axis: all `environmentId` or all `namedHostId`, never a mix. Duplicates are deduplicated by id, preserving order. A per-target failure does NOT abort its siblings, so read `outcome` rather than treating the `202` as "everything started": `started` means every target launched, `partial` means some did, `failed` means none did. Send `Idempotency-Key` (or `idempotencyKey`) to make the launch replayable: the group id is DERIVED from the key, and each target carries a run key derived from that, so a retry after a crash mid-launch returns the original run ids instead of double-launching the targets that already started. `x-mcpjam-launcher` and `x-mcpjam-ci` are read once and applied to EVERY sibling: one fan-out is one launch by one process, so the targets badge identically. # List a suite's eval cases Source: https://docs.mcpjam.com/api-reference/eval-runs/list-a-suites-eval-cases /reference/openapi.json get /projects/{projectId}/eval-suites/{suiteId}/cases Every case in the suite, in the public steps-first shape. # List a suite's per-run stage analytics Source: https://docs.mcpjam.com/api-reference/eval-runs/list-a-suites-per-run-stage-analytics /reference/openapi.json get /projects/{projectId}/eval-suites/{suiteId}/stage-analytics One materialized stage-analytics document per RUN, newest completion first: where trials fell out of the six-stage user-value chain (connection, discovery, selection, tool call, response, user value), overall and sliced marginally by intent, model and host. Each item is one run's COMPLETE document and stands alone. There is deliberately no cross-run aggregate — two funnels averaged together describe no run — so compare runs by rendering them side by side, never by summing these documents. Counts are returned; RATES are not. A zero denominator is `notMeasured`, never `0`, and every excluded observation is counted under a named class so a denominator can never shrink silently. NOT backfilled: a run that completed before this shipped has no document at all, and that absence means UNMEASURED — it is never a funnel of zeros. # List a suite's runs Source: https://docs.mcpjam.com/api-reference/eval-runs/list-a-suites-runs /reference/openapi.json get /projects/{projectId}/eval-suites/{suiteId}/runs Recent runs for a suite, newest first. # List run iterations Source: https://docs.mcpjam.com/api-reference/eval-runs/list-run-iterations /reference/openapi.json get /projects/{projectId}/eval-runs/{runId}/iterations Per-iteration results: actual tool calls, structured token usage, and latency. Cursor-paginated. # Open a run for incremental ingestion Source: https://docs.mcpjam.com/api-reference/eval-runs/open-a-run-for-incremental-ingestion /reference/openapi.json post /projects/{projectId}/eval-ingest/runs/start Opens a run that iterations are appended to, for a long CI job that reports as it goes. Pair with `runs/iterations` and `runs/finalize`. How eval runs executed OUTSIDE the platform (local dev, CI) reach the Evals dashboard. Authenticate like any other `/api/v1` route (typically an `sk_` key); the gateway swaps in a delegated org-scoped token so the backend's fail-closed org scoping applies. The `{projectId}` segment declares where results land and always wins over any `projectId` in the body. The literal `default` resolves to the key org's Default project — the zero-config CI case. STATUS AND BODY PASS THROUGH VERBATIM: success shapes are the legacy `{ ok: true, ... }` envelopes the SDK reporter parses, not the v1 resource envelope. # Resolve or create a file-owned eval suite Source: https://docs.mcpjam.com/api-reference/eval-runs/resolve-or-create-a-file-owned-eval-suite /reference/openapi.json post /projects/{projectId}/eval-suites/from-file Resolve a file-owned suite by declared id within the project, or create one. Lookup is by `(projectId, declaredSuiteId)` and never by name. A UI-authored suite has no declared id and cannot be claimed. The inspector parses the suite file; this body is the declared identity, source hash, optional provenance, and hosted settings — not the raw file. `201` on first create; `200` on a later update of the same declared id. # Revoke a gate waiver Source: https://docs.mcpjam.com/api-reference/eval-runs/revoke-a-gate-waiver /reference/openapi.json delete /projects/{projectId}/eval-runs/{runId}/gate-waivers/{waiverId} End a waiver early, putting the gate and the GitHub Check Run back. Requires the manage tier. IDEMPOTENT, and `already_revoked` is a SUCCESS: it reports the ORIGINAL revocation rather than restamping it, so a retry cannot overwrite the record of who actually ended the waiver. An already-expired waiver may still be revoked — the audit trail distinguishes "this was wrong" from "this ran out". # Update an eval case Source: https://docs.mcpjam.com/api-reference/eval-runs/update-an-eval-case /reference/openapi.json patch /projects/{projectId}/eval-suites/{suiteId}/cases/{caseId} Partial update — omitted fields are left as they are. Supplying `steps` REPLACES the case's definition rather than merging into it. `matchOptions` and `checks` accept `null` to clear the case-level override and fall back to the suite's. # Update an eval suite's settings Source: https://docs.mcpjam.com/api-reference/eval-runs/update-an-eval-suites-settings /reference/openapi.json patch /projects/{projectId}/eval-suites/{suiteId} Partial update — omitted fields are left as they are. `environmentIds` is tri-state: a non-empty array sets/replaces, `null` clears (reverting the suite to legacy config), and `[]` is rejected rather than silently treated as a clear. Responds with the full updated suite. # Waive a run gate Source: https://docs.mcpjam.com/api-reference/eval-runs/waive-a-run-gate /reference/openapi.json post /projects/{projectId}/eval-runs/{runId}/gate-waivers Override a FAILING run's release gate until an expiry you name. This does NOT make the run pass: the run keeps its `result`, the waiver is a separate audited record, and every surface that honors it — the GitHub Check Run and the CLI's `eval gate` — reports the gate as waived, by whom, why, and until when. Requires the manage tier; whoever launched the run gets no exception for having launched it. `reason` is stored UNREDACTED and readable by anyone who can see the suite, for as long as the suite exists. `400` covers five distinct refusals, each with a message written for the caller: the suite belongs to no organization, the reason is blank, the reason exceeds 500 characters, the expiry is not in the future, or the expiry is more than 30 days out. `403` means the caller can see the suite but lacks the manage tier — deliberately not collapsed to `404`, which would send a legitimate member hunting for a run sitting in front of them. # Attach one environment to a suite (atomic append) Source: https://docs.mcpjam.com/api-reference/eval-suites/attach-one-environment-to-a-suite-atomic-append /reference/openapi.json post /projects/{projectId}/eval-suites/{suiteId}/environments APPEND one project environment to the suite's attachments, if it is not already there. Distinct from `PATCH /eval-suites/{suiteId}` with `environmentIds`, which REPLACES the whole list. An append built on the replace door is a read-modify-write across two round trips, and a concurrent attach landing in between is silently DETACHED. The compose-and-run path attaches on every launch, which makes that race ordinary rather than theoretical, so the append happens inside one transaction instead. Idempotent: attaching an already-attached environment reports `attached: false` and changes nothing, which is what lets a retried launch converge instead of erroring. Subject to the same cap, membership and schedule-pin rules as the replace door — growing a single-environment suite to two is exactly the edit that strands an enabled unpinned schedule. # List a suite's settings history Source: https://docs.mcpjam.com/api-reference/eval-suites/list-a-suites-settings-history /reference/openapi.json get /projects/{projectId}/eval-suites/{suiteId}/revisions One entry per committed edit to the suite's settings, newest first: who made it, which STORED fields moved, the note they left, how many runs were launched against it, and the revision group that ties one request's writes together. Rows carry no configuration snapshots. Send a `revisionNumber` back as `expectedRevisionNumber` on `PATCH /eval-suites/{suiteId}` to make that edit a compare-and-set. # Grade an eval run with LLM as Judge Source: https://docs.mcpjam.com/api-reference/evals/grade-an-eval-run-with-llm-as-judge /reference/openapi.json post /projects/{projectId}/eval-runs/{runId}/judge **Spends.** Runs the goal-completion judge over the finished run, scoring each case's final answer against its expected output. `202`: scheduled, not done. Read the grades from the run detail's `judges.goalCompletion` rather than re-requesting — a second POST only spends again. A run's grading config is pinned when the run is created, so turning the judge on for the suite does not reach an already-recorded run: `enable: true` is what grades one, and it changes nothing beyond that run. Omitting `model` and `threshold` clears any override a previous request left on the run. # List GitHub Checks repositories Source: https://docs.mcpjam.com/api-reference/evals/list-github-checks-repositories /reference/openapi.json get /organizations/{organizationId}/eval-check-repos The repositories in this organization whose pull requests run an eval suite, plus the repositories the MCPJam GitHub App can reach (the choices a connect has). `available: false` means GitHub Checks is not enabled for this organization at all — connecting a repository will not help. `connectable: null` means the App could not be asked (GitHub unavailable, or no installation), which is different from an empty list meaning it reaches nothing; the already-connected list needs no GitHub call and is returned either way. # Request insights for an eval run Source: https://docs.mcpjam.com/api-reference/evals/request-insights-for-an-eval-run /reference/openapi.json post /projects/{projectId}/eval-runs/{runId}/insights **Spends.** Runs a model over the finished run and draws on the same shared `insightsPerDay` ledger as swarm and user-testing insights. `202`: scheduled, not done. Poll the run detail's `insights` envelope rather than re-requesting — the envelope's `status` distinguishes `pending` from `not_requested`, which a second POST would not. # Run an eval suite on a repository's pull requests Source: https://docs.mcpjam.com/api-reference/evals/run-an-eval-suite-on-a-repositorys-pull-requests /reference/openapi.json post /organizations/{organizationId}/eval-check-repos Connect a repository so every pull request to it runs one eval suite and reports a GitHub check. **Affects everyone who opens a pull request on that repository**, and with `outagePolicy: fail_closed` it can block their merges. The repository is verified against the organization's GitHub App installation before it is stored. A repository that does not exist and one the App cannot see answer identically, on purpose: answering differently would make this an oracle for private repository names. Deliberately narrow: retargeting a repository at a different suite, pausing it, and disconnecting are repo-level decisions and live in the app's Settings → Integrations, where every connected repository is visible at once. # Call a tool Source: https://docs.mcpjam.com/api-reference/execution/call-a-tool /reference/openapi.json post /projects/{projectId}/servers/{serverId}/tools/call Executes a tool on the server and returns the MCP `CallToolResult` plus an additive `durationMs` (wall-clock time of `executeTool`). Tool-level failures (`isError: true` in the result) are **successful calls** — the server answered; only transport/auth errors use the error envelope. # Render a prompt Source: https://docs.mcpjam.com/api-reference/execution/render-a-prompt /reference/openapi.json post /projects/{projectId}/servers/{serverId}/prompts/get Renders a prompt with arguments and returns the MCP `GetPromptResult` directly (`description?`, `messages`). # Render an MCP App widget headlessly Source: https://docs.mcpjam.com/api-reference/execution/render-an-mcp-app-widget-headlessly /reference/openapi.json post /projects/{projectId}/servers/{serverId}/widgets/render Call an MCP App tool and mount its `ui://` widget in real headless Chromium running the production host bridge, then report whether it rendered, what it logged, what it was blocked from fetching, and the widget as an accessibility tree with addressable elements. **Executes the tool**, so it has whatever side effects that tool has — the render is what happens afterwards. Stateless: connect, call, render, read, dispose, all inside one request. Interactive widget sessions are a local-Inspector capability (`/api/mcp/widget-session`) because they hold a live browser in process, which a no-affinity hosted plane cannot serve. Each render launches a browser, so the endpoint carries a per-replica concurrency cap and answers 429 when it is full. # Export a server snapshot Source: https://docs.mcpjam.com/api-reference/export/export-a-server-snapshot /reference/openapi.json post /projects/{projectId}/servers/{serverId}/export 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. # List a harness's native built-in tools Source: https://docs.mcpjam.com/api-reference/harness/list-a-harnesss-native-built-in-tools /reference/openapi.json get /harness/{harnessId}/builtin-tools 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. # Import OAuth tokens Source: https://docs.mcpjam.com/api-reference/oauth/import-oauth-tokens /reference/openapi.json post /projects/{projectId}/servers/{serverId}/oauth/import-tokens 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. This closes the loop after an `OAUTH_REQUIRED` error. # Get a plugin version Source: https://docs.mcpjam.com/api-reference/plugins/get-a-plugin-version /reference/openapi.json get /plugin-versions/{pluginVersionId} One imported plugin version with its component projections: declared MCP servers (placement, auth timing, materialized server id) and declared skills (namespaced model refs). Addressed by the version id alone — access is membership of the version's own project, and historical versions of uninstalled plugins stay readable because eval snapshots and stale environment pins reference them. # List a project's plugins Source: https://docs.mcpjam.com/api-reference/plugins/list-a-projects-plugins /reference/openapi.json get /projects/{projectId}/plugins The live (installed, non-uninstalled) Agent Plugins in the project, disabled ones included (marked `enabled: false`). Read-only: import, activation, enable/disable and uninstall are app flows, not API operations. # Get a server skill Source: https://docs.mcpjam.com/api-reference/primitives/get-a-server-skill /reference/openapi.json post /projects/{projectId}/servers/{serverId}/skills/get Fetches one skill by URI and verifies it before returning a byte: the SKILL.md digest against the manifest, the fetched frontmatter against what the listing advertised, and the name against the URI. Reaches skills a partial listing never mentioned. When a check fails the response carries a `refusal` naming it, not an error. # List prompts Source: https://docs.mcpjam.com/api-reference/primitives/list-prompts /reference/openapi.json post /projects/{projectId}/servers/{serverId}/prompts Returns the server's prompts as a collection page. # List resources Source: https://docs.mcpjam.com/api-reference/primitives/list-resources /reference/openapi.json post /projects/{projectId}/servers/{serverId}/resources Returns the server's resources as a collection page. # List server skills Source: https://docs.mcpjam.com/api-reference/primitives/list-server-skills /reference/openapi.json post /projects/{projectId}/servers/{serverId}/skills Returns the Agent Skills a connected server serves over the MCP skills extension (SEP-2640), including skills it advertises that MCPJam declines to load. Not paginated: the catalog is drained server-side, because duplicate detection spans the whole listing. # List tools Source: https://docs.mcpjam.com/api-reference/primitives/list-tools /reference/openapi.json post /projects/{projectId}/servers/{serverId}/tools Returns the server's tools as a collection page. Each item is the MCP tool definition (`name`, `description`, `inputSchema`, ...). # Read a resource Source: https://docs.mcpjam.com/api-reference/primitives/read-a-resource /reference/openapi.json post /projects/{projectId}/servers/{serverId}/resources/read Reads a single resource by URI and returns its contents. # Read a server skill's supporting file Source: https://docs.mcpjam.com/api-reference/primitives/read-a-server-skills-supporting-file /reference/openapi.json post /projects/{projectId}/servers/{serverId}/skills/read-file Reads one supporting file belonging to a skill, checking its byte length and digest against the skill's own manifest. The manifest is the read allowlist and is re-fetched here rather than accepted from the caller, so a request cannot authorize its own read. # Create a project Source: https://docs.mcpjam.com/api-reference/projects/create-a-project /reference/openapi.json post /projects Creates a project in the caller's organization and responds `201` with its detail. Requires organization membership and counts against the plan's `maxProjects` limit. Guest callers are denied. # Delete a project and everything scoped to it Source: https://docs.mcpjam.com/api-reference/projects/delete-a-project-and-everything-scoped-to-it /reference/openapi.json delete /projects/{projectId} IRREVERSIBLE, and it cascades: project access grants and invites, the mapped workspace and its memberships, every saved server in the project (each soft-deleted asynchronously, which also rotates the host configs that referenced it), and hosted tasks. Requires project admin. The request body must be empty. # Update project metadata Source: https://docs.mcpjam.com/api-reference/projects/update-project-metadata /reference/openapi.json patch /projects/{projectId} Updates name, description, icon or visibility. Requires project admin; a non-member gets `404` rather than `403`, so the response never confirms that a project exists. The project's server map is not updatable here — see the per-server routes. # What the caller may do here Source: https://docs.mcpjam.com/api-reference/projects/what-the-caller-may-do-here /reference/openapi.json get /projects/{projectId}/capabilities Role, gate state, plan limits and a set of derived booleans — so an agent on a static surface can check before it acts instead of attempting a write and reading the failure. Descriptive, never authoritative: see the schema. # Get one directory server Source: https://docs.mcpjam.com/api-reference/registry/get-one-directory-server /reference/openapi.json get /registry/directory-servers/{idOrName} Resolve by Convex `catalogServerId` (long unhyphenated id) or by `serverName`. Pass `source` to disambiguate a name that exists in more than one directory. The response includes `latestContentHash` — pass it as `expectedContentHash` on install to freeze the row. # Install a curated or org registry card Source: https://docs.mcpjam.com/api-reference/registry/install-a-curated-or-org-registry-card /reference/openapi.json post /projects/{projectId}/registry/installs Same install-not-connect contract as the directory install. Pass `expectedUpdatedAt` from `listRegistryServers` to freeze the card. # Install a directory server into a project Source: https://docs.mcpjam.com/api-reference/registry/install-a-directory-server-into-a-project /reference/openapi.json post /projects/{projectId}/registry/directory-installs Writes a `servers` row and catalog provenance. SUCCESS MEANS INSTALLED, NOT CONNECTED — follow with `get_project_server_connection_status` and, for OAuth, the browser connect-link handoff. Pass `expectedContentHash` from `GET /registry/directory-servers/{id}` to reject a row that moved (TOCTOU). There is no catalog-uninstall route: delete the project server. # List curated and org registry cards Source: https://docs.mcpjam.com/api-reference/registry/list-curated-and-org-registry-cards /reference/openapi.json get /projects/{projectId}/registry/servers Merges global approved cards with the project's organization cards. Guest-denied. `scope` defaults to all. # List directory sources Source: https://docs.mcpjam.com/api-reference/registry/list-directory-sources /reference/openapi.json get /registry/directory-sources Discovery for the free-string `source` filter. Sources are data, not an enum — do not hardcode `claude`/`chatgpt` client-side. # List registry installs in a project Source: https://docs.mcpjam.com/api-reference/registry/list-registry-installs-in-a-project /reference/openapi.json get /projects/{projectId}/registry/connections Merged catalog + card provenance. Guest-denied. Rows whose `servers` row is deleted are filtered out. # Search the scraped MCP directories Source: https://docs.mcpjam.com/api-reference/registry/search-the-scraped-mcp-directories /reference/openapi.json get /registry/directory-servers Cross-source search over Claude, ChatGPT, and any future directory source. Omit `source` (or pass `all`) to search every source. The `cursor` is filter-bound. Prefer a matching curated card from `listRegistryServers` (global scope) when one exists — those carry vetted OAuth config. Bearer always required (guest tokens allowed). Not in PUBLIC_OPERATIONS. # Uninstall a registry card Source: https://docs.mcpjam.com/api-reference/registry/uninstall-a-registry-card /reference/openapi.json delete /projects/{projectId}/registry/installs/{registryServerId} Card-only. Directory uninstall is `DELETE /projects/{projectId}/servers/{serverId}` — provenance is cleaned with the server. # Boot your computer from a sandbox image Source: https://docs.mcpjam.com/api-reference/sandbox-images/boot-your-computer-from-a-sandbox-image /reference/openapi.json post /projects/{projectId}/images/{imageId}/use 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). # Build a sandbox image Source: https://docs.mcpjam.com/api-reference/sandbox-images/build-a-sandbox-image /reference/openapi.json post /projects/{projectId}/images/{imageId}/build 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). # Create a sandbox image Source: https://docs.mcpjam.com/api-reference/sandbox-images/create-a-sandbox-image /reference/openapi.json post /projects/{projectId}/images 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). # Delete a sandbox image Source: https://docs.mcpjam.com/api-reference/sandbox-images/delete-a-sandbox-image /reference/openapi.json delete /projects/{projectId}/images/{imageId} 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). # Get a sandbox image Source: https://docs.mcpjam.com/api-reference/sandbox-images/get-a-sandbox-image /reference/openapi.json get /projects/{projectId}/images/{imageId} One sandbox image's blueprint, sharing, and latest build. An image that exists but belongs to a different project reads as `404 NOT_FOUND`. # List a project's sandbox images Source: https://docs.mcpjam.com/api-reference/sandbox-images/list-a-projects-sandbox-images /reference/openapi.json get /projects/{projectId}/images The custom Computer images (digest-pinned Dockerfiles built into immutable images) saved in the project — your own personal drafts plus the project-shared ones. # List a sandbox image's builds Source: https://docs.mcpjam.com/api-reference/sandbox-images/list-a-sandbox-images-builds /reference/openapi.json get /projects/{projectId}/images/{imageId}/builds A sandbox image's builds, newest first, each with its status and capped log preview. # Reset your computer to its image Source: https://docs.mcpjam.com/api-reference/sandbox-images/reset-your-computer-to-its-image /reference/openapi.json post /projects/{projectId}/computer/reset Reset the caller's computer back to its current image, wiping mutable state. Bodyless. Guest callers are denied (a write). # Share a sandbox image with the project Source: https://docs.mcpjam.com/api-reference/sandbox-images/share-a-sandbox-image-with-the-project /reference/openapi.json post /projects/{projectId}/images/{imageId}/promote Promote a personal-draft sandbox image to a project-shared one. Requires project admin. Bodyless. Guest callers are denied (a write). # Update a sandbox image Source: https://docs.mcpjam.com/api-reference/sandbox-images/update-a-sandbox-image /reference/openapi.json patch /projects/{projectId}/images/{imageId} 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). # Validate a blueprint Source: https://docs.mcpjam.com/api-reference/sandbox-images/validate-a-blueprint /reference/openapi.json post /projects/{projectId}/images/validate 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. # Get a scenario Source: https://docs.mcpjam.com/api-reference/scenarios/get-a-scenario /reference/openapi.json get /projects/{projectId}/scenarios/{scenarioId} One scenario's full read-only settings: model, system prompt, tool-approval policy, attached servers, and share link. A scenario that exists but belongs to a different project reads as `404 NOT_FOUND`. # List a project's scenarios Source: https://docs.mcpjam.com/api-reference/scenarios/list-a-projects-scenarios /reference/openapi.json get /projects/{projectId}/scenarios The scenarios published from this project — name, access mode, attached servers, and share link. Read-only. # Create a secret Source: https://docs.mcpjam.com/api-reference/secrets/create-a-secret /reference/openapi.json post /projects/{projectId}/secrets Store a credential so environments can grant it to runs. THE VALUE TRAVELS IN THE REQUEST BODY and becomes visible to whatever makes the call. The response is metadata only. Creating a project-shared secret requires project admin; a personal one does not. # Delete a secret Source: https://docs.mcpjam.com/api-reference/secrets/delete-a-secret /reference/openapi.json delete /projects/{projectId}/secrets/{secretId} Delete a stored credential. HARD — the row and the encrypted value both go, and MCPJam stops delivering it. Deliberately NOT blocked when an environment still selects it: refusing would leave a leaked credential undeletable until someone edited every environment naming it, and deletion must never wait on cleanup. Runs already in flight keep the value they were handed, and this does NOT revoke the credential at the provider that issued it. # Get a secret Source: https://docs.mcpjam.com/api-reference/secrets/get-a-secret /reference/openapi.json get /projects/{projectId}/secrets/{secretId} One secret's metadata: delivery mode, host binding, sharing, and when it was last handed to a run. Never its value. A secret from another project — and another member's personal secret — both read as 404. # List secrets Source: https://docs.mcpjam.com/api-reference/secrets/list-secrets /reference/openapi.json get /projects/{projectId}/secrets The project's credentials as METADATA ONLY — no value is returned by this or any other route. Shows the project-shared secrets plus the caller's own personal ones; another member's personal secret does not appear at all, not even its name. # Rotate or re-bind a secret Source: https://docs.mcpjam.com/api-reference/secrets/rotate-or-re-bind-a-secret /reference/openapi.json patch /projects/{projectId}/secrets/{secretId} Rotate the value and/or change how it is delivered. A rotation reaches NEW RUNS ONLY: a session already running holds the old value — materialized in its box's environment, or inside an egress policy that cannot be read back — and there is no safe way to replace it mid-run. # Cancel a connection request Source: https://docs.mcpjam.com/api-reference/server-connections/cancel-a-connection-request /reference/openapi.json post /server-connections/{requestId}/cancel Stops a request and invalidates its handoff link. Cancelling an already-terminal request is a no-op. # Connect an MCP server Source: https://docs.mcpjam.com/api-reference/server-connections/connect-an-mcp-server /reference/openapi.json post /server-connections Starts a connection request for an MCP server URL. Discovery, browser consent, and validation happen asynchronously; poll the returned `connectionRequestId` until the status is terminal. When the next step belongs to a person, the response carries a `handoffUrl` to open. # Get a connection request Source: https://docs.mcpjam.com/api-reference/server-connections/get-a-connection-request /reference/openapi.json get /server-connections/{requestId} Current state of one connection request. Metered on its own poll budget rather than the shared per-caller one, so polling on a short interval is expected and will not spend the budget your other calls need. A 429 here means the poll interval itself is too fast; honour Retry-After. # Retry validation Source: https://docs.mcpjam.com/api-reference/server-connections/retry-validation /reference/openapi.json post /server-connections/{requestId}/retry-validation Re-arms a stalled validation immediately instead of waiting out its backoff. Does not revive a terminal request — after `failed`, `expired`, or `cancelled`, start a new one. # Check OAuth requirement Source: https://docs.mcpjam.com/api-reference/server-diagnostics/check-oauth-requirement /reference/openapi.json post /projects/{projectId}/servers/{serverId}/check-oauth Lightweight, no MCP connection: reports whether the server is configured to require an OAuth grant. # Run the doctor Source: https://docs.mcpjam.com/api-reference/server-diagnostics/run-the-doctor /reference/openapi.json post /projects/{projectId}/servers/{serverId}/doctor 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." # Validate a server Source: https://docs.mcpjam.com/api-reference/server-diagnostics/validate-a-server /reference/openapi.json post /projects/{projectId}/servers/{serverId}/validate 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. # Delete a saved server Source: https://docs.mcpjam.com/api-reference/servers/delete-a-saved-server /reference/openapi.json delete /projects/{projectId}/servers/{serverId} Soft-deletes the server and schedules its cascade: MCP Apps views, hosted OAuth credentials, encrypted secret objects, and a rotation of every host config that referenced it. The cascade is ASYNCHRONOUS, so a read immediately after this call may still observe the row. The request body must be empty. # Get one saved server Source: https://docs.mcpjam.com/api-reference/servers/get-one-saved-server /reference/openapi.json get /projects/{projectId}/servers/{serverId} The server's detail, projected toward the hosted shape. Secrets are never included — `hasClientSecret` reports presence only. A server id from another project reads as `404`. # Save a server into a project Source: https://docs.mcpjam.com/api-reference/servers/save-a-server-into-a-project /reference/openapi.json post /projects/{projectId}/servers Creates a saved MCP server and responds `201` with its detail. This is the provisioning entry point: every eval, host and environment route addresses servers that were created here. Names are unique per workspace, so a clash responds `409` rather than silently returning the existing row. Secret-bearing fields (`env`, `headers`, `clientSecret`) are encrypted at rest and never returned by any read. # Update a saved server Source: https://docs.mcpjam.com/api-reference/servers/update-a-saved-server /reference/openapi.json patch /projects/{projectId}/servers/{serverId} Sparse update: omitted fields are unchanged. Renaming re-checks per-workspace uniqueness (`409` on a clash). Editing a server shared as an OAuth connection requires project admin. `clientSecret` and `clearClientSecret` are mutually exclusive. # Get a skill Source: https://docs.mcpjam.com/api-reference/skills/get-a-skill /reference/openapi.json get /projects/{projectId}/skills/{skillId} One skill, including its SKILL.md body. The body is mutable and an edit overwrites the previous one in place, so `aggregateHash` is the only handle on which content this read returned. # List a project's skills Source: https://docs.mcpjam.com/api-reference/skills/list-a-projects-skills /reference/openapi.json get /projects/{projectId}/skills The Cloud Skills visible to the caller in this project: the project-shared ones plus the caller's own drafts. Each row reports `pinnability`, which is what decides whether its id is usable in an environment's `skillSelection`. # Cancel a pending wave-insights generation Source: https://docs.mcpjam.com/api-reference/swarm-insights/cancel-a-pending-wave-insights-generation /reference/openapi.json delete /projects/{projectId}/waves/{waveId}/insights Stops a generation that is still `pending`. A wave with no generation in progress answers 409 — there is nothing to cancel, and reporting success would tell a caller it stopped something it did not. # Dismiss a finding Source: https://docs.mcpjam.com/api-reference/swarm-insights/dismiss-a-finding /reference/openapi.json post /projects/{projectId}/journey-findings/{findingId}/dismiss Hides a finding from your view. ORTHOGONAL to `status`: dismissing does not claim the problem stopped happening, and a dismissed finding that fires again stays `recurring`. Addressed entirely by the path `findingId` — there is no body. # Get a run's rubric scorecard Source: https://docs.mcpjam.com/api-reference/swarm-insights/get-a-runs-rubric-scorecard /reference/openapi.json get /projects/{projectId}/journey-runs/{runId}/scorecard The deterministic rubric result for one run. No model, no spend. Divide by `sessionsGraded`, never `sessionsTotal`, and keep `failedGradingCount` separate from `failCount` — a crashed judge is not a regression. # Get wave insights Source: https://docs.mcpjam.com/api-reference/swarm-insights/get-wave-insights /reference/openapi.json get /projects/{projectId}/waves/{waveId}/insights Poll this after requesting insights. **404 means nobody asked** — it is deliberately not an empty `status: "none"` body, so a caller in a polling loop cannot mistake "never requested" for "asked and still working". # List swarm findings Source: https://docs.mcpjam.com/api-reference/swarm-insights/list-swarm-findings /reference/openapi.json get /projects/{projectId}/journey-findings Criteria that keep failing, tracked across waves. One finding is one problem OVER TIME — `occurrenceCount` and `status` carry the history, so do not treat repeat appearances as separate problems. # Project swarm roll-up Source: https://docs.mcpjam.com/api-reference/swarm-insights/project-swarm-roll-up /reference/openapi.json get /projects/{projectId}/journeys-overview Recent runs with their goal-completion rates and repeat-failure findings, plus a project-wide trend. Deterministic and free — start here. # Request wave insights Source: https://docs.mcpjam.com/api-reference/swarm-insights/request-wave-insights /reference/openapi.json post /projects/{projectId}/waves/{waveId}/insights Answers **202**: generation is scheduled, not done. Poll the GET above rather than re-requesting. **SPENDS.** This runs models on your organization's account and draws on the `insightsPerDay` ledger, which is SHARED with eval-run and user-testing insights — burning it here takes it from there too. Three refusals, and they mean different things: - `429` with `code: "RATE_LIMITED"` and a short `Retry-After` — the per-minute burst brake. Wait seconds. - `429` naming `insightsPerDay` in `details` — the daily ledger. `Retry-After` counts to UTC midnight. - `403` — the feature is not available to your organization. Waiting will never help; collapsing this into the 429s would send someone who hit today's cap shopping for a plan they already have. # Undismiss a finding Source: https://docs.mcpjam.com/api-reference/swarm-insights/undismiss-a-finding /reference/openapi.json post /projects/{projectId}/journey-findings/{findingId}/undismiss Reverses a dismissal. Bodyless, like its twin. # Cancel a journey run Source: https://docs.mcpjam.com/api-reference/swarm-runs/cancel-a-journey-run /reference/openapi.json post /projects/{projectId}/journey-runs/{runId}/cancel Stops a run and settles its in-flight attempts. IDEMPOTENT: cancelling an already-cancelled run answers 200 with `alreadyCanceled: true` and `finalized: 0`, not a 409. Addressed entirely by the path `runId` — there is no body. Deliberately NOT behind the beta gate: cancelling REDUCES exposure and spend, so an organization that loses the flag can still stop a run that is under way. # Get a journey run Source: https://docs.mcpjam.com/api-reference/swarm-runs/get-a-journey-run /reference/openapi.json get /projects/{projectId}/journey-runs/{runId} The full record, including per-session `attempts` and `targetSummaries`. Read `canceled` before reporting a failure: a stopped run carries `status: "failed"`. # Launch a journey Source: https://docs.mcpjam.com/api-reference/swarm-runs/launch-a-journey /reference/openapi.json post /projects/{projectId}/journeys/{journeyId}/runs Answers **202**: the run row exists and its fan-out has started. It is not finished — poll `GET /projects/{projectId}/journey-runs/{runId}` until `status` leaves `running`. **SPENDS.** A launch fans out into `targets × sessionsPerTarget` chat sessions, every one of which runs a model on your organization's account. Two different 429s can come back and they mean different waits: the per-minute burst brake (retry in seconds) and your plan's daily launch cap (resets at UTC midnight). Both normally carry `Retry-After` — honor it when it is there, and back off on your own when it is not, rather than blocking on a header that may not come. A retried `Idempotency-Key` consumes NO quota. # List a journey's runs Source: https://docs.mcpjam.com/api-reference/swarm-runs/list-a-journeys-runs /reference/openapi.json get /projects/{projectId}/journeys/{journeyId}/runs Newest first. Compact records — `attempts` is on the single-run read. # List a run's sessions Source: https://docs.mcpjam.com/api-reference/swarm-runs/list-a-runs-sessions /reference/openapi.json get /projects/{projectId}/journey-runs/{runId}/sessions One entry per chat session the run produced. Summaries, not transcripts — read a transcript through the chat-session routes using `id`. # Archive a journey Source: https://docs.mcpjam.com/api-reference/swarms/archive-a-journey /reference/openapi.json delete /projects/{projectId}/journeys/{journeyId} ARCHIVES rather than deletes, and the response says so. Existing runs keep pointing at the definition they executed, so their history stays readable. # Archive a swarm Source: https://docs.mcpjam.com/api-reference/swarms/archive-a-swarm /reference/openapi.json delete /projects/{projectId}/swarms/{swarmId} ARCHIVES the container. The journeys authored under it are not touched — they keep their own definitions and stay launchable. # Create a journey Source: https://docs.mcpjam.com/api-reference/swarms/create-a-journey /reference/openapi.json post /projects/{projectId}/journeys Creates one journey: a persona pursuing a `goal` against one or more environments. To draft several with a model first, use `POST /projects/{projectId}/journeys/generate` — that route persists nothing. `personaId` and `swarmId` must belong to THIS project. Naming one from another project is a `404`, not a silent cross-project reference. Creating a journey does not run it. Launch it with `POST /projects/{projectId}/journeys/{journeyId}/runs`. # Create a persona Source: https://docs.mcpjam.com/api-reference/swarms/create-a-persona /reference/openapi.json post /projects/{projectId}/personas Creates one persona. To draft several with a model first, use `POST /projects/{projectId}/personas/generate` — that route persists nothing. # Create a swarm Source: https://docs.mcpjam.com/api-reference/swarms/create-a-swarm /reference/openapi.json post /projects/{projectId}/swarms A swarm is an authoring container holding DEFAULTS for the journeys made under it. Creating one starts nothing. # Delete a persona Source: https://docs.mcpjam.com/api-reference/swarms/delete-a-persona /reference/openapi.json delete /projects/{projectId}/personas/{personaId} Refused with 409 while journeys still reference this persona — deleting it would strand them. # Draft journeys for a persona Source: https://docs.mcpjam.com/api-reference/swarms/draft-journeys-for-a-persona /reference/openapi.json post /projects/{projectId}/journeys/generate Returns DRAFTS and persists nothing — see `POST /projects/{projectId}/personas/generate` for why, and for what this spends. # Draft personas with a model Source: https://docs.mcpjam.com/api-reference/swarms/draft-personas-with-a-model /reference/openapi.json post /projects/{projectId}/personas/generate Returns DRAFTS and persists nothing. That separation is deliberate: generation is the step most likely to produce something nobody wanted, and a route that both invented and saved would make "let me see what you'd suggest" indistinguishable from "fill my project with these". It is also why there is no idempotency key — a call with no effect has nothing to de-duplicate, and offering one would imply the drafts are stable across retries when they are not. **SPENDS.** This runs models on your organization's account and is metered two ways: a per-minute burst brake (429, retry in seconds) and your plan's daily budget (429, resets at UTC midnight). Both normally carry `Retry-After` — honor it when it is there, and back off on your own when it is not. # Get a journey Source: https://docs.mcpjam.com/api-reference/swarms/get-a-journey /reference/openapi.json get /projects/{projectId}/journeys/{journeyId} # Get a persona Source: https://docs.mcpjam.com/api-reference/swarms/get-a-persona /reference/openapi.json get /projects/{projectId}/personas/{personaId} A persona that lives in a DIFFERENT project answers 404, never 403 — the route is not an existence oracle for a project you cannot see. # Get a swarm Source: https://docs.mcpjam.com/api-reference/swarms/get-a-swarm /reference/openapi.json get /projects/{projectId}/swarms/{swarmId} # List journeys Source: https://docs.mcpjam.com/api-reference/swarms/list-journeys /reference/openapi.json get /projects/{projectId}/journeys The project's journeys, newest first. Archived journeys are excluded. # List personas Source: https://docs.mcpjam.com/api-reference/swarms/list-personas /reference/openapi.json get /projects/{projectId}/personas The project's personas, newest first. Archived personas are excluded. # List swarms Source: https://docs.mcpjam.com/api-reference/swarms/list-swarms /reference/openapi.json get /projects/{projectId}/swarms # Update a journey Source: https://docs.mcpjam.com/api-reference/swarms/update-a-journey /reference/openapi.json patch /projects/{projectId}/journeys/{journeyId} Editing a journey never disturbs a run already under way: a run pins its own immutable snapshot of the definition at launch. # Update a persona Source: https://docs.mcpjam.com/api-reference/swarms/update-a-persona /reference/openapi.json patch /projects/{projectId}/personas/{personaId} Partial update. Journeys reference the persona by id, so renaming one does not disturb anything pointing at it. # Update a swarm Source: https://docs.mcpjam.com/api-reference/swarms/update-a-swarm /reference/openapi.json patch /projects/{projectId}/swarms/{swarmId} Changes the container's DEFAULTS. Journeys already authored under it keep the values they were created with — this is not a cascade. # Create (or revive) a relay tunnel for a named project server Source: https://docs.mcpjam.com/api-reference/tunnels/create-or-revive-a-relay-tunnel-for-a-named-project-server /reference/openapi.json post /projects/{projectId}/tunnels 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 scenarios 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. The 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 cloud tunnel` does. When the host disconnects, the server record stays and calls to the public URL fail fast at the edge. # Revoke a tunnel's live grant Source: https://docs.mcpjam.com/api-reference/tunnels/revoke-a-tunnels-live-grant /reference/openapi.json post /projects/{projectId}/tunnels/{serverId}/close 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. # Cancel a window's generation Source: https://docs.mcpjam.com/api-reference/user-testing/cancel-a-windows-generation /reference/openapi.json delete /projects/{projectId}/user-testing/scenarios/{scenarioId}/insights Releases a generation that is stuck `pending`. Idempotent against an already-finished one. # Current window signals Source: https://docs.mcpjam.com/api-reference/user-testing/current-window-signals /reference/openapi.json get /projects/{projectId}/user-testing/scenarios/{scenarioId}/signals What the miner extracted from the current window — and where you get the window id the insights read takes. `404` when no window has been analyzed yet. # Dismiss a finding Source: https://docs.mcpjam.com/api-reference/user-testing/dismiss-a-finding /reference/openapi.json post /projects/{projectId}/user-testing/scenarios/{scenarioId}/findings/{findingId}/dismiss Keyed on the finding's stable remediation id, so the same problem stays dismissed when insights regenerate. Bodyless: the path names the finding. # Get a scenario Source: https://docs.mcpjam.com/api-reference/user-testing/get-a-scenario /reference/openapi.json get /projects/{projectId}/user-testing/scenarios/{scenarioId} The scenario, plus the insights envelope over its latest analyzed window when the caller may have it. Project members only. This route is deliberately outside the guest allowlist, and the envelope additionally needs workspace membership — share-link visitors can never read other visitors' evidence. # Get a session transcript Source: https://docs.mcpjam.com/api-reference/user-testing/get-a-session-transcript /reference/openapi.json get /projects/{projectId}/user-testing/scenarios/{scenarioId}/sessions/{sessionId} The projected transcript, paged. Tool payloads and blobs are dropped, and the stored blob URL is never returned. Check `transcriptUnavailable` before trusting `messageCount`: an unreadable transcript reports `null`, never `0`. # Invite a member Source: https://docs.mcpjam.com/api-reference/user-testing/invite-a-member /reference/openapi.json put /projects/{projectId}/user-testing/scenarios/{scenarioId}/members Upsert by email, so re-inviting somebody is not an error. Only meaningful while `mode` is `invited_only`. # List a scenario's sessions Source: https://docs.mcpjam.com/api-reference/user-testing/list-a-scenarios-sessions /reference/openapi.json get /projects/{projectId}/user-testing/scenarios/{scenarioId}/sessions What visitors did, as summaries. Project members only — this route is deliberately absent from the guest allowlist, so a share-link visitor can never read other visitors' sessions. # List scenario findings Source: https://docs.mcpjam.com/api-reference/user-testing/list-scenario-findings /reference/openapi.json get /projects/{projectId}/user-testing/scenarios/{scenarioId}/findings Every finding raised over this scenario's windows, dismissed ones included — dismissal is a state on the finding, not a deletion. # Point a scenario at a different environment Source: https://docs.mcpjam.com/api-reference/user-testing/point-a-scenario-at-a-different-environment /reference/openapi.json post /projects/{projectId}/user-testing/scenarios/{scenarioId}/rebind Keeps the scenario's LINK, its members and its session history while changing what visitors actually reach. The alternative — unpublish and republish — mints a new link, which means re-sharing it with everyone. The new environment must belong to THIS project; one that does not answers 404 rather than confirming it exists elsewhere. # Publish an environment for user testing Source: https://docs.mcpjam.com/api-reference/user-testing/publish-an-environment-for-user-testing /reference/openapi.json put /projects/{projectId}/environments/{environmentId}/scenario PUT, not POST, because publishing is IDEMPOTENT: one scenario per environment, and publishing an already-published environment returns the existing one rather than minting a second. `created` says which happened. Requires project ADMIN. # Read a window's insights Source: https://docs.mcpjam.com/api-reference/user-testing/read-a-windows-insights /reference/openapi.json get /projects/{projectId}/user-testing/scenarios/{scenarioId}/windows/{windowId}/insights Poll this after requesting a generation. `404` means NOBODY ASKED — deliberately distinct from a `pending` status, so a caller looping on this cannot mistake "never requested" for "requested and still working". # Remove a member Source: https://docs.mcpjam.com/api-reference/user-testing/remove-a-member /reference/openapi.json delete /projects/{projectId}/user-testing/scenarios/{scenarioId}/members/{memberIdOrEmail} Removal NARROWS access, so it bumps `accessVersion` and cuts off any session that member already had — it takes effect at once rather than at expiry. # Request insights for the current window Source: https://docs.mcpjam.com/api-reference/user-testing/request-insights-for-the-current-window /reference/openapi.json post /projects/{projectId}/user-testing/scenarios/{scenarioId}/insights **Spends.** Runs a model over the scenario's current window and draws on the organization's `insightsPerDay` ledger, which is SHARED with eval-run and swarm wave insights — spending it here takes it from there. `202`: scheduled, not done. Poll `GET .../windows/{windowId}/insights` with the returned `windowId` rather than re-requesting. `409` when the current window has not been mined yet: the request was well formed and retrying it verbatim will not help until sessions have been processed. # Restore a dismissed finding Source: https://docs.mcpjam.com/api-reference/user-testing/restore-a-dismissed-finding /reference/openapi.json post /projects/{projectId}/user-testing/scenarios/{scenarioId}/findings/{findingId}/undismiss Undoes a dismissal. Bodyless: the path names the finding. # Rotate the share link Source: https://docs.mcpjam.com/api-reference/user-testing/rotate-the-share-link /reference/openapi.json post /projects/{projectId}/user-testing/scenarios/{scenarioId}/rotate-link Mints a new share link and stops the old URL from granting access. Everyone you shared it with needs the new one. **Rotation does NOT evict anyone who already redeemed the old link.** Their grant is a stored row, not a property of the URL, and it survives rotation — `accessVersion` is deliberately not bumped here. So rotation alone does not undo a leak: rotate to close the door, then `DELETE .../members/{memberIdOrEmail}` for anyone who already walked through it. Bodyless: the scenario is the whole request, and the new secret is minted server-side by definition. # Session metrics Source: https://docs.mcpjam.com/api-reference/user-testing/session-metrics /reference/openapi.json get /projects/{projectId}/user-testing/scenarios/{scenarioId}/metrics Volumes, completion and feedback rates over the scenario's sessions. `404` when the scenario has produced no sessions yet — an empty scenario has no metrics, which is different from metrics that are all zero. # Set the guest-execution caps Source: https://docs.mcpjam.com/api-reference/user-testing/set-the-guest-execution-caps /reference/openapi.json put /projects/{projectId}/user-testing/scenarios/{scenarioId}/guest-execution The spend dial for anonymous visitors. A FULL REPLACEMENT — send every field. These caps only mean something as a set, and a partial update that raised one while leaving another stale would produce a combination nobody chose. Requires project ADMIN: it is the one exposure control that genuinely does, because it authorizes strangers to spend. # Unpublish an environment Source: https://docs.mcpjam.com/api-reference/user-testing/unpublish-an-environment /reference/openapi.json delete /projects/{projectId}/environments/{environmentId}/scenario Takes the scenario down. Deliberately NOT behind the beta gate that guards publishing: an organization that loses the flag must still be able to take a live scenario down. Unpublishing something already unpublished answers 200 with `deleted: false` — a no-op, not an error. # Update a scenario Source: https://docs.mcpjam.com/api-reference/user-testing/update-a-scenario /reference/openapi.json patch /projects/{projectId}/user-testing/scenarios/{scenarioId} Change a scenario's identity or its exposure — but not both in one call. **Send `mode` on its own.** Identity and exposure are separate operations upstream, and applying them in sequence could leave the scenario live in a mode you did not ask for. Narrowing `mode` bumps `accessVersion` upstream and invalidates sessions minted under the old one. # Usage breakdown Source: https://docs.mcpjam.com/api-reference/user-testing/usage-breakdown /reference/openapi.json get /projects/{projectId}/user-testing/scenarios/{scenarioId}/usage Which models, tools and servers the scenario's sessions used. **Check `scan.truncated` before quoting a rate.** `404` when there is no usage data yet. # Changelog Source: https://docs.mcpjam.com/changelog/overview Track the latest updates, features, and improvements to MCPJam Inspector ## Overview This changelog tracks all notable changes to MCPJam Inspector. We follow [Semantic Versioning](https://semver.org/) and keep our changelog in the spirit of [Keep a Changelog](https://keepachangelog.com/). ## Latest Releases ### 2026-09-09 #### Added * **Findings tab on User Testing scenario detail** — opening a User Testing scenario now lands on a **Findings** tab instead of Insights. The tab groups sessions by how users came across — Gave up, Frustrated, Neutral, Satisfied, or Uncategorized — and lists the goal clusters each group engaged with, ordered worst-first. Expanding a goal shows the per-goal user-value chain (Connection → Discovery → Selection → Call → Response → Value) once stage data is available. Click any goal's session count to jump to Sessions with the matching transcript pre-selected. Insights and Sessions remain reachable from the tab strip. #### Changed * **User Testing scenarios analyze themselves** — opening a User Testing scenario's Findings or Insights tab when sessions exist but no analysis has run now starts the analysis automatically. The "Analyze sessions" button and the "These sessions haven't been analyzed yet" state are gone; the tab shows an analyzing banner instead and fills in once the run completes. The cluster tuning control and the freshness chip still require an explicit action. Swarm and Benchmark scopes are unchanged — Swarm already queues analysis when a run settles, and Benchmark flow analysis remains explicitly requested. ### 2026-09-02 #### Added * **GPT-5.6 models available with your own OpenAI key** — `gpt-5.6-luna`, `gpt-5.6-sol`, and `gpt-5.6-terra` are now selectable in the model picker under **Your providers → OpenAI** when you have an OpenAI API key configured. Each model has a 1,050,000-token context window. These models were already available in the free hosted catalog; this change makes them reachable for BYOK users as well. ### 2026-08-31 #### Changed * **Public score runner redesign** — the score runner landing page has been rebuilt with a new design. After you paste a public MCP server URL and click **Score this server**, you are now prompted for an email address before the scan starts. The scorecard is delivered to that address once the run completes. The email is preserved across OAuth redirects, so servers that require authorization before scanning still collect it. Legacy OAuth resume records that predate the email step are kept on the email prompt rather than bypassing it. The featured scores section on the landing page now shows real MCP endpoints (Linear, GitHub Copilot, Notion), and each row populates the URL field when clicked. URL examples default to Streamable HTTP at `/mcp`. ### 2026-06-26 #### Added * **Compatibility destination** — a new Inspector page that evaluates your connected MCP server against a catalog of AI hosts (Claude, ChatGPT, Cursor, Copilot, Codex, and others). Shows conformance gates, per-host findings split into Apps and Server lanes, and live widget rendering. Accessible from the left sidebar when a server is connected. * **OTLP/OpenInference trace export** — export eval run traces as OTLP JSON from the run detail page. Compatible with Arize Phoenix, Datadog, and any OTLP-compatible observability backend. Supports exporting a single run or the whole project, with an opt-in to include content and artifacts. * **`@mcpjam/sdk/host-compat` subpackage** — the shared host-compatibility engine is now importable directly. Use `deriveServerRequirements`, `evaluateHostCompat`, `evaluateAllHosts`, `scanWidgetSource`, and `detectHostCompatBridgeFromMeta` to build your own compatibility checks in CI or custom tooling. ### Version 1.1.0 #### Improved * **Code Quality** * Improved code consistency and maintainability ### Recent Updates #### Added * **VS Code and Notion market hosts (`@mcpjam/sdk` minor)** * **VS Code** (GitHub Copilot chat panel) is now a market host in the SDK's host-compat catalog. It renders MCP UI inline and supports all display modes (`inline`, `fullscreen`, `pip`). Capabilities mirror the VS Code client template; `updateModelContext` and `message` are not advertised. Provenance: probe. * **Notion** is now a market host. It is a tools-only/headless client (no widget rendering) that supports protocol version `2025-11-25`. Provenance: probe. * Both hosts are available via `buildMarketHostProfiles()` / `evaluateMarketHosts()` and carry logos in the inspector's host-compat UI. * **`imageSupport` dimension on all market hosts (`@mcpjam/sdk` minor)** * Every market host now carries an `imageSupport` field on its `HostCompatProfile` describing how it handles MCP tool-result images. * Three image sources are tracked per host: `toolImageContent` (direct MCP image blocks), `embeddedResourceImages` (image blobs inside MCP resources), and `resourceLinkImages` (image links resolved via `resources/read`). * Each source records `model` (whether the image is passed to the model) and `ui` (whether the host UI renders it). * A single `placement` value (`"inline"` | `"collapsed"` | `"none"`) describes where rendered previews appear in the host UI. * New exported types: `HostImageSupport`, `ImageSourceSupport`, `ImagePlacement` from `@mcpjam/sdk/host-compat`. * New capability matrix: `MCP_APPS_VSCODE` exported from `@mcpjam/sdk/host-compat`. | Host | Tool image (model / UI) | Placement | | ---------- | ----------------------- | --------- | | Claude | ✓ / ✓ | inline | | ChatGPT | ✓ / ✓ | inline | | Cursor | ✓ / ✗ | none | | Copilot | ✓ / ✗ | none | | VS Code | ✓ / ✓ | inline | | Notion | ✗ / ✓ | collapsed | | Goose | ✓ / ✓ | collapsed | | Codex | ✓ / ✓ | collapsed | | Cline | ✓ / ✗ | none | | Slack | ✗ / ✗ | none | | Mistral | ✗ / ✗ | none | | n8n | ✗ / ✗ | none | | Perplexity | ✗ / ✗ | none | #### Changed * **Stateless MCP protocol version updated to `2026-07-28` RC** * The stateless transport protocol version has been updated from the retired `DRAFT-2026-v1` placeholder to the upstream RC literal `2026-07-28`, matching the canonical pin in `modelcontextprotocol/main`. * The protocol version picker in the inspector UI now shows **Latest (2025-11-25)** and **2026 RC (2026-07-28)** instead of "Latest" and "Draft". * The SDK constant `STATELESS_DRAFT_2026_V1` has been renamed to `LATEST_STATELESS_PROTOCOL_VERSION` and now equals `"2026-07-28"`. * **Migration required**: If you had servers pinned to `DRAFT-2026-v1`, you must re-select the protocol version in the inspector UI. Stored `DRAFT-2026-v1` pins are rejected by spec-conforming servers with a `-32004 UnsupportedProtocolVersionError`. #### Added * **OAuth Protocol 2025-03-26 Support** * Added support for original MCP OAuth specification (2025-03-26) * Direct RFC8414 discovery from MCP server base URL with fallback endpoints * PKCE required for all clients * DCR and pre-registered client registration strategies * **OAuth Debugger Enhancements** * Edit Config button to modify server settings without reconnecting * Copy logs feature with guide and raw formats for debugging * Deprecated transport detection with migration guidance for HTTP+SSE servers * RFC 8707 compliant resource URL canonicalization * **GPT-5 Model Support** * Added support for GPT-5 model variants: `gpt-5`, `gpt-5-mini`, `gpt-5-nano`, `gpt-5-chat-latest`, `gpt-5-pro`, `gpt-5-codex` * Organization verification notice for GPT-5 access #### Improved * **OAuth Flow** * Refined diagram focusing and zoom behavior for better step visualization * Improved error handling for expected 4xx responses during transport detection * Enhanced header merging in OAuth state machines across all protocol versions * **Error Handling** * Enhanced streaming error handling in chat interface * Errors now display as inline alerts instead of failing silently * Better error messages from AI providers ### Version 1.0.0 #### Added * **MCP Server Connection Management** * Support for STDIO, SSE, and Streamable HTTP transports * Multi-server connection support * Real-time connection status monitoring * **LLM Playground** * OpenAI integration (GPT-3.5/4) * Anthropic Claude integration (Claude 2/3) * DeepSeek AI support (DeepSeek R1) * Ollama local model compatibility * Interactive chat interface with streaming responses * **Tools & Resources Testing** * Tool execution and validation * Resource schema verification * Prompt testing interface * Real-time parameter validation * **OAuth 2.0 Testing** * Guided OAuth flow setup * Token management * Scope verification * Refresh token handling * **MCP Evals** * Automated compliance testing * Custom evaluation framework * Test result reporting * Performance benchmarking * **Developer Tools** * Comprehensive logging system * Request/response tracing * Error reporting and analysis * Performance monitoring #### Improved * Enhanced UI/UX with modern design * Better error messages and debugging support * Optimized performance for large MCP responses * Improved documentation and examples #### Fixed * Various bug fixes and stability improvements * Memory leak fixes in long-running sessions * Edge cases in MCP protocol handling ## Coming Soon We're working on: * Enhanced evaluation templates * More LLM provider integrations * Advanced debugging tools * Performance analytics dashboard * CI/CD integration support ## Stay Updated * Follow our [GitHub repository](https://github.com/MCPJam/inspector) for the latest updates * Join our [Discord community](https://discord.gg/JEnDtz8X6z) for announcements * Check our [website](https://mcpjam.com) for release notes ## Reporting Issues Found a bug or have a feature request? Please [open an issue](https://github.com/MCPJam/inspector/issues/new) on GitHub. # MCP Apps Conformance Source: https://docs.mcpjam.com/cli/apps-conformance Validate MCP Apps tool metadata and ui:// resource wiring from the CLI The `apps conformance` command validates the server-side MCP Apps surface your server exposes through tools and `ui://` resources. Use `tools call --ui` when you want to execute one UI-capable tool and render the completed result in the local Inspector App Builder. This is currently **server-side** MCP Apps conformance only. A passing run does **not** prove full SEP-1865 host behavior such as `ui/initialize`, `ui/notifications/tool-input` ordering, sandbox proxy behavior, or host display-mode handling. ## Quick start ```bash theme={"theme":"css-variables"} mcpjam apps conformance --url https://your-server.com/mcp ``` For a local stdio server: ```bash theme={"theme":"css-variables"} mcpjam apps conformance --command node --args server.js --cwd /path/to/project ``` You can emit CI-friendly JUnit XML from either the single-run or suite command: ```bash theme={"theme":"css-variables"} mcpjam apps conformance \ --url https://your-server.com/mcp \ --reporter junit-xml > apps-report.xml ``` To reproduce one tool result in the local Inspector: ```bash theme={"theme":"css-variables"} mcpjam tools call \ --url https://your-server.com/mcp \ --tool-name create_view \ --tool-args @params.json \ --ui \ --quiet \ --format json ``` ## What it checks The current runner validates: 1. MCP Apps tools are present. 2. Tool metadata uses valid `_meta.ui.resourceUri` and `visibility` values. 3. Tool `inputSchema` is a non-null JSON Schema object. 4. Listed UI resources use `ui://` URIs and `text/html;profile=mcp-app`. 5. Referenced UI resources can be fetched with `resources/read`. 6. Resource contents provide exactly one HTML payload via `text` or `blob`. 7. `_meta.ui.csp`, `permissions`, `domain`, and `prefersBorder` use valid shapes. ## Example output ```bash theme={"theme":"css-variables"} mcpjam apps conformance \ --url https://your-server.com/mcp \ --format json ``` Typical success summary: ```json theme={"theme":"css-variables"} { "passed": true, "outcome": "passed", "summary": "7/7 checks passed, 0 failed, 0 could not run, 0 not applicable", "discovery": { "toolCount": 3, "uiToolCount": 1, "listedResourceCount": 1, "listedUiResourceCount": 1, "checkedUiResourceCount": 1 } } ``` ## Suite runner Use `apps conformance-suite` when you want a named CI matrix from a config file. Example config: ```json theme={"theme":"css-variables"} { "name": "Apps CI", "target": { "url": "https://your-server.com/mcp", "timeout": 30000 }, "defaults": { "checkIds": ["ui-tools-present", "ui-tool-metadata-valid"] }, "runs": [ { "label": "metadata" }, { "label": "resources", "checkIds": [ "ui-resources-readable", "ui-resource-contents-valid", "ui-resource-meta-valid" ] } ] } ``` Run it with: ```bash theme={"theme":"css-variables"} mcpjam apps conformance-suite \ --config ./apps-conformance.json \ --reporter junit-xml > apps-report.xml ``` `target` uses the same shared server shape as the rest of the CLI, so it can point at either: * an HTTP target with `url` * a stdio target with `command`, optional `args`, optional `env`, and optional `cwd` ## Categories and check ids Two categories are available: * `tools` * `resources` Specific check ids: * `ui-tools-present` * `ui-tool-metadata-valid` * `ui-tool-input-schema-valid` * `ui-listed-resources-valid` * `ui-resources-readable` * `ui-resource-contents-valid` * `ui-resource-meta-valid` Use `--category` to run a subset by category, or `--check-id` to run specific checks. ```bash theme={"theme":"css-variables"} # Tool-only checks mcpjam apps conformance \ --url https://your-server.com/mcp \ --category tools # Specific checks mcpjam apps conformance \ --url https://your-server.com/mcp \ --check-id ui-resources-readable \ --check-id ui-resource-contents-valid ``` If you pass both `--category` and `--check-id`, the explicit `--check-id` selection wins. ## Render one app result Use `tools list` to find UI-capable tools. A tool has interactive UI when its metadata includes `_meta.ui.resourceUri`, deprecated `_meta["ui/resourceUri"]`, or `openai/outputTemplate` in `toolsMetadata`. Then use `tools call --ui` when you need to inspect the output of one UI-capable tool call. The CLI starts or attaches to the local Inspector backend, opens Inspector by default in a TTY, connects the target server, focuses App Builder in the active Inspector browser client, injects the already-completed tool result, and requests a UI snapshot. `--inspector-url` is the Inspector backend/API URL; `--frontend-url` is the browser/client URL and skips frontend discovery. Use `--no-open` when browser automation already opened `http://127.0.0.1:6274/#app-builder`; use `--attach-only` to disallow startup, browser opening, and discovery. Default non-TTY `--ui` runs do not open a browser unless `--open` is passed. TTY stderr runs print the App Builder URL and initial wait progress unless `--quiet` is set; the elapsed-seconds heartbeat only appears when stderr is a TTY. ```bash theme={"theme":"css-variables"} mcpjam tools call \ --command node --args server.js --cwd /path/to/project \ --tool-name create_view \ --tool-args @params.json \ --ui \ --protocol mcp-apps \ --device desktop \ --theme light \ --format json ``` `tools call --ui` executes the server tool once. Inspector receives the completed result through `renderToolResult`, so the UI render path does not call the tool a second time. The rendered state lives in the active Inspector client; opening the URL in a fresh tab later does not hydrate the old render. If `inspectorRender.status` is `skipped`, the tool call succeeded but Inspector had no active browser client, a render precondition was missing, or the render wait timed out; the envelope includes a stable root `warning` and `inspectorRender.warning` with one of the stable codes `no_active_client`, `timeout`, `disconnected_server`, or `unsupported_in_mode`, plus an `inspectorRender.remediation` hint. Non-quiet runs print a stderr tip, and the command keeps the tool-call exit code unless `--require-render` is set. If the command returns `success: false` with an `error`, check whether the failure came from `result` or from an Inspector render command. Raw `ui://` resource HTML can be inspected directly: ```bash theme={"theme":"css-variables"} mcpjam resources read \ --url https://your-server.com/mcp \ --resource-uri ui://widget/app.html \ --quiet \ --format json ``` ## Shared connection flags `apps conformance` uses the same shared target flags as the rest of the CLI: | Flag | Description | | ------------------------------ | ------------------------------------------------------------- | | `--transport ` | Explicit transport type (`http` or `stdio`) | | `--url ` | HTTP MCP server URL | | `--access-token ` | Bearer access token | | `--oauth-access-token ` | OAuth bearer access token | | `--refresh-token ` | OAuth refresh token | | `--client-id ` | OAuth client ID (with `--refresh-token`) | | `--client-secret ` | OAuth client secret (with `--refresh-token`) | | `--header
` | HTTP header in `Key: Value` format (repeatable) | | `--client-capabilities ` | Client capabilities as inline JSON, `@path`, or `-` for stdin | | `--command ` | Command for a stdio server | | `--args ` | Preferred stdio command arguments | | `--command-args ` | Legacy stdio command argument (repeatable) | | `-e, --env ` | Stdio environment `KEY=VALUE` values | | `--cwd ` | Working directory for the stdio child process | Stdio child processes inherit the parent shell environment by default. Use `-e/--env` to add values or override inherited ones when the app server needs project-specific configuration. `--transport` is optional; without it, `--url` implies HTTP and `--command` implies stdio. ## Outcomes: passed, failed, incomplete A run reports one of three outcomes, and `passed` is `true` only for the first: | `outcome` | Exit code | Meaning | | ------------ | --------- | ------------------------------------------------------------------------------------------------------------ | | `passed` | `0` | Every selected check either ran and passed, or does not apply to this server. | | `failed` | `1` | At least one check found a violation. | | `incomplete` | `3` | Nothing failed, but at least one selected check could not be run, so the run does not establish conformance. | Every skipped check carries a `skipReason`: * `not-applicable` — the check cannot apply here (no MCP Apps tools discovered, no UI resources listed). These never hold a run back. * `could-not-run` — the check applies but was never exercised (tools/list did not complete, connection failed). These make the run `incomplete`. An incomplete run carries a root `incompleteReason` naming the checks that did not run and what to change; non-quiet runs also print it to stderr. For a suite, the exit code is the worst of all runs (failure outranks incomplete). ## Notes * The runner always advertises the MCP Apps UI extension capability so servers do not hide their MCP Apps surface when custom client capabilities are provided. * Deprecated `_meta["ui/resourceUri"]` is accepted but reported as a warning. * Tool name SHOULD validations (length, character set, uniqueness) surface as warnings, not failures. * `permissions` are validated against the SEP-1865 object shape, for example `geolocation: {}` instead of booleans. * Exit codes are CI-friendly: `0` when all selected checks pass, `1` when any check fails, `2` for invalid command usage or config files, and `3` when the run is incomplete because a selected check could not be run. ## Related commands * [Server inspection](/cli/server-inspection) for breadth-first connectivity and capability triage * [Tools, resources & prompts](/cli/tools-resources-prompts) for direct connected checks * [Command reference](/cli/reference) for the full flag list # CI / CD Source: https://docs.mcpjam.com/cli/ci Run MCP health checks, conformance suites, and evals in GitHub Actions, GitLab CI, and other CI environments Run `mcpjam` in CI to catch MCP server regressions on every push. The examples below cover GitHub Actions and GitLab CI, but the same commands work in any CI environment. ## GitHub Actions ### Authentication There are three ways to authenticate in CI, depending on your server setup. #### Option 1: Headless OAuth login Best when your server supports OAuth with auto-consent (no interactive login page). The workflow obtains a fresh access token on every run. **Secrets needed:** | Secret | Description | | ---------------- | ------------------- | | `MCP_SERVER_URL` | Your MCP server URL | ```yaml theme={"theme":"css-variables"} name: MCP Health Check on: push: branches: [main] pull_request: jobs: mcp-doctor: runs-on: ubuntu-latest steps: - uses: actions/setup-node@v4 with: node-version: 20 - name: OAuth login (headless) run: | set -euo pipefail npx -y @mcpjam/cli@latest oauth login \ --url ${{ secrets.MCP_SERVER_URL }} \ --protocol-version 2025-11-25 \ --registration dcr \ --auth-mode headless \ --format json > /tmp/oauth-result.json TOKEN=$(jq -r '.credentials.accessToken // empty' /tmp/oauth-result.json) rm -f /tmp/oauth-result.json if [ -z "$TOKEN" ]; then echo "::error::OAuth login did not return an access token" exit 1 fi echo "::add-mask::$TOKEN" echo "MCP_TOKEN=$TOKEN" >> "$GITHUB_ENV" - name: Run doctor run: npx -y @mcpjam/cli@latest server doctor --url ${{ secrets.MCP_SERVER_URL }} --access-token $MCP_TOKEN --format json ``` #### Option 2: Refresh token Best when you already have a refresh token from a previous `oauth login`. Refresh tokens are long-lived and safe to store as secrets. The CLI handles the token exchange automatically. **Secrets needed:** | Secret | Description | | ------------------- | --------------------------------------------------- | | `MCP_SERVER_URL` | Your MCP server URL | | `MCP_REFRESH_TOKEN` | OAuth refresh token from a previous login | | `MCP_CLIENT_ID` | OAuth client ID (required with refresh tokens) | | `MCP_CLIENT_SECRET` | OAuth client secret (if the client is confidential) | ```yaml theme={"theme":"css-variables"} name: MCP Health Check on: push: branches: [main] pull_request: jobs: mcp-doctor: runs-on: ubuntu-latest steps: - uses: actions/setup-node@v4 with: node-version: 20 - name: Run doctor run: | npx -y @mcpjam/cli@latest server doctor \ --url ${{ secrets.MCP_SERVER_URL }} \ --refresh-token ${{ secrets.MCP_REFRESH_TOKEN }} \ --client-id ${{ secrets.MCP_CLIENT_ID }} \ --client-secret ${{ secrets.MCP_CLIENT_SECRET }} \ --format json ``` To get a refresh token, run `mcpjam oauth login` locally with `--format json` and grab `.credentials.refreshToken` from the output. #### Option 3: Static API key Best when your server uses a non-expiring API key instead of OAuth. **Secrets needed:** | Secret | Description | | ---------------- | ------------------- | | `MCP_SERVER_URL` | Your MCP server URL | | `MCP_API_KEY` | Static API key | ```yaml theme={"theme":"css-variables"} name: MCP Health Check on: push: branches: [main] pull_request: jobs: mcp-doctor: runs-on: ubuntu-latest steps: - uses: actions/setup-node@v4 with: node-version: 20 - name: Run doctor run: npx -y @mcpjam/cli@latest server doctor --url ${{ secrets.MCP_SERVER_URL }} --access-token ${{ secrets.MCP_API_KEY }} --format json ``` #### Option 4: No auth Some servers don't require authentication at all. **Secrets needed:** | Secret | Description | | ---------------- | ------------------- | | `MCP_SERVER_URL` | Your MCP server URL | ```yaml theme={"theme":"css-variables"} name: MCP Health Check on: push: branches: [main] pull_request: jobs: mcp-doctor: runs-on: ubuntu-latest steps: - uses: actions/setup-node@v4 with: node-version: 20 - name: Run doctor run: npx -y @mcpjam/cli@latest server doctor --url ${{ secrets.MCP_SERVER_URL }} --format json ``` ### Tool surface diffing Snapshot your tool surface before and after a deploy to catch breaking changes (renamed parameters, changed descriptions, removed tools). ```yaml theme={"theme":"css-variables"} - name: Snapshot before run: npx -y @mcpjam/cli@latest server export --url ${{ secrets.MCP_SERVER_URL }} --access-token $MCP_TOKEN --format json > before.json # your deploy step here - name: Snapshot after run: npx -y @mcpjam/cli@latest server export --url ${{ secrets.MCP_SERVER_URL }} --access-token $MCP_TOKEN --format json > after.json - name: Diff run: diff <(jq -S . before.json) <(jq -S . after.json) ``` ### OAuth conformance suite Run the full registration x protocol version x auth mode matrix from a config file and output JUnit XML for test reporters. ```yaml theme={"theme":"css-variables"} - name: OAuth conformance run: | npx -y @mcpjam/cli@latest oauth conformance-suite \ --config ./oauth-matrix.json \ --reporter junit-xml > report.xml - name: Upload test report uses: actions/upload-artifact@v4 if: always() with: name: oauth-conformance path: report.xml ``` See [OAuth Conformance](/cli/oauth-conformance) for details on the config file format. ### Protocol conformance suite Run a repeatable matrix of protocol check selections from a config file and publish JUnit XML. ```yaml theme={"theme":"css-variables"} - name: Protocol conformance run: | npx -y @mcpjam/cli@latest protocol conformance-suite \ --config ./protocol-conformance.json \ --reporter junit-xml > protocol-report.xml - name: Upload protocol report uses: actions/upload-artifact@v4 if: always() with: name: protocol-conformance path: protocol-report.xml ``` ### MCP Apps conformance suite Run the server-side MCP Apps surface checks from a config file and publish JUnit XML for CI dashboards. ```yaml theme={"theme":"css-variables"} - name: MCP Apps conformance run: | npx -y @mcpjam/cli@latest apps conformance-suite \ --config ./apps-conformance.json \ --reporter junit-xml > apps-report.xml - name: Upload apps report uses: actions/upload-artifact@v4 if: always() with: name: apps-conformance path: apps-report.xml ``` Single-run `protocol conformance`, `oauth conformance`, and `apps conformance` also accept `--reporter junit-xml` when you only need one target/check selection instead of a suite config file. *** ## GitLab CI The same CLI commands work in GitLab CI. The examples below use GitLab CI/CD variables for secrets and `.gitlab-ci.yml` syntax. ### Authentication #### Headless OAuth login ```yaml theme={"theme":"css-variables"} mcp-health-check: image: node:20 variables: MCP_SERVER_URL: $MCP_SERVER_URL script: - | npx -y @mcpjam/cli@latest oauth login \ --url "$MCP_SERVER_URL" \ --protocol-version 2025-11-25 \ --registration dcr \ --auth-mode headless \ --format json > /tmp/oauth-result.json TOKEN=$(jq -r '.credentials.accessToken // empty' /tmp/oauth-result.json) rm -f /tmp/oauth-result.json if [ -z "$TOKEN" ]; then echo "OAuth login did not return an access token" exit 1 fi export MCP_TOKEN="$TOKEN" - npx -y @mcpjam/cli@latest server doctor --url "$MCP_SERVER_URL" --access-token "$MCP_TOKEN" --format json rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH ``` #### Refresh token ```yaml theme={"theme":"css-variables"} mcp-health-check: image: node:20 variables: MCP_SERVER_URL: $MCP_SERVER_URL MCP_REFRESH_TOKEN: $MCP_REFRESH_TOKEN MCP_CLIENT_ID: $MCP_CLIENT_ID MCP_CLIENT_SECRET: $MCP_CLIENT_SECRET script: - | npx -y @mcpjam/cli@latest server doctor \ --url "$MCP_SERVER_URL" \ --refresh-token "$MCP_REFRESH_TOKEN" \ --client-id "$MCP_CLIENT_ID" \ --client-secret "$MCP_CLIENT_SECRET" \ --format json rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH ``` #### Static API key ```yaml theme={"theme":"css-variables"} mcp-health-check: image: node:20 variables: MCP_SERVER_URL: $MCP_SERVER_URL MCP_API_KEY: $MCP_API_KEY script: - npx -y @mcpjam/cli@latest server doctor --url "$MCP_SERVER_URL" --access-token "$MCP_API_KEY" --format json rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH ``` ### Tool surface diffing Snapshot your tool surface before and after a deploy to catch breaking changes. ```yaml theme={"theme":"css-variables"} mcp-tool-diff: image: node:20 variables: MCP_SERVER_URL: $MCP_SERVER_URL MCP_TOKEN: $MCP_TOKEN script: - npx -y @mcpjam/cli@latest server export --url "$MCP_SERVER_URL" --access-token "$MCP_TOKEN" --format json > before.json # your deploy step here - npx -y @mcpjam/cli@latest server export --url "$MCP_SERVER_URL" --access-token "$MCP_TOKEN" --format json > after.json - jq -S . before.json > /tmp/before-sorted.json - jq -S . after.json > /tmp/after-sorted.json - diff /tmp/before-sorted.json /tmp/after-sorted.json - rm -f /tmp/before-sorted.json /tmp/after-sorted.json ``` ### OAuth conformance suite ```yaml theme={"theme":"css-variables"} mcp-oauth-conformance: image: node:20 script: - | npx -y @mcpjam/cli@latest oauth conformance-suite \ --config ./oauth-matrix.json \ --reporter junit-xml > report.xml artifacts: when: always reports: junit: report.xml ``` See [OAuth Conformance](/cli/oauth-conformance) for details on the config file format. *** ## Evals in CI There are two ways to wire MCPJam evals into a pipeline: trigger a **hosted eval run** with the CLI, or run evals **locally with the SDK** and upload the results. Both authenticate with an MCPJam API key (`sk_…`) from **Settings → API keys**. ### Trigger a hosted eval suite `mcpjam cloud eval run` starts an asynchronous run of a suite that lives in your MCPJam project. Without `--wait`, it prints a launch receipt and returns immediately. In CI, add `--wait` and `--out` to write a structured JSON report after every launched run reaches a terminal state. **Secrets needed:** | Secret | Description | | ---------------- | ----------------------- | | `MCPJAM_API_KEY` | MCPJam API key (`sk_…`) | ```yaml theme={"theme":"css-variables"} - name: Run hosted eval env: MCPJAM_API_KEY: ${{ secrets.MCPJAM_API_KEY }} run: | npx -y @mcpjam/cli@latest cloud eval run \ --suite "Nightly regression" \ --project "My project" \ --wait \ --out eval-report.json \ --format json > eval-result.json echo "Completed run $(jq -r '.runs[0].id' eval-result.json)" - name: Gate and write JUnit # Always run this step, even if the run above exited 1 on a failed # verdict: a default `if:` is `success()`, and skipping this step # would lose the gate's configurable policy and its JUnit report # exactly when the run failed. The job still ends up failed either # way — the run step above already exited nonzero. if: always() env: MCPJAM_API_KEY: ${{ secrets.MCPJAM_API_KEY }} run: | RUN_ID=$(jq -r '.runs[0].id' eval-result.json) npx -y @mcpjam/cli@latest cloud eval gate \ --run "$RUN_ID" \ --project "My project" \ --wait \ --min-pass-rate-percent 100 \ --reporter junit-xml \ --out eval-report.xml - name: Upload eval reports uses: actions/upload-artifact@v4 if: always() with: name: hosted-eval path: | eval-report.json eval-report.xml ``` **Runs launched from Actions badge as GitHub, and fill the CI column.** The CLI declares what it is on every launch, so a run started from a workflow shows **GitHub** in the Runs table rather than the generic **API** that every hosted launch used to show, and its commit, branch and job appear alongside it. Nothing to configure: the declaration is derived from the standard `GITHUB_*` environment, exactly as conformance uploads already do it. Outside CI the same command declares **CLI**. That commit is also what `eval compare --baseline-sha` resolves against, so a run launched by the CLI in CI can now be compared to a previous commit's run the same way an SDK-reported one can. This is a *declared* label, deliberately: the platform still stamps the run's own `source` server-side and records the verified credential separately, so a badge can never claim more than "the client said so". No secret that could prove it can live in a public npm package. In human format, `eval run` prints a `View:` line after the payload so you can open the run directly from the terminal: ```text theme={"theme":"css-variables"} View: https://app.mcpjam.com/evals/suite//runs/?project= ``` This line is only emitted in human format — `--format json` output is unchanged, so scripts that parse the JSON stream are unaffected. Use `--wait-timeout ` to replace the 10-minute default. `--out` defaults to the structured JSON format; add `--reporter junit-xml` to write JUnit XML instead, or `--reporter html` for a self-contained HTML page (decision summary + failures only — traces, parity, and history are paid tiers not included here). When `--reporter` is present, the same report is also written to stdout. **`eval run --wait` sets a verdict-based exit code.** `0` pass, `1` a completed run's verdict failed (the ONLY condition that produces `1`), `2` usage error or an invalid suite file, `3` auth failed (no credential, or the platform rejected it, at launch or mid-wait), `4` a connection/setup failure this CLI itself observed before evaluation ran (or a local `--out` write failure), `5` no valid verdict — `inconclusive`, a null/unrecognized result, a run status of failed/cancelled/timed-out, or a wait that hit its deadline. A multi-target launch merges these worst-of across every waited run, in the order `1 > 3 > 4 > 5 > 0`. Retry guidance: `4` and `5` mean infrastructure, or an absence of observation — nothing here says the server is wrong, so retrying the CI job is reasonable. But a bare re-run is not automatically safe: `eval run --suite` only dedupes against an in-flight or already-completed launch when you pass a **stable** `--idempotency-key`, and exit `5` can mean the run is still running (a wait that hit its deadline) — without that key, a retry can start a second paid run alongside the first rather than resuming it. Pass `--idempotency-key` (or poll/resume the run id already in the receipt) before retrying on `4` or `5`. `3` means fix the credential first; it poisons every other observation in the same launch. Never retry blindly on `1` — that code is reserved for a run the platform actually graded as failed. Either `eval run --wait` or `eval gate` already fails the job on its own — the `eval gate` step above adds a configurable pass/fail *policy* (thresholds, per-scorer gates) and baseline comparison on top of the same verdict, so keep it when you want more than "did this run's own verdict pass". `eval gate` sets a verdict-based exit code, and writes its report before doing so: `0` passed **or waived**, `1` an eval verdict failed, `2` usage error, `3` incomplete or non-gateable. Infrastructure conditions never map to `1`, so retrying on `3` is safe. This is a **different, four-code contract from `eval run --wait`** above — `gate`'s `3` means "incomplete", not the six-code scheme's `3` ("auth failed"), and the two are deliberately not unified (see the [CLI reference](/cli/reference#cloud-eval-gate) for why). `eval status` also prints a `View:` line in human format, identical to the one `eval run` prints. #### Gating on cost Cost is the question people actually have about a change: *did this make the suite more expensive?* Gate it relatively rather than absolutely — an absolute ceiling goes stale on every prompt and model change, while a percentage against the previous run keeps meaning the same thing. ```bash theme={"theme":"css-variables"} npx -y @mcpjam/cli@latest cloud eval compare --run "$RUN_ID" \ --project "My project" \ --max-cost-increase-percent 10 ``` With no `--base-run` or `--base-sha`, the baseline is the nearest earlier completed run in the same suite — which is what "did my change make this more expensive?" usually means. An absolute ceiling is also available, for a suite whose budget is fixed: ```bash theme={"theme":"css-variables"} npx -y @mcpjam/cli@latest cloud eval gate --run "$RUN_ID" \ --project "My project" \ --max-cost-usd 0.50 ``` Both report **non-gateable** (exit `3`) rather than passing when the cost is unknown or only partly measured — a run on your own API keys, a harness run, or a run only some of whose iterations MCPJam priced. Exit `3` is safe to retry and safe to treat as "no opinion"; what it never does is let an unpriced run through as if it were cheap. #### Waiving a gate A run whose gate failed can be overridden by an authorized user until an expiry they name, so a release is not blocked while a known regression is being fixed: ```bash theme={"theme":"css-variables"} mcpjam cloud eval gate waive --run "$RUN_ID" --reason "hotfix ships today; tracked in ENG-4821" --expires-in 3d ``` `eval gate` then exits `0` and reports the outcome as `waived`. It is **not** reported as a pass: the run keeps its failed result, the failing verdicts stay in the report, and the waiver — who granted it, why, and until when — is named in every artifact the command writes, including the JUnit XML your CI job uploads (as a `` element, so it neither fails the build nor renders as a clean green row). Only a real verdict failure is waivable. A cancelled run, a `--wait` timeout, or a network failure still exits `3` with a waiver in place — those established nothing, and a waiver granted for a regression is not consent to ship on an infrastructure failure. Waivers expire, and expiry is enforced on both sides: the platform republishes the GitHub Check Run when the waiver lapses, and the CLI re-derives the expiry itself rather than trusting the platform's answer. `mcpjam cloud eval gate unwaive --run "$RUN_ID"` ends one early. The waiver reason is stored **unredacted** and readable by anyone who can see the suite, for as long as the suite exists. Never put secrets, tokens, or customer data in it. #### Decision summary `eval run --wait`, `eval status`, `eval gate` and `eval compare` all read one versioned object — the **run decision summary** — and every output format restates it. Where each command puts it: | Command | `--format json` | `--format human` | `--out` / `--reporter` | | ----------------- | ---------------------------------------- | ------------------------------------------ | ------------------------------- | | `eval run --wait` | `decisionSummary` on the stdout receipt | block on stdout, after the receipt | `decisionSummary` on the report | | `eval status` | `decisionSummary` on the stdout document | block on stdout, above the `View:` line | — | | `eval gate` | `decisionSummary` beside `gate` | block on **stderr**, under the gate report | `decisionSummary` on the report | | `eval compare` | `decisionSummary` beside `compare` | block on **stderr**, under the gate report | `decisionSummary` on the report | Two scoping rules that are easy to miss. `eval run --wait` attaches a summary only when the invocation launched **one** run: a fan-out has several, and labelling a receipt about N runs with the decision of one would be a false claim rather than a partial one. `eval compare` reports the **compare side's** decision only — the baseline's failures are a different run's diagnostics, and printing them here would read as this run's. ```text theme={"theme":"css-variables"} Decision summary: failed (verdict policy v2) — 2/3 case variants passed, 1 failed Why: a case did not meet its pass threshold Diagnostics: 2 non-passing of 9 iterations examined (the complete set) First break: Tool call — the call arguments did not match what the case expects (1 of 2 measured iterations) Fetch order (c_orders, iteration 2) — failed First failed stage: Tool call — the call arguments did not match what the case expects Failure category: call arguments Expected tool calls: fetch_order Observed failure: server rejected arguments Evidence at Tool call: span ids span-call-2; reasons order_id must be a string Trace: /projects/prj_1/eval-runs/run_7/iterations/it_2/trace Next action: review the authored arguments against the tool input schema Setup abort (c_setup, iteration 5) — failed First failed stage: none was established — the run never reached the server's stages Failure category: setup Trace: /projects/prj_1/eval-runs/run_7/iterations/it_5/trace Next action: check the server connection and environment configuration ``` Four things about it are worth knowing before you script against it. **The counts carry the population they count.** `measurementUnit` is `caseVariant` under verdict policy v2 — one case under one provider/model, with repetitions as *iterations inside it* — and `trial` on a legacy percent-threshold run. A 3-case suite with 5 repetitions is legitimately "3" under one unit and "15" under the other, so a count quoted without its unit is not a fact. **The summary explains the verdict; it never re-decides it.** Under policy v2 the run's own decision is the authority for the verdict, the rates, the validity phase and the per-case aggregation, and it is carried through on `decision`. The per-iteration diagnostics sit *underneath* that: a case can pass with a failing iteration in it, so tallying the diagnostics gives a different answer than the platform reached. **`notEstablished` is not a failure.** It is a fourth verdict meaning no verdict exists at all — the run is unfinished, it stopped before finishing, or its decision could not be read. `undecided.reason` says which. It is also not `inconclusive`, which *is* a decision: the validity phase ran and withheld a verdict because the run did not measure the server well enough. **A page of diagnostics says whether it is the whole story.** `diagnostics.complete` is true only when the listed iterations are the run's entire non-passing set, and `scannedIterations` says how many were examined — so an empty list from a complete page ("nothing failed") is distinguishable from an empty list from a partial one ("we did not look"). Evidence is scoped to the claim it supports: for a measured failure the span ids, prompt indexes and reasons come from the first failed stage's row alone, and a setup abort or evaluator error keeps a stage-less pointer rather than naming a stage nothing established. `eval status` prints the block only when a terminal run did not pass — a clean pass has nothing to diagnose. `--format json` stays exactly one parseable document in every case: the summary rides *inside* it, never as a second block appended after it. If the summary cannot be fetched, it is omitted rather than failing the command. **The human block leads with where the chain broke.** Under the diagnostics headline, before any per-iteration detail, a non-passing run gets one line naming the earliest stage at which a readable iteration stopped, why, and how many iterations stopped there: ```text theme={"theme":"css-variables"} First break: Tool call — the call arguments did not match what the case expects (2 of 3 measured iterations) ``` "First" means earliest in chain order — `connection → discovery → selection → call → response → userValue` — never "most common", so the count beside it is what tells you whether the run had one problem or several. When the breaks are spread the line says so (`earliest of 3 stages that broke`), and when some chains could not be read it says that too (`1 more had no readable chain`), because otherwise the denominator quietly shrinks to the iterations that happened to validate. A run that reached no stage at all — a setup abort, an evaluator error — names its bucket instead of inventing a location. The line is **not** a diagnosis. A first failed stage is a location and a failure category is a bucket; neither on its own says what to change. `eval status --stages` expands each failing iteration to all six chain rows with their states and reasons. It is off by default: six rows per iteration is a lot of terminal on a run with twenty failures, and the first-break line above already carries the answer. Human output is not a stable contract — script against `--format json`, where the enums travel as enums. Hosted runs execute LLM iterations on the platform and consume your organization's credits or configured provider keys. See the [`cloud eval` command reference](/cli/reference#cloud-eval-commands) for the full surface, including `cloud eval judge` (request LLM-as-judge grading on a finished run), `cloud eval validate` (offline suite-file validation), `cloud eval export` (write a hosted suite to a local file), `cloud eval github list/connect` (GitHub checks integration), and more. ### Upload SDK eval results If you instead run evals inside your own CI job with [`@mcpjam/sdk`](/sdk/concepts/running-evals) (`EvalTest` / `EvalSuite`), set `MCPJAM_API_KEY` and results upload automatically to the CI Evals dashboard (pass-rate trends, per-model breakdowns, and a full trace per iteration): ```yaml theme={"theme":"css-variables"} - name: Run SDK evals env: MCPJAM_API_KEY: ${{ secrets.MCPJAM_API_KEY }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} run: npx vitest run evals/ ``` See [Save Results to MCPJam](/sdk/concepts/saving-results) for auto-save, the manual reporting APIs, CI metadata (branch, commit SHA, run URL), and artifact upload (JUnit XML, Jest/Vitest JSON). # MCPJam as an MCP Server Source: https://docs.mcpjam.com/cli/mcp-server Run MCPJam as a local stdio MCP server so agents can connect to, exercise, and debug other MCP servers `mcpjam mcp` runs MCPJam itself as an MCP server over stdio. Add it to any MCP client — Claude Desktop, Claude Code, Cursor, or your own agent — and the agent gets MCPJam's local testing engine as tools: connect to a server under test, call its tools, read its resources and prompts, watch the notifications it emits, and run diagnostic sweeps. ```bash theme={"theme":"css-variables"} npx -y @mcpjam/cli@latest mcp ``` Everything runs locally in the spawned process. No account, login, or hosted backend is involved, and the server under test can be a local stdio process — something hosted tooling can't reach. ## Why use this instead of the CLI? Agents with shell access can already run `mcpjam` commands directly, and for one-shot checks that is often the better choice. The MCP server mode adds two things the CLI cannot do: * **Clients without a shell.** Claude Desktop and other chat-style MCP clients can't run CLI commands. This is the only way to give them MCPJam's testing engine. * **Persistent sessions.** Each CLI invocation reconnects to the target server. `mcpjam mcp` holds connections open across tool calls, so the agent can observe `notifications/message` logs, `list_changed` events, resource updates, and other session behavior that one-shot commands miss. ## Setup Add to `claude_desktop_config.json` (Settings → Developer → Edit Config): ```json theme={"theme":"css-variables"} { "mcpServers": { "mcpjam": { "command": "npx", "args": ["-y", "@mcpjam/cli@latest", "mcp"] } } } ``` ```bash theme={"theme":"css-variables"} claude mcp add mcpjam -- npx -y @mcpjam/cli@latest mcp ``` Add to `.cursor/mcp.json` in your project (or `~/.cursor/mcp.json`): ```json theme={"theme":"css-variables"} { "mcpServers": { "mcpjam": { "command": "npx", "args": ["-y", "@mcpjam/cli@latest", "mcp"] } } } ``` Any client that supports stdio servers can launch: ```bash theme={"theme":"css-variables"} npx -y @mcpjam/cli@latest mcp ``` With a global install (`npm i -g @mcpjam/cli`), use `mcpjam mcp` as the command instead. In MCP server mode all status output goes to stderr; stdout carries only JSON-RPC. The `--timeout ` global flag sets the default per-request timeout against target servers (individual tool calls can override it with `timeoutMs`). ## Tools ### Connections | Tool | Description | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `connect_server` | Open a persistent connection to a target server: `url` (+ optional `accessToken`, `headers`) for HTTP, or `command` (+ optional `args`, `env`, `cwd`) for stdio. Returns the negotiated initialization info. | | `disconnect_server` | Close a connection and discard its buffered notifications | | `list_servers` | List open connections with status and a redacted target summary | | `server_info` | Initialization info for a connection: protocol version, transport, server version, capabilities, instructions | | `ping_server` | MCP ping with round-trip latency | ### Exercising the target | Tool | Description | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `list_tools` | List the target's tools (with pagination cursor) | | `call_tool` | Call a tool and return the target's raw `CallToolResult` — check the payload's `isError` for tool-level failures | | `list_resources` | List resources, or resource templates with `templates: true` | | `read_resource` | Read a resource by URI | | `list_prompts` | List prompts | | `get_prompt` | Fetch a named prompt with optional string arguments | | `get_notifications` | Notifications buffered since connect: log messages, progress, `list_changed` events, resource updates. Filter by `server`/`method`, optionally `clear` | ### Stateless diagnostics | Tool | Description | | --------------- | ------------------------------------------------------------------------------------------------------------------ | | `server_doctor` | One-shot diagnostic sweep (HTTP or stdio target) — same checks as [`mcpjam server doctor`](/cli/server-inspection) | | `probe_server` | HTTP-only probe: transport selection, auth requirements, OAuth metadata discovery — same as `mcpjam server probe` | ## Skills over MCP Alongside its tools, `mcpjam mcp` serves two of MCPJam's Agent Skills over the [`io.modelcontextprotocol/skills` extension](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640) (SEP-2640): | Skill | What it is for | | -------------------- | -------------------------------------------------------------------------------------------------------- | | `mcp-inspector` | Interpreting probe, doctor, OAuth, and conformance output conservatively — the tools this server exposes | | `mcpjam-eval-import` | Converting an existing test corpus into an MCPJam eval suite | A client that advertises the extension sees them in `skills/list` and can load them with no install step — you do not need `npx skills add` for these two when you are connected this way. Each skill's files are also readable as ordinary resources under `skill://mcpjam//…`, which is why `resources/list` now returns rows for a server that previously exposed none. Every file carries a SHA-256 digest and a byte size, and a conforming host verifies both before loading. The listing deliberately carries **no** `ttlMs`: because the documented launch command is `npx -y @mcpjam/cli@latest mcp`, the bytes behind an identical command change whenever a release lands, so this server issues no cache-freshness directive it could not honour. `mcpjam-eval-import` is served by the [hosted MCP server](/cli/mcp-server#relationship-to-the-hosted-mcp-server) too, under the same URI. If you are connected to both at once, address it by server (`/mcpjam-eval-import`) rather than by bare URI. ## Example session A typical agent flow while developing a stdio server: 1. `connect_server` with `{ "name": "dev", "command": "node", "args": ["dist/server.js"] }` 2. `list_tools` with `{ "server": "dev" }` — review names, descriptions, schemas 3. `call_tool` with `{ "server": "dev", "tool": "search", "arguments": { "query": "test" } }` 4. `get_notifications` with `{ "server": "dev" }` — did the server log what you expected? Did it emit `notifications/tools/list_changed`? 5. Iterate on the server code, then `disconnect_server` / `connect_server` to pick up the rebuild 6. `server_doctor` with the same `command` for a final health sweep Tool results are JSON payloads describing the target server. Errors come back as structured `{ "error": { "code", "message" } }` payloads with `isError: true` — for example `USAGE_ERROR` for invalid input or `SERVER_UNREACHABLE` when the target is down. ## Relationship to the hosted MCP server MCPJam also operates a hosted MCP server at `mcp.mcpjam.com/mcp` that exposes your MCPJam **account** — projects, saved servers, eval suites and runs — behind OAuth. The two are complementary. `mcpjam mcp` protocol `serverInfo.name` is `mcpjam`; its title is `MCPJam CLI`. The hosted server identifies as `MCPJam MCP`. | | `mcpjam mcp` (this page) | `mcp.mcpjam.com` | | ------------------- | ------------------------------------------------------------------ | ---------------------------------------- | | Runs | Locally, spawned by your MCP client | Hosted | | Auth | None | MCPJam account (OAuth) | | Scope | Test any server reachable from your machine, including local stdio | Servers and evals saved in your projects | | Transport to MCPJam | stdio | Streamable HTTP | | Protocol name | `mcpjam` | `MCPJam MCP` | | Title | `MCPJam CLI` | `MCPJam MCP` | ## Troubleshooting * **Client says the server failed to start** — run `npx -y @mcpjam/cli@latest mcp` in a terminal; you should see `MCPJam CLI MCP server listening on stdio` on stderr. First runs may be slow while `npx` downloads the package; pre-install with `npm i -g @mcpjam/cli` and use `mcpjam mcp` to avoid the download. * **`connect_server` fails with `SERVER_UNREACHABLE`** — the target URL or command is wrong, or the target crashed on startup. Try `server_doctor` against the same target for a structured diagnosis. * **A connection name is already taken** — `connect_server` refuses to overwrite an existing name; `disconnect_server` it first or pass a different `name`. # Migrating to CLI 4.0 Source: https://docs.mcpjam.com/cli/migration Command-path and flag changes when account-bound mcpjam commands moved under mcpjam cloud `@mcpjam/cli` 4.0 separates local MCP operations from account-bound Cloud operations. Most account-bound commands live under `mcpjam cloud`; hosted readiness is the intentional root-level exception because it targets publisher directory checks and does not use Cloud project links. There are no compatibility aliases for the old Cloud command paths. ```bash theme={"theme":"css-variables"} mcpjam server probe --url https://example.com/mcp # local mcpjam oauth login --url https://example.com/mcp # local MCP OAuth mcpjam cloud login # MCPJam Cloud account mcpjam cloud projects list # MCPJam Cloud mcpjam cloud eval run --suite smoke # MCPJam Cloud mcpjam cloud tunnel --server dev -- npm start # local target, Cloud registration ``` ## What did not change * Frozen local commands: `server`, `tools`, `resources`, `prompts`, `subscriptions`, `tasks`, `apps`, `oauth`, `protocol`, `xaa`, `compat`, `inspector`, `mcp`, `telemetry` * Local commands still reject `--api-key` / `--api-url` / `--project` * Hosted `readiness start|status|list|cancel|report` stays under the root `readiness` command (leaf `--api-key`; no Cloud project link) * Local `mcpjam mcp` protocol `serverInfo.name` is still `mcpjam` (title is now `MCPJam CLI`) ## Command paths | 3.x | 4.0 | | ------------------------------------------------------------------------ | ---------------------------------------------------------------- | | `mcpjam login` / `logout` / `whoami` | `mcpjam cloud login` / `logout` / `whoami` | | `mcpjam organizations` | `mcpjam cloud organizations` | | `mcpjam projects …` | `mcpjam cloud projects …` | | `mcpjam eval …` | `mcpjam cloud eval …` | | `mcpjam chat-sessions list` | `mcpjam cloud sessions list` | | `mcpjam sessions search` | `mcpjam cloud sessions search` | | `mcpjam hosts …` | `mcpjam cloud clients …` (`cloud hosts` still works as an alias) | | `mcpjam environments …` | `mcpjam cloud environments …` | | `mcpjam capabilities` | `mcpjam cloud projects capabilities` | | `mcpjam projects server …` | `mcpjam cloud projects servers …` (plural only) | | `mcpjam personas` / `journeys` / `scenarios` / `swarms` / `user-testing` | `mcpjam cloud …` (same names) | | `mcpjam images …` | `mcpjam cloud images …` | | `mcpjam tunnel …` | `mcpjam cloud tunnel …` | ## Flags | 3.x | 4.0 | | | | | | | | | | | -------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----- | ---------- | ---- | ------- | ----- | ----- | ---------- | -------------------------- | ------------------------------------------------- | | `--api-key` / `--api-url` on each Cloud leaf | Declared on `mcpjam cloud`; work before or after descendants | | | | | | | | | | | `projects list --organization-id` | `mcpjam cloud projects list --org` | | | | | | | | | | | `tunnel --id ` | `mcpjam cloud tunnel --server ` (`--id` still works, hidden) | | | | | | | | | | | \`eval status | cancel | judge | iterations | gate | compare | trace | steps | screenshot | video --project\` required | Optional. Same precedence as other Cloud commands | | `sessions list` with no `--project` | Project-scoped (link / env / automatic). Pass `--all-projects` for the old all-projects listing | | | | | | | | | | Project-selection precedence: `--project` > explicit `project` in `--file`/`--json` > `MCPJAM_PROJECT` > nearest `.mcpjam/project.json` > automatic (most recently updated). `MCPJAM_PROJECT_ID` does not select a Cloud CLI project. `mcpjam cloud link` writes `.mcpjam/project.json`; use `mcpjam cloud link --remove` to remove the nearest link. `mcpjam cloud status` is zero-network. ## Local MCP identity `mcpjam mcp` still announces protocol name `mcpjam`. The display title is `MCPJam CLI`. Instructions say this is the local testing engine, not the hosted Cloud MCP at `mcp.mcpjam.com`. # OAuth Conformance Source: https://docs.mcpjam.com/cli/oauth-conformance Test your MCP server's OAuth implementation across all registration methods and protocol versions The OAuth conformance module runs real OAuth handshakes against your MCP server — the same flows Claude Desktop, ChatGPT, and Claude Code perform — and tells you exactly where they break. ## Why you need this * **Each MCP client exercises OAuth slightly differently.** Passing conformance means your server works for all of them. * **OAuth bugs are silent.** A valid-looking token can be rejected by MCP authentication downstream. Users see "failed to connect" with no context. `--verify-tools` catches this. * **3 registration strategies x 4 protocol versions x 3 auth modes** = 36 possible flow combinations. The suite runner covers the matrix in one invocation. * **CI-ready output.** `--reporter junit-xml` for dashboards. Exit 0/1/2 for scripts. ## Quick start ```bash theme={"theme":"css-variables"} mcpjam oauth conformance \ --url https://your-server.com/mcp \ --protocol-version 2025-11-25 \ --registration dcr \ --verify-tools ``` The default `--auth-mode` is `interactive` — a browser opens for consent. Pass `--auth-mode headless` for CI against auto-consenting auth servers, or `--auth-mode client_credentials` for M2M. **What success looks like:** ```text theme={"theme":"css-variables"} OAuth conformance: PASSED Server: https://your-server.com/mcp Flow: 2025-11-25 / dcr Summary: OAuth conformance passed for https://your-server.com/mcp (2025-11-25, dcr) Duration: 4312ms Steps: 12 passed steps, 0 failed steps, 0 skipped steps Verification listTools: PASS (8 tools) ``` ## What it tests Each conformance run walks through: 1. **Initial request** — sends an unauthenticated request to trigger a 401 2. **Discovery** — fetches resource metadata and authorization server metadata 3. **Registration** — registers the client via CIMD, DCR, or pre-registered credentials 4. **Authorization** — obtains an authorization code (interactive, headless, or client\_credentials) 5. **Token exchange** — exchanges the code for access/refresh tokens 6. **Authenticated request** — retries the MCP request with the token 7. **Post-auth verification** (optional) — connects via MCP and lists tools / calls a tool When `--conformance-checks` is enabled, six additional negative checks run after the flow: * **DCR redirect URI policy** — attempts dynamic client registration with a non-loopback `http://` redirect URI and expects rejection under the MCP authorization profile * **Invalid client** — confirms the auth server rejects an unknown client ID * **Invalid redirect at the authorization endpoint** — sends an authorization request with a mismatched `redirect_uri` and looks for rejection before the server redirects back to it * **Invalid token** — confirms the MCP server rejects an obviously invalid bearer token with HTTP 401 * **Invalid redirect** — attempts a token request with a mismatched redirect URI to look for exact-match enforcement; this check may be skipped if the request is rejected for a different reason first * **Token format** — validates the token response has the expected fields ## Scenarios ### I just added OAuth to my MCP server ```bash theme={"theme":"css-variables"} mcpjam oauth conformance \ --url http://localhost:8080/mcp \ --protocol-version 2025-11-25 \ --registration dcr \ --verify-tools ``` ### CI conformance testing without a browser ```bash theme={"theme":"css-variables"} # Option A: headless (requires auto-consenting auth server) mcpjam oauth conformance \ --url $MCP_SERVER_URL \ --protocol-version 2025-11-25 \ --registration dcr \ --auth-mode headless \ --verify-tools # Option B: client_credentials (M2M, no browser) mcpjam oauth conformance \ --url $MCP_SERVER_URL \ --protocol-version 2025-11-25 \ --registration preregistered \ --auth-mode client_credentials \ --client-id "$M2M_CLIENT_ID" \ --client-secret "$M2M_CLIENT_SECRET" \ --verify-tools ``` Always pass `--auth-mode` explicitly in CI. The default is `interactive` and will attempt to open a browser. ### Test multiple protocol versions at once Create a config file: ```json theme={"theme":"css-variables"} { "name": "Full Protocol Matrix", "serverUrl": "https://your-server.com/mcp", "defaults": { "auth": { "mode": "headless" }, "verification": { "listTools": true } }, "flows": [ { "label": "2025-03-26 / dcr", "protocolVersion": "2025-03-26", "registrationStrategy": "dcr" }, { "label": "2025-06-18 / dcr", "protocolVersion": "2025-06-18", "registrationStrategy": "dcr" }, { "label": "2025-11-25 / dcr", "protocolVersion": "2025-11-25", "registrationStrategy": "dcr" }, { "label": "2025-11-25 / cimd", "protocolVersion": "2025-11-25", "registrationStrategy": "cimd" }, { "label": "2026-07-28 / cimd", "protocolVersion": "2026-07-28", "registrationStrategy": "cimd" } ] } ``` ```bash theme={"theme":"css-variables"} mcpjam oauth conformance-suite --config ./oauth-tests.json ``` ### Users report tokens work but tool calls fail This is exactly what `--verify-call-tool` catches. OAuth can succeed (valid token issued) while the MCP layer rejects it (wrong audience, missing scope, session binding). ```bash theme={"theme":"css-variables"} mcpjam oauth conformance \ --url https://your-server.com/mcp \ --protocol-version 2025-11-25 \ --registration dcr \ --verify-call-tool your_tool_name ``` `--verify-call-tool` implies `--verify-tools`. ### Test specific scopes ```bash theme={"theme":"css-variables"} mcpjam oauth conformance \ --url https://your-server.com/mcp \ --protocol-version 2025-11-25 \ --registration dcr \ --scopes "read:tools write:tools" \ --verify-tools ``` `--scopes` sets what you **request** from the auth server. The runner does not currently verify that granted scopes match requested scopes. When `--scopes` is omitted, the runner prefers the `scope=` value from the initial `WWW-Authenticate` challenge and falls back to `scopes_supported`. ### Drive consent via Playwright or automation ```bash theme={"theme":"css-variables"} mcpjam oauth conformance \ --url https://your-server.com/mcp \ --protocol-version 2025-11-25 \ --registration dcr \ --print-url \ --verify-tools ``` `--print-url` writes the consent URL to stderr (`OAUTH_CONSENT_URL: https://...`) instead of launching a browser. The local callback listener still runs — your Playwright script opens the URL, drives consent, and the CLI catches the redirect. ### Run the OAuth negative checks ```bash theme={"theme":"css-variables"} mcpjam oauth conformance \ --url https://your-server.com/mcp \ --protocol-version 2025-11-25 \ --registration dcr \ --conformance-checks \ --verify-tools ``` This adds 6 checks after the main flow: DCR redirect URI policy, invalid client rejection, invalid redirect enforcement at the authorization endpoint, invalid bearer token rejection at the MCP server, invalid redirect enforcement at the token endpoint, and token format validation. ## Registration strategies | Strategy | Description | Protocol versions | | --------------- | ------------------------------------------------------------------------ | ---------------------- | | `cimd` | Client ID Metadata Document — the client publishes its identity at a URL | 2025-11-25, 2026-07-28 | | `dcr` | Dynamic Client Registration — the client registers itself at runtime | All | | `preregistered` | Pre-registered client ID and optional secret | All | **When to choose what:** * **CIMD** (2025-11-25, 2026-07-28): preferred for production when the auth server supports it — avoids mutable DCR state. * **DCR**: the default for quick testing — works everywhere, no pre-configuration needed. * **Preregistered**: use when the auth server doesn't support DCR, or for `client_credentials` M2M flows. When `cimd` is used without `--client-metadata-url`, the runner falls back to mcpjam's public metadata document at `https://www.mcpjam.com/.well-known/oauth/client-metadata.json`. ## Auth modes | Mode | Use case | User interaction | | ----------------------- | ------------------------------------- | ---------------- | | `interactive` (default) | Local dev — opens browser for consent | Browser popup | | `headless` | CI with auto-consenting auth servers | None | | `client_credentials` | Machine-to-machine service accounts | None | ### Compatibility | Registration | `headless` | `interactive` | `client_credentials` | | ------------------------------- | :--------: | :-----------: | :------------------------------: | | `cimd` (2025-11-25, 2026-07-28) | yes | yes | no | | `dcr` | yes | yes | yes (secret from DCR) | | `preregistered` | yes | yes | yes (requires `--client-secret`) | `cimd` + `client_credentials` is rejected with a clear error: *"CIMD is a browser-based registration flow and only works with --auth-mode headless or --auth-mode interactive."* ## Post-auth verification OAuth conformance proves a token was issued. Verification proves the token is **usable for MCP operations**. Three silent-failure modes this catches: 1. **Token audience mismatch** — OAuth flow succeeds but `tools/list` returns 401 because `aud` doesn't match the MCP resource URL. 2. **Scope mismatch** — token issued but the MCP tool handler requires different scopes. 3. **Session binding** — some servers require a separate session init after auth. | Flag | What it does | | --------------------------- | ------------------------------------------------------------ | | `--verify-tools` | After OAuth, connect to the MCP server and call `tools/list` | | `--verify-call-tool ` | Also call the named tool (implies `--verify-tools`) | ## Reuse credentials safely Use `--credentials-out ` when a conformance run should hand credentials to later connected commands. The file is written with `0600` permissions and can be passed to commands such as `tools list`, `apps conformance`, `resources list`, and `server doctor` with `--credentials-file `. ```bash theme={"theme":"css-variables"} mcpjam oauth conformance \ --url https://your-server.com/mcp \ --protocol-version 2025-11-25 \ --registration dcr \ --credentials-out creds.json mcpjam tools list \ --url https://your-server.com/mcp \ --credentials-file creds.json ``` Raw JSON output redacts OAuth secrets by default. `--credentials-out` is the supported way to persist live tokens. ## Troubleshooting | Step that failed | Likely cause | Fix | | --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `request_without_token` returned 200 | Server allows anonymous access or missing `WWW-Authenticate` | Return 401 with `WWW-Authenticate: Bearer resource_metadata="..."` | | `request_resource_metadata` 404 | `/.well-known/oauth-protected-resource` missing or wrong Content-Type | Ensure the endpoint exists and returns `application/json` | | `received_resource_metadata` — resource validation failed | Protected Resource Metadata is missing its required `resource` field, uses a non-HTTPS URL, or advertises a resource on a different origin than the server URL | Ensure your PRM document includes a `resource` field that is an HTTPS URL on the same origin as the MCP server (RFC 9728 §2) | | `received_resource_metadata` — AS URL mismatch | `authorization_servers` points to a URL without AS metadata | Verify the AS URL has `/.well-known/oauth-authorization-server` | | `request_client_registration` 400 | DCR endpoint rejected the registration metadata | Check required fields; use SDK `dynamicRegistration` overrides for custom metadata | | `received_authorization_code` — HTML login page | Auth server requires interactive login but you used `--auth-mode headless` | Switch to `--auth-mode interactive` | | `received_authorization_code` — state mismatch | Stale popup or concurrent authorization flows | Close other browser tabs and retry | | `token_request` 401 `invalid_client` | Wrong `token_endpoint_auth_method` or client\_id mismatch | Check DCR response for `token_endpoint_auth_method`; CIMD uses `none` | | `authenticated_mcp_request` 401 | Token `aud` doesn't match MCP resource URL | Ensure your auth server sets `aud` correctly | | `verify_list_tools` auth error | Token works for OAuth but MCP layer rejects it — scope issue | Check that the token's scope matches what tool handlers require | | DCR + `client_credentials`: "Dynamic registration produced a public client" | DCR returned `token_endpoint_auth_method: "none"` | Use `--registration preregistered` with explicit credentials instead | ## CI/CD integration ### GitHub Actions ```yaml theme={"theme":"css-variables"} - name: OAuth Conformance run: | npx -y @mcpjam/cli@latest oauth conformance \ --url ${{ secrets.MCP_SERVER_URL }} \ --protocol-version 2025-11-25 \ --registration preregistered \ --auth-mode client_credentials \ --client-id ${{ secrets.M2M_CLIENT_ID }} \ --client-secret ${{ secrets.M2M_CLIENT_SECRET }} \ --verify-tools ``` ### GitLab CI (JUnit output) ```yaml theme={"theme":"css-variables"} oauth-conformance: stage: test script: - npx -y @mcpjam/cli@latest oauth conformance-suite --config ./oauth-tests.json --reporter junit-xml > report.xml artifacts: reports: junit: report.xml ``` ## All flags ### `oauth conformance` | Flag | Required | Default | Description | | ----------------------------- | -------- | -------------- | ------------------------------------------------------------------------ | | `--url ` | Yes | | MCP server URL | | `--protocol-version ` | Yes | | `2025-03-26`, `2025-06-18`, `2025-11-25`, or `2026-07-28` | | `--registration ` | Yes | | `cimd`, `dcr`, or `preregistered` | | `--auth-mode ` | No | `interactive` | `headless`, `interactive`, or `client_credentials` | | `--client-id ` | No | | OAuth client ID (required for `preregistered`) | | `--client-secret ` | No | | OAuth client secret | | `--client-metadata-url ` | No | | CIMD metadata document URL | | `--redirect-url ` | No | Auto-generated | OAuth redirect URL | | `--scopes ` | No | | Space-separated scope string | | `--header
` | No | | HTTP header `Key: Value` (repeatable) | | `--step-timeout ` | No | `30000` | Per-step timeout | | `--verify-tools` | No | | After OAuth, connect and list tools | | `--verify-call-tool ` | No | | Also call the named tool (implies `--verify-tools`) | | `--conformance-checks` | No | | Run additional negative OAuth checks after the main flow | | `--credentials-out ` | No | | Write OAuth credentials to file (mode 0600); stdout has secrets redacted | | `--print-url` | No | | Print consent URL to stderr instead of launching a browser | | `--reporter ` | No | | `json-summary` or `junit-xml` CI report output | For interactive conformance runs, a custom `--redirect-url` must still be an `http://localhost` or `http://127.0.0.1` loopback URL. Custom callback paths are supported. ### `oauth conformance-suite` | Flag | Required | Default | Description | | --------------------------- | -------- | ------- | ---------------------------------------------------------------------------------------- | | `--config ` | Yes | | Path to JSON config file | | `--verify-tools` | No | | Enable tool listing on all flows | | `--verify-call-tool ` | No | | Call the named tool after listing | | `--credentials-out ` | No | | Write OAuth credentials from the first flow that returns credentials to file (mode 0600) | | `--reporter ` | No | | `json-summary` or `junit-xml` CI report output | `--verify-tools` and `--verify-call-tool` on the suite command are forced onto **every** flow. Per-flow `verification` entries in the config file cannot disable them once the CLI flag is set. ## Related * [OAuth Login & Debugging](/cli/oauth-login) — get tokens and debug OAuth flows * [Server Inspection](/cli/server-inspection) — probe and diagnose before testing conformance * [OAuth Conformance SDK Reference](/sdk/reference/oauth-conformance) — programmatic usage with `OAuthConformanceTest` # OAuth Login & Debugging Source: https://docs.mcpjam.com/cli/oauth-login Authenticate with MCP servers and debug OAuth flows The `oauth` command group handles authentication against MCP servers that require OAuth. Use `oauth login` to obtain tokens interactively, and the proxy/metadata commands to debug discovery and endpoint behavior. ## Quick start ```bash theme={"theme":"css-variables"} # Interactive login — opens a browser for consent mcpjam oauth login --url https://your-server.com/mcp \ --protocol-version 2025-11-25 \ --registration dcr ``` On success, the command prints credentials such as an access token, refresh token, client ID, and sometimes client secret. You can pass access tokens to other commands via `--oauth-access-token` or `--access-token`. `oauth login` validates the server's Protected Resource Metadata (PRM) at the discovery step. If the PRM document is missing its required `resource` field, or if the advertised resource is on a different origin than the server URL, the login fails before any authorization request is made. Fix the PRM document on your server to resolve this. The OAuth Debugger accepts these values with a warning so you can still observe the flow. ## Commands ### `oauth login` Runs a full OAuth flow: discovery, registration, authorization, token exchange. Supports all three registration strategies and auth modes. ```bash theme={"theme":"css-variables"} # DCR + interactive (most common for local dev) mcpjam oauth login --url https://your-server.com/mcp \ --protocol-version 2025-11-25 \ --registration dcr # CIMD + interactive (2025-11-25 and 2026-07-28) mcpjam oauth login --url https://your-server.com/mcp \ --protocol-version 2025-11-25 \ --registration cimd # Preregistered + client_credentials (M2M, no browser) mcpjam oauth login --url https://your-server.com/mcp \ --protocol-version 2025-11-25 \ --registration preregistered \ --auth-mode client_credentials \ --client-id "$CLIENT_ID" \ --client-secret "$CLIENT_SECRET" ``` Prefer environment variables for `--client-secret`. Passing it inline leaks to shell history and process listings. The `--credentials-out ` flag saves OAuth credentials to a file with `0600` permissions. Depending on the flow, the file may include an access token, refresh token, client ID, and client secret. Secret fields are redacted from stdout output. Use the saved file with `--credentials-file` on other commands. ```bash theme={"theme":"css-variables"} mcpjam oauth login --url https://your-server.com/mcp \ --credentials-out creds.json # Then reuse across commands mcpjam tools list --url https://your-server.com/mcp --credentials-file creds.json mcpjam server doctor --url https://your-server.com/mcp --credentials-file creds.json ``` The `--debug-out ` flag captures a structured debug artifact with full HTTP traces — useful for filing bug reports or handing off to another engineer. ```bash theme={"theme":"css-variables"} mcpjam oauth login --url https://your-server.com/mcp \ --protocol-version 2025-11-25 \ --registration dcr \ --debug-out oauth-debug.json ``` ### `oauth metadata` Fetch and display OAuth metadata from a URL. Useful for verifying your server's discovery endpoints are correct. ```bash theme={"theme":"css-variables"} mcpjam oauth metadata --url https://your-server.com/.well-known/oauth-protected-resource mcpjam oauth metadata --url https://auth.example.com/.well-known/oauth-authorization-server ``` ### `oauth proxy` Send an arbitrary HTTP request through the SSRF-hardened OAuth proxy. Useful for testing individual OAuth endpoints (registration, token, etc.) without building raw HTTP requests. Private targets — loopback, your LAN, or a hostname that resolves to one — are reachable by default, since the CLI runs on your machine. Add `--https-only` to apply the hosted app's stricter policy instead. Neither mode will dial a link-local or cloud-metadata address, and neither sends a plaintext request to a public host. ```bash theme={"theme":"css-variables"} # Test a DCR registration mcpjam oauth proxy \ --url https://auth.example.com/register \ --method POST \ --header "Content-Type: application/json" \ --body '{"redirect_uris":["http://localhost:8080/callback"],"client_name":"test"}' # Fetch an endpoint mcpjam oauth proxy --url https://auth.example.com/v1/oauth2/token --method GET ``` ### `oauth debug-proxy` Same as `oauth proxy` but routes through the debug proxy path. Use when testing endpoints that behave differently for debug vs. production requests. ## Login flags | Flag | Required | Default | Description | | ----------------------------- | -------- | -------------- | ------------------------------------------------------------------------------------- | | `--url ` | Yes | | MCP server URL | | `--protocol-version ` | Yes | | `2025-03-26`, `2025-06-18`, `2025-11-25`, or `2026-07-28` | | `--registration ` | Yes | | `cimd`, `dcr`, or `preregistered` | | `--auth-mode ` | No | `interactive` | `headless`, `interactive`, or `client_credentials` | | `--client-id ` | No | | OAuth client ID (required for `preregistered`) | | `--client-secret ` | No | | OAuth client secret | | `--client-metadata-url ` | No | | CIMD metadata document URL | | `--redirect-url ` | No | Auto-generated | OAuth redirect URL | | `--scopes ` | No | | Space-separated scope string | | `--header
` | No | | HTTP header `Key: Value` (repeatable) | | `--step-timeout ` | No | `30000` | Per-step timeout | | `--verify-tools` | No | | After login, connect and list tools | | `--verify-call-tool ` | No | | After listing, also call a named tool | | `--credentials-out ` | No | | Write OAuth credentials to file (mode 0600); stdout output has secret fields redacted | | `--debug-out ` | No | | Write debug artifact to file | For interactive login, a custom `--redirect-url` must still be an `http://localhost` or `http://127.0.0.1` loopback URL. Custom callback paths are supported. ## Using tokens from login ### Credentials file (interactive & agent workflows) For interactive or agent workflows where you login once and run multiple commands, save credentials to a file: ```bash theme={"theme":"css-variables"} # Login and save credentials mcpjam oauth login --url https://your-server.com/mcp --credentials-out creds.json # Reuse across commands mcpjam tools list --url https://your-server.com/mcp --credentials-file creds.json mcpjam server doctor --url https://your-server.com/mcp --credentials-file creds.json mcpjam protocol conformance --url https://your-server.com/mcp --credentials-file creds.json ``` The credentials file includes the server URL and any reusable OAuth credentials returned by the login flow. The CLI validates that the file's server URL matches the target and checks token expiry before use. Connected commands such as `tools list`, `resources list`, `prompts list`, and `server doctor` can use refresh-token credentials from the file when the saved access token is expired. `protocol conformance` and `server probe` only read a non-expired access token from the file; if the access token is expired, run `oauth login --credentials-out` again or pass a fresh token directly. `--credentials-file` cannot be combined with `--access-token`, `--oauth-access-token`, `--refresh-token`, `--client-id`, or `--client-secret`. The CLI rejects conflicting auth sources upfront. ### Direct token passing (CI/CD & M2M) When you already have a token from an environment variable, a secret store, or a `client_credentials` flow, pass it directly: ```bash theme={"theme":"css-variables"} # From an environment variable or secret store mcpjam tools list --url https://your-server.com/mcp --access-token "$TOKEN" mcpjam server doctor --url https://your-server.com/mcp --oauth-access-token "$TOKEN" # M2M: preregistered client_credentials — token comes back on stdout TOKEN=$(mcpjam oauth login --url https://your-server.com/mcp \ --protocol-version 2025-11-25 --registration preregistered \ --auth-mode client_credentials \ --client-id "$CLIENT_ID" --client-secret "$CLIENT_SECRET" \ --format json | jq -r '.credentials.accessToken') mcpjam tools list --url https://your-server.com/mcp --access-token "$TOKEN" ``` You can also use `--refresh-token` with `--client-id` (and optionally `--client-secret`) for commands that support automatic token refresh. # CLI Overview Source: https://docs.mcpjam.com/cli/overview Local MCP testing and mcpjam cloud: probing, debugging, OAuth, conformance, and hosted evals from your terminal The `mcpjam` CLI is two invocations. Local MCP testing stays at the top level: inspect, debug, and test MCP servers from the command line or CI (connectivity, OAuth, MCP Apps, tools/resources/prompts, protocol conformance). Most local commands are one-shot; `mcpjam mcp` and `subscriptions listen` keep a process open. Account-bound commands live under `mcpjam cloud` and talk to your MCPJam account. There are no compatibility aliases for the old Cloud paths — see [Migrating to CLI 4.0](/cli/migration). ## Recommended: Use with Agent Skills The easiest way to use the CLI is through the [MCPJam skill](https://github.com/MCPJam/inspector/tree/main/skills/mcp-inspector), which gives your agent full context on every command and workflow. ```bash theme={"theme":"css-variables"} npx skills add mcpjam/inspector --skill mcp-inspector ``` To convert existing promptfoo, pytest, Jest, or CSV tests, use the [MCP eval import skill](https://github.com/MCPJam/inspector/tree/main/skills/mcpjam-eval-import). ```bash theme={"theme":"css-variables"} npx skills add mcpjam/inspector --skill mcpjam-eval-import ``` To drive MCPJam's hosted eval tools — launch a run, poll it to a verdict, and triage a failure down to the step — use the [run MCPJam evals skill](https://github.com/MCPJam/inspector/tree/main/skills/run-mcpjam-evals). This one is about running suites that already exist, not writing them. ```bash theme={"theme":"css-variables"} npx skills add mcpjam/inspector --skill run-mcpjam-evals ``` Once installed, your agent will know how and when to invoke `mcpjam` commands automatically. ## Install ```bash theme={"theme":"css-variables"} # Global (gives you the mcpjam command) npm i -g @mcpjam/cli # Or run without installing npx -y @mcpjam/cli@latest --help ``` All examples in these docs use `mcpjam` directly. If you installed via `npx`, replace `mcpjam` with `npx -y @mcpjam/cli@latest`. ## Command groups ### Local MCP testing These stay at the program root. They reject `--api-key` / `--api-url` / `--project`. | Group | Purpose | Key commands | | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | | [`server`](/cli/server-inspection) | Triage connectivity and capabilities | `probe`, `doctor`, `info`, `validate`, `ping`, `capabilities`, `export` | | [`tools`](/cli/tools-resources-prompts) | Exercise the tool surface | `list`, `call` | | [`resources`](/cli/tools-resources-prompts) | Read resources and templates | `list`, `read`, `templates` | | [`prompts`](/cli/tools-resources-prompts) | Fetch prompts | `list`, `get` | | [`subscriptions`](/cli/reference) | Open a long-lived MCP subscription and stream notifications | `listen` | | [`tasks`](/cli/tasks) | Drive MCP Tasks and validate the tasks wire | `list`, `get`, `cancel`, `conformance` | | [`apps`](/cli/apps-conformance) | Validate MCP Apps metadata and resource wiring | `conformance`, `conformance-suite` | | [`oauth`](/cli/oauth-conformance) | Test OAuth flows and conformance against an MCP server | `conformance`, `conformance-suite`, `login`, `metadata`, `proxy` | | [`protocol`](/cli/reference) | MCP protocol conformance checks | `conformance`, `conformance-suite` | | [`readiness`](/cli/reference#readiness-hosted-commands) | Grade a server against a publisher's directory. Hosted `start` / `status` / `list` / `cancel` / `report` stay here (leaf `--api-key`; no Cloud project link) | `check`, `start claude`, `start openai`, `status`, `list`, `cancel`, `report` | | [`xaa`](/cli/xaa) | Run the Cross-App Access (ID-JAG) debugger against an MCP server | `run` | | [`compat`](/cli/reference#compat-command) | Check whether a server's tools and widgets work on each AI host | `compat` | | [`inspector`](/cli/reference#inspector-commands) | Start, open, or stop the local Inspector from the CLI | `open`, `start`, `stop` | | [`mcp`](/cli/mcp-server) | Run MCPJam as a stdio MCP server for agents (Claude Desktop, Claude Code, Cursor, ...) | `mcp` | | [`telemetry`](/cli/telemetry) | Inspect and configure anonymous CLI telemetry | `status`, `disable`, `enable` | ### MCPJam Cloud Account-bound commands. Declare `--api-key` / `--api-url` on `mcpjam cloud` (before or after descendants). `mcpjam oauth login` is MCP-server OAuth; `mcpjam cloud login` is your MCPJam account. | Group | Purpose | Key commands | | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | [`cloud`](/cli/reference#cloud-workspace) | Account, project link, and workspace | `login`, `logout`, `whoami`, `link`, `status`, `organizations`, `projects`, `sessions` | | [`cloud eval`](/cli/reference#cloud-eval-commands) | Author and run eval suites in your hosted MCPJam projects | `create`, `list`, `run`, `status` | | [`cloud tunnel`](/cli/reference#cloud-tunnel) | Expose a local MCP server through a public MCPJam tunnel URL, registered as a server in your project | `tunnel --server `, `tunnel --server -- ` | | [`cloud clients`](/cli/reference#cloud-clients-commands) | Manage clients in your hosted MCPJam projects (`cloud hosts` is a deprecated alias) | `list`, `get`, `create`, `update`, `servers`, `duplicate`, `delete`, `templates` | | [`cloud environments`](/cli/reference#cloud-environments-commands) | Manage project environments — the host + servers + pinned skills/plugins bundles eval suites and journeys run against | `list`, `get`, `resolve`, `create`, `update`, `archive`, `restore` | | [`cloud images`](/cli/reference#cloud-images-commands) | Manage custom Computer sandbox images (blueprints) | `list`, `get`, `create`, `validate`, `edit`, `build`, `logs`, `use`, `reset`, `promote`, `delete` | | [`cloud skills`](/cli/reference#cloud-skills-commands) | List and read Cloud Skills in your hosted MCPJam projects — discover the skill IDs that `--compose-skill` and `--skill` require (read-only; authoring is in the app) | `list`, `get` | | [`cloud projects servers`](/cli/reference#cloud-projects-servers-commands) | Connect MCP servers to a project and manage pending connection requests | `connect`, `connect-status`, `connect-cancel` | Also under `mcpjam cloud`: `journeys`, `scenarios`, `personas`, `swarms`, `user-testing`. Full flag tables are in the [command reference](/cli/reference). ## Cloud credentials and API URL `--api-key` and `--api-url` are declared on `mcpjam cloud` and work before or after descendants (`mcpjam cloud --api-key sk_… eval list` and `mcpjam cloud eval list --api-key sk_…` are the same). Local commands, including hosted `readiness`, reject those flags at the program root. Hosted `readiness` still takes leaf `--api-key`. Credential precedence for Cloud commands: 1. `--api-key` — must be an `sk_` key. A `mcpjam_…` legacy key is a usage error. 2. `MCPJAM_API_KEY` — same `sk_` rule. A legacy value in this env var is ignored (with a warning) so SDK eval reporting can still use it. 3. Stored OAuth from `mcpjam cloud login` 4. Missing — Cloud commands fail with login guidance before they hit the network (`mcpjam cloud status` reports `missing` instead) API URL precedence: 1. `--api-url` 2. `MCPJAM_API_URL` 3. The `apiUrl` stored with the OAuth login (only when that login is the credential) 4. `https://app.mcpjam.com/api/v1` An explicit `--api-url` or `MCPJAM_API_URL` that is not an `http(s)` URL is a usage error for commands that talk to the network (exit `2`). `mcpjam cloud status` uses the same checks but stays network-free: it still emits the complete JSON report, sets `ok: false`, includes `credential.valid` / `deployment.valid` plus an `error` string, and exits `1`. Missing credentials are informational (`credential.valid: null`) and successful. Successful Cloud commands print an audience line to stderr (`Using MCPJam Cloud as … · project: … · `). `--quiet` suppresses it. Machine-readable JSON stays on stdout. `cloud status` redacts API keys. ## Cloud project scope Most Cloud commands pick one project: 1. `--project ` 2. An explicit `project` field in `--file` / `--json` 3. `MCPJAM_PROJECT` 4. The nearest valid `.mcpjam/project.json` 5. Automatic — the most recently updated project you can see Empty `--project` or `MCPJAM_PROJECT` is a usage error. `MCPJAM_PROJECT_ID` does **not** select a Cloud CLI project (it is SDK eval reporting only). ```bash theme={"theme":"css-variables"} mcpjam cloud login mcpjam cloud link # writes .mcpjam/project.json (no secrets) mcpjam cloud status # zero-network: credential, deployment, selector mcpjam cloud eval list # uses the link mcpjam cloud link --remove ``` `mcpjam cloud eval` run-inspection commands (`status`, `cancel`, `judge`, `iterations`, `gate`, `compare`, `trace`, `steps`, `screenshot`, `video`) follow the same rule — `--project` is optional. `mcpjam cloud sessions list` does too; `--all-projects` restores a cross-project listing and cannot be combined with `--project`. Hosted `readiness` never reads the project link. ## What `--host` means `--host` is not one flag. The meaning depends on the command: | Where | What it selects | | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `server`, `tools`, `resources`, `prompts` | An **AI client** (`claude`, `chatgpt`, `cursor`, …). The CLI sends that host's `clientInfo`, capabilities, and protocol version in `initialize`. | | `compat --host` | Which AI clients to report. Repeatable; default is all. | | `cloud eval --host`, `cloud journeys`, `cloud scenarios` | A **saved project Host** (``), the client config an eval or journey is stamped with. | | `cloud environments --host-id` | The project Host id the environment runs as. Distinct from eval's `--host`. | `--host` on a local command is mutually exclusive with `--client-capabilities`. A Cloud `--host` is mutually exclusive with `--server` / `--environment` when those already supply a closed server set. ## Global flags These flags apply to every command. Cloud credentials `--api-key` / `--api-url` are declared on `mcpjam cloud`, not the program root. | Flag | Default | Description | | ------------------- | --------------------------------- | -------------------------------------------------------------------------- | | `--timeout ` | `30000` | Request timeout in milliseconds | | `--rpc` | off | Include raw JSON-RPC request/response logs in JSON output under `_rpcLogs` | | `--quiet` | off | Suppress non-result progress output on stderr | | `--no-telemetry` | off | Disable anonymous telemetry for this invocation | | `--format ` | `human` on TTY, `json` when piped | Raw output format (`json` or `human`) | | `-v, --version` | | Print the CLI version | ## Output formats The CLI auto-detects whether stdout is a terminal: * **Interactive terminal** — defaults to `--format human`. For most commands this is pretty-printed JSON; `server doctor` and the OAuth conformance commands provide dedicated human-readable summaries. * **Piped or redirected** (CI, `| jq`, agent invocation) — defaults to `--format json`, the full structured result. * **Conformance in CI** — `protocol conformance`, `protocol conformance-suite`, `oauth conformance`, `oauth conformance-suite`, `apps conformance`, and `apps conformance-suite` support `--reporter junit-xml` and `--reporter json-summary`. * **Explicit `--format`** always wins over the auto-detected default. **For agents**: raw JSON is the source of truth. Human format is a lossy presentation layer. If you're parsing output programmatically, pass `--quiet --format json`. JSON-valued flags accept inline JSON, `@path`, or `-` for stdin. `tools call` also accepts `--tool-args-stdin` as a shorthand for `--tool-args -`. Use files or stdin for large payloads to avoid shell escaping issues: ```bash theme={"theme":"css-variables"} echo '{"key":"value"}' | mcpjam tools call --url $URL --access-token $TOKEN \ --tool-name my_tool --tool-args - --quiet --format json ``` ## Connecting to servers The CLI supports two transport modes, selected by which flags you pass: * `--url ...` selects HTTP. * `--command ...` selects stdio. * `--transport http|stdio` is optional and acts as an explicit override/validator when you want the CLI to reject mismatched flags early. ### HTTP (Streamable HTTP / SSE) ```bash theme={"theme":"css-variables"} mcpjam server doctor --url https://your-server.com/mcp ``` Add auth when needed: ```bash theme={"theme":"css-variables"} # Static bearer token mcpjam server doctor --url https://your-server.com/mcp --access-token $TOKEN # OAuth tokens from a prior login mcpjam server doctor --url https://your-server.com/mcp --oauth-access-token $TOKEN # Custom headers mcpjam server doctor --url https://your-server.com/mcp --header "X-API-Key: $KEY" ``` ### Stdio (local subprocess) ```bash theme={"theme":"css-variables"} mcpjam server doctor --command node --args server.js --cwd /path/to/project -e API_KEY=$KEY ``` Stdio child processes inherit the parent shell environment by default. Use `-e/--env` to add values or override inherited ones for the spawned server. Structured debug artifacts only record the explicit env keys you pass through `-e/--env`; inherited shell variables are not enumerated. `oauth ...` and `protocol ...` are HTTP-only command groups. They do not accept stdio targets. ## Update notifications After a successful command, the CLI checks whether a newer version of `@mcpjam/cli` is available and prints a notice to stderr when one is found: ``` Update available: @mcpjam/cli 1.2.0 -> 1.3.0 Run npm install -g @mcpjam/cli to update ``` The check is non-blocking: version data is fetched in a detached background process and cached locally for 24 hours, so it never slows down your commands. The notice is written to stderr only and never touches stdout or JSON output, so it is safe for both interactive terminals and AI agents (Claude Code, Cursor, etc.) that capture stderr. It is suppressed automatically in CI. To opt out, set one of these environment variables before running any command: | Variable | Effect | | -------------------------- | ------------------------------------------------ | | `MCPJAM_NO_UPDATE_CHECK=1` | Disable update checks for `@mcpjam/cli` | | `NO_UPDATE_NOTIFIER=1` | Disable update checks (conventional npm opt-out) | | `CI=true` | Automatically suppressed in CI environments | ## Exit codes | Code | Meaning | | ---- | ------------------------------------------------------------------------------- | | `0` | Success / all checks passed | | `1` | Command ran but reported a failure (e.g., server unhealthy, conformance failed) | | `2` | Invalid arguments or configuration | ## Quick triage workflow The fastest path from "I have a server URL" to "I know what's wrong": ```bash theme={"theme":"css-variables"} # 1. One-shot health check (probe + connect + sweep) mcpjam server doctor --url https://your-server.com/mcp # 2. If oauth_required: get a token mcpjam oauth login --url https://your-server.com/mcp \ --protocol-version 2025-11-25 --registration dcr # 3. Re-run doctor with the token mcpjam server doctor --url https://your-server.com/mcp --oauth-access-token $TOKEN # 4. Exercise tools directly mcpjam tools list --url https://your-server.com/mcp --access-token $TOKEN mcpjam tools call --url https://your-server.com/mcp --access-token $TOKEN \ --tool-name my_tool --tool-args @params.json --quiet --format json # 5. If the server exposes MCP Apps, validate the ui:// surface mcpjam apps conformance --url https://your-server.com/mcp --access-token $TOKEN # 6. Render one UI-capable tool result in Inspector's App Builder. # --ui opens Inspector by default in a TTY; add --no-open if browser automation already opened it. mcpjam tools call --url https://your-server.com/mcp --access-token $TOKEN \ --tool-name my_app_tool --tool-args @params.json --ui --quiet --format json ``` ## Use from an MCP client instead of a shell If your agent speaks MCP but can't run shell commands (Claude Desktop, chat clients), run the same testing engine as a stdio MCP server: ```bash theme={"theme":"css-variables"} npx -y @mcpjam/cli@latest mcp ``` Connections to servers under test stay open between tool calls, so agents can also observe notifications and session behavior that one-shot CLI commands can't. See [MCPJam as an MCP server](/cli/mcp-server). ## What's next * [MCPJam as an MCP server](/cli/mcp-server) — give MCP clients the testing engine over stdio * [Server inspection](/cli/server-inspection) — probe, doctor, and diagnostics * [OAuth conformance](/cli/oauth-conformance) — test your OAuth implementation * [OAuth login](/cli/oauth-login) — authenticate and debug OAuth flows * [MCP Apps conformance](/cli/apps-conformance) — validate `_meta.ui.resourceUri` and `ui://` resource wiring * [Tools, resources & prompts](/cli/tools-resources-prompts) — exercise the connected surface * [Migrating to CLI 4.0](/cli/migration) — command-path and flag changes under `mcpjam cloud` * [Full command reference](/cli/reference) — every flag for every command # Command Reference Source: https://docs.mcpjam.com/cli/reference Complete flag reference for every mcpjam CLI command Complete flag tables for every `mcpjam` command. For guides and recipes, see the individual command pages. Account-bound Cloud commands live under `mcpjam cloud`. Local MCP testing stays at the top level (`mcpjam server`, `mcpjam oauth login`, …). Credential flags `--api-key` / `--api-url` / `--api-header` are declared on `mcpjam cloud` and work before or after descendants. Hosted `readiness` stays at the root and still takes leaf `--api-key`. See [Migrating to CLI 4.0](/cli/migration). ## Global flags | Flag | Default | Description | | ------------------- | --------------------------------- | --------------------------------------------------------- | | `--timeout ` | `30000` | Request timeout in milliseconds | | `--rpc` | off | Include raw JSON-RPC logs in JSON output under `_rpcLogs` | | `--quiet` | off | Suppress non-result progress output on stderr | | `--no-telemetry` | off | Disable anonymous telemetry for this invocation | | `--format ` | `human` on TTY, `json` when piped | Raw output format (`json` or `human`) | | `-v, --version` | | Print the CLI version | *** ## `server` commands All server commands accept the shared connection flags below, plus command-specific options. ### Shared connection flags | Flag | Description | | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | | `--transport ` | Explicit transport type (`http` or `stdio`) | | `--url ` | HTTP MCP server URL | | `--access-token ` | Bearer access token | | `--oauth-access-token ` | OAuth bearer access token | | `--refresh-token ` | OAuth refresh token | | `--client-id ` | OAuth client ID (with `--refresh-token`) | | `--client-secret ` | OAuth client secret (with `--refresh-token`) | | `--credentials-file ` | Load OAuth credentials from a file created by `oauth login --credentials-out` or `oauth conformance --credentials-out` | | `--header
` | HTTP header `Key: Value` (repeatable) | | `--client-capabilities ` | Client capabilities as inline JSON, `@path`, or `-` for stdin | | `--command ` | Stdio server command | | `--args ` | Preferred stdio command arguments | | `--command-args ` | Legacy stdio command argument (repeatable) | | `-e, --env ` | Stdio environment `KEY=VALUE` values | | `--cwd ` | Working directory for the stdio child process | Transport selection is inferred from `--url` vs `--command` when `--transport` is omitted. Use `--transport http|stdio` when you want an explicit validation step. For stdio targets, child processes inherit the parent shell environment by default. `-e/--env` adds or overrides child env values, and structured debug artifacts only record the explicit env keys you passed on the command line. `--credentials-file` cannot be combined with individual token flags (`--access-token`, `--oauth-access-token`, `--refresh-token`, `--client-id`, `--client-secret`). The CLI rejects conflicting auth sources upfront. ### `server probe` No additional flags beyond shared connection flags. ### `server doctor` | Flag | Description | | -------------- | ---------------------------------------- | | `--out ` | Write the doctor JSON artifact to a file | ### `server info` No additional flags. ### `server validate` No additional flags. ### `server ping` No additional flags. ### `server capabilities` No additional flags. ### `server export` No additional flags. *** ## `tools` commands ### `tools list` Uses shared connection flags, plus: | Flag | Description | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--host ` | Connect as this host (e.g. `claude`, `chatgpt`, `cursor`) — sends its `clientInfo`, `clientCapabilities`, and protocol version in `initialize`, and hides app-only tools its model can't see. Mutually exclusive with `--client-capabilities`. | | `--cursor ` | Pagination cursor | | `--model-id ` | Model ID used for token counting | When `--host` is set, the output includes a `host` field and a `toolsDroppedVisibility` count. Hosts that opt out of visibility filtering (e.g. `cursor`) keep all tools. ### `tools call` | Flag | Description | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--host ` | Connect as this host — sends its identity/capabilities/protocol in `initialize`, and rejects app-only tools the host's model can't call. Mutually exclusive with `--client-capabilities`. | | `--tool-name ` | Name of the tool to call | | `--name ` | Legacy alias for `--tool-name` | | `--interactive` | Drive the `input_required` multi-round-trip loop: render embedded elicitations to the terminal and collect responses from stdin. Cannot be combined with `--tool-args -`, `--tool-args-stdin`, or `--params -`. | | `--yes` | With `--interactive`, decline every embedded input request without prompting (non-interactive mode). | | `--tool-args ` | Tool arguments as inline JSON, `@path`, or `-` for stdin | | `--params ` | Legacy alias for `--tool-args` | | `--tool-args-stdin` | Read tool arguments JSON from stdin | | `--validate-response` | Validate the MCP tool-call envelope returned by the server | | `--expect-success` | Fail when the tool result reports `isError` | | `--reporter ` | `json-summary`, `junit-xml`, or `html` validation report output | | `--debug-out ` | Write debug artifact to file | | `--ui` | Attach to Inspector and render the completed tool result in App Builder; opens a browser by default in a TTY | | `--require-render` | Treat skipped Inspector renders as errors (requires `--ui`) | | `--open` | Open Inspector in the system browser before rendering (default with `--ui` in a TTY) | | `--no-open` | Start/use Inspector without opening a system browser | | `--attach-only` | Require an already-running Inspector browser client; do not start or open Inspector | | `--inspector-url ` | Local Inspector backend/API base URL | | `--frontend-url ` | Inspector frontend/browser base URL; skips frontend discovery | | `--server-name ` | Server name to use inside Inspector | | `--protocol ` | Render protocol: `mcp-apps` or `openai-sdk` | | `--device ` | Render device: `mobile`, `tablet`, `desktop`, or `custom` | | `--theme ` | Render theme: `light` or `dark` | | `--locale ` | Render locale | | `--time-zone ` | Render IANA timezone | Plus shared connection flags. Without `--ui`, `tools call` returns the raw tool result. When that result is a JSON object (the usual MCP `CallToolResult`), the default JSON output also includes `_durationMs` — wall-clock milliseconds spent in the tool call itself, excluding connection setup and the `--host` visibility probe, so it measures the same window as the `durationMs` returned by `POST /v1/projects/{projectId}/servers/{serverId}/tools/call`. Arrays and non-objects are left unchanged so a list-shaped payload is not rewritten. `--reporter` keeps its own `durationMs`, which is end-to-end (connection setup plus the call, stopped before validation, Inspector render, and debug I/O), and does not get `_durationMs`. With `--ui`, it opens Inspector by default in a TTY and returns a compact envelope with `result`, `inspectorBrowserUrl`, and `inspectorRender` status. Object-shaped `result` values carry `_durationMs`; the envelope root does not. `inspectorRender.status` is `rendered` when Inspector accepted the render, `skipped` when the tool succeeded but Inspector had no active browser client, an unsatisfied render precondition, or a render timeout, and `error` for non-recoverable render command failures. `inspectorRender.remediation` is always present and is one of `open_browser`, `retry`, `reconnect_server`, or `none`. Skipped renders are emitted as a stable root `warning` plus `inspectorRender.warning`, both with the shape `{ code, message, remediation, browserUrl?, hasActiveClient?, inspectorStarted? }`. Stable skipped-render codes are `no_active_client`, `timeout`, `disconnected_server`, and `unsupported_in_mode`. Skipped renders keep the tool-call exit code unless `--require-render` is set; tool failures, validation failures, non-skippable render command errors, and `--require-render` skipped renders all exit nonzero. `--attach-only` is an exception to the skipped-render rule for `no_active_client`: by default a missing browser client yields `inspectorRender.status = "skipped"` with `inspectorRender.remediation = "open_browser"`, but when `--attach-only` is set, `no_active_client` is treated as non-skippable, surfaces as a root `error` (not a downgraded `warning`), and exits nonzero like other non-skippable render failures. `--inspector-url` points to the Inspector backend/API; pass `--frontend-url` when you already know the browser/client URL and want to skip health-advertised frontend checks and local dev port discovery. Use `--no-open` when browser automation already opened `inspectorBrowserUrl`; use `--attach-only` when startup, browser opening, and discovery should all be disallowed. Default non-TTY `--ui` runs do not open a browser unless `--open` is passed. When `--open` is in effect (default in TTYs, opt-in elsewhere), the App Builder URL and the initial browser-client wait progress are emitted to stderr unless `--quiet` is set, regardless of whether stderr is a TTY; only the elapsed-seconds heartbeat is gated on stderr being a TTY. The Inspector path injects the completed tool result through `renderToolResult`; it does not call the tool a second time. Fresh tabs do not hydrate the injected render state; use the active Inspector client that received the render. Use `--debug-out` for the full render envelope including params and command responses. `--ui` cannot be combined with `--reporter`. ### Reading `tools call --ui` output as an agent Treat the tool result and the Inspector render as separate outcomes. An exit code of `0` means the tool call succeeded and no hard render error occurred; it does not, by itself, prove the UI rendered. Confirm UI delivery with `inspectorRender.status === "rendered"`. If `inspectorRender.status === "skipped"`, branch on `inspectorRender.remediation` or the stable root `warning.code`. If `--require-render` is set, the same skipped-render issue moves from root `warning` to root `error` and the command exits with code `1`. ```json theme={"theme":"css-variables"} { "success": true, "command": "tools call", "inspectorUi": true, "inspectorBrowserUrl": "http://127.0.0.1:6274/#app-builder", "result": { "content": [{ "type": "text", "text": "view created" }], "_durationMs": 42 }, "inspectorRender": { "status": "skipped", "remediation": "open_browser", "mode": "active-client", "urlHydratesRender": false, "browserUrl": "http://127.0.0.1:6274/#app-builder", "hasActiveClient": false, "inspectorStarted": false, "warning": { "code": "no_active_client", "message": "Inspector has no active browser client. Open the Inspector App Builder URL in your browser, then rerun `tools call --ui`; or pass `--open` to let the CLI open a system browser.", "remediation": "open_browser", "browserUrl": "http://127.0.0.1:6274/#app-builder", "hasActiveClient": false, "inspectorStarted": false } }, "warning": { "code": "no_active_client", "message": "Inspector has no active browser client. Open the Inspector App Builder URL in your browser, then rerun `tools call --ui`; or pass `--open` to let the CLI open a system browser.", "remediation": "open_browser", "browserUrl": "http://127.0.0.1:6274/#app-builder", "hasActiveClient": false, "inspectorStarted": false } } ``` *** ## `resources` commands ### `resources list` Uses shared connection flags, plus: | Flag | Description | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `--host ` | Connect as this host — sends its identity/capabilities/protocol in `initialize`. Mutually exclusive with `--client-capabilities`. | | `--cursor ` | Pagination cursor | ### `resources read` | Flag | Description | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `--host ` | Connect as this host — sends its identity/capabilities/protocol in `initialize`. Mutually exclusive with `--client-capabilities`. | | `--resource-uri ` | URI of the resource to read | | `--uri ` | Legacy alias for `--resource-uri` | | `--interactive` | Drive the `input_required` multi-round-trip loop: render embedded elicitations to the terminal and collect responses from stdin. | | `--yes` | With `--interactive`, decline every embedded input request without prompting (non-interactive mode). | Plus shared connection flags. ### `resources templates` Uses shared connection flags, plus: | Flag | Description | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `--host ` | Connect as this host — sends its identity/capabilities/protocol in `initialize`. Mutually exclusive with `--client-capabilities`. | | `--cursor ` | Pagination cursor | *** ## `prompts` commands ### `prompts list` Uses shared connection flags, plus: | Flag | Description | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `--host ` | Connect as this host — sends its identity/capabilities/protocol in `initialize`. Mutually exclusive with `--client-capabilities`. | | `--cursor ` | Pagination cursor | ### `prompts get` | Flag | Description | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--host ` | Connect as this host — sends its identity/capabilities/protocol in `initialize`. Mutually exclusive with `--client-capabilities`. | | `--prompt-name ` | Name of the prompt | | `--name ` | Legacy alias for `--prompt-name` | | `--interactive` | Drive the `input_required` multi-round-trip loop: render embedded elicitations to the terminal and collect responses from stdin. Cannot be combined with `--prompt-args -`. | | `--yes` | With `--interactive`, decline every embedded input request without prompting (non-interactive mode). | | `--prompt-args ` | Prompt arguments as inline JSON, `@path`, or `-` for stdin | Plus shared connection flags. *** ## `xaa` commands ### `xaa run` Run the Cross-App Access (ID-JAG) debugger: self-issue an ID-JAG, redeem it at the target authorization server (RFC 7523), and call the MCP server with the resulting access token. See the [XAA Debugger guide](/cli/xaa) for the trust setup, registration strategies, and CI usage. | Flag | Required | Default | Description | | --------------------------------------- | -------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `--url ` | Yes | | Target MCP server URL (the protected resource) | | `--issuer-base-url ` | Yes | | Origin the local mock IdP issues from. It must publish the CLI's local signing key — typically a running [local inspector](/cli/xaa#before-you-run-make-the-issuer-reachable), not your real IdP | | `--sub ` | Yes | | Simulated end-user subject identifier | | `--client-id ` | No | | OAuth client ID. Required for `preregistered`; rejected for `dcr`/`cimd` | | `--registration ` | No | `preregistered` | `preregistered`, `dcr`, or `cimd` | | `--client-metadata-url ` | No | | CIMD only: the Client ID Metadata Document URL to present as the `client_id` | | `--client-auth ` | No | `none` | CIMD only: `none` (public) or `private-key-jwt` (confidential — generates a local EC P-256 key, publishes it via the hosted reflector, and signs a `client_assertion`) | | `--cimd-metadata-origin ` | No | `https://app.mcpjam.com` | Confidential CIMD only: bare origin hosting the metadata-document reflector. An `http://` loopback origin is a dev-only opt-in; rejected when `--https-only` is set | | `--authz-server-issuer ` | No | | Target AS issuer. When set, protected-resource metadata discovery is skipped | | `--token-endpoint ` | No | | AS token endpoint. When set, AS-metadata discovery is skipped. Not valid with `dcr` or `cimd` | | `--email ` | No | | Simulated end-user email claim | | `--client-secret ` | No | | OAuth client secret presented at redemption | | `--token-endpoint-auth-method ` | No | | `client_secret_basic`, `client_secret_post`, or `none`. Not valid with `dcr` or `cimd` | | `--assertion-format ` | No | `oidc` | Identity assertion format: `oidc` (ID token) or `saml` (SAML 2.0 assertion) | | `--scopes ` | No | | Space-separated scope string | | `--https-only` | No | | Reject non-HTTPS / private targets | #### Registration strategies | Strategy | Description | | --------------- | --------------------------------------------------------------------------------------- | | `preregistered` | Supply a pre-registered `--client-id` (and optional `--client-secret`) | | `dcr` | Dynamic Client Registration (RFC 7591) — the CLI registers a client at the RAS each run | | `cimd` | Client ID Metadata Document — the client publishes its identity at a URL | #### Confidential CIMD (`--client-auth private-key-jwt`) When `--registration cimd --client-auth private-key-jwt` is used, the CLI loads or generates a local EC P-256 key pair (stored in `~/.mcpjam`). The public key is encoded into a reflector URL on `app.mcpjam.com`, which becomes the `client_id`. The CLI signs a `client_assertion` with the private key at token-redemption time; the private key never leaves the machine. ```bash theme={"theme":"css-variables"} mcpjam inspector start # serves the issuer at http://localhost:6274/api/mcp/xaa mcpjam xaa run \ --url http://localhost:8080/mcp \ --issuer-base-url http://localhost:6274/api/mcp \ --sub user@example.com \ --registration cimd \ --client-auth private-key-jwt ``` For a cloud authorization server, expose the inspector origin through a tunnel and pass the public origin as `--issuer-base-url` — see [making the issuer reachable](/cli/xaa#before-you-run-make-the-issuer-reachable). **Key rotation:** the key is the identity. Deleting `~/.mcpjam/xaa-client-private.pem` or changing the `XAA_CLIENT_PRIVATE_KEY` environment variable generates a new `client_id`; any RAS-side allowlisting must be updated after rotation. *** ## `oauth` commands ### `oauth login` | Flag | Required | Default | Description | | ----------------------------- | -------- | -------------- | ------------------------------------------------------------------------ | | `--url ` | Yes | | MCP server URL | | `--protocol-version ` | Yes | | `2025-03-26`, `2025-06-18`, or `2025-11-25` | | `--registration ` | Yes | | `cimd`, `dcr`, or `preregistered` | | `--auth-mode ` | No | `interactive` | `headless`, `interactive`, or `client_credentials` | | `--client-id ` | No | | OAuth client ID | | `--client-secret ` | No | | OAuth client secret | | `--client-metadata-url ` | No | | CIMD metadata document URL | | `--redirect-url ` | No | Auto-generated | OAuth redirect URL | | `--scopes ` | No | | Space-separated scope string | | `--header
` | No | | HTTP header `Key: Value` (repeatable) | | `--step-timeout ` | No | `30000` | Per-step timeout | | `--verify-tools` | No | | After login, list tools | | `--verify-call-tool ` | No | | Also call the named tool | | `--credentials-out ` | No | | Write OAuth credentials to file (mode 0600); stdout has secrets redacted | | `--debug-out ` | No | | Write debug artifact to file | ### `oauth conformance` | Flag | Required | Default | Description | | ----------------------------- | -------- | -------------- | ---------------------------------------------------------------------------------------------------- | | `--url ` | Yes | | MCP server URL | | `--protocol-version ` | Yes | | `2025-03-26`, `2025-06-18`, or `2025-11-25` | | `--registration ` | Yes | | `cimd`, `dcr`, or `preregistered` | | `--auth-mode ` | No | `interactive` | `headless`, `interactive`, or `client_credentials` | | `--client-id ` | No | | OAuth client ID | | `--client-secret ` | No | | OAuth client secret | | `--client-metadata-url ` | No | | CIMD metadata document URL | | `--redirect-url ` | No | Auto-generated | OAuth redirect URL | | `--scopes ` | No | | Space-separated scope string | | `--header
` | No | | HTTP header `Key: Value` (repeatable) | | `--step-timeout ` | No | `30000` | Per-step timeout | | `--verify-tools` | No | | After OAuth, list tools | | `--verify-call-tool ` | No | | Also call the named tool | | `--conformance-checks` | No | | Run additional negative OAuth checks, including DCR redirect URI policy and redirect-mismatch probes | | `--credentials-out ` | No | | Write OAuth credentials to file (mode 0600); stdout has secrets redacted | | `--print-url` | No | | Print consent URL to stderr (interactive only) | | `--reporter ` | No | | `json-summary` or `junit-xml` CI report output | ### `oauth conformance-suite` | Flag | Required | Default | Description | | --------------------------- | -------- | ------- | ---------------------------------------------------------------------------------------- | | `--config ` | Yes | | Path to JSON config file | | `--verify-tools` | No | | Enable tool listing on all flows | | `--verify-call-tool ` | No | | Call the named tool after listing | | `--credentials-out ` | No | | Write OAuth credentials from the first flow that returns credentials to file (mode 0600) | | `--reporter ` | No | | `json-summary` or `junit-xml` CI report output | ### `oauth metadata` | Flag | Required | Description | | -------------- | -------- | ------------------------------------------------------------- | | `--url ` | Yes | OAuth metadata URL to fetch | | `--https-only` | No | Apply the hosted policy: reject non-HTTPS and private targets | ### `oauth proxy` / `oauth debug-proxy` | Flag | Required | Default | Description | | ------------------- | -------- | ------- | ------------------------------------------------------------- | | `--url ` | Yes | | OAuth request URL | | `--method ` | No | `GET` | HTTP method | | `--header
` | No | | HTTP header `Key: Value` (repeatable) | | `--body ` | No | | Request body as JSON, raw string, `@path`, or `-` for stdin | | `--https-only` | No | | Apply the hosted policy: reject non-HTTPS and private targets | These three commands reach private destinations by default — a server on loopback, on your LAN, or on a hostname that resolves to one — because the CLI runs on your own machine. `--https-only` opts in to the policy the hosted web app enforces, which is what you want when reproducing a hosted failure. Neither mode will dial a link-local or cloud-metadata address. *** ## `protocol` commands ### `protocol conformance` | Flag | Required | Default | Description | | --------------------------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------- | | `--url ` | Yes | | MCP server URL | | `--access-token ` | No | | Bearer access token | | `--credentials-file ` | No | | Load OAuth credentials from a file created by `oauth login --credentials-out` or `oauth conformance --credentials-out` | | `--header
` | No | | HTTP header `Key: Value` (repeatable) | | `--check-timeout ` | No | `15000` | Per-check timeout in milliseconds | | `--category ` | No | all | Restrict checks to one or more categories | | `--check-id ` | No | all | Restrict checks to one or more check IDs | | `--reporter ` | No | | `json-summary` or `junit-xml` CI report output | Use `--format json|human` for raw output and `--reporter json-summary|junit-xml` for CI reports. ### `protocol conformance-suite` | Flag | Required | Default | Description | | ----------------------- | -------- | ------- | ---------------------------------------------- | | `--config ` | Yes | | Path to JSON config file | | `--reporter ` | No | | `json-summary` or `junit-xml` CI report output | *** ## `apps` commands ### Shared connection flags | Flag | Description | | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | | `--transport ` | Explicit transport type (`http` or `stdio`) | | `--url ` | HTTP MCP server URL | | `--access-token ` | Bearer access token | | `--oauth-access-token ` | OAuth bearer access token | | `--refresh-token ` | OAuth refresh token | | `--client-id ` | OAuth client ID (with `--refresh-token`) | | `--client-secret ` | OAuth client secret (with `--refresh-token`) | | `--credentials-file ` | Load OAuth credentials from a file created by `oauth login --credentials-out` or `oauth conformance --credentials-out` | | `--header
` | HTTP header `Key: Value` (repeatable) | | `--client-capabilities ` | Client capabilities as inline JSON, `@path`, or `-` for stdin | | `--command ` | Stdio server command | | `--args ` | Preferred stdio command arguments | | `--command-args ` | Legacy stdio command argument (repeatable) | | `-e, --env ` | Stdio environment `KEY=VALUE` values | | `--cwd ` | Working directory for the stdio child process | Apps commands share the same transport inference rules as the rest of the CLI: `--url` implies HTTP, `--command` implies stdio, and `--transport` is an optional explicit override. ### `apps conformance` MCP Apps server-side conformance checks. Uses shared connection flags plus: | Flag | Description | | ----------------------- | -------------------------------------------------------- | | `--category ` | Check category to run (`tools`, `resources`). Repeatable | | `--check-id ` | Specific check id to run. Repeatable | | `--reporter ` | `json-summary` or `junit-xml` CI report output | Use `--format json|human` for raw output and `--reporter json-summary|junit-xml` for CI reports. ### `apps conformance-suite` | Flag | Required | Default | Description | | ----------------------- | -------- | ------- | ---------------------------------------------- | | `--config ` | Yes | | Path to JSON config file | | `--reporter ` | No | | `json-summary` or `junit-xml` CI report output | *** ## `compat` command Check whether an MCP server's tools and widgets work on each AI host. Uses shared server connection flags (see [`server` commands](#server-commands)) plus: | Flag | Description | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `--host ` | Only report this host ID. Repeat for several. Default: all. Valid IDs: `claude`, `chatgpt`, `cursor`, `copilot`, `codex`, `goose`, and others. | ```bash theme={"theme":"css-variables"} mcpjam compat --url https://your-server.com/mcp mcpjam compat --url https://your-server.com/mcp --host claude --host chatgpt ``` The result includes a per-host `verdict` (`works`, `degraded`, `blocked`, or `unknown`), a `findings` list explaining each verdict, and a top-level `summary` count. When the tool list is truncated (more than 50 pages), verdicts that would otherwise be `works` are downgraded to `unknown`. *** ## `cloud clients` commands Manage the clients saved in your hosted MCPJam projects — the named, reusable configurations that define how MCPJam connects to and talks to your MCP servers. Every `clients` command that contacts Cloud requires an `sk_` API key (via `--api-key` or the `MCPJAM_API_KEY` environment variable) or a prior `mcpjam cloud login`. `clients templates` is the exception: it prints the built-in template list from the bundled SDK and needs no credential. `mcpjam cloud hosts …` still works as a command alias, and every command accepts `--host` as a deprecated spelling of `--client`. Passing both `--client` and `--host` is a usage error rather than a silent precedence rule. ### Editing safely: `configId` and `expectedConfigId` A client's config is immutable and content-addressed: `configId` identifies the exact settings, so the same id means byte-identical settings. Every config edit takes the `configId` you last read as `expectedConfigId`, and a rename takes the `name` you last read as `expectedName`. If someone else edited the client since you read it, the write is refused with a `409` naming the current value instead of silently reverting their edit. The loop is always the same: ```bash theme={"theme":"css-variables"} mcpjam cloud clients get --client "Claude" # note configId + name mcpjam cloud clients update --client "Claude" \ --set temperature=0.2 --expected-config-id hc_01J8... ``` On a conflict, re-run `get` and retry with the fresh value. ### Shared platform flags | Flag | Description | | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--api-key ` | MCPJam `sk_` API key (overrides `MCPJAM_API_KEY`) | | `--api-url ` | MCPJam API base URL (defaults to `https://app.mcpjam.com/api/v1`) | | `--api-header ` | Extra request header as `"Name: value"`, repeatable — for a deployment behind Cloudflare Access, a WAF, or a corporate proxy. Also `MCPJAM_API_HEADERS`, one per line. Cannot set `authorization`, `idempotency-key`, or `content-type`. | ### `cloud clients templates` List the built-in templates usable with `mcpjam cloud clients create --template`. No additional flags. ### `cloud clients list` | Flag | Description | | ------------------------ | ------------------------------------------------------------------ | | `--project ` | Project name or ID (defaults to the most recently updated project) | Clients that exist only as the private backing of a User Testing scenario are omitted, matching the Clients tab. ### `cloud clients get` | Flag | Required | Description | | ------------------------ | -------- | -------------------------------------------------- | | `--client ` | Yes | Client name or ID (`--host` is a deprecated alias) | | `--project ` | No | Project name or ID | Returns the resolved config, the `configId` every edit takes, and `impact` — the live environments, scenario attachments and active legacy journeys a config edit would follow. ### `cloud clients create` | Flag | Required | Description | | ------------------------ | -------- | ---------------------------------------------------------------------------------- | | `--name ` | Yes | Display name for the new client | | `--project ` | No | Project name or ID | | `--template ` | No | Built-in template ID (see `clients templates`), e.g. `claude`, `chatgpt`, `cursor` | | `--theme ` | No | Theme for the seeded config: `light` or `dark` (template only) | | `--file ` | No | Client config v2 JSON file (or `-` for stdin) | | `--json ` | No | Inline client config v2 JSON (or `@file`, or `-`) | Provide either `--template` or `--file`/`--json`, not both. ### `cloud clients update` | Flag | Required | Description | | --------------------------- | -------------------- | --------------------------------------------------------------------------- | | `--client ` | Yes | Client name or ID (`--host` is a deprecated alias) | | `--project ` | No | Project name or ID | | `--name ` | No | New display name (requires `--expected-name`) | | `--expected-name ` | With `--name` | The `name` you last read | | `--expected-config-id ` | With any config edit | The `configId` you last read | | `--set ` | No | Set one field. Repeatable | | `--unset ` | No | Clear an optional field, or reset a required one to its default. Repeatable | | `--file ` | No | Replacement client config v2 JSON (or `-` for stdin) | | `--json ` | No | Inline replacement client config v2 JSON (or `@file`, or `-`) | Use either `--set`/`--unset` or `--file`/`--json`, not both. Prefer `--set`: a whole-config replacement composed from a stale read reverts anything that landed in between. #### `--set` field types Values are parsed by the FIELD, never guessed from what the value looks like — so `--set systemPrompt='{"a":1}'` sets that literal string, and a `--set temperature=0.2x` typo fails locally instead of reaching the API as a string. | Field | Value | `--unset` | | ----------------------------- | --------------------------------- | ------------------------------------------------------------------- | | `modelId` | string | Not allowed — a client cannot be edited into one that pins no model | | `systemPrompt` | string | Resets to `""` | | `temperature` | number | Resets to `0.7` | | `requireToolApproval` | `true` \| `false` | Resets to `false` | | `connectionDefaults` | JSON object | Resets to the platform defaults | | `respectToolVisibility` | `true` \| `false` | Clears | | `progressiveToolDiscovery` | `true` \| `false` | Clears | | `harness` | string (`claude-code` \| `codex`) | Clears | | `computer` | JSON object | Clears | | `builtInToolIds` | JSON array | Clears | | `skillSelection` | JSON object | Clears | | `modelVisibleMcpToolResults` | JSON object | Clears | | `mcpToolResultImageRendering` | JSON object | Clears | | `mcpProfile` | JSON object | Clears | | `hostCapabilitiesOverride` | JSON object | Clears | | `chatUiOverride` | JSON object | Clears | Object-valued fields are whole-object replacements, not merges. To change a deep knob, read the client, overlay the sub-object, and send the whole object back. ### `cloud clients servers` | Flag | Required | Description | | -------------------------------- | -------- | -------------------------------------------------- | | `--client ` | Yes | Client name or ID (`--host` is a deprecated alias) | | `--server-ids ` | Yes | Comma-separated required server IDs | | `--expected-config-id ` | Yes | The `configId` you last read | | `--optional-server-ids ` | No | Comma-separated optional server IDs | | `--project ` | No | Project name or ID | This REPLACES the server set: every server you leave out is detached. ### `cloud clients duplicate` | Flag | Required | Description | | ------------------------ | -------- | -------------------------------------------------- | | `--client ` | Yes | Client name or ID (`--host` is a deprecated alias) | | `--name ` | No | Name for the new client | | `--project ` | No | Project name or ID | ### `cloud clients delete` | Flag | Required | Description | | ------------------------ | -------- | -------------------------------------------------- | | `--client ` | Yes | Client name or ID (`--host` is a deprecated alias) | | `--project ` | No | Project name or ID | *** ## `cloud environments` commands Manage **project environments** in your hosted MCPJam projects. A project environment is a named, live-editable execution bundle — one host, optionally a standalone server group, optionally a pinned skill selection and pinned plugin versions — that eval suites and journeys run against. A project environment is not a Computer sandbox image (those are `mcpjam cloud images`), and not the STDIO environment variables you pass with `--env`. All `environments` commands require an `sk_` API key (via `--api-key` or the `MCPJAM_API_KEY` environment variable) or a prior `mcpjam cloud login`. Reading requires project membership; **creating, updating, archiving, and restoring require project admin** — a key bound to a non-admin gets a `FORBIDDEN` error on those. ### Shared platform flags | Flag | Description | | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--api-key ` | MCPJam `sk_` API key (overrides `MCPJAM_API_KEY`) | | `--api-url ` | MCPJam API base URL (defaults to `https://app.mcpjam.com/api/v1`) | | `--api-header ` | Extra request header as `"Name: value"`, repeatable — for a deployment behind Cloudflare Access, a WAF, or a corporate proxy. Also `MCPJAM_API_HEADERS`, one per line. Cannot set `authorization`, `idempotency-key`, or `content-type`. | ### The revision workflow Environments use optimistic concurrency. Every write takes `--expected-revision`, the `revision` you last read: ```bash theme={"theme":"css-variables"} mcpjam cloud environments get --environment Staging # note the "revision" field mcpjam cloud environments update --environment Staging --expected-revision 3 --name Prod ``` If someone else changed the environment in between, the write fails with a `CONFLICT` error (HTTP 409) instead of overwriting their edit — re-read it and retry. `CONFLICT` also covers a duplicate name and archive-state errors (archiving something already archived, editing something archived), so read the message. ### `cloud environments list` | Flag | Description | | ------------------------ | ------------------------------------------------------------------ | | `--project ` | Project name or ID (defaults to the most recently updated project) | | `--include-archived` | Include archived environments (needed to find one to restore) | ### `cloud environments get` | Flag | Required | Description | | ---------------------------- | -------- | ---------------------- | | `--environment ` | Yes | Environment name or ID | | `--project ` | No | Project name or ID | ### `cloud environments resolve` Preview what the environment resolves to right now: the host's current config, the closed server set, and the pinned plugin versions. Fails with `CONFLICT` when the environment can't currently produce a runnable configuration (for example a pinned plugin was disabled); `details.code` carries the specific reason. | Flag | Required | Description | | ---------------------------- | -------- | ---------------------- | | `--environment ` | Yes | Environment name or ID | | `--project ` | No | Project name or ID | ### `cloud environments create` | Flag | Required | Description | | ------------------------ | -------- | ------------------------------------------------------------------ | | `--name ` | Yes\* | Display name; must be unique among the project's live environments | | `--host-id ` | Yes\* | ID of the host this environment runs against | | `--project ` | No | Project name or ID | | `--description ` | No | Optional description | | `--file ` | No | Environment JSON file (or `-` for stdin) | | `--json ` | No | Inline environment JSON (or `@file`, or `-`) | \* `--name` and `--host-id` may instead be supplied inside `--file`/`--json`; explicit flags override the same key in the JSON body. Use the JSON body for the structured fields that have no flag — `serverAttachmentId`, `skillSelection`, `secretSelection`, and `pluginVersionIds`: ```bash theme={"theme":"css-variables"} mcpjam cloud environments create --project Acme --json '{ "name": "Staging", "hostId": "h_123", "skillSelection": { "mode": "explicit", "skillIds": ["sk_1"] } }' ``` ### `cloud environments update` | Flag | Required | Description | | ---------------------------- | -------- | ------------------------------------------------------------- | | `--environment ` | Yes | Environment name or ID | | `--expected-revision ` | Yes | The revision you last read (from `environments get`) | | `--project ` | No | Project name or ID | | `--name ` | No | New display name | | `--host-id ` | No | New host | | `--description ` | No | New description (empty string clears it) | | `--file ` | No | Environment JSON with the fields to change (or `-` for stdin) | | `--json ` | No | Inline environment JSON (or `@file`, or `-`) | Only the fields you pass change. To **clear** `serverAttachmentId`, `skillSelection`, `secretSelection`, or `pluginVersionIds`, send an explicit `null` in the JSON body — an empty array is rejected, it is not a way to clear: ```bash theme={"theme":"css-variables"} mcpjam cloud environments update --environment Staging --expected-revision 3 \ --json '{ "pluginVersionIds": null }' ``` ### `cloud environments archive` Archiving is reversible and frees the name for a new environment; the row is kept. | Flag | Required | Description | | ---------------------------- | -------- | -------------------------- | | `--environment ` | Yes | Environment name or ID | | `--expected-revision ` | Yes | The revision you last read | | `--project ` | No | Project name or ID | ### `cloud environments restore` Fails with `CONFLICT` if another live environment took the name while this one was archived. Plugin pins whose version no longer exists at all are dropped on restore — compare the returned `pluginVersionIds` against what you archived. | Flag | Required | Description | | ---------------------------- | -------- | ----------------------------------------------------------------------- | | `--environment ` | Yes | Environment name or ID | | `--expected-revision ` | Yes | The revision you last read (use `environments list --include-archived`) | | `--project ` | No | Project name or ID | ### `cloud environments ensure-adhoc` Get or create an **unnamed** environment for a composed stack. Deduplicated by content: the same stack always returns the same environment, with `created: false` on the second call. Ad-hoc environments do not appear in `environments list` — they exist so a one-off combination can be run and reproduced without adding a name to the project's list. Requires project membership, not admin (pinning plugin versions still requires admin). | Flag | Required | Description | | ------------------------- | -------- | ----------------------------------------------------------------------------- | | `--host ` | Yes | Host the stack runs as — the client whose configuration a run is stamped with | | `--server-group ` | No | Standalone server group to pin (omit to use the host's own servers) | | `--model ` | No | Model to run instead of the host's pinned one | | `--computer ` | No | Project-shared sandbox image to pin, so runs boot a fresh computer from it | | `--skill ` | No | Project-shared skill IDs to pin | | `--secret ` | No | Project secret IDs the stack grants to its runs (`secrets list` shows them) | | `--project ` | No | Project name or ID | ### `cloud environments name` Promote an ad-hoc environment to a named one **in place** — the same id every existing run already points at, so history stays attached. Fails with `CONFLICT` if the environment already has a name. | Flag | Required | Description | | ------------------------- | -------- | --------------------------------------------------------------------------------------------- | | `--environment ` | Yes | The ad-hoc environment to promote, by ID (an unnamed environment has no name to select it by) | | `--name ` | Yes | Display name for the promoted environment | | `--expected-revision ` | Yes | The revision you last read | | `--description ` | No | Optional description | | `--project ` | No | Project name or ID | ### What can be pinned The pin fields are narrower than they look: * **`skillSelection`** accepts project-shared skills only. Personal skills, skills with supporting files or extra frontmatter, and plugin-component skills are all rejected. * **`pluginVersionIds`** accepts a plugin version only when its plugin is installed and enabled, the version is `ready`, at most one version per plugin is pinned, and none of the version's skills carry supporting files. * **`secretSelection`** is a GRANT, not a pin: the environment is the boundary, so a run receives the secrets it lists and nothing else. There is no "all of them" mode, an empty list is rejected (revoke with `"secretSelection": null` on update), and a `sharing: "user"` secret still reaches only sessions its owner started. ### Deployment support for model overrides and secret grants `--model` / `"modelId"` and `--secret` / `"secretSelection"` are answered by the deployment, not by your account: a self-hosted or older MCPJam whose environment surface predates a field rejects it outright. The CLI asks first — `GET /projects/:id/environments/capabilities` reports `modelOverrides` and `secretGrants` — and refuses before sending, rather than letting the field die in a validator. Absence reads as a no, so a deployment too old to answer the probe at all is treated as not supporting either. The refusal opens with one of these two sentences: * **This MCPJam deployment does not support environment model overrides.** * **This MCPJam deployment does not support environment secret grants.** A second sentence then names what to drop, and it differs by surface — `--model` / `--clear-model` / `"modelId"` on the environment writes, `--compose-model` / `compose.models` on a composed launch, and the `--secret` / `--compose-secret` equivalents. Grep for the sentence above, not for the whole message. Every other flag on these commands works regardless; only the calls that actually carry a model or a grant are checked. *** ## `cloud images` commands Manage custom Computer sandbox images in your hosted MCPJam projects. An image is defined by a **blueprint** — YAML with a digest-pinned `base` image, `initialize` steps baked into the image at build time, and `maintenance` / `knowledge` text delivered to the agent at runtime (never executed automatically): ```yaml theme={"theme":"css-variables"} base: debian:bookworm-slim@sha256:<64-hex-digest> initialize: - name: Install tooling run: apt-get update && apt-get install -y git jq maintenance: - name: Refresh deps run: cd ~/app && npm install knowledge: - name: Test notes contents: Run `make test` before pushing. ``` All `images` commands require an `sk_` API key (via `--api-key` or the `MCPJAM_API_KEY` environment variable) or a prior `mcpjam cloud login`. ### Shared platform flags | Flag | Description | | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--api-key ` | MCPJam `sk_` API key (overrides `MCPJAM_API_KEY`) | | `--api-url ` | MCPJam API base URL (defaults to `https://app.mcpjam.com/api/v1`) | | `--api-header ` | Extra request header as `"Name: value"`, repeatable — for a deployment behind Cloudflare Access, a WAF, or a corporate proxy. Also `MCPJAM_API_HEADERS`, one per line. Cannot set `authorization`, `idempotency-key`, or `content-type`. | ### `cloud images list` | Flag | Description | | ------------------------ | ------------------------------------------------------------------ | | `--project ` | Project name or ID (defaults to the most recently updated project) | ### `cloud images get` | Flag | Required | Description | | ------------------------ | -------- | ------------------------ | | `--image ` | Yes | Sandbox image name or ID | | `--project ` | No | Project name or ID | ### `cloud images validate` Lint a blueprint without saving it. Prints `ok` plus the resolved base digest, or structured errors with the YAML path of each violation. | Flag | Required | Description | | ------------------------ | -------- | ---------------------------------------------- | | `--file ` | Yes | Blueprint YAML path, or `-` to read from stdin | | `--project ` | No | Project name or ID | ### `cloud images create` | Flag | Required | Description | | ------------------------ | -------- | ---------------------------------------------- | | `--name ` | Yes | Display name for the new sandbox image | | `--file ` | Yes | Blueprint YAML path, or `-` to read from stdin | | `--project ` | No | Project name or ID | ### `cloud images edit` | Flag | Required | Description | | ------------------------ | -------- | -------------------------------------------------- | | `--image ` | Yes | Sandbox image name or ID | | `--project ` | No | Project name or ID | | `--name ` | No | New display name | | `--file ` | No | Replacement blueprint YAML path (or `-` for stdin) | ### `cloud images build` Trigger an image build for the sandbox image (async). Poll `images logs` to check build status. | Flag | Required | Description | | ------------------------ | -------- | ------------------------ | | `--image ` | Yes | Sandbox image name or ID | | `--project ` | No | Project name or ID | ### `cloud images logs` Show a sandbox image's builds (newest first) with their log preview. | Flag | Required | Description | | ------------------------ | -------- | ------------------------ | | `--image ` | Yes | Sandbox image name or ID | | `--project ` | No | Project name or ID | ### `cloud images use` Boot your computer from this sandbox image. This rebuilds the computer — installed files are wiped. | Flag | Required | Description | | ------------------------ | -------- | ------------------------ | | `--image ` | Yes | Sandbox image name or ID | | `--project ` | No | Project name or ID | ### `cloud images reset` Reset your computer to its current image, wiping mutable state. | Flag | Required | Description | | ------------------------ | -------- | ------------------ | | `--project ` | No | Project name or ID | ### `cloud images promote` Share a personal-draft sandbox image with the whole project. Requires project admin permissions. | Flag | Required | Description | | ------------------------ | -------- | ------------------------ | | `--image ` | Yes | Sandbox image name or ID | | `--project ` | No | Project name or ID | ### `cloud images delete` Permanently delete a sandbox image from a project. | Flag | Required | Description | | ------------------------ | -------- | ------------------------ | | `--image ` | Yes | Sandbox image name or ID | | `--project ` | No | Project name or ID | *** ## `cloud skills` commands List and read the Cloud Skills in your hosted MCPJam projects. These commands exist to close a gap: three flags across the CLI take a skill ID (`eval run --compose-skill`, `eval cases run --compose-skill`, `environments ensure-adhoc --skill`) and until now there was no way to discover those IDs without opening the web app. Authoring stays in the app — these commands are read-only. All `skills` commands require an `sk_` API key (via `--api-key` or the `MCPJAM_API_KEY` environment variable) or a prior `mcpjam cloud login`. ### Shared platform flags | Flag | Description | | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--api-key ` | MCPJam `sk_` API key (overrides `MCPJAM_API_KEY`) | | `--api-url ` | MCPJam API base URL (defaults to `https://app.mcpjam.com/api/v1`) | | `--api-header ` | Extra request header as `"Name: value"`, repeatable — for a deployment behind Cloudflare Access, a WAF, or a corporate proxy. Also `MCPJAM_API_HEADERS`, one per line. Cannot set `authorization`, `idempotency-key`, or `content-type`. | ### `cloud skills list` List the skills visible to you in a project — the project-shared ones plus your own personal drafts. Each row includes a `pinnability` field that says whether the skill's ID is usable with `--compose-skill` and environment `skillSelection`, and why not if it isn't. | Flag | Description | | ------------------------ | ------------------------------------------------------------------ | | `--project ` | Project name or ID (defaults to the most recently updated project) | ### `cloud skills get` Show one skill, including its SKILL.md body. | Flag | Required | Description | | ------------------------ | -------- | ---------------------------------- | | `--skill ` | Yes | Skill ID, from `cloud skills list` | | `--project ` | No | Project name or ID | *** ## `cloud projects servers` commands Manage the MCP servers saved in a project and connect new ones. All `projects servers` commands require an `sk_` API key (via `--api-key` or the `MCPJAM_API_KEY` environment variable) or a prior `mcpjam cloud login`. ### Shared platform flags | Flag | Description | | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--api-key ` | MCPJam `sk_` API key (overrides `MCPJAM_API_KEY`) | | `--api-url ` | MCPJam API base URL (defaults to `https://app.mcpjam.com/api/v1`) | | `--api-header ` | Extra request header as `"Name: value"`, repeatable — for a deployment behind Cloudflare Access, a WAF, or a corporate proxy. Also `MCPJAM_API_HEADERS`, one per line. Cannot set `authorization`, `idempotency-key`, or `content-type`. | ### `cloud projects servers connect` Start a connection request for an MCP server URL. Discovery, browser consent, and validation happen asynchronously. When the server requires OAuth, the command prints a private authorization link and (by default) opens it in a browser. The command then polls until the request reaches a terminal status. | Flag | Required | Description | | ------------------------ | -------- | ------------------------------------------------------------ | | `--url ` | Yes | MCP server URL to connect | | `--project ` | No | Project name or ID | | `--server ` | No | Existing server ID, when the project has several on this URL | | `--name ` | No | Name for the server, if one is created | | `--reauthorize` | No | Force a fresh authorization | | `--no-browser` | No | Print the authorization link instead of opening it | | `--no-wait` | No | Return as soon as the request is created | With `--no-wait`, the command prints the request id and exits immediately. The request continues in the cloud; use `connect-status` to check it and `connect-cancel` to stop it. Each pending request holds one of the account's five concurrent-connection slots for up to an hour. If `connect` reports `ACTIVE_REQUEST_LIMIT`, the error details include the ids of the open requests — cancel the abandoned ones with `connect-cancel` to free slots. ### `cloud projects servers connect-status` Check the current state of a connection request started by `connect`. | Flag | Required | Description | | ---------------- | -------- | ------------------------------- | | `--request ` | Yes | Connection request id (`scr_…`) | ### `cloud projects servers connect-cancel` Cancel a pending connection request, releasing the concurrent-connection slot it holds. Cancelling an already-terminal request is a no-op. | Flag | Required | Description | | ---------------- | -------- | ------------------------------- | | `--request ` | Yes | Connection request id (`scr_…`) | Cancelling is idempotent — calling it twice on the same request is safe. Use this when an authorization was abandoned (link opened in the wrong browser, consent screen closed) and you want to free the slot before starting over. *** ## `inspector` commands ### `inspector open` Start or attach to the local Inspector and open the UI. | Flag | Required | Description | | ----------------------- | -------- | -------------------------------- | | `--inspector-url ` | No | Local Inspector base URL | | `--tab ` | No | Open Inspector on a specific tab | ### `inspector start` Start the local Inspector in the background without opening a browser. | Flag | Required | Description | | ----------------------- | -------- | ------------------------ | | `--inspector-url ` | No | Local Inspector base URL | ### `inspector stop` Stop the local Inspector if it is running. | Flag | Required | Description | | ----------------------- | -------- | ------------------------ | | `--inspector-url ` | No | Local Inspector base URL | *** ## `cloud tunnel` Expose a local MCP server through an MCPJam relay tunnel and register it as a server in your hosted project, so evals and scenarios can target it. Requires an `sk_` API key or a prior `mcpjam cloud login`. The tunnel stays up until Ctrl-C; the server record outlives the session (calls fail fast at the edge until you re-run, which revives the same URL slug with a fresh secret). ```bash theme={"theme":"css-variables"} # HTTP target mcpjam cloud tunnel http://localhost:9090/mcp --server my-server --project acme # stdio target (command goes after --) mcpjam cloud tunnel --server everything --project acme -- npx -y @modelcontextprotocol/server-everything ``` | Flag | Required | Description | | ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `--server ` | Yes | Server name to register in the project. An existing server with this name is pointed at the tunnel: its URL is overwritten, and stdio records are converted to HTTP. Hidden alias: `--id`. | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | | `--api-key ` | No | MCPJam `sk_` API key (overrides `MCPJAM_API_KEY`) | | `--api-url ` | No | MCPJam API base URL (defaults to `https://app.mcpjam.com/api/v1`) | | `-e, --env ` | No | Stdio environment assignment in `KEY=VALUE` format (stdio targets only) | | `--cwd ` | No | Working directory for the stdio MCP server process (stdio targets only) | With `--format json`, a single machine-readable startup object (public URL, server ID, slug, project) is written to stdout; ongoing status goes to stderr in both formats. The public tunnel URL embeds a bearer secret and is stored on the project server record so the platform can call it. Every re-run rotates the secret and disconnects any previous tunnel session for the same server. *** ## `readiness` hosted commands Grade a saved server against a publisher's directory as the platform reaches it — through the saved server row and the authorize exchange. This is the hosted half of `readiness check`: it answers a different question (what the platform sees, not what your machine sees), can optionally spend credits for model observations, and leaves a persistent record. All `readiness` hosted commands require an `sk_` API key (via `--api-key` or the `MCPJAM_API_KEY` environment variable) or a prior `mcpjam cloud login`. ### Shared platform flags | Flag | Description | | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--api-key ` | MCPJam `sk_` API key (overrides `MCPJAM_API_KEY`) | | `--api-url ` | MCPJam API base URL (defaults to `https://app.mcpjam.com/api/v1`) | | `--api-header ` | Extra request header as `"Name: value"`, repeatable — for a deployment behind Cloudflare Access, a WAF, or a corporate proxy. Also `MCPJAM_API_HEADERS`, one per line. Cannot set `authorization`, `idempotency-key`, or `content-type`. | ### `readiness start claude` Start a hosted readiness run that grades a saved server against Anthropic's connector-directory rules. Returns immediately with a run id; poll with `readiness status`. | Flag | Required | Description | | ------------------------- | -------- | ----------------------------------------------------------------------- | | `--server ` | Yes | Saved server to grade | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | | `--ai-observations` | No | Add optional model observations. **Consumes MCPJam credits.** | | `--idempotency-key ` | No | Replay guard: a retry with the same key returns the run already started | ### `readiness start openai` Start a hosted readiness run that grades a saved server against OpenAI's app-directory rules. `--submission-mode` is required and never inferred — omitting it would silently report the package lane as not-applicable. | Flag | Required | Description | | -------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `--server ` | Yes | Saved server to grade | | `--submission-mode ` | Yes | Declared submission shape: `mcp-only` or `mcp-imported-skills`. The two package shapes need a local package — use `readiness check openai` for those. | | `--project ` | No | Project name or ID | | `--ai-observations` | No | Add optional model observations. **Consumes MCPJam credits.** | | `--idempotency-key ` | No | Replay guard | ### `readiness status` Read one hosted readiness run. The response carries three independent axes: `status` (whether the run finished), `overallStatus` (the grade — a `completed` run can be `not-ready`), and `llmObservations` (whether the optional model pass ran). | Flag | Required | Description | | ------------------------ | -------- | ------------------ | | `--run ` | Yes | Readiness run id | | `--project ` | No | Project name or ID | ### `readiness list` List hosted readiness runs, newest first. | Flag | Required | Description | | ------------------------ | -------- | ------------------------------ | | `--project ` | No | Project name or ID | | `--kind ` | No | Narrow to `claude` or `openai` | | `--server ` | No | Narrow to one saved server | | `--limit ` | No | Rows to return (1–100) | ### `readiness cancel` Stop a hosted readiness run that is still going. The run's real terminal state arrives on a later `readiness status` — this response reports the request, not the outcome. | Flag | Required | Description | | ------------------------ | -------- | ------------------ | | `--run ` | Yes | Readiness run id | | `--project ` | No | Project name or ID | ### `readiness report` Read a finished run's findings, ordered most-consequential-first and capped. `truncated` and `totalFindings` indicate when you are seeing a subset. | Flag | Required | Description | | ------------------------ | -------- | ------------------ | | `--run ` | Yes | Readiness run id | | `--project ` | No | Project name or ID | *** ## `cloud eval` commands All `eval` commands accept the shared platform flags below. ### Shared platform flags | Flag | Description | | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--api-key ` | MCPJam `sk_` API key (overrides `MCPJAM_API_KEY`) | | `--api-url ` | MCPJam API base URL (defaults to `https://app.mcpjam.com/api/v1`) | | `--api-header ` | Extra request header as `"Name: value"`, repeatable — for a deployment behind Cloudflare Access, a WAF, or a corporate proxy. Also `MCPJAM_API_HEADERS`, one per line. Cannot set `authorization`, `idempotency-key`, or `content-type`. | ### `cloud eval create` Create a runnable eval suite from authored test cases (does not run it). | Flag | Required | Description | | -------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | | `--file ` | No | Path to a **create-API JSON** body (or `-` for stdin). A versioned suite file (`schemaVersion: "1"`) belongs on `eval run --file` | | `--json ` | No | Inline suite definition JSON (or `@file`, or `-` for stdin) | | `--name ` | No | Suite name (overrides the file) | | `--model ` | No | Suite-level default model (overrides the file) | | `--provider ` | No | Suite-level default provider (overrides the file) | | `--server ` | No | Project HTTP server names or IDs (overrides the file) | | `--client ` | No | Clients to attach the suite to (overrides the file). Without one the suite lists no client, and `eval run --client` has nothing to select | | `--host ` | No | **Deprecated** alias for `--client`. Passing both is an error. | ### `cloud eval list` List the eval suites saved in a project. | Flag | Required | Description | | ------------------------ | -------- | ------------------------------------------------------------------ | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | ### `cloud eval run` Start an eval run of an existing suite, or upload a versioned suite file and run it. Provide **either** `--suite` **or** `--file`, not both. By default the command prints a launch receipt and returns immediately. `--wait` polls every launched run to a terminal state. **Exit codes (`--wait` only).** Without `--wait` this command's exit code is unchanged: `0` on any launch, `1` on a partial or wholly failed fan-out. `--wait` owns a separate six-code contract — a caller who asked this process to sit and watch a run to completion is asking it to say how the run went: | Code | Meaning | | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `0` | Every waited run passed, unanimously, and (when a report was requested) every report was assembled | | `1` | A run **completed** with a **failed** verdict. The only condition that produces this code | | `2` | Usage error — bad flags, `--reporter`/`--out` without `--wait`, or an invalid suite file (after auth, before any server/model work) | | `3` | Auth failed — no credential, or the platform rejected it, at launch or mid-wait (a token that expired during a long poll). Zero credits spent if it happened before launch | | `4` | Connection/setup failed before evaluation — a launch failure this CLI itself observed (network, timeout, rate limit, a partial or failed fan-out, an unrecognized launch error), or a local `--out` write failure — unless a more severe outcome (`1` or `3`) already applies to the same run, per the merge order above | | `5` | No valid verdict — `inconclusive`, a null/unrecognized result, a run whose own status is failed/cancelled/timed-out, a wait that hit its deadline, a mid-wait failure that was not auth-shaped, or a report that could not be assembled | No infrastructure condition ever maps to `1` — see the same rule under `eval gate` below. A multi-target launch merges these worst-of across every waited run, in the order `1 > 3 > 4 > 5 > 0`: a real verdict failure is never masked by a sibling's infrastructure noise, and a credential failure (which may be *why* other targets show no verdict) outranks a plain connection failure. This six-code contract is new and deliberately **different** from `eval gate`'s four codes below — see that command's own doc for why the two are not unified. An invalid suite file exits **2** either way — `eval validate` still exits 1 for the same contract failure, because it is a verdict on the file and this command is not. | Flag | Required | Description | | -------------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--suite ` | Yes, unless `--file` | Eval suite name or ID | | `--file ` | Yes, unless `--suite` | Versioned suite file to upload and run (`.yaml` or `.json`, or `-` for stdin) | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | | `--server ` | No | Override the suite's saved server selection (HTTP servers only) | | `--environment ` | No | Attached project environment(s) to run. Several values start one **paid run each**. | | `--client ` | No | Attached client(s) to run, so the run is stamped with that client's config. Several values start one **paid run each**. | | `--host ` | No | **Deprecated** alias for `--client`. Passing both is an error. Not the host-compat catalog id `mcpjam compat --host` / `mcpjam tools --host` take — see [Two different `--host` flags](#two-different---host-flags). | | `--all-targets` | No | Run **every** attached environment (or, if none, every attached host) — one **paid run per target** | | `--repetitions ` | No | Run each case this many times (1-10), for this run only | | `--iterations ` | No | **Deprecated** alias for `--repetitions`. Passing both is an error. | | `--case ` | No | Run only these cases instead of the whole suite | | `--exclude-skills` | No | Run the "without skills" A/B arm: nothing is pinned, and the run is labelled as excluded | | `--refresh-snapshot` | No | **Persists a new host-config snapshot on the suite** (see below). Single-target runs only. | | `--notes ` | No | Free-text note stored on the run | | `--min-pass-rate ` | No | Pass threshold for this run (0-100), overriding the suite's own | | `--match-options ` | No | Tool-call match options for this run, e.g. `'{"toolCallOrder":"exact"}'` | | `--idempotency-key ` | No | Retry-safety key: repeating the call returns the run it already started | | `--wait` | No | Wait for every launched run to reach a terminal status | | `--wait-timeout ` | No | Maximum wait time (default 600000); requires `--wait` | | `--reporter ` | No | Write the completed structured report to stdout; requires `--wait`. `html` is a self-contained decision summary + failures page — traces/parity/history are not included. | | `--out ` | No | Atomically write the completed report to a file; defaults to `json-summary`, or uses `--reporter` when present; requires `--wait` | | `--compose-client ` | No | Compose a stack to run instead of naming a saved environment (see below). **Ephemeral by default** — pass `--save-targets` to attach it to the suite. | | `--compose-host ` | No | **Deprecated** alias for `--compose-client` | | `--compose-computer ` | No | Sandbox image to pin on the composed stack | | `--compose-model ` | No | Model(s) to run on the composed stack. Replaces the client's pinned model unless `--with-client-default` is set | | `--with-client-default` | No | Also launch an inherit cell using each client's pinned model, alongside `--compose-model` | | `--compose-server ` | No | Server(s) to pin on the composed stack, snapshotted into a server group so a rerun keeps testing them. Mutually exclusive with `--compose-server-group` | | `--compose-server-group ` | No | Standalone server group to pin on the composed stack | | `--compose-host-servers` | No | Run against whatever servers the client points at **right now** instead of pinning a set — editing that client later changes what a rerun tests | | `--compose-skill ` | No | Project-shared skill IDs to pin on the composed stack | | `--compose-secret ` | No | Project secret IDs the composed stack grants to its runs (`secrets list` shows them) | | `--save-targets` | No | Attach the composed environments to the suite (append, capped at 10). Without it the stack is ephemeral | #### Composing a target instead of naming one `--compose-client ` builds an execution stack for this run instead of naming a saved environment, refined by `--compose-computer`, `--compose-model`, `--compose-server-group`, `--compose-skill` and `--compose-secret`. The stack becomes an unnamed, content-addressed environment (the same one `environments ensure-adhoc` produces), so composing the same stack twice reuses one environment. `--compose-host` is the deprecated spelling of the same flag. `--compose-model` and `--compose-secret` carry the deployment requirement described under [Deployment support for model overrides and secret grants](#deployment-support-for-model-overrides-and-secret-grants): a launch that names either is refused before it composes anything if this deployment does not publish the capability. **With `--save-targets`, this edits the suite.** The composed environment is appended to the suite's environment list — deliberately, because an environment the suite does not list is one nobody can re-run from the app afterwards. Without it the stack is ephemeral and the suite is untouched, except on a deployment with no ephemeral-launch support, where a single-target compose still attaches. The result reports both writes (`composed.environment.created`, `composed.attachment.attached`) even when the launch itself fails, so you always know what changed. Any `--compose-*` flag alongside `--environment`, `--client`, `--server` or `--all-targets` is a usage error: compose builds the stack the run uses, so naming a target alongside it describes two different runs. #### Two different `--host` flags `--host` means two unrelated things in this CLI, and only one of them lives under `cloud eval`. | Command | `--host` names | | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `mcpjam tools` / `resources` / `prompts` / `probe` / `compat` | A **host-compat catalog id** — which AI host to emulate. `claude`, `chatgpt`, `cursor`, `copilot`, `codex`, `goose`, and the rest of the catalog. | | `mcpjam cloud eval run` / `cases run` / `update` / `create` | A **client saved in your project** (`mcpjam cloud clients list`). | The catalog sense keeps `--host`: it genuinely means "which AI host am I emulating". Under `cloud eval`, **`--client` is canonical** and `--host` is a deprecated alias for it — so `mcpjam cloud eval run --host claude` looks for a saved client named `claude` and fails if you do not have one. When the value you pass is a catalog id, the error says so and points at `--client`. #### Which target runs **Fan-out is explicit and never inferred.** Choosing for you would be choosing how much to spend, so: * **nothing attached** → the suite's saved server selection runs (unchanged); * **exactly one** attached environment or client → that one runs automatically, and the run is stamped with its configuration; * **several attached** → the run fails with `TARGET_REQUIRED`, listing every choice. Name one with `--environment` / `--client`, several by repeating the flag, or run all of them with `--all-targets`. `--all-targets` expands **one axis**: attached environments if the suite has any, otherwise attached clients — never a cross product, because an environment already resolves a client. It starts **one paid run per target**, launched as a single group that occupies one concurrency slot rather than N. `--environment` / `--client` and `--server` are mutually exclusive: an environment or client supplies a closed server set that an override cannot change. `--refresh-snapshot` **changes the suite**, not just this run: it re-derives and stores the suite's host-config snapshot, so every future run uses the new one. It is rejected on any multi-target launch — several runs racing to write one snapshot would leave the suite pinned to whichever finished last. #### Output and exit codes With `--format json`, stdout is **exactly one JSON document** — the launch receipt, with nothing appended — so CI can parse it directly. In human format the CLI prints a `View:` link per started run, a `Started N/M runs (group …)` summary when several launched, and a `Failed: : ` line per failure. With `--wait`, the default output contains the launch receipt and completed runs. `--reporter` replaces that output with the canonical structured report. `--out` writes the same report atomically, including on failed-run and post-completion reporting-error paths, before the command sets its exit code. Without `--wait`, a partial or wholly failed fan-out **exits 1**; with `--wait` it exits **4** (see the exit-code table above). A per-target failure does not abort its siblings, so exiting 0 would let a pipeline read "1 of 3 runs never started" as a clean launch. #### `--file`: upload and run a suite file `eval run --file` reads the versioned suite file (`schemaVersion: "1"`), authenticates, then validates. A contract-invalid file exits **2** after the auth request — it does not start a run. `eval validate` on the same bytes still exits 1: that command is a verdict on the file; this one is not. The file's `suite.id` is the declared suite identity. The first run creates a file-owned suite stamped with that id; later runs of the same id in the same project update that suite rather than creating a second one. A UI-authored suite has no declared id, so no file can claim it. Resolve is never by name. File `passThreshold` is a fraction; the hosted suite grades on a percent. The conversion refuses rather than approximates. File `repetitions` above **10** are refused by name (`REPETITIONS_CAP`) and are not clamped. Cases are synced in batches of at most 100 (`create_eval_cases`). Every declared case is created or updated, including `disabled: true` rows. A case the file **no longer declares** is deleted from the hosted suite before launch. A case the file still declares but marks `disabled: true` is **kept, with its history** — it is simply left out of the launch, so parking a flaky test does not destroy its past results and re-enabling it later resumes the same case. The run is scoped to the enabled cases either way. A file with no enabled cases is refused (`NO_ENABLED_CASES`) rather than launching the whole persisted suite. `--case` may only name an enabled case. A later run updates existing cases by declared id and creates new ones. An enabled case that overrides `passThreshold` is refused: hosted grading is suite-wide. A disabled case with the same override is ignored, matching how repetitions are checked. Authored `defaults.toolPolicy` and non-empty `defaults.validity` gates are refused at hosted CLI launch (`TOOL_POLICY_UNSUPPORTED` / the corresponding validity refusal). Hosted platform-authored suites do not currently have a backend tool-policy field or enforcement path, so the CLI does not upload a suite that would claim those guarantees. When supported by an eval runner, `toolPolicy` is enforced at execution time with denied tools kept visible but blocked; server annotations are advisory and **UNTRUSTED**. File `defaults.repetitions` is inherited per case and is not uploaded as a suite `minIterations` floor. Authored `defaults.systemPrompt` and `defaults.temperature` are uploaded as execution defaults; omitted fields stay omitted so the platform applies its own defaults. `target.hosts` and `target.environment` are attached before launch. A file environment takes precedence over file hosts for that launch, while an explicit CLI target takes precedence over both. When an explicit CLI target is provided (for example `--host`), the file's `target.hosts` are **not** synchronized to the suite's persisted host attachments — the suite keeps whatever hosts it already has. Host attachment synchronization only runs when no explicit CLI target overrides the file. Environment attachment synchronization is unaffected and always runs. Without `--idempotency-key`, the CLI derives one from the file's SHA-256, declared suite id, project, target, and every run-affecting knob (`--repetitions`, `--case`, `--min-pass-rate`, `--exclude-skills`, `--match-options`, `--environment` / `--host` / `--server` / `--all-targets`, `--refresh-snapshot`, compose flags, and the normalized `--allow-approximated` set with its `--approval-reason`). Repeating the same file with the same knobs returns the run it already started; changing a knob starts a new one. The approval set is sorted and keyed by AUTHORED case id, so `--allow-approximated a b` and `--allow-approximated b a` are the same run while approving nothing is a different one. `--notes` is not part of the key. ##### Imported cases: live tool resolution, and per-run approval Every file run performs the same live check `eval validate --project` performs, **before it writes anything**. It is not opt-in and there is no flag to skip it. A file with no deterministic `toolCall` steps asks no question and pays no round trip. What happens when a deterministic reference does not resolve depends on the case: | Case | Outcome | | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Selected (enabled, and named by `--case` if you used it) | The launch **refuses before the suite is synced** — nothing is written and nothing is billed | | Imported, not selected | Its claim is rewritten to `unresolved` (keeping `sourceCaseKey`) and it is **still persisted**, so the hosted record says what MCPJam found rather than still asserting a claim about a missing tool | | Native, not selected | Left exactly as authored. A hand-written case never acquires an `import` block from this — "somebody wrote this" and "something converted this" are different facts | Each target is checked on **its own** server set. `--host` is checked against that host's configured servers rather than the file's, because that is the set the run connects — validating `target.servers` would approve a tool the host cannot reach. A host the FILE declares uses its own `servers` list when it declares one (those are attached to the host before launch) and the host's configured set when the field is omitted. An explicitly empty `servers: []` is a real, empty set — it clears the attachment before launch — and is validated as such, never as an omitted field. A step that pins `serverId` is resolved by id; `serverName` is a display fallback and never overrides it. If the run's target set cannot be enumerated before the suite exists (`--all-targets`, `--compose-*`), or a host does not report a server set, or a host pins a server the project no longer has, the launch refuses rather than assuming the references are fine. `--case` accepts a hosted row id, which does not exist yet when this check runs. A selector this stage cannot map is therefore treated as possibly naming any enabled case, so an unresolved reference still refuses; the launcher resolves the selector itself a moment later. A selected `approximated` case refuses unless this invocation approves it: | Flag | Required | Description | | -------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `--allow-approximated ` | No | Approve an `approximated` imported case for **this run only**, by AUTHORED case id (`cases[].id`). Repeatable. `--file` runs only | | `--approval-reason ` | With the above | Why the approximations are acceptable for this run. 1–500 characters after trimming; one reason covers every approval in the invocation | ```bash theme={"theme":"css-variables"} mcpjam cloud eval run --file .mcpjam/evals/s_billing.yaml \ --allow-approximated c_refund_partial \ --approval-reason "Reviewed against the upstream rubric; ENG-4821" ``` Checked before anything is billed, each with its own message: approving a **native** case (nothing to approve), a **claimed-exact** one (needs no approval), an **unsupported** or **unresolved** one (approval covers a case whose behaviour was approximated, not one whose behaviour is missing), a **disabled** or **unselected** one (the run will not execute it), an unknown id, or the same id twice. The flags are refused outright with `--suite`: a hosted suite's cases are not the ones this invocation authored, so an authored-id selector has nothing to resolve against. **The approval belongs to the run, never to the case.** You supply an id and a reason and nothing else — the approver and the timestamp are derived by the server and frozen into that run's own snapshot, because a caller-supplied approver would file one person's approval under another's name and a caller-supplied timestamp could be backdated past the edit that invalidated the claim. Nothing persists: edit the suite, re-sync, or launch again, and the flags are required again. Scheduled runs and GitHub-App runs cannot approve anything. A scheduled suite containing a selected approximation is refused by platform policy, which is the intended behaviour — an unattended job is exactly the caller that must not be able to wave one through. `--file` pointing at create-API JSON (no `schemaVersion`) is a usage error that names `eval create --file`. The converse is also true: `eval create --file` on a versioned suite file names `eval run --file`. Export of a file-owned suite writes `declaredId` as `suite.id`. Export of a UI suite still writes the Convex id; running that file back is the ownership refusal. When the run targets an **attached project environment** (`eval environments set`), it executes against that environment's resolved host config, closed server set, and pinned plugin versions instead of the suite's saved selection. Every run records the environment and the exact revision it used, so `eval status` can answer "which configuration did this run actually execute against?" long after the environment has been edited. ### `cloud eval server-facts` Read the server a run was taken against. | Flag | Required | Description | | ------------------------ | -------- | ------------------------------------------------------------------ | | `--run ` | Yes | Eval run ID (from `eval run`) | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | Per server: the tool count, the catalog's measured size, annotation and output-schema coverage, and the deterministic tool-metadata prechecks — plus what connect and discovery observed, and any conformance or readiness runs for the same servers. **None of it is a verdict.** A large tool surface is not a defect and a slow connect is not a failure; only a precheck with `class: "spec_required"` names a violation, and a row marked `protocolDependent` is a rule that may not apply to this server's protocol version at all. **Payload size is three different numbers** and this reports two of them, each carrying its `payload.basis`: `aggregated_catalog_json` is the catalog as the client assembled it (measured at capture), `normalized_snapshot` is what was retained after redaction — smaller, and `payload.complete` says so. What the model actually saw is a host fact and is not here. Token figures are estimated as `json_chars_div_4` against a **reference** window, not measured against any model's real context. Server facts are computed on read, so there is no backfill window: a run that finished long ago still answers. A run with nothing to describe answers inside the document with `state: "unavailable"` and a reason — `snapshotMissing`, `snapshotPartial` (the servers that answered are still listed, and their numbers are real), or `setupNotObserved` (unmeasured, **not** failed). Related conformance and readiness runs are joined by **server id alone**, so a different server version, environment or auth context is not excluded by the match. Each carries its own timestamp, and none of them is this run's verdict. ### `cloud eval status` Get the status and summary of an eval run. | Flag | Required | Description | | ------------------------------- | -------- | ----------------------------------------------------------------------------- | | `--run ` | Yes | Eval run ID (from `eval run`) | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | | `--diagnostics-limit ` | No | Failure diagnostics per page (1–200; default 20) | | `--diagnostics-cursor ` | No | Cursor from a previous response's `decisionSummary.diagnostics.nextCursor` | | `--stages` | No | Print all six user-value chain rows for each failing trial (`--format human`) | The response carries a `judges` block with each advisory grader's state and per-case grades. `status: null` means that judge was never requested for the run — different from a judge that ran and graded nothing. In `--format human`, each judge that actually graded gets a one-line summary. The failure diagnostics inside `decisionSummary` are **paginated**, and a page that is not the whole set says so: `diagnostics.complete` is `false` and `diagnostics.nextCursor` carries the next page. Without `--diagnostics-cursor` a run with more failures than the page size reports only its first 20, so a script that concludes "only these failed" from one page is reading a sample. Walk the pages: ```bash theme={"theme":"css-variables"} mcpjam cloud eval status --run "$RUN_ID" --diagnostics-limit 200 --format json # then, while .decisionSummary.diagnostics.complete is false: mcpjam cloud eval status --run "$RUN_ID" --diagnostics-limit 200 \ --diagnostics-cursor "$NEXT_CURSOR" --format json ``` `--stages` expands each failing trial from one first-failed-stage line to all six chain rows — connection, discovery, selection, tool call, response, user value — with each row's state and reason in words. It is off by default because six rows per trial is a lot of terminal on a run with twenty failures; the first-break line above the per-trial detail is printed either way. ### `cloud eval judge` Grade a finished eval run with LLM as Judge. **Spends your organization's model budget.** Returns a pending receipt; read the grades from `eval status`. | Flag | Required | Description | | ------------------------- | -------- | ------------------------------------------------------------------ | | `--run ` | Yes | Eval run ID (from `eval run`) | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | | `--force` | No | Re-grade a run that already has a judge result | | `--enable` | No | Grade this run even though the judge was off when it ran | | `--judge-model ` | No | Judge model for this run only | | `--judge-threshold <0-1>` | No | Pass threshold for this run only | A run's grading config is pinned when the run starts, so turning the judge on for the suite does not reach a run that was already recorded — `--enable` is what grades it, and it changes nothing beyond that run. Passing neither `--judge-model` nor `--judge-threshold` grades with the suite's own config, clearing any override a previous request left on the run. ### `cloud eval stage-analytics` Read the user-value chain funnel — how far value travelled through `connection → discovery → selection → call → response → userValue`, and how much of the run was measured at each stage. Address it two ways, and exactly one of them per invocation: | Flag | Required | Description | | ------------------------ | ------------- | ----------------------------------------------------------------------------- | | `--run ` | XOR `--suite` | One run's funnel, as a single document | | `--suite ` | XOR `--run` | One page of that suite's runs, newest completion first — one document per run | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | | `--cursor ` | No | Pagination cursor from a previous response (`--suite` only) | | `--limit ` | No | Documents per page, 1–100 (`--suite` only) | ```bash theme={"theme":"css-variables"} mcpjam cloud eval stage-analytics --run "$RUN_ID" --format json mcpjam cloud eval stage-analytics --suite checkout --limit 10 --format json ``` **Counts are returned; rates are not.** Every field is a count of trials, and the arithmetic is stated on the document: `passed + failed = measured`, `measured + notMeasured = reached`, `reached + notReached + reachUnknown = applicable`. Derive a rate only with its denominator in hand — **a zero denominator means not measured**, never 0% and never 100%. **Never sum across stages, and never merge across runs.** One trial is counted in every stage's tally, so adding the six counts the same trial six times. Each document describes one run's population; the `--suite` listing renders runs side by side as a trend series and never aggregates them. **A listing is only comparable within a partition.** Two funnels drawn beside each other *is* a comparability claim, and it holds only across runs that share `runGroupId`, `configRevision`, `caseSetFingerprint`, `stageAnalyzerVersion` and `measurementsSchemaVersion`, and whose `materializationState` is `final`. An **absent** `runGroupId`, `configRevision` or `caseSetFingerprint` blocks comparability rather than being assumed compatible — two runs that both record nothing compare equal while sharing nothing at all. **Three absences, three different answers.** A run that does not exist fails as a run-not-found error. A deployment that does not serve this route fails with a message saying so — that is a fact about the deployment, not about the run. Only a run that was retrieved and has no document reports `analyticsState: "unmeasured"` with `analytics: null`, and that state is permanent: there is no backfill, so a run that finished before stage measurement shipped will never gain a funnel. ### `cloud eval iterations` List per-iteration results for an eval run (pass/fail, tool calls, tokens, latency). Results are paginated. `--project` is optional. Selection follows `--project` → input selector → `MCPJAM_PROJECT` → nearest project link → automatic newest accessible project. | Flag | Required | Description | | ------------------------ | -------- | ------------------------------------------------------------------ | | `--run ` | Yes | Eval run ID (from `eval run`) | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | | `--cursor ` | No | Pagination cursor from a previous response | | `--limit ` | No | Max iterations per page (1–200) | ### `cloud eval gate` Apply a pass/fail policy to an eval run. Add `--wait` to poll an in-progress run before evaluating it. | Flag | Required | Description | | -------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--run ` | Yes | Eval run ID (from `eval run`) | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | | `--min-pass-rate-percent <0-100>` | No | Minimum share of iterations that must pass | | `--no-gating-score-errors` | No | Fail if any gating scorer errored | | `--min-scorer-pass-rate ` | No | Minimum pass rate for one scorer; repeatable | | `--min-mean-score ` | No | Minimum mean score for one scorer; repeatable | | `--baseline ` | No | Baseline run ID to gate a regression delta against, in addition to any threshold flags above. Mutually exclusive with `--baseline-sha` | | `--baseline-sha ` | No | Baseline **source commit SHA**, resolved server-side to the completed run in this suite recorded against it. Mutually exclusive with `--baseline` | | `--min-sample-size ` | No | Iterations required on **each** side before a pass-rate regression is decidable (default 5); requires `--baseline` or `--baseline-sha` | | `--min-effect-size-percent <0-100>` | No | Smallest pass-rate drop worth failing on, as a percentage (default 1); requires `--baseline` or `--baseline-sha` | | `--gate-deterministic-regressions` | No | Fail if a deterministic gating scorer flipped from passed to failed; requires `--baseline` or `--baseline-sha` | | `--max-p95-latency-increase-ms ` | No | Fail if p95 end-to-end latency rose by more than this many milliseconds vs the baseline; requires `--baseline` or `--baseline-sha` | | `--max-cost-usd ` | No | Fail if the run's MCPJam-billed cost exceeded this many dollars. Accepts decimals (`0.05`) | | `--max-cost-increase-percent ` | No | Fail if cost rose by more than this percentage of the baseline's; requires `--baseline` or `--baseline-sha`. Values above 100 are allowed — a run costing three times its baseline is a 200% increase | | `--wait` | No | Poll until the run reaches a terminal status | | `--wait-timeout ` | No | Maximum wait time (default 600000) | | `--reporter ` | No | Write the structured eval and gate report to stdout. `html` is a self-contained decision summary + failures page — traces/parity/history are not included. | | `--out ` | No | Atomically write the report to a file; defaults to `json-summary`, or uses `--reporter` when present | Both cost gates report **non-gateable** (exit 3) rather than passing when the cost is unknown or only partly measured. Cost is stamped per iteration and is absent whenever the model was not MCPJam-billed (your own API key), the run used a harness (which mixes models within a turn), or the trial reported no tokens — so a total built from some of the run is smaller than the truth, and gating a ceiling against it would pass exactly the runs least understood. A `--max-cost-increase-percent` gate is also non-gateable when the baseline cost nothing, since a percentage of zero is undefined. Report files are flushed before gate exit codes are set, including failed and incomplete gate outcomes. **`--baseline` / `--baseline-sha`.** Either flag fetches the same run comparison `cloud eval compare` uses and folds a regression verdict into the same report and exit code as the threshold flags above — either one alone enables the pass-rate regression gate with its default tuning; the four tuning flags need a baseline to mean anything and are a usage error without one. A case set that changed, was added or removed, or ran an unequal iteration count between the two runs makes the whole-run pass-rate and p95-latency gates non-gateable (exit `3`) rather than a silent pass or a misread regression; the deterministic per-case regression gate is exempt and can still fail (exit `1`) on a matching case. The two are **mutually exclusive** — passing both is a usage error (exit `2`). There is deliberately no single flag that guesses which kind you meant: a Convex run ID is an opaque string with no documented format, and an abbreviated commit SHA is indistinguishable from one by construction, so the kind is named by the flag rather than inferred. A commit SHA passed to `--baseline` is rejected with a pointer to `--baseline-sha` instead of being sent as a doomed run lookup. A SHA that matches no completed run in the suite is **not** an error: it is the ordinary "no baseline" outcome, `incomplete` (exit `3`), which means the comparison established nothing — never a regression. When the SHA matched more than one eligible run, or the lookup was truncated before it could prove uniqueness, the gate report's baseline provenance records `baselineMatchCount` alongside `baselineMatchCountTruncated`; the count is a **floor**, not a total, so the two are always read together. An absent count means the match was unambiguous. Baseline provenance records both what was requested and what it resolved to — `requestedBaselineKind`, `requestedBaselineRunId` or `requestedBaselineCommitSha`, the backend-echoed `resolvedBaselineCommitSha`, and the resolved `baseRunId` — so an archived report can answer "which run did commit X compare against?" without the invocation that produced it. There is no automatic baseline selection: a baseline is compared only when one is pinned. **Gate waivers.** A run whose gate failed can be overridden by an authorized user, on the record, until an expiry they name — see [`cloud eval gate waive`](#cloud-eval-gate-waive) below. When a waiver is in force, `eval gate` reports the outcome as **`waived`** and exits `0`. `waived` is deliberately not the same as `passed`. The run keeps its own failed result, the failing verdicts stay in the report, and the waiver — **who** granted it, **why**, and **until when** — is named in every output format: the human report, `json-summary`, `junit-xml` (as a `` element on the gate testcase, which does not fail the build and does not render as a clean green row), and `html` (its own badge and section, neither green nor red). Only a real verdict failure can be waived. An `incomplete` gate — a cancelled run, a `--wait` timeout, a network failure, non-gateable score evidence — keeps exit `3` no matter what waiver is on the run: a waiver granted because the evals regressed is not consent to ship on an infrastructure failure. A `usage_error` keeps exit `2` for the same reason. The CLI also re-derives the waiver's expiry itself rather than trusting the platform's `active` flag, so a lapsed waiver cannot keep a gate green. **Exit codes.** `eval gate` is the command that fails a build, so it is the one that maps a verdict onto an exit code. It keeps four: | Code | Meaning | | ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `0` | Every requested gate passed, **or** a failed gate was waived | | `1` | An eval **verdict** failed. Reserved for exactly that | | `2` | Usage error — an unknown scorer, or a threshold out of range | | `3` | Incomplete: the run was cancelled, the wait timed out, the network failed, or the run is non-gateable — its score evidence did not verify, or its **import evidence is incomplete** | **Incomplete import evidence is exit `3`, decided first.** When the platform reports the run's `importEligibility` as `incomplete` (or `gateable: false` under any status), `eval gate` returns `3` **before** any verdict is computed and before a waiver is considered. That ordering is the point: import completeness is evidence ELIGIBILITY, not a measurement of the server, so it is never exit `1` — the run has not said the server regressed, it has said its own evidence cannot be relied on. A waiver cannot override it either, for the same reason it cannot override a cancelled run: a waiver granted because the evals regressed is not consent to ship on evidence nobody finished reviewing. Fix it by re-running with the approvals the cases need, or with the unsupported cases excluded. **The baseline is checked too.** A `--baseline` / `--baseline-sha` gate rests on two runs, so `eval gate` reads the resolved baseline run's own `importEligibility` and returns `3` when the baseline cannot gate — the compare response reports counters, not provenance, so the baseline is fetched by id rather than assumed sound. Without that, a baseline whose own approximations were never approved could still produce a confident "no regression". A baseline run that cannot be read at all is also `3`: "we could not look" is not "it is fine". A run whose eligibility is `legacy` (no imported cases at all — every native run) or `eligible` goes through the ordinary verdict logic unchanged. A server that reports no eligibility at all behaves exactly as it did before the field existed. No infrastructure condition ever maps to `1`. A job that fails a release because a network call flaked, and reports it as a regression, teaches people to ignore the gate — so retry on `3`, and treat `1` as a real finding about the server. These four are deliberately **not** the same set `eval run --wait` uses (see that command above): `gate`'s exit `3` means "incomplete / non-gateable", and remapping it to the six-code scheme would invert pipelines that already retry on `3`. This is a deliberate compatibility exception for v1 — a six-code migration for `gate` is deferred behind a future opt-in flag, kept here so the exception stays documented rather than silently diverging. ### `cloud eval gate waive` Override a **failing** run's gate until an expiry you name. Requires the manage tier, enforced by the platform — whoever launched the run gets no exception for having launched it. This does **not** make the run pass. The run keeps its `result`, and the waiver is reported by both things that read the gate: the GitHub Check Run and `eval gate` itself. | Flag | Required | Description | | ------------------------- | -------- | ---------------------------------------------------------------- | | `--run ` | Yes | Eval run ID (inherited from `eval gate`) | | `--project ` | No | Project name or ID (inherited from `eval gate`) | | `--reason ` | Yes | Why the gate is being overridden. Max 500 characters | | `--expires-in ` | Yes | How long the waiver lasts: `30m`, `12h`, `7d`. Capped at 30 days | ```bash theme={"theme":"css-variables"} mcpjam cloud eval gate waive --run run_123 --reason "hotfix ships today; regression tracked in ENG-4821" --expires-in 3d ``` **The reason is stored unredacted, for the life of the suite.** It is readable by anyone who can see the suite and has no independent retention window. Never paste secrets, tokens, or customer data into it. The CLI prints this notice before it accepts a reason. A bare number is rejected: `--expires-in 7` is ambiguous between seven minutes and seven days, and the difference is a gate that reopens before lunch or three weeks later. The 30-day cap is enforced by the platform, so the refusal names the cap and what to do instead. Waiving a run that already has a waiver in force is a **conflict**, not a second waiver: the existing one is reported back and is left untouched, because two active waivers would make "which reason is on the check" a race. ### `cloud eval gate unwaive` Revoke a waiver, putting the gate and the GitHub Check Run back. | Flag | Required | Description | | ------------------------ | -------- | ------------------------------------------------------------------- | | `--run ` | Yes | Eval run ID (inherited from `eval gate`) | | `--project ` | No | Project name or ID (inherited from `eval gate`) | | `--waiver ` | No | Waiver to revoke. Omit to revoke whichever is in force over `--run` | ```bash theme={"theme":"css-variables"} mcpjam cloud eval gate unwaive --run run_123 ``` Idempotent. A second revoke reports the **original** revocation rather than restamping it — that is a success, not an error, and it preserves the record of who actually ended the waiver. An already-expired waiver can still be revoked; the audit trail distinguishes "this was wrong" from "this ran out". ### `cloud eval trace` Fetch the full trace for one eval iteration (complete message history and spans). Can be large. `--project` is optional. Selection follows `--project` → input selector → `MCPJAM_PROJECT` → nearest project link → automatic newest accessible project. | Flag | Required | Description | | ------------------------ | -------- | ------------------------------------------------------------------ | | `--run ` | Yes | Eval run ID (from `eval run`) | | `--iteration ` | Yes | Iteration ID (from `eval iterations`) | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | ### `cloud eval screenshots` Pull the rendered widget screenshots an eval iteration captured — printed inline when the terminal supports images, otherwise as URLs, or saved to disk with `--out`. | Flag | Required | Description | | ------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--run ` | Yes | Eval run ID (from `eval run`) | | `--iteration ` | Yes | Iteration ID (from `eval iterations`) | | `--out ` | No | Save the PNGs here instead of printing them. A directory — except without `--video`, where a single-screenshot iteration may name a file. Created if it does not exist | | `--index ` | No | Only the Nth screenshot (1-based) | | `--video` | No | With `--out`, also save the iteration's replay recording beside the screenshots, as `.mp4` or `.webm` | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | ### `cloud eval video` Get the replay recording an eval iteration made — prints the URL, or downloads it with `--out`. **Unattended runs on a hosted browser are recorded at 15 fps.** Idle time is held rather than dropped, so the file's duration matches the run's wall clock even when the page sat still for a minute. Recordings stop at 60 MiB and **say so**: a capped take is a complete, playable *beginning* of the run rather than a corrupt file, and both this command and the run page label it. Read `videoMeta.truncated` before treating a duration as the length of the run. A local widget-harness run records a `.webm` through Playwright and reports nothing else about it; a hosted run records an `.mp4` and reports its duration, rate, and distinct-frame count. `--format json` returns `videoMeta` when the recording carried one. Recording can be turned off for a deployment, in which case hosted runs simply have no recording to fetch. | Flag | Required | Description | | ------------------------ | -------- | ------------------------------------------------------------------ | | `--run ` | Yes | Eval run ID (from `eval run`) | | `--iteration ` | Yes | Iteration ID (from `eval iterations`) | | `--out ` | No | Download the recording to this file instead of printing the URL | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | ```bash theme={"theme":"css-variables"} # The URL, plus what the recording says about itself mcpjam cloud eval video --run "$RUN_ID" --iteration "$ITERATION_ID" # Screenshots and the recording together, into one directory mcpjam cloud eval screenshots --run "$RUN_ID" --iteration "$ITERATION_ID" \ --out ./evidence/ --video ``` ### `cloud eval get` Show an eval suite's full settings. | Flag | Required | Description | | ------------------------ | -------- | ------------------------------------------------------------------ | | `--suite ` | Yes | Eval suite name or ID | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | ### `cloud eval validate` Validate a local eval **suite file** — the versioned declarative document that describes a suite, its defaults and its cases (`schemaVersion: "1"`, YAML canonical, JSON accepted, conventionally `.mcpjam/evals/*.yaml`). Offline by default: without `--project` this command builds no API client, reads no API key and never touches the network. Passing `--project` is the only thing that changes that, and it is keyed off the FLAG — a linked directory or an `MCPJAM_PROJECT` in the environment never turns the offline command into a networked one. | Flag | Required | Description | | ------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `--file ` | Yes | Suite file to validate, `.yaml` or `.json` (or `-` for stdin) | | `--project ` | No | Also resolve the file's deterministic tool references against this project's live servers. Opt-in; authenticates and makes network calls | | Exit code | Meaning | | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `0` | Valid | | `1` | Parsed, but invalid against the suite-file contract. Every finding is reported, not just the first. `eval run --file` on the same bytes exits **2** — 1 is reserved there for a real verdict | | `2` | Nothing was validated: unreadable path, input over the 1 MiB limit, or malformed YAML | Input is capped at **1,048,576 bytes (1 MiB) of UTF-8** and is never truncated — a file over the cap is rejected outright. Under `--format json`, stdout carries this envelope whether the file is valid or not: ```jsonc theme={"theme":"css-variables"} // exit 0 { "valid": true, "file": ".mcpjam/evals/s_billing.yaml", "suite": { "id": "s_billing", "name": "Billing smoke", "cases": 12, "enabledCases": 11 }, "findings": [] } // exit 1 or 2 { "valid": false, "file": ".mcpjam/evals/s_billing.yaml", "stage": "contract", // "contract" is exit 1; "input" and "parse" are exit 2 "findings": [ { "code": "SUITE_FILE_INVALID", // or SUITE_FILE_TOO_LARGE / _EMPTY / _YAML_INVALID / _MULTIPLE_DOCUMENTS "path": ["cases", 1, "id"], // field path as segments "pointer": "cases[1].id", // the same path, rendered "message": "duplicate case id \"c_refund\"" // `location` accompanies SUITE_FILE_YAML_INVALID findings only, where the // YAML parser reported a position: { "line": 24, "column": 9 } } ] } ``` Findings are deterministically ordered and carry no timestamps, so two runs over the same bytes produce byte-identical output. An unreadable or missing path is a usage error and goes to stderr in the CLI's standard error envelope rather than as findings — nothing was read, so there is nothing to report about. **What "valid" does not mean, without `--project`.** The offline check judges the file against the suite-file contract. It does **not** re-resolve tool names, server references or fixtures against a project's live discovery, so a file that validates here can still fail to run. #### `--project`: resolving the names against a real project With `--project`, the command authenticates, resolves the named project, and checks every deterministic `toolCall` step against the live tool inventory of the targets the file would run on. Structural findings are unchanged and the live half is additive, under its own key: ```jsonc theme={"theme":"css-variables"} { "valid": false, "file": ".mcpjam/evals/s_billing.yaml", "suite": { "id": "s_billing", "name": "Billing smoke", "cases": 12, "enabledCases": 11 }, "findings": [], "projectValidation": { "project": { "id": "proj_123", "name": "Alpha" }, "targets": ["environment prod"], "valid": false, "findings": [ { "code": "TOOL_REFERENCE_UNRESOLVED", // or TOOL_DISCOVERY_UNAVAILABLE "path": ["cases", 1, "steps", 0, "toolName"], "pointer": "cases[1].steps[0].toolName", "caseId": "c_render_legacy", "caseTitle": "Renders the legacy receipt", "toolName": "render_gone", "serverName": "billing", "targetLabel": "environment prod", "disabled": true, "imported": true, "message": "Server \"billing\" in environment prod exposes no tool named \"render_gone\"." } ] } } ``` Three properties of the live check are worth knowing before you rely on it: * **Only `toolCall` steps count.** A tool named in prompt text is a hint the model may or may not act on, and an `assert` step is an expectation a case may legitimately fail at run time. Treating either as a deterministic reference would make a negative test unwritable. * **Per target, never over their union.** A file that runs against several targets must resolve in all of them; a union would green-light a case that fails on two targets out of three. * **Auth and network failures are command errors, not findings.** "Your file names a tool that does not exist" and "we could not look" send you to two different places, so they are never reported as the same thing. A completed live check with unresolved references is a **verdict on the file** and exits `1`, like any other contract failure. A failure to authenticate, reach the project, or list a server's tools is a command error on stderr. ### `cloud eval export` Write a hosted eval suite to a local suite file. | Flag | Required | Description | | ------------------------ | -------- | ---------------------------------------------------------------------------- | | `--suite ` | Yes | Eval suite to export (name or ID) | | `--project ` | No | Project the suite belongs to (defaults to the most recently updated project) | | `--out ` | No | Where to write (default `.mcpjam/evals/.yaml`) | | `--force` | No | Replace an existing file at the output path | The default path uses the suite's **id**, not its display name: an id is stable and path-safe, so a rename does not leave a second file behind. **Fail-closed.** A suite file cannot express everything a hosted suite can, and this command refuses rather than writing a file that describes a different test than the dashboard runs. When any part of a suite is not representable it emits `UNSUPPORTED_SUITE_EXPORT` findings, exits `1`, and writes **no file at all** — not a partial one, not one with a warning comment. Every finding names the field it is about, so the command's output is the authoritative list; the constructs that refuse today include: * several attached project environments, or legacy servers combined with an attached environment (the suite file can name one environment); * a pinned sandbox image; * automatic LLM-as-judge grading, non-default match options, and an iterations floor that raises a case above its own count; * a compare-across-models case, a scenario-bound case, and cases that name **different model providers** (a suite file carries one suite-level provider); * `replace`/`extend` check overrides, a case that inherits the suite's checks while also carrying its own, and any suite check the predicate contract does not recognise; * a suite with no execution model, no minimum accuracy, no name, no runnable server or environment target, or no cases; a case with no steps; and a suite or case id that is not an opaque id; * a suite that serializes past the 1 MiB suite-file limit — the size is a property of the suite, and this never truncates. Case identity is never freshly minted: a case exports under its declared id, or under its platform row id, or it refuses. Exporting the same suite twice produces the same case ids. | Exit code | Meaning | | --------- | ----------------------------------------------------------------------------------------------------- | | `0` | Written | | `1` | The suite cannot be represented losslessly; nothing was written | | `2` | Usage: the output path exists and `--force` was not passed, or the suite's cases did not fit one page | The file is written through a sibling temp file and a rename, so an interrupted write leaves the previous file exactly as it was. ### `cloud eval update` Edit an eval suite's settings (only the flags you pass change). | Flag | Required | Description | | ------------------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `--suite ` | Yes | Eval suite name or ID | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | | `--file ` | No | Suite-update JSON body (or `-` for stdin) | | `--json ` | No | Inline suite-update JSON (or `@file`, or `-`) | | `--name ` | No | Rename the suite | | `--description ` | No | Suite description | | `--server ` | No | Replace the suite's server selection (project server names) | | `--computer-image ` | No | Sandbox image eval runs boot a fresh computer from (list them with `mcpjam cloud images list`). `off` uses the provider's default base image | | `--client ` | No | Replace client attachments (by name/ID) | | `--host ` | No | **Deprecated** alias for `--client`. Passing both is an error. | | `--model ` | No | Execution model ID | | `--system-prompt ` | No | Execution system prompt | | `--temperature ` | No | Execution temperature | | `--min-accuracy ` | No | Minimum accuracy, 0–100 | | `--min-iterations <1-10\|off>` | No | Floor on per-case iterations — every case runs at least this many times. `off` removes the floor | | `--tool-call-order ` | No | Tool call order | | `--arguments ` | No | Argument matching | | `--extra-tool-calls ` | No | Allowed extra tool calls | | `--judge ` | No | Turn LLM-as-judge grading on/off. `on` both enables the judge and sets it to grade every run as it completes; `off` stops both | | `--judge-model ` | No | Judge model ID | | `--judge-threshold <0-1>` | No | Judge pass threshold, 0–1 (a case passes when its score is at or above it) | ### `cloud eval revisions` List a suite's settings history, newest first: one entry per committed edit, with who made it, which stored fields moved, the note they left, and how many runs were launched against it. Rows carry no configuration snapshots. | Flag | Required | Description | | ------------------------ | -------- | ------------------------------------------------------------------ | | `--suite ` | Yes | Eval suite name or ID | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | | `--limit ` | No | Maximum revisions to return, 1–100 (defaults to 25) | | `--cursor ` | No | Pagination cursor from a previous page | ```bash theme={"theme":"css-variables"} mcpjam cloud eval revisions --suite "Asana MCP Evals" --format json ``` ### `cloud eval github list` List the repositories whose pull requests run an eval suite, plus the repositories the MCPJam GitHub App can reach. This subgroup was `cloud eval checks`, and `mcpjam cloud eval checks list|connect` still works. It was renamed because `checks` under `cloud eval` already meant a case's **grading rules** — the suite's checks, the app's Checks section, `create_eval_case`'s `checks` — while these two commands manage **GitHub** checks. Two resources under one noun. | Flag | Required | Description | | ------------------------ | -------- | ------------------------------------------------------------------------------------------------------- | | `--project ` | No | Project name or ID — only used to pick the organization (defaults to the most recently updated project) | `available: false` means GitHub Checks is not enabled for the organization at all, so connecting a repository will not help. `connectable: null` means the App could not be asked (GitHub unavailable, or no installation) — different from an empty list, which would mean it reaches nothing. ### `cloud eval github connect` Run this suite on every pull request to a repository. **Affects everyone who opens a pull request on that repository**, and with `fail-closed` it can block their merges. | Flag | Required | Description | | ------------------------------------------ | -------- | ---------------------------------------------------------------------------- | | `--suite ` | Yes | Eval suite name or ID | | `--repo ` | Yes | Repository to connect. Must be one `eval github list` reports as connectable | | `--outage-policy ` | Yes | What the check reports when MCPJam cannot conclude | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | `--outage-policy` has no default on purpose: it decides what happens to other people's pull requests during an MCPJam outage, and it is not editable from this command afterwards. Retargeting a repository at a different suite, pausing it, and disconnecting all live in the app's Settings → Integrations, where every connected repository is visible at once. Connecting also turns on pull-request comments: MCPJam posts one comment on each pull request to the repository and keeps that same comment updated on every later push. Comments are on for every connected repository, and can be turned off per repository in the app's Settings → Integrations → GitHub. ### `cloud eval delete` Permanently delete an eval suite (and its cases and runs). | Flag | Required | Description | | ------------------------ | -------- | ------------------------------------------------------------------ | | `--suite ` | Yes | Eval suite name or ID | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | ### `cloud eval schedule` Enable or disable scheduled runs for a suite. | Flag | Required | Description | | ---------------------------- | -------- | -------------------------------------------------------------------- | | `--suite ` | Yes | Eval suite name or ID | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | | `--enable` | No | Enable scheduled runs | | `--disable` | No | Disable scheduled runs | | `--interval ` | No | Run interval in minutes (5–10080) | | `--environment ` | No | Project environment the scheduled runs launch (only with `--enable`) | Pass either `--enable` or `--disable` (not both). A scheduled run launches exactly one run, so a suite with several attached environments must pin one with `--environment`; a suite with exactly one defaults to it. `--environment` is rejected with `--disable`: disabling preserves the existing pin, so accepting one there would silently do nothing. ### `cloud eval environments` subcommands Attach or detach the project environments an eval suite runs against. Attaching is what makes `eval run --environment` available for the suite. #### `cloud eval environments set` Replace the suite's attached environments (this sets the whole list, in order). | Flag | Required | Description | | ------------------------------- | -------- | ------------------------------------------------------------------ | | `--suite ` | Yes | Eval suite name or ID | | `--environment ` | Yes | Project environments to attach, in order | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | ```bash theme={"theme":"css-variables"} mcpjam cloud eval environments set --suite "Checkout smoke" --environment Staging Prod ``` Rejected if it would strand an enabled schedule pinned to an environment being removed — repoint the schedule (`eval schedule --enable --environment ...`) or disable it first. #### `cloud eval environments clear` Detach every environment, reverting the suite to its saved server selection. | Flag | Required | Description | | ------------------------ | -------- | ------------------------------------------------------------------ | | `--suite ` | Yes | Eval suite name or ID | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | Subject to the same schedule guard as `set`: rejected if the suite has an enabled schedule pinned to one of the environments being removed. Repoint the schedule or disable it first. ### `cloud eval cases` subcommands #### `cloud eval cases list` List a suite's test cases. | Flag | Required | Description | | ------------------------ | -------- | ------------------------------------------------------------------ | | `--suite ` | Yes | Eval suite name or ID | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | #### `cloud eval cases get` Show one test case. | Flag | Required | Description | | ------------------------ | -------- | ------------------------------------------------------------------ | | `--suite ` | Yes | Eval suite name or ID | | `--case ` | Yes | Eval case title or ID | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | #### `cloud eval cases run` Run a single case as a persisted, fully-queryable run — inspect it with `eval iterations` / `eval steps` like any other run. | Flag | Required | Description | | --------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `--suite ` | Yes | Eval suite name or ID | | `--case ` | Yes | Eval case title or ID | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | | `--server ` | No | Override the suite's saved servers for this run | | `--environment ` | No | Project environment to run against (must be attached to the suite) | | `--client ` | No | Attached client to run against, so the run is stamped with that client's config | | `--host ` | No | **Deprecated** alias for `--client`. Passing both is an error. | | `--compose-client ` | No | Compose a stack to run instead of naming a saved environment: the client it runs as. **Ephemeral** — this command has no `--save-targets` | | `--compose-host ` | No | **Deprecated** alias for `--compose-client` | | `--compose-computer ` | No | Sandbox image to pin on the composed stack | | `--compose-model ` | No | Model to run on the composed stack, instead of the host's | | `--compose-server-group ` | No | Standalone server group to pin on the composed stack | | `--compose-skill ` | No | Project-shared skill IDs to pin on the composed stack | | `--compose-secret ` | No | Project secret IDs the composed stack grants to its runs (`secrets list` shows them) | | `--repetitions ` | No | Run the case this many times (1-10) | | `--iterations ` | No | **Deprecated** alias for `--repetitions`. Passing both is an error. | | `--idempotency-key ` | No | Retry-safety key: repeating the call returns the run it already started | Every `--compose-*` flag behaves exactly as it does for `eval run` — see [Composing a target instead of naming one](#composing-a-target-instead-of-naming-one). Target selection follows the same rules ([Which target runs](#which-target-runs)), except that a single case run targets ONE thing — there is no `--all-targets` here. #### `cloud eval cases create` Add a test case to a suite (definition via `--file`/`--json`). | Flag | Required | Description | | ------------------------ | -------- | ------------------------------------------------------------------ | | `--suite ` | Yes | Eval suite name or ID | | `--project ` | No | Project name or ID (defaults to the most recently updated project) | | `--file ` | No | Case JSON body (or `-` for stdin) | | `--json ` | No | Inline case JSON (or `@file`, or `-`) | | `--title ` | No | Case title (overrides the body) | #### `cloud eval cases update` Edit a test case (definition via `--file`/`--json`). | Flag | Required | Description | | ------------------------ | -------- | ------------------------------------------------------------------ | | `--suite <id-or-name>` | Yes | Eval suite name or ID | | `--case <id-or-title>` | Yes | Eval case title or ID | | `--project <id-or-name>` | No | Project name or ID (defaults to the most recently updated project) | | `--file <path>` | No | Case JSON body (or `-` for stdin) | | `--json <json>` | No | Inline case JSON (or `@file`, or `-`) | | `--title <title>` | No | Rename the case | #### `cloud eval cases delete` Permanently delete a test case. | Flag | Required | Description | | ------------------------ | -------- | ------------------------------------------------------------------ | | `--suite <id-or-name>` | Yes | Eval suite name or ID | | `--case <id-or-title>` | Yes | Eval case title or ID | | `--project <id-or-name>` | No | Project name or ID (defaults to the most recently updated project) | #### `cloud eval cases generate` AI-generate test cases from the suite's tools (spends credits). | Flag | Required | Description | | ---------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------- | | `--suite <id-or-name>` | Yes | Eval suite name or ID | | `--project <id-or-name>` | No | Project name or ID (defaults to the most recently updated project) | | `--mode <normal\|negative>` | No | Generation mode (default `normal`) | | `--server <id-or-name...>` | No | Servers to discover tools from (default: suite's) | | `--environment <id-or-name>` | No | Discover tools from this attached environment's server set | | `--case-model <id...>` | No | Execution model(s) for the generated cases | | `--simple <n>` | No | How many easy, single-tool cases | | `--multi-tool <n>` | No | How many medium, 2+ tool cases | | `--multi-turn <n>` | No | How many multi-turn follow-up cases | | `--complex <n>` | No | How many hard / cross-server cases | | `--negative <n>` | No | How many negative (no-tool) cases | | `--vary-user-styles` | No | Vary query phrasing across a realistic range of user styles | | `--idempotency-key <key>` | No | Retry-safety key: repeating the call replays the first attempt's drafts instead of generating (and billing) again | *** ## `mcp` command ### `mcp` Run MCPJam as an MCP server over stdio so MCP clients (Claude Desktop, Claude Code, Cursor, ...) can connect to, exercise, and debug other MCP servers. See [MCPJam as an MCP server](/cli/mcp-server) for the exposed tools and client setup. ```bash theme={"theme":"css-variables"} npx -y @mcpjam/cli@latest mcp ``` The command takes no flags of its own. The global `--timeout <ms>` flag sets the default per-request timeout against target servers, and `--quiet` suppresses the startup notice on stderr. Stdout carries only JSON-RPC; never pipe other output into it. *** ## `telemetry` commands Telemetry commands inspect and configure anonymous CLI telemetry. They never emit telemetry events themselves. ### `telemetry status` Shows the effective telemetry state, install ID state, state file path, debug mode, and disable reason when disabled. This command does not create an install ID. ### `telemetry disable` Persistently disables anonymous CLI telemetry by writing `enabled: false` to the telemetry state file. If no install ID exists yet, this command does not create one. ### `telemetry enable` Persistently enables anonymous CLI telemetry. If no install ID exists yet, this command creates a random install UUID. *** ## `cloud` workspace `mcpjam cloud` is the account-bound namespace. Log in, link a project, then run evals, tunnels, and the rest of the Cloud groups. ### Cloud account session See [`cloud login` / `logout` / `whoami`](#cloud-login--logout--whoami) below. ### `cloud link` Pin the current Git worktree (or `--here` cwd) to a Cloud project by writing `.mcpjam/project.json`. No secrets. Bare `link` ignores an existing file when choosing what to pin. | Flag | Description | | ----------- | ------------------------------------------------------------------------------------------------------------------- | | `[project]` | Optional project name or ID to pin. Omit to pick the most recently updated project. | | `--here` | Write the link in the current working directory instead of the Git worktree root. | | `--remove` | Remove the nearest project link (or the current-directory link with `--here`). Cannot be combined with `[project]`. | ### `cloud status` Zero-network. Prints credential source, deployment URL, and the project selector that Cloud commands would use (flag / env / link / automatic). API keys are redacted. The report includes `credential.valid` (`true` / `false` / `null`) and `deployment.valid`. `null` means no credential is configured and is a successful informational status. An invalid explicit `--api-key` or `--api-url` still emits the complete JSON report, sets `ok: false`, includes an `error` string, and exits `1`. Other Cloud commands reject those same values with exit `2` and no report. ### `cloud organizations list` List organizations and their ids. An `sk_` key sees only its own. Use the id with `mcpjam cloud projects list --org <id>`. ### `cloud projects list` | Flag | Description | | ------------ | ---------------------------------------------------------------------------- | | `--org <id>` | Restrict the listing to one organization (ID only; see `organizations list`) | ### `cloud sessions list` List Playground chat sessions in the selected project. Same project-selection rule as other Cloud commands. | Flag | Description | | ------------------------ | ------------------------------------------------------------------ | | `--project <id-or-name>` | Project name or ID (defaults to the most recently updated project) | | `--all-projects` | List across every accessible project. Conflicts with `--project`. | | `--status <status>` | Filter by session status | | `--limit <n>` | Maximum sessions to return (1–200) | ### `cloud sessions send` Send one message to a project's MCP servers and print the reply plus the raw tool calls, per-call latency and token usage — the telemetry a participant in the conversation cannot see. **This spends model credits on every call.** `--idempotency-key` is required and must be **stable for the intent**, not regenerated per attempt. It is what makes a retry safe: with the same key, a turn that already completed is replayed rather than re-run and re-billed. A fresh key per attempt deduplicates nothing. Omit `--session` to start a conversation; pass the `sessionId` the command returns to continue one. A session created by `cloud sessions browser open` is initially unconfigured and accepts its model configuration with its first send. Configuration is fixed after the first model turn, so `--model`, `--environment`, `--server`, `--system-prompt` and `--tool-mode` are refused alongside `--session` rather than silently repinning the session. | Flag | Description | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--message <text>` | The message to send, as the user (required) | | `--idempotency-key <key>` | Stable key for this turn's intent (required). Reuse it when retrying. | | `--session <sessionId>` | Continue this session instead of starting one | | `--model <modelId>` | Provider-prefixed model id, e.g. `anthropic/claude-sonnet-5`. Required to start a session. A bare id is rejected rather than guessed — unprefixed ids are indistinguishable from local Ollama models. | | `--environment <environmentId>` | Target this environment's servers. Conflicts with `--server`. | | `--server <serverId...>` | Target these project servers. Conflicts with `--environment`. | | `--system-prompt <text>` | System prompt for the session | | `--tool-mode <mode>` | `read_only` (default) advertises only tools annotated `readOnlyHint: true`. `auto` advertises everything and **may cause real side effects**. | | `--max-steps <n>` | Maximum engine steps this turn (1–16) | | `--max-tool-calls <n>` | Cap the tool calls this turn may make. `0` answers without tools. | | `--temperature <n>` | Sampling temperature (0–2) | | `--project <id-or-name>` | Project name or ID (required to start a session) | `read_only` filters on a hint the SERVER asserts, so it is a policy this host applies rather than a guarantee it can verify. A server is free to annotate a mutating tool as read-only. <Note> Hosted session browsers require browser availability for your account and deployment. An unavailable browser reports a refusal or `browser.attached: false` with its reason. The live pane opens in the Playground when the browser workspace is enabled. </Note> Add `--browser` to advertise browser tools for this turn. The first browser turn requires `--browser-mode allow_all|read_only|allowlist` unless the selected client supplies a browser policy. Optional `--browser-origins <origin...>` and `--browser-tools <name...>` restrict that grant; `--browser-profile <profileId>` selects its initial saved profile. The grant and profile are fixed thereafter. A client's current browser policy and the session's `read_only` tool mode may further restrict execution. A continuation must opt in again with `--browser`; a plain send does not wake the desktop. Screenshots are returned as URLs and trace pointers. Use the printed **Open in Playground** link to view the transcript, take over the browser, and select **Resume agent** when finished. API session transcripts are view-only there. ### `cloud sessions browser` Drive the session browser without a model turn. All verbs except a new `open` require `--session <sessionId>`; use the public session ID, never its read-only conversation UUID or logical browser ID. | Verb | Description | | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `open` | Open or wake an existing session browser. Omit `--session` and provide `--project`, a browser policy, and a stable `--idempotency-key` to create an empty API session. | | `navigate <url>` | Navigate to an allowed URL. Use a stable `--command-id` for retries. | | `act --verb <verb>` | Act on the page; pass `--target` as JSON and optional `--value` and `--expected-state`. | | `invoke <toolKey> --input <json>` | Invoke a permitted page tool. | | `observe --mode <mode>` | Read the page; `--mode screenshot --download <dir>` saves screenshot evidence. | | `note <text>` | Append a note to the command ledger. | | `trace` | Read command receipts and screenshot URLs. | | `artifact --command-id <id> --download <dir>` | Fetch screenshot evidence for a command. | | `close` | Release the desktop; a later open or browser turn can revive the logical browser. | Desktop time is metered. The default cap is two live conversation desktops per user across projects; close one before retrying a capacity refusal. Sleeping browsers wake on the next browser command. Human takeover blocks agent actions; after `browser_in_use`, wait for the person to hand control back and observe the current page. Never repeat an `unknown` command under a new ID without inspecting its trace and page state. ### `cloud sessions show` Print a session's metadata and a window of its raw messages. Message `index` values are **absolute transcript positions** — the same indices `cloud sessions trace` spans reference, so the two outputs join on that number. A transcript that could not be read reports `transcriptUnavailable: true` and a `null` `messageCount` — never `0`, which would claim the conversation is empty. | Flag | Description | | ------------------------- | ---------------------------------------------------------------------- | | `--session <sessionId>` | The session to read (required) | | `--after-message <index>` | Start the window at this absolute transcript index | | `--limit <n>` | Messages to return (1–200) | | `--project <id-or-name>` | Optional project scope; a session in another project answers not-found | ### `cloud sessions trace` Print a session's per-turn execution spans: per-tool-call latency, token usage, and indices into the transcript. Returns the **latest turn** by default rather than the whole session — a session's spans are the largest thing the API emits, and the turn you just took is the one you are debugging. A turn whose spans could not be read reports `spansUnavailable: true` rather than an empty `spans` array: "made no calls" and "could not fetch" are opposite conclusions. | Flag | Description | | ---------------------------- | -------------------------------------------------------- | | `--session <sessionId>` | The session to trace (required) | | `--turn <turnId>` | Return exactly this turn. Conflicts with `--after-turn`. | | `--after-turn <promptIndex>` | Page forward from this turn index | | `--limit <n>` | Turns to return (1–20). Defaults to 1. | | `--no-spans` | Per-turn summaries without span payloads | | `--project <id-or-name>` | Optional project scope | *** ## `cloud login` / `logout` / `whoami` These commands manage your MCPJam platform session. `login` opens a browser for OAuth and stores the session locally; `logout` removes it; `whoami` shows the account behind the current credentials. ### `cloud login` | Flag | Default | Description | | ----------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--api-url <url>` | `https://app.mcpjam.com/api/v1` | MCPJam API base URL. The value is persisted with the session so subsequent cloud commands target the same deployment without needing `--api-url` again. Must be a valid `http(s)` URL — an invalid value exits with code `2` before any network call. | | `--no-browser` | | Print the login URL to stderr instead of opening a browser. | The `MCPJAM_API_URL` environment variable is equivalent to `--api-url`. An invalid value in either source is a hard error (exit 2). **URL resolution order for cloud commands after login:** 1. Explicit `--api-url` flag 2. `MCPJAM_API_URL` environment variable 3. The `apiUrl` stored with the OAuth session (only when the stored login is the credential; `sk_` API keys do not inherit the stored URL) 4. Production default (`https://app.mcpjam.com/api/v1`) **Login output fields:** After a successful login, the result includes: | Field | Description | | ----------- | ----------------------------------------------------------------------- | | `status` | `"logged_in"` | | `issuer` | OAuth issuer URL | | `apiUrl` | MCPJam API base URL persisted with the session | | `authFile` | Path to the stored session file | | `email` | Account email address (present when account lookup succeeded) | | `plan` | Account plan (present when account lookup succeeded) | | `expiresAt` | ISO 8601 access-token expiry (present when the token carries an expiry) | Account details (`email` and `plan`) are fetched from the platform after the OAuth exchange completes. If the lookup fails, login still succeeds and a warning is printed to stderr (suppressed with `--quiet`). ### `cloud logout` No additional flags. Removes the stored MCPJam session. When `MCPJAM_API_KEY` is set to an active `sk_` key, `logout` prints a warning to stderr (human format only) noting that the CLI remains authenticated via the environment variable even after the stored session is cleared. JSON output is unaffected. ### `cloud whoami` | Flag | Default | Description | | ----------------- | ------------------------------- | -------------------------------------------------- | | `--api-key <key>` | | MCPJam `sk_` API key (overrides `MCPJAM_API_KEY`). | | `--api-url <url>` | `https://app.mcpjam.com/api/v1` | MCPJam API base URL. | *** ## Exit codes | Code | Meaning | | ----- | ------------------------------------------------------------------------------------------------------------------------------------------ | | `0` | Success / all checks passed | | `1` | Command ran but reported a failure | | `2` | Invalid arguments or configuration | | `3` | `tasks conformance` only: the run was incomplete — its task-dependent checks never ran, so it is neither a pass nor a spec violation | | `6` | `tasks watch` only: the task needs input this run could not answer. Answer it with `tasks update`, or re-run with `--interactive` in a TTY | | `7` | `tasks watch` only: `--duration-ms` elapsed before the task reached a terminal status | | `130` | Interrupted by a signal (`tasks watch` aborted, or a second Ctrl-C during `tunnel` shutdown) | Codes above `2` are reserved for outcomes a script should act on *differently* — not merely for naming a failure. See [MCP Tasks](/cli/tasks#watch-exit-codes) for the full `tasks watch` mapping. # Server Inspection Source: https://docs.mcpjam.com/cli/server-inspection Probe, diagnose, and export MCP server connectivity and capabilities The `server` command group gives you a breadth-first view of any MCP server — connectivity, OAuth discovery, capabilities, tools, resources, and prompts — without writing code. ## Quick start ```bash theme={"theme":"css-variables"} # One command does everything: probe, connect, sweep tools/resources/prompts mcpjam server doctor --url https://your-server.com/mcp ``` If the server requires OAuth, doctor reports `oauth_required` with the discovery metadata. See [OAuth login](/cli/oauth-login) to obtain a token, then re-run with `--oauth-access-token`. ## Commands ### `server probe` Stateless HTTP probe — no full client connection. Tests transport selection, OAuth discovery, and `WWW-Authenticate` parsing. ```bash theme={"theme":"css-variables"} mcpjam server probe --url https://your-server.com/mcp ``` Use this when you want to check reachability and OAuth metadata without triggering a full MCP initialize handshake. The probe sends an unauthenticated `initialize` request and inspects the response. **What it returns:** * Transport type (streamable-http, SSE, or failed) * OAuth metadata (resource metadata URL, authorization server metadata, registration strategies, scopes) * `WWW-Authenticate` header parsing Sensitive fields such as `Authorization` headers are automatically redacted to `[REDACTED]` in the printed result. ### `server doctor` Combined triage — runs probe, then attempts a full client connection, then sweeps tools, resources, resource templates, and prompts. Returns a single JSON artifact. ```bash theme={"theme":"css-variables"} # Human-readable summary mcpjam server doctor --url https://your-server.com/mcp # Full JSON artifact with RPC logs mcpjam server doctor --url https://your-server.com/mcp --rpc --out doctor.json # Stdio server mcpjam server doctor --command node --args server.js --cwd /path/to/project ``` **What it returns:** * `status`: `ready`, `oauth_required`, or `error` * Probe results (transport, OAuth discovery) * Initialization info and negotiated capabilities * Counts: tools, resources, resource templates, prompts The `--out <path>` flag writes the full JSON artifact to a file — useful for handoff to another engineer or agent. For stdio targets, those artifacts only record the explicit env keys you passed via `-e/--env`; inherited shell variables are not enumerated. Sensitive fields such as `Authorization` headers are automatically redacted to `[REDACTED]` in all printed output and written artifacts, including RPC logs attached via `--rpc`. ### `server info` Get initialization info for a connected server. ```bash theme={"theme":"css-variables"} mcpjam server info --url https://your-server.com/mcp --access-token $TOKEN ``` Returns the server's `serverInfo` (name, version) and `capabilities` from the MCP initialize response. ### `server capabilities` Get resolved server capabilities. ```bash theme={"theme":"css-variables"} mcpjam server capabilities --url https://your-server.com/mcp --access-token $TOKEN ``` ### `server validate` Connect to a server and verify the debugger surface works. ```bash theme={"theme":"css-variables"} mcpjam server validate --url https://your-server.com/mcp --access-token $TOKEN ``` ### `server ping` Simple connectivity check. ```bash theme={"theme":"css-variables"} mcpjam server ping --url https://your-server.com/mcp ``` ### `server export` Export a full snapshot of the server's tools, resources, prompts, and capabilities as JSON. ```bash theme={"theme":"css-variables"} mcpjam server export --url https://your-server.com/mcp --access-token $TOKEN > snapshot.json ``` ## Shared server flags Every `server` subcommand accepts these flags for specifying how to connect: | Flag | Description | | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | | `--transport <transport>` | Explicit transport type (`http` or `stdio`) | | `--url <url>` | HTTP MCP server URL | | `--access-token <token>` | Bearer access token | | `--oauth-access-token <token>` | OAuth bearer access token | | `--refresh-token <token>` | OAuth refresh token | | `--client-id <id>` | OAuth client ID (used with `--refresh-token`) | | `--client-secret <secret>` | OAuth client secret (used with `--refresh-token`) | | `--credentials-file <path>` | Load OAuth credentials from a file created by `oauth login --credentials-out` or `oauth conformance --credentials-out` | | `--header <header>` | HTTP header in `Key: Value` format (repeatable) | | `--client-capabilities <json>` | Client capabilities as inline JSON, `@path`, or `-` for stdin | | `--command <command>` | Command for a stdio server | | `--args <arg...>` | Preferred stdio command arguments | | `--command-args <arg>` | Legacy stdio command argument (repeatable) | | `-e, --env <env...>` | Stdio environment `KEY=VALUE` values | | `--cwd <path>` | Working directory for the stdio child process | Stdio child processes inherit the parent shell environment by default. Use `-e/--env` to add values or override inherited ones, and `--cwd` when the command must run from a project directory. If you pass `--transport`, the CLI validates that it matches the target flags you provided instead of silently inferring the transport. ## Common patterns ### Triage an unknown server ```bash theme={"theme":"css-variables"} # Start with doctor mcpjam server doctor --url https://unknown-server.com/mcp # If it returns oauth_required, login first mcpjam oauth login --url https://unknown-server.com/mcp \ --protocol-version 2025-11-25 --registration dcr # Then re-run with the token mcpjam server doctor --url https://unknown-server.com/mcp --oauth-access-token $TOKEN ``` For multi-command sessions, save credentials to a file to avoid re-extracting the token: ```bash theme={"theme":"css-variables"} mcpjam oauth login --url https://unknown-server.com/mcp --credentials-out creds.json mcpjam server doctor --url https://unknown-server.com/mcp --credentials-file creds.json mcpjam tools list --url https://unknown-server.com/mcp --credentials-file creds.json ``` ### Compare before/after a deploy ```bash theme={"theme":"css-variables"} # Capture a snapshot before mcpjam server export --url https://your-server.com/mcp --access-token $TOKEN > before.json # Deploy... # Capture after mcpjam server export --url https://your-server.com/mcp --access-token $TOKEN > after.json # Diff diff <(jq -S . before.json) <(jq -S . after.json) ``` ### CI health check ```bash theme={"theme":"css-variables"} mcpjam server doctor --url $MCP_SERVER_URL --access-token $TOKEN --format json | jq '.status' # exits 0 if ready, 1 if not ``` # MCP Tasks Source: https://docs.mcpjam.com/cli/tasks Create, inspect, answer and watch MCP Tasks from the CLI — legacy 2025-11-25 and the SEP-2663 extension The `tasks` commands give the CLI the manual-testing loop the Inspector's Tasks tab has: create a task from a tool call, read it, answer its input, cancel it, and watch it run to a terminal status — on whichever tasks wire the server resolves to. For judging a server's *conformance* to the tasks spec, see [MCP Tasks Conformance](/cli/tasks-conformance). These commands do the opposite job: they assume the server works and let you drive it. <Note> Every task command connects directly to the server you name, exactly like `tools call`. Tasks are not available in hosted mode. </Note> ## The two wires A connection resolves to exactly one tasks wire, and the verbs differ between them. Ask before you assume: ```bash theme={"theme":"css-variables"} mcpjam tasks capabilities --url https://your-server.com/mcp ``` ```json theme={"theme":"css-variables"} { "wire": "extension", "toolCalls": true, "list": false, "cancel": true, "update": true, "inlineResult": true } ``` `tasks capabilities` always exits `0` — `"wire": "none"` is a valid answer, not a failure. Every other task command exits `1` on a server with no tasks wire. | | `legacy` (2025-11-25) | `extension` (SEP-2663, 2026-07-28) | | ---------------- | ----------------------------- | ---------------------------------------------------------- | | Created by | `params.task` on `tools/call` | a per-call `CreateTaskResult` opt-in | | Client-set TTL | yes (`--task-ttl`) | no — the server owns it | | `tasks list` | yes | no (removed by SEP-2663) | | Result | separate `tasks result` call | inline on `tasks get` | | `input_required` | out-of-band elicitation | `inputRequests` on `tasks get`, answered by `tasks update` | Pass `--wire legacy` or `--wire extension` to any command to assert the wire you expect; a mismatch exits `1` rather than quietly running against the other one. <Note> On the legacy wire the sub-capabilities are independent: a server that declares only `tasks.cancel` still resolves to `wire: "legacy"`. Commands check the specific capability they need and exit `1` with `TASKS_UNSUPPORTED` if the server never declared it, rather than issuing a request it did not advertise. `tasks list` is the one that would otherwise mislead — the SDK turns a method-unavailable response into an empty list, which reads as "no tasks running" when the truth is "this server cannot tell you". </Note> ## Creating a task Add `--task` to a normal `tools call`. The CLI picks the right opt-in for the resolved wire: ```bash theme={"theme":"css-variables"} mcpjam tools call \ --url https://your-server.com/mcp \ --tool-name long_job \ --task ``` ```json theme={"theme":"css-variables"} { "status": "task_created", "wire": "extension", "task": { "resultType": "task", "taskId": "ext-task-1", "status": "working", "ttlMs": 60000 } } ``` If the server decides the call is cheap enough to answer synchronously — legal on the extension wire — you get the ordinary tool result instead, plus a note on stderr that no task materialized. A synchronous answer keeps the ordinary `tools call` exit contract: a result with `isError: true` exits `1`, exactly as it would without `--task`. The 0-on-`isError` rule below applies only to a task that completed. `--task-ttl <ms>` requests a TTL. It applies only to the legacy wire; on the extension wire it is a usage error (exit `2`), because SEP-2663 gives the client no say in the TTL. `--task` cannot be combined with `--ui`, `--reporter`, `--validate-response`, or `--expect-success`: a task-creation envelope is not a `CallToolResult`, and those flags all assume one. ## Inspecting a task ```bash theme={"theme":"css-variables"} mcpjam tasks get --task-id ext-task-1 --url https://your-server.com/mcp mcpjam tasks list --url https://your-server.com/mcp # legacy wire only mcpjam tasks result --task-id legacy-task-1 --url … # legacy wire only mcpjam tasks cancel --task-id ext-task-1 --url … ``` A task id the server does not know — never created, or expired past its TTL — comes back as `TASK_UNKNOWN_OR_EXPIRED` with exit `1`. `tasks result` exists only on the legacy wire. On the extension wire a completed task carries its result inline, so `tasks result` exits `1` and points you at `tasks get`. `tasks cancel` on the extension wire is **cooperative**: the ack is empty, the task may still be running, and it may still reach `completed`. Re-poll with `tasks get`. ## Answering `input_required` On the extension wire a task that needs input exposes `inputRequests` on `tasks get`. Answer them with `tasks update`: ```bash theme={"theme":"css-variables"} mcpjam tasks update \ --url https://your-server.com/mcp \ --task-id ext-task-1 \ --input-responses '{"name": {"action": "accept", "content": {"name": "Luca"}}}' ``` `--input-responses` accepts inline JSON, `@path`, or `-` for stdin, like `--tool-args`. The ack is empty by design — the task's status does not move until a later `tasks get`. Only `elicitation/create` requests are answerable. A task asking for `roots/list` or `sampling/createMessage` is reported as an unanswered rejection; the Inspector UI cannot answer those either. ## Watching a task `tasks watch` polls a task until it reaches a terminal status, printing transitions to stderr and a single envelope to stdout: ```bash theme={"theme":"css-variables"} mcpjam tasks watch --url https://your-server.com/mcp --task-id ext-task-1 ``` ```json theme={"theme":"css-variables"} { "outcome": "completed", "wire": "extension", "taskId": "ext-task-1", "task": { "status": "completed", "result": { "content": [{ "type": "text", "text": "done" }] } } } ``` Or create and watch in one invocation, over one connection: ```bash theme={"theme":"css-variables"} mcpjam tools call --url … --tool-name long_job --task --task-watch ``` which emits `{ "created": …, "watch": … }` and exits with the watch's code. Interrupting before the task exists — Ctrl-C while the `tools/call` is still in flight — emits `{ "status": "aborted", "phase": "create", "wire": … }` and exits `130`, the same code an interrupted watch uses. There is no task id to report yet, so `phase` says where the interrupt landed instead. ### Pacing `--poll-interval-ms` sets **your** minimum interval. The server's advertised `pollIntervalMs`, any `Retry-After` the transport surfaces on a failed read, and error backoff still apply as floors on top of it, so the CLI cannot poll a server faster than that server asked for. `--duration-ms` bounds the whole watch (default 5 minutes). `--max-input-rounds` and `--max-consecutive-errors` bound the input loop and transient read failures. ### Interactivity `--interactive` answers the task's elicitations from the terminal. Like every other interactive verb, it needs a real TTY on stdin: piped or redirected stdin, `--yes`, `CI`, or `MCPJAM_NON_INTERACTIVE` all make it decline cleanly rather than prompt into the void. A declined or unanswered input ends the watch with exit `6`, and you can answer out of band with `tasks update` and watch again. <Warning> **`--interactive` answers task input on the extension wire only.** The legacy wire has no `tasks/update`, and its `Task` carries no `inputRequests` — the request arrives out of band as an `elicitation/create` stamped with `relatedTaskId`, which a polling loop cannot correlate with the task it is watching. `tasks watch` therefore reports `input_required` on that wire and exits `6` rather than polling an unchanging status until `--duration-ms` runs out. Drive a legacy task that needs input from the Inspector UI, which reads the elicitation off a live stream. </Warning> Ctrl-C (or `SIGTERM`) aborts the watch cleanly: you still get an envelope, with `"outcome": "aborted"` and exit `130`. <Note> `tasks watch` does not consult the host tasks policy. Like the Inspector's Tools tab, it is a debugging affordance — the policy is a modelling concern for hosts, not a boundary on manual testing. </Note> ### Watch exit codes | Code | Outcome | | ----- | ----------------------------------------------------------------- | | `0` | `completed` | | `1` | `failed`, `cancelled`, `expired`, `unreachable` | | `6` | `input-required` — the task needs input this run could not answer | | `7` | `timeout` — `--duration-ms` elapsed first | | `130` | `aborted` — interrupted by a signal | A completed task whose result carries `isError: true` still exits `0`: the task finished, and the tool's error is in the envelope. Branch on `task.result.isError` if you need to fail on it. Outcomes that collapse onto `1` are still distinguishable — `outcome` is always in the envelope. # MCP Tasks Conformance Source: https://docs.mcpjam.com/cli/tasks-conformance Validate MCP Tasks wire behavior — legacy and SEP-2663 extension — from the CLI The `tasks conformance` command validates the MCP Tasks wire your server exposes: which wire the connection resolves to, whether declaration hygiene holds for that wire, and whether the server honours the observable parts of the contract (result-type discipline, `-32021` on undeclared requests, TTL shapes, inline results, and `Mcp-Name` routing for HTTP transports). <Note> Tasks conformance provokes and then polls a real task, so it requires a persistent connection. It is not available in hosted mode. </Note> ## Quick start ```bash theme={"theme":"css-variables"} mcpjam tasks conformance --url https://your-server.com/mcp ``` For a local stdio server: ```bash theme={"theme":"css-variables"} mcpjam tasks conformance --command node --args server.js --cwd /path/to/project ``` On the extension wire you must name the probe tool. Auto-selection reads `execution.taskSupport`, which the 2026-07-28 `ToolSchema` strips, so a tasks-extension server cannot advertise which tool creates a task. Without `--tool-name` the six task-dependent checks cannot run and the command reports `"outcome": "incomplete"` (exit code `3`) rather than a green run: ```bash theme={"theme":"css-variables"} mcpjam tasks conformance \ --url https://your-server.com/mcp \ --tool-name long_job ``` A `--tool-name` the server does not list is treated the same way: the run is incomplete, and the message names both the tool you asked for and the tools the server actually lists. Emit CI-friendly JUnit XML: ```bash theme={"theme":"css-variables"} mcpjam tasks conformance \ --url https://your-server.com/mcp \ --reporter junit-xml > tasks-report.xml ``` ## What it checks | Check id | Category | What it asserts | | -------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `tasks-wire-resolvable` | `dispatch` | The negotiated protocol version and capabilities resolve to exactly one tasks wire. | | `tasks-declaration-hygiene` | `dispatch` | Outbound requests carry `params.task` only on the legacy wire and the extension declaration only on the extension wire. | | `tasks-result-type-discipline` | `creation` | A task-eligible `tools/call` returns either a normal tool result or a flat `CreateTaskResult` with `resultType: "task"` and a non-empty `taskId`. The discriminator is required whether absent or wrong — it is the only signal that separates a task from a standard result. | | `tasks-undeclared-creation-refused` | `creation` | On the extension wire, a `tools/call` that did not carry the extension declaration must not come back as a `CreateTaskResult`; the server must answer normally or reject with `-32021`. | | `tasks-ttl-shape` | `lifecycle` | TTL and poll interval use the era-native shapes: `ttlMs`/`pollIntervalMs` on the extension, `ttl`/`pollInterval` on the legacy wire. | | `tasks-inline-result` | `lifecycle` | A completed extension task carries its result inline on `tasks/get`; a legacy task exposes it via `tasks/result`. | | `tasks-mcp-name-routing` | `lifecycle` | Over HTTP, `tasks/get` is sent with `Mcp-Name` set to the task id. | | `tasks-undeclared-capability-rejected` | `lifecycle` | `tasks/get`, `tasks/update`, `tasks/cancel` and a task-filtered `subscriptions/listen` sent WITHOUT the extension declaration must each be rejected with `-32021` (Missing Required Client Capability). | ## Categories and check ids Three categories are available: * `dispatch` * `creation` * `lifecycle` Use `--category` to run a subset by category, or `--check-id` to run specific checks: ```bash theme={"theme":"css-variables"} # Dispatch checks only mcpjam tasks conformance \ --url https://your-server.com/mcp \ --category dispatch # Specific checks mcpjam tasks conformance \ --url https://your-server.com/mcp \ --check-id tasks-ttl-shape \ --check-id tasks-inline-result ``` <Note> If you pass both `--category` and `--check-id`, the explicit `--check-id` selection wins. </Note> ## Options | Flag | Description | | ----------------------- | ---------------------------------------------------------------------------------------------------------- | | `--category <category>` | Check category to run. Repeat for multiple. Default: all. | | `--check-id <id>` | Specific check id to run. Repeat for multiple. Default: all. | | `--tool-name <tool>` | Tool used to provoke a task. Required for servers whose tools carry no task metadata (the extension wire). | | `--tool-args <json>` | Tool arguments as a JSON object. | | `--poll-timeout <ms>` | How long to poll a created task for a terminal status. Default: `30000`. | | `--reporter <reporter>` | Structured reporter output: `json-summary` or `junit-xml`. | ## Example output ```bash theme={"theme":"css-variables"} mcpjam tasks conformance \ --url https://your-server.com/mcp \ --tool-name long_job \ --format json ``` Typical success summary: ```json theme={"theme":"css-variables"} { "passed": true, "outcome": "passed", "summary": "8/8 checks passed, 0 failed, 0 could not run, 0 not applicable", "discovery": { "protocolVersion": "2026-07-28", "wire": "extension", "toolCount": 3, "taskCapableToolCount": 1, "probedTool": "long_job", "createdTaskId": "task-abc123" } } ``` ## Outcomes: passed, failed, incomplete A run reports one of three outcomes, and `passed` is `true` only for the first: | `outcome` | Exit code | Meaning | | ------------ | --------- | ------------------------------------------------------------------------------------------------------------ | | `passed` | `0` | Every selected check either ran and passed, or does not apply to this server. | | `failed` | `1` | At least one check found a violation. | | `incomplete` | `3` | Nothing failed, but at least one selected check could not be run, so the run does not establish conformance. | Every skipped check says which kind of skip it is in `skipReason`: * `not-applicable` — the check cannot apply here (an extension-only check on a legacy connection, `Mcp-Name` routing over stdio, any task check on a connection with no tasks wire). These never hold a run back. * `could-not-run` — the check applies but was never exercised (no probe tool resolved, the named tool is not listed, the tool produced no task, the task never became readable). These make the run `incomplete`. An incomplete run carries a root `incompleteReason` naming the checks that did not run and what to change; non-quiet runs also print it to stderr. A skipped check can never add up to a passing verdict. ## Shared connection flags `tasks conformance` uses the same shared target flags as the rest of the CLI: | Flag | Description | | ------------------------------ | ------------------------------------------------------------- | | `--transport <transport>` | Explicit transport type (`http` or `stdio`) | | `--url <url>` | HTTP MCP server URL | | `--access-token <token>` | Bearer access token | | `--oauth-access-token <token>` | OAuth bearer access token | | `--refresh-token <token>` | OAuth refresh token | | `--client-id <id>` | OAuth client ID (with `--refresh-token`) | | `--client-secret <secret>` | OAuth client secret (with `--refresh-token`) | | `--header <header>` | HTTP header in `Key: Value` format (repeatable) | | `--client-capabilities <json>` | Client capabilities as inline JSON, `@path`, or `-` for stdin | | `--command <command>` | Command for a stdio server | | `--args <arg...>` | Preferred stdio command arguments | | `--command-args <arg>` | Legacy stdio command argument (repeatable) | | `-e, --env <env...>` | Stdio environment `KEY=VALUE` values | | `--cwd <path>` | Working directory for the stdio child process | `--transport` is optional; without it, `--url` implies HTTP and `--command` implies stdio. ## Notes * Exit codes are CI-friendly: `0` when all selected checks pass, `1` when any check fails, `2` for invalid command usage, and `3` when the run is incomplete because a selected check could not be run. * Declaration hygiene is asserted against captured outbound JSON-RPC bytes, not re-derived from intent. * The undeclared-request checks (`tasks-undeclared-creation-refused`, `tasks-undeclared-capability-rejected`) apply to the extension wire only and are skipped on the legacy wire. * `tasks-undeclared-capability-rejected` needs a live task to probe with, so it is reported as `could-not-run` (and the run as incomplete) when no task could be provoked. Its probes run last, after every check that reads the task, because a server that wrongly accepts an undeclared `tasks/update` or `tasks/cancel` would otherwise mutate the very task the other checks inspect. If the server does not implement `subscriptions/listen` at all (`-32601`), that sub-probe is reported as a warning instead of a failure. * The `tasks-mcp-name-routing` check applies to HTTP transports only and is skipped for stdio servers. * Checks that require a created task cannot run when no probe tool resolves. That is a `could-not-run` skip, so the command exits `3` instead of reporting a pass — pass `--tool-name` to fix it. ## Related commands * [Apps conformance](/cli/apps-conformance) for MCP Apps surface validation * [Server inspection](/cli/server-inspection) for breadth-first connectivity and capability triage * [Command reference](/cli/reference) for the full flag list # CLI Telemetry Source: https://docs.mcpjam.com/cli/telemetry Anonymous command-level telemetry, opt-outs, and debug mode `mcpjam` collects anonymous command-level telemetry so we can understand CLI usage and reliability. Telemetry is designed for aggregate product insight, not user or server inspection. Telemetry is enabled by default. The first command invocation that is not opted out writes `telemetry.json` with `enabled: true` and a random install UUID. ## What's collected The CLI emits one `cli_command` event after a command action starts. Event properties are limited to: | Property | Description | | -------------- | ------------------------------------------------------------------ | | `platform` | Always `cli` | | `command` | Command and subcommand names only, such as `server probe` | | `success` | Whether the command exited successfully | | `exit_code` | CLI exit code | | `duration_ms` | Command duration in milliseconds | | `error_code` | Normalized error code, not the error message | | `cli_version` | Installed CLI version | | `os` | Operating system, such as `darwin`, `linux`, or `win32` | | `arch` | CPU architecture | | `node_version` | Node.js version | | `transport` | `http` or `stdio`, inferred from flag presence | | `is_ci` | Whether the command appears to be running in CI | | `ci_name` | Coarse CI provider enum, such as `github_actions`, only when in CI | ## What's not collected Telemetry does not collect raw argv, URLs, hostnames, ports, tokens, headers, environment values, working directories, file paths, tool names, resource names, prompt names, error messages, stack traces, repository names, branch names, workflow names, or CI job IDs. ## Install ID Telemetry uses a random install UUID as the event `distinct_id`. The UUID is stored in the same platform cache directory used by update checks, in a `telemetry.json` file: | Platform | Cache directory | | -------- | --------------------------------------------- | | macOS | `~/Library/Caches/mcpjam` | | Linux | `$XDG_CACHE_HOME/mcpjam` or `~/.cache/mcpjam` | | Windows | `%LOCALAPPDATA%\\mcpjam\\Cache` | The install ID is created on the first real telemetry capture or when you run `mcpjam telemetry enable`. Running `mcpjam telemetry disable` before an ID exists does not create one. ## Opt out Disable telemetry for one invocation: ```bash theme={"theme":"css-variables"} mcpjam --no-telemetry server doctor --url https://your-server.com/mcp ``` Disable telemetry persistently: ```bash theme={"theme":"css-variables"} mcpjam telemetry disable ``` Check or re-enable telemetry: ```bash theme={"theme":"css-variables"} mcpjam telemetry status mcpjam telemetry enable ``` Environment opt-outs: | Variable | Effect | | ----------------------------- | ----------------- | | `DO_NOT_TRACK=1` | Disable telemetry | | `MCPJAM_TELEMETRY_DISABLED=1` | Disable telemetry | `--no-telemetry` overrides environment and persisted state for the current invocation. Disable environment variables override persisted state. ## Debug mode Set `MCPJAM_TELEMETRY_DEBUG=1` to print the exact sanitized payload to stderr instead of sending it: ```bash theme={"theme":"css-variables"} MCPJAM_TELEMETRY_DEBUG=1 mcpjam server probe --url https://your-server.com/mcp ``` Debug mode only logs when telemetry is otherwise enabled. If `--no-telemetry`, `DO_NOT_TRACK=1`, `MCPJAM_TELEMETRY_DISABLED=1`, or persisted disabled state applies, no payload is logged or sent. # Tools, Resources & Prompts Source: https://docs.mcpjam.com/cli/tools-resources-prompts List, read, and call MCP server tools, resources, and prompts Once connected (with or without auth), the `tools`, `resources`, and `prompts` command groups let you explore and exercise what the server exposes. ## Running commands as a host Add `--host <id>` to any `tools`, `resources`, or `prompts` command to connect the way a real host would — sending that host's `clientInfo`, advertised `clientCapabilities`, and protocol version in the MCP `initialize` handshake, exactly as the Inspector Playground does. ```bash theme={"theme":"css-variables"} mcpjam tools list --url https://your-server.com/mcp --host claude ``` Valid host IDs match the presets in the Inspector: `claude`, `chatgpt`, `cursor`, `copilot`, `codex`, `mcpjam`. **Tool visibility with `--host`:** `tools list --host` hides tools whose `_meta.ui.visibility` is `["app"]` — those are app-only tools the host's model cannot see. The output includes a `host` field and a `toolsDroppedVisibility` count. Hosts that opt out of visibility filtering (such as `cursor`) keep all tools. `tools call --host` rejects app-only tools with a usage error; omit `--host` to call them as an operator. **Conflict with `--client-capabilities`:** `--host` and `--client-capabilities` both set the exact advertised capabilities and cannot be combined. The CLI exits with a usage error if both are passed. ## Interactive multi-round-trip (`input_required`) Add `--interactive` to `tools call`, `prompts get`, or `resources read` to handle servers that return an `input_required` result. When the server embeds an elicitation request, the CLI renders it to the terminal and collects your response from stdin, then retries the original operation — repeating for as many rounds as the server needs. ```bash theme={"theme":"css-variables"} mcpjam tools call --url https://your-server.com/mcp \ --tool-name book_flight \ --tool-args '{}' \ --interactive ``` **How prompts appear:** The server's message is printed to stderr, followed by a consent prompt (`[a]ccept / [d]ecline / [c]ancel`). For form fields, each field is prompted in turn with its type shown in brackets. For URL-mode elicitations, the URL is printed as plain text and you are asked for consent — the CLI never opens a browser automatically. **Non-interactive fallback (`--yes`):** Pass `--yes` alongside `--interactive` to decline every embedded input request without prompting. This is also the automatic behavior when stdin is not a TTY or the `CI` environment variable is set — the CLI declines cleanly rather than blocking. **Stdin conflict:** `--interactive` reads answers from stdin, so it cannot be combined with `--tool-args -`, `--tool-args-stdin`, or `--prompt-args -`. Pass arguments as inline JSON or `@path` instead. **Capability advertisement:** `--interactive` advertises `elicitation: { form, url }` in the MCP `initialize` handshake. If you also pass `--client-capabilities` or `--host` and that exact set omits `elicitation`, the CLI exits with a usage error — add `"elicitation": { "form": {}, "url": {} }` to the pinned set or drop `--interactive`. ## Tools ### List tools ```bash theme={"theme":"css-variables"} mcpjam tools list --url https://your-server.com/mcp --access-token $TOKEN ``` Returns every tool the server exposes, including names, descriptions, and input schemas. ### Call a tool ```bash theme={"theme":"css-variables"} mcpjam tools call --url https://your-server.com/mcp --access-token $TOKEN \ --tool-name search_docs \ --tool-args @params.json --quiet --format json ``` Tool arguments can be inline JSON, `@path`, `-` for stdin, or `--tool-args-stdin` as a shorthand for stdin. The result includes the tool's response content. <Note> `tools call` supports `--debug-out <path>` to capture the full request/response trace for debugging. For stdio targets, the artifact records only the explicit env keys passed through `-e/--env`; inherited shell variables are not enumerated. </Note> ### Calling as a less-capable client Real MCP clients differ from each other, and from the spec. Two flags let you call a server as one of those clients, without authoring a host first — the same behaviors a `--host` carries via its `mcpProfile`: ```bash theme={"theme":"css-variables"} # Read only the first page of tools/list, like the hosts that never follow # nextCursor. Tools past page one are invisible to the call. mcpjam tools list --url https://your-server.com/mcp --first-page-only # Call as a client that never implemented MRTR. On 2026-07-28 this stops # advertising `elicitation`, so a server that would have elicited answers # -32021 instead of starting a round. mcpjam tools call --url https://your-server.com/mcp \ --tool-name confirm_delete --tool-args '{"id":"42"}' --no-mrtr ``` Unlike the header flags below, neither is HTTP-only — pagination truncation and the MRTR knob both work over stdio, so they mean the same thing with `--command`. `--first-page-only` has a second-order effect worth knowing on `2026-07-28`: the SEP-2243 mirroring source is the aggregated tool list the client cached, so a tool from page two is called with **no** `Mcp-Param-*` headers and a strict server answers `-32020`. That is not a bug in the flag — it is exactly how a real first-page-only client fails, and it is often the reason a tool "works in the inspector but not in that host". ### Debugging `Mcp-Param-*` headers (SEP-2243) On a `2026-07-28` HTTP connection, a tool argument annotated with `x-mcp-header` is mirrored into an `Mcp-Param-{Name}` request header, and a conforming server cross-checks the two. Two flags let you exercise the failure paths on purpose: ```bash theme={"theme":"css-variables"} # Call as a client that has NOT implemented the mirroring. A conforming server # should answer -32020 HeaderMismatch; the CLI surfaces that as-is. mcpjam tools call --url https://your-server.com/mcp --tool-name execute_sql --tool-args '{"region":"us-east"}' --no-param-headers # Send a deliberately WRONG value, reproducing a header/body mismatch exactly. mcpjam tools call --url https://your-server.com/mcp --tool-name execute_sql --tool-args '{"region":"us-east"}' --mcp-header Mcp-Param-Region=eu-west ``` `--mcp-header` uses `Name=Value` (not `--header`'s `Key: Value`) because a mirrored value routinely contains `:`. Repeat it for several headers; the last occurrence of a name wins. Supplying any `Mcp-Param-*` header turns the automatic mirroring **off** for that call, so the headers you pass are the only `Mcp-Param-*` ones sent. This is not a convenience — it is what makes the override work at all: the MCP client merges the mirrored values *over* caller-supplied headers, so with mirroring left on your deliberately wrong value would be silently replaced by the correct one and no `-32020` would ever occur. Headers outside the `Mcp-Param-*` family (`Mcp-Method`, `Mcp-Name`, …) are unaffected and do not change mirroring. When either flag is in play — or with `--rpc` — the CLI prints the headers that actually went out to **stderr**, including on the failure path, so stdout stays a clean machine-readable result: ``` Mcp-Param-*: Mcp-Param-Region=us-east ``` Both flags are HTTP-only: SEP-2243 mirroring is a Streamable HTTP concern, so using them with `--command` is a usage error rather than a silent no-op. ### Render a tool result in Inspector If `tools list` shows `_meta.ui.resourceUri`, deprecated `_meta["ui/resourceUri"]`, or `openai/outputTemplate` in `toolsMetadata`, the tool has interactive UI. Add `--ui` to execute it once and render the completed result in Inspector's Playground: ```bash theme={"theme":"css-variables"} mcpjam tools call --url https://your-server.com/mcp --access-token $TOKEN \ --tool-name create_view \ --tool-args @params.json \ --ui \ --quiet --format json ``` Without `--ui`, `tools call` returns the raw tool result. With `--ui`, it opens Inspector by default in a TTY and returns an envelope containing the raw `result`, `inspectorBrowserUrl`, and compact `inspectorRender` evidence. `inspectorRender.status` is the UI signal: `rendered` means Inspector accepted the render, `skipped` means the tool succeeded but the active browser client, a render precondition, or the render wait was missing, and `error` means a non-recoverable Inspector render command failed. `inspectorRender.remediation` is always present and is one of `open_browser`, `retry`, `reconnect_server`, or `none`. Skipped renders emit a stable root `warning` plus `inspectorRender.warning` with `code`, `message`, `remediation`, and optional `browserUrl`, `hasActiveClient`, and `inspectorStarted` fields. Stable skipped-render codes are `no_active_client`, `timeout`, `disconnected_server`, and `unsupported_in_mode`. Pass `--require-render` when a skipped render should become a hard error instead of a warning. Browser automation can open `inspectorBrowserUrl` itself and pass `--no-open`; `--inspector-url` is the local Inspector backend/API base URL. If you already know the Inspector browser/client URL, pass `--frontend-url <url>` to use it directly and skip health-advertised frontend checks and local dev port discovery. For agent/browser automation, either let `--ui` open Inspector automatically in a TTY, or open `http://127.0.0.1:6274/#playground` in the automation browser first and run `tools call --ui --no-open --quiet --format json`. Add `--attach-only` when startup, browser opening, and discovery should be disallowed. Agents should confirm `inspectorRender.status === "rendered"` before assuming the UI is visible, and use `inspectorRender.remediation` to recover from skipped renders. Default non-TTY `--ui` runs do not open a browser unless `--open` is passed. TTY stderr runs print the Playground URL and initial wait message unless `--quiet` is set; the elapsed-seconds heartbeat only appears when stderr is a TTY. The normal JSON output is compact; pass `--debug-out <path>` for the full render envelope. For a local stdio server: ```bash theme={"theme":"css-variables"} mcpjam tools list \ --command npx \ --args -y @modelcontextprotocol/server-everything \ --cwd $PWD \ -e DEBUG=1 ``` ## Resources ### List resources ```bash theme={"theme":"css-variables"} mcpjam resources list --url https://your-server.com/mcp --access-token $TOKEN ``` ### Read a resource ```bash theme={"theme":"css-variables"} mcpjam resources read --url https://your-server.com/mcp --access-token $TOKEN \ --resource-uri "file:///docs/readme.md" ``` Use `resources read --resource-uri ui://...` when you need to inspect raw widget HTML or other UI resources directly. ### List resource templates ```bash theme={"theme":"css-variables"} mcpjam resources templates --url https://your-server.com/mcp --access-token $TOKEN ``` ## Prompts ### List prompts ```bash theme={"theme":"css-variables"} mcpjam prompts list --url https://your-server.com/mcp --access-token $TOKEN ``` ### Get a prompt ```bash theme={"theme":"css-variables"} mcpjam prompts get --url https://your-server.com/mcp --access-token $TOKEN \ --prompt-name summarize \ --prompt-args '{"text": "Hello world"}' ``` ## Common patterns ### Enumerate the full server surface ```bash theme={"theme":"css-variables"} # One-shot: doctor already sweeps everything mcpjam server doctor --url https://your-server.com/mcp --access-token $TOKEN # Or individually for focused output mcpjam tools list --url $URL --access-token $TOKEN --format json | jq '.tools[].name' mcpjam resources list --url $URL --access-token $TOKEN --format json | jq '.resources[].uri' mcpjam prompts list --url $URL --access-token $TOKEN --format json | jq '.prompts[].name' ``` ### Test a specific tool end-to-end ```bash theme={"theme":"css-variables"} # List to find the tool mcpjam tools list --url $URL --access-token $TOKEN --format json \ | jq '.tools[] | select(.name == "search_docs")' # Call it mcpjam tools call --url $URL --access-token $TOKEN \ --tool-name search_docs \ --tool-args @params.json \ --quiet \ --format json ``` For generated payloads, pipe JSON through stdin: ```bash theme={"theme":"css-variables"} echo '{"query":"setup guide"}' | mcpjam tools call --url $URL --access-token $TOKEN \ --tool-name search_docs --tool-args - --quiet --format json ``` `--tool-args-stdin` is equivalent to `--tool-args -` and cannot be combined with `--tool-args` or `--params` in the same command: ```bash theme={"theme":"css-variables"} generate-params | mcpjam tools call --url $URL --access-token $TOKEN \ --tool-name search_docs --tool-args-stdin --quiet --format json ``` ### Using a credentials file Instead of passing `--access-token` to every command, save credentials once and reuse the file: ```bash theme={"theme":"css-variables"} # Login and save mcpjam oauth login --url https://your-server.com/mcp --credentials-out creds.json # Reuse everywhere mcpjam tools list --url https://your-server.com/mcp --credentials-file creds.json mcpjam resources list --url https://your-server.com/mcp --credentials-file creds.json mcpjam tools call --url https://your-server.com/mcp --credentials-file creds.json \ --tool-name search_docs --tool-args @params.json --quiet --format json ``` ### No-auth servers For servers that don't require authentication (like `mcp.excalidraw.com/mcp`), omit the auth flags: ```bash theme={"theme":"css-variables"} mcpjam tools list --url https://mcp.excalidraw.com/mcp mcpjam tools call --url https://mcp.excalidraw.com/mcp \ --tool-name read_me ``` Stdio child processes inherit the parent shell environment by default. Use `-e/--env` to add values or override inherited ones for local subprocesses. # XAA Debugger (CLI) Source: https://docs.mcpjam.com/cli/xaa Run the full Cross-App Access (ID-JAG) grant chain against your authorization server and MCP server, headlessly, from one command `mcpjam xaa run` drives a complete Cross-App Access flow — the [Identity Assertion Authorization Grant](https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-assertion-authz-grant/) (ID-JAG, an active IETF Internet-Draft) — against your authorization server and MCP server. MCPJam plays the **enterprise identity provider** (it mints and signs the ID-JAG) and the **client/agent** (it redeems the ID-JAG and calls your MCP server with the resulting access token). Your authorization server and MCP server are the system under test. ```bash theme={"theme":"css-variables"} mcpjam xaa run \ --url http://localhost:8788/mcp \ --issuer-base-url http://localhost:6274/api/mcp \ --sub alice@example.com \ --client-id my-registered-client ``` ## Recommended: use with Agent Skills The easiest way to use the CLI is through the [MCPJam skill](https://github.com/MCPJam/inspector/tree/main/skills/mcp-inspector), which gives your agent full context on every command and workflow. ```bash theme={"theme":"css-variables"} npx skills add mcpjam/inspector --skill mcp-inspector ``` Once installed, your agent will know how and when to invoke `mcpjam` commands automatically — and how to read `xaa run` output conservatively (advertisement is evidence, redemption is the verdict; an issuer it can't reach is your setup, not a server bug). ## The three parties Cross-App Access separates trust into three relationships that are easy to conflate. The CLI covers two of them and simulates the third: | Relationship | Who configures it | In this test | | ------------ | -------------------------------------------------------- | --------------------------------------------------------------- | | Client ↔ IdP | Enterprise SSO registration | Simulated — the CLI **is** the IdP, so this leg always succeeds | | RAS ↔ IdP | Your authorization server trusts the IdP's issuer + JWKS | **You set this up once** (see below) | | Client ↔ RAS | OAuth client registration at your authorization server | Exercised per run via `--registration` | ## Before you run: make the issuer reachable The CLI signs ID-JAGs with a local key pair (`~/.mcpjam/xaa-idp-private.pem`; override the directory with `XAA_IDP_KEY_DIR`). Your authorization server must be able to fetch the matching public key, so `--issuer-base-url` must be an origin that **publishes that same key** — not your real IdP's URL. The local MCPJam inspector serves exactly this: it publishes the issuer metadata and JWKS from the same key directory at `/api/mcp/xaa/.well-known/openid-configuration` and `/api/mcp/xaa/.well-known/jwks.json`, no authentication required. ```bash theme={"theme":"css-variables"} mcpjam inspector start # serves the issuer at http://localhost:6274/api/mcp/xaa ``` * **Local authorization server:** pass `--issuer-base-url http://localhost:6274/api/mcp`. * **Cloud authorization server:** it cannot reach your localhost. Expose the inspector origin through a tunnel and pass the public origin instead. Then register the issuer in your authorization server: trust `<issuer-base-url>/xaa` as an ID-JAG issuer and point it at the JWKS URL above. The first flow step, `verify_issuer_publication`, fails fast if the issuer origin is unreachable or publishes a different key — nothing is sent to your servers until it passes. ## What a run checks Each run walks the grant chain and reports every step: 1. **`verify_issuer_publication`** — the configured issuer publishes the CLI's local signing key 2. **`discover_resource_metadata`** — protected-resource metadata ([RFC 9728](https://www.rfc-editor.org/rfc/rfc9728)) names the authorization server protecting `--url` 3. **`discover_authz_metadata`** — authorization-server metadata ([RFC 8414](https://www.rfc-editor.org/rfc/rfc8414)) provides the token endpoint and capability advertisements 4. **`mint_id_jag`** — the mock IdP exchanges the simulated user's identity assertion for an ID-JAG (`typ: oauth-id-jag+jwt`, audience = your authorization server, `resource` = your MCP server) 5. **`redeem_id_jag`** — the ID-JAG is presented at your token endpoint via the JWT bearer grant ([RFC 7523](https://www.rfc-editor.org/rfc/rfc7523)); your server validates it and issues its own access token 6. **`authenticated_mcp_request`** — an MCP `initialize` with the issued token, advertising the [Enterprise-Managed Authorization extension](https://github.com/modelcontextprotocol/ext-auth/blob/main/specification/stable/enterprise-managed-authorization.mdx) The result also records **capability evidence** separately from operational outcomes: whether your authorization server advertises the ID-JAG grant profile and the JWT bearer grant, which token-endpoint auth method was selected, and whether your MCP server advertises the enterprise-managed authorization extension back. Missing advertisements are reported as findings but never fail a flow that operationally succeeds — redemption is the verdict, advertisement is evidence. Capability evidence fields are three-valued: `advertised`, `not_advertised` (the metadata key was present but did not include the expected value), or `unknown` (the metadata key was absent entirely). `unknown` is weaker evidence than `not_advertised` — the two are not equivalent. The decoded ID-JAG claims and a local signature-verification verdict (`idJag.verified`) are included in the result so you can inspect exactly what your authorization server received. `idJag.verified` confirms the CLI's own mint was well-formed and correctly signed; it is not evidence that your authorization server validated the assertion — the AS's behavior is in the `redemption` block. Raw tokens, assertions, and secrets are always `[REDACTED]` in the output, including when a server reflects them back inside an error body. ## Registration strategies `--registration` selects how the CLI identifies itself to your authorization server, mirroring the client-registration models MCP clients use: | Strategy | Credentials needed | Posture notes | | ---------------------------------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `preregistered` (default) | `--client-id` (+ optional `--client-secret`) | Matches the draft's recommended deployment | | `dcr` | none | A diagnostic: the CLI performs open [RFC 7591](https://www.rfc-editor.org/rfc/rfc7591) registration and supplies the IdP→RAS client mapping itself. Each run may leave a registration behind. RFC 7591 conformance findings are reported as warnings in `registration.warnings[]` | | `cimd` | none | Public client by default — the run completes but is flagged: the draft recommends confidential clients | | `cimd` + `--client-auth private-key-jwt` | none | Confidential CIMD: the CLI generates a local EC P-256 key, publishes it through the hosted reflector, and signs a `client_assertion`. The private key never leaves your machine | The `registration.warnings[]` array in the result uses these codes: | Code | Meaning | Weight | | ----------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | `public_client` | Client authenticates with no key or secret | Posture note — the draft recommends confidential clients, but this is not itself a vulnerability | | `profile_metadata_not_echoed` | DCR response omitted `authorization_grant_profiles_supported` | Informational — RFC 7591 allows a server to ignore unknown metadata | | `grant_types_not_echoed` | DCR response omitted `grant_types` | Informational — same rationale; redemption is the real signal | | `missing_no_store` | DCR credential response lacked `Cache-Control: no-store` | Low compliance finding — RFC 7591 requires this header for credential-bearing responses | For confidential CIMD, **the key is the identity**: the reflector URL derived from the public key becomes the `client_id`. Deleting `~/.mcpjam/xaa-client-private.pem` (or changing `XAA_CLIENT_PRIVATE_KEY`) mints a new identity, and any allowlisting on your authorization server must be updated. ## Identity assertion formats `--assertion-format` selects the identity rail the simulated enterprise uses: * `oidc` (default) — the mock IdP mints an OIDC ID token as the token-exchange subject token. * `saml` — the mock IdP mints a **SAML 2.0 assertion** as the subject token, and the ID-JAG carries a `saml-nameid` `sub_id` claim so a SAML-federated authorization server can resolve the user. The ID-JAG itself is always a JWT; the format flag changes the SSO leg and the subject identifier, not the grant. Either format composes with **any** registration strategy. Use `--sub` (and optionally `--email`) to control the simulated user, and `--scopes` for the permissions requested in both the ID-JAG and the token request. ## Scenarios ### First run against a local authorization server ```bash theme={"theme":"css-variables"} mcpjam inspector start # serves the issuer mcpjam xaa run \ --url http://localhost:8788/mcp \ --issuer-base-url http://localhost:6274/api/mcp \ --sub alice@example.com \ --client-id my-registered-client \ --scopes "mcp.access" ``` ### Confidential client without pre-registration ```bash theme={"theme":"css-variables"} mcpjam xaa run \ --url http://localhost:8788/mcp \ --issuer-base-url http://localhost:6274/api/mcp \ --sub alice@example.com \ --registration cimd \ --client-auth private-key-jwt ``` ### SAML-federated enterprise ```bash theme={"theme":"css-variables"} mcpjam xaa run \ --url http://localhost:8788/mcp \ --issuer-base-url http://localhost:6274/api/mcp \ --sub alice@example.com \ --client-id my-registered-client \ --assertion-format saml ``` ### CI The JSON result goes to stdout; progress and advisory notes go to stderr. The exit code is `0` when the flow completes and `1` otherwise, so a run can gate a pipeline directly. Pass `--quiet` to suppress the advisory notes. ```bash theme={"theme":"css-variables"} mcpjam xaa run \ --url "$MCP_SERVER_URL" \ --issuer-base-url "$ISSUER_ORIGIN" \ --sub ci-probe@example.com \ --client-id "$XAA_CLIENT_ID" \ --quiet > xaa-result.json ``` ## Skipping discovery * `--authz-server-issuer` pins the authorization server and skips protected-resource discovery (useful while your MCP server's RFC 9728 metadata is still in progress). * `--token-endpoint` pins the token endpoint and skips authorization-server discovery entirely. Not valid with `dcr` or `cimd`, which need the metadata document for the registration endpoint / CIMD advertisement. * `--https-only` rejects non-HTTPS and private targets; by default the CLI allows `http://localhost` for local development. ## What this is not These are targeted debugging checks for the current ID-JAG draft, not a conformance suite. The negative-test scorecard — sending deliberately broken ID-JAGs (bad signature, wrong audience, expired, and six more) and verifying your authorization server rejects each — lives in the [inspector's XAA Debugger](/inspector/xaa-debugger), which also visualizes the flow and inspects the ID-JAG interactively before it is sent. ## Related standards * [Identity Assertion Authorization Grant (ID-JAG)](https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-assertion-authz-grant/) * [OAuth 2.0 Token Exchange (RFC 8693)](https://www.rfc-editor.org/rfc/rfc8693) * [JWT Bearer Grant (RFC 7523)](https://www.rfc-editor.org/rfc/rfc7523) * [Protected Resource Metadata (RFC 9728)](https://www.rfc-editor.org/rfc/rfc9728) * [MCP Enterprise-Managed Authorization](https://github.com/modelcontextprotocol/ext-auth/blob/main/specification/stable/enterprise-managed-authorization.mdx) # Getting Started Source: https://docs.mcpjam.com/getting-started Start testing your MCP server in minutes ## 1. Open MCPJam **Web**: HTTPS only, runs in the browser with no install. Share server links with teammates. **Terminal** and **Desktop**: HTTP/S and local STDIO. <CardGroup> <Card title="Web App" icon="globe" href="https://app.mcpjam.com"> HTTPS only. No install. Share with your team. </Card> <Card title="Terminal" icon="terminal"> `npx @mcpjam/inspector@latest`. HTTP/S and local STDIO. </Card> <Card title="Desktop App" icon="monitor" href="/installation"> Mac or Windows. HTTP/S and local STDIO. </Card> </CardGroup> ## 2. Draw something On first launch, MCPJam connects the Excalidraw sample server (diagramming MCP app) and opens the **Playground** with this prompt: ``` Draw me an MCP architecture diagram ``` Press **Send**. The diagram renders in the app: tool call, then widget, then inspect. No API key, ngrok, or ChatGPT/Claude subscription needed. <Check> You should see a tool call to `create_view` followed by a rendered diagram inline in the chat. If you don't, check that the Excalidraw server shows **Connected** in the **Connect** tab. </Check> <Tip> Use the widget debug icons to open tool input/output, CSP activity, widget state, and JSON-RPC for that result. </Tip> ## 3. Connect your own server Open the **Connect** tab in the left sidebar (labelled **Servers** in some builds). Click **Add server**. * **HTTP**: Paste a URL ending in `/mcp`. The web app accepts **HTTPS** URLs only. Desktop and Terminal accept **HTTP** or **HTTPS**. Add a bearer token or use the [OAuth Debugger](/inspector/guided-oauth) if the server requires auth. * **STDIO** (Desktop and Terminal only): Paste a command such as `npx -y @modelcontextprotocol/server-everything`. Not available in the web app. See [Hosted App](/hosted/overview). Each **Connected** server is available in the Playground, Tools, Prompts, and Resources. Connect more from the **Connect** tab anytime. <Check> You should see your server show **Connected** with a green dot in the **Connect** tab, and its tools should appear in the Playground's left rail. </Check> ## 4. Where to go next <CardGroup> <Card title="Playground" icon="joystick" href="/inspector/playground"> IDE-style workspace for developing against your MCP servers. Chat with frontier models, invoke tools by hand, render OpenAI Apps SDK and MCP app widgets, and inspect every step via Chat / Trace / Raw. Compare up to 3 models side by side; debug widgets with the emulator (iframe, `window.openai`, CSP, device frames, locale). </Card> <Card title="OAuth Debugger" icon="shield-check" href="/inspector/guided-oauth"> Step through your MCP authorization flow and inspect each stage. Check conformance across protocol versions (03-26, 06-18, 11-25) and client registration paths: Dynamic Client Registration (DCR), client pre-registration, and CIMD. </Card> <Card title="Projects" icon="folder" href="/inspector/projects"> Group servers; share configuration with your team </Card> <Card title="API keys" icon="key" href="/reference/api-keys"> The three things called "API key" in MCPJam (LLM provider key, `MCPJAM_API_KEY`, per-server bearer) and when you need each one. </Card> </CardGroup> ## FAQ <AccordionGroup> <Accordion title="Does MCPJam need a database?" icon="database"> No. MCPJam Inspector runs as a hosted web app, a desktop app, or via `npx` — none of them require you to install or configure a database. Accounts, sharing, and OAuth token storage on [app.mcpjam.com](https://app.mcpjam.com) are handled by the hosted backend; the Terminal and Desktop apps store config locally on disk. </Accordion> <Accordion title="How do I check which version of Inspector I'm running?" icon="tag"> All three installs (Web, Desktop, Terminal) show the running version in the **Settings** tab under **About**. To see the latest published version on npm, run this. It's useful for confirming an `@latest` pull actually moved you forward: ```bash theme={"theme":"css-variables"} npm view @mcpjam/inspector version ``` The Desktop and Web apps update on their own. For the Terminal install, the `@latest` tag in `npx @mcpjam/inspector@latest` pulls the most recent release every run. </Accordion> <Accordion title="Can MCPJam auto-install MCP servers into my repo?" icon="package"> Not directly. MCPJam doesn't reach into your repo. The closest workflow is **Skills**: install the MCPJam CLI skill into a project so an agent working in that repo knows how to use MCP tools and call MCPJam against your servers. See [Skills](/inspector/skills). </Accordion> <Accordion title="Where are my files saved?" icon="folder"> It depends what you mean. The word covers three different things: * **OAuth credentials** from `mcpjam oauth login`: `~/.mcpjam/config.json`. Override per command with `--credentials-out`. See [CLI OAuth login](/cli/oauth-login). * **Eval results**: uploaded to your MCPJam project when `MCPJAM_API_KEY` is set to an MCPJam API key (`sk_…`). See [Saving Results](/sdk/concepts/saving-results). * **Inspector UI changes** (server config, client edits): persisted via the **Save** button in the project view. </Accordion> <Accordion title="How do I sign out of the hosted app?" icon="log-out"> Open the **account menu** at the bottom of the sidebar and pick **Sign out**. You'll be returned to the public app as a guest. See [Hosted App → Signing out](/hosted/overview#signing-out). </Accordion> </AccordionGroup> # GitHub Checks Source: https://docs.mcpjam.com/github-checks Run an eval suite against the MCP server built from every pull request, and read the result as a check run, a pull-request comment, and a machine-readable JSON block. Connect a repository and MCPJam runs the eval suite bound to it against the MCP server built from each pull request's **head commit**, then reports back as a check run named **MCPJam Evals**. Nothing about the check reads your default branch. It builds the pull request, starts the server it produces, runs the suite against that server, and reports what it measured. <Note> GitHub Checks is configured in the hosted app, at **Settings → Integrations → GitHub**. Every write on this page — connecting, installing, disconnecting, and changing per-repository settings — requires an organization owner or admin. Members can open the page to see the current setup, but all controls are disabled and a note explains why. **Connect a GitHub account** starts it. You sign in to GitHub so we can confirm which accounts you administer, then pick one from the list that comes back — an organization, or your own account. Installing the app is not on its own proof that an installation is yours to connect, which is why the sign-in step is there and not skippable. Accounts that already have the app appear in that list ready to connect. **Install on another account**, in that same list, is **not generally available yet.** During the beta the MCPJam app can only be installed on the MCPJam organization, so there is no other account for it to reach — following it sends you to the existing installation rather than offering a choice. Even once that opens up, GitHub sends an administrator who already has the app somewhere to that installation instead of asking which account to use. So for now, connect an account that already appears in the list. A GitHub account connects to **one MCPJam organization at a time**, for the whole account rather than per repository. Two organizations cannot split the repositories of one GitHub account between them. If the account is already connected elsewhere, connecting it again is refused — free it up in the organization holding it, or use a different account. Which organization is holding it is **rolling out**: until it reaches your deployment the refusal names nothing, so ask an owner of the GitHub account, who can see the installation. After it reaches you the account picker marks the account before you click, and names the holding organization when you are a member of it — when you are not, it still cannot name it, because that is a fact about an organization you cannot see. The **pull-request comment** described below is rolling out: it is enabled per deployment by an operator, and until it is on for yours a connected repository gets the check run and no comment. The per-repository toggle is visible before then and records your choice; nothing is posted until the rollout reaches you. </Note> ## Suite credentials in approved forks Each connected repository has **Allow suite credentials in approved forks** in Settings and in its suite's connected-repository section. It is off by default; only organization admins can change it. * **Off:** approved fork runs require a credential-free suite. Checks needing saved credentials report **Action required**. * **On:** approved fork runs can use that suite's existing credentials and connected services. Only approve code you trust. This does not inject saved secrets into the PR server. Every new commit needs maintainer approval. Enabling the setting does not upgrade old approvals or running jobs: approve again. Changes to selected credentials also require fresh approval. Disabling blocks further credential access and stops affected runs; credentials already delivered cannot be taken back. Changing the connected suite resets the setting off. Environments with plugin credential grants that cannot be verified remain blocked. ## `mcpjam.yaml` MCPJam can usually work out how to build and start a server on its own. When it cannot — or when you would rather say it explicitly than let detection guess — commit an `mcpjam.yaml` at the **root of the repository**. A committed file is authoritative: MCPJam runs what it says and never falls back to detection. A file that is present but invalid fails the check rather than being ignored, because ignoring it would run a server the author never declared. ```yaml mcpjam.yaml theme={"theme":"css-variables"} version: 1 checks: build: npm ci && npm run build start: node dist/server.js port: 8080 path: /mcp env: LOG_LEVEL: debug ``` ### Fields | Field | Required | What it is | | ------------------ | -------- | -------------------------------------------------------------------------------------------------------------------- | | `version` | Yes | Must be `1`. | | `checks.build` | Yes | The command that installs and compiles. An install-only ecosystem still declares one — `npm ci`, `uv sync --frozen`. | | `checks.start` | Yes | The command that starts the server. It must keep running. | | `checks.port` | Yes | An integer, 1–65535. The port your server listens on. | | `checks.path` | Yes | The MCP endpoint path. Must start with `/`. | | `checks.transport` | No | Only `streamable-http`, which is also the default. Version 1 of this file is HTTP-only. | | `checks.env` | No | Environment variables to set before `start`. | Unknown keys **inside `checks:`** are rejected — inside the authoritative section a stray key is a typo that would otherwise silently change what runs. Unknown keys at the top level are ignored, so the file can grow new sections without breaking older checks. The file itself is capped at 32 KB. ### `env` `checks.env` is a flat mapping of at most 20 entries. Names must be uppercase environment-variable names (`^[A-Z][A-Z0-9_]*$`, up to 64 characters) and values must be **strings** of up to 1024 characters — quote anything that looks like a number, a boolean, or an empty value, or the file is rejected rather than coerced. <Warning> **Never put a credential in `checks.env`.** This file is committed to the repository, so it is readable by anyone who can read the repository, and MCPJam stores its values verbatim alongside the check. It is for the flags a server needs in order to boot — `LOG_LEVEL`, `FIXTURE_MODE` — and for nothing that would matter if it were printed in public. </Warning> ### Binding and ports **The port is what has to match; the bind address is not.** MCPJam reaches your server through the sandbox's own host bridge, from inside the box, so a server bound to loopback is reachable. What fails is listening on a port other than the one `checks.port` declares, or a process that exits after startup — both are reported as an unhealthy server, and the check body says so. If your framework needs a `HOST` (or equivalent) variable set to bind the way you want, set it in `checks.env` like any other boot flag. Only add it if your framework actually needs it — it is not something MCPJam requires. ## The pull-request comment Alongside the check run, MCPJam posts **one comment per pull request** and updates that same comment in place on every later push. It is posted for every conclusion, including a pass — so the comment is always current, and there is never a thread of stale verdicts to scroll past. The comment carries: * **The verdict line** — what the check concluded on this head commit. * **What the result is not.** A check that could not produce a verdict says so in the comment rather than leaving you to infer it from a colour. * **"What to fix"** — one line per failing case, saying what to go and change. Each line names the case and the one thing to look at; it does not claim to know why the case failed. * **A collapsed JSON block**, described below, for coding agents. Comments are posted for every connected repository by default, and an admin can turn them off per repository — see [Turning comments off](#turning-comments-off). ## The machine-readable JSON block The last section of the comment is a collapsed, fenced JSON block. It exists so a coding agent working on the pull request can read the result without parsing prose, and it is stable enough to be worth parsing. It is a **bounded projection** of MCPJam's eval decision contract — not the whole contract, and not a dump of the run. It carries: | Key | What it says | | --------------------------- | ------------------------------------------------------------------------------------------------------------------- | | `schemaVersion` | The version of this projection. Read it before anything else. | | The head SHA | The commit this result describes. A result whose SHA is not the tip is out of date. | | The outcome and conclusion | What MCPJam measured, and what it reported to GitHub. | | Trial counts | How many trials ran, passed, and failed. | | One entry per failing trial | Its **first failed stage**, its failure category, and the tool names the case expected versus the ones it observed. | | `omittedFields` | The fields of the contract this projection deliberately leaves out. | Two things to know when reading it: * **It is a projection, and it says so.** Anything the projection drops is named in `omittedFields`, so a consumer can tell "MCPJam did not include this" from "MCPJam measured nothing here". Do not infer an absent field's value. * **The first failed stage is a location, not a diagnosis.** It says where the chain stopped — connection, discovery, selection, the tool call, the response, or the user's request being satisfied — and nothing about why. Treating it as a cause is how a change lands in the wrong file. ## Review comments on removed tool names When a failing case asserts a tool that was never called, MCPJam looks for that tool name in the pull request's own diff. If the diff **removes** a line containing the name in quotes, and the name does not come back on any added line, MCPJam leaves a single review comment on that removed line. That comment is a **location, not a cause**. It says: the case expected this tool, this line is where the name stopped existing, start here. It does not claim the removal is a mistake — renaming or deleting a tool on purpose is an ordinary thing for a pull request to do, and when it is, the fix is to update the case in MCPJam rather than to put the line back. MCPJam never requests changes. Review comments are posted as plain comments, so they never sit as a blocking review on your pull request. ## What appears in a comment, and what never does Comments and review comments are **public on a public repository**. What MCPJam writes there is deliberately bounded. **What can appear:** * fixed MCPJam copy — the same sentences on every repository; * the head SHA, the check-run id, and the pull-request number; * links back into MCPJam; * eval case titles, as you wrote them; * tool names; * stage reasons and counts; * the same clamped build and server output the check run already shows. **What never appears:** * organization, project, or installation identifiers; * model or provider names; * prompts; * tool-call arguments; * credentials — evidence strings are credential-redacted before they are rendered. If a case title or a tool name is itself sensitive, treat it as published: those are the two fields that carry your own words into a public comment. ## Per-repository switches Each connected repository has three labeled switches: | Switch | What it controls | | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Checks** | Whether the eval suite runs on pull requests to this repository. | | **Conformance** | Whether a conformance check runs alongside the eval suite. Disabled and dimmed while **Checks** is off, because it is a sub-setting of checks. | | **Comments** | Whether MCPJam posts a pull-request comment with the result. Independent of **Checks** — you can pause checks while still allowing MCPJam to comment, or turn comments off while checks keep running. | ## Turning comments off Comments are **on for every connected repository by default**, including repositories connected before the feature existed. An admin opts a repository out; there is nothing to opt in to. Go to **Settings → Integrations → GitHub**, find the repository in **Connected repositories**, and turn off the **Comments** switch. This setting changes nothing about the check itself — whether it runs, what it measures, and how it reports are all decided elsewhere. Only the comment stops. # My First ChatGPT App Source: https://docs.mcpjam.com/guides/first-chatgpt-app-react Build a simple Coffee Shop ChatGPT App with React We're going to create a simple Coffee Shop ChatGPT App where users can order and drink coffees! ## Getting Started Clone the repo and navigate to the Coffee Shop example: ```bash theme={"theme":"css-variables"} git clone https://github.com/MCPJam/inspector.git cd inspector/examples/chatgpt-apps/CoffeeShop ``` ## Setting up the MCP Server ### Creating the MCP Server ```typescript server.ts theme={"theme":"css-variables"} import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { createServer, IncomingMessage, ServerResponse } from "http"; const server = new McpServer({ name: "coffee-shop", version: "1.0.0" }); ``` ### Registering Resources and Tools Widget resources and tools work together. The resource provides the widget HTML, and tools reference it to display your UI: ```typescript server.ts theme={"theme":"css-variables"} // Register the widget resource server.registerResource( "coffee-widget", "ui://widget/coffee.html", // Identifier that tools reference via openai/outputTemplate { description: "Coffee Shop widget" }, async () => ({ contents: [{ uri: "ui://widget/coffee.html", mimeType: "text/html+skybridge", // Marks this as a widget that receives window.openai text: WIDGET_HTML, _meta: { "openai/widgetPrefersBorder": true, // Adds a border around your widget in the chat } }] }) ); // Register a tool that uses this widget server.registerTool( "orderCoffee", { title: "Order Coffee", description: "Order a coffee to add to your collection.", _meta: { "openai/outputTemplate": "ui://widget/coffee.html", // Which widget to display "openai/widgetAccessible": true, // Allow widget buttons to call this tool "openai/toolInvocation/invoking": "Brewing coffee...", // Loading message while tool runs "openai/toolInvocation/invoked": "Coffee ready!" // Success message when tool completes } }, async () => { // ... return { structuredContent: { // Data for widget + model reasoning coffeeCount: coffeeCount, message: "Here's your coffee! ☕️" }, content: [{ // Text the model uses to craft its response type: "text" as const, text: `Ordered a coffee! You now have ${coffeeCount} coffees.` }] }; } ); ``` ## Understanding the Widget ChatGPT Apps can display interactive widgets inside the chat. Widget resources are registered by your app and become available when the client connects to your MCP server. Your widget can be built using vanilla JavaScript or a framework like React (optionally with TypeScript), and is bundled into a self-contained HTML file. When your tool is called, the client renders this HTML inside a sandboxed iframe and injects `window.openai` into it, which is how your widget communicates with the client and invokes tools on your MCP server. ### The `window.openai` API `window.openai` provides globals and methods for calling tools, sending follow-ups, and managing layout. Here's a basic React widget structure using the `window.openai` API: ```tsx src/CoffeeShopWidget.tsx theme={"theme":"css-variables"} import { StrictMode, useState, useCallback, useRef, useEffect } from "react"; import { createRoot } from "react-dom/client"; import { useToolOutput } from "./hooks/useToolOutput"; // Listens for toolOutput changes via the openai:set_globals event import type { CoffeeToolOutput } from "./types"; // TypeScript types for window.openai and our tool's output function CoffeeShopWidget() { const toolOutput = useToolOutput(); const [state, setState] = useState<CoffeeToolOutput>({ // ... fallback values for TypeScript }); // Sync state when toolOutput changes (e.g., from chat commands) const prevToolOutputRef = useRef<CoffeeToolOutput | undefined>(undefined); useEffect(() => { if (toolOutput && toolOutput !== prevToolOutputRef.current) { prevToolOutputRef.current = toolOutput; setState(toolOutput); } }, [toolOutput]); // Call tools directly from button clicks (requires openai/widgetAccessible: true) const handleOrder = useCallback(async () => { const result = await window.openai?.callTool("orderCoffee", {}); if (result?.structuredContent) { setState(result.structuredContent); } }, []); // ... more handlers and JSX in the full example } ``` * **`window.openai.toolOutput`** - The `structuredContent` your MCP server returned. It's the data your tool sends to both the widget and the model for context * **`window.openai.callTool()`** - Lets widget buttons trigger server tools directly (requires `openai/widgetAccessible: true` in the tool's metadata) * **`openai:set_globals`** - Event that fires when users trigger tools via chat (e.g., "order me a coffee"), keeping everything in sync <Note> Our Coffee Shop stores state on the server, so the widget just reads `toolOutput`. If you need to persist state in the widget and expose it to the client, use `window.openai.widgetState` and `window.openai.setWidgetState()`. </Note> For more on the `window.openai` component bridge (file uploads, modals, follow-up messages, and more), see the [OpenAI Apps SDK docs](https://developers.openai.com/apps-sdk/reference#windowopenai-component-bridge). ### Display Modes Widgets can request different display modes to optimize their presentation: * **Inline** (default) - Widget renders within the chat message flow * **Picture-in-Picture (PiP)** - Widget floats at the top of the screen, staying visible while scrolling * **Fullscreen** - Widget expands to fill the entire viewport Widgets start in inline mode. To request a different mode: ```javascript theme={"theme":"css-variables"} window.openai.requestDisplayMode({ mode: "pip" }); window.openai.requestDisplayMode({ mode: "fullscreen" }); ``` Users can exit PiP or fullscreen by clicking the close button, returning to inline. Our Coffee Shop uses the default inline mode, but you can test all three in MCPJam Inspector's Playground. When your widget is in fullscreen, you can add an **"Open in \<server name>"** button to the header chrome by calling `setOpenInAppUrl`. This lets users open the widget's underlying web app in a new tab: ```javascript theme={"theme":"css-variables"} window.openai.setOpenInAppUrl({ href: "https://app.example.com/item/42" }); ``` Only `http` and `https` URLs are accepted. The link opens in a new tab with `noopener,noreferrer`. This capability is optional — hosts can turn it off, in which case `window.openai.setOpenInAppUrl` won't be defined. Guard for it before calling, for example `window.openai.setOpenInAppUrl?.({ href: "https://app.example.com/item/42" })`. ### Content Security Policy (CSP) Widgets run in a sandboxed iframe, so you need to declare which external domains your widget can interact with. Configure these permissions in your resource's `_meta` using one of two formats: * **`_meta.ui.csp`** (preferred) — uses camelCase field names and is the standard MCP Apps format * **`_meta["openai/widgetCSP"]`** (legacy) — the original ChatGPT Apps format, still supported When both are present, `ui.csp` values take precedence field-by-field; any field absent from `ui.csp` falls back to `openai/widgetCSP`. Both formats support the same domain categories: | `ui.csp` field | `openai/widgetCSP` field | Purpose | | ----------------- | ------------------------ | -------------------------------------------------- | | `connectDomains` | `connect_domains` | Domains your widget can fetch from (API calls) | | `resourceDomains` | `resource_domains` | Domains for static assets (images, fonts, scripts) | | `frameDomains` | `frame_domains` | Domains allowed in nested iframes | <Note> Without declaring a domain in your CSP, the sandbox will block the request. Only declare the domains you actually need. </Note> #### Third-party APIs with referrer or origin restrictions Some APIs (such as Google Maps with a referrer-restricted key) validate the page URL before serving a response. Widgets run at a real sandbox URL, so you can allowlist it in your API provider's dashboard. Locally that is `http://localhost:*` **and** `http://127.0.0.1:*` — the sandbox uses whichever of the two you did not open the Inspector with — and `https://sandbox.mcpjam.com` when hosted. See [View origins](/inspector/view-origins) for how to read the exact value. You still need to declare the API's domains in `_meta.ui.csp`: * Add script and asset CDN domains to `resourceDomains` * Add any XHR/fetch endpoints to `connectDomains` ## A note on authentication and monetization To keep this guide focused on building your first ChatGPT App, we've skipped authentication and monetization. For production apps, check out [OpenAI's authentication docs](https://developers.openai.com/apps-sdk/build/auth) and [monetization docs](https://developers.openai.com/apps-sdk/build/monetization). ## Running Your App ### Build and start the server ```bash theme={"theme":"css-variables"} npm install npm start ``` This builds the React widget with Vite and starts the server at `http://localhost:8787/mcp`. ### Testing with MCPJam Inspector The easiest way to test your app: 1. Run the inspector: `npx @mcpjam/inspector@latest` 2. Enter URL: `http://localhost:8787/mcp` 3. Try your app in our Playground! ### Connecting to ChatGPT To connect your app to ChatGPT: 1. Sign in to MCPJam Inspector (tunnels require an account) 2. Click **Create Tunnel** on the server card with your server connected 3. Use the tunnel URL as your connector endpoint in ChatGPT For more information, see [Tunneling](/inspector/connecting-servers#tunneling). ## What's next? Now that your Coffee Shop is running, you can: 1. **Test the flow** - Call the `orderCoffee` tool to see your widget 2. **Try the buttons** - Click "Order" and "Drink" to interact with your server 3. **Chat naturally** - Say "order me 3 coffees" and watch the widget update 4. **Iterate and expand** - Add more tools, improve the UI, or build something completely new! Congratulations! You've built your first ChatGPT App! 🎉 # My First MCP App Source: https://docs.mcpjam.com/guides/first-mcp-app Build a simple Reservation MCP App from scratch We're going to create a simple Reservation MCP App for a restaurant called Jammy Wammy! ## Setting up your environment ### Initialize the project ```bash theme={"theme":"css-variables"} mkdir reservation-app cd reservation-app npm init -y ``` ### Install dependencies ```bash theme={"theme":"css-variables"} npm install @modelcontextprotocol/sdk @modelcontextprotocol/ext-apps react react-dom zod npm install -D @types/node @types/react @types/react-dom typescript vite @vitejs/plugin-react vite-plugin-singlefile cross-env ``` ### Configure package.json Add the following to your `package.json`: ```json theme={"theme":"css-variables"} { "type": "module", "main": "server.ts", "scripts": { "build": "cross-env INPUT=reservation-app.html vite build", "start": "node server.ts" } } ``` ### Create tsconfig.json ```json theme={"theme":"css-variables"} { "compilerOptions": { "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "verbatimModuleSyntax": true, "noEmit": true, "jsx": "react-jsx", "strict": true, "skipLibCheck": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, "include": ["src"] } ``` ### Create vite.config.ts ```ts theme={"theme":"css-variables"} import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import { viteSingleFile } from "vite-plugin-singlefile"; const INPUT = process.env.INPUT; if (!INPUT) { throw new Error("INPUT environment variable is not set"); } const isDevelopment = process.env.NODE_ENV === "development"; export default defineConfig({ plugins: [react(), viteSingleFile()], build: { sourcemap: isDevelopment ? "inline" : undefined, cssMinify: !isDevelopment, minify: !isDevelopment, rollupOptions: { input: INPUT, }, outDir: "dist", emptyOutDir: false, }, }); ``` ## Creating your first View Now let's create the HTML file and React component that will be displayed when the MCP tool is called. ### Create reservation-app.html First, create the HTML file that will serve as the entry point for your app: ```html theme={"theme":"css-variables"} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="color-scheme" content="light dark"> <title>Reservation App
``` This is a standard HTML file with a few important elements: * A `root` div where React will mount your app * A module script that imports your React component * Color scheme meta tag for light/dark mode support ### Create src/reservation.tsx ```tsx theme={"theme":"css-variables"} import { useApp } from "@modelcontextprotocol/ext-apps/react"; import { StrictMode, useCallback } from "react"; import { createRoot } from "react-dom/client"; const IMPLEMENTATION = { name: "Reservation App", version: "1.0.0" }; function ReservationApp() { const { app, error } = useApp({ appInfo: IMPLEMENTATION, capabilities: {}, }); const handleMenuRequest = useCallback(async () => { if (!app) return; try { await app.sendMessage({ role: "user", content: [{ type: "text", text: `What's on the menu at Jammy Wammy?` }], }); } catch (e) { console.error("Failed to send message:", e); } }, [app]); if (error) return
ERROR: {error.message}
; if (!app) return
Loading...
; return (

🎉 Reservation Confirmed!

Your table at Jammy Wammy is ready!

); } createRoot(document.getElementById("root")!).render( , ); ``` ### Understanding the MCP App features #### The `useApp` hook The `useApp` hook is the foundation of your MCP App. It establishes the connection between your UI and the MCP host. ```tsx theme={"theme":"css-variables"} const { app, error } = useApp({ appInfo: IMPLEMENTATION, capabilities: {}, }); ``` * **`appInfo`**: Identifies your app with a name and version * **`capabilities`**: Declares what features your app supports (empty for this simple example) * **`app`**: The app instance you'll use to interact with the MCP host * **`error`**: Contains any connection errors #### Sending messages with `app.sendMessage()` The `sendMessage` method lets your app send follow-up messages to the LLM, enabling interactive conversations: ```tsx theme={"theme":"css-variables"} await app.sendMessage({ role: "user", content: [{ type: "text", text: `What's on the menu at Jammy Wammy?` }], }); ``` This lets your UI trigger additional LLM interactions. When the user clicks "What's on the menu?", the message is sent back to the LLM, which can then call another MCP tool (like `get-menu`) to respond. #### What else can your app do? The `app` object provides several methods to interact with the MCP host: * **`app.sendMessage()`** - Send messages to the LLM (as shown above) * **`app.callServerTool()`** - Call other MCP tools on your server directly * **`app.sendLog()`** - Send log messages to the host for debugging * **`app.openLink()`** - Request the host to open a URL (only `http` and `https` URLs are allowed; other schemes resolve `{ isError: true }`) * **`app.getHostContext()`** - Get information about the host environment (safe area insets, theme, etc.) For a complete reference of all available methods and capabilities, see the [MCP Apps API documentation](#). #### Mounting the React app Don't forget to mount your app to the DOM: ```tsx theme={"theme":"css-variables"} createRoot(document.getElementById("root")!).render( , ); ``` ## Setting up the MCP Server Now let's create the server that will register your tools and serve the UI. ### Create server.ts ```ts theme={"theme":"css-variables"} import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server"; const DIST_DIR = path.join(import.meta.dirname, "dist"); export function createServer(): McpServer { const server = new McpServer({ name: "Jammy Wammy Reservation Server", version: "1.0.0", }); const resourceUri = "ui://reservation/reservation-app.html"; // Resource: The built HTML file registerAppResource(server, resourceUri, resourceUri, { mimeType: RESOURCE_MIME_TYPE }, async (): Promise => { const html = await fs.readFile(path.join(DIST_DIR, "reservation-app.html"), "utf-8"); return { contents: [{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }], }; }, ); // Tool: get-reservation - Shows the reservation UI registerAppTool(server, "get-reservation", { title: "Get Reservation", description: "Make a reservation at Jammy Wammy restaurant.", inputSchema: {}, _meta: { ui: { resourceUri } }, }, async (): Promise => { return { content: [{ type: "text", text: "Reservation confirmed at Jammy Wammy! 🎉" }] }; }, ); // Tool: get-menu - Returns the menu (no UI) server.registerTool( "get-menu", { title: "Get Menu", description: "Get the menu for Jammy Wammy restaurant.", inputSchema: {}, }, async (): Promise => { const menu = ` 🍽️ Jammy Wammy Menu Appetizers: - Bruschetta - $8 - Calamari - $12 Main Courses: - Margherita Pizza - $16 - Spaghetti Carbonara - $18 - Grilled Salmon - $24 - Chicken Parmesan - $20 Desserts: - Tiramisu - $9 - Panna Cotta - $8 Beverages: - House Wine (glass) - $10 - Craft Beer - $7 - Fresh Lemonade - $4 `.trim(); return { content: [{ type: "text", text: menu }] }; }, ); return server; } async function main() { const server = createServer(); const transport = new StdioServerTransport(); await server.connect(transport); } main().catch((e) => { console.error(e); process.exit(1); }); ``` ### Understanding the server components #### Creating the MCP Server ```ts theme={"theme":"css-variables"} const server = new McpServer({ name: "Jammy Wammy Reservation Server", version: "1.0.0", }); ``` This initializes your MCP server with a name and version. #### Registering the App Resource ```ts theme={"theme":"css-variables"} const resourceUri = "ui://reservation/reservation-app.html"; registerAppResource(server, resourceUri, resourceUri, { mimeType: RESOURCE_MIME_TYPE }, async (): Promise => { const html = await fs.readFile(path.join(DIST_DIR, "reservation-app.html"), "utf-8"); return { contents: [{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }], }; }, ); ``` `registerAppResource` registers a resource with UI metadata so the host knows where the assets are. #### Registering an App Tool (with UI) ```ts theme={"theme":"css-variables"} registerAppTool(server, "get-reservation", { title: "Get Reservation", description: "Make a reservation at Jammy Wammy restaurant.", inputSchema: {}, _meta: { ui: { resourceUri } }, }, async (): Promise => { return { content: [{ type: "text", text: "Reservation confirmed at Jammy Wammy! 🎉" }] }; }, ); ``` This is the tool that will be directly called by the LLM to render your tool. We give it our `resourceUri` in the `_meta` field allowing us to have multiple tools to render different UI. We can also return text content to the LLM as well to give it more context about our View. #### Registering a Regular Tool (without UI) ```ts theme={"theme":"css-variables"} server.registerTool( "get-menu", { title: "Get Menu", description: "Get the menu for Jammy Wammy restaurant.", inputSchema: {}, }, async (): Promise => { return { content: [{ type: "text", text: menu }] }; }, ); ``` Regular tools work just like standard MCP tools - they return text content without any UI. #### Starting the Server ```ts theme={"theme":"css-variables"} async function main() { const server = createServer(); const transport = new StdioServerTransport(); await server.connect(transport); } ``` This connects your server to STDIO transport, allowing it to communicate with MCP clients. ## Building and Running Your App Now that everything is set up, let's build and run your MCP App! ### Build the app The build process uses Vite to bundle your React component and the HTML file into a single, self-contained HTML file: ```bash theme={"theme":"css-variables"} npm run build ``` This will create a `dist/reservation-app.html` file with all your JavaScript and CSS inlined - perfect for serving through MCP. ### Run the server Start your MCP server: ```bash theme={"theme":"css-variables"} npm start ``` Your server is now running and communicating via STDIO. It's ready to be connected to an MCP client like MCPJam Inspector! ### Testing with MCPJam Inspector The easiest way to test your app is using MCPJam Inspector: ```bash theme={"theme":"css-variables"} npx @mcpjam/inspector ``` On first launch MCPJam auto-connects a sample Excalidraw server. Open the **Servers** tab and add your own, then head to the **Playground** to render widgets, debug CSP/device behavior, and drive your app with an LLM. ### What's next? Now that your server is running, you can: 1. **Test the flow** - Call the `get-reservation` tool to see your UI 2. **Try the menu button** - Click "What's on the menu?" and watch the LLM call the `get-menu` tool 3. **Iterate and expand** - Add more tools, improve the UI, or build something completely new! Congratulations! You've built your first MCP App! 🎉 # Hosted App Source: https://docs.mcpjam.com/hosted/overview Use MCPJam Inspector in the browser — no installation required MCPJam Inspector is available as a hosted web app at [app.mcpjam.com](https://app.mcpjam.com). Build, test, debug, and evaluate MCP servers and apps with frontier models — all from the web. Launch the hosted inspector in your browser ## Why use the hosted app? * **No installation** — Open a browser and start testing. * **Always up to date** — You're always on the latest version without running `npx @mcpjam/inspector@latest`. * **Shareable** — Share server URLs with your team. Everyone can inspect the same server without local setup. ## Share your MCP server Send a link to your MCP server or app the same way you'd share a Google Doc. Anyone with a MCPJam account who opens it can immediately start chatting with the server right in their browser. Try it yourself with the [Excalidraw MCP server](https://app.mcpjam.com/shared/excalidraw/mN4lJGfKanjpnBdZvE-lL):