LYT·node

From your agent to your first node.

One MCP server, nine tools, and a key. Everything below has been run against the live service - no snippet here is written from memory.

Set up in three steps

In the project where the work should land. It must be a git repository with at least one commit, because the finished work comes back to it as a branch.

  1. Download the setup into the project:

    git clone https://github.com/LYT-AS/lytnode
  2. Create an API key in your account: /account/keys

  3. Open the project in your agent and say:

    read lytnode/docs/first-node.md and do what it says

The agent does the rest. It asks you only for the key and, in Claude Code, one sign-in in the browser. It works the same in Claude Code, Codex, Cursor, OpenCode, Gemini and Pi.

Rather use a terminal? The same setup is one command:

cd lytnode
bash setup.sh --agent claude    # or codex, cursor, opencode, gemini, pi

What the setup does

The same whether your agent runs it or you do. Run it again later to update - there is no separate update command.

Setting up by hand

Rather do it without the setup above? Point your agent at our MCP endpoint with your key and one of the client configurations below. You then make the two keys yourself, and the skill that sends work to the node and brings it home is not installed.

# 1. Your two keys. They must be DIFFERENT.
ssh-keygen -t ed25519 -f ~/.ssh/lytnode-1    -N "" -C "lytnode first key"
ssh-keygen -t ed25519 -f ~/.ssh/lytnode-2    -N "" -C "lytnode second key"

# 2. Your API key, from the environment. Never in a config file.
export LYT_NODES_API_KEY="lyt_live_..."

# 3. Ask your agent, in its own words:
#    "Estimate five Node M Dedicated in the US for two hours, then start them."

Two keys, always

A node needs two DIFFERENT SSH public keys from you. Generate both before your first call: provision_node takes them as ssh_public_key and ssh_cert_public_key, and rejects the same key twice. We never store the second one, so extend_node asks for it again.

The tools

Nine, and your key decides which of them you see. A key without nodes:write cannot spend money - that is the single most useful thing scopes do for you.

list_offersoffers:read

The classes your key may rent. Each entry states its own jurisdiction.

required
—
optional
origin
estimate_costoffers:read

What a rental would cost before anything is created. Enforces nothing.

required
class, hours
optional
count
list_modelsoffers:read

Local models that fit a class, with the memory each needs and its licence.

required
—
optional
class
provision_nodenodes:write

Rents one node and joins it to your own tailnet.

required
class, ssh_public_key, ssh_cert_public_key, tailscale_authkey
optional
hours, label, job_id, agent, toolchain, exit_policy
provision_nodesnodes:write

Rents several at once as a job, or adds more to a job already running.

required
class, count, ssh_public_key, ssh_cert_public_key, tailscale_authkey
optional
hours, label, job_id, agent, agents, toolchain, exit_policy
list_nodesnodes:read

Your nodes with hostname, job, expiry and state.

required
—
extend_nodenodes:write

Pushes the expiry out and renews your time-limited access.

required
node_id, hours, ssh_cert_public_key
release_nodenodes:write

Releases one of your nodes before its expiry.

required
node_id
list_toolchainsoffers:read

The developer tools a node can be prepared with, and which classes can run each of them.

required
—
optional
class

Client setup

Six agents, six configuration formats. Five of the blocks below were tested against a real run on 10-12 September 2026, and each note underneath is a pitfall in that client - the kind that parses cleanly and connects to nothing.

Claude Code.mcp.json
{
  "mcpServers": {
    "lytnode": {
      "type": "http",
      "url": "https://nodes.lyt.no/mcp",
      "headers": { "Authorization": "Bearer ${LYT_NODES_API_KEY}" }
    }
  }
}
Codex~/.codex/config.toml
[mcp_servers.lytnode]
url = "https://nodes.lyt.no/mcp"
bearer_token_env_var = "LYT_NODES_API_KEY"

The key is read from the environment, never written into the file. In non-interactive runs, codex exec sets its own approval policy — pass --approve-for-me to let tool calls through (measured 2026-09-12, codex-cli 0.154.0).

Cursor~/.cursor/mcp.json
{
  "mcpServers": {
    "lytnode": {
      "url": "https://nodes.lyt.no/mcp",
      "headers": { "Authorization": "Bearer ${LYT_NODES_API_KEY}" }
    }
  }
}

In -p mode cursor-agent needs --approve-mcps before it will call a tool, and --force before it runs a shell at all. "Connection failed" from cursor-agent mcp list usually just means the key is not exported in that shell.

OpenCodeopencode.json
{
  "mcp": {
    "lytnode": {
      "type": "remote",
      "url": "https://nodes.lyt.no/mcp",
      "headers": { "Authorization": "Bearer ${LYT_NODES_API_KEY}" }
    }
  }
}

Configure a model explicitly. Without one it picks an image model and fails with a message that says nothing about the cause.

Gemini~/.gemini/settings.json
{
  "mcpServers": {
    "lytnode": {
      "httpUrl": "https://nodes.lyt.no/mcp",
      "headers": { "Authorization": "Bearer ${LYT_NODES_API_KEY}" }
    }
  }
}

httpUrl, not url. url is the older SSE transport and connects to nothing without a useful error (measured 2026-09-10, gemini 0.59.0).

Pi~/.pi/agent/extensions/lytnode.ts
# pi has no MCP client of its own. The package installs ours:
bash install-agent.sh pi
export LYT_NODES_API_KEY="lyt_live_..."

The extension reaches this one server and nothing else, and reads the key from the environment. Newer than the five above, and not yet measured against a real run the same way.

Measured against a real run, with the date. An undated claim is a guess with confidence.

Mac nodes, ready to build

A Mac node arrives prepared for Apple development. Ask for the build tools when you order, and they are in place when the machine is yours - so your agent's first command is the build, not the setup. list_toolchains shows what each class can run.

# Ask for the tools when you order. Your agent can do this in its own words:
#   "Rent a Mac for 24 hours with fastlane, cocoapods and xcodes installed."
provision_node(class, hours: 24, toolchain: ["xcodes", "fastlane", "cocoapods"], ...)

Available today: xcodes, fastlane, cocoapods, swiftlint, xcbeautify, eas. Xcode itself is installed with xcodes under your own Apple ID, which never reaches us. A Mac is rented for at least 24 hours - Apple's licence, not our price.

Read more about Mac nodes →

Errors

Every error carries a code, an HTTP status and enough detail to act on. Two are worth knowing before you meet them: 402 means you are over your spend cap, and waiting will not help - unlike 429, which means exactly that you should wait.

codeHTTPwhat to do
unknown_offer_class400No such class, or not visible to your key.
invalid_count400count must be a whole number from 1 to 10.
invalid_agents400agents must have exactly one entry per node.
agent_not_supported_on_class400That agent cannot be prepared on that class.
ttl_required400Every node needs an expiry. There is no default.
ttl_too_long400Over the maximum period. The reply names the maximum.
spend_cap_reached402Over your spend cap. Release something, or ask us to raise it.
unknown_job404No such job_id — including when it is not a valid uuid.
job_not_running409That job has already settled. Start a new one.
rate_limited429Ten calls or forty nodes an hour. Wait.
region_not_served451We cannot serve your region.
no_capacity503Nobody could deliver that class right now. Choose another.

Limits

Ten calls and forty nodes an hour, at most ten nodes per call, and a spend cap that counts what you have standing rather than what you have spent. Every node must carry a time to live - there is no way to start one without an expiry.

LYT AS · Norway · node@lyt.noChoose a nodePrivacyTermsCookiesRefer a friend