Files
mcp/CHANGELOG.md
T

271 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Changelog — einherjar/mcp
All notable changes to this module are documented here.
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
This module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
---
## [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 frameworks v1.3.0 removal of `web.Config.AllowedOrigins`.
### Added
- `validate_snippet` rule `web.allowedorigins-removed` flags any reference to the removed
`web.Config.AllowedOrigins` field (selector or struct-literal key), pointing callers to
`server.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 root `web.Config`.
## [1.2.0] — 2026-08-08
Minor. Scaffold and wire docs track the frameworks v1.2.0 CORS change.
### Changed
- Scaffold no longer invents an `APP_CORS_ORIGINS` var — it reads the frameworks real
`EINHERJAR_SERVER_CORS_ORIGINS` (now on `server.Config`) via `cfg.Server.CORSOrigins`.
- `.env.example` documents `EINHERJAR_SERVER_CORS_ORIGINS` in the server section instead of a
bespoke app-owned var.
### Added
- Wire builtin docs explain when to use `web.New` (batteries-included) vs `server.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-noop` rule** (`validate_snippet`). Flags `mw.CORS(...)` with a `"*"` literal —
which the framework now rejects at boot — and points to `CORSAllowAll()` or explicit origins.
### Changed
- **`get_scaffold`**: CORS is now env-gated (`CORSAllowAll` in local, `mw.CORS(origins)` elsewhere);
composes `launcher.Config` and `health.Config` so `EINHERJAR_COMPONENT_STOP_TIMEOUT` and
`EINHERJAR_HEALTH_CHECK_TIMEOUT` are reachable and appear in `.env.example`; the health hook uses
`health.NewHandlerWithConfig(...).ServeHTTP`; `.env.example` no longer emits `APP_CORS_ORIGINS=*`.
- **`wire` builtin**: documents the CORS convention and recommends `check_env`'s `composes` selectors.
### Fixed
- **`web.server-not-appended`** no longer false-positives on a correctly-appended server; it fires
only when `server.New` is present and `.Append` is 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_env` `composes` input** — declare the exact config structs an app composes as
`module/subpackage/Struct` selectors (e.g. `web/server/Config`, `db-postgres/Config`,
`core/logz/Config`), alongside or instead of the coarse `modules` list. A var counts as
composed when any listed module or struct declares it.
### Changed
- **`not-composed` now reasons over the composed var set, not just modules.** With struct
selectors, `check_env` flags struct-level dead vars — e.g. `EINHERJAR_SERVER_CORS_ORIGINS`
(which lives on `web.Config`) is now reported dead when the app composes `web/server/Config`
rather than `web.Config`. Passing coarse `modules: ["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_env` tool.** 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 which `EINHERJAR_*` vars
exist; no hand-maintained list to drift.
- **`check_env` tool.** Checks a `.env` / `.env.example` against the framework: flags
`EINHERJAR_*` 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 way `EINHERJAR_SERVER_CORS_ORIGINS` is inert unless you compose `web.Config`).
- **`config.unknown-env-var` rule** (now twelve). `validate_snippet` rejects any struct field
tagged `env:"EINHERJAR_*"` whose name the framework does not declare — the exact class of
drift that shipped `EINHERJAR_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_scaffold` now derives `.env.example` from 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.Config`** instead of hard-coding the logger, so
`EINHERJAR_LOG_LEVEL` / `_JSON` are live and documented rather than silently ignored.
Verified end to end: `env.Parse` loads `slog.Level` from `EINHERJAR_LOG_LEVEL`. (Log format
is now env-driven; set `EINHERJAR_LOG_JSON=true` in production.)
- **The `wire` builtin routes the incremental flow to the new tools.** Composing a component
later now points at `get_config_env("<module>")` for its exact vars and `check_env` to
confirm `.env.example` is complete — and spells out the split between framework `EINHERJAR_*`
(discoverable, name-checked) and app-owned `APP_*` (your discipline).
### Fixed
- **`logz.Logger``logging.Logger`.** The logger interface is `contracts/logging.Logger`;
`logz.New` returns it. The scaffold's health hook and the `wire` builtin's `withUsers`
example typed loggers as the non-existent `logz.Logger`.
- **`postgres.Component``postgres.Provider`** in hook signatures. `postgres.New` returns a
`Component` (a lifecycle component that embeds `Provider`); hooks and `NewUnitOfWork` take a
`Provider`.
- **The real env tags throughout:** `EINHERJAR_SERVER_ADDR``EINHERJAR_SERVER_HOST` +
`_PORT`, and `EINHERJAR_PG_DATABASE``EINHERJAR_PG_NAME` (plus `EINHERJAR_PG_SSL_MODE`).
- The scaffold's minimal health hook no longer takes unused `logger`/`db` parameters.
## [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_scaffold` tool.** Returns the canonical minimum application scaffold as
ready-to-write files, with import paths filled from a `module` argument: a clean `main.go`
(godotenv autoload + `wire.Run()`), `internal/wire/wire.go` (the launcher assembly), a
composed `internal/config/config.go`, a health feature hook, and `.env.example`.
- **Three `validate_snippet` rules** (now eleven total), with a rules test suite:
`main.dirty` (the launcher/components built in `main` instead of `internal/wire`),
`main.no-godotenv-autoload` (a wire-convention `main` that never loads `.env`), and
`config.raw-getenv` (reading a framework `EINHERJAR_*` var via `os.Getenv` instead of
composing the component's `Config` type).
- **Config conventions in the `wire` builtin.** A `Config` section (compose the framework's
component configs, load with `caarlos0/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
- **`wire` builtin re-synced to the current gold standards** (`iron-dough-api`, `pei-api`):
`main.go` now shows the `_ "github.com/joho/godotenv/autoload"` blank import (previously
omitted, so the AI produced a `main` that never loaded `.env`); the assembly file is
`wire.go` (was `launcher.go`).
- **Version alignment.** The README badge and `serverVersion` were stale at `v0.1.0`; both
now read `v1.0.0`.
### Removed
- **Migrations and seeding from the `wire` builtin.** 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 `fields` array — field name, type, raw struct tag (backticks stripped), doc comment, and an `embedded` marker. `get_symbol` returns it; previously the signature was truncated to the bare `type X struct` header, 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 `methods` array — method name, signature (without the leading `func`), and doc comment, including embedded interfaces. Consumers can now see what a port like `db-postgres` `Provider` actually requires.
- **Search by field/tag/method.** `search_symbols` now 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/index` test 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; `SchemaVersion` is 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` (see `Dockerfile`). 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 from `LISTEN_FDS` via `github.com/coreos/go-systemd/v22/activation` when present, falling back transparently to TCP `-addr` binding 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 `/healthz` to `<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 `/healthz` route receive 404; update to `/mcp/healthz` (or whatever path matches your `-path` flag).
- **`README.md` deployment section** rewritten to be hosting-agnostic. Points at the `Dockerfile` and 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 `/mcp` location, 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. The `Dockerfile` at the module root remains the only portable, public-facing build artefact.
### Dependencies
- **Added:** `github.com/coreos/go-systemd/v22 v22.7.0` — used by `cmd/server` to 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-sdk` v1.0.0
- Listen address and HTTP path configurable via `EINHERJAR_MCP_ADDR` (default `:8080`) and `EINHERJAR_MCP_PATH` (default `/mcp`)
- `/healthz` liveness 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.go` package comment, sub-package doc comments, every exported symbol (type/interface/func/method/const/var) with signature + godoc, ADRs, README code-fence examples, dependency edges from `go.mod`, and the contents of `compliance_test.go` (interface assertions + structural test names)
- Appends a synthetic `wire` module documenting canonical Einherjar application wiring conventions
#### Tools (10)
- `list_modules` — enumerate every Einherjar module with purpose and sub-packages
- `get_module` — package doc, dependencies, sub-packages, key symbols, ADRs, compliance counts; optional embedded README
- `search_symbols` — full-text search across name, doc, sub-package, module
- `get_symbol` — full signature, doc, and source location for one symbol
- `list_adrs` — list architectural decision records, optionally filtered by module
- `get_adr` — fetch one ADR's markdown body
- `get_example` — canonical usage snippets extracted from module READMEs and the `wire` conventions
- `get_compliance` — interface assertions and structural test names from a module's `compliance_test.go`
- `get_changelog` — full CHANGELOG.md markdown for one module
- `validate_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-discarded`
- `logz.direct-env-read`
- `web.server-not-appended`
- `wire.hook-bad-signature`, `wire.hook-outside-beforestart`, `wire.route-specific-after-param`
#### Synthetic `wire` module
- Authored in `internal/index/builtins/README.md`; participates in `list_modules`, `get_module`, and `get_example` exactly 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 `Dockerfile` that 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