Getting started with Nightjar

Who this is for. You have an AI agent (Claude Code, Claude Desktop, Codex, Gemini, a custom SDK agent) — or just a terminal — and you want it to drive a real browser. Maybe against a public site, maybe against the app you’re running on your own laptop. This guide tells you exactly what to do in your environment, whatever that is.

Honesty up front. Part 1 (drive a public site) works today with zero install. Part 2 (reach your own localhost) works today but is rough — the smooth one-command version is designed in ADR-035 — Local Reach Grants and not built yet. Every step below is labelled Today or Target so you never follow an instruction for something that doesn’t exist.


The mental model (read this first — it removes 90% of the confusion)

Nightjar gives your agent a browser running in our cloud. Your agent talks to it over MCP (an HTTPS tool endpoint). That’s the whole product. Everything else is detail hanging off two independent questions plus one rule:

            ┌─────────────────────────┐        HTTPS / MCP        ┌──────────────────────────┐
  YOU  ───► │  your AGENT HOST         │ ───────────────────────► │  Nightjar CLOUD BROWSER   │
            │  (Claude Code / Desktop, │   session_start,         │  (runs in our datacenter) │
            │   Codex, Gemini, SDK,    │   navigate, perceive…    │                           │
            │   or a plain terminal)   │ ◄─────────────────────── │                           │
            └─────────────────────────┘                          └──────────┬────────────────┘
                                                                            │
                                              ┌─────────────────────────────┴───────────────┐
                                              │                                              │
                                       a PUBLIC site                              YOUR localhost / LAN
                                    (nothing to install)                    (needs a daemon on YOUR machine)
  • Question A — where does your agent run? (the “host”) This decides how you connect the MCP and get a token. It’s the same answer whether you’re on Windows, macOS, or Linux — it depends on the agent, not the OS.
  • Question B — what OS are you on? This only matters for installing the nightjar CLI/daemon, and you only need that for local reach (Part 2). For driving public sites (Part 1), the OS is irrelevant — you install nothing.
  • The one rule: the cloud browser’s localhost is our datacenter, not your machine. To make a session reach your app, a small daemon must run on the same machine as that app, and tunnel the traffic back. If your agent already runs on that machine (the common case), this is trivial. If your agent runs somewhere else (a cloud agent, a CI runner), the daemon still goes on your machine — see the co-location rule.

Keep these separate and the rest is mechanical.


Part 1 — Drive a cloud browser against a public site (Today, no install)

This is the two-call delight. You need (1) an org and a token, and (2) your host pointed at the Nightjar MCP endpoint:

https://api.nightjar.cloud/mcp

It’s a streamable-HTTP MCP server, authenticated with Authorization: Bearer <token>. The endpoint is open on the public internet (alpha access is gated by org membership + your token, not by your IP) — so it works the same from a laptop, a server, or a cloud sandbox.

Step 1 — Get a token

Two ways, pick whichever your host makes easy:

  • Paste a static key (works everywhere). Log in to console.nightjar.cloud with GitHub, land in your org, and mint an API key (njk_…). Alpha is invite-gated by org membership: a logged-in user with no org can’t do anything, so if the console is empty, you need an invite.
  • OAuth, no key to copy (smoothest). Hosts that support remote-MCP OAuth (Claude Code, Codex, Gemini) will discover Nightjar’s OAuth metadata at https://api.nightjar.cloud/.well-known/oauth-* and run a device/PKCE login in your browser — you never hand-copy a secret. Nightjar supports Dynamic Client Registration (RFC 7591), so even a client we’ve never seen can self-register and complete the flow. (If your host prompts you to “log in” to the Nightjar MCP server, that’s this path.)

Step 2 — Point your host at the MCP endpoint

Find your host below. The OS doesn’t change these — a macOS Claude Code and a Windows Claude Code use the identical command.

Claude Code (CLI and the VS Code extension) — Today

export NIGHTJAR_API_KEY="njk_your_token_here"
claude mcp add --scope user --transport http nightjar https://api.nightjar.cloud/mcp 
  --header "Authorization: Bearer $NIGHTJAR_API_KEY"

Or by config — user-global ~/.claude.json, or per-repo .mcp.json (shareable with your team):

{
  "mcpServers": {
    "nightjar": {
      "type": "http",
      "url": "https://api.nightjar.cloud/mcp",
      "headers": { "Authorization": "Bearer ${NIGHTJAR_API_KEY}" }
    }
  }
}

The VS Code extension reads the same config — no separate setup.

Claude Desktop (macOS / Windows) — Today

Remote MCP is added through the UI, not a JSON file:

Settings → Connectors → Add custom connector → https://api.nightjar.cloud/mcp

The connector UI is OAuth-first, so the cleanest path is the OAuth login from Step 1. If you must use a static njk_ key and the UI won’t take a header, fall back to the local mcp-remote bridge in claude_desktop_config.json (%APPDATA%\Claude\claude_desktop_config.json on Windows, ~/Library/Application Support/Claude/claude_desktop_config.json on macOS):

{
  "mcpServers": {
    "nightjar": {
      "command": "npx",
      "args": ["mcp-remote", "https://api.nightjar.cloud/mcp",
               "--header", "Authorization: Bearer ${NIGHTJAR_API_KEY}"],
      "env": { "NIGHTJAR_API_KEY": "njk_your_token_here" }
    }
  }
}

(The bridge needs Node 18+. Prefer the OAuth connector when you can.)

Codex CLI (any OS) — Today

export NIGHTJAR_API_KEY="njk_your_token_here"
codex mcp add nightjar --url https://api.nightjar.cloud/mcp 
  --bearer-token-env-var NIGHTJAR_API_KEY

Or in ~/.codex/config.toml:

[mcp_servers.nightjar]
url = "https://api.nightjar.cloud/mcp"
bearer_token_env_var = "NIGHTJAR_API_KEY"

For the OAuth path instead of a static key: codex mcp login nightjar. Verify with /mcp inside a Codex session.

Gemini CLI (any OS) — Today

gemini mcp add nightjar --transport http https://api.nightjar.cloud/mcp 
  -H "Authorization: Bearer $NIGHTJAR_API_KEY"

Or in ~/.gemini/settings.json:

{
  "mcpServers": {
    "nightjar": {
      "httpUrl": "https://api.nightjar.cloud/mcp",
      "headers": { "Authorization": "Bearer $NIGHTJAR_API_KEY" }
    }
  }
}

Gemini also supports OAuth discovery — if you omit the header and the server returns 401, it will discover the OAuth endpoints and run the flow for you. Verify with /mcp.

Custom SDK agent (TypeScript / Python) — Today

The Claude Agent SDK takes the same MCP shape:

options = ClaudeAgentOptions(
    mcp_servers={
        "nightjar": {
            "type": "http",
            "url": "https://api.nightjar.cloud/mcp",
            "headers": {"Authorization": f"Bearer {os.environ['NIGHTJAR_API_KEY']}"},
        }
    },
    allowed_tools=["mcp__nightjar__*"],
)

Plain terminal, no agent at all (any OS) — Today

There’s no agent to configure — you are the driver. Two options:

  • Just the HTTP API + your keycurl https://api.nightjar.cloud/v1/... with Authorization: Bearer njk_…. No install.
  • The nightjar CLI for ergonomic session/network commands — but note the CLI is currently build-from-source only (see Part 2’s install note); for pure cloud-drive you usually don’t need it.

Step 3 — Drive

Once connected, your agent calls session_startnavigateperceive (plus act, screenshot, session_end). Reaching a public URL is a two-call job; the agent is resolved from your token, so you never pass it as an argument.

So, the four examples you asked about

Your environmentWhat you do
VS Code on Windows (Claude Code extension)Claude Code recipe above — claude mcp add … --transport http.
Claude on a Mac (Desktop or Code)Claude Desktop connector, or the Claude Code recipe.
Codex on LinuxCodex recipe — codex mcp add / ~/.codex/config.toml.
Plain CLI on a MacThe HTTP API + key (or the SDK); no agent host to wire up.

They’re all “easy” for the same reason: driving the cloud browser needs no local install — you register a remote MCP URL and supply a token. The OS never entered into it.


Part 2 — Reach your own localhost (Today: rough · Target: one command)

The cloud browser can’t see your machine by default — its localhost is our datacenter. To let a session reach the app you’re running on port 5173 (or your LAN), you run the nightjar-daemon on that machine. It joins an org-scoped network over wss://tunnel.nightjar.cloud/ws/control, and your session opens with egress: "split" (public traffic goes direct; only declared local targets route through your daemon) or egress: "local" (everything through your machine).

Today — the honest, ~7-step path

  1. Install from source. There are no binaries yet — no brew, no curl | sh, no winget, no npx. You build the CLI and daemon with cargo build. (This is exactly what ADR-035 Stage 1 is about fixing.)
  2. Create a network, mint a join token, start the daemon with the network + token + the control URL (wss://tunnel.nightjar.cloud/ws/control), then open a session with egress: "local" / "split".

The full, tested recipe — including the gotchas — lives in local-server-via-nightjar-daemon.md.

Known sharp edges (don’t be surprised):

  • The recommended per-developer OAuth login is scope-clamped and can’t create a network (that needs networks:write), so today this path leans on an org/admin key.
  • egress: "local" with no daemon connected fails silently — the session opens fine and only errors later, at navigate, with a tunnel 502.
  • The CLI can’t always derive the control URL, so you may have to pass the “magic string” wss://tunnel.nightjar.cloud/ws/control by hand.

Target — what this becomes (ADR-035, proposed)

One command, on the machine running your app:

$ nightjar tunnel 5173
Nightjar 0.9.3 verified
Logged in as you@example.com in org acme-dev
Grant: rg_7f3a   Allows: http://127.0.0.1:5173, localhost:5173
Egress: split    Expires: 18:30 (or Ctrl-C)    Daemon: your-laptop connected
Ready for agents:  session_start({ "egress": "split", "network": "rg_7f3a" })

It logs you in if needed, mints a narrow, expiring Reach Grant (“this session may reach this local port through this daemon for this long”), runs the daemon, and prints a ready-to-use session — defaulting to split so a single-port verify never silently routes all your browsing through your laptop. The architecture (daemon = trusted local courier; grant = the user-facing object; signed install; /.well-known/nightjar discovery; a typed E_LOCAL_REACH_NOT_READY instead of the silent dead-end) is specified in ADR-035 — Local Reach Grants. This getting-started guide is the user’s-eye view that ADR-035’s Stages 1–2 exist to deliver.

The co-location rule (the thing everyone trips on)

localhost resolves on the daemon’s machine — not where your agent runs, and not in the cloud browser.

  • Agent runs on the same machine as your app (all four examples above, the common case): run the daemon there too. Same box, done.

  • Agent runs elsewhere (a cloud-hosted agent, a CI runner, a remote sandbox) but the app is on your laptop: the daemon goes on your laptop. The agent can’t start it for you — so the Target design hands the agent a typed error it can act on:

    { "code": "E_LOCAL_REACH_NOT_READY",
      "remedy": { "command": "nightjar tunnel 5173 --claim njrg_3p9x --org acme-dev",
                  "authorizes": ["127.0.0.1:5173", "localhost:5173"],
                  "scope": "this agent/session only" } }

    You run that one command on your laptop; the session becomes ready. (This is ADR-035 Stage 3 — a reach capability the driving identity can self-serve, without handing it org-network admin power. Not built yet.)


Quick reference

HostInstall needed for cloud-drive?Connect (Today)Token
Claude Code (CLI/VS Code)Noclaude mcp add --transport http nightjar <url> --header …njk_ or OAuth
Claude DesktopNoSettings → Connectors → Add custom connector → <url> (or mcp-remote bridge)OAuth (preferred) / njk_
Codex CLINocodex mcp add / ~/.codex/config.toml (bearer_token_env_var)njk_ or codex mcp login
Gemini CLINogemini mcp add -H / ~/.gemini/settings.json (httpUrl)njk_ or OAuth discovery
SDK (TS/Python)NomcpServers: { type: http, url, headers }njk_
Plain terminalNocurl …/v1 + Bearer, or the SDKnjk_

<url> = https://api.nightjar.cloud/mcp everywhere.

Local reach (Part 2) is the only thing that needs an install, and that install is build-from-source today → signed brew/curl/winget/npx under ADR-035 Stage 1.


What works today vs. what’s proposed

Works today

  • Hosted MCP drive from any remote-MCP host (Claude Code, Codex, Gemini, SDK) + Claude Desktop (connector/bridge), with njk_ keys or OAuth (incl. DCR self-registration).
  • The local-reach tunnel itself — daemon, networks, join tokens, egress modes, the declared-host allowlist + SSRF floor — built and running (just build-from-source and multi-step to set up).
  • Console login (GitHub OAuth) + key minting.

Proposed (ADR-035 — awaiting ratification)

  • Signed, one-line distribution (brew / curl | sh / winget / npx), replacing build-from-source.
  • nightjar tunnel 5173 — one command for local reach, default split.
  • /.well-known/nightjar client discovery (kills the control-URL guessing).
  • Typed E_LOCAL_REACH_NOT_READY + a local_reach_ensure MCP tool, so an agent hits a clear, claimable hand-off instead of a silent 502.
  • A reach:self scope family so a driving identity can provision its own ephemeral reach without org-network admin power.

See also

  • ADR-035 — Local Reach Grants — the distribution + first-mile-local-reach architecture this guide is the user-facing view of.
  • local-server-via-nightjar-daemon.md — the full, tested Part 2 recipe as it works today.
  • credentials-and-identity / credential-security-model — tokens, scopes, and the identity model behind the Bearer you paste. (porting to the docs site shortly.)