• v0.1.1 e23e86b06c

    Rene Nochebuena released this 2026-05-29 14:09:46 -06:00 | 0 commits to main since this release

    v0.1.1

    code.nochebuena.dev/einherjar/mcp

    Patch release. Two changes to cmd/server make the binary cleaner to run
    behind a unix socket on a reverse-proxied host, plus two repository-hygiene
    changes that follow from the same deployment exercise.


    Changes

    cmd/server

    • Systemd socket activation. The binary now inherits the listener from
      systemd's LISTEN_FDS protocol via
      github.com/coreos/go-systemd/v22/activation
      when present, falling back transparently to TCP -addr binding when not.
      The startup log records "mode":"socket-activated" or "mode":"tcp" so
      operators can confirm which path is live. Same binary, same flags, both
      modes — no env var or flag toggles behaviour.

    • Health probe moves under <path>/healthz. The handler is now
      registered relative to the -path flag. With the default -path /mcp,
      the probe URL becomes /mcp/healthz. The legacy root-level /healthz
      route is no longer registered. This lets a reverse proxy expose the entire
      MCP service through one location prefix instead of needing a second
      forward for the probe.

    Docs

    • Deployment section in README.md rewritten to be hosting-agnostic.
      The v0.1.0 draft prescribed a specific systemd layout; the new text
      points at the Dockerfile and at systemd socket activation as a supported
      binary mode without dictating one operator's setup. Adds an explicit note
      that any reverse proxy must disable response buffering on the /mcp
      location (streamable MCP delivers tool results via Server-Sent Events;
      default nginx, Envoy, or Caddy buffering breaks the stream).

    Repository hygiene

    • /deploy/ is now .gitignored. Local deployment artefacts (systemd
      units, reverse-proxy templates, per-release scripts) are operator-specific
      by design and live outside the public repository. The Dockerfile at the
      module root remains the only portable, public-facing build artefact.

    Upgrade Notes

    If you… Action
    Consume the MCP service from an MCP client (Claude, Cursor, Zed, etc.) None — the /mcp endpoint is unchanged.
    Monitor the service via the healthz probe Update the probe URL from /healthz to /mcp/healthz (or <your -path>/healthz if you have customised the path).
    Run the binary directly (no reverse proxy) None — the legacy -addr flag still binds a TCP listener; activation only kicks in when systemd has passed a listener.
    Run the binary under systemd with a socket unit The same binary now inherits the systemd listener automatically; no recompile, no flags.

    API

    No tool surface, no validation rules, no index schema, and no behaviour of
    the indexer changed. The 10 tools and 8 validation rules listed in
    v0.1.0's release notes remain the public surface. The only
    externally-visible URL change is the move of /healthz to
    <path>/healthz.


    Install

    go install code.nochebuena.dev/einherjar/mcp/cmd/server@v0.1.1
    

    Dependencies

    Module Version Status Role
    github.com/modelcontextprotocol/go-sdk v1.0.0 unchanged from v0.1.0 Official Go MCP SDK — server, streamable-HTTP transport, tool registration
    github.com/coreos/go-systemd/v22 v22.7.0 new in v0.1.1 Inherit listener from systemd via LISTEN_FDS; loaded only by cmd/server
    github.com/google/jsonschema-go v0.3.0 unchanged (indirect) JSON Schema generation for tool input/output validation
    github.com/yosida95/uritemplate/v3 v3.0.2 unchanged (indirect) RFC 6570 URI template support used by the SDK
    Downloads
  • v0.1.0 cc62906c6f

    Rene Nochebuena released this 2026-05-29 12:14:53 -06:00 | 1 commits to main since this release

    v0.1.0

    code.nochebuena.dev/einherjar/mcp


    Architecture Decisions Resolved

    Decision Outcome
    Deployment model — remote HTTP vs MCPB bundle vs local stdio Remote streamable-HTTP — every Einherjar user adds one URL; no per-machine Go install
    Knowledge model — live filesystem reads at runtime vs build-time index Build-time index — cmd/indexer produces JSON; server embeds via go:embed; runtime reads nothing from disk
    SDK / language — TypeScript vs Python (FastMCP) vs Go Go — github.com/modelcontextprotocol/go-sdk v1.0.0; natural for a framework that is Go
    Tool surface — 1-tool-per-action vs search+execute 1-per-action (10 tools) — surface is small enough to enumerate; AI assistants discover everything from tools/list
    Module dependency on einherjar/* None at compile time — indexer parses framework source on disk; mcp stays outside the framework dependency graph
    Wiring conventions exposure Synthetic 15th "wire" module — authored in internal/index/builtins/README.md, participates in list_modules/get_module/get_example like a real module
    Validation engine — real Go AST checks vs pattern rules Pattern rules (go/parser + selector-based matchers) — lightweight, no external tooling required at request time
    Root doc.go package comment surface First-class Module.Doc field — promoted from the empty-name SubPackage entry and returned by get_module
    compliance_test.go indexing Parsed for var _ Iface = impl assertions and Test* function names + doc comments; surfaced via get_compliance
    Module dependency edges Captured from each go.mod's require lines (einherjar/* only); returned by get_module.dependsOn
    auth-jwt.IssueTokenPair parity with micro-lib Intentionally not provided by the framework — wire canonical example shows the application signing access and refresh tokens itself via Signer.Sign(...)
    data/index.json git handling Committed once as a stub via git add -f; .gitignore strips subsequent indexer regenerations

    Tools

    Ten tools registered on initialise. Tool descriptions are surfaced via the MCP
    tools/list method; this section documents the input schemas and structured
    output shapes.

    list_modules

    // input
    {}
    
    // output
    { "modules": [{ "name": "core", "importPath": "code.nochebuena.dev/einherjar/core",
                    "purpose": "...", "goVersion": "1.26", "subPackages": ["launcher", "..."] }] }
    

    get_module

    // input
    { "name": "core", "includeReadme": false }
    
    // output
    { "name": "core", "importPath": "...", "purpose": "...", "doc": "<root doc.go comment>",
      "goVersion": "1.26", "dependsOn": ["contracts"],
      "subPackages": [{ "name": "launcher", "importPath": "...", "doc": "..." }],
      "keySymbols": [{ "kind": "interface", "name": "Launcher", "subPackage": "launcher",
                       "signature": "type Launcher interface { ... }" }],
      "adrs":       [{ "id": "ADR-001", "title": "core module composition" }],
      "compliance": { "interfaceAssertCount": 7, "testCount": 15 },
      "readme":     "<markdown, when includeReadme=true>" }
    

    search_symbols

    // input
    { "query": "Launcher", "limit": 25, "module": "core", "kind": "interface" }
    
    // output
    { "results": [{ "module": "core", "subPackage": "launcher", "kind": "interface",
                    "name": "Launcher", "signature": "...", "doc": "...", "file": "launcher/launcher.go", "line": 18 }],
      "total":   1 }
    

    get_symbol

    // input
    { "module": "core", "name": "Launcher", "subPackage": "launcher" }
    
    // output
    { "matches": [{ "module": "core", "subPackage": "launcher", "kind": "interface",
                    "name": "Launcher", "signature": "...", "doc": "...", "file": "...", "line": 0 }] }
    

    list_adrs

    // input
    { "module": "core" }
    
    // output
    { "adrs": [{ "module": "core", "id": "ADR-001", "title": "core module composition" }] }
    

    get_adr

    // input
    { "module": "core", "id": "ADR-001" }
    
    // output
    { "module": "core", "id": "ADR-001", "title": "...", "body": "<markdown>" }
    

    get_example

    // input
    { "module": "wire", "topic": "hook" }
    
    // output
    { "examples": [{ "module": "wire", "subPackage": "", "title": "Feature hook",
                     "language": "go", "code": "<source>" }] }
    

    get_compliance

    // input
    { "module": "core" }
    
    // output
    { "module": "core",
      "interfaceAsserts": [{ "module": "core", "interface": "logging.Logger",
                             "impl": "logz.New(logz.Config{})", "file": "compliance_test.go", "line": 27 }],
      "tests":            [{ "module": "core", "name": "TestAtMostOneExportedTypePerFile",
                             "doc": "", "file": "compliance_test.go", "line": 0 }] }
    

    get_changelog

    // input
    { "module": "core" }
    
    // output
    { "module": "core", "changelog": "<markdown>" }
    

    validate_snippet

    // input
    { "code": "package wire\n\nimport ..." }
    
    // output
    { "summary":  "1 error, 2 warnings, 0 notes",
      "findings": [{ "ruleId":   "launcher.missing-run",
                     "severity": "error",
                     "module":   "core",
                     "message":  "core/launcher constructed but Run() never called...",
                     "hint":     "After lc := launcher.New(logger); lc.Append(...); call ...",
                     "line":     7 }] }
    

    Validation rules shipped at v0.1.0

    Rule ID Severity Module What it catches
    launcher.missing-run error core Launcher constructed without lc.Run()
    launcher.no-components warning core launcher.New() called without any .Append(...)
    launcher.run-error-discarded warning core lc.Run() invoked as a statement (return value ignored)
    logz.direct-env-read warning core os.Getenv("EINHERJAR_LOG_*") bypassing logz config
    web.server-not-appended info web web/server constructed but not added to the launcher
    wire.hook-bad-signature warning wire with<Feature>(...) first param is not launcher.Launcher
    wire.hook-outside-beforestart warning wire repo/service/handler construction or route registration at the top level of a hook (outside lc.BeforeStart)
    wire.route-specific-after-param warning web /users/{id} registered before sibling /users/me (chi shadows the literal route)

    Commands

    cmd/server

    Usage: einherjar-mcp [flags]
    
    Flags:
      -addr string   listen address  (default ":8080",  env EINHERJAR_MCP_ADDR)
      -path string   MCP HTTP path   (default "/mcp",   env EINHERJAR_MCP_PATH)
    
    Endpoints:
      POST  /mcp       — streamable-HTTP MCP transport
      GET   /healthz   — liveness probe (200 "ok")
    

    cmd/indexer

    Usage: indexer [-out path] <einherjar-repo-root>
    
    Flags:
      -out string    output path for the generated index  (default "data/index.json")
    
    Argument:
      einherjar-repo-root  directory containing sibling Einherjar modules
                           (auth, core, web, …); defaults to ".."
    

    Environment

    Variable Default Effect
    EINHERJAR_MCP_ADDR :8080 Listen address for the MCP server
    EINHERJAR_MCP_PATH /mcp HTTP path served by the streamable-HTTP endpoint

    Install

    Binary

    go install code.nochebuena.dev/einherjar/mcp/cmd/server@v0.1.0
    einherjar-mcp -addr :8080 -path /mcp
    

    The installed binary embeds whatever framework index was produced at the moment
    the module was built. For an index built against your own checkout, build
    locally — see README.md.

    Container

    docker build -f mcp/Dockerfile -t einherjar-mcp:0.1.0 .   # from the einherjar repo root
    docker run --rm -p 8080:8080 einherjar-mcp:0.1.0
    

    The image is multi-stage; runtime is gcr.io/distroless/static-debian12:nonroot.


    Dependencies

    Module Version Role
    github.com/modelcontextprotocol/go-sdk v1.0.0 Official Go MCP SDK — server, streamable-HTTP transport, tool registration
    github.com/google/jsonschema-go v0.3.0 (indirect) JSON Schema generation for tool input/output validation
    github.com/yosida95/uritemplate/v3 v3.0.2 (indirect) RFC 6570 URI template support used by the SDK

    mcp has no compile-time dependency on any code.nochebuena.dev/einherjar/*
    module. The indexer reads framework source from disk and writes a JSON blob; the
    server embeds the blob. This isolation lets mcp index any version of the
    framework without versioning itself in lock-step.

    Downloads