-
Notifications
You must be signed in to change notification settings - Fork 1
Use case Building a local guide agent
Five tools that turn a vague name into a coordinate and a coordinate back into a sentence — and one licence, the ODbL, that asks something of you if you keep the results.
You are building an agent that answers local questions about Germany: "where is the adesso office in Dortmund", "what is at 52.5163, 13.3777", "which Neustadt do you mean", "what's near the station". The hard part is not the lookup. The hard part is that a user says a name and you do not know what kind of thing it is.
"Neustadt" is one of about twenty towns. "adesso" is a company. "Hauptstraße 12, 36037 Fulda" is a door. "Köln Hbf" is a station. "A7" is a motorway. Each
one lives in a different table, and pointing the wrong tool at it gets you nothing
or, worse, the wrong Neustadt.
Wo ist das adesso-Büro in Dortmund?
Which Neustadt is near Hamburg?
What's at 52.5163, 13.3777?
Wo ist die nächste Apotheke zum Leipziger Hauptbahnhof?
When the kind is genuinely unknown, there is a prompt for that:
find_a_place with query (and optionally near to break a tie). It tries
each table in turn and stops at the first that answers — the right recovery move
after a tool has reported a name as ambiguous. Once you know the kind, skip the
prompt and go straight to the tool: a settlement to find_place, a company or
landmark to find_poi, a street address to find_address.
| you have | you want | tool |
|---|---|---|
| a settlement, district, Land, station, stop, motorway or junction name | which one it is, and its coordinate | find_place |
| a business or landmark name, or a brand | its address and coordinate | find_poi |
| a street and house number | the coordinate of that door | find_address |
| a coordinate | where that is, in words | describe_location |
| a point | what is around it | find_nearby |
There is a sixth way in that costs no tool call: the resource template
viafrei://place/{query} resolves a name exactly the way every tool's place
argument resolves it — a point, a short list of candidates, or an honest no. Read
it once, pin the coordinate, and reuse it as lat/lon for every subsequent
call. That is the cheapest way to stop re-resolving the same name.
{ "query": "Neustadt", "near": "Hamburg", "radius_km": 50, "limit": 5, "language": "de" }Returns every match with its kind, its official key (AGS/RS), population,
English name and coordinate. near is the disambiguator: pass it and every result
carries its distance from that point. lat+lon does the same job when you
already hold a coordinate. radius_km (1–200) drops candidates outside the
circle; omit it to rank by distance without dropping any.
kind narrows to one of city, district (Kreis), admin (Land or
Regierungsbezirk), station, stop, motorway, junction. Omit it unless the
person was specific.
This is the recovery tool. When any other tool reports a name as ambiguous,
find_place is where you find out which one is meant — then you ask the user, then
you pass the coordinate. English names and spellings without umlauts both work.
At most 10 results.
{ "name": "adesso", "in": "Dortmund", "category": "office", "limit": 5, "language": "de" }Finds a company, shop, clinic, hotel, office or landmark and returns its address,
category and coordinate. Part of the name is enough — "adesso" finds
"adesso SE" — and brands are matched as well as names, so a chain name works.
Three things that decide whether this tool feels fast or slow:
-
Give
in(a town) whenever you can. It is both far faster and far less ambiguous than a nationwide search. Useinornear/lat+lon, never both. -
near+radius_km(1–50, default 10) is for "the nearest X" rather than "X in Y".radius_kmis ignored wheninis used. -
categorynarrows to one OpenStreetMap family:office(companies, agencies),shop,amenity(fuel, pharmacy, school, restaurant),healthcare,tourism(hotels, attractions),leisure,industrial,building. Omit unless the person was specific — the wrong family returns nothing rather than something close.
The coordinate it returns passes straight to any other tool as lat/lon. At
most 10 results.
{ "query": "Hauptstraße 12, 36037 Fulda", "limit": 3, "language": "de" }Needs a street and a number, plus a town or a postcode. Both orders work. Returns the coordinate and what OpenStreetMap holds under it.
One thing to get right in your UI: more than one result does not mean the address is ambiguous — it means the door is mapped more than once in OpenStreetMap. At most 5 results. Do not present them as alternative addresses.
And one honest caveat, because it has misled a careful measurement before: an address that does not resolve says nothing about its region. A real house number in a fully imported Bundesland can come back unfound simply because it is not in OpenStreetMap under the spelling it was asked for. Address and POI coverage spans all sixteen Länder (measured 2026-09-27); a miss is a miss on that door, not a gap in that Land.
{ "lat": 52.5163, "lon": 13.3777, "language": "en" }Turns a coordinate into words: the nearest address, settlement, Kreis, administrative area and motorway junction, each with its own distance. Only this direction — it never looks a name up.
Two caveats it states about itself, and both matter if you build on it:
- The gazetteer facts are the nearest point, not a boundary lookup. Near a border the nearest Kreis point can belong to the other side.
- The administrative point can be a Regierungsbezirk rather than a Land. Do not build a jurisdiction or tax rule on it.
The explain_this_coordinate prompt wraps this into a sentence a traveller
understands, with the same caveats stated in the answer.
{ "place": "Leipzig Hbf", "radius_km": 3,
"categories": ["fuel", "charging", "parking"], "language": "de" }One glance across five categories — fuel, charging, parking, station
(railway station), junction (motorway junction) — each with its distance. Radius
1–15 km, default 5; up to 3 per category. Omit categories for all five, because
the full picture is usually the useful answer.
It is deliberately broad and shallow. It does not answer a fuel grade's price
(find_cheapest_fuel), a connector or live charger status
(find_charging_station), parking kind or occupancy (find_parking), a name
lookup (find_place/find_poi/find_address) or a coordinate's address
(describe_location). The plan_local_errand prompt does the glance first and
then goes deeper on one category only if the user asks — that is the pattern to
copy.
user names something
↓
kind unknown? → viafrei://place/{query} or the find_a_place prompt
↓
ambiguous? → find_place with `near` → ask the user which one
↓
pin lat/lon once
↓
every subsequent call takes lat+lon, not the name
↓
describe_location / find_nearby to say where it is and what is there
Pinning the coordinate matters for more than speed. Every tool that takes a
place re-resolves it, and place resolution falls through the server's own
gazetteer to the OpenStreetMap tables — so the same name can be answered from a
different table on a different call, which changes the licence on the answer. Pin
it once, and you know what you are holding.
Candidates with kind, official key, population, English name, coordinate and distance; POIs with address, category and coordinate; addresses with the point and what is mapped there; a coordinate rendered as five facts with five distances; a neighbourhood as up to three things per category. All in the language of the question, with place names never translated.
viafrei://gazetteer tells you what the place index actually holds — rows and last
load per source and kind, how many entries carry an English name, and the sixteen
Länder it can name. Read it when you want to know the shape of the coverage rather
than probing it with queries.
This is the page where the ODbL applies most of the time, so it is worth understanding rather than skimming.
Address and point-of-interest results are OpenStreetMap-derived under the ODbL 1.0, and they carry this line:
OSM-Standortdaten: © OpenStreetMap-Mitwirkende, ODbL 1.0
Reproduce that line, not a shorter one, and reproduce the version the server
sent you. (An earlier register printed a Geokodierung: prefix; it is wrong, and
no answer carries it. If you copied it, change it.)
The test is which table answered, not which tool you called and not what your input looked like:
- A place the server resolved from its own gazetteer is not OSM-derived and carries neither the line nor the obligation. Nor does a station, nor a motorway.
- A place it resolved from OpenStreetMap does. And because place resolution
falls through the gazetteer to the OSM tables, any tool that takes a
placecan come back OSM-derived — a weather warning, a road status, a charging station, a car park,find_nearby. Measured on 2026-09-27, a weather warning asked forZeiss-Großplanetariumnamed["dwd", "osm"]and carried the ODbL line; one forAllianz Arenanamed["osm"]alone.
So "did I ask for an address?" is the wrong question, and so is "is this a
geocoding tool?". Read _meta.sources and the attribution line on the answer you
actually got. That is a positive test, it costs nothing, and it is the only one
that is right.
ODbL's share-alike is on the database, not on the sentence. Showing a user an address you looked up is ordinary use. But:
If you build your own database out of address or point-of-interest results and use it publicly, you owe your recipients the same offer ViaFrei makes.
That offer is ODbL § 4.6, and ViaFrei's side of it stands: ask, and you get
both extracts — addresses and points of interest, one file each — plus the
alterations made to them, under ODbL 1.0 with a single licence notice covering the
two. The server states the offer in viafrei://attribution, and
viafrei://addresses reports what the address table holds per Bundesland, with the
extract date. An issue on the
repository reaches the
maintainers, and the server's own register names the contact route as well.
Practically, for a local-guide agent:
- Answering a user's question and forgetting it: ordinary use. Show the line.
- Caching a resolved coordinate for a session: ordinary use. Show the line.
- Accumulating the results into a place database and publishing a product on top of it: you have a Derivative Database. Show the line, and make the § 4.6 offer to your own recipients.
find_place's candidates are not only OSM. The gazetteer is built from GeoNames
(CC BY 4.0) and the BKG's GN250 administrative names under dl-de/by-2-0,
whose source note is printed on the product's own terms, character for character.
Station names come from Deutsche Bahn's station directory under CC BY 4.0. Which
means a single find_place answer can carry several lines at once — show all of
them, in the form the server sent them.
Several lines end in bearbeitet ("edited") because an answer is always a derived
form and CC BY 4.0 § 3(a)(1)(B) makes you say that you changed the material. That
is part of the string, not an afterthought.
And the general rule, which applies here as everywhere: when a result carries
_meta.purposeNote, reproduce that sentence verbatim. It is a legal condition
of the data, not a caption.
Full register:
SOURCES.md,
or the live resource viafrei://attribution, which is authoritative if the two
ever disagree.
- Use case An EV on a long weekend — the place tools feeding a real trip.
-
Tools at a glance — every parameter, plus
viafrei://place/{query}. - FAQ — commercial use, and what the licences make of it.
- Use case Driving Munich to Berlin
- Use case The commute that broke
- Use case An EV on a long weekend
- Use case Fleet and logistics briefings
- Use case Building a local guide agent
https://mcp.viafrei.de/mcp
https://mcp.viafrei.de/sse
No key. No account.