Builtins README gains Bind/BindEmpty adapter rows + a path/query binding section (uuid/time via TextUnmarshaler; default: with min not omitempty,min). New info rule httputil.prefer-bind flags hand-rolled chi.URLParam / r.URL.Query() reads and points to Bind. Escape-hatch note rescoped to custom responses only.
17 KiB
Changelog — einherjar/mcp
All notable changes to this module are documented here. Format follows Keep a Changelog. This module adheres to Semantic Versioning.
[1.3.4] — 2026-08-13
Patch. Teaches the framework's v1.6.0 request-binding adapters (httputil.Bind / BindEmpty).
Added
- Wiring builtin documents
Bind/BindEmpty: the adapter table gains both rows, plus a "Binding path & query parameters" section withpath:/query:/json:tag examples, theuuid.UUID/time.Timeconversion note, and thedefault:+min(notomitempty,min) guidance so a defaulted parameter cannot slip past its bound. - New rule
httputil.prefer-bind(info): flagschi.URLParam/chi.URLParamFromCtx/r.URL.Query()read by hand inside a handler and points toBind— the escape hatch stays for genuinely custom responses only.
Changed
- The httputil "Escape hatch" note now scopes
HandlerFuncto custom responses (streaming, non-JSON), not parameter reading.Bindsymbols/signatures are picked up automatically by the indexer from the v1.6.0websource.
[1.3.3] — 2026-08-09
Patch. Documents the httputil handler adapters and configurable success status.
Added
- Wire builtin gains an "HTTP handlers (httputil)" section —
Handle/HandleNoBody/HandleEmpty, their default success statuses (200/200/204), andhttputil.WithStatus(web v1.5.0) for 201 Created / 202 Accepted, with the 2xx-only panic-at-wiring rule and automatic error mapping.
Fixed
- Wire builtin feature-hook example passes
loggerto the handler constructor (httputil needs it).
[1.3.2] — 2026-08-08
Patch. Scaffold/wire middleware order now mirrors web.New.
Changed
- Generated wire (and the wire builtin example) apply middleware in
web.New's order —Recoveroutermost, a time-ordered UUID v7 request ID (newRequestID), thenRequestLogger— with the env-gated allow-all CORS as the only deliberate divergence. Previously the scaffold putRequestIDbeforeRecoverand used a v4 request ID.
[1.3.1] — 2026-08-08
Patch. Correct the web.allowedorigins-removed rule message and migration docs to name the
real removal version: the field was removed in v1.3.0 (breaking-in-minor), not v2.0.0.
[1.3.0] — 2026-08-08
Minor. Tracks the framework’s v1.3.0 removal of web.Config.AllowedOrigins.
Added
validate_snippetruleweb.allowedorigins-removedflags any reference to the removedweb.Config.AllowedOriginsfield (selector or struct-literal key), pointing callers toserver.Config.CORSOrigins.
Changed
- Wire builtin docs cover the CORS single-source-of-truth and the v1.x → v1.3.0 migration.
- envspec test fixture updated: CORS lives on
server.Config, not the rootweb.Config.
[1.2.0] — 2026-08-08
Minor. Scaffold and wire docs track the framework’s v1.2.0 CORS change.
Changed
- Scaffold no longer invents an
APP_CORS_ORIGINSvar — it reads the framework’s realEINHERJAR_SERVER_CORS_ORIGINS(now onserver.Config) viacfg.Server.CORSOrigins. .env.exampledocumentsEINHERJAR_SERVER_CORS_ORIGINSin the server section instead of a bespoke app-owned var.
Added
- Wire builtin docs explain when to use
web.New(batteries-included) vsserver.New(full control, env-gated allow-all CORS), so generated services pick the right tier.
[1.1.2] — 2026-08-08
Patch. Fixes surfaced by an adversarial review plus the CORS wildcard trap.
Added
cors.wildcard-nooprule (validate_snippet). Flagsmw.CORS(...)with a"*"literal — which the framework now rejects at boot — and points toCORSAllowAll()or explicit origins.
Changed
get_scaffold: CORS is now env-gated (CORSAllowAllin local,mw.CORS(origins)elsewhere); composeslauncher.Configandhealth.ConfigsoEINHERJAR_COMPONENT_STOP_TIMEOUTandEINHERJAR_HEALTH_CHECK_TIMEOUTare reachable and appear in.env.example; the health hook useshealth.NewHandlerWithConfig(...).ServeHTTP;.env.exampleno longer emitsAPP_CORS_ORIGINS=*.wirebuiltin: documents the CORS convention and recommendscheck_env'scomposesselectors.
Fixed
web.server-not-appendedno longer false-positives on a correctly-appended server; it fires only whenserver.Newis present and.Appendis absent.
[1.1.1] — 2026-08-07
Patch. check_env gains struct-level granularity so it can catch dead vars that module
granularity cannot.
Added
check_envcomposesinput — declare the exact config structs an app composes asmodule/subpackage/Structselectors (e.g.web/server/Config,db-postgres/Config,core/logz/Config), alongside or instead of the coarsemoduleslist. A var counts as composed when any listed module or struct declares it.
Changed
not-composednow reasons over the composed var set, not just modules. With struct selectors,check_envflags struct-level dead vars — e.g.EINHERJAR_SERVER_CORS_ORIGINS(which lives onweb.Config) is now reported dead when the app composesweb/server/Configrather thanweb.Config. Passing coarsemodules: ["web"]keeps the previous behavior.
[1.1.0] — 2026-08-07
Minor release. The env-var story is now derived from the framework's real struct tags end
to end: a tool to read the truth, .env.example generated from it, and a rule that rejects
invented names. Also folds in the scaffold-symbol fixes that were staged as v1.0.1 (never
tagged separately). The generated scaffold compiles clean against einherjar v1.0.0.
Added
get_config_envtool. Lists the real environment variables a component config reads — name, declaring module/struct/field,required, and default — parsed from the indexed struct tags (internal/envspec). The single source of truth for whichEINHERJAR_*vars exist; no hand-maintained list to drift.check_envtool. Checks a.env/.env.exampleagainst the framework: flagsEINHERJAR_*names that don't exist (e.g.EINHERJAR_PG_DATABASE), required vars missing for the modules an app composes, and vars set for a module it does not compose (dead vars, the wayEINHERJAR_SERVER_CORS_ORIGINSis inert unless you composeweb.Config).config.unknown-env-varrule (now twelve).validate_snippetrejects any struct field taggedenv:"EINHERJAR_*"whose name the framework does not declare — the exact class of drift that shippedEINHERJAR_PG_DATABASE/_SERVER_ADDR. The valid-name set is injected from the index at startup, so the rule can never drift from the real tags.
Changed
get_scaffoldnow derives.env.examplefrom the index instead of a hand-written string. Required vars (no default) are emitted uncommented with a runnable dev value; defaulted vars are emitted commented, documenting the framework default. Names and defaults can no longer drift from the modules the scaffold composes (core/logz+web/server+db-postgres).- The scaffold composes
logz.Configinstead of hard-coding the logger, soEINHERJAR_LOG_LEVEL/_JSONare live and documented rather than silently ignored. Verified end to end:env.Parseloadsslog.LevelfromEINHERJAR_LOG_LEVEL. (Log format is now env-driven; setEINHERJAR_LOG_JSON=truein production.) - The
wirebuiltin routes the incremental flow to the new tools. Composing a component later now points atget_config_env("<module>")for its exact vars andcheck_envto confirm.env.exampleis complete — and spells out the split between frameworkEINHERJAR_*(discoverable, name-checked) and app-ownedAPP_*(your discipline).
Fixed
logz.Logger→logging.Logger. The logger interface iscontracts/logging.Logger;logz.Newreturns it. The scaffold's health hook and thewirebuiltin'swithUsersexample typed loggers as the non-existentlogz.Logger.postgres.Component→postgres.Providerin hook signatures.postgres.Newreturns aComponent(a lifecycle component that embedsProvider); hooks andNewUnitOfWorktake aProvider.- The real env tags throughout:
EINHERJAR_SERVER_ADDR→EINHERJAR_SERVER_HOST+_PORT, andEINHERJAR_PG_DATABASE→EINHERJAR_PG_NAME(plusEINHERJAR_PG_SSL_MODE). - The scaffold's minimal health hook no longer takes unused
logger/dbparameters.
[1.0.0] — 2026-08-07
The MCP reaches v1.0.0, aligned with the v1.0.0 framework. The headline is the
canonical application scaffold: the one opinionated starting point so an AI no longer
hand-rolls main.go and the launcher when creating a service from zero.
Added
get_scaffoldtool. Returns the canonical minimum application scaffold as ready-to-write files, with import paths filled from amoduleargument: a cleanmain.go(godotenv autoload +wire.Run()),internal/wire/wire.go(the launcher assembly), a composedinternal/config/config.go, a health feature hook, and.env.example.- Three
validate_snippetrules (now eleven total), with a rules test suite:main.dirty(the launcher/components built inmaininstead ofinternal/wire),main.no-godotenv-autoload(a wire-conventionmainthat never loads.env), andconfig.raw-getenv(reading a frameworkEINHERJAR_*var viaos.Getenvinstead of composing the component'sConfigtype). - Config conventions in the
wirebuiltin. AConfigsection (compose the framework's component configs, load withcaarlos0/env,APP_*for app-owned fields,EINHERJAR_*for framework ones) and a Config & .env.example section: every env var the config reads must also be documented in.env.example, kept in lock-step.
Changed
wirebuiltin re-synced to the current gold standards (iron-dough-api,pei-api):main.gonow shows the_ "github.com/joho/godotenv/autoload"blank import (previously omitted, so the AI produced amainthat never loaded.env); the assembly file iswire.go(waslauncher.go).- Version alignment. The README badge and
serverVersionwere stale atv0.1.0; both now readv1.0.0.
Removed
- Migrations and seeding from the
wirebuiltin. How migrations run and how the first admin is seeded (via code, a DB team, an endpoint, a webhook, …) is the developer's choice — it belongs to no Einherjar module and is not a hard convention, so it is out of the scaffold and the documented conventions.
[0.2.0] — 2026-06-10
Minor release. The indexer now captures the members of composite types, closing a gap where get_symbol and search_symbols could name a struct or interface but not describe its shape — most painfully, struct tags (env-var keys, json names) were invisible.
Added
- Struct fields in the index. Each struct-type symbol now carries a
fieldsarray — field name, type, raw struct tag (backticks stripped), doc comment, and anembeddedmarker.get_symbolreturns it; previously the signature was truncated to the baretype X structheader, so field names, types, and tags (e.g.env:"EINHERJAR_PG_HOST") were dropped entirely. - Interface method sets in the index. Each interface-type symbol now carries a
methodsarray — method name, signature (without the leadingfunc), and doc comment, including embedded interfaces. Consumers can now see what a port likedb-postgresProvideractually requires. - Search by field/tag/method.
search_symbolsnow also matches a query against struct field names, field types, and struct tags, and against interface method names/signatures — so an env-var key or a method name resolves to the type that declares it. internal/indextest suite covering field, tag, embedded-field, interface-method, and search-by-tag capture (the package previously had no tests).
Notes
- The index schema gains two optional (
omitempty) fields;SchemaVersionis unchanged because the change is additive and older consumers parse the new index unchanged. - This is a pure indexer/schema change. The live server picks it up on its next image build, which re-runs
cmd/indexer(seeDockerfile). A deployment that has not been rebuilt will still serve the older, member-less index.
[0.1.1] — 2026-05-29
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.
Added
- Systemd socket activation in
cmd/server. The binary inherits the listener fromLISTEN_FDSviagithub.com/coreos/go-systemd/v22/activationwhen present, falling back transparently to TCP-addrbinding otherwise. Startup log records"mode":"socket-activated"or"mode":"tcp". Same binary, no flag or env var to toggle.
Changed
- Health probe path moved from
/healthzto<path>/healthz(default/mcp/healthz). Lets a reverse proxy expose the entire MCP service through one location prefix. v0.1.0 consumers hitting the old/healthzroute receive 404; update to/mcp/healthz(or whatever path matches your-pathflag). README.mddeployment section rewritten to be hosting-agnostic. Points at theDockerfileand systemd socket activation as supported binary modes without prescribing one operator's setup. Adds the SSE-buffering caveat once: any reverse proxy must disable response buffering on the/mcplocation, otherwise Server-Sent Events get batched and streamable MCP sessions break./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. TheDockerfileat the module root remains the only portable, public-facing build artefact.
Dependencies
- Added:
github.com/coreos/go-systemd/v22 v22.7.0— used bycmd/serverto detect and use a systemd-passed listener.
Upgrade notes
| If you… | Action |
|---|---|
| Consume the MCP service from an MCP client (Claude, Cursor, Zed, etc.) | None — /mcp is unchanged |
| Monitor the service via the healthz probe | Update the probe URL from /healthz to /mcp/healthz |
| Run the binary directly (no reverse proxy) | None — -addr TCP binding still works the same way |
| Run the binary under systemd with a socket unit | The same binary now picks up the inherited listener automatically |
[0.1.0] — 2026-05-29
Initial release. The mcp module hosts the Einherjar Model Context Protocol server — a remote, streamable-HTTP service that teaches AI assistants about every other module of the framework.
Added
Server (cmd/server)
- Streamable-HTTP MCP server built on
github.com/modelcontextprotocol/go-sdkv1.0.0 - Listen address and HTTP path configurable via
EINHERJAR_MCP_ADDR(default:8080) andEINHERJAR_MCP_PATH(default/mcp) /healthzliveness endpoint- Embedded framework index loaded once at startup; in-memory for the lifetime of the process
Indexer (cmd/indexer)
- Walks an Einherjar repository checkout and produces
data/index.json - For each sibling module captures: import path, Go version, README (full + extracted tagline), CHANGELOG, root
doc.gopackage comment, sub-package doc comments, every exported symbol (type/interface/func/method/const/var) with signature + godoc, ADRs, README code-fence examples, dependency edges fromgo.mod, and the contents ofcompliance_test.go(interface assertions + structural test names) - Appends a synthetic
wiremodule documenting canonical Einherjar application wiring conventions
Tools (10)
list_modules— enumerate every Einherjar module with purpose and sub-packagesget_module— package doc, dependencies, sub-packages, key symbols, ADRs, compliance counts; optional embedded READMEsearch_symbols— full-text search across name, doc, sub-package, moduleget_symbol— full signature, doc, and source location for one symbollist_adrs— list architectural decision records, optionally filtered by moduleget_adr— fetch one ADR's markdown bodyget_example— canonical usage snippets extracted from module READMEs and thewireconventionsget_compliance— interface assertions and structural test names from a module'scompliance_test.goget_changelog— full CHANGELOG.md markdown for one modulevalidate_snippet— pattern-match a Go snippet against framework conventions; returns findings with severity, hint, and line
Validation rules (8)
launcher.missing-run,launcher.no-components,launcher.run-error-discardedlogz.direct-env-readweb.server-not-appendedwire.hook-bad-signature,wire.hook-outside-beforestart,wire.route-specific-after-param
Synthetic wire module
- Authored in
internal/index/builtins/README.md; participates inlist_modules,get_module, andget_exampleexactly like a real module - Sections: project layout,
Run()shape, feature hook shape, route ordering, authorization, middleware helpers, adapters at the wire boundary, migrations and seeds - All examples use einherjar import paths
Packaging
- Multi-stage
Dockerfilethat builds from the einherjar repository root (docker build -f mcp/Dockerfile .) so the indexer can walk every sibling module at image-build time - Distroless runtime image; static binary; non-root user