Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

src/bin/nornir-server.rs

//! `nornir-server` — tonic gRPC server on `:7878` (all 20 services incl. Saga),
//! the intended single owner of the iceberg warehouse lock.
//!
//! This bin is now **thin**: the whole server stack (service registration, the
//! workspace registry, the monitor poll loop, boot/serve) lives in the reusable
//! library module [`nornir::server_grpc`] so that BOTH this bin AND the fat
//! `dwarves-full` bin (nornir `:7878` side-by-side with dwarves `:7900`) call the
//! SAME [`nornir::server_grpc::run`] — no twinned service registration (REUSE law).
//!
//! Config surface (flag > env > default), unchanged: `--token`/`NORNIR_SERVER_TOKEN`
//! (≥16 chars, else `<home>/.nornir/token`); `--addr`/`NORNIR_SERVER_ADDR` (default
//! `127.0.0.1:7878`). `Health.Ping` is the one un-intercepted (token-free) RPC.

// Multicore-friendly global allocator. The deep-scan flush columnizes millions of
// small Strings across many threads; glibc malloc's arena lock caps that at ~2x
// scaling, mimalloc scales near-linear (per-thread heaps). Defined in the bin (the
// final artifact), not the library module.
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Build-version LAW: `--version`/`-V` short-circuits BEFORE booting the server
    // (no config, no lock, no bind needed to answer "which build am I?"). Prints the
    // pure-Rust git-hash identity `nornir::VERSION_LINE` (= `v<pkg> (<git-sha>)`,
    // stamped by build.rs on every build) — the SAME source the main `nornir` bin
    // and `Health.Ping` use, so the server's reported identity can never drift.
    if std::env::args()
        .skip(1)
        .any(|a| a == "--version" || a == "-V")
    {
        // `version_full`, not bare `VERSION_LINE`: the daemon is a launchable like
        // any other and must say WHICH ATOM it is. Measured 2026-08-01, before this:
        // `nornir-server --version` printed only `nornir-server v0.5.4 (339d6b2)`
        // while `nornir` and `nornir-solo` printed an atom line, so the claim that
        // every launchable identifies its atom was false for the one binary you
        // actually deploy.
        println!(
            "nornir-server {}",
            nornir::version_full(env!("CARGO_BIN_NAME"))
        );
        return Ok(());
    }

    // ── `nornir-server mcp` — THE MCP STDIO SERVER, COMPILED IN ────────────────
    //
    // The owner, 2026-08-03: *"mcp skall väl ingå inkompilerat i server!"* This atom
    // is the one declared `role = server, linkage = fat` — "the service,
    // state-holding, everything compiled in" — so MCP is a capability of it, not
    // the sixth atom of a five-atom matrix. `src/bin/nornir-mcp.rs` became
    // `nornir::mcp_stdio` and is dispatched here.
    //
    // ⚠ A SUBCOMMAND, NOT AN RPC, and the transport is the reason. MCP is stdio:
    // the agent SPAWNS this process and speaks JSON-RPC over its stdin/stdout.
    // The gRPC daemon below owns `:7878` and has no client stdin to read, so
    // folding MCP into `server_grpc` would have deleted the capability while
    // looking like it kept it. The daemon's `SubmitMcpCalls` RPC is the other
    // direction and unchanged: it ingests this module's telemetry.
    //
    // The two modes are mutually exclusive by construction — this returns before
    // `server_grpc::run()` — so one binary never tries to hold the port AND own
    // stdio at once.
    let argv: Vec<String> = std::env::args().collect();
    if argv.get(1).map(String::as_str) == Some("mcp") {
        // Everything after `mcp` is the MCP server's own argv; nothing inside it
        // reads `std::env::args`, which is what lets it run in another binary.
        return nornir::mcp_stdio::run(env!("CARGO_BIN_NAME"), &argv[2..]).await;
    }

    nornir::server_grpc::run().await
}