Skip to main content
Glama

Israel Hiking MCP

An MCP server that puts Israel Hiking Map / Mapeak trail data in reach of an LLM host. Five tools answer the questions a walk starts with: where a place is, what routes are mapped near it, what one of those routes actually looks like, what the map draws beside it, and how to get from one point to another.

search_places         "Haifa"                    → 32.8191, 34.9984, ranked, linked
search_hiking_routes  15 km around it, 4–12 km   → 16 routes, the 10 nearest first
get_route_details     OSM/relation_13207704      → the Haifa Trail as GeoJSON, 903 positions
find_pois_along_route that route, 1 km, Water    → 3 water features, distances in metres
route_between_points  two points on the Carmel   → 6.39 km walking path, both ends snapped

Every answer is grounded: it names its sources and their licences, lists the questions the data cannot settle, and flags the results somebody might act on. That is what the design turns on — an answer about a trail is only worth repeating if it says where it came from and where it stops.

Status: v0.1.0, feature-complete. Five tools built across nine reviewed pull requests, ~4,900 lines of typed Python under strict mypy, 409 offline tests and an opt-in live group. It is a personal-scale project pointed at a volunteer-run service — read Responsible use before pointing anything automated at it.

The map data belongs to Israel Hiking Map and OpenStreetMap, under licences that make everything this server returns non-commercial and share-alike. Read LICENSE-NOTICE.md before using any output.

What it does

  • Reads the map's own sources rather than scraping its pages. Places come from the site's search index. Routes and points of interest are decoded from the vector tiles the map itself draws (global_points, zoom 10–14), which is the only place they exist — there is no query API for them. Route geometry comes from the OpenStreetMap API and from the site's user-shared routes; paths come from its routing engine.

  • Assembles geometry that arrives in pieces. An OSM route is a relation of ways, each drawn in whatever direction its mapper chose and listed in whatever order the relation holds. The server sews them into continuous lines, follows nested relations breadth-first under a request budget, and returns a MultiLineString — saying so — where the mapping genuinely does not join up. A user-shared route of more than 3,000 positions is thinned by Douglas-Peucker at the smallest tolerance that fits, and reports which tolerance that was, how many positions it kept, and that the ends survived.

  • Ranks candidates and lets the host choose. Searches return an ordered list and say what they dropped — upstream's place index is worldwide, so "Haifa" reaches Syria, France and Chile first, and results are ranked into an Israel bounding box with the omissions counted in warnings. Ordering is deterministic: the same inputs give the same routes in the same sequence.

  • Works to a budget and says when it hits one. An area search costs one tile request per tile it covers, growing with the square of the radius, so it runs under a tile budget and refuses with a radius that would have worked rather than returning part of an area as though it were all of it. The zoom it searches at was picked for that budget and then checked against zoom-14 children over four regions to confirm nothing is dropped at the lower zoom.

  • Carries its provenance and its limits into the model's context. Every response ships an attribution object naming both sources, a warnings list, an unknowns list of what the data cannot establish — closure, access, water, waymarking, terrain, time — and a per-result caution on the things people act on. Water above all: a spring on this map is an undated record in a country where most water is seasonal, so the caution rides on the water result itself, and survives a summary that keeps the points and drops the warnings.

  • Fails in a typed, stable way. Ten error codes, split into the transient and the permanent, with upstream bodies, URLs and tracebacks kept out of the message and sent to the log instead. A response in a shape the server does not recognise is reported as upstream_schema_changed rather than read optimistically, and an unresolvable source is refused by name.

  • Touches nothing upstream. Every request is a GET, identified by a descriptive User-Agent, capped in concurrency, timed out, cached in memory and retried at most once after a transient failure.

Related MCP server: MCP Ping-Pong Server

How it is built

  • Python 3.11+ on FastMCP, with httpx for both upstreams, shapely and mercantile for geometry and tile maths, and mapbox-vector-tile for decoding.

  • Pydantic models are the contract. Their field descriptions travel to the client as each tool's output schema, so they are written to be read by a model and not only by a Python caller. A feature is always identified by a {source, identifier} ref, never a bare id.

  • Two upstreams, two clients. mapeak.com and api.openstreetmap.org are different hosts under different usage policies, so they get separate connection pools and caches rather than a shared one that blurs which is being spent.

  • Strict mypy and ruff over source and tests, with local stubs for the few third-party names worth describing.

  • 409 offline tests. Every upstream response is either a fixture in the shape a live one was observed in, or a vector tile built in-test with mapbox-vector-tile's own encoder — so the tile path is exercised against real encoded bytes without committing any map data.

  • An opt-in live group that asserts contract rather than content against the real hosts, catching what a fixture cannot: a renamed field, a moved endpoint, a routing type upstream stopped recognising.

  • CI on Python 3.11, 3.12 and 3.13 across Linux and Windows, with the live group on a workflow of its own.

  • Measured numbers carry their dates. Upstream keeps moving: measurements a day apart disagreed about how many disconnected pieces the Haifa Trail resolves to and what the Israel National Trail's corridor costs in tiles. The acceptance run below is the whole server driven through a real MCP session against the live hosts, with its output.

Install

There is nothing to clone. uv is the only prerequisite — it fetches a suitable Python (3.11+) itself.

Claude Code:

claude mcp add israel-hiking -- uvx --from git+https://github.com/tisheldev/israel-hiking-mcp israel-hiking-mcp

Claude Desktopclaude_desktop_config.json, then restart the app:

{
  "mcpServers": {
    "israel-hiking": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/tisheldev/israel-hiking-mcp",
        "israel-hiking-mcp"
      ]
    }
  }
}

Any other MCP client takes the same three things: the command uvx, the arguments above, and stdio transport. Environment variables from the Configuration table go in an "env" object beside "command".

To hold a known version rather than whatever main holds, append a tag to the URL — git+https://github.com/tisheldev/israel-hiking-mcp@v0.1.0. Without one, every launch resolves the default branch, which is fine for trying it and worth pinning for anything you rely on.

Run uvx --from git+https://github.com/tisheldev/israel-hiking-mcp israel-hiking-mcp --help to check the install without a host attached. Run it with no arguments and it will sit there saying nothing useful, which is correct: it is waiting for a client on stdin. It says so before it waits.

Working on it

From a clone, for changing the code rather than using it:

uv sync
uv run israel-hiking-mcp

The server communicates over stdio and produces no stdout output of its own — stdout is reserved for the JSON-RPC message stream. Logs go to stderr; set IHM_LOG_LEVEL=DEBUG for verbose output.

CONTRIBUTING.md covers the rest: the checks, what the live test group costs, and what a bug report needs.

Tests

uv run pytest

The suite runs fully offline: every upstream response is either a fixture in the shape a live one was observed in, or a vector tile built in-test with mapbox-vector-tile's own encoder. No map data is committed, because none of it is this project's to redistribute.

A second, opt-in group talks to the real hosts and is deselected by default:

uv run pytest -m live

That is the run which catches what a fixture cannot — a renamed field, a moved endpoint, a routing type upstream stopped recognising. It asserts contract rather than content (that a route comes back with an identity, a name, a link and its cautions — not which routes are near Haifa today), and costs about a dozen requests. See tests/test_live.py.

Running a clone from a host

The Install section above is what to hand somebody else. To point a host at a working copy instead — so an edit is picked up on the next restart — give it uv and an absolute path:

claude mcp add israel-hiking-dev -- uv --directory /absolute/path/to/israel-hiking-mcp run israel-hiking-mcp

The path must be absolute; the host does not run in this directory.

Try it with MCP Inspector

npx @modelcontextprotocol/inspector uv run israel-hiking-mcp

Set the working directory to this repository, connect, and open the Tools tab. Calling the five in order walks the whole server:

  1. search_places with query: "Haifa" → Haifa, Israel first, with an ihmUrl that opens on the map site, and a warning saying how many worldwide matches were dropped.

  2. search_hiking_routes with those coordinates, radiusKm: 15, minLengthKm: 4, maxLengthKm: 12 → Haifa Trail sections, Nakeb routes and shared routes, nearest first.

  3. get_route_details with the ref of any of them. {"source": "OSM", "identifier": "relation_13207704"} is the Haifa Trail, assembled from the ways it is mapped as; a Users ref comes back with its author's own title, description and recorded climb; {"source": "OSM", "identifier": "relation_282071"} is the Israel National Trail — 2,480 ways and 41,689 recorded positions, returned as one continuous line thinned to fit; and {"source": "Nakeb", "identifier": "255"} is refused as unsupported_source.

  4. find_pois_along_route with the Haifa Trail ref, bufferMeters: 1000 and categories: ["Water"] → three water features, each with the same caution, under a warning saying not to plan water on any of them. The Israel National Trail's ref shows the tile budget refusing a corridor too long to scan.

  5. route_between_points between two points on the Carmel → a calculated line, with the warning that nobody has walked it.

The acceptance run below is that walk, with its output.

Tools

Tool

Description

search_places

Find places by name (Hebrew or English) and return ranked candidate coordinates, each with a {source, identifier} ref and a link to the map site.

search_hiking_routes

List hiking routes mapped near a point, nearest first, with length, any difficulty rating, and a link to the map site.

get_route_details

Resolve one route ref into its GeoJSON line plus the title, description, length, climb and difficulty its source records. Resolves OSM and user-shared routes.

find_pois_along_route

List the springs, caves, viewpoints and other mapped points within a given distance of a route's line, nearest first, each with its distance in metres.

route_between_points

Calculate a path between two points for walking, cycling or 4x4, using the map site's own routing engine. The one tool here whose answer is computed rather than recorded.

search_places

Argument

Type

Default

Notes

query

string

Place name, 2–100 characters after trimming

israelOnly

boolean

true

Drop matches outside the Israel bounding box

language

he | en

en

Language of the returned names, and of the ihmUrl link

limit

integer

10

1–20

Resolves a name — in Hebrew or English — into ranked candidate coordinates, each with a {source, identifier} ref that the route and POI tools accept and an ihmUrl that opens the place on the map site. It is the entry point for everything else here: the {lat, lng} it returns is the shape search_hiking_routes and route_between_points take.

Upstream's search is a worldwide index — "Haifa" matches places in Syria, the United States, France and Chile before it reaches Haifa, Israel. Results are therefore ranked by whether they fall inside an approximate bounding box around Israel (29.3–33.4 N, 34.2–35.9 E), and by default the rest are dropped; the warnings field always says when that happened. The box is a ranking device, not a border: it is axis-aligned, so it also takes in parts of neighbouring territory.

No match is not an error — the tool returns an empty places list with a warning explaining what to try next. The tool never picks a candidate for you.

search_hiking_routes

Argument

Type

Default

Notes

center

{lat, lng}

Point to search around; search_places returns this shape

radiusKm

number

10

1–40, measured to each route's start marker

minLengthKm

number | null

null

0–1000

maxLengthKm

number | null

null

0–1000

difficulty

Easy | Moderate | Hard | Very Hard | null

null

See below

language

he | en

en

Language of names, descriptions and the ihmUrl link

limit

integer

10

1–20

Returns the hiking routes mapped around a point, nearest first, each with its length, any difficulty its source records, a {source, identifier} ref for get_route_details and find_pois_along_route, and a link to the map site. The routes are read out of the map's vector tiles, which is where they live — the same tiles carry cycling and 4x4 routes, which this tool does not search.

Results are deterministic: the same centre, radius and constraints produce the same routes in the same order, sorted by distance from the search centre and then by {source, identifier}. Distances are great-circle, reported to 10 m, and are not walking distances.

Two upstream quirks the tool mirrors deliberately, and reports in warnings:

  • Most routes carry no difficulty rating. Upstream's own route filter keeps an unrated route whatever difficulty is asked for, and so does this tool — dropping them would empty almost every search. Each such result carries difficulty: null, and a warning says how many were kept that way.

  • A route the map does not name is not returned. The map site hides those features too; the count of skipped markers appears in warnings.

A route with no length in the map data is returned with lengthKm: null, and cannot satisfy a length range.

Nothing in a result establishes that a route is open, marked, passable, permitted, or safe. description is the mapper's own undated note, and is often the only place a closure or hazard is recorded.

get_route_details

Argument

Type

Default

Notes

route

{source, identifier}

The ref object from a search_hiking_routes result

language

he | en

en

Preferred language where the source holds both

Returns the route's line as GeoJSON (LineString, or MultiLineString when the route's parts do not join up), with startPoint, title, description, activity, difficulty, lengthKm, ascentMeters, descentMeters and a link to the map site.

Two sources resolve: OSM and Users. Nakeb, iNature and Wikidata return unsupported_source rather than a guessed URL; the ihmUrl from the search result opens any of them on the map site meanwhile.

Geometry is GeoJSON, so its positions are [longitude, latitude] — the opposite order to every other coordinate this server returns.

source: "OSM"

Identifiers look like relation_282071, way_12345 or node_67890, which is what the search returns. Geometry comes from api.openstreetmap.org directly, as the map site's own frontend does — a second host, under OSM's usage policy, with the same identifying User-Agent and the same caching as every other request here.

A route is a relation of ways, each way drawn in whatever direction its mapper drew it and listed in whatever order the relation holds; ways whose ends coincide are sewn back into continuous lines. Where the mapped ways genuinely do not meet, the result is a MultiLineString and a warning says so — the whole Haifa Trail came back as 24 disconnected pieces on 2026-08-15, which is the mapping and not a failure to join it. A node is refused as invalid_input without asking OSM: a point cannot be a route.

/full returns a relation's members but not the members of relations nested inside it, so a trail split into sections costs one request per section. That recursion is bounded by IHM_MAX_OSM_REQUESTS_PER_TOOL_CALL (16), follows sections breadth-first, visits each relation once however often it is referenced, and past the budget fails with geometry_too_large rather than returning part of a trail as if it were all of it.

Everything reported besides the geometry is an OSM tag, so most of it is absent on most routes: lengthKm comes from distance only, ascentMeters and descentMeters from ascent/descent, difficulty from a difficulty tag that happens to name one of upstream's four grades, and activity from route in the map site's own words (hiking/foot → Hiking, bicycle/mtb → Bicycle, road + scenic=yes → 4x4). The non-standard length tag is deliberately not read — a sampled Haifa Trail section tags length=3 where the map's own computed length is 8.59 km. For length, use the lengthKm of the search result that produced the ref.

source: "Users"

A share stores a route as segments cut at each point its author dropped, each restating the previous segment's last point. Those are joined into one line and the repeated junction positions are dropped. A share holding several routes comes back as a MultiLineString. Positions are reported to six decimals, about 0.11 m.

A route of more than 3,000 positions is thinned by Douglas-Peucker at the smallest tolerance that brings it under that cap — 10 m, then 25, 50, 100 — measured in a local equirectangular frame. geometryDetail reports recordedPointCount, pointCount, simplified and toleranceMeters, and a warning says the same in words. Every kept position is one the recorded line really passed through, and the first and last always survive. A shape still over the cap at 100 m fails with geometry_too_large rather than being truncated.

Every response carries a fixed unknowns list — closure and access status, water, waymarking and terrain, time required. These are properties of the data, not of the particular route: the map records where somebody went, not whether going there is currently a good idea.

find_pois_along_route

Argument

Type

Default

Notes

route

{source, identifier}

The same ref get_route_details takes

bufferMeters

number

500

25–2000, straight-line distance to the route's drawn line

categories

list of category | null

null

Defaults to Water, Natural, Historic, Viewpoint, Camping

language

he | en

en

Language of names, descriptions and the ihmUrl link

limit

integer

20

1–50

Resolves the route through the same adapters as get_route_details, then returns the map's points of interest within bufferMeters of its line, nearest first. Each carries category, subtype, coordinates, distanceFromRouteMeters and a link to the map site.

Water results are the ones people act on, and the ones to be careful with. A spring, cistern, waterhole or pool here is a feature somebody recorded on a map at an unrecorded date. Nothing establishes that it is flowing, reachable, permitted or safe to drink, and most water in this country is seasonal. Every water result carries that in a caution field of its own — not only in warnings, so that a summary keeping the points and dropping the warnings still carries it — and a warning repeats it at the head of the list.

Distances are straight lines from the route's recorded line, unthinned: this tool returns numbers about a route rather than the route itself, so nothing is simplified first. They are not walking distances, and say nothing about whether anything leads there — a spring 80 m off the trail may be 80 m down a cliff.

Categories are upstream's, not this server's. The plan for this tool was to port the map site's osm-tags.service.ts mapping from raw OSM tags to categories; a live zoom-12 tile settles it differently. Sampled over Haifa on 2026-08-15, every feature carried poiCategory, poiIcon and poiIconColor and not one raw OSM tag — the tag mapping has already run by the time the tileset is cut. So the category is read, and only the naming is ported: poiIcon back to the label the map site puts on that icon in its own POI category list. subtype is null for an icon that list does not name, including icon-search, upstream's fallback for a feature it recognised only well enough to draw.

Category

Subtypes

Water

Spring, Pond · Waterfall · Waterhole · Water Well · Cistern · Stream, River

Natural

Cave · Tree · Flowers · Peak, Ridge, Valley

Historic

Ruins · Archaeological Site · Memorial

Viewpoint

Viewpoint

Camping

Picnic Area · Campsite · Alpine Hut

Other

Place · Attraction · Artwork · Synagogue · Church · Mosque · Place of Worship · Accommodation · Nature Reserve, National Park · iNature Entry · Wikipedia Entry

Other is upstream's bucket for whatever its mapping did not place, and it outnumbers everything else in a built-up area — 111 of 168 features in a sampled Haifa tile. It is therefore excluded unless asked for. The route categories (Hiking, Bicycle, 4x4) share those tiles and are never returned here; search_hiking_routes is where they belong.

A stream or river is mapped as a line and marked at one point of it, so its distance is to that mark rather than to wherever the route crosses the water. When one is in the result, a warning says so.

What a scan costs is set by how long the route is, not by the buffer: the corridor is fetched as map tiles, which are kilometres across. A local trail costs one or two; the Israel National Trail's corridor cost 128 tiles at zoom 12 when it was measured on 2026-08-15, over the budget of 100, and fails with search_area_too_large saying that a narrower buffer would still need 119 — resolve one of its sections instead.

An empty list means nothing of that kind is mapped near this route. It is not evidence that there is no water, and every response says so in unknowns.

route_between_points

Argument

Type

Default

Notes

start

{lat, lng}

Where to start; search_places returns this shape

end

{lat, lng}

Where to finish

activity

Hiking | Bicycle | 4x4

Hiking

Each uses a different set of ways

Asks the map site's own routing engine for a path between two points and returns it as a GeoJSON LineString, with three profiles to choose from — walking, cycling and 4x4, each over a different set of ways. It is the one tool here that answers a question the map does not already hold an answer to.

Which is also the thing to keep in mind: its answer is a computation rather than a record. Everything else this server returns is something a person put on a map. This is a line a routing engine drew just now by joining ways in a graph: nobody has walked it, and nothing in it knows about a gate, a fence, a firing zone or a stream in flood. Every response leads with a warning saying so, and it is not conditional on anything.

Returns the path as a GeoJSON LineString, its lengthKm measured along the line, the straightLineKm between the two points for comparison, and both ends as {requested, onPath, metersApart} — because a path starts where the router can, not where it was asked to. The ends are snapped to the nearest mapped way, which in open country can be hundreds of metres away, and covering that ground is a walk the path does not include. Past 100 m either end says so in warnings too.

Upstream sends an elevation with every position. It is dropped: it is interpolated from a profile sampled every 30 m, and the numbers worth computing from it — total climb, above all — would be noise dressed as data.

Bounds: both points must be inside the area the map covers (about 29.3–33.4 N, 34.2–35.9 E, the same box search_places ranks by), at least 10 m apart, and no more than 100 km apart in a straight line. Each of those is refused as invalid_input before any request is sent — the router's graph stops where the map does, and a live call from Haifa into the sea was still unanswered after 40 seconds.

activity is a closed set of three, deliberately. Upstream's type= takes Hike, Bike and 4WD, and answers anything else — a typo, a renamed constant — with a walking route rather than an error. So this server sends only words it knows, and then checks the profile upstream says it used against the one asked for: a cycling request that comes back as a footpath is reported as upstream_schema_changed rather than returned. The live test group exercises that check on every activity, which is the only place it can be exercised.

None — upstream's fourth routing type, a straight line — is not offered. A live call answers HTTP 500 (checked twice, 2026-08-15), and the map site's own frontend never sends it either: it interpolates the straight line client-side. Calling one a route would be the most misleading thing this server could return.

There is no ihmUrl: a calculated path is not a feature on the map site, and this server creates nothing upstream to link to.

Known limitations

Things that are true of this server by design, and worth knowing before trusting an answer:

  • Everything is a mapped fact, not a current one. The data carries no observation dates. A trail may have closed, a spring dried, a track been fenced, at any point since somebody typed it in.

  • Only hiking routes are searchable. The same tiles carry cycling and 4x4 routes; search_hiking_routes does not return them.

  • Two of the map's sources resolve to geometry. OSM and Users do; Nakeb, iNature and Wikidata appear in search results and are refused by get_route_details with unsupported_source. Their ihmUrl still opens on the map site.

  • Area searches are bounded by a tile budget, so a 40 km radius is the largest circle and a country-length route is too long a corridor to scan.

  • Long geometries are thinned, and a route still too detailed at 100 m is refused rather than truncated.

  • Names come in one language or the other, whichever the map data holds; language is a preference, not a translation.

  • Nothing is cached across restarts. The cache is in memory, and a five minute TTL by default.

  • No elevation, no duration, no turn-by-turn. The routing endpoint can return instructions; this server does not ask for them.

Configuration

All settings are environment variables, read once at startup — an invalid value stops the server with a message naming the variable, rather than failing later inside a tool call.

Variable

Default

Range

Purpose

IHM_BASE_URL

https://mapeak.com

http(s) URL

API and tile host

IHM_OSM_API_URL

https://api.openstreetmap.org/api/0.6/

http(s) URL

Where OSM route geometry is fetched from

IHM_REQUEST_TIMEOUT_SECONDS

10

0 < x ≤ 60

Applied to connect, read, write and pool

IHM_USER_AGENT

israel-hiking-mcp/<version> (+repo url)

non-empty

Sent on every request

IHM_CACHE_TTL_SECONDS

300

0–86400

0 disables caching

IHM_CACHE_MAX_ENTRIES

512

≥ 1

Bounds the in-memory response cache

IHM_MAX_CONCURRENT_REQUESTS

4

1–16

Simultaneous upstream connections

IHM_MAX_TILES_PER_TOOL_CALL

100

1–500

Tile budget for area searches

IHM_MAX_OSM_REQUESTS_PER_TOOL_CALL

16

1–64

Request budget for nested OSM relations

IHM_LOG_LEVEL

INFO

log level

Logs go to stderr only

Area searches and the tile budget

The map's routes and points of interest have no query API; they exist only as the vector tiles the map site draws (/vector/data/global_points/{z}/{x}/{y}.mvt, zoom 10–14). An area search therefore costs one request per tile covering it, which grows with the square of the radius. Searches run at zoom 12 by default, where a 40 km radius costs 98 tiles — just inside the default budget of 100. Only tiles that actually reach into the search circle are fetched; the box around that circle would cost 121.

Zoom 12 was chosen for that budget and then checked for completeness: sampling zoom-12 tiles over Haifa, Jerusalem, Eilat and the Golan on 2026-08-14 against all sixteen of each tile's zoom-14 children found no features dropped at the lower zoom (817 features in the densest sample, 0 missing). The tileset is not thinned by zoom, so a zoom-12 search sees every marker a zoom-14 one would.

The same budget covers the corridor along a route, where the shape is a line rather than a circle and only the tiles the line itself comes near are fetched — the box around a route running diagonally holds more than twice as many.

Past the budget, a search fails with search_area_too_large naming the tile count, the limit, and either a radius that would have worked or, for a route too long for any buffer, that fact. It never silently returns part of the area.

Request behaviour

Every upstream request is a GET with a timeout, capped concurrency, and a short-lived in-memory cache. A request is retried once, and only after a timeout, a connection failure, or a 5xx; 4xx responses are never retried and failures are never cached. This is a personal-scale tool pointed at a volunteer-run service — please keep it that way.

Two hosts are contacted, each with its own connection pool and cache: mapeak.com for search, tiles and shared routes, and api.openstreetmap.org for OSM route geometry. Both see the same identifying User-Agent. OSM's API usage policy applies to the second, and one deeply nested relation is the only thing here that can cost more than a handful of requests — hence its own budget above.

Error codes

Tool failures arrive as MCP errors whose text begins with a stable code:

invalid_input · place_not_found · route_not_found · unsupported_source · search_area_too_large · geometry_too_large · upstream_timeout · upstream_unavailable · upstream_schema_changed · rate_limited

upstream_timeout, upstream_unavailable and rate_limited are transient; the rest will not be fixed by retrying. Upstream response bodies, URLs and tracebacks are never included in the message — those go to the stderr log.

Acceptance run

The Haifa scenario, driven through a real MCP session against the live hosts on 2026-08-15. Abridged — warnings and attribution are on every response.

search_places "Haifa"
  Haifa | Haifa, Haifa Subdistrict, Israel | /poi/OSM/node_1656107649
  warning: 10 match(es) outside Israel were omitted.

search_hiking_routes  15 km around it, 4–12 km long
   1.23 km  OSM/relation_13363223    8.59 km  Haifa Trail - Upper Hadar and Ramat Hadar
   1.75 km  Nakeb/255                 7.5 km  טיול מדרגות חיפה
   5.21 km  OSM/relation_20976850    5.87 km  Connecting Paths
   7.18 km  Users/iXKu2M4BV5         6.46 km  נחל גלים, כלח והמבצר האחרון
   …
  warning: Showing the 10 nearest of 16 matching routes.

get_route_details  OSM/relation_13207704
  Haifa Trail | Hiking | 903 positions, not thinned | MultiLineString
  warning: recorded as 24 separate lines that do not meet end to end

get_route_details  Users/iXKu2M4BV5
  נחל גלים, כלח והמבצר האחרון | 6.46 km | +256 / −251 m | 538 positions

find_pois_along_route  the Haifa Trail, 1,000 m, Water
    120 m  Water Well     Vardiya Spring Well
    615 m  Waterfall      מפל נחל הגיבורים
    960 m  Spring, Pond   בריכת מעיין התאנה
  warning: 3 of these are water features. Mapped water feature — not known to
           be flowing, reachable, permitted or safe to drink. …

find_pois_along_route  the same route, 500 m, default categories
    106 m  Natural   Cave        מערה כפולה
    120 m  Water     Water Well  Vardiya Spring Well
    443 m  Historic  Memorial    גדוד 22 חטיבת "כרמלי"

route_between_points  32.8191,34.9984 → 32.7800,35.0200 (straight line 4.79 km)
  Hiking    6.39 km, 367 positions, ends snapped 11 m / 5 m
  Bicycle   7.84 km, 391 positions, ends snapped 33 m / 5 m
  4x4       8.01 km, 417 positions, ends snapped 33 m / 5 m

refusals
  route_between_points into Cyprus     → [invalid_input] outside the area this map covers
  route_between_points Haifa → Eilat   → [invalid_input] 364 km apart; routes up to 100 km
  find_pois_along_route on the INT     → [search_area_too_large] 128 tiles; a narrower
                                         buffer would still need 119

Two things the run showed that had already changed upstream since they were first measured: the Haifa Trail now resolves to 24 disconnected lines rather than the ten counted for its downtown section on 2026-08-14, and the Israel National Trail's corridor now costs 128 tiles rather than 123. Both are the mapping moving, not the server — and both are why the numbers in this README carry the date they were measured on.

Licensing and attribution

Read LICENSE-NOTICE.md in full before using any output. The short version:

  • Upstream map data is CC BY-NC-SA 3.0 (Israel Hiking Map / Mapeak) and ODbL (OpenStreetMap). The upstream licence states that all output of the work carries the same licence, so anything this server returns is non-commercial and share-alike.

  • Every response carries an attribution object naming both sources and their licences. Preserve it wherever the data is displayed or passed on. A model handed data with no provenance will invent some.

  • This project is unofficial. It is not affiliated with, endorsed by, or supported by the Israel Hiking Map project or its maintainers.

  • This repository's own code is distributed under the same CC BY-NC-SA 3.0 terms, to keep the combined work unambiguous. The full legal text is in LICENSE.

Responsible use

Both hosts this server talks to are run for the public by people who are not being paid to serve it. Everything below is a deliberate constraint, not an accident of implementation:

  • Read-only. Every request is a GET. Nothing is written, no account is created, no session is held.

  • Identified. A descriptive User-Agent naming this project and its repository goes on every request to both hosts.

  • Bounded. Concurrency is capped, every request has a timeout, area searches have a tile budget and nested OSM relations have a request budget. A retry happens once, after a transient failure, never after a 4xx.

  • Cached. Responses are cached in memory for the session, so a repeated tool call in one conversation costs nothing upstream.

Before any public deployment, shared instance, or sustained automated querying: contact the Israel Hiking Map maintainers first, and read OpenStreetMap's API usage policy. A tool that makes it easy for a language model to fan out over somebody else's map tiles is exactly the kind of thing that gets an API closed for everybody.

And the point the whole design turns on: nothing here is a substitute for a current map, local knowledge, and carrying enough water. An answer from this server is a lead to check, not a plan to follow.

Available Tools

1 tool
pingA

Check that the server is alive and report its version and data attribution.

This tool touches no upstream service. It is a placeholder used to verify transport and tool wiring, and will be removed once real tools exist.

ParametersJSON Schema
NameRequiredDescriptionDefault
echoNopong

Output Schema

ParametersJSON Schema
NameRequiredDescription
echoYesThe caller's echo string, returned verbatim.
serverYesMCP server name.
statusYesAlways 'ok' when the server is reachable.
versionYesServer version.
attributionYesRequired data attribution notice.

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description discloses key behavioral traits: it touches no upstream service, is a placeholder, and will be removed. No annotations are provided, so this is valuable. However, it doesn't explain the behavior of the echo parameter or detail the response beyond version/attribution, though an output schema exists.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences: the first gives the core purpose, the second adds placeholder context. It is front-loaded and every sentence earns its place with no fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple placeholder tool, the description covers purpose, safety, and lifecycle, but it omits any explanation of the echo parameter. Given the output schema likely covers return values, the description is mostly complete but not fully self-sufficient.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema defines an optional 'echo' parameter with a default but no description. The description does not mention this parameter at all, leaving its semantics entirely unspecified. With 0% schema coverage, this is a significant gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool checks server liveness and reports version/attribution, and explicitly labels it a placeholder for verifying transport/wiring. This is a specific verb+resource with clear scope.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly says it is a placeholder for verifying transport and tool wiring, and notes it will be removed, which guides when to use it. It also states it touches no upstream service, implying it's safe for testing. No sibling alternatives exist, but the context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

TDQS

A3.8/5.0
Disambiguation5/5

With only a single tool, there is no possibility of confusing it with other tools. The tool's purpose as a health check is clearly distinct.

Naming Consistency5/5

The tool name 'ping' is a standard, recognizable term for a liveness check. While there is no pattern to compare against, the naming is unambiguous and follows common convention.

Tool Count2/5

The server has only one placeholder tool, which is far too few for the domain implied by 'Israel Hiking MCP.' A single ping tool does not provide any substantive functionality.

Completeness1/5

The tool surface is severely incomplete. The server's purpose appears to be related to hiking, but no actual hiking tools exist; only a placeholder ping is provided, so there is no coverage of the domain.

Maintenance

ActivityMaintained
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tisheldev/israel-hiking-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server