Guidance for coding agents working on this repository.
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:
--transport http, serving /mcp and /healthz.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
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 ./...
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
slog is configured that way). Never use fmt.Print
or log.Print for diagnostics.search_pages, get_page_by_id, get_page_by_path, get_all_pages,
create_page, edit_page; camelCase arguments such as isPublished.create_page and edit_page are registered only
when WIKIJS_ENABLE_EDIT=true.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).WIKIJS_API_KEY and MCP_AUTH_TOKEN
come from the environment or the env file. Request bodies and headers are
never logged..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.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.isError and puts the message in the content so the model can
react.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:
omitempty are required and appear in required in the input
schema.*int, *bool) for optional values with defaults so "unset"
and "zero" remain distinguishable. Apply defaults in the handler.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.internal/server/server.go with a description and
annotations (ReadOnlyHint for reads).internal/server/tools.go.internal/wikijs/queries.go, a typed method to client.go, and tests.server.go (inside the edit gate if it mutates).internal/server/tools_test.go and, for HTTP, a
transport test if relevant.README.md.<WIKIJS_URL>/graphql with Authorization: Bearer <api key>.WIKIJS_URL may already end in /graphql or /; New() normalizes both.query + variables). Keep optional variables
absent rather than sending null/empty strings.errors[]; APIError aggregates their
messages. HTTP failures include the status and a truncated body.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.createdAt, updatedAt) are kept as strings to preserve WikiJS'
format and avoid time zone/layout assumptions.single and singleByPath are nullable. Return a nil
page and let the handler emit null.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.| 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.
github.com/modelcontextprotocol/go-sdk/mcp for the protocol.github.com/joho/godotenv for correct .env parsing (quotes, comments,
export).version is a main variable set with
-ldflags "-X main.version=<value>"; make build uses git describe and
falls back to dev.