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 MCP2024-11-05protocol.mcp.NewTool(name, opts...)— declarative tool schema with description, parameters, examples.server.ServeStdio(s)— blocks reading stdin, dispatchingtools/callrequests 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:
- Type-asserts arguments out of
request.Params.Arguments. - Validates and applies defaults.
- Calls into Layer 3 (spatial primitives).
- 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
numWorkersgoroutines (default 8 — bump toruntime.NumCPU()for very large inputs) drains a channel of point indices, computing each point'seps-neighbourhood in parallel. - The DBSCAN expansion itself runs single-threaded — it mutates the
visitedarray and the working cluster list, and the cost is dominated by the (already parallel) neighbour search. - Channels are bounded by
nto 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.