Kernel

← Documentation

Kernel over MCP

Kernel's MCP server lets a coding agent work a Kernel work item entirely through tools: read its current work and a bounded context, record decisions and artifacts, report progress and blockers, ask for an approval, hand off, and complete the work. It is a thin client of /api/v1: it holds no database session and no privileged path of its own, so every permission, scope and tenant rule is enforced exactly once, in the API, whichever way a call arrives. admin:* tokens gain nothing extra over MCP, and organization, member, token, machine and runtime management are deliberately absent.

There are two transports of the same server:

transport how it is reached credential
stdiokernel-mcp the agent runtime starts it as a subprocess KERNEL_TOKEN in its environment
streamable HTTPPOST /mcp on the Kernel server a remote client, over the network Authorization: Bearer on every request

Both are authenticated by a Kernel API token. Over HTTP the server checks the bearer before a request reaches the MCP transport at all: no header, a malformed token, a revoked or expired one — 401 auth.invalid_token, the same answer the API gives, and the MCP layer never sees the request. KERNEL_MCP_HTTP_ENABLED=0 removes the /mcp path entirely; the stdio server is unaffected.

Managed sessions get it per invocation, never through your files

A session the daemon starts is handed Kernel's server for that one invocation, by the adapter, with the session's own token — the same credential the daemon puts in the session's environment as KERNEL_SESSION_TOKEN (D-054), never the daemon's device token. It is attached only when the launch carries both a server URL and a session token; a launch the daemon gave no credential is started without it.

  • Claude Code gets a private --mcp-config file, written 0600 in a directory the adapter creates for the invocation and deletes with it, naming the server kernel: kernel-mcp over stdio with KERNEL_URL, KERNEL_TOKEN, KERNEL_SESSION_ID and KERNEL_WORK_KEY in its env block. --strict-mcp-config stays on, so that file is the only source of servers the session has — your own ~/.claude.json servers, and any .mcp.json in the checkout, never load into a managed session (D-041).
  • Codex gets -c mcp_servers.kernel.command=…, -c mcp_servers.kernel.args=[…] and -c mcp_servers.kernel.env_vars=["KERNEL_URL","KERNEL_TOKEN","KERNEL_SESSION_ID","KERNEL_WORK_KEY"] on the command line, after --ignore-user-config, on the launch and on every resume. The overrides name the program and the variables; the values are in the Codex process's environment and never on the command line, which ps shows to every account on the machine.

Neither adapter ever edits your configuration. Not ~/.claude.json, not ~/.claude/settings.json, not ~/.codex/config.toml — a test per adapter asserts those files are byte-identical before and after a launch, and a daemon that wrote to them would change every session you ever run, on a machine, from a work item. The setup below is for people who want Kernel's tools in their own interactive sessions, by their own choice; it is not how a managed session gets them, and a managed session does not read it.

The program the daemon launches is kernel-mcp beside its own interpreter (the console script of this package), else the one on PATH; a machine may name another with the kernel_mcp setting in its daemon configuration — a program name or an argv prefix, machine-owned like executable, never taken from a runtime definition. The server name kernel is reserved: a runtime definition whose mcp_servers names one is refused at launch.

Setting it up for your own sessions

You need a token. A service token scoped to what you want the agent to do is the right shape. The session tokens the daemon mints read and write, but only their own work item (see What a session token reads, and what it does not), so an agent you drive yourself — one that must range across work items, or read the fleet — wants a service token instead.

$ kernel token create --name mcp \
    --scope work:read --scope work:write \
    --scope decisions:read --scope decisions:write \
    --scope artifacts:read --scope artifacts:write \
    --scope blockers:read --scope blockers:write \
    --scope approvals:request --scope approvals:read \
    --scope handoffs:read --scope handoffs:write \
    --scope sessions:read --scope sessions:write \
    --scope runtimes:read --scope org:read

That is the full set needed to drive all fourteen tools: list_work needs org:read and handoff needs runtimes:read + handoffs:write, which a narrower "write my own work" set omits. Grant less for an agent that does less — the read scopes let it read back what it wrote.

The token is shown once. Put it in the environment rather than on a command line (export KERNEL_TOKEN=krn_…), and point KERNEL_URL at your server (https://kernel.example.com, no trailing /api/v1). KERNEL_WORK_KEY is optional: set it to pin get_current_work to one work item, or leave it unset and let the server resolve the single running work item assigned to you.

Claude Code

Over stdio, in your user scope (-s user), the values passed as -e rather than typed into the arguments:

$ claude mcp add kernel -s user \
    -e KERNEL_URL="$KERNEL_URL" -e KERNEL_TOKEN="$KERNEL_TOKEN" -- kernel-mcp

Or over HTTP, against the Kernel server directly:

$ claude mcp add --transport http kernel "$KERNEL_URL/mcp" \
    --header "Authorization: Bearer $KERNEL_TOKEN"

Either writes to ~/.claude.json; claude mcp list shows it, claude mcp remove kernel takes it out. The tools appear as mcp__kernel__get_current_work and so on.

Codex

Over stdio:

$ codex mcp add kernel --env KERNEL_URL="$KERNEL_URL" --env KERNEL_TOKEN="$KERNEL_TOKEN" \
    -- kernel-mcp

Or over HTTP, with the token read from an environment variable at start rather than stored in the file:

$ codex mcp add kernel --url "$KERNEL_URL/mcp" --bearer-token-env-var KERNEL_TOKEN

Both write [mcp_servers.kernel] into ~/.codex/config.toml; codex mcp list shows it and codex mcp remove kernel takes it out.

Gemini CLI

In ~/.gemini/settings.json (or the project's .gemini/settings.json):

{
  "mcpServers": {
    "kernel": {
      "command": "kernel-mcp",
      "env": { "KERNEL_URL": "https://kernel.example.com", "KERNEL_TOKEN": "krn_…" }
    }
  }
}

or, over HTTP:

{
  "mcpServers": {
    "kernel": {
      "httpUrl": "https://kernel.example.com/mcp",
      "headers": { "Authorization": "Bearer krn_…" }
    }
  }
}

Recent Gemini CLI releases also take gemini mcp add kernel -e KERNEL_URL=… -e KERNEL_TOKEN=… -- kernel-mcp, which writes the same entry. Gemini is not a managed runtime in this build (M9), so this is the only way it reaches Kernel.

The tools

Fourteen, named by the dossier. Every result is a compact JSON object with ids, a status and a next_actions hint — never prose — except get_context, which returns the bounded context document verbatim.

tool when to call it
get_current_work() first: which work item this session is for (the session's, else KERNEL_WORK_KEY, else the one running work item assigned to the token's user, else an error saying which to set)
get_work(work) one work item by key or id
list_work(state?, limit=50) a page of work items
get_context(work?) the bounded context: work, constraints, acceptance criteria, the newest 20 decisions and artifacts, open blockers, pending approvals, the last 5 handoffs, with truncated counters — the same document as GET /api/v1/work/{id}/context and the kernel://work/{key}/context resource
update_work_status(work, command, reason?) make_ready, start, submit_review, rework, complete, cancel, reopen, send_back
complete_work(work, reason?) end the work item successfully
record_decision(work, statement, rationale="", supersedes_decision_id?) a decision, attributed to this session
create_artifact(work, type, title, content? | external_uri?, filename?, content_type?) an artifact: inline text, or one held elsewhere
report_progress(message, session_id?) a progress event on the session timeline
report_blocker(work, severity, title, description="") something in the way
resolve_blocker(blocker_id, resolution) it no longer is
request_approval(work, action, reason="", required_role?) ask a person before doing something
get_approval_status(approval_id) has anyone answered
handoff(work, target_runtime, purpose, objective?) hand the work to another runtime; with an objective the package is compiled too

Raw event and transcript history is never in a tool result; the API serves it.

A worked example

A managed Claude Code session, started by the daemon, has kernel attached and is briefed to record what it decides. It calls the tool the way it calls any other — over MCP this is a tools/call request:

{
  "jsonrpc": "2.0", "id": 3, "method": "tools/call",
  "params": {
    "name": "record_decision",
    "arguments": {
      "work": "KBK-1",
      "statement": "Codex is told the names of the server's variables, not their values",
      "rationale": "an argument list is readable by every process on the machine"
    }
  }
}

The server sends POST /api/v1/work/{id}/decisions with the session's bearer and session_id set to the session, so the decision lands on the session's timeline exactly as if the session had made the request itself. The result is the kind of object every tool returns:

{
  "id": "b3ac828b-9ed4-49d0-88ed-aed364bff7b5",
  "work_id": "dcd533f6-3bb7-4f13-a781-ca197d15c9e2",
  "session_id": "39f088d3-1dc8-414f-b75f-9191f6161b83",
  "status": "recorded",
  "next_actions": ["create_artifact", "report_progress", "complete_work"]
}

A refusal comes back as an MCP tool error carrying the API's problem code and detail — a token without decisions:write calling record_decision is told auth.permission_denied: decision.record requires the scope decisions:write, naming the scope, and nothing is recorded.

What a session token reads, and what it does not

A managed session's token (token_service.SESSION_TOKEN_SCOPES) holds work:read/work:write, sessions:write, and, for the work-object families it produces, both halves: it may request an approval (approvals:request) and read one back (approvals:read), and write and read artifacts, blockers and decisions (artifacts:read, blockers:read, decisions:read); it may read a handoff (handoffs:read). So get_approval_status, and reading back a decision, an artifact, a blocker or a handoff it recorded, all work over the tools (D-055/KBK-46). Every one of those reads is confined by the API to the token's own work item: a row of another work item answers exactly as a write to it does — 403 auth.session_scope inside the organization, 404 across organizations — so an agent sees its own Work and never the fleet.

The token still holds no sessions:read (D-035): it cannot read the session list or even its own session row (GET /sessions/{id} answers 403), and it manages no tokens and touches no organization. A caller that needs those reads uses a service token with the scopes it needs.

Limitations

  • The HTTP transport needs this build's MCP package. A server built without kernelos.mcp.server.http_transport still refuses an unauthenticated request at /mcp (401) and tells an authenticated one the transport is unavailable (503 mcp.transport_unavailable) rather than pretending the path does not exist. Use kernel-mcp over stdio there.
  • Managed Codex sessions get Kernel's server and nothing else. A runtime definition's own mcp_servers are wired for Claude Code only; see docs/runtimes/codex.md.

Where this is tested

  • tests/unit/adapters/test_kernel_mcp.py — how the server is described, from which of the daemon's variables, and when it is not.
  • tests/unit/adapters/test_claude_adapter.py, test_codex_adapter.py — the file and the overrides the process was really started with, the stub kernel-mcp started from that configuration the way the runtime would, the user's configuration byte-identical before and after a launch.
  • tests/integration/daemon/test_runner_mcp_wiring.py — through the daemon's runner: the session's token reaches the server and no fragment of the device token does.
  • tests/unit/api/test_mcp_http.py, tests/integration/api/test_mcp_http.py — the /mcp gate: what it refuses before asking a database, and who passes through to the transport.
  • tests/contract/test_mcp_tools.py — the server itself, over stdio, against a live API.

Keyboard shortcuts

Shortcuts are a faster way to do what the keyboard already does, never the only way.

?
Show this help
Esc
Close dialogs
/
Search
g h
Go to Home
g p
Go to Projects
g w
Go to Work
g n
Go to Needs You
g r
Go to Runtimes
g a
Go to Activity
c
Create Work
j k
Move between rows in Needs You and work lists
Enter
Open the focused row
a r
Approve or reject the focused approval
e
Edit the open item