# AGENTS.md — go-server-with-otel (template) A ready-to-fork template app for the `go-app` framework: HTTP + gRPC + grpc-gateway + MCP + OTEL/Prometheus, wired and building. Fork it, rename it, replace the `demo` domain with your service. See go-app's AGENTS.md for framework internals. ## Kick off a new app (do these in order) 1. **Rename via the Makefile** (rewrites module path, binary/app name, Dockerfile, helm, buf `merge_file_name`, main.go `appName`; deletes the old swagger file). GNU `sed` required (macOS: `brew install gnu-sed` or coreutils on PATH). The target is interactive, so pipe confirmation: ```sh echo y | make rename NAME=your.gitremote/pathto/newapp APP=new-app-name ``` It does **not** rename the `demo` domain — that's steps 4–6. Afterward verify `git grep go-server-with-otel` is empty and update `.gitea/workflows/ci.yml`. 2. **Edit config** (`pkg/config/custom.go`): put app fields on `ServiceConfig` (keep the embedded `*config.AppConfig`). Add `env:"..."` tags for anything env-configurable — **and see the env gotcha below.** Update `config.yaml`, `env-sample`, and `helm/values.yaml` to match. Then `make schema`. 3. **Define your proto**: replace `proto/demo/app/v1alpha1/` with `proto//v1alpha1/`, update `PROTO_DIRS` in the Makefile, and run `make proto` (buf; managed mode → generates into `api/`, so the proto `go_package` option is overridden — don't fight it). Delete `api/demo`. 4. **Implement `service.AppService`** (`Init`, `GetGRPC`, `GetHTTP`) in a new `pkg//` package, mirroring `pkg/demo`: - `grpc/` — gRPC impl (embed `pb.Unimplemented...Server`); `GetServices` returns `*opts.GRPCService`; `GetDialOpts` adds the insecure creds the gateway needs. - `mcp/` — `mcp.NewServer` exposed as `HTTPHandler{Prefix:"/api/mcp"}`; tools via `mcp.AddTool`, typically delegating to the gRPC impl. - Remove `demohttp/` unless you actually need plain HTTP funcs. 5. **Register it in `main.go`**: set `appServices = []service.AppService{&yoursvc}`. This should be the only functional change in `main.go`. 6. **Delete `pkg/demo`** and any remaining `demo` references. 7. Build/verify: `go build ./... && go test ./...`, then run and hit `/metrics`, `/api/...` (REST), `:8081` (grpc + reflection), `/api/mcp`. ## Gotchas the template inherits from go-app - **Custom-field env doesn't auto-apply.** `MustLoadConfigInto` only parses env for the embedded `AppConfig`. Add a `ServiceConfig.LoadEnv()` that runs `env.Parse` over the custom fields (nil the embedded `*AppConfig` first to keep go-app's precedence) and call it in `main.go` right after `MustLoadConfigInto`. - **REST gateway 404s** unless `grpcGatewayPathStrip: true` is set in the grpc config (default is false, so the `/api` prefix isn't stripped before matching). - **Prometheus double-unit suffix**: don't use `metric.WithUnit` for gauges whose names already carry the unit; the exporter appends another suffix. - MCP streamable HTTP: clients must POST to `/api/mcp/` (trailing slash); the bare path 307-redirects. ## What `make rename` does NOT touch The `demo` domain naming: package dirs (`pkg/demo*`), `proto/demo`, `api/demo`, types (`DemoService`, `DemoAppService`, `DemoTool`, …), `PROTO_DIRS`, and the demo values in `config.yaml`/`helm`. Rename those by hand in steps 2–6.