Files
state-hub/dashboard/src/docs/connecting.md

6.4 KiB

title
title
Connecting to the State Hub — Reference

Connecting to the State Hub — Reference

How Claude Code agents on local and remote machines connect to the State Hub API and its MCP server.


Architecture overview

The State Hub runs on the work laptop only. Remote machines (COULOMBCORE, Railiance nodes) never run their own copy — they connect to the single source of truth via an encrypted SSH reverse tunnel managed by ops-bridge.

Work laptop                         Remote machine (e.g. COULOMBCORE)
─────────────────────               ─────────────────────────────────
PostgreSQL :5432                    Claude Code session
    ↑                                   │
FastAPI  :8000  ←── ops-bridge ────→  :18000  (API health / tools)
MCP SSE  :8001  ←── ops-bridge ────→  :18001  (MCP for Claude Code)

Two services are exposed through the tunnel:

Service Local port Remote port Purpose
State Hub API 8000 18000 Health checks, direct curl queries
MCP SSE server 8001 18001 Claude Code MCP integration

Local setup (work laptop)

Start the services

cd ~/state-hub

make api        # FastAPI on :8000
make mcp-http   # MCP SSE server on :8001 (separate terminal)

make mcp-http sets MCP_TRANSPORT=sse MCP_PORT=8001 and starts the same MCP server that Claude Code uses locally in stdio mode. The SSE mode exposes the identical tool surface over HTTP.

Start the tunnels

bridge up   # brings up both tunnels defined in ~/.config/bridge/tunnels.yaml
bridge status

Both tunnels must show connected (or degraded — see below) before remote agents can connect.

Local Claude Code registration (stdio, default)

Claude Code on the work laptop uses the stdio MCP server. Registration is in ~/.claude.json via .mcp.json at the repo root. No changes needed for local use.


Remote setup (COULOMBCORE or any remote host)

Prerequisites

  • ops-bridge tunnels running on the work laptop (bridge status)
  • SSH key ~/.ssh/id_ops authorised on the remote host for user tegwick

One-time MCP registration on the remote

claude mcp add-json -s user state-hub \
  '{"type":"sse","url":"http://127.0.0.1:18001/sse"}'

Restart Claude Code after running this. That's the only setup required — no Python, no repo clone, no local services.

Verify connectivity

# API reachable through tunnel?
curl -s http://127.0.0.1:18000/state/health
# → {"status":"ok","db":"connected"}

# MCP SSE endpoint reachable?
curl -s --max-time 2 http://127.0.0.1:18001/sse | head -2
# → event: endpoint
# → data: /messages/?session_id=...

ops-bridge tunnel config

Tunnels are defined in ~/.config/bridge/tunnels.yaml on the work laptop:

tunnels:
  state-hub-coulombcore:        # API tunnel
    host: 92.205.130.254
    remote_port: 18000
    local_port: 8000
    ssh_user: tegwick
    ssh_key: ~/.ssh/id_ops
    actor: agent.claude-coulombcore
    health_check:
      url: http://127.0.0.1:18000/state/health
      interval_seconds: 30
      timeout_seconds: 5
    reconnect:
      max_attempts: 0           # retry forever
      backoff_initial: 5
      backoff_max: 60

  state-hub-mcp-coulombcore:    # MCP SSE tunnel
    host: 92.205.130.254
    remote_port: 18001
    local_port: 8001
    ssh_user: tegwick
    ssh_key: ~/.ssh/id_ops
    actor: agent.claude-coulombcore
    health_check:
      url: http://127.0.0.1:18001/sse
      interval_seconds: 30
      timeout_seconds: 5
    reconnect:
      max_attempts: 0
      backoff_initial: 5
      backoff_max: 60

  state-hub-railiance01:        # API tunnel
    host: 92.205.62.239
    remote_port: 18000
    local_port: 8000
    ssh_user: tegwick
    ssh_key: ~/.ssh/id_ops
    actor: agent.claude-railiance01
    health_check:
      url: http://127.0.0.1:8000/state/health
      interval_seconds: 30
      timeout_seconds: 5
    reconnect:
      max_attempts: 0
      backoff_initial: 5
      backoff_max: 60

  state-hub-mcp-railiance01:    # MCP SSE tunnel
    host: 92.205.62.239
    remote_port: 18001
    local_port: 8001
    ssh_user: tegwick
    ssh_key: ~/.ssh/id_ops
    actor: agent.claude-railiance01
    health_check:
      url: http://127.0.0.1:18001/sse
      interval_seconds: 30
      timeout_seconds: 5
    reconnect:
      max_attempts: 0
      backoff_initial: 5
      backoff_max: 60

ops-bridge source: ~/ops-bridge · SSH key: ~/.ssh/id_ops


Bridge states

State Meaning
connected SSH process alive, health check passing
degraded SSH process alive, health check failing (SSE endpoint streams — not always a real error)
reconnecting SSH dropped, backoff loop active
stopped Not started or manually stopped

The MCP SSE tunnel often shows degraded because the /sse health check receives a streaming response rather than a clean 200 — the tunnel is still functional. Confirm with curl http://127.0.0.1:18001/sse from the remote.


MCP transport modes

The MCP server (state-hub/mcp_server/server.py) supports two transports selected by environment variable:

Variable Default Effect
MCP_TRANSPORT stdio stdio transport (local Claude Code)
MCP_TRANSPORT=sse SSE/HTTP transport for remote clients
MCP_PORT 8001 Port for SSE mode
API_BASE http://127.0.0.1:8000 State Hub API URL the MCP server calls

On a remote machine the MCP server process runs locally inside Claude Code — it does not run on the remote host. The transport layer (SSE over the tunnel) handles the connection.


Adding a new remote host

  1. Generate or reuse an SSH key pair (recommend ~/.ssh/id_ops)
  2. Add the public key to the remote host's ~/.ssh/authorized_keys
  3. Check port availability on the remote: ssh user@host "ss -tlnp | grep 18001"
  4. Add two tunnel entries to ~/.config/bridge/tunnels.yaml (API + MCP)
  5. bridge up <tunnel-name> for each
  6. On the remote: claude mcp add-json -s user state-hub '{"type":"sse","url":"http://127.0.0.1:18001/sse"}'

The State Hub runs on the work laptop only. Remote machines are read-only consumers connected via tunnel — they never own a copy of the database.