AGENTS.md¶
Canonical agent instructions for this repository. Humans should read
README.mdanddocs/.CLAUDE.mdandmcpserver/CLAUDE.mdare thin imports — do not duplicate content there.
Project¶
Mattermost server plugin (mattermost-ai) that integrates LLM providers. Go 1.26 backend + React/TypeScript webapp (Node 24.11).
Commands¶
make help lists every documented target with a one-line description.
Pre-PR aggregate (lint + unit tests + e2e shard coverage + i18n/lockfile drift; recommended):
make checkLint with auto-fix (also re-extracts i18n strings):
make check-style-fixLint only:
make check-styleAll unit tests:
make testSingle Go test:
go test -v ./<pkg> -run TestNameBuild & deploy plugin to a running Mattermost (linux-amd64 only by default):
make deploySetMAKE_ALL_PLATFORMS=1only if you’re deploying to a Mac- or Windows-native server.E2E (self-contained, no env setup needed; slow, defer to CI when possible):
make e2eSingle e2e spec:
cd e2e && npx playwright test tests/path/spec.ts --reporter=listPrompt evals (non-interactive):
make evals-ciProvider:LLM_PROVIDER=openai|anthropic|azure|openaicompatible|all make evals-ciModel:ANTHROPIC_MODEL=claude-sonnet-4-5-20250929 make evals-ciStreaming benchmarks:
go test -bench=. -benchmem ./llm/... ./streaming/...
When make check fails, run the underlying targets individually (make check-style, make test, make check-shards, make check-i18n, make check-locks) to isolate which step broke. CI runs the same drift checks; if i18n or a lockfile is out of sync, those targets regenerate the file in place — review and commit.
Repository layout¶
Most Go packages live at the repo root, not under server/.
server/— plugin entrypoint, lifecycle, configuration adapter.api/,mmapi/— HTTP handlers; Mattermost API wrappers.llm/— LLM provider abstractions and provider implementations.mcp/,mcpserver/— MCP client; embedded/HTTP/stdio MCP servers and tools.format/— formatting of Mattermost entities for LLM consumption (see Conventions).Other top-level feature packages exist (e.g.
bots/,channels/,threads/,meetings/,search/,embeddings/,streaming/,toolrunner/,websearch/, …). Read the package name and skim the package source before assuming purpose — note in particular that bothconversation/andconversations/exist and are not the same.config/— plugin configuration types and migration.webapp/— React/TypeScript UI bundle (webapp/src/).e2e/— Playwright + Testcontainers end-to-end tests.evals/,cmd/evalviewer/— prompt evaluation harness and TUI.i18n/— extracted translation strings.docs/— user/admin docs.public/bridgeclient/— separate Go module published for other plugins.
Conventions¶
Linters (golangci-lint, ESLint, gofmt/goimports, header check, editorconfig) already enforce formatting, imports, error checking, license headers, and indentation. The rules below are the ones a linter cannot enforce.
File names:
snake_case.go/snake_case.ts(x).TypeScript/React: PascalCase components, strict typing, always styled-components, never inline
style={{...}}.New user-facing strings must go through i18n (
make i18n-extractpicks them up).Go tests must be table-driven when there is more than one case.
Never introduce a new test/mocking library; prefer to test against real implementations instead.
All formatting of Mattermost entities (posts, users, channels, teams, members) for LLM consumption or tool output must go through the
format/package. Neverfmt.Sprintfmodel types inline; add a formatter toformat/instead.E2E shard maintenance: when adding a new spec that should run in CI, assign it in
e2e/scripts/ci-test-groups.mjsin the same change.make check-shardsvalidates coverage and is part ofmake check. Mock/non-real-api tests go in the lighteste2e-shard-*group; provider-backed tests go in the matching*-real*group; balance by expected runtime, not alphabetically.Test for behavior that could break due to a real bug. Before writing a test ask: “If this test fails, does it indicate a real bug in our code?” In particular, do not assert on implementation details like validation order or which error appears first.
OpenTelemetry tracing¶
The plugin emits OpenTelemetry traces. Agent-relevant rules:
Thread
ctx context.Contextas the first parameter through every entry point → LLM call code path. Don’t introducecontext.Background()shortcuts in production code; the request-scoped context is what makes spans correlate.Existing spans live in
bifrost/(LLM calls),llm/tools.go,conversations/tool_handling.go,mcp/,search/,websearch/, andstreaming/. Theotelginmiddleware adds HTTP spans automatically.To add a span:
ctx, span := telemetry.Tracer().Start(ctx, "span name", trace.WithAttributes(...)), thendefer span.End(). Record errors withspan.RecordError(err)andspan.SetStatus(codes.Error, msg). Reuse attribute keys fromtelemetry/attributes.goinstead of inventing new ones.When a
*llm.Contextparameter would shadow thecontextpackage in the same file, import"context"asstdcontext.Config fields (
TelemetryOutput,OpenTelemetryEndpoint) and local Tempo/Grafana setup live indocs/admin_guide.md.
Never do¶
Never edit
webapp/dist/,server/dist/, ordist/— regenerate with the build commands above.Never hand-edit
webapp/src/i18n/en.json—make check-style-fixre-extracts it from webapp source. Add the user-facing string at the call site instead. (Server-sidei18n/en.jsonis hand-curated; mmgotool extraction doesn’t apply to this repo’snicksnyder/go-i18nsetup.)Never push to
master; open a PR.
Gotchas¶
If
make install-go-toolsfails to buildmattermost-govet, the pinned commit is incompatible with the active Go toolchain. The Makefile prints the exact fix: bumpMATTERMOST_GOVET_VERSIONin the Makefile to a newer commit. This is a real problem to fix, not a warning to ignore.postgres/pgvector_test.goboots its own pgvector container viatestcontainers-go(pgvector/pgvector:pg17);go test ./postgres/...works on a fresh checkout as long as Docker is available. To run against an existing pgvector instance for fast iteration, setPGVECTOR_TEST_DSN.Plugin config is migrated to the plugin DB on activation. For automation, read/write
GET/PUT /plugins/mattermost-ai/admin/configrather than patching the Mattermost server config.The embedded MCP server requires
SiteURLto be set on the Mattermost server, and uses in-memory transport (no HTTP). On tool name collisions across MCP servers, first-registered wins; later duplicates are skipped with a warning.public/bridgeclient/is a separately published Go module, not HTTP assets;HAS_PUBLICis intentionally cleared in the Makefile.
Pull requests and commits¶
Commit subject: one succinct line. Optional Jira prefix (
MM-12345:) or short scope (fix:,docs:,webapp:) is fine.Do not add
Co-Authored-Bylisting the agent.Use the GitHub PR template for the PR body.
Pointers¶
Read these only when the trigger applies:
Working inside
mcpserver/(config-vs-runtime, search service wiring, adding optional capabilities):mcpserver/AGENTS.md.Configuring providers, agents, or the admin UI:
docs/admin_guide.md.When working on prompt evals or modifying the eval harness:
cmd/evalviewer/README.md.