AGENTS.md 9.0 KB

AGENTS.md

Guidance for coding agents working on this repository.

What this project is

A Go implementation of a Model Context Protocol (MCP) server for WikiJS. It is a rewrite of the TypeScript package @cahaseler/wikijs-mcp. The public tool surface is compatible with that server; no Node.js, npm, or GraphQL codegen is involved.

The server exposes two transports:

  • stdio (default): the client launches the binary and speaks JSON-RPC over stdin/stdout.
  • streamable HTTP: --transport http, serving /mcp and /healthz.

Commands

make build       # bin/wikijs-mcp, version injected from git describe
make test        # go test ./...
make test-race   # go test -race ./...
make vet         # go vet ./...
make fmt         # gofmt -w .
make check       # vet + race tests
make run         # stdio transport (needs WIKIJS_URL/WIKIJS_API_KEY)
make run-http    # HTTP transport on 127.0.0.1:8080
make docker-up   # local WikiJS on http://localhost:3000

Formatting and imports (gofmt + gopls)

Always format Go files with gofmt. Use gopls on top of it for import tidying, canonical formatting, and diagnostics. Run both on the files you changed:

gofmt -w path/to/file.go           # standard formatting
gopls imports -w path/to/file.go   # add/remove/sort imports
gopls format -w path/to/file.go    # canonical formatting
gopls check path/to/file.go        # type/lint diagnostics

make fmt runs gofmt -w .; CI enforces gofmt -l ..

Before finishing a change, run at minimum:

gofmt -w <changed-go-files>
gopls imports -w <changed-go-files>
gopls format -w <changed-go-files>
gopls check <changed-go-files>
gofmt -l .        # must print nothing
go vet ./...
go test -race ./...
go build ./...

Layout

cmd/wikijs-mcp/main.go        flags, env wiring, logger, signals, transport selection
internal/config/              environment and .env parsing, validation
  config.go                   Config, defaults, validation
  dotenv.go                   optional .env loading
internal/wikijs/              typed WikiJS GraphQL client (stdlib HTTP only)
  types.go                    API and output types
  queries.go                  GraphQL documents as raw string constants
  client.go                   request/response handling, URL enrichment, errors
internal/server/              MCP server construction and tool handlers
  server.go                   mcp.Server, tool metadata, edit gate
  tools.go                    input structs, validation, handlers
internal/transport/           streamable HTTP handler, bearer auth, graceful shutdown

Data flow:

MCP client ── stdio / HTTP ──▶ mcp.Server ──▶ tool handler ──▶ wikijs.Client ──▶ WikiJS /graphql

Invariants

  • stdout is protocol-only. With stdio, stdout carries MCP JSON-RPC. All logging must go to stderr (slog is configured that way). Never use fmt.Print or log.Print for diagnostics.
  • Tool names and argument names are a public API. Keep them stable: search_pages, get_page_by_id, get_page_by_path, get_all_pages, create_page, edit_page; camelCase arguments such as isPublished.
  • Write tools are opt-in. create_page and edit_page are registered only when WIKIJS_ENABLE_EDIT=true.
  • URLs are always absolute. wikijs.Client appends url to pages, list items, and search hits. The locale segment is included only for non-English pages (/de/path, but /path for en).
  • Secrets stay out of flags and logs. WIKIJS_API_KEY and MCP_AUTH_TOKEN come from the environment or the env file. Request bodies and headers are never logged.
  • Environment first, .env optional. --env-file (scanned by envFileFromArgs before flag parsing) wins over ENV_FILE, which wins over ./.env. config.LoadDotEnv()/LoadEnvFile() never override existing process variables. A missing default file is ignored, an explicit one must exist. Document new variables in README.md and env.example.
  • HTTP is fail-closed without auth. config.Validate() rejects an HTTP listen address that is not loopback unless MCP_AUTH_TOKEN is set. Keep the loopback default (127.0.0.1:8080) and require a token for :8080 or 0.0.0.0:8080.
  • Errors are tool errors. Handlers return Go errors; the MCP SDK marks the result with isError and puts the message in the content so the model can react.

Tool handler conventions

Each handler uses the generic mcp.AddTool and a typed input struct:

type exampleInput struct {
    Required string `json:"required" jsonschema:"description for the model"`
    Optional string `json:"optional,omitempty" jsonschema:"optional value"`
}

func (h *handlers) example(ctx context.Context, _ *mcp.CallToolRequest, in exampleInput) (*mcp.CallToolResult, someOutput, error) {
    if err := validate...; err != nil {
        return nil, someOutput{}, err
    }
    return nil, result, nil // SDK fills structuredContent and JSON text content
}

Rules:

  • Fields without omitempty are required and appear in required in the input schema.
  • Use pointers (*int, *bool) for optional values with defaults so "unset" and "zero" remain distinguishable. Apply defaults in the handler.
  • Validate semantic constraints in the handler (positive IDs, locale syntax, non-empty paths). The SDK validates JSON types and required fields.
  • Return nil for *mcp.CallToolResult when the SDK should serialize the output. Use nullResult() only for nullable results: a typed nil pointer would otherwise be coerced to an empty object by the SDK's output schema handling.
  • Register the tool in internal/server/server.go with a description and annotations (ReadOnlyHint for reads).

Adding a tool

  1. Add an input struct and handler in internal/server/tools.go.
  2. If it needs a new WikiJS operation, add the document to internal/wikijs/queries.go, a typed method to client.go, and tests.
  3. Register the tool in server.go (inside the edit gate if it mutates).
  4. Add a handler test in internal/server/tools_test.go and, for HTTP, a transport test if relevant.
  5. Document the tool in README.md.

WikiJS GraphQL notes

  • Endpoint: <WIKIJS_URL>/graphql with Authorization: Bearer <api key>.
  • WIKIJS_URL may already end in /graphql or /; New() normalizes both.
  • Requests are plain JSON (query + variables). Keep optional variables absent rather than sending null/empty strings.
  • GraphQL errors arrive with HTTP 200 in errors[]; APIError aggregates their messages. HTTP failures include the status and a truncated body.
  • Mutations return responseResult { succeeded errorCode slug message }. Successful transport does not imply a successful mutation: check Succeeded.
  • list returns tags as []string; single/singleByPath return tags { tag title }. The types reflect this difference.
  • Timestamps (createdAt, updatedAt) are kept as strings to preserve WikiJS' format and avoid time zone/layout assumptions.
  • Pages may not exist: single and singleByPath are nullable. Return a nil page and let the handler emit null.

Testing strategy

  • internal/wikijs: httptest fake GraphQL server. Assert auth header, variables, normalization, URL building, GraphQL errors, and HTTP errors.
  • internal/server: real MCP client over mcp.NewInMemoryTransports() and a fake WikiJS. Assert tool visibility, structured output, defaults, validation, and isError behavior.
  • internal/transport: httptest for /healthz, bearer auth (401 and 200), and a full streamable-HTTP MCP roundtrip.
  • Prefer table-driven tests where inputs vary; keep fake responses minimal.

Configuration reference

Variable Default Notes
WIKIJS_URL – Required; absolute http(s) URL
WIKIJS_API_KEY – Required
WIKIJS_ENABLE_EDIT false Registers write tools
WIKIJS_TIMEOUT 30s Per-request HTTP timeout
MCP_TRANSPORT stdio stdio or http
MCP_ADDR 127.0.0.1:8080 HTTP listen address
MCP_PATH /mcp Must start with /, must not be /healthz
MCP_STATELESS false No sessions; for load-balanced setups
MCP_AUTH_TOKEN – Bearer token for HTTP; required for non-loopback addresses; /healthz stays open
LOG_LEVEL info debug, info, warn, error
ENV_FILE .env Alternative env file; --env-file overrides it

Flags override the transport-related environment values. Secrets are available only through the environment or the env file.

Dependencies and versioning

  • Direct dependencies:
    • github.com/modelcontextprotocol/go-sdk/mcp for the protocol.
    • github.com/joho/godotenv for correct .env parsing (quotes, comments, export).
  • GraphQL is implemented with the standard library; do not add a GraphQL client or code generator without a strong reason.
  • version is a main variable set with -ldflags "-X main.version=<value>"; make build uses git describe and falls back to dev.