Documentation

tileserver-rs is the first Rust tile server with native Model Context Protocol (MCP) support. Any deployment can expose its tile sources, styles, renderer, and PostGIS/STAC backends as MCP tools — turning the tile server into an LLM-addressable geospatial endpoint.

What you get

TierToolPurpose
Atileserver_list_sourcesList every configured tile source
Atileserver_get_source_tilejsonFull TileJSON 3.0 metadata for one source
Atileserver_list_stylesList every configured map style
Atileserver_get_styleFull MapLibre style JSON
Atileserver_get_tile_metadataBounds, zoom range, vector-layer schemas
Atileserver_get_server_infoVersion, source/style counts, renderer state
Btileserver_render_static_mapRender a static WebP/PNG/JPEG map (≤ 2048×2048, ≤ 1.5 MB)
Btileserver_get_tileFetch raw vector tile bytes at z/x/y
Btileserver_query_features_at_pointFeatures intersecting a coordinate (PostGIS)
Btileserver_query_features_cql2CQL2 filter against a PostGIS table
Btileserver_search_stac_itemsSearch a configured STAC catalog by bbox/datetime
Dtileserver://styles/{id}Resource template — style JSON
Dtileserver://data/{id}.jsonResource template — TileJSON metadata

Transports

TransportUse caseAuth
stdioLocal — Claude Desktop, Cursor, Copilot CLI, Claude CodeTrusted (inherits user perms)
Streamable HTTPRemote — nested at /mcp inside the main HTTP serverStatic Bearer via [mcp].auth_token or OAuth 2.0 DCR via [mcp.oauth] (mutually exclusive)
Info

claude.ai Custom Connectors require OAuth 2.0 — they do not accept static bearer tokens. Enable [mcp.oauth] and point claude.ai at https://your-server/.well-known/oauth-authorization-server. See the OAuth 2.0 for claude.ai section below.

Quick Start

1. Build with the mcp feature

The MCP server is opt-in to keep default builds slim:

cargo install --git https://github.com/vinayakkulkarni/tileserver-rs --features mcp

If you build from source:

cargo build --release --features mcp

2. Add an [mcp] block to your config

[server]
host = "127.0.0.1"
port = 8080

# Embedded MCP at /mcp (Streamable HTTP). Stdio is always available via
# the `mcp-stdio` subcommand and ignores this block.
[mcp]
enabled = true

# Pick ONE auth mode (mutually exclusive):
# 1. Static bearer — simplest, works with Cursor / Claude Code / Copilot CLI:
# auth_token = "${MCP_AUTH_TOKEN}"

# 2. OAuth 2.0 — required by claude.ai Custom Connectors (see section below):
# [mcp.oauth]
# enabled = true
# issuer_url = "https://tiles.example.com"
# signing_key_path = "/etc/tileserver/mcp-jwt-key.pem"

# Strict CORS for the /mcp endpoint (defaults to ["*"] — lock down in prod):
# cors_origins = ["https://claude.ai", "https://cursor.sh"]

[[sources]]
id = "openmaptiles"
type = "pmtiles"
path = "/data/tiles/openmaptiles.pmtiles"

[[styles]]
id = "osm-bright"
path = "/data/styles/osm-bright/style.json"

A complete example lives at data/configs/mcp.toml.

3a. Wire into Claude Desktop (stdio)

Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) — see the Claude Desktop docs for Windows/Linux paths:

{
  "mcpServers": {
    "tileserver-rs": {
      "command": "/usr/local/bin/tileserver-rs",
      "args": ["mcp-stdio", "--config", "/path/to/config.toml"]
    }
  }
}

Restart Claude Desktop. Ask: "What tile sources are loaded on my server?"

3b. Wire into Cursor / Copilot CLI / Claude Code (stdio)

The same mcp-stdio subcommand works for any stdio-speaking client. Add to your client's MCP config:

{
  "tileserver-rs": {
    "command": "tileserver-rs",
    "args": ["mcp-stdio"]
  }
}

3c. Use over HTTP (Cursor / Claude Code remote)

Start tileserver-rs normally with [mcp].enabled = true:

tileserver-rs --config config.toml

Point your MCP client at http://localhost:8080/mcp. If auth_token is set in config, add an Authorization: Bearer <token> header.

3d. OAuth 2.0 for claude.ai Custom Connectors

claude.ai's "Custom Connectors" feature requires OAuth 2.0 Dynamic Client Registration (RFC 7591). Static bearer tokens are not accepted.

1. Generate a signing key:

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out /etc/tileserver/mcp-jwt-key.pem
chmod 600 /etc/tileserver/mcp-jwt-key.pem

2. Enable OAuth in your config (mutually exclusive with auth_token):

[mcp]
enabled = true

[mcp.oauth]
enabled = true
issuer_url = "https://tiles.example.com"   # must match your public URL
signing_key_path = "/etc/tileserver/mcp-jwt-key.pem"
token_ttl_secs = 3600                       # clamped at 24h

3. Add the connector in claude.ai:

In claude.ai → Settings → Custom Connectors → Add → paste:

https://tiles.example.com

claude.ai discovers the OAuth flow via /.well-known/oauth-authorization-server, dynamically registers itself via /register, walks the consent screen at /authorize, exchanges the code at /token, and finally talks MCP at /mcp with the issued JWT bearer.

Endpoints exposed when [mcp.oauth].enabled = true:

EndpointMethodPurpose
/.well-known/oauth-authorization-serverGETRFC 8414 discovery
/.well-known/oauth-protected-resourceGETRFC 9728 resource metadata
/registerPOSTRFC 7591 Dynamic Client Registration
/authorizeGETConsent screen (askama-rendered)
/approvePOSTApproval → redirects with auth code
/tokenPOSTAuthorization code & refresh token grants

Security defaults:

  • PKCE S256 is mandatory; plain is rejected per the MCP spec.
  • JWT access tokens are RS256-signed with the configured key.
  • Refresh tokens rotate on every /token call.
  • Token TTL is clamped at 86400s (24h) regardless of config.
  • Tokens are stored in-memory — restarting the server invalidates all active tokens. Plan deployments accordingly.
Warning

The in-memory token store is intentional for v1 simplicity. For zero-downtime deployments or long-lived sessions, run tileserver-rs behind a sticky-session reverse proxy and refresh tokens reasonably often (the default 1h TTL plus refresh rotation handles most cases).

Example prompts

Once wired in, try:

  • "List all tile sources on my server."
  • "Render a static map of Paris at zoom 12, 800x600 pixels."
  • "What vector layers are in the openmaptiles source?"
  • "Search the sentinel-2-l2a STAC collection for items over San Francisco from October 2024."
  • "Use CQL2 to find all features in the buildings table where height > 50 within bbox [-74, 40.7, -73.9, 40.8]."

Architecture notes

  • Same-port nest: the Streamable HTTP service mounts at /mcp inside the existing axum router, not on a separate TcpListener. One bind, one reverse-proxy config.
  • Renderer-aware: tileserver_render_static_map returns isError: true with a descriptive message when the raster feature is disabled instead of disappearing from the tool list.
  • Image size discipline: every static render goes through WebP@Q75 compression, capped at 2048×2048 dimensions and 1.5 MB bytes. The 5 MB hard limit on the Claude side can corrupt sessions irrecoverably (Claude Code #2939) — better to refuse oversized requests than to ship them.
  • Error contract: backend failures return CallToolResult { isError: true }, never JSON-RPC errors. JSON-RPC errors are reserved for protocol-level problems (parse failures, unknown methods).
  • Feature-gated cascade: CQL2 tools require postgres; STAC search requires stac. When those features are off, the tools either don't appear in the tool list or return a clear "not available" error.

Configuration reference

KeyTypeDefaultDescription
[mcp].enabledboolfalseNest the MCP HTTP service at /mcp. Stdio mode ignores this.
[mcp].auth_tokenstring?unsetStatic bearer token for HTTP. Supports ${VAR} env expansion. Mutually exclusive with [mcp.oauth].enabled.
[mcp].cors_originsVec<string>["*"]Allowed CORS origins for /mcp. Lock down in production.
[mcp.oauth].enabledboolfalseEnable OAuth 2.0 DCR. Required for claude.ai Custom Connectors.
[mcp.oauth].issuer_urlstringunsetPublic URL the server is reachable at. Required when oauth enabled.
[mcp.oauth].signing_key_pathpathunsetRSA PKCS#8 private key path for RS256 JWT signing. Required when oauth enabled.
[mcp.oauth].token_ttl_secsu643600Access token TTL in seconds. Clamped to 86400 (24h) max.

The [mcp] block is only honored when tileserver-rs is built with --features mcp. Without that feature, the block is ignored and no MCP code is compiled in.

Prompt templates

When connected, the server advertises four user-role prompt templates that LLM clients can surface as quick actions:

PromptArgsPurpose
describe_stylestyle_idAsk the assistant to describe a style's design and use case.
suggest_cql2_filtertable_id, intentGet a suggested CQL2 expression for a given intent.
render_location_previewlocation, zoom?Render a static map preview of a place.
explain_tile_metadatasource_idWalk through a source's layers, zoom range, and contents.

What's deferred

These are planned for follow-up releases:

  • CIMD discovery (oauth_cimd — Claude-Initiated Metadata Discovery, the high-traffic alternative to DCR).
  • Persistent token store — current OAuth implementation uses an in-memory Arc<RwLock<HashMap>> token store. Server restart invalidates all tokens. A future release will add an sled or SQLite backend.
  • Admin tools (reload_config, invalidate_cache) — gated on a separate mcp-admin sub-feature to keep the security surface small.
  • Per-tool rate limiting for render_static_map and get_tile.

References