Install
$ agentstack add mcp-markuman-sms ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
🗺️ OSM - self host the entire planet 🌎 in ~30 minutes 🚀
simple mbtiles server
SETUP - TL;DR
mkdir osm
wget --continue -O osm/planet.mbtiles https://nx93856.your-storageshare.de/public.php/dav/files/apP4zEacCwqPfWd/planet.mbtiles
wget --continue -O osm/contours.mbtiles https://nx93856.your-storageshare.de/public.php/dav/files/apP4zEacCwqPfWd/contours_90m-Z11-Z13_30m-Z14.mbtiles
podman run -ti --rm -p 9000:9000 --name sms -v "$(pwd)/osm/:/data/" registry.gitlab.com/markuman/sms:latest
firefox http://localhost:9000
requirements:
podman(ordocker)- 100 GB storage is required (1 core and 512MB memory are sufficient)
- optional 315 GB storage is required for contour lines
notes:
- "~30 minutes" depends on your bandwidth ...and the hidrive performance of ionos.
credits
- https://github.com/onthegomap/planetiler is used to generate the planet.mbtiles file
- https://github.com/uktrade/mbtiles-s3-server is the origin code-base of my
smsproject
Usage
nextcloud GpxPod
- Deploy the container/service behind a webproxy (caddy, nginx, traefik,...you name it.) to get a valid SSL certificate.
- Goto GpxPod Settings -> Tile Servers
- Type: Vector
- Server address:
https:///v1/styles/osm-bright-gl-style@1.0.0/style.json?fonts=fonts-gl@1.0.0&tiles=mytiles@1.0.0
URL Parameter
You can add coordinates and Zoomlevel.
https://maps.osuv.de/?lat=48.1374&lng=11.5752&zoom=9
Reverse Geolocation / Photon Integration
- sms supports photon reverse geocoding server.
Just set -e PHOTONSERVER="https://photon.osuv.de" for your sms Container.
This also enables the geocode and reverse_geocode MCP tools; without it they are not registered at all.
Two consumers, possibly two URLs
PHOTONSERVER is substituted into index.html at startup, so the browser talks to Photon directly with that URL — it has to be publicly reachable.
The MCP tools, on the other hand, call Photon from inside the container. If both run in podman/docker, the public hostname often resolves to a LAN address the container network cannot route to (split-horizon DNS), and you get Connection refused. Changing PHOTONSERVER is not the fix: that would break the frontend.
Use PHOTONSERVER_INTERNAL for the server-side path instead:
podman network create osm
podman run -d --rm --network osm --name photon \
-e UPDATE_STRATEGY=DISABLED \
-p 8888:2322 \
-v /home/m/osm/photon/:/photon/data \
docker.io/rtuszik/photon-docker:2.1.1
podman run -ti --rm --network osm --name sms \
-p 9000:9000 \
-e PHOTONSERVER="https://photon.osuv.de" \
-e PHOTONSERVER_INTERNAL="http://photon:2322" \
-v /home/m/osm/sms/:/data/ \
localhost/sms:dev
If PHOTONSERVER_INTERNAL is unset, server-side calls fall back to PHOTONSERVER, which is the right thing when both are reachable from everywhere (single host, no container network in between).
Configuration
Tilesets are configured through numbered environment variable groups (MBTILES__1__*, MBTILES__2__*, …):
| Variable | Required | Description | |---|---|---| | PORT | yes | Listen port | | MBTILES__n__URL | yes | Path to the .mbtiles file | | MBTILES__n__IDENTIFIER | yes | Tileset name used in URLs | | MBTILES__n__VERSION | yes | Tileset version used in URLs | | MBTILES__n__MIN_ZOOM | yes | Minimum zoom level | | MBTILES__n__MAX_ZOOM | yes | Maximum zoom level | | HTTP_ACCESS_CONTROL_ALLOW_ORIGIN | no | CORS header value | | PHOTONSERVER | no | Public Photon base URL; enables geocoding and is embedded into the map UI | | PHOTONSERVER_INTERNAL | no | Photon URL used for server-side calls (MCP tools) when the public one is not reachable from inside the container | | TILE_CACHE_SIZE | no | Decoded road tiles kept in memory (default 2000; contour and POI caches get a quarter of that each) | | ROUTE_MAX_TILES | no | Corridor tile limit per segment (default 1200) | | ROUTE_MAX_CROW_KM | no | Straight-line limit per segment in km (default 50) | | GPX_DIR | no | Where generated GPX files are stored (default $TMPDIR/sms-gpx) | | GPX_MAX_FILES | no | Keep at most this many GPX files (default 200) | | GPX_TTL_SECONDS | no | Delete GPX files older than this (default 86400) |
HELP WANTED
- Improve Style
- special for Nextcloud GxpPod
- provide more style? remove some?
planet.mbtiles
My provided planet.mbtiles is generated by using https://github.com/onthegomap/planetiler I just followed this tutorial: https://github.com/onthegomap/planetiler/blob/main/PLANET.md
contours.mbtiles
Contours will be displayed if a file called contours.mbtiles is places next to planet.mbtiles. There are two different contours files provided. 90m resolution in Zoom Level 11 to 14 and 90m resolution in Zoom Level 11 to 13 with 30m resolution in Zoom Level 14.
> GLO-30: "produced using Copernicus WorldDEM-30 © DLR e.V. 2010-2014 and © Airbus Defence and Space GmbH 2014-2018 provided under COPERNICUS by the European Union and ESA; all rights reserved" > GLO-90: "produced using Copernicus WorldDEM-90 © DLR e.V. 2010-2014 and © Airbus Defence and Space GmbH 2014-2018 provided under COPERNICUS by the European Union and ESA; all rights reserved"
Core API
GET /
Serves the map UI frontend (index.html) with optional URL parameters for coordinates and zoom level.
URL Parameters:
lat— Latitude coordinatelng— Longitude coordinatezoom— Zoom level
Example: https://maps.osuv.de/?lat=48.1374&lng=11.5752&zoom=9
GET /v1/capabilities
Returns JSON capabilities indicating available optional features.
Response:
{
"contours": true,
"routing": true,
"mcp": true,
"gpx": true,
"geocoding": false,
"mcp_tools": ["search_poi", "plan_route", "export_gpx"],
"difficulty_data": {
"sac_scale": true, "via_ferrata_scale": true,
"trail_visibility": true, "smoothness": true
},
"routing_limits": { "max_tiles": 1200, "max_crow_km": 50.0, "zoom": 14 },
"tile_cache": {
"roads": { "entries": 812, "max_entries": 2000, "hits": 4210, "misses": 812 },
"contours": { "entries": 240, "max_entries": 500, "hits": 190, "misses": 240 },
"poi": { "entries": 0, "max_entries": 500, "hits": 0, "misses": 0 }
}
}
contours— whethercontours.mbtileswas detected and loadedrouting— alwaystrue; indicates the/v1/route/endpoint is availablemcp— the MCP endpoint atPOST /mcpis availablegpx— GPX export is availablegeocoding— whetherPHOTONSERVERis configured (enables thegeocode
and reverse_geocode MCP tools)
mcp_tools— names of the registered MCP toolsdifficulty_data— which hiking attributes the loaded tiles actually carry.
All false on stock OpenMapTiles tiles (see Hiking attributes below), and in that case a missing warning means missing data, not easy terrain. The flags flip to true as soon as a tile containing them has been decoded.
sac_scale_values— accepted values formax_sac_scale, easiest firsttile_cache— live hit/miss counters of the decoded-tile caches
GET /v1/poi/{identifier}@{version}?lat={lat}&lon={lon}&category={category}&radius={radius}
Search for Points of Interest (POI) within a radius and return GeoJSON features.
Path Parameters:
identifier— Tileset identifierversion— Tileset version
Query Parameters:
lat— Latitude coordinate (required)lon— Longitude coordinate (required)category— POI category (required):supermarket,pharmacy,hospital,
fuel, charging_station, alpine_hut, camp_site, shelter, drinking_water, cave, viewpoint, or emergency
drinking_water, cave, viewpoint and emergency need tiles built with the extended profile linked above — stock OpenMapTiles does not put springs, wells or cave entrances into the poi layer at all. Note that drinking_water includes springs and cattle troughs: that is a location, not a potability guarantee.
Note that alpine_hut covers only real mountain huts (alpine_hut, wilderness_hut, basic_hut). OpenMapTiles files bus stop shelters and public air-raid shelters under subclass=shelter, which around Garmisch is 113 of 122 hits and mostly unnamed — those live in the separate shelter category so they cannot bury the actual huts.
radius— Search radius in km, default 15, max 50 (optional)
Response: GeoJSON FeatureCollection of POIs, each with a properties.distance_km. Sorted by distance, but named POIs come first within the same ~500 m band — an unnamed hut slightly closer is less useful than a named one you can look up.
GET /v1/tiles/{identifier}@{version}/{z}/{x}/{y}.mvt
Fetch a tile in Mapbox Vector Tile (MVT) format.
Path Parameters:
identifier— Tileset identifier (e.g., fromMBTILES__1__IDENTIFIER)version— Tileset version (e.g., fromMBTILES__1__VERSION)z— Zoom levelx— Tile column coordinatey— Tile row coordinate (converts from Web Mercator to TMS)
Special: contours@1.0.0 is auto-loaded if contours.mbtiles exists.
Gzip Negotiation: Returns gzip-compressed tiles if client sends Accept-Encoding: gzip.
Response: application/vnd.mapbox-vector-tile (MVT format)
GET /v1/styles/{identifier}@{version}/style.json
Returns a MapLibre GL style JSON with injected tile, font, and sprite URLs.
Path Parameters:
identifier— Style identifier (e.g.,osm-bright-gl-style)version— Style version (e.g.,1.0.0)
Query Parameters:
tiles— Tile source in format{tile_id}@{tile_version}(required)fonts— Font source in format{font_id}@{font_version}(required)
Available Styles:
dark-matter-gl-style@1.0.0fiord-color-gl-style@1.0.0maptiler-3d-gl-style@1.0.0maptiler-terrain-gl-style@1.0.0maptiler-basic-gl-style@1.0.0maptiler-toner-gl-style@1.0.0osm-bright-gl-style@1.0.0positron-gl-style@1.0.0osuv-style@1.0.0
Response: application/json with complete style JSON including:
- Tile source URL:
/v1/tiles/{tiles}@{version}/{z}/{x}/{y}.mvt - Glyph URL:
/v1/fonts/{fonts}@{version}/{fontstack}/{range}.pbf - Sprite URL:
/v1/styles/{identifier}@{version}/sprite - Contour layers (if contours.mbtiles detected):
contour-lineandcontour-label(visible when toggled in frontend)
GET /v1/styles/{identifier}@{version}/sprite.json
Returns the sprite JSON index file.
Path Parameters:
identifier— Style identifierversion— Style version
Response: application/json with sprite image references and bounds
GET /v1/styles/{identifier}@{version}/sprite@2x.json
Returns the high-resolution (2x) sprite JSON index file.
Path Parameters:
identifier— Style identifierversion— Style version
Response: application/json with high-res sprite references
GET /v1/styles/{identifier}@{version}/sprite.png
Returns the sprite image (1x resolution).
Path Parameters:
identifier— Style identifierversion— Style version
Response: image/png
GET /v1/styles/{identifier}@{version}/sprite@2x.png
Returns the sprite image (2x resolution for retina displays).
Path Parameters:
identifier— Style identifierversion— Style version
Response: image/png
GET /v1/fonts/{identifier}@{version}/{stack}/{range}.pbf
Returns merged glyph data in Protocol Buffer format for font rendering.
Path Parameters:
identifier— Font identifier (fonts-gl)version— Font version (1.0.0)stack— Comma-separated font stack (max 4 fonts, e.g.,Noto Sans Regular,Noto Sans Bold)range— Glyph range (e.g.,0-255)
Response: application/vnd.google.protobuf
- Gzip-compressed if client sends
Accept-Encoding: gzip - Merges glyphs from all fonts in stack, deduplicating by glyph ID
GET /v1/route/{identifier}@{version}?from={lat},{lon}&to={lat},{lon}&profile={profile}
Compute a walking or cycling route between two coordinates using the vector tile road network. No external routing engine required — routing is performed entirely server-side from the MBTiles data.
Path Parameters:
identifier— Tileset identifier (same as used for/v1/tiles/)version— Tileset version
Query Parameters:
from— Start point aslat,lon(required)to— End point aslat,lon(required)profile— Routing profile:foot(default) orbikebuffer_km— Corridor half-width around the straight line (optional).
Defaults to 10 % of the segment length, at least ~3.3 km. Raise it when a detour around a lake or a closed area is needed.
elevation—trueto addascent_m/descent_mfromcontours.mbtiles
and switch the duration estimate to DIN 33466 (optional)
max_sac_scale— hardest SAC grade allowed:hiking(T1),
mountain_hiking (T2), demanding_mountain_hiking (T3), alpine_hiking (T4), demanding_alpine_hiking (T5), difficult_alpine_hiking (T6). Ways tagged above the limit are excluded from routing. Requires tiles with hiking attributes (optional)
allow_via_ferrata—falseexcludes ways tagged as via ferrata or with
fixed ladders (optional, default true)
prefer_routes—trueprefers ways carrying a marked hiking route
(iwn/nwn/rwn/lwn), or a cycle route for profile=bike (optional)
follow_route— follow one named route, e.g.Malerwegor the refE3
(optional)
Note these are independent axes: a way can be T6 scrambling without any ferrata tag, and a cabled route can be tagged T2. On a real Zugspitze test, excluding ferratas alone still produced a T6 route — for a safe tour set both. The response says so via terrain_warnings when only one is used.
Example:
GET /v1/route/mytiles@1.0.0?from=48.137,11.575&to=48.155,11.602&profile=foot
Response: GeoJSON Feature with LineString geometry
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [[11.575, 48.137], ...]
},
"properties": {
"distance_km": 3.241,
"duration_min": 43.2,
"profile": "foot",
"tiles_loaded": 36,
"buffer_tiles": 2.0,
"nodes": 12847,
"cache_hits": 30,
"cache_misses": 6,
"snap_start_m": 12.4,
"snap_end_m": 31.9
}
}
Profile weights (OpenMapTiles transportation layer class attribute):
| Road class | foot | bike | |---|---|---| | footway, path, pedestrian | preferred (1.0) | not passable | | cycleway | allowed (1.3) | preferred (1.0) | | track | 1.1 | 1.2 | | residential, living_street | 1.2–1.3 | 1.1 | | tertiary | 1.8 | 1.4 | | secondary | 2.5 | 1.6 | | primary | not passable | 2.5 | | motorway, trunk | not passable | not passable | | steps | 1.2 | not passable |
Duration estimate: 4.5 km/h for foot, 15 km/h for bike. With elevation=true and contours.mbtiles present, foot switches to a DIN 33466 / SAC estimate instead (300 m ascent or 500 m descent per hour, combined as max(horizontal, vertical) + min(horizontal, vertical) / 2).
Hiking attributes: sac_scale, via ferratas, surface
OpenMapTiles is a rendering schema. Its transportation layer whitelists the attributes it keeps, and sac_scale, trail_visibility and via_ferrata_scale are not on that list — they exist in the OSM source data but are dropped during tile generation. The practical consequence is severe: a router cannot tell a T1 stroll from a T5 scramble, and via ferratas appear as ordinary paths. A route from the Zugspitze to the Alpspitze happily runs over "Stopselzieher" and "Höllentalsteig", both cabled climbing routes with ladders and a glacier crossing.
sms therefore evaluates these attributes when they are present:
| Attribute | Used for | |---|---| | `sa
…
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: markuman
- Source: markuman/sms
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.