Files
mopac-glpi-go/README.md
T

9.1 KiB

mopac-glpi-go

A 100% Go client library, CLI (mglpi), and MCP server (mglpi-mcp) for the GLPI REST API (ITSM/CMDB). Replaces python glue for every CMDB update: one static binary, zero third-party modules (stdlib only), machine-parseable JSON output, and tokens that never appear in flags, logs, or error strings. Clones the architecture of mopac-redmine-go file for file.

Status: 2026-09-03 — v0 complete and green: changes (create / show / list / transition / followup), CI search + raw item fetch, profiles + agent-profile auto-switch, session lifecycle (init / kill), 0600 env-file config, -o json on every command, exit codes 0/1/2 with one-line API-error stderr, and a stdio MCP server (initialize / tools-list / 6 tools). Built and tested entirely against a fake GLPI (unit suite + end-to-end smoke); the live CMDB (cmdb.knownelement.com) attaches with zero code change via MGLPI_URL/MGLPI_APP_TOKEN/MGLPI_USER_TOKEN. [#767]

Architecture

flowchart LR
    subgraph host[Host]
        M[mglpi CLI\nbin/mglpi] --config 0600 env --> M
        P[mglpi-mcp\nstdio JSON-RPC] --> M
        S[smoke/smoke.sh]
    end
    subgraph docker[Digest-pinned Docker builder — ALL dev]
        B[go build / vet / test]
        F[smoke/fakeglpi\nstateful in-memory GLPI]
    end
    subgraph wire[Library: package glpi]
        C[Client\nApp-Token + Session-Token headers only]
    end
    M --> C
    P --> C
    C --> F
    C -.-> G[(real GLPI\ncmdb.knownelement.com)]
    S --> B
    S --> F

All compilation, vetting, and testing runs inside the digest-pinned golang:1.26-bookworm builder (./dev.sh); the host never runs a Go toolchain. Tests and smoke talk only to the in-process/containerized fake; the real CMDB is never contacted by the test suite.

GLPI quirks modeled (and tested)

  • Create endpoints return an ARRAY: [{"id":N,"message":"..."}] — never an object. CreateChange / AddFollowup parse it.
  • Input shapes differ per endpoint: Change create takes {"input":{...}} (object); ITILFollowup REQUIRES {"input":[{...}]} (array of objects) — the fake rejects the wrong shape with a 400, and the client sends each faithfully.
  • Search rows are keyed by field-id string under forcedisplay: {"data":[{"1":"name","2":7,"12":3}]} — parsed into typed rows (fields 1 name, 2 id, 12 status).
  • Sessions: POST /initSession (App-Token + Authorization: user_token ...) returns {"session_token":"..."}; every later call carries Session-Token; killSession cleans up (the client re-inits transparently afterwards).
  • Profiles: GET /getMyProfiles lists the user's profiles; POST /changeActiveProfile with {"profiles_id":N} switches the session's active profile. Agent mode uses this to move into the right role (e.g. 5 = Hotliner) before working.
  • initSession auth failures are auth failures: GLPI answers 400 (ERROR_GLPI_LOGIN / ERROR_APP_TOKEN_...) for bad credentials — the client maps that to ErrAuth, not ErrValidation.

Quickstart (verified against the fake)

This is the exact flow the smoke run (./dev.sh smoke) executes:

./dev.sh check          # build + vet + test inside the Docker builder
./dev.sh smoke          # boots fake GLPI on 127.0.0.1:8602, drives bin/mglpi

# against the real CMDB instead (0600 env file, see env.example):
export MGLPI_URL=https://cmdb.knownelement.com/apirest.php
export MGLPI_APP_TOKEN=<app token>
export MGLPI_USER_TOKEN=<user token>

mglpi whoami                                       # profiles, [active] marker
mglpi change create --title "Disk pressure" --urgency 4 <<'EOF'
- array05 on pfv-tsys5 at 92%
EOF
mglpi change list --status new
mglpi change show 104
mglpi change transition 104 solved
mglpi change followup 104 <<'EOF'
REPORT delivered: array expanded
EOF
mglpi ci search Computer web
mglpi ci show Computer 101 -o json

Agent mode: put MGLPI_PROFILE_ID=5 in the env file (or pass --profile 5) — the CLI switches to that profile right after InitSession, so profile-gated servers accept the calls. whoami never switches. Add -o json to any command for machine output.

Command reference

Command Effect Key flags
whoami the session's GLPI profiles -o json
change create --title T create change (body from stdin) --content FILE|-, --urgency 1..5, --impact 1..5, --profile N, -o json
change show ID one change with content -o json
change list search (forcedisplay 1/2/12) --status N|NAME, -o json
change transition ID STATUS move status (name or number) -o json (re-fetches)
change followup ID append ITILFollowup (body from stdin) --content FILE|-, --profile N
ci search TYPE TERM name-substring search -o json
ci show TYPE ID raw CI object -o json (default shape)
help usage

Flags may appear before or after positionals (mglpi change show 104 -o json parses).

Configuration

Source Keys Discipline
environment MGLPI_URL, MGLPI_APP_TOKEN, MGLPI_USER_TOKEN, MGLPI_PROFILE_ID (optional) env wins over file
--config PATH (also $MGLPI_CONFIG, default ~/.config/mglpi/env) same keys file must be 0600 or stricter; refused before any read; parsed in pure Go (no sourcing/expansion)

MGLPI_URL is the FULL API base INCLUDING /apirest.php — e.g. https://cmdb.knownelement.com/apirest.php, not just the server host.

Tokens travel only in headers: App-Token on every request, Authorization: user_token ... on initSession, Session-Token after. Response bodies are never surfaced in error strings — the fake GLPI deliberately echoes the presented token in error bodies, and the test suite proves nothing leaks (TestTokensNeverLeak, plus the smoke redaction pass).

Exit codes and stderr

Code Meaning stderr shape
0 ok
1 usage / config error one line, names the problem (never values)
2 API error one line: mglpi: glpi: <sentinel>: http NNN — parseable

Sentinels (library): glpi.ErrAuth, ErrNotFound, ErrValidation, ErrServer, ErrUnreachable, ErrMalformedResponse — all comparable with errors.Is.

Library surface (what the harness calls)

import "git.knownelement.com/ukrrs/mopac-glpi-go/glpi"

c := glpi.New(glpi.Config{BaseURL: url, AppToken: app, UserToken: user})

_ = c.InitSession(ctx)                       // also lazy on first call
_ = c.ChangeActiveProfile(ctx, 5)            // agent mode (5 = Hotliner)
profs, _ := c.GetMyProfiles(ctx)             // id/name/is_active
id, _ := c.CreateChange(ctx, "title", "<p>content</p>", 3, 4)
ch, _   := c.GetChange(ctx, id)              // full read shape
rows, _ := c.ListChanges(ctx, glpi.StatusNew) // search, fields 1/2/12
_       = c.TransitionChange(ctx, id, glpi.StatusSolved)
_       = c.AddFollowup(ctx, id, "note")     // {"input":[{...}]} shape
cis, _  := c.SearchCI(ctx, "Computer", "web")
obj, _  := c.GetItem(ctx, "Computer", 101)   // raw map for ci show
_       = c.KillSession(ctx)

Status helpers: glpi.StatusNewStatusClosed constants, StatusName(id), StatusID(name). All methods take a context.Context.

MCP server

cmd/mglpi-mcp is a minimal stdio JSON-RPC MCP server (stdlib only): initialize (echoes protocol_version + serverInfo), tools/list (six tools), tools/call, ping. Tool failures are results with isError: true; only unknown methods are JSON-RPC errors (-32601).

docker exec -i mopac-cli-mglpi mglpi-mcp          # from the compose exec-host
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}' \
  | mglpi-mcp --config ~/.creds/mglpi.env

Tools: change_create, change_list, change_transition, change_followup, ci_search, ci_show.

Development

./dev.sh build|vet|test|check|smoke|mglpi|mcp|shell   # everything runs in the digest-pinned builder
make build|vet|test|check|smoke                       # same, Makefile front door

Test suite: table-driven round-trips against the stateful fake (internal/fakeglpi): session/header discipline, create/array-reply parsing, keyed search rows, transition, followup input-shape contract, CI search, profiles + the RequireProfile rejection path (agent mode), error mapping (400/401/403/404/422/5xx → sentinels), unreachable/malformed handling, and token redaction. Shell scripts are shellcheck-clean (including info level).

Status

Area State
Library (changes, followups, search, profiles, sessions) done, green
CLI surface (mglpi) done, green
MCP server (mglpi-mcp, 6 tools) done, green (handshake + calls tested)
Fake-GLPI test suite done, green (unit + smoke)
Token redaction done, tested (fake echoes all three tokens; nothing leaks)
Dev harness (Docker-only builds) done (dev.sh/Makefile)
Live-CMDB verification pending first live attach via MGLPI_URL/MGLPI_APP_TOKEN/MGLPI_USER_TOKEN

License: AGPLv3 (see LICENSE). Pattern source: https://git.knownelement.com/ukrrs/mopac-redmine-go