Jern Cloud docs

MCP server and API tokens.

Jern Cloud is an MCP server. Claude Code, Cursor, or an agent of your own can start governed sessions here, follow them, stop them, read their receipts, and be told when the work has settled, from where you already work, with a workspace API token that acts as you, narrowed to what it should be able to do.

API tokens

An admin creates tokens under Settings › API tokens. A token is shown once and only its hash is kept. It acts as the member who created it: every call runs under that member's standing and the workspace's approval requirement, concurrency limit, allowances, cap per attempt, plan, and provider keys, exactly as the dashboard would. Revoke it there at any time; it also stops when its creator leaves the workspace. Creation, revocation, and every session started with it are in the audit log.

Scope

A token need not act as all of its creator. When it is created it can be narrowed, and what is written on it is exactly what it can cost if it leaks: the repositories it sees and acts on, others do not exist to it; read only, so it lists and reads but starts nothing; a cap per session it starts, below what is asked for and below the workspace's, never above; a spend limit in dollars across every session it started, counted from those sessions each time, after which it can only read; and an expiry, at most a year. A scope only narrows. The token a tool outside the dashboard should hold is a scoped one: its repositories, a cap, a spend limit, and an expiry.

Connecting a client

The server is https://cloud.jern.ai/mcp, streamable HTTP, one request per call, with Authorization: Bearer <token>. In Claude Code:

claude mcp add --transport http jern-cloud https://cloud.jern.ai/mcp --header "Authorization: Bearer jern_…"

In Cursor or any client that reads a JSON configuration:

{
  "mcpServers": {
    "jern-cloud": {
      "url": "https://cloud.jern.ai/mcp",
      "headers": { "Authorization": "Bearer jern_…" }
    }
  }
}

Tools

ToolWhat it does
list_repositoriesThe repositories connected to the workspace.
list_sessionsRecent sessions with their status and pull request.
start_sessionOpens a session on a repository with a task as its first message; the model and cap default to the workspace's trigger settings. Takes an optional idempotency_key and callback_url. Returns the session id and link.
send_messageThe session's next turn, which becomes a new attempt on a fresh machine and updates the pull request. Takes the same two optional arguments.
stop_sessionStops the session's current attempt and keeps its work; cancels one that has not started. Safe to call twice.
get_sessionThe session with its messages, attempts, and receipts: tokens against the cap, estimated spend, files touched, services started, hosts contacted, the browser's screenshots, the receipt check, and the callbacks this token registered with how each delivery went.
get_policyThe baseline a session on the repository would run under.

A refused call, for instance because the workspace has no key for the requested provider or an attempt is already running, comes back as a tool result that says why, so the calling agent can act on it rather than fail.

Idempotency and cancellation

A client that loses the reply to start_session cannot tell whether the session opened, and a blind retry would open a second one and spend twice. So start_session and send_message take an idempotency_key of the client's choosing, up to 64 characters. The first call with a key does the work; a retry with the same key and the same request returns what the first call made, marked replayed, and does nothing more. Two identical calls racing cannot both open a session; the loser is told the start is in flight and to retry. The same key with a different request is refused as reused, never answered with someone else's session.

stop_session is the session page's Stop: a working attempt is told to stop on its next heartbeat, within twenty seconds, ends the way a time limit ends it, and publishes what it has, so the pull request holds the work so far and the next message continues from it. An attempt that has not started is cancelled outright. Calling it twice is safe, and a read-only token cannot.

Completion callbacks

A tool that sends work here should not have to poll for it. start_session and send_message take an optional callback_url: when the attempt that call started has settled, Jern Cloud makes one POST to it with the compact handback, and the reply to the call carries the secret that signs it, shown once. A replayed call returns the callback the first call registered, secret included.

The URL is https to a public host on port 443, with no credentials and no fragment, held to the rule the agent's network allowlist uses, so an address, localhost, or a platform name is refused with the call before any work starts. At delivery the host is resolved again and refused unless every address is public, and no redirect is followed, so a name changed after registration cannot point the callback at anything private.

The POST is JSON with three headers: X-Jern-Event: session.settled, X-Jern-Delivery with the callback id and the delivery number, and X-Jern-Signature: sha256=…, the HMAC-SHA256 of the body under the secret, the way GitHub signs its webhooks. The body carries outcome in one word to branch on, published, no_change, asked with the question, cancelled, or failed with the reason; the pull request and check run; the changed files with counts; the acceptance run with its verdict; blockers, what stands between the work and a merge, such as a question, a failure, or new failing tests; and the usage.

{
  "event": "session.settled",
  "session_id": "session_…", "repository": "owner/name",
  "status": "pr_ready", "outcome": "published",
  "pull_request_url": "https://github.com/owner/name/pull/12",
  "changes": { "files": [ { "path": "web/converter.js", "additions": 1, "deletions": 1 } ] },
  "verification": { "status": "passed", "conclusion": "success", "new_failures": [] },
  "blockers": [],
  "usage": { "input_tokens": 41200, "output_tokens": 3900, "cost_microdollars": 38000 }
}

A 2xx ends the delivery. Any other answer, or none within ten seconds, earns another try after a minute, then five, fifteen, an hour, four, and twelve: seven tries over about eighteen hours, then the callback is given up as an audit event, and get_session shows what happened. A 410 Gone ends it at once. Deliveries are at least once, so treat the callback id as the key. The callback changes nothing about the session: an endpoint that cannot be reached costs the client a notification and nothing else.

What stays the same

A session started this way is a session like any other: the repository's pinned baseline, the isolated machine with loopback-only networking, the receipt as a check on the pull request, and the audit trail. The dashboard shows it, and follow-up messages can come from either side.