Files
mcp/CHANGELOG.md
Rene Nochebuena 13a186c60a feat(mcp): index struct fields and interface method sets (#1)
Minor release. The indexer named composite types but could not describe
their shape: every declaration was truncated at the first brace, so a
struct symbol carried only its `type X struct` header and an interface
symbol only its `type X interface` header. Field names, field types, and
— most painfully — struct tags such as `env:"EINHERJAR_PG_HOST"` were
dropped, as were the method sets of every port interface. An assistant
could be told that `db-postgres` has a `Config` and a `Provider`, but not
what env vars configure the one or what methods the other requires. This
change captures both.

internal/index (schema):
- Symbol gains two optional fields. `fields` ([]Field) carries a struct's
  field set — name, type, raw struct tag (surrounding backticks stripped),
  doc comment, and an `embedded` marker. `methods` ([]Method) carries an
  interface's method set — name, signature without the leading `func`, and
  doc comment, including embedded interfaces. Both are omitempty and absent
  for every other kind.
- SchemaVersion is deliberately unchanged. The two additions are additive
  and omitempty, so an older consumer parses the new index unchanged; per
  the existing rule the constant only bumps on a breaking format change.

internal/index (builder):
- collectSymbols now inspects each type's TypeSpec and, for a *ast.StructType
  or *ast.InterfaceType, fills the new Symbol members. A grouped field
  declaration (`x, y int`) yields one Field per name; an embedded field or
  interface yields an entry with an empty name.
- New helpers: typeSpecType (underlying type expr of a lone type spec),
  extractFields, extractIfaceMethods, fieldDoc (doc comment or trailing
  line comment), and nodeString — a non-truncating printer used for field
  types, tags, and method signatures, distinct from formatNode which keeps
  truncating to produce the one-line header.

internal/index (search):
- matches() now also tests the query against struct field names, field
  types, and struct tags, and against interface method names and
  signatures. A query like an env-var key or a method name now resolves to
  the type that declares it.

internal/tools:
- get_symbol and search_symbols descriptions updated to advertise the new
  struct-field and interface-method coverage. No input/output schema change
  beyond the additive Symbol fields, which get_symbol already returns whole.

internal/index (tests):
- New builder_test.go — the package previously had no tests. Builds a
  temporary module fixture and asserts capture of struct fields (with tags
  and docs), embedded fields, interface methods (with signatures and docs),
  embedded interfaces, and discovery of a struct by one of its struct tags
  (the failure mode that motivated the change).

Docs:
- CHANGELOG.md gains an [Unreleased] entry; README.md tool table updated to
  state that get_symbol returns struct fields and interface methods and
  that search_symbols matches fields, tags, and methods.

No new dependencies. The committed data/index.json placeholder is untouched
— the index is regenerated at image build (Dockerfile runs cmd/indexer),
so a deployment must be rebuilt to serve the richer index; a server still
running the prior image keeps serving the older, member-less one.

Reviewed-on: #1
Co-authored-by: Rene Nochebuena Guerrero <rene@nochebuena.dev>
Co-committed-by: Rene Nochebuena Guerrero <rene@nochebuena.dev>
2026-06-10 10:38:30 -06:00

7.1 KiB

Changelog — einherjar/mcp

All notable changes to this module are documented here. Format follows Keep a Changelog. This module adheres to Semantic Versioning.


[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