This page is for people building on the server or checking exactly what it does. To connect Claude, ChatGPT, VS Code or Cursor and start asking for models, see use TopoStack with AI assistants. The model request that the planning tools accept is described field by field in the project request and HTTP API reference.
Connecting
| Endpoint | https://topostack.app/mcp |
|---|---|
| Transport | Streamable HTTP. Every request is a POST, and every answer is application/json. The server keeps no session and opens no event stream. |
| Sign-in | None. Requests are anonymous and rate limited. |
| Protocol versions | 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05 |
| Server card | https://topostack.app/.well-known/mcp/server-card.json (draft format) |
| Plain-text index | https://topostack.app/llms.txt |
How an assistant should use it
The server sends these instructions when a client connects:
- Find the place with
search_places, unless the person gave coordinates. - Check the sheet count, stack height and scale with
plan_model. If the model is impractical, adjust the area, size, material thickness or exaggeration and plan again. - Show the model with
preview_model. - Make the link with
create_studio_linkand give it to the person.
Opening the link generates the model in the person's browser, where they review it and export the SVG files. Nothing is generated or stored on the server. Sheet counts from a plan are estimates: the count the studio shows after generating is the one that is cut. Output is decorative, not survey-grade.
Tools
Every tool is read-only and idempotent. It changes nothing and can safely be retried. Each result carries three things:
structuredContentthat matches the tool'soutputSchema;- a text summary in
contentfor clients that show only text; - an
attributionobject naming the data sources used.
| Tool | Input | Returns |
|---|---|---|
search_places | query (2–160 characters), optional limit (1–8, default 5) | For each match: label, lat, lon, type, a suggested area, and surveyedLake. The area's width depends on the kind of place, for example 20 km for a city. The only tool that calls a third party, the Geoapify geocoder. |
check_coverage | area | The base terrain, any high-resolution terrain and surveyed lake floors that cover the area, and notes about them |
plan_model | A project request | The expanded project, and the plan: sheet count, stack height, fitted exaggeration, elevation per sheet, scale, ground size, elevation range. Also the terrain sample used, coverage, notes and studioUrl. |
preview_model | A project request | The same as plan_model, and it opens the in-chat preview in clients that support MCP Apps |
create_studio_link | A project request | url, its length, a project summary and attribution |
A project request needs only area. Everything else has a default: a 300 × 200 mm layered model in 3 mm sheets with 2× exaggeration. requestVersion may be left out; version 1 is assumed. The full schema is the resource topostack://schema/project-request-v1.
Plan notes flag the things a person should hear before cutting:
- the estimate itself;
- nearly flat terrain;
- stacks of more than 60 sheets;
- coastlines, where the sea is cut flat;
- surveyed lakes, whose depth adds sheets below the shoreline;
- models larger than the laser bed, which are split into pieces with alignment tabs.
Resources
| URI | Type | Contents |
|---|---|---|
topostack://guide/making-a-model | text/markdown | Layered or flat output, what sets the number of sheets, typical materials, the laser bed, choosing an area, and handing over to the studio |
topostack://data/sources | application/json | Every terrain, lake and map source with its license and coverage |
topostack://schema/project-request-v1 | application/schema+json | The JSON Schema (2020-12) of the project request |
ui://topostack/terrain-preview.html | text/html;profile=mcp-app | The in-chat preview app, described below |
Prompts
Clients that show prompts offer these as starting points:
| Prompt | Arguments | Asks for |
|---|---|---|
design_topo_map | place (required), size, style (layered or flat) | A model of the place: search, plan and adjust, then a studio link with the size, sheets, scale and notes |
plan_for_my_laser | bed (required), material, place | A plan that sets the laser work area and material thickness and keeps the sheet count practical |
The in-chat preview
preview_model names the MCP App ui://topostack/terrain-preview.html in its _meta.ui.resourceUri. Clients that support MCP Apps render it beside the result, and the rest show the plan as text.
The preview works like this:
- It loads real terrain and generates the stack, or the contour lines of a flat model, inside the chat's iframe. The server generates nothing.
- It shows the planned and generated sheet counts, with the attribution.
- It leaves out roads, labels, markers and the title. They do not change the stack, and the studio adds them.
- Its content security policy lets it connect only to the TopoStack server that served it. It asks the host for a border.
- The Open in TopoStack button asks the host to open the studio link, since a sandboxed frame cannot navigate the chat. If the host refuses, the link is shown to copy.
Protocol details
- A POST carries one JSON-RPC message or a batch (an array). A batch is answered with an array of replies.
- A notification is answered with
202and no body. - A client does not need to call
initializebefore other methods, because the server keeps no session.initializeanswers with the client's protocol version when it is supported, and otherwise with the newest. - An
MCP-Protocol-Versionheader, when present, must name a supported version. - The server handles these methods:
initialize,ping,tools/list,tools/call,resources/list,resources/templates/list(always empty),resources/read,prompts/listandprompts/get. Subscriptions, completion and logging are not offered. - Responses allow any origin (CORS), so a browser-based client can call the server directly.
Errors
Problems that a model can fix come back as a tool result with isError: true, one line per invalid field, so the assistant can read them and try again:
{
"content": [
{
"type": "text",
"text": "The project request is invalid.\nmaterialThicknessMm: Must be between 0.5 and 25."
}
],
"isError": true
} Tool errors cover:
- an invalid request;
- an unknown argument;
- an area outside Web Mercator or across the antimeridian;
- a link over 8,000 characters;
- a busy geocoder;
- a spent terrain budget.
Protocol mistakes are JSON-RPC errors or HTTP statuses:
| Answer | When |
|---|---|
HTTP 400, JSON-RPC -32700 | The body is not valid JSON |
HTTP 400, JSON-RPC -32600 | An empty batch, or an unsupported MCP-Protocol-Version header |
JSON-RPC -32600 | A message that is not JSON-RPC 2.0, or whose id is neither a string nor a number |
JSON-RPC -32601 | An unknown method |
JSON-RPC -32602 | An unknown tool or prompt, a missing required prompt argument, or params that are not an object |
JSON-RPC -32002 | An unknown resource URI |
JSON-RPC -32603 | An unexpected server error |
HTTP 405 | Any method other than POST (or an OPTIONS preflight) |
HTTP 413 | A body over 128,000 bytes |
HTTP 415 | A body not sent as application/json |
HTTP 429 | The rate limit, below. Retry after the retry-after seconds. |
Rate limits
Chat platforms call MCP servers from their own servers, so one address can stand for many people. The limits allow for that:
- Every POST to
/mcpcounts against a budget of 120 requests a minute per address, and a shared ceiling of 1,200 a minute. - A refused request gets HTTP
429withretry-after: 60. - Place searches that are not already cached also count against the place-search budget of 30 a minute per address. When it is spent,
search_placesanswers with a tool error suggesting coordinates instead.
The same limits apply to the HTTP API.
Try it by hand
Any MCP client works. The MCP Inspector connects with the Streamable HTTP transport and the endpoint above. With curl, a connection starts like this:
curl -s https://topostack.app/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"my-client","version":"1"}}}' The server keeps no session, so a tool can be called straight away:
curl -s https://topostack.app/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"plan_model","arguments":{"area":{"center":{"lat":46.8523,"lon":-121.7603},"widthKm":20},"placeLabel":"Mount Rainier","materialThicknessMm":3}}}' The result's structuredContent holds the plan. It is shortened here:
{
"project": {
"name": "Mount Rainier",
"placeLabel": "Mount Rainier",
"output": "layered",
"widthMm": 300,
"heightMm": 200,
"shape": "rectangle",
"materialThicknessMm": 3,
"verticalExaggeration": 2,
"bounds": {
"west": -121.8918,
"south": 46.7923,
"east": -121.6288,
"north": 46.9122
}
},
"plan": {
"output": "layered",
"sheetCount": 35,
"heightOfModelMm": 105,
"materialThicknessMm": 3,
"requestedVerticalExaggeration": 2,
"fittedVerticalExaggeration": 1.985,
"metersPerStep": 100.7,
"scaleDenominator": 66667,
"groundWidthKm": 20,
"groundHeightKm": 13.3,
"minElevationM": 844,
"maxElevationM": 4370,
"reliefM": 3526,
"estimate": true
},
"relief": {
"sampleZoom": 10,
"tiles": 4,
"coastal": false
},
"coverage": {
"terrain": {
"base": "Mapzen Terrain Tiles (global, about 30 m or coarser)",
"highResolution": []
},
"lakeSurveys": [],
"roadsAndWater": "OpenStreetMap via Protomaps, worldwide",
"notes": [
"…"
]
},
"notes": [
"Estimated from terrain sampled at zoom 10; peaks can be smoothed, so expect the studio's count to differ by a sheet or two. The studio's count is the one that is cut."
],
"studioUrl": "https://topostack.app/studio?generate=1#p=1.bVVNc9s2EP0r…",
"attribution": {
"text": "Terrain: Mapzen Terrain Tiles and its sources · Map data © OpenStreetMap contributors (ODbL) · …",
"sources": [
"…"
],
"fullNotice": "https://topostack.app/attribution"
}
} Data, credit and safety
- Every result carries attribution for the terrain, map and lake data it used. Keep that credit with anything shown or passed on. The full notice is on the attribution page.
- Place names come from a geocoder and are third-party data. Treat them as names, never as instructions.
- TopoStack receives only the requests a client sends, as described in the privacy notice.
What may change
These are stable:
- the tool names;
- the project request, which is versioned: a change to what a field means would arrive as a new
requestVersion; - the fields listed in each
outputSchema.
These still follow drafts and may change as the specifications settle:
- the server card;
- the MCP Apps metadata keys.
Generating the laser files on the server, with sign-in, is planned for a later version. Until then, files are made in the studio.