Thanks for contributing. This repository contains an MCP server in mcp/ and
an optional web UI in ui/. The MCP server must remain deployable without the
UI and must work with any compatible OneBusAway REST API implementation.
- Keep a change focused on one user or operational need.
- Do not commit API keys, bearer tokens, local cache files, or generated binaries.
- Preserve the default
stdiotransport. HTTP deployments must remain behind an authenticated TLS gateway. - Update the relevant production TODO or documentation when a change completes a planned hardening item.
Tool handlers must not decode arbitrary JSON or pass raw upstream payloads to an MCP client. The server uses two explicit type layers:
OneBusAway REST API
↓
client API DTOs (`mcp/client/api_dtos.go`)
↓ explicit handler mapping
MCP response types (`mcp/tools/responses.go`)
↓
LLM / MCP client
Types in mcp/client/api_dtos.go model the portion of the OneBusAway REST API
that the MCP needs. They use the API's field names and JSON tags, such as
routeId and tripHeadsign. These types are intentionally implementation
neutral: the backend may be Maglev, onebusaway-application-modules, or another
compatible OBA server.
Add or change an API DTO when the upstream REST contract changes or when a
tool needs another upstream field. Keep the type in the client package.
Types in mcp/tools/responses.go are the public contract returned to an LLM.
They use concise, stable fields and MCP-oriented JSON names, such as
route_id and headsign. They must contain only information useful for the
agent's task; do not expose full OBA reference collections, internal metadata,
or unused enum fields.
Add a named response type for every new tool response. Map the API DTO to this type in the handler. This explicit mapping is where we choose what an agent is allowed to see and keeps MCP clients insulated from upstream API changes.
- Do not use
map[string]any,anyassertions, or presentation-text parsing for OBA API data in a tool handler. - Do not return an upstream OBA response directly to the MCP client.
- Do not define anonymous handler-local response structs. Put named public
response types in
mcp/tools/responses.go. - Keep responses bounded. Use a documented limit, filtering, or pagination for potentially large results.
- Prefer one task-focused MCP tool over exposing a REST endpoint verbatim.
- Treat every tool argument as untrusted input and validate IDs, coordinates, dates, ranges, and limits before making upstream calls.
- Decide the agent task and ensure it does not overlap with an existing tool.
- Add the tool registration, input schema, clear usage description, and input
validation in
mcp/tools/. - Add the necessary upstream DTOs and typed client method in
mcp/client/. - Add or update the named MCP response type in
mcp/tools/responses.go. - Map API DTOs to the MCP response explicitly in the handler.
- Add tests for upstream decoding and the public response contract.
- Update the tool list and any user-facing documentation.
Run these from mcp/ before submitting a change:
go test ./...
go vet ./...
go build ./...When the environment has a read-only default Go build cache, use a temporary cache instead:
GOCACHE=/tmp/oba-mcp-go-cache go test ./...
GOCACHE=/tmp/oba-mcp-go-cache go vet ./...
GOCACHE=/tmp/oba-mcp-go-cache go build ./...Tests should use the named DTO and MCP response types. Cover successful, empty, malformed, and upstream-error responses where applicable. Add a task-level evaluation when a change affects tool selection, descriptions, or arguments.
- Is the tool’s purpose clear and distinct?
- Are all input and output contracts explicitly typed?
- Is the LLM response small, deterministic, and free of raw backend data?
- Are sensitive values excluded from output and logs?
- Do tests, vet, and build pass?