Skip to content

Architecture

A grand-total-of-three-layers application.

Layer 1 — MCP transport

mark3labs/mcp-go provides:

  • server.NewMCPServer(name, version, opts...) — wires up JSON-RPC over stdio with the MCP 2024-11-05 protocol.
  • mcp.NewTool(name, opts...) — declarative tool schema with description, parameters, examples.
  • server.ServeStdio(s) — blocks reading stdin, dispatching tools/call requests to registered handlers.

Layer 2 — Tool handlers

Each tool is a Go function:

func clusterHandler(
    ctx context.Context,
    request mcp.CallToolRequest,
) (*mcp.CallToolResult, error) { ... }

A handler:

  1. Type-asserts arguments out of request.Params.Arguments.
  2. Validates and applies defaults.
  3. Calls into Layer 3 (spatial primitives).
  4. Marshals a result and returns mcp.NewToolResultText(...).

Layer 3 — Spatial primitives

Concern Library
GeoJSON marshalling github.com/paulmach/orb/geojson
Geodesic distance github.com/paulmach/orb/geo
Spatial index github.com/paulmach/orb/quadtree
Computational geometry GEOS (linked statically)

Sequence

sequenceDiagram
    autonumber
    participant C as MCP Client
    participant M as main()
    participant H as clusterHandler
    participant Q as Quadtree
    participant G as Orb / GEOS
    C->>M: stdin: tools/call cluster_points
    M->>H: dispatch
    H->>G: UnmarshalFeatureCollection
    H->>Q: Add(N points)
    par parallel neighbour search
      H->>Q: InBound(p_1, eps)
      H->>Q: InBound(p_2, eps)
      H->>Q: InBound(p_N, eps)
    end
    H->>H: DBSCAN expand clusters
    H->>G: createCirclePolygon per cluster
    H->>M: ToolResult (FeatureCollection)
    M->>C: stdout

Concurrency model

  • A worker pool of numWorkers goroutines (default 8 — bump to runtime.NumCPU() for very large inputs) drains a channel of point indices, computing each point's eps-neighbourhood in parallel.
  • The DBSCAN expansion itself runs single-threaded — it mutates the visited array and the working cluster list, and the cost is dominated by the (already parallel) neighbour search.
  • Channels are bounded by n to avoid blocking the producer.

Memory model

  • Inputs and outputs are streamed via stdio — no on-disk state, no network sockets.
  • The Quadtree holds light wrappers {Loc orb.Point, Index int}, not the full GeoJSON feature, so a million-point input fits in hundreds of MB rather than gigabytes.

Failure modes

Mode Surfaced as
Malformed JSON error from handler
Missing geojson argument error from handler
geojson parses but has no Point features "No points found in collection" text result
Internal panic propagated by mcp-go as JSON-RPC error

Want to read the code?

Start at main.go — the entire server fits on one page.