Skip to main content

Module server

Module server 

Source
Expand description

isb serve: the MCP server layer, with the tools supplied by the embedder.

Security model:

  • TCP listeners bind loopback only. Remote clients arrive through a cloudflared tunnel, behind a Cloudflare Access application with Managed OAuth; Cloudflare runs the OAuth flow and isb stays the resource origin.
  • With Access configured, every /mcp request must carry a valid Cf-Access-Jwt-Assertion for the application’s audience, so a request reaching the port by another route is still refused.
  • A TCP listener without Access is refused unless the embedder opts in, and then only accepts browser requests whose Origin is localhost, which blocks DNS rebinding.
  • The unix socket (0600, in a 0700 directory) is the trusted local path: filesystem permissions are the gate, and its callers are Caller::Local, which Caller::is_trusted reports.
  • /healthz never requires auth and reveals only what the embedder puts in it.
  • A listener can carry extra Routes (isb serve mounts the identity endpoints, /api/v1/auth/*, this way). They authenticate their own callers; with Access configured they sit behind it, as /mcp does.
  • Listener::public_routes are served ahead of Access, for requests that carry their own credential (app webhooks, signed by the sender).
  • Listener::preview sees every request first, and takes the ones addressed to a preview host (a workspace port’s own origin), which it authenticates itself; nothing of isb’s (UI, API, headers) is served on those hosts.

Re-exports§

pub use access::AccessValidator;
pub use access::Identity;
pub use http::Shutdown;
pub use mcp::Authenticated;
pub use mcp::Caller;
pub use mcp::Hooks;
pub use mcp::Registry;
pub use mcp::Tool;
pub use mcp::ToolHandler;
pub use mcp::ToolPolicy;

Modules§

access
Cloudflare Access JWT validation.
aliases
Argument spellings a newcomer reaches for, mapped to the canonical ones before a call is authorized. The schemas and docs show only the canonical names; the aliases make a guess work instead of failing on an unknown field.
client
Calling isb serve tools from the CLI over its unix socket.
http
A minimal synchronous HTTP/1.1 server: one request per connection, a thread per connection, a hard cap on connections, and every read and write bounded.
mcp
MCP over Streamable HTTP, hand-rolled JSON-RPC 2.0.
openapi
GET /api/v1/openapi.json: the whole HTTP surface as one OpenAPI 3.1 document, generated from what serves it:
service
Installing isb serve as a systemd user service.
ssh
SSH without open ports: GET /orgs/<org>/api/v1/ssh?instance=NAME upgrades to a websocket whose binary frames are an SSH connection’s bytes, both ways, to an sshd the embedder starts inside the instance (isb serve: sshd -i through incus exec, docs/guides/ssh.md). Nothing in the instance listens, and nothing on the host opens a port: the websocket is the daemon’s own, behind its usual authentication.
ssh_config
What isb ssh-config writes: Host blocks whose ProxyCommand is isb ssh-proxy, and a known_hosts file of the instances’ host keys under each block’s HostKeyAlias, so plain ssh, scp, editors and herdr machine add pin the right key without ever trusting on first use.
tailnet
Tailnet identity, for isb serve --superadmin-tailnet and orgs’ agent identities: who is at the other end of a TCP connection from a tailnet address, asked of the local tailscaled.
terminal
A terminal over a websocket: GET /orgs/<org>/api/v1/terminal?app=NAME (or ?instance=NAME) upgrades to a websocket bridged to a pseudo-terminal the embedder opens (isb serve: a shell in one of the app’s replicas, or in an instance of the org).

Structs§

Listener
One address the server answers on, with its own gate and tool policy.

Enums§

ListenerKind

Functions§

default_socket_path
Where the CLI and the server meet: $ISB_SERVE_SOCKET, else $XDG_RUNTIME_DIR/isb/serve.sock, else a per-uid directory under /tmp. On macOS, where the daemon runs inside the isb machine, it is that machine’s forwarded socket, ~/.isb/machine/isb/serve.sock.
handler
What a listener’s requests go to: /healthz, /mcp, the REST surface and its routes, through its hooks and policy.
serve
Serve until SIGINT or SIGTERM.
serve_shared
serve, with a registry the embedder also keeps (to serve it on listeners it adds later, spawn_private).
serve_until
Serve until shutdown is triggered, then give in-flight requests up to 10s. Every listener is bound before any is served, so a bad one fails startup.
serve_until_shared
serve_until with a shared registry.
spawn_private
Serve handler on a private (RFC 1918) address that is not loopback, such as an org bridge’s, until stop (or a bind failure, returned at once). The caller’s handler decides who gets in: nothing about such an address keeps anyone out.

Type Aliases§

Healthz
Health for GET /healthz: (ok, body). 200 when ok, else 503. Called on every probe, so it must be cheap.
Routes
Extra routes on a listener: Some answers the request, None leaves it to the server (a 404).