Hawkeyecontrol plane

Connect your AI

Point your AI client at your own machines.

Hawkeye is an MCP server. Your AI client launches it as a local process; it gives that client 81 tools that reach the machines you have enrolled — shell, desktop control, browsers, file transfer, and a shared channel your agents talk to each other on. The paired session is root-equivalent on every machine it reaches: a human authorises the join — by minting the install link, and by approving any machine that cannot attest strongly — and separately approves the device-code pairing that lets a client authenticate at all, but individual actions are not gated once paired. Below is the exact command for each client, how pairing authenticates it, and a check that proves it worked instead of hoping.

hawkeye-bridge v2.6.3 81 tools stdio, local subprocess zero npm dependencies

What you are wiring up

One local process, 81 tools, configured entirely by environment.

There is no daemon to configure here and no port to open. The client spawns a verified Node >=22.13.0 executable with node:sqlite and mcp-server/server.js, talks MCP to it over stdin/stdout, and the server dials out to your machines' tunnels. Everything it needs is these variables:

variabledefaultwhat it does
HAWK_WORKER optional https://hawkeye-browser.framebright-sync.workers.dev Control-plane worker: job queue fallback, one-shot render, R2 staging.

The 7 agent_* tools work without pairing (agent_hello, agent_say, agent_inbox, agent_window, agent_roster, agent_ack, agent_heartbeat). They are a local table on your own machine, not authority over a machine, and requiring a credential to say “hello, I am working in repo X” meant sessions that could not comply with their own instructions. Every other tool still fails closed until you have paired — see below.

Node version: that channel is backed by node:sqlite, unflagged in Node 22.13. The installers resolve and verify that floor before writing a launch command. If a client starts the server under an older Node, the server looks for a compatible executable and re-execs before the handshake; if none exists it emits an actionable warning and the agent_* calls report the exact runtime error. Install Node 22.13+ or set HAWK_NODE to a compatible executable.

Pairing

How a client authenticates — no key to type or paste.

Everything moves to pairing: the old master-key model is retired. Nothing above needs a value from you at install time. Instead, the first tool call the client makes returns a short device code and a pairing URL instead of a result:

What the first tool call prints

pairing required — open https://gethawkeye.app/pair?code=HAWK-XXXX-XXXX
and approve this device. Then retry the tool.

Open that URL, sign in if you are not already, and approve the device once. The server polls in the background and stores the resulting bearer token at ~/.hawkeye/paired-token.json (mode 0600) — not in your client's own config, so it survives re-registering the MCP server and is never visible to whatever prints your claude mcp list / config.toml. Retry the same tool call and it goes through. Every client card above shares this exact flow; there is nothing client-specific left to configure for auth.

Treat the approval like handing out a root password, because that is what it is: the paired session is root-equivalent — shell, desktop control, browsers and file transfer — on every machine your org has enrolled. Approve a device code only when you were the one who just ran the install command.

A different, older credential still exists — do not confuse the two

The machine installer (install-hawkeye-linux.sh / -macos.sh / -windows.ps1) still writes a machine bridge key at ~/hawkeye/bridge/.bridge_key (Linux/macOS) or <InstallRoot>\bridge\.bridge_key (Windows). That is a real, current, unrelated credential — it authenticates the daemons running on an enrolled machine to hawkeye's transfer/browser infrastructure, not an MCP client to the pairing service above, and #76 did not touch it. You will never need to read or paste it for anything on this page. It is also distinct from the single-use join key Installs mints: that one enrols a machine; pairing above authorises a client. Three names, three different jobs — worth saying plainly rather than letting a reader guess which one a given instruction means.

Per client

Install it, configure it, and prove the tools are visible.

Each block below is a real command from this project's own installers, not an illustration. Where a client genuinely cannot work, that is the first thing its card says.

Claude Code (CLI)

supported

Registers a local stdio server with the claude CLI. This is the path the project itself runs on, and bin/install-claude-code.sh does nothing more than compose and run the command below — no key to type or paste; the server pairs itself on first use.

Install — the script (dry run first, always)

bin/install-claude-code.sh            # prints the exact command
bin/install-claude-code.sh --apply    # actually registers it

It is idempotent: if claude mcp list already names hawkeye it changes nothing. On first tool call afterwards, the server prints a short device code and a gethawkeye.app/pair URL — open it, approve the device once, and every following call authenticates with the resulting token. No --key flag exists any more; it was retired with the master-key model.

Install — by hand, if you would rather not run a script

claude mcp add hawkeye --scope user -- node /path/to/hawkeye/mcp-server/supervisor.js

supervisor.js, not server.js directly — it respawns the real server across a self-update without the client noticing. --scope user registers it for every project, not just this one; claude mcp add --help lists the other scopes (local, project).

Verify

claude mcp list

Expected: a line naming hawkeye. Then start Claude Code and ask it to call hawk_health.

If the tools do not appear inside a session, the client did not load the server — restart Claude Code after registering.

Claude Desktop

supported · macOS + Windows

Claude Desktop installs a local stdio server as an .mcpb extension bundle (the format previously called .dxt): a zip with manifest.json at its root. Hawkeye builds one from the source tree. The bundle declares darwin and win32 only — Claude Desktop ships for macOS and Windows, so advertising Linux here would be false; on Linux use the Claude Code or Codex CLI cards instead.

Build the bundle

bin/build-mcpb.sh
# -> dist/hawkeye-bridge-<version>.mcpb

The manifest is generated, never hand-written: the <version> in that filename is mcp-server/package.json's, and the tool list is regenerated from the server's own tools/list, so the bundle cannot advertise a tool the server does not have. Run the command and it prints the exact path.

Install it

Claude Desktop -> Settings -> Extensions -> install the .mcpb file
No key to enter — the dialog only asks for optional fleet-location overrides

The manifest's user_config is now four optional URL fields (Worker / VM / PC / SOCKS proxy), not a credential — the master-key field is gone entirely. They default to this project's own fleet; change them in the same dialog to your machines' tunnels, or the tools will call somewhere you do not own. On first tool call the server pairs itself: a short device code and a gethawkeye.app/pair URL appear in the chat, you approve the device once in a browser, and the resulting token is stored by the server, not by Claude Desktop.

Verify

# macOS
ls ~/Library/Application\ Support/Claude/Claude\ Extensions
# Windows (PowerShell)
dir "$env:APPDATA\Claude\Claude Extensions"

Expected: the extension listed there, and Hawkeye Bridge visible under Settings -> Extensions.

Then ask Claude for hawk_health in a new chat. An extension that installed but shows no tools usually means the app needs a restart.

ChatGPT desktop app — Codex mode, and the Codex CLI

supported

There is no separate “Codex desktop app”: Codex is a mode inside the ChatGPT desktop app for macOS and Windows, and that mode — like the Codex CLI — reads ~/.codex/config.toml and runs local stdio MCP servers. Both are wired by the same file, so one script does both.

Install

bin/install-codex.sh            # dry run: prints the exact block
bin/install-codex.sh --apply    # writes it (existing file backed up alongside)

Or write it yourself — ~/.codex/config.toml

[mcp_servers.hawkeye]
command = "node"
args = ["/path/to/hawkeye/mcp-server/server.js"]

It must be the user-level ~/.codex/config.toml. The desktop app ignores a project-scoped .codex/config.toml (openai/codex#13025), which is why the installer writes the user-level file even when you are inside a repo. No [mcp_servers.hawkeye.env] table — there is no key to write, in plaintext or otherwise; the server pairs itself on first tool call instead (a device code and a gethawkeye.app/pair URL print in the reply, you approve once in a browser, the token is stored by the server).

Verify

grep -A4 'mcp_servers.hawkeye' ~/.codex/config.toml

Expected: the command and args you just wrote. Restart the app or CLI, then ask for hawk_health.

Any other MCP client

manual setup

Hawkeye is an ordinary stdio MCP server with no client-specific code in it, so anything that can launch a subprocess and speak MCP over stdin/stdout can use it. Most clients take one of these two shapes. Point command at a Node binary and args at mcp-server/server.js — no credential belongs in either shape below; the server pairs itself on first tool call. The env block is optional and only for overriding which fleet the server talks to, shown here so a client that requires the field can still be satisfied.

JSON (the shape most clients use)

{
  "mcpServers": {
    "hawkeye": {
      "command": "node",
      "args": ["/path/to/hawkeye/mcp-server/server.js"]
    }
  }
}

TOML (Codex-style clients)

[mcp_servers.hawkeye]
command = "node"
args = ["/path/to/hawkeye/mcp-server/server.js"]

# optional — only if you run your own control-plane worker
[mcp_servers.hawkeye.env]
HAWK_WORKER = "https://your-worker.example"

Verify — without trusting any client's UI

NODE_BIN="$(bin/require-node-sqlite.sh)" || exit 1
printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
  | "$NODE_BIN" mcp-server/server.js | grep -c '"name":"hawk_health"'

Expected: 1

That is the MCP handshake done by hand: initialize, then tools/list. It needs no key (only tools/call is gated), it exits as soon as stdin closes, and it proves the server itself works before you start blaming a client. Swap the grep for grep -o '"name":"[a-z_]*"' | sort -u | wc -l and it prints 81.

ChatGPT chat mode, and the mobile apps

not possible today

Said plainly rather than buried: you cannot connect ChatGPT's ordinary chat mode, or any phone app, to Hawkeye right now. Those surfaces accept only a remote MCP endpoint (Streamable HTTP/SSE, with OAuth or nothing). Hawkeye is stdio-only — it runs as a subprocess next to your files — so there is nothing for them to point at. “ChatGPT works” is true of Codex mode and false of chat mode, and collapsing the two would be the lie.

No remote MCP endpoint is configured on this deployment, so there is nothing to paste into a connector field. An operator sets MCP_REMOTE_URL once such an endpoint exists; until then, use a computer client above — those are live today.

Why it is not just switched on: Hawkeye is root-equivalent on the machines it reaches. An internet-facing relay for that, without an authorization story worked out first, is more attack surface than the feature is worth.

Proof, not vibes

One check that works no matter which client you used.

Client UIs disagree about where they show MCP tools, and “it seems connected” is not evidence. Run the handshake yourself, from the repo root:

Any OS with node on PATH

NODE_BIN="$(bin/require-node-sqlite.sh)" || exit 1
printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
  | "$NODE_BIN" mcp-server/server.js | grep -c '"name":"hawk_health"'

Expected: 1

A 1 means the server started, completed initialize, and listed hawk_health among its tools — so any failure after that is the client's wiring, not the server. A 0 means the server did not answer: check that the path in your config is the real one and that node resolves on the PATH your client hands the subprocess (a desktop app inherits the desktop session's PATH, which is often not your shell's).

Then, inside your client, ask it to call hawk_health. That is the one tool whose whole job is to report the state of both daemons, the job queue, the browser pool and desktop availability — if it answers, the whole chain is up.

Honest gaps

What is not built, so you do not find out the hard way.

Known limits, today

  • The payload is not code-signed, and its checksum is self-published. The install payload does now carry mcp-server/, with a per-file MANIFEST.sha256 you can verify after extracting. But there is no signing certificate, no notarisation and no transparency log anywhere in this pipeline — so the digest is worth exactly as much as your TLS connection to this site. Whoever could serve you a tampered archive could serve you a matching hash. A signature would survive a compromised host; a self-published hash does not.

    And you cannot audit it yourself: the Hawkeye source repository is private. You receive server.js and can hash it, but you cannot diff it against a public tree, and it runs with your user's full authority on every machine you enrol. That is a real thing to weigh, and it is why this section exists rather than a badge that says secure.
  • There is no hosted .mcpb download. The bundle is built locally by bin/build-mcpb.sh. Serving it from this site (and code-signing it) is an open decision, not a shipped feature.
  • Nothing here is code-signed. Same as the desktop app on the home page: Windows SmartScreen will warn, macOS Gatekeeper will refuse a plain double-click.
  • No remote MCP endpoint. Which is why ChatGPT chat mode and the phone apps are marked not-possible above rather than “coming soon”.
  • Per-action approval is not enforced yet, and this is the biggest limit on the page. Enrollment and client pairing require approval. After pairing, allowed actions execute without per-action approval: each machine's policy is evaluated and written to the audit trail in observe mode (XFER_POLICY_MODE defaults to observe, deliberately) rather than applied. A connected AI client can still raise an explicit human request when it creates one, and cross-org access is still a request the other side grants. What that gives you between those boundaries is a record of what was done, not a gate in front of it. Read it as evidence after the fact, not as a control that stops something first. One caller stands outside even the record's attribution: the shared fleet key is a break-glass credential, accepted without an identity chain, so its actions are attributable to the key rather than to a person, and the policy that is being observed is not evaluated for it at all. Its uses are counted and the recent ones kept, which is how a machine can show you how often break-glass was reached for. Narrowing it is open work, not a shipped property — treat possession of that key as possession of the fleet. Current claim state: observe_only. Anyone holding the key has shell, desktop control, browsers and file transfer on every enrolled machine; treat it accordingly. Policies shows what each machine's policy WOULD have blocked, so enforcement can eventually be turned on with real data in front of it rather than none — that page existing does not mean enforcement is on anywhere; it is the prerequisite this note used to say was missing.

Already signed in at https://www.gethawkeye.app? Connect an AI client shows the same wiring with your org's details, and Installs mints the single-use link that enrols a machine. New here — start with Install.