Files
mcp-chassis/README.md
T
mathiasandClaude Opus 4.8 69d389d3f7 chore(module): rename gitea.d-ma.be → git.d-ma.be module path
The host rename broke `go mod download` for consumers: the server serves a
go-import meta tag of git.d-ma.be/... which no longer matches the old
gitea.d-ma.be/... import path (hyperguild #74). Rename the module path to
match. Single leaf package (auth/), no internal self-imports — go.mod +
README example only. Tagged v0.2.0; v0.1.0 (old path) left intact for
unmigrated consumers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 21:34:18 +02:00

97 lines
2.8 KiB
Markdown

# mcp-chassis
Shared Go library for Mathias-owned MCP servers. Provides the
auth + middleware primitives that every MCP server needs.
## Why
By 2026-05-22 there were three+ MCP servers (`gitea-mcp`, `brain-mcp` /
`ingestion`, future ones from `template-go-agent`) each carrying their
own near-identical:
- Dex JWT validator (~80 LOC, identical `jwx/v2` plumbing)
- Bearer middleware (~50 LOC, dual-mode static + JWT)
- RFC 9728 protected-resource metadata handler (~25 LOC)
The homelab architecture review's spike S3 (see
`gitea.d-ma.be/mathias/infra/docs/superpowers/handoffs/2026-05-22-mcp-chassis-spike.md`)
concluded a thin shared lib pays for itself within the first migration.
This is that lib.
## Non-goals
- Replacing each MCP's tool registration / handler logic — that is per-domain.
- Solving HTTP routing — consumers keep their own `http.ServeMux`.
- Solving observability — see `gitea.d-ma.be/mathias/hyperguild/ingestion/internal/metrics`
for the hand-rolled Prometheus pattern. May absorb a `metrics` subpackage here
later, once a second consumer needs it.
## Packages
### `auth`
- `JWTValidator` — Dex OIDC JWT validation. `nil` is a valid value meaning
"JWT auth disabled".
- `BearerMiddleware` — static-Bearer-or-Dex-JWT gate. Static wins first; only
emits `WWW-Authenticate: Bearer ... resource_metadata=...` on 401 when
`resourceMetadataURL` is non-empty (claude.ai OAuth discovery).
- `ProtectedResourceHandler` — RFC 9728 metadata document for
`GET /.well-known/oauth-protected-resource`.
## Usage
```go
package main
import (
"context"
"net/http"
"os"
"git.d-ma.be/mathias/mcp-chassis/auth"
)
func main() {
staticToken := os.Getenv("BRAIN_MCP_TOKEN")
dexIssuer := os.Getenv("DEX_ISSUER_URL")
audience := os.Getenv("MCP_AUDIENCE")
resourceURL := os.Getenv("MCP_RESOURCE_URL")
validator, err := auth.NewJWTValidator(context.Background(), dexIssuer, audience)
if err != nil {
panic(err)
}
mux := http.NewServeMux()
mux.HandleFunc("GET /.well-known/oauth-protected-resource",
auth.ProtectedResourceHandler(resourceURL, dexIssuer))
mux.Handle("/mcp", auth.BearerMiddleware(
staticToken,
validator,
"brain",
resourceURL+"/.well-known/oauth-protected-resource",
mcpHandler(),
))
_ = http.ListenAndServe(":3300", mux)
}
func mcpHandler() http.Handler { /* per-domain */ return nil }
```
## Versioning
Trunk-based development on `main`. Tagged with semver. Consumers pin
specific tags (`go.mod` `require git.d-ma.be/mathias/mcp-chassis v0.x.y`)
and bump deliberately.
Migrations are documented per-consumer in the consumer's CHANGELOG / commits.
## Dependencies
- `github.com/lestrrat-go/jwx/v2` — JWKS cache + JWT parsing. Same dep
every MCP already had; no new transitive cost when adopting the chassis.
- `github.com/stretchr/testify` — tests only.
stdlib otherwise.