Skip to content

Project structure

kartoza-mcp/
├── main.go               # MCP server, tool registry, all current handlers
├── main_test.go          # Unit + integration tests
├── go.mod / go.sum       # Go module graph
├── flake.nix             # Reproducible dev shell + build/run/test/clean apps
├── flake.lock            # Pinned flake inputs
├── request.json          # Sample MCP tools/call payload
├── test.json             # Sample GeoJSON FeatureCollection
├── README.md
├── SPECIFICATION.md      # Authoritative product spec
├── AGENT.md              # Operating brief for AI assistants
├── PACKAGES.md           # Annotated dependency list
├── docs/                 # mkdocs site (you're reading it)
│   ├── index.md
│   ├── getting-started/
│   ├── users/
│   ├── developers/
│   ├── devops/
│   ├── reference/
│   ├── about/
│   ├── assets/brand/     # Curated Kartoza brand assets
│   └── stylesheets/
├── .github/workflows/    # Go CI + mkdocs gh-pages publish
└── mkdocs.yml            # Site config

Why this shape?

  • One Go file keeps the cost of "go read the code" near zero. The whole server fits on one screen.
  • docs/ mirrors mkdocs convention so contributors don't have to guess where to put a new page.
  • flake.nix is the source of truth for the toolchain. No Makefile, no Justfile, no shell tarpits.
  • No internal/ — the package is small enough that the Go default scope (lowercase = unexported) does the job.

When to break this up

Add a pkg/<feature>/ directory the moment any of these become true:

  • A handler grows past ~150 lines.
  • A geometry helper is shared by two or more handlers.
  • A handler needs its own goroutine pool / config struct / state.

Until then, resist the urge to over-modularise. Kartoza's house style is DRY but flat.

Things that intentionally don't live here

  • A binary in source control (.gitignore excludes them).
  • The full Kartoza brand pack zip (it's ignored — curated assets are copied into docs/assets/brand/).
  • IDE configs beyond .vscode/ and .exrc.