Skip to content

A minimal STDIO ↔ HTTP proxy built in Elixir. Legatus bridges agents and MCP servers, translating JSON-RPC dialogue between local and remote worlds.

License

Notifications You must be signed in to change notification settings

sovetnik/legatus

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Legatus

JSON-RPC STDIO ↔ HTTP Proxy
A messenger agent speaking between worlds

Legatus is a minimal, stateless bridge that translates dialogue over STDIN into HTTP actions. It embodies the Umwelt principle of perception–action unity: every request passes through a complete functional circle — perception, cognition, action — before returning as a meaningful response.

Why Legatus Exists

Legatus solves a fundamental problem: how to let any process speak JSON-RPC over STDIO while delegating execution to an HTTP realm.

Use cases:

  • Editor integrations: Connect your editor to language servers over HTTP
  • CLI tools: Build command-line tools that proxy to HTTP APIs
  • Agent communication: Enable processes to communicate via standard streams
  • Testing: Interact with HTTP JSON-RPC servers from shell scripts

Legatus is not merely a tool; it is a ritual of translation — a disciplined pattern for agent-to-world communication.

Quick Start

# Build the escript
mix escript.install hex legatus

# or
git clone git@github.com:sovetnik/legatus.git
mix escript.build

# Start your JSON-RPC HTTP server (example on port 4000)
# Then send a request:
echo '{"jsonrpc":"2.0","method":"add","params":[2,3],"id":1}' | \
  ./legatus http://localhost:4000/rpc

Expected output:

{"jsonrpc":"2.0","result":5,"id":1}

That's it. One line in, one line out. STDIO becomes HTTP, HTTP becomes STDIO.

Philosophy: The Umwelt Architecture

The architecture follows Jakob von Uexküll's Umwelt concept — organisms perceive and act within their own "subjective universe":

  • Aussenwelt — The external world (STDIO streams)
  • Merkwelt — Perception layer (request validation)
  • Wirkwelt — Action layer (HTTP transport)
  • Umwelt — The complete functional circle (pipeline coordinator)
  • Geist — The animating principle (main loop)

Legatus acts within the Clausura Operationalis — it knows only the world it perceives and acts upon it coherently. Its simplicity is not limitation but discipline.

Usage Modes

As Escript (recommended for production)

mix escript.build
./legatus http://localhost:4000/rpc

As Mix Task (for development)

mix legatus http://localhost:4000/rpc

With Bearer Token Authentication

When your upstream server requires authentication, pass the token via environment variable:

# Using escript
token=your_secret_token ./legatus http://localhost:4000/rpc

# Using mix task
token=your_secret_token mix legatus http://localhost:4000/rpc

Legatus will automatically add the Authorization: Bearer <token> header to all HTTP requests.

Editor integration example (Zed, Claude Code, etc.):

{
  "context_servers": {
    "my_server": {
      "source": "custom",
      "enabled": true,
      "command": "legatus",
      "args": ["http://localhost:4000/rpc"],
      "env": {"token": "your_secret_token"}
    }
  }
}

Pipeline Flow

Every request flows through the complete Umwelt cycle:

STDIN → Geist.loop → Aussenwelt.receptio → Merkwelt.percipere → 
Wirkwelt.portare → Aussenwelt.profanatio → STDOUT

Each arrow represents a transformation of meaning, not just data.

Architecture

Data Flow

The pipeline uses tagged tuples to track data state:

  1. Receptio (Aussenwelt): Parse JSON
    {:phaenomenon, map} | {:fiasco, json_error}

  2. Percipere (Merkwelt): Validate request
    {:actio, map} | {:fiasco, error_map}

  3. Portare (Wirkwelt): HTTP transport
    {:gloria, map} | {:fiasco, error_map} | {:silentium, map}

  4. Profanatio (Aussenwelt): Format output
    {:gloria, json} | {:fiasco, json} | {:silentium, "Nullius in verba"}

  5. Emit (Geist): Write to STDOUT or skip

Module Responsibilities

  • Legatus — Entry point and configuration
  • Geist — Main loop (STDIN → STDOUT cycle)
  • Aussenwelt — I/O boundaries (parse/format JSON)
  • Merkwelt — Request validation (JSON-RPC compliance)
  • Wirkwelt — HTTP coordinator (interprets transport responses into Umwelt concepts)
  • Wirkwelt.Httpc — HTTP transport (pure POST with status/body)
  • Chronica — Logging (stderr diagnostics)

Transport Layer Design

Wirkwelt follows a clean separation of concerns:

Wirkwelt behaviour contract:

@callback post(request :: map()) :: 
  {status :: pos_integer(), body :: map() | String.t()} | {:error, reason :: term()}
  • Transport (Httpc): Returns raw HTTP responses {200, body} or {:error, reason}
  • Coordinator (Wirkwelt): Interprets responses into {:gloria, map()}, {:silentium, map()}, or {:fiasco, map()}

This design makes it easy to add new HTTP adapters (Finch, Hackney) without changing business logic.

Error Handling

All errors are JSON-RPC compliant:

  • -32700 Parse error (invalid JSON)
  • -32600 Invalid Request (missing method)
  • -32000 HTTP errors (4xx/5xx)
  • -32001 Transport errors (connection refused)

Configuration

Legatus is configured via command-line argument — no static config required:

mix legatus http://localhost:4000/rpc

The upstream URL is set dynamically at runtime via Application.put_env/3.

Design Principles

  1. Tagged tuples for explicit data flow
  2. Pure functions where possible (I/O at boundaries)
  3. Pattern matching over conditionals
  4. Separation of concerns (parse, validate, transport, format)
  5. Testability through dependency injection

JSON-RPC Support

Requests

  • ✅ Standard requests with id
  • ✅ Notifications (no id)
  • ✅ Batches (array of requests)

Responses

  • ✅ Success responses (result)
  • ✅ Error responses (error)
  • ✅ HTTP 204 handling (notifications)
  • ✅ Batch responses

Limitations

  • STDIO-based (one request per line)
  • HTTP POST only (no WebSocket/SSE)
  • No built-in retry logic
  • Single upstream server

License

See LICENSE file.

Etymology

  • Legatus (Latin) — envoy, ambassador, messenger
  • Umwelt (German) — environment, "self-centered world"
  • Geist (German) — spirit, mind, animating principle
  • Aussenwelt (German) — outer world
  • Merkwelt (German) — perceptual world
  • Wirkwelt (German) — world of action

Next Steps

Explore further:

Related projects:

  • Skull — The cognitive substrate for agent systems
  • Reticulum Universalis — Communication patterns for distributed agents

Legatus is an experiment in operational closure — every component speaks the same language of perception and action. If this resonates with you, welcome to the conversation.

About

A minimal STDIO ↔ HTTP proxy built in Elixir. Legatus bridges agents and MCP servers, translating JSON-RPC dialogue between local and remote worlds.

Topics

Resources

License

Stars

Watchers

Forks

Languages