viafrei 0.0.9 → 1.3.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +349 -0
- package/README.md +173 -42
- package/SOURCES.md +237 -71
- package/dist/config.d.ts +10 -0
- package/dist/config.js +39 -6
- package/dist/failure.d.ts +10 -1
- package/dist/failure.js +51 -3
- package/dist/fetch.d.ts +19 -0
- package/dist/fetch.js +77 -4
- package/package.json +15 -2
package/SOURCES.md
CHANGED
|
@@ -14,7 +14,11 @@ can be breached without noticing. Those two are first, before the catalogue.
|
|
|
14
14
|
The authoritative, always-current register is the resource
|
|
15
15
|
**`viafrei://attribution`** on the server itself. Any MCP client can read it. If
|
|
16
16
|
this page and that resource ever disagree, the resource is right and this page is
|
|
17
|
-
stale: tell us and we will fix it.
|
|
17
|
+
stale: tell us and we will fix it. One row we re-measured on 2026-09-27 disagrees
|
|
18
|
+
the other way round — it is named in the catalogue below, at the BKG row, and the
|
|
19
|
+
fix belongs to the server rather than to this page. Whatever the surfaces say,
|
|
20
|
+
the answer you hold is the thing to read: `_meta.sources` and the attribution
|
|
21
|
+
lines on it are produced from the same result you are looking at.
|
|
18
22
|
|
|
19
23
|
---
|
|
20
24
|
|
|
@@ -57,42 +61,75 @@ Realtime public-transport data comes from **DELFI e.V.** under
|
|
|
57
61
|
If that is not what you want for your product, ask for the same answer from a
|
|
58
62
|
source that is not BY-SA, or keep the two apart.
|
|
59
63
|
|
|
60
|
-
### And one that applies
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
### And one that applies if you get an OpenStreetMap-derived result back
|
|
65
|
+
|
|
66
|
+
Some answers are built from **OpenStreetMap**, under the **ODbL 1.0**. Address
|
|
67
|
+
and point-of-interest lookups always are; so is any other answer whose place our
|
|
68
|
+
own gazetteer could not resolve, because place resolution falls through to the
|
|
69
|
+
OSM tables. The public service gives such answers, measured on 2026-09-27, so
|
|
70
|
+
**this binds you** as soon as one of those tables answered you — not at some
|
|
71
|
+
later date, today. An earlier version of this page said the opposite, on the
|
|
72
|
+
strength of a measurement taken before the extract was loaded; if you read that
|
|
73
|
+
version, re-read this section.
|
|
74
|
+
|
|
75
|
+
You can tell which answers are affected without guessing, and the test is the
|
|
76
|
+
answer rather than the question: the result carries
|
|
77
|
+
`OSM-Standortdaten: © OpenStreetMap-Mitwirkende, ODbL 1.0` and names `osm` in
|
|
78
|
+
`_meta.sources`. What decides it is **which table answered**, not which tool you
|
|
79
|
+
called and not what your input looked like. A place the server resolved from its
|
|
80
|
+
own gazetteer does not carry the line, and neither does a station or a motorway;
|
|
81
|
+
a place it resolved from OpenStreetMap does — and place resolution falls through
|
|
82
|
+
the gazetteer to the OSM tables, so **any** tool that takes a `place` can come
|
|
83
|
+
back with an OSM-derived answer. Measured on 2026-09-27: a weather warning asked
|
|
84
|
+
for `Zeiss-Großplanetarium` named `["dwd","osm"]` and carried the ODbL line. So
|
|
85
|
+
"did I ask for an address?" is the wrong question, and so is "is this a
|
|
86
|
+
geocoding tool?" — **read `_meta.sources` and the attribution line on the answer
|
|
87
|
+
you actually got.**
|
|
66
88
|
|
|
67
89
|
ODbL's share-alike is on the *database*, not on the sentence: if you build your
|
|
68
|
-
own database out of address results and use it publicly, you
|
|
69
|
-
the same offer we make. **Our side of that offer stands** —
|
|
70
|
-
server states it in `viafrei://attribution`: ask, and you get
|
|
71
|
-
|
|
72
|
-
|
|
90
|
+
own database out of address or point-of-interest results and use it publicly, you
|
|
91
|
+
owe your recipients the same offer we make. **Our side of that offer stands** —
|
|
92
|
+
ODbL § 4.6 — and the server states it in `viafrei://attribution`: ask, and you get
|
|
93
|
+
**both** our extracts — addresses and points of interest, one file each — and our
|
|
94
|
+
alterations to them, under ODbL 1.0 with a single licence notice covering the two.
|
|
95
|
+
Both, because a recipient who derived from POI results is entitled to the POI
|
|
96
|
+
database and this sentence named only the addresses until 2026-09-27. An issue on
|
|
97
|
+
this repository reaches us, and the server's own register names the contact route
|
|
98
|
+
as well.
|
|
73
99
|
|
|
74
100
|
---
|
|
75
101
|
|
|
76
102
|
## The catalogue
|
|
77
103
|
|
|
78
|
-
**Status is not a promise, it is a measurement.** Every "live" below
|
|
79
|
-
by calling the public endpoint on
|
|
80
|
-
answer named. A source can be licensed, cleared and loaded and still not answer
|
|
104
|
+
**Status is not a promise, it is a measurement.** Every "live" below **except the
|
|
105
|
+
one labelled `not re-measured`** was checked by calling the public endpoint on
|
|
106
|
+
**2026-09-27** and reading which source the answer named. A source can be licensed, cleared and loaded and still not answer
|
|
81
107
|
a question today; where that is so, this page says it.
|
|
82
108
|
|
|
109
|
+
The measurement was taken by calling **fifteen of the server's sixteen read-only
|
|
110
|
+
tools — every one except `find_cheapest_fuel`** — once each, and reading
|
|
111
|
+
`_meta.sources` out of the result: not by reading the code, and not by asking
|
|
112
|
+
whether a feed was running. Thirteen sources were named by at least one answer.
|
|
113
|
+
The tool not called is the one whose row is deliberately not re-measured, and
|
|
114
|
+
the count above says so rather than absorbing it: a status this page cannot stand behind is
|
|
115
|
+
worse than an honest gap.
|
|
116
|
+
|
|
83
117
|
- **live** — an answer came back naming it when this page was checked;
|
|
84
118
|
- **in the service** — licensed and loaded, and the spot check produced no
|
|
85
119
|
answer that named it, so it is reported as unconfirmed rather than as live;
|
|
86
|
-
- **not on the public service today** — cleared and built, and it does not
|
|
87
|
-
answer right now. Do not design around it yet;
|
|
88
120
|
- **read** — the licence is read and cleared, and nothing uses it yet.
|
|
89
121
|
|
|
122
|
+
There used to be a fourth value, **not on the public service today**, and no row
|
|
123
|
+
carries it any more: the three rows that did now answer. It is removed from this
|
|
124
|
+
legend rather than left standing, because a legend entry nothing uses reads as a
|
|
125
|
+
status somebody could still be relying on.
|
|
126
|
+
|
|
90
127
|
| Source | Publisher | Covers | Publisher's rhythm | Licence | Status |
|
|
91
128
|
|---|---|---|---|---|---|
|
|
92
129
|
| Motorway traffic | Die Autobahn GmbH des Bundes | Roadworks, warnings, closures, webcams, lorry parking, charging points on the Bundesautobahnen | continuous | open, no licence text published — see below | live |
|
|
93
130
|
| Roadworks (DATEX II) | Bundesanstalt für Straßen- und Verkehrswesen (BASt), via the national access point | Arbeitsstellen on the Bundesautobahnen | a few times a day | CC BY 4.0 | live |
|
|
94
131
|
| Lorry parking, static | Lkw-Parken BAB Deutschland / BMV, via the national access point | Nationwide lorry parking sites on the Bundesautobahnen | a few releases a year | GeoNutzV | live |
|
|
95
|
-
| Fuel prices (MTS-K) | Tankerkönig / Markttransparenzstelle für Kraftstoffe | Prices and station details for German filling stations | continuous, in the publisher's own cycle | CC BY 4.0 **plus the MTS-K purpose limit** | live, **limited coverage** |
|
|
132
|
+
| Fuel prices (MTS-K) | Tankerkönig / Markttransparenzstelle für Kraftstoffe | Prices and station details for German filling stations | continuous, in the publisher's own cycle | CC BY 4.0 **plus the MTS-K purpose limit** | live, **limited coverage**, **not re-measured** — see below |
|
|
96
133
|
| Timetables | Deutsche Bahn AG (DB API Marketplace) | Planned and changed rail departures per station | continuous | CC BY 4.0 | live |
|
|
97
134
|
| StaDa — Station Data | Deutsche Bahn AG (DB API Marketplace) | Station master data: name, number, address, coordinates, facilities | static master data | CC BY 4.0 | live |
|
|
98
135
|
| Official weather warnings | Deutscher Wetterdienst (DWD) | Amtliche Wetterwarnungen per municipality | as issued | CC BY 4.0, with a source note fixed by law | live |
|
|
@@ -101,36 +138,71 @@ a question today; where that is so, this page says it.
|
|
|
101
138
|
| Charging point master data | EnBW AG, via the national access point | AFIR charge-point master data for EnBW mobility+ | static releases | CC BY 4.0 | in the service |
|
|
102
139
|
| Charging point availability | Tesla Germany GmbH and Volkswagen Group Charging GmbH, via the national access point | AFIR dynamic status for their own networks | live status | **CC0 1.0** | in the service |
|
|
103
140
|
| German road rules | ViaFrei, compiled from official sources | Environmental zones, tolls, equipment duties, charging rules | reviewed at least twice a year | our own text | live |
|
|
104
|
-
| Public-transport realtime (GTFS-RT Trip Updates) | DELFI e.V., via the national access point (Mobilithek) | Germany-wide departure and arrival forecasts | real time | **CC BY-SA 4.0** |
|
|
105
|
-
| FaSta — Facility Status | Deutsche Bahn AG (DB API Marketplace) | Live state of lifts and escalators at stations | live status | CC BY 4.0 |
|
|
106
|
-
| Geocoding (addresses) | OpenStreetMap contributors | Street and house-number points in Germany | refreshed from the OSM extract | **ODbL 1.0** |
|
|
141
|
+
| Public-transport realtime (GTFS-RT Trip Updates) | DELFI e.V., via the national access point (Mobilithek) | Germany-wide departure and arrival forecasts | real time | **CC BY-SA 4.0** | live |
|
|
142
|
+
| FaSta — Facility Status | Deutsche Bahn AG (DB API Marketplace) | Live state of lifts and escalators at stations | live status | CC BY 4.0 | live |
|
|
143
|
+
| Geocoding (addresses and points of interest) | OpenStreetMap contributors | Street and house-number points and mapped points of interest in Germany | refreshed from the OSM extract | **ODbL 1.0** | live |
|
|
107
144
|
| Timetable data (static GTFS) | DELFI e.V. | Germany-wide scheduled public transport | weekly release | CC BY 4.0 | read |
|
|
108
145
|
| Stop directory (zHV) | DELFI e.V. | Every public-transport stop in Germany with its identifier and coordinates | weekly release | CC BY 4.0 | read |
|
|
109
|
-
|
|
|
110
|
-
|
|
|
146
|
+
| Disruption reports (Störungsmeldungen) | DELFI e.V., via the national access point (Mobilithek) | Germany-wide public-transport disruption messages | real time | **CC BY-SA 4.0** | read |
|
|
147
|
+
| Station car parks (DB BahnPark) | Deutsche Bahn AG (DB API Marketplace) | Car parks at railway stations, with their operator and access details | continuous | **dl-de/by-2-0** | read |
|
|
148
|
+
| Administrative units and place names | Bundesamt für Kartographie und Geodäsie (BKG), product GN250 | Länder, Regierungsbezirke, Kreise, Gemeinden with their official keys and names | yearly release | **dl-de/by-2-0** | live |
|
|
149
|
+
| Police traffic events | Landesbetrieb Straßenbau NRW (VIZ.NRW), via the national access point | Police traffic reports, Germany-wide | continuous | **Datenlizenz Deutschland – Zero – 2.0** | in the service — see below |
|
|
111
150
|
|
|
112
|
-
|
|
113
|
-
|
|
151
|
+
Two of those rows need saying out loud rather than in a cell, because a cell
|
|
152
|
+
cannot carry a reason — fuel takes two bullets, because the coverage limit and
|
|
153
|
+
the decision not to re-measure are different facts.
|
|
114
154
|
|
|
115
155
|
- **Fuel coverage is not nationwide.** We watch a limited set of stations, on
|
|
116
156
|
the terms the publisher sets, and we ask for a station's details only when
|
|
117
157
|
somebody actually asks a question. Outside that set you may get nothing back.
|
|
118
158
|
That is a consequence of the MTS-K rules, not a gap we are hiding.
|
|
119
|
-
- **
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
159
|
+
- **Fuel is the one row not re-measured on 2026-09-27, and that was deliberate.**
|
|
160
|
+
MTS-K sets a minimum interval per station and its terms make needless querying
|
|
161
|
+
a real risk to the access itself, so this page does not spend a request on
|
|
162
|
+
proving a status it already knew. The cell is carried forward from the previous
|
|
163
|
+
check and labelled `not re-measured` rather than dressed up as today's
|
|
164
|
+
measurement. Everything else in the table was called.
|
|
165
|
+
- **Police traffic events are stored, and on 2026-09-27 no answer named them.**
|
|
166
|
+
It is polled and its rows are kept. The status is a statement about the
|
|
167
|
+
answers, not about the code: `check_road_status` was called on the A40, A3,
|
|
168
|
+
A1, A57 and A46 — VIZ.NRW's own Land — and named only the motorway interface
|
|
169
|
+
and the BASt roadworks feed. So by the rule at the top of this section the
|
|
170
|
+
row reads `in the service` and not `live`. What it does **not** say is that no
|
|
171
|
+
tool can reach the feed: whether a source is named is decided by a live
|
|
172
|
+
catalogue row rather than by code, so this row could begin carrying its
|
|
173
|
+
attribution line with no release at all. Do not design around it yet — and
|
|
174
|
+
read `_meta.sources`, not this page, on the day you do.
|
|
175
|
+
|
|
176
|
+
**What changed on 2026-09-27, stated plainly because this page said the
|
|
177
|
+
opposite until today.** Address lookup, lift and escalator status, and
|
|
178
|
+
public-transport realtime were all listed as not answering. All three now
|
|
179
|
+
answer, and each was confirmed by a call whose result named the source: OSM for
|
|
180
|
+
addresses and points of interest, FaSta for lifts and escalators, DELFI for
|
|
181
|
+
realtime. The BKG administrative gazetteer was listed as merely `read` and is
|
|
182
|
+
in fact named by answers from five different tools. If you read an earlier
|
|
183
|
+
version of this page and concluded something was unavailable, re-check it here.
|
|
184
|
+
|
|
185
|
+
**And one row where this page is newer than the register.** BKG is `live` here, on
|
|
186
|
+
five measured answers, while `viafrei://attribution` carries it as planned —
|
|
187
|
+
"licence read, data not ingested yet". The answers decide it, and one part of an
|
|
188
|
+
answer decides it: `bkg_gvisys` appeared in `_meta.sources`, and only a row that
|
|
189
|
+
answered can put an id there. The attribution line came with it, which is
|
|
190
|
+
corroboration and not proof — that line is rendered from whichever ids a result
|
|
191
|
+
carries and knows nothing about whether the data is loaded. The flag is a server-side change and the server is
|
|
192
|
+
where it will be fixed.
|
|
193
|
+
|
|
194
|
+
**Do not read that as "one" being measured across the table.** It is the only
|
|
195
|
+
disagreement among the rows this page re-measured on 2026-09-27, which is not the
|
|
196
|
+
same statement. The nearest other candidate is the DELFI stop directory (zHV),
|
|
197
|
+
flagged the same way on the same day: we asked for a stop by name and the station
|
|
198
|
+
directory answered instead (`db_stada`, with BKG and GeoNames), which is consistent
|
|
199
|
+
with the flag and a long way from proof — stops and stations live in the same table
|
|
200
|
+
and the resolver ranks them, so a station hit can hide a stop row completely.
|
|
201
|
+
Asking a question is not reading the server's table, and this is reported as the
|
|
202
|
+
weaker thing it is. **What settles any of
|
|
203
|
+
these for you is the answer in your hand**: read `_meta.sources` and the
|
|
204
|
+
attribution lines on the result, never a status cell on a page that was printed
|
|
205
|
+
before you asked.
|
|
134
206
|
|
|
135
207
|
## The sources in detail
|
|
136
208
|
|
|
@@ -187,18 +259,30 @@ Share-alike. See [the obligation above](#public-transport-realtime-is-share-alik
|
|
|
187
259
|
what you derive from this stays CC BY-SA 4.0, and it may not be blended into a
|
|
188
260
|
result you publish under another licence.
|
|
189
261
|
|
|
190
|
-
**It
|
|
191
|
-
|
|
192
|
-
|
|
262
|
+
**It answers.** Asked for Hamburg on 2026-09-27, `check_transit_disruption`
|
|
263
|
+
returned a region-wide punctuality answer naming this feed. Until 2026-09-21 the
|
|
264
|
+
same call produced an internal error and this page said so; that is fixed, and
|
|
265
|
+
the paragraph is left here rather than deleted so a reader who saw the old one
|
|
266
|
+
knows which of the two is current.
|
|
267
|
+
|
|
268
|
+
What it answers is a **region**, not a line: see the scope note in the
|
|
269
|
+
README — no tool here answers "is the S1 on time".
|
|
193
270
|
|
|
194
271
|
Licence: <https://creativecommons.org/licenses/by-sa/4.0/> ·
|
|
195
272
|
publisher: <https://www.opendata-oepnv.de>
|
|
196
273
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
274
|
+
**Three** more DELFI datasets are cleared and not yet in use, and they do not
|
|
275
|
+
share one licence:
|
|
276
|
+
|
|
277
|
+
- the Germany-wide static timetable — `Fahrplandaten: DELFI e.V., CC BY 4.0, bearbeitet` — **CC BY 4.0**;
|
|
278
|
+
- the central stop directory — `Haltestellendaten: DELFI e.V. (zentrales Haltestellenverzeichnis), CC BY 4.0, bearbeitet` — **CC BY 4.0**;
|
|
279
|
+
- the disruption reports — `Störungsmeldungen: DELFI e.V. via Mobilithek, CC BY-SA 4.0` — **CC BY-SA 4.0, share-alike**.
|
|
280
|
+
|
|
281
|
+
This page said "two, both CC BY 4.0 — the share-alike is on the realtime feed"
|
|
282
|
+
until 2026-09-27, and both halves were wrong: there are three, and the
|
|
283
|
+
share-alike is on **two** DELFI feeds, the trip updates we serve today and the
|
|
284
|
+
disruption reports we do not serve yet. If you are planning for the day the
|
|
285
|
+
disruption feed appears, plan for share-alike, not for CC BY.
|
|
202
286
|
|
|
203
287
|
### Tankerkönig / MTS-K — fuel prices · CC BY 4.0 **plus a purpose limit**
|
|
204
288
|
|
|
@@ -226,9 +310,10 @@ The historical price archive published beside the API is licensed
|
|
|
226
310
|
**CC BY-NC-SA** — non-commercial. It is not a source for any answer this
|
|
227
311
|
service gives, and it never will be while that licence stands.
|
|
228
312
|
|
|
229
|
-
### Deutsche Bahn AG — timetables, stations, facilities
|
|
313
|
+
### Deutsche Bahn AG — timetables, stations, facilities, station car parks
|
|
230
314
|
|
|
231
|
-
|
|
315
|
+
**Four** products on the DB API Marketplace, each read at its own product page,
|
|
316
|
+
and they do **not** share one licence. The three we serve are CC BY 4.0:
|
|
232
317
|
|
|
233
318
|
```
|
|
234
319
|
Fahrplandaten: Deutsche Bahn AG, DB API Marketplace, CC BY 4.0, bearbeitet
|
|
@@ -236,6 +321,20 @@ Bahnhofsdaten: Deutsche Bahn AG, DB API Marketplace, CC BY 4.0, bearbeitet
|
|
|
236
321
|
Aufzüge und Fahrtreppen: Deutsche Bahn AG, DB API Marketplace, CC BY 4.0, bearbeitet
|
|
237
322
|
```
|
|
238
323
|
|
|
324
|
+
The fourth is read and not yet held — station car parks, under **Datenlizenz
|
|
325
|
+
Deutschland – Namensnennung – Version 2.0**, which is a different licence with a
|
|
326
|
+
different attribution line:
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
Parking Information Daten der DB BahnPark – API über den DB API Marketplace
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
It is in the table because this page is the register and a row is how we say a
|
|
333
|
+
licence has been read. It is `read`, so no answer carries that line today; the
|
|
334
|
+
line is here so that nobody has to go and find it on the day one does. Do not
|
|
335
|
+
assume the CC BY 4.0 line above covers it — one publisher, four products, two
|
|
336
|
+
licences.
|
|
337
|
+
|
|
239
338
|
The product pages say it in one sentence: *"Dieser Datensatz wird bereitgestellt
|
|
240
339
|
unter der Lizenz Creative Commons Attribution 4.0 International (CC BY 4.0)."*
|
|
241
340
|
DB adds one carve-out, and it is narrower than it looks: once the data has been
|
|
@@ -243,10 +342,16 @@ contributed to OpenStreetMap, a mention of Deutsche Bahn AG in the contributor
|
|
|
243
342
|
list is enough. That is a relaxation **for OSM**, not permission to drop the
|
|
244
343
|
line from your own results.
|
|
245
344
|
|
|
246
|
-
Facility status — the live state of lifts and escalators — **
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
345
|
+
Facility status — the live state of lifts and escalators — **answers on the
|
|
346
|
+
public service**: asked for Köln Messe/Deutz on 2026-09-27 it returned ten
|
|
347
|
+
facilities with their states, naming this feed and the station directory that
|
|
348
|
+
resolves the name to the number the feed is keyed by. Until the directory was
|
|
349
|
+
loaded the lookup stopped before it started, and this page said not to build on
|
|
350
|
+
it; that is no longer the case.
|
|
351
|
+
|
|
352
|
+
The station directory is still what the lookup depends on, so a station missing
|
|
353
|
+
from it produces "I cannot resolve that station" rather than an empty facility
|
|
354
|
+
list — the distinction matters if you are deciding whether to retry.
|
|
250
355
|
|
|
251
356
|
Publisher: <https://developers.deutschebahn.com>
|
|
252
357
|
|
|
@@ -308,26 +413,82 @@ nothing else.
|
|
|
308
413
|
|
|
309
414
|
Master data — where the posts are, which plug, how many kW — answers today.
|
|
310
415
|
**Live availability does not exist for most of Germany**, because only some
|
|
311
|
-
operators publish it
|
|
312
|
-
|
|
416
|
+
operators publish it. A result marks every such site `keine Statusdaten` and
|
|
417
|
+
closes with a sentence saying that this means unknown and not free — measured on
|
|
418
|
+
2026-09-27, asking for Leipzig. It does not count them for you, and this page
|
|
419
|
+
said it did until today; what it does is refuse to leave them out or to call them
|
|
420
|
+
free, which is the part that matters when you act on the answer.
|
|
313
421
|
|
|
314
|
-
### OpenStreetMap —
|
|
422
|
+
### OpenStreetMap — addresses and points of interest · ODbL 1.0
|
|
315
423
|
|
|
316
424
|
```
|
|
317
|
-
|
|
425
|
+
OSM-Standortdaten: © OpenStreetMap-Mitwirkende, ODbL 1.0
|
|
318
426
|
```
|
|
319
427
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
428
|
+
**Reproduce that line, not a shorter one.** This page printed
|
|
429
|
+
`Geokodierung: …` until 2026-09-27 and no answer has carried that prefix since the
|
|
430
|
+
release in which `find_poi` and `find_address` began returning an OSM row **as**
|
|
431
|
+
the answer — a name, a brand, a door — rather than only a coordinate resolved from
|
|
432
|
+
one. `Geokodierung` describes the narrower thing and would have been a false
|
|
433
|
+
statement about what the data was used for. If you copied the old string, change it.
|
|
434
|
+
|
|
435
|
+
**Which answers carry it**, in the platform's own words — verbatim, with the
|
|
436
|
+
qualifier emphasised here because it is the part two earlier versions of this
|
|
437
|
+
page dropped: *"Only results whose
|
|
438
|
+
input resolved through one of these two OSM tables carry this line. A result about
|
|
439
|
+
a place **from our own gazetteer**, a station or a motorway is not built from
|
|
440
|
+
OpenStreetMap and carries neither the ODbL attribution nor the obligation."*
|
|
441
|
+
|
|
442
|
+
Read the qualifier. The predicate is **which table answered**, and it is not a
|
|
443
|
+
list of tools. Place resolution falls through the gazetteer to the OSM tables, so
|
|
444
|
+
a tool that takes a `place` — a weather warning, a road status, a charging
|
|
445
|
+
station, a car park, `find_nearby` — returns an OSM-derived answer whenever the
|
|
446
|
+
gazetteer did not know the name. Measured on 2026-09-27: a weather warning for
|
|
447
|
+
`Zeiss-Großplanetarium` named `["dwd","osm"]` and carried this line; one for
|
|
448
|
+
`Allianz Arena` named `["osm"]` alone.
|
|
449
|
+
|
|
450
|
+
Two wrong versions of this sentence have now been caught, and both erred the same
|
|
451
|
+
way, towards telling a reader an obligation did not apply. **One of them shipped.**
|
|
452
|
+
1.3.12 said the line was carried "on every answer whose input was an address, and
|
|
453
|
+
on no other answer" — false for `find_poi`, and published. The second never
|
|
454
|
+
shipped: it was written while fixing the first and caught in review. It said "an
|
|
455
|
+
answer about a place, a station or a motorway does not", which drops the four
|
|
456
|
+
words that make it true and is false for every place the gazetteer could not
|
|
457
|
+
resolve. Two rounds of correcting one sentence is the reason the paragraph above
|
|
458
|
+
now states a predicate instead of listing tools or input shapes.
|
|
459
|
+
|
|
460
|
+
The positive test is the one to rely on: **read `_meta.sources` and the attribution
|
|
461
|
+
line on the answer you actually got.** Do not infer either from the shape of your
|
|
462
|
+
question.
|
|
463
|
+
|
|
464
|
+
Address data is imported **per deployment**, and the public service has it:
|
|
465
|
+
asked for a house number on 2026-09-27 it answered with the address and this
|
|
466
|
+
line. **So the line and the obligation apply to what you get from the public
|
|
467
|
+
service today.** How complete that import is was measured the same day from the
|
|
468
|
+
service's own per-Land verdict — which reads both tables, names any missing Land and exits
|
|
469
|
+
non-zero on one — rather than extrapolated from a single lookup: **all sixteen
|
|
470
|
+
Länder are imported, and every row is scoped to the Land it came from.** Two
|
|
471
|
+
address lookups in the two Länder least likely to have been staged —
|
|
472
|
+
Mecklenburg-Vorpommern and Saarland, asked in Rostock and Saarbrücken — both
|
|
473
|
+
answered through the public endpoint with `osm` in `_meta.sources`, over OSM data
|
|
474
|
+
dated 2026-09-22. No row count is written here on
|
|
475
|
+
purpose: it would be true the day it was typed and stale at the next import, and
|
|
476
|
+
reporting it is the verdict's job rather than this page's. One caveat, because it
|
|
477
|
+
nearly misled this very measurement — **a single address that does not resolve
|
|
478
|
+
says nothing about its Land**: a market-square house number in Erfurt came back
|
|
479
|
+
unfound from a Land that is fully imported, because that address is not in
|
|
480
|
+
OpenStreetMap under the spelling it was asked for. (The postcode is left out on
|
|
481
|
+
purpose - the leak sweep refuses a bare five-digit number in this repository, and
|
|
482
|
+
an allow-list entry added to carry an example would be a caption pointing at a
|
|
483
|
+
value.) Until the extract was loaded they did not, and this page said
|
|
484
|
+
so — the correction is the substantive one in this release.
|
|
485
|
+
|
|
486
|
+
A deployment that has not imported the extract answers that it can find places,
|
|
325
487
|
stations and motorways but not house numbers, which is the right answer rather
|
|
326
|
-
than a guessed coordinate.
|
|
327
|
-
|
|
328
|
-
extract, both apply in full.
|
|
488
|
+
than a guessed coordinate. There, neither this line nor the obligation arises,
|
|
489
|
+
because no OSM-derived answer is produced.
|
|
329
490
|
|
|
330
|
-
Our ODbL § 4.6 offer is [above](#and-one-that-applies-
|
|
491
|
+
Our ODbL § 4.6 offer is [above](#and-one-that-applies-if-you-get-an-openstreetmap-derived-result-back).
|
|
331
492
|
|
|
332
493
|
Licence: <https://opendatacommons.org/licenses/odbl/1-0/> ·
|
|
333
494
|
copyright: <https://www.openstreetmap.org/copyright>
|
|
@@ -440,10 +601,15 @@ Leaving a dataset out is as much a part of "we use all legal ways" as using one:
|
|
|
440
601
|
- The dates in this page are the dates the relevant page was read, not the dates
|
|
441
602
|
it was written.
|
|
442
603
|
- **The statuses are measured, not declared.** Each one was checked on
|
|
443
|
-
2026-09-
|
|
444
|
-
the answer named
|
|
445
|
-
|
|
446
|
-
|
|
604
|
+
2026-09-27 by asking the public endpoint a question and reading which source
|
|
605
|
+
the answer named — except the fuel row, which says `not re-measured` for the
|
|
606
|
+
reason given above. That is why **three** rows say a source is loaded but not
|
|
607
|
+
confirmed by an answer where the register says the licence is cleared — EnBW
|
|
608
|
+
static, the Tesla/VW availability feeds, and the police traffic events, which
|
|
609
|
+
moved into that state in this release: cleared is not live, and a page that
|
|
610
|
+
blurred the two would be the one thing this page exists not to be. The number is
|
|
611
|
+
written here because it is small enough to count; if it stops matching the table,
|
|
612
|
+
the table is right.
|
|
447
613
|
- Where a source's terms are unknown or unreadable, this page says so instead of
|
|
448
614
|
rounding it up to "open data" — the motorway interface above is named on every
|
|
449
615
|
answer for exactly that reason, and the datasets in the section before this
|
package/dist/config.d.ts
CHANGED
|
@@ -13,6 +13,16 @@ export declare const DEFAULT_MCP_URL: string;
|
|
|
13
13
|
export declare const URL_ENV_VAR = "VIAFREI_MCP_URL";
|
|
14
14
|
/** Environment variable that overrides the request timeout, in milliseconds. */
|
|
15
15
|
export declare const TIMEOUT_ENV_VAR = "VIAFREI_MCP_TIMEOUT_MS";
|
|
16
|
+
/**
|
|
17
|
+
* Environment variable that adds HTTP headers, for an API key an MCP client
|
|
18
|
+
* cannot pass as a flag. Several headers are separated by a NEWLINE, because a
|
|
19
|
+
* newline can never appear in a header name or value, so no value is
|
|
20
|
+
* unrepresentable: a comma, a semicolon and a space all occur inside real header
|
|
21
|
+
* values, and any of those as the separator would make something unsendable.
|
|
22
|
+
*/
|
|
23
|
+
export declare const HEADER_ENV_VAR = "VIAFREI_MCP_HEADER";
|
|
24
|
+
/** The separator between headers in HEADER_ENV_VAR. See HEADER_ENV_VAR. */
|
|
25
|
+
export declare const HEADER_ENV_SEPARATOR = "\n";
|
|
16
26
|
/** How long a single HTTP request may take before it is given up on. */
|
|
17
27
|
export declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
18
28
|
/**
|
package/dist/config.js
CHANGED
|
@@ -13,6 +13,18 @@ export const DEFAULT_MCP_URL = new URL(DEFAULT_MCP_PATH, DEFAULT_MCP_ORIGIN).toS
|
|
|
13
13
|
export const URL_ENV_VAR = 'VIAFREI_MCP_URL';
|
|
14
14
|
/** Environment variable that overrides the request timeout, in milliseconds. */
|
|
15
15
|
export const TIMEOUT_ENV_VAR = 'VIAFREI_MCP_TIMEOUT_MS';
|
|
16
|
+
/**
|
|
17
|
+
* Environment variable that adds HTTP headers, for an API key an MCP client
|
|
18
|
+
* cannot pass as a flag. Several headers are separated by a NEWLINE, because a
|
|
19
|
+
* newline can never appear in a header name or value, so no value is
|
|
20
|
+
* unrepresentable: a comma, a semicolon and a space all occur inside real header
|
|
21
|
+
* values, and any of those as the separator would make something unsendable.
|
|
22
|
+
*/
|
|
23
|
+
export const HEADER_ENV_VAR = 'VIAFREI_MCP_HEADER';
|
|
24
|
+
/** The separator between headers in HEADER_ENV_VAR. See HEADER_ENV_VAR. */
|
|
25
|
+
export const HEADER_ENV_SEPARATOR = '\n';
|
|
26
|
+
/** Column width of the `Environment:` block in `helpText`. See its use there. */
|
|
27
|
+
const ENV_NAME_WIDTH = Math.max(URL_ENV_VAR.length, TIMEOUT_ENV_VAR.length, HEADER_ENV_VAR.length);
|
|
16
28
|
/** How long a single HTTP request may take before it is given up on. */
|
|
17
29
|
export const DEFAULT_TIMEOUT_MS = 30_000;
|
|
18
30
|
/**
|
|
@@ -45,18 +57,23 @@ const RESERVED_HEADERS = new Set([
|
|
|
45
57
|
'content-length',
|
|
46
58
|
'host'
|
|
47
59
|
]);
|
|
48
|
-
|
|
60
|
+
/**
|
|
61
|
+
* `source` is what the reader typed — `--header` or the name of the environment
|
|
62
|
+
* variable — because an error that names the wrong one sends them to the wrong
|
|
63
|
+
* place to fix it.
|
|
64
|
+
*/
|
|
65
|
+
function parseHeader(raw, source = '--header') {
|
|
49
66
|
const separator = raw.indexOf(':');
|
|
50
67
|
if (separator < 1) {
|
|
51
|
-
throw new UsageError(
|
|
68
|
+
throw new UsageError(`${source} expects "Name: value", got ${JSON.stringify(raw)}`);
|
|
52
69
|
}
|
|
53
70
|
const name = raw.slice(0, separator).trim();
|
|
54
71
|
const value = raw.slice(separator + 1).trim();
|
|
55
72
|
if (name === '') {
|
|
56
|
-
throw new UsageError(
|
|
73
|
+
throw new UsageError(`${source} expects "Name: value", got ${JSON.stringify(raw)}`);
|
|
57
74
|
}
|
|
58
75
|
if (RESERVED_HEADERS.has(name.toLowerCase())) {
|
|
59
|
-
throw new UsageError(
|
|
76
|
+
throw new UsageError(`${source}: ${name} is set by the transport and cannot be overridden`);
|
|
60
77
|
}
|
|
61
78
|
return [name, value];
|
|
62
79
|
}
|
|
@@ -102,6 +119,19 @@ export function parseOptions(argv, env = process.env) {
|
|
|
102
119
|
if (timeoutFromEnv !== undefined && timeoutFromEnv.trim() !== '') {
|
|
103
120
|
options.timeoutMs = parseTimeout(timeoutFromEnv.trim(), TIMEOUT_ENV_VAR);
|
|
104
121
|
}
|
|
122
|
+
// Before the argv loop, so a --header of the same name overwrites this one
|
|
123
|
+
// and a --header of a different name joins it — the same precedence --url
|
|
124
|
+
// has over VIAFREI_MCP_URL, established by position rather than by a rule.
|
|
125
|
+
const headersFromEnv = env[HEADER_ENV_VAR];
|
|
126
|
+
if (headersFromEnv !== undefined && headersFromEnv.trim() !== '') {
|
|
127
|
+
for (const line of headersFromEnv.split(HEADER_ENV_SEPARATOR)) {
|
|
128
|
+
if (line.trim() === '') {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const [name, value] = parseHeader(line, HEADER_ENV_VAR);
|
|
132
|
+
options.headers[name] = value;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
105
135
|
for (let index = 0; index < argv.length; index += 1) {
|
|
106
136
|
const argument = argv[index];
|
|
107
137
|
const next = () => {
|
|
@@ -163,8 +193,11 @@ export function helpText(version) {
|
|
|
163
193
|
' -h, --help print this text and exit',
|
|
164
194
|
'',
|
|
165
195
|
'Environment:',
|
|
166
|
-
|
|
167
|
-
|
|
196
|
+
// Padded from the names rather than by hand: three variables went ragged
|
|
197
|
+
// the first time this block was written, and the next one added would too.
|
|
198
|
+
` ${URL_ENV_VAR.padEnd(ENV_NAME_WIDTH)} same as --url`,
|
|
199
|
+
` ${HEADER_ENV_VAR.padEnd(ENV_NAME_WIDTH)} same as --header; separate several with a newline`,
|
|
200
|
+
` ${TIMEOUT_ENV_VAR.padEnd(ENV_NAME_WIDTH)} same as --timeout`,
|
|
168
201
|
'',
|
|
169
202
|
'Exit codes:',
|
|
170
203
|
` ${EXIT.OK} clean shutdown ${EXIT.UNEXPECTED} unexpected error`,
|
package/dist/failure.d.ts
CHANGED
|
@@ -14,7 +14,16 @@ export interface Failure {
|
|
|
14
14
|
/** HTTP status, when the endpoint answered at all. */
|
|
15
15
|
status?: number;
|
|
16
16
|
}
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* True when the failure is worth exactly one more attempt.
|
|
19
|
+
*
|
|
20
|
+
* Deliberately NOT 429, although `src/fetch.ts` does retry a 429 response. The
|
|
21
|
+
* difference is that there we still hold the response and can read
|
|
22
|
+
* `Retry-After`; here we hold only a thrown error, so a retry would happen after
|
|
23
|
+
* the fixed delay — which for a rate limit is the thing that makes it worse
|
|
24
|
+
* rather than better. A 429 that reaches this function is left to fail with its
|
|
25
|
+
* own line.
|
|
26
|
+
*/
|
|
18
27
|
export declare function isRetryable(error: unknown): boolean;
|
|
19
28
|
/** Raised by the fetch wrapper when our own timeout fired. */
|
|
20
29
|
export declare class RequestTimeoutError extends Error {
|
package/dist/failure.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { StreamableHTTPError } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
2
|
-
import { EXIT } from './config.js';
|
|
2
|
+
import { DEFAULT_MCP_PATH, EXIT } from './config.js';
|
|
3
3
|
const STATUS_TEXT = {
|
|
4
4
|
400: 'Bad Request',
|
|
5
5
|
401: 'Unauthorized',
|
|
@@ -46,7 +46,16 @@ function errorCode(error) {
|
|
|
46
46
|
}
|
|
47
47
|
return undefined;
|
|
48
48
|
}
|
|
49
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* True when the failure is worth exactly one more attempt.
|
|
51
|
+
*
|
|
52
|
+
* Deliberately NOT 429, although `src/fetch.ts` does retry a 429 response. The
|
|
53
|
+
* difference is that there we still hold the response and can read
|
|
54
|
+
* `Retry-After`; here we hold only a thrown error, so a retry would happen after
|
|
55
|
+
* the fixed delay — which for a rate limit is the thing that makes it worse
|
|
56
|
+
* rather than better. A 429 that reaches this function is left to fail with its
|
|
57
|
+
* own line.
|
|
58
|
+
*/
|
|
50
59
|
export function isRetryable(error) {
|
|
51
60
|
if (error instanceof StreamableHTTPError) {
|
|
52
61
|
return error.code === 502 || error.code === 503 || error.code === 504;
|
|
@@ -125,7 +134,7 @@ export function describeFailure(error, url) {
|
|
|
125
134
|
const detail = extractServerDetail(error.message);
|
|
126
135
|
const suffix = detail === undefined ? '' : ` - ${detail}`;
|
|
127
136
|
return {
|
|
128
|
-
line: `viafrei: ${url} refused the request: HTTP ${status} ${name}${suffix}`,
|
|
137
|
+
line: `viafrei: ${url} refused the request: HTTP ${status} ${name}${suffix}${pathHint(status, url)}`,
|
|
129
138
|
exitCode: EXIT.REFUSED,
|
|
130
139
|
status
|
|
131
140
|
};
|
|
@@ -156,6 +165,45 @@ export function describeFailure(error, url) {
|
|
|
156
165
|
exitCode: EXIT.UNEXPECTED
|
|
157
166
|
};
|
|
158
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* The one hint this file gives, and the reason it is a hint and not a fix.
|
|
170
|
+
*
|
|
171
|
+
* `--url http://127.0.0.1:3000` is the mistake a self-hoster makes on their
|
|
172
|
+
* first try: the MCP endpoint is at a path, so a pathless URL gets a bare 404
|
|
173
|
+
* that is accurate and useless. We know enough to say something here.
|
|
174
|
+
*
|
|
175
|
+
* We do NOT append the path. Quietly rewriting what somebody typed hides a
|
|
176
|
+
* different mistake later: if the server really is at `/`, a silent rewrite
|
|
177
|
+
* sends the request somewhere they never asked for, and the 404 they would then
|
|
178
|
+
* get back would be about a URL that is not in their configuration. So the hint
|
|
179
|
+
* is a clause on the same line, and the URL in the message stays the URL we
|
|
180
|
+
* actually tried.
|
|
181
|
+
*
|
|
182
|
+
* It appears only when it is warranted. A 404 on a URL that already has a path
|
|
183
|
+
* means something else — wrong path, wrong service, a proxy route that is gone —
|
|
184
|
+
* and guessing there is noise. A 403 or a 500 never carries it, whatever the
|
|
185
|
+
* path.
|
|
186
|
+
*
|
|
187
|
+
* The parse is inside a `try` because a failure message that itself throws is
|
|
188
|
+
* the worst version of this bug: `describeFailure` is on the path where
|
|
189
|
+
* everything has already gone wrong, and it must always produce a line.
|
|
190
|
+
*/
|
|
191
|
+
function pathHint(status, url) {
|
|
192
|
+
if (status !== 404) {
|
|
193
|
+
return '';
|
|
194
|
+
}
|
|
195
|
+
let path;
|
|
196
|
+
try {
|
|
197
|
+
path = new URL(url).pathname;
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return '';
|
|
201
|
+
}
|
|
202
|
+
if (path !== '' && path !== '/') {
|
|
203
|
+
return '';
|
|
204
|
+
}
|
|
205
|
+
return ` - the URL has no path; the MCP endpoint is usually ${DEFAULT_MCP_PATH}`;
|
|
206
|
+
}
|
|
159
207
|
/**
|
|
160
208
|
* The SDK wraps the response body into the error message. Keep a short, single
|
|
161
209
|
* line of it - it is often the only thing that says *why* - and drop the rest.
|