viafrei 1.3.12 → 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 CHANGED
@@ -3,6 +3,314 @@
3
3
  All notable changes to this package are documented here. The format follows Keep
4
4
  a Changelog and the versions follow Semantic Versioning.
5
5
 
6
+ ## [1.3.15] - 2026-09-27
7
+
8
+ The first release with features in it since the bridge got code. Three
9
+ capabilities, and the documentation caught up with a service that had quietly
10
+ grown past it.
11
+
12
+ **If you only use `npx -y viafrei`, nothing changes and nothing breaks.** No flag
13
+ was removed, no default moved, no exit code changed meaning. The three additions
14
+ are opt-in or invisible.
15
+
16
+ ### Added
17
+
18
+ - **A contributor surface: `CODE_OF_CONDUCT.md`, three issue templates with a
19
+ chooser, and a pull request template.** The Code of Conduct is Contributor
20
+ Covenant 2.1, and a report with no GitHub account goes to the Impressum e-mail
21
+ rather than to the **postal** address printed there — that address is still a
22
+ visible placeholder, and a reporting route has to be one that works. The three
23
+ forms are a bug, an idea, and the one report this service cannot make about
24
+ itself: a tool answered and the answer was useless. Beside them the chooser
25
+ carries the three routes that are not forms — the private security advisory,
26
+ Discussions, and `SOURCES.md` for a data or licence question. None of these
27
+ existed in 1.3.12.
28
+ - **`VIAFREI_MCP_HEADER` sets HTTP headers from the environment.** Many MCP
29
+ clients can only give a command an `env` block, never arguments, which left
30
+ those users no way to send a header at all. It takes the same `Name: value`
31
+ syntax as `--header`, refuses the same transport-owned names, and separates
32
+ several headers with a **newline**, which can never appear in a header name or
33
+ value, so nothing you might need to send is unrepresentable - a comma, a
34
+ semicolon and a space all occur inside real header values. A `--header` of the
35
+ same name wins; one of a different name is added alongside. An error names
36
+ `VIAFREI_MCP_HEADER`, not the flag you did not type.
37
+ - **`Retry-After` is honoured on the single retry, with a cap.** Both legal forms
38
+ are read - a number of seconds and an HTTP-date - and a value that cannot be
39
+ parsed, or names a moment already past, falls back to the fixed delay instead
40
+ of throwing. **The cap is the per-request timeout**: a server answering
41
+ `Retry-After: 3600` does not make `npx viafrei` sit silently for an hour; the
42
+ retry is abandoned and the ordinary one-line failure, which already names the
43
+ URL and the status, stands. `429` is retried now that the delay it asks for is
44
+ respected - and **only when the header gives a usable delay**. A 429 carrying
45
+ nothing readable is not retried at all, because the only thing left would be the
46
+ fixed delay, and a fixed delay is what makes a rate limit worse. For the same
47
+ reason a *thrown* 429 is still not retryable: there the header is gone. A 503 in
48
+ the same state IS retried, and a test pins the difference so it cannot be
49
+ flattened by accident.
50
+ - **A pathless `--url` that gets a 404 is told where the endpoint usually is.**
51
+ `--url http://127.0.0.1:3000` now fails with `... HTTP 404 Not Found - the URL
52
+ has no path; the MCP endpoint is usually /mcp`. **It is a hint and not a
53
+ rewrite**: quietly appending the path would send the request somewhere nobody
54
+ asked for and hide a different mistake later. The hint appears only when the
55
+ path is empty or `/`, never on another status, and a URL that will not parse
56
+ skips the hint rather than making the failure message itself throw.
57
+
58
+ ### Changed
59
+
60
+ - **The README leads with how to connect, and says why to build on it.** The page
61
+ opened by explaining that you probably do not need this package, which is true
62
+ and was the first thing a developer read. The three transports now come first,
63
+ with the address to use, and a section sets out what the service is actually
64
+ offering: one protocol in place of twenty-one sources from sixteen publishers,
65
+ the licence obligations travelling with each answer, machine-readable failures,
66
+ and answers in the language of the question.
67
+ - **The tool count was wrong and is now a capability table.** The page said
68
+ thirteen tools; the server registers **eighteen**. The five geocoding tools had
69
+ shipped on the platform before the previous bridge release was even cut. The
70
+ table groups tools by the question they answer, which is stable, and the page
71
+ still refuses to paste the catalogue itself - a published tarball cannot be
72
+ corrected, so `tools/list` on the running server remains the only source of
73
+ truth.
74
+ - **The one completeness claim the new table introduced was measured before it
75
+ shipped.** The Places row says "nationwide, all sixteen Länder", which no tool
76
+ reports and which would rot at a partial import - the same objection that removed
77
+ the two address figures under **Fixed** ("Two address figures nobody could check
78
+ are gone"). It was kept because it was measured
79
+ rather than inferred, and it now carries the date: on 2026-09-27 the service's own
80
+ per-Land verdict - which reads both OSM tables, names any missing Land and exits
81
+ non-zero on one - reported all sixteen imported with every row scoped to its Land,
82
+ and two address lookups in the two Länder least likely to have been staged —
83
+ Mecklenburg-Vorpommern and Saarland, asked in Rostock and Saarbrücken — both
84
+ answered through the public endpoint with `osm` in `_meta.sources`. `SOURCES.md` carries the measurement, and one caveat that nearly
85
+ misled it: a single address that does not resolve says nothing about its Land.
86
+ - **The README now says that a slow call may be a retried call.** A `429`, `502`,
87
+ `503` or `504` is tried again **once** after the delay the server asked for, as
88
+ is a connection that failed outright; a `429` with no usable `Retry-After`, and
89
+ any delay longer than the per-request timeout, are not retried at all. The page
90
+ also now says what that timeout does and does not bound, because the first draft
91
+ of the paragraph got it wrong in the direction a user would act on: it is attached
92
+ per HTTP request, so the second attempt gets a fresh one and so does each hop of a
93
+ same-origin redirect, and **no setting here caps the whole call**. Two drafts of
94
+ that one clause were wrong, in the same direction and in the sentence a reader
95
+ would act on: the first said a retried call "can never exceed the timeout you gave
96
+ it", and the correction that replaced it said to halve the timeout for a hard
97
+ ceiling — but two attempts plus a delay that may itself be as long as the timeout
98
+ is three times it, not two, and more again if the endpoint redirects. Both were
99
+ caught in review rather than published, which is the argument for rounds that cost
100
+ nothing, and the second is the sharper lesson: a corrected number is still a
101
+ number, and nothing was reading it. That behaviour shipped earlier and the
102
+ page did not mention it, so a user watching a call take longer than expected had
103
+ nothing to read. Nothing about the behaviour changed in this release.
104
+ - **Both examples on the page are now captured output rather than plausible
105
+ output.** The departures block is a real answer from `mcp.viafrei.de` on
106
+ 2026-09-27, and the failure line is copied from a run.
107
+ - **The badge hosts are admitted to the leak ruleset as their own group, with
108
+ their one cost written down.** A shields.io badge is the only entry on that
109
+ allow-list that causes a request from somebody else's browser, so the ruleset
110
+ now says that, says the admission test, and says it is the reason to refuse the
111
+ next image host rather than to admit it by precedent. The list itself stays
112
+ sorted alphabetically rather than grouped, and the ruleset now says why: one flat
113
+ sorted list is what makes a duplicate, or a near-miss spelling of a host already
114
+ on it, visible at a glance.
115
+ - **`numbers.allowed` is back to six entries, and the two rules governing it are
116
+ separated.** A seventh was added during this release to accommodate two bare
117
+ millisecond literals, then removed once both were written `1_000`: an entry that
118
+ covers nothing in the tree is a caption pointing at a value, which is the one
119
+ thing the inverse construction exists to avoid. (The entry is not named here,
120
+ because naming it would put the bare form back in the tree and the sweep would
121
+ be right to say so — it said so about the first draft of this very bullet.) The ruleset now distinguishes **the rule** (an
122
+ entry occurs in the tree and is not an internal value — which `3600` and `9110`,
123
+ both prose in a comment, satisfy) from **the preference** (write a numeric
124
+ separator where one is available, so the literal never reaches the list at all).
125
+ Citing the second as if it were the first is what made the list look arbitrary
126
+ for one review round.
127
+ - **Two `allowedHosts` entries covering nothing are gone.** Bare `shields.io` and
128
+ bare `contributor-covenant.org` sat beside `img.shields.io` and
129
+ `www.contributor-covenant.org`, and no URL in the tree uses either; the bare
130
+ spellings occur only in prose about them, and the host extractor reads a
131
+ scheme-prefixed URL and nothing else. That is the same shape as the
132
+ allow-list number this release removed: an entry that permits nothing is a
133
+ permission nobody can audit. The history-residue block is also back to one line
134
+ per entry; reformatting it to five four-line objects changed no meaning and cost
135
+ twenty-five lines of diff in the file a reviewer reads hardest.
136
+ - **A re-wrap put a private name into a public file, and the sweep caught it —
137
+ which is also a demonstration of the one limit it declares loudest.** The gate
138
+ reads **one line at a time**, and says so in its own limits list. A paragraph
139
+ of this release's prose was re-flowed, and prose that had passed every earlier
140
+ run matched a live entry the moment the wrapping changed. Nothing was ever
141
+ published: it reached a working tree and no further. What the offending text
142
+ was is deliberately not recorded — `scripts/rules.json` says to record the
143
+ rule, the lengths and the narrowing and never the subject, because a
144
+ description narrows the candidate space for a live entry further than the hash
145
+ alone does, and this file cannot be unpublished. The lesson is the gate's, not
146
+ the prose's — a line-oriented scanner's verdict depends on where the wrapping
147
+ falls, so its PASS is about this wrapping and not about this text, and a
148
+ re-wrap is a change the sweep has to see again.
149
+
150
+ ### Fixed
151
+
152
+ - **`package-lock.json` still said 1.3.12 while `package.json` said 1.3.15, and
153
+ nothing in this repository would have noticed.** Found in the last pre-push check
154
+ of the release, by reading the two files rather than the diff: seven review rounds
155
+ had confirmed the lockfile was *unchanged*, which is true and is not the same as
156
+ correct. The publish workflow asserts that the TAG and `package.json` agree and
157
+ says so in its own step name; no gate compares the lockfile to either, and the
158
+ lockfile is not in the published tarball, so the only thing that reads it is the
159
+ `npm ci` the release build runs. It is now 1.3.15, regenerated with
160
+ `--package-lock-only` so the change is exactly the two version fields and no
161
+ dependency moved. **The missing gate is deliberately not added here**: it belongs
162
+ in `publish.yml`, and an untested edit to the workflow that publishes, inside the
163
+ commit it publishes from, is the wrong trade a day before the tag — it is filed
164
+ for the next release and rehearsed through the `workflow_dispatch` dry run, beside
165
+ the two other workflow items this release also deferred.
166
+ - **SOURCES.md described a service three capabilities smaller than the one that
167
+ is running.** Address lookup, lift and escalator status, and public-transport
168
+ realtime were all documented as not answering on the public service. All three
169
+ answer, and each was re-confirmed by a call whose result named the source. The
170
+ BKG administrative gazetteer was listed as merely licence-read and is in fact
171
+ named by answers from five different tools. Every status in the catalogue was
172
+ re-measured on 2026-09-27 by calling **fifteen of the server's sixteen
173
+ read-only tools** - every one except `find_cheapest_fuel` - once each and
174
+ reading `_meta.sources`. That one exception is **fuel**, which is deliberately not
175
+ re-measured because MTS-K sets a per-station floor and its terms make needless
176
+ querying a risk to the access itself. That row is labelled `not re-measured`
177
+ rather than dressed up as today's measurement.
178
+ - **SOURCES.md contradicted itself, and one of the contradictions was a licence
179
+ statement.** The table was re-measured; three paragraphs beneath it were not,
180
+ and they still told a reader that address lookup, facility status and
181
+ public-transport realtime do not answer. The worst of them sat in the ODbL
182
+ obligations section and said a share-alike obligation *"binds nobody using that
183
+ service right now"* - measurably false, on the one page whose job is to state
184
+ obligations, inside a tarball that cannot be corrected after publication. Every
185
+ such paragraph now states today's measurement and says which of the two
186
+ versions a reader saw. The status legend lost the value no row carries any more,
187
+ rather than leaving a status somebody could still be relying on.
188
+ - **The README promised rail disruptions "on a line or at a stop".** The server
189
+ declares the opposite in its own tool description and in `viafrei://coverage`:
190
+ it never answers whether a named line, trip or stop is on time. The row now says
191
+ region-wide and says never one line or stop.
192
+ - **Two address figures nobody could check are gone.** The Places row quoted a
193
+ count of addresses and POIs that no tool reports and that would rot at the next
194
+ import - the exact argument the paragraph below it makes against pasting a
195
+ catalogue.
196
+ - **The departures example names both of its edits.** It said the only edit was
197
+ shortening two URIs; five of the ten departures were dropped as well.
198
+ - **The OSM attribution line printed in `SOURCES.md` was not the line the server
199
+ emits.** The code fence said `Geokodierung: …`; no answer has carried that prefix
200
+ since `find_poi` and `find_address` began returning an OSM row **as** the answer
201
+ — a name, a brand, a door — rather than only a coordinate resolved from one. The
202
+ correct line is `OSM-Standortdaten: © OpenStreetMap-Mitwirkende, ODbL 1.0`, and
203
+ 1.3.12 carried it nowhere: the page was wrong about the one string it exists to
204
+ publish, inside a tarball that cannot be corrected. A draft of this release then
205
+ printed both spellings at once, which is how it was caught. Every other fenced
206
+ attribution line was swept against the server's register; this was the only
207
+ mismatch.
208
+ - **"An answer about a place does not carry the ODbL line" — three words missing,
209
+ and the sentence reverses.** That wording is this release's own: 1.3.12 carried a
210
+ different wrong version of the same sentence (see the bullet below), and this one
211
+ was written while fixing that one and caught in review. The platform's own
212
+ wording is "a result about a
213
+ place **from our own gazetteer**, a station or a motorway is not built from
214
+ OpenStreetMap". Place resolution falls through the gazetteer to `osm_pois` and
215
+ then `osm_addresses`, so **every** tool that takes a `place` can return an
216
+ OSM-derived answer: measured on 2026-09-27, a weather warning for
217
+ `Zeiss-Großplanetarium` named `["dwd","osm"]` and carried the line, and one for
218
+ `Allianz Arena` named `["osm"]` alone. Both places on this page now carry the
219
+ qualifier and state the predicate — **which table answered**, not which tool
220
+ was called. Two wrong versions of this sentence have now been caught, and both
221
+ erred towards telling a reader an obligation did not apply to them. The last
222
+ round found the same error in the two places nobody re-reads after fixing a
223
+ paragraph: the **section heading** and its opening sentence still framed the
224
+ obligation as something that applies "if you get an address or a point of
225
+ interest back", so a reader who scans headings could conclude the section was
226
+ not theirs. Heading, lead and both bodies now state the predicate, and the
227
+ back-reference to the section was moved in the same edit as the heading.
228
+ - **Twenty-one sources, not nineteen — and the table was missing a row it needs
229
+ to be the register it claims to be.** This release added the station car parks
230
+ row and recorded it in its own bullet below, without correcting the number it
231
+ invalidated; the review
232
+ then found the DELFI disruption feed has a licence row in the server's register
233
+ and no row here. It is in the table now, `read`, at **CC BY-SA 4.0**. Re-counted
234
+ by hand: 21 rows, 16 distinct publisher cells. The README's dare — "count them
235
+ in that table" — is a good one and now survives being taken up.
236
+ - **The cleared-but-unused DELFI sentence was wrong in both halves, and it is a
237
+ licence statement.** It said "two more DELFI datasets … both CC BY 4.0 — the
238
+ share-alike is on the realtime feed". There are **three**, and two of them are
239
+ share-alike: the trip updates we serve and the disruption reports we do not.
240
+ Anybody planning for the day the disruption feed appears was being told to plan
241
+ for CC BY.
242
+ - **The station car parks row had no attribution line, on a page that promises one
243
+ per source** — a defect this release created and closed. 1.3.12 has no car-parks
244
+ row anywhere, and its "three products" sentence was correct for what it listed;
245
+ adding the row, in the bullet that raised the source count, made that sentence wrong, so the Deutsche Bahn
246
+ section said "three products · CC BY 4.0" over four products and two licences. Both fixed, with the BahnPark attribution
247
+ line written out — `read`, so no answer carries it today, and it is here so
248
+ nobody has to go looking on the day one does.
249
+ - **"No tool exposes the police traffic events yet" was a claim about the code,
250
+ and the code says otherwise.** That sentence is also this release's own — 1.3.12
251
+ carries the row at `read` and says nothing about reach. The road analysis already reads that feed, and
252
+ whether a source is NAMED is gated on a live catalogue row, so the row could
253
+ start carrying its attribution line with no release at all. The page now says
254
+ what it measured — `check_road_status` on the A40, A3, A1, A57 and A46 named
255
+ only the motorway interface and the BASt roadworks feed — and says out loud
256
+ that this is a statement about answers and not about reach. The row therefore
257
+ reads `in the service` and not `read`, and the page says not to design around
258
+ it.
259
+ - **Two dead intra-document anchors, both created by this release's own
260
+ corrections.** The `no API key` badge — on the npm package page and the
261
+ repository front page — pointed at `#quick-start`, a heading this release
262
+ renamed to "Connect in one line"; and `SOURCES.md`'s back-reference to the
263
+ § 4.6 offer kept the old slug of a heading the previous review round renamed.
264
+ Every intra-document link in the tree was checked; these were the only two, and
265
+ both were ours. A later round found the shape that sweep could not see: an
266
+ issue **form** renders at `/issues/new?template=idea.yml`, so the relative
267
+ `../../blob/main/SOURCES.md` in it resolved one level short of the repository
268
+ and 404ed. It is an absolute URL now, like the one `config.yml` already used
269
+ for the same document.
270
+ - **The one place where `SOURCES.md` is now NEWER than the server's register is
271
+ named on the page.** BKG is `live` there on five measured answers, while
272
+ `viafrei://attribution` still flags it as planned — "licence read, data not
273
+ ingested yet". The page's own rule is that the resource wins and the page is
274
+ stale; that rule is right in general and wrong for this row today, so both the
275
+ rule and the row now say so. No bridge release can close it: it is tracked where
276
+ the server is developed, and what a reader of the published page can act on is
277
+ the answer itself - `_meta.sources` and the attribution lines it carries.
278
+ - **The charging answer does not count the sites without a status.** The page said
279
+ "a result says how many nearby sites had no status". It marks each one
280
+ `keine Statusdaten` and closes by saying that means unknown and not free —
281
+ measured, asking for Leipzig. What it refuses to do is leave them out or call
282
+ them free, which is the part that matters when you act on the answer.
283
+ - **The test for "which answers carry the ODbL line" was the question, and it
284
+ should have been the answer.** `SOURCES.md` said the line is carried "on every
285
+ answer whose input was an address, and on no other answer". `find_poi` asked for
286
+ a name and a city returns `_meta.sources: ["osm"]` and the line — so a reader
287
+ using that sentence to decide whether a share-alike obligation had arisen would
288
+ have concluded it had not. The rule is now stated as the platform states it, and
289
+ the page says to read `_meta.sources` on the answer rather than infer anything
290
+ from the shape of the question.
291
+ - **Our own § 4.6 offer was described more narrowly than it is.** It covers **both**
292
+ ODbL databases — addresses and points of interest, one file each under a single
293
+ licence notice — and the page named only the addresses. A recipient who derived
294
+ from POI results is entitled to the POI database.
295
+ - **The README promised car parking the public service does not answer.** Asked for
296
+ a `car_park` in Köln, Hamburg and Leipzig on 2026-09-27, every facility returned
297
+ was a lorry park or unclassified, and none carried occupancy. The row now says
298
+ lorry parking and a note says what is missing and why — the same over-claim as
299
+ the rail row, one row up, in the table this release rewrote to stop over-claiming.
300
+ - **The count of unconfirmed rows said two and there are three** — in a draft of
301
+ this release. 1.3.12 said **four**, counting a different set under a legend this
302
+ release replaced: under its own legend five rows said a source does not answer
303
+ today, and four was the number of `read` rows, which that legend defined as
304
+ "nothing uses it yet". The police-events row moved into the unconfirmed state
305
+ here, the changelog recorded the move and the count did not follow it. The station car-park source, in the server's register with no row
306
+ on the page at all, now has its `read` row.
307
+ - **The Code of Conduct sent a reporter without a GitHub account to a postal
308
+ address that does not exist** — in its first draft, in this release. The document
309
+ is new here (see `### Added`), so no published version ever sent anybody
310
+ anywhere. The Impressum's street and city are still visible placeholders; only
311
+ the e-mail address is real, and that is what the document names. Nothing linked
312
+ to it either; the README's Contributing section does.
313
+
6
314
  ## [1.3.12] - 2026-09-23
7
315
 
8
316
  The bridge's code is unchanged since 0.0.9. This release replaces 1.3.10,
package/README.md CHANGED
@@ -1,36 +1,51 @@
1
1
  # viafrei
2
2
 
3
- **German road, rail, parking, charging and fuel data — live, inside your AI
4
- assistant.**
3
+ [![npm](https://img.shields.io/npm/v/viafrei?color=cb3837&label=npm&logo=npm)](https://www.npmjs.com/package/viafrei)
4
+ [![node](https://img.shields.io/node/v/viafrei?logo=node.js&logoColor=white)](https://nodejs.org)
5
+ [![licence](https://img.shields.io/npm/l/viafrei?color=blue)](LICENSE)
6
+ [![no API key](https://img.shields.io/badge/API%20key-none-brightgreen)](#connect-in-one-line)
7
+ [![MCP](https://img.shields.io/badge/MCP-Streamable%20HTTP-6f42c1)](https://modelcontextprotocol.io)
8
+
9
+ **Live German traffic, rail, parking, charging, fuel, addresses and weather —
10
+ inside your AI assistant.** Ask in plain German or plain English and the answer
11
+ comes back from official open data, with the attribution the licence requires.
5
12
 
6
13
  ```
7
14
  npx viafrei
8
15
  ```
9
16
 
17
+ **No account, no API key, no sign-up.**
18
+
10
19
  ## Ask your assistant things like
11
20
 
12
21
  **Welche Züge fahren als Nächstes ab Hamburg Hbf?**
13
22
 
14
23
  ```
15
24
  Abfahrten ab Hamburg Hbf (nächste 60 Minuten):
16
- 09:45 ICE 519 → München Hbf, Gleis 14, +3 min (ca. 09:48)
17
- 09:51 IC 306 → Stockholm Central, Gleis 12, pünktlich
18
- 09:51 ICE 707 → Berlin Hbf, Gleis 8A-F, +1 min (ca. 09:52)
19
- … seven more
25
+ 12:28 RJ 384 → Koebenhavn H, Gleis 5, pünktlich
26
+ 12:29 ICE 7 → Karlsruhe Hbf, Gleis 14, pünktlich
27
+ 12:33 ICE 774 → Kiel Hbf, Gleis 11, pünktlich
28
+ 12:33 ME RB31 → Lüneburg, Gleis 13A-C, pünktlich
29
+ 12:34 ICE 601 → München Hbf, Gleis 8A-F, pünktlich
20
30
 
21
- Stand 09:43 · Quelle: Fahrplandaten: Deutsche Bahn AG, DB API Marketplace,
31
+ Stand 12:23 · Quelle: Fahrplandaten: Deutsche Bahn AG, DB API Marketplace,
22
32
  CC BY 4.0, bearbeitet (…) · Bahnhofsdaten: Deutsche Bahn AG,
23
33
  DB API Marketplace, CC BY 4.0, bearbeitet (…)
24
34
  ```
25
35
 
36
+ That is a real answer, not a mock-up: it came back from
37
+ `https://mcp.viafrei.de/mcp` on 2026-09-27 at 12:23 Berlin time. **Two edits, both
38
+ named:** the two source URIs are shortened to `…` for the reason in the next
39
+ paragraph, and only the first five of the ten departures it returned are shown.
40
+ The server sends no "… and five more" line — the truncation is this page's, not
41
+ its.
42
+
26
43
  Every answer ends with a line like that last one. It is the licence talking,
27
44
  and it is meant to be shown to whoever reads the answer — see
28
45
  [Using the data you get back](#using-the-data-you-get-back). **Reproduce the
29
46
  line the server sends you, not this one:** it has been shortened here, and the
30
47
  real one names each source's URL, which the licence requires you to keep.
31
48
 
32
- **No account, no API key, no sign-up — ask and the answer comes back.**
33
-
34
49
  ViaFrei is in its stabilisation and testing phase at the time of writing: live
35
50
  and free, with some sources thinner than they will be, and the occasional tool
36
51
  that answers slowly or not at all. **Tell us when that happens** —
@@ -48,27 +63,76 @@ the language you asked in, not translated from one house language:
48
63
  - *Wo kann ich in Leipzig mit Typ 2 laden?*
49
64
  - *Gibt es eine Unwetterwarnung für Freiburg?*
50
65
  - *Brauche ich in Deutschland eine Umweltplakette?*
66
+ - *Funktioniert der Aufzug am Bahnhof Köln Messe/Deutz?* — lift and escalator
67
+ status, which is the difference between a station being usable and not
68
+ - *Wo ist die nächste Apotheke zum Leipziger Hauptbahnhof?*
69
+ - *What is at 52.5163, 13.3777?* — and the other direction: a street address to
70
+ coordinates
51
71
  - *Tell me when the A8 reopens* — the server can watch a situation and tell
52
72
  your assistant when it changes, so you need not keep asking. The watch lives
53
73
  in the conversation that opened it: it reaches no inbox and no phone, and
54
74
  it ends with the session. Three hours by default; 24 is the longest one can
55
75
  be asked to run
56
76
 
57
- Thirteen tools at the time of writing, and no list of them on this page. A
58
- pasted catalogue goes stale the first time a description changes on the server,
59
- and this page is frozen inside a published tarball — it cannot be corrected
60
- without a release. So there is one source of truth and it is the running
61
- server: connect any MCP client and call `tools/list` for the catalogue as it
62
- is today. (<https://viafrei.de> is the live national traffic digest, not a
63
- catalogue.)
77
+ ## What it covers
64
78
 
65
- ## Quick start
79
+ **Eighteen tools** as of this release. Grouped by the question they answer,
80
+ because the grouping is stable and a pasted catalogue is not:
66
81
 
67
- Node 22 or newer. Nothing to install — `npx` fetches the bridge when your
68
- client starts it.
82
+ | | |
83
+ |---|---|
84
+ | **Roads** | live incidents, jams and closures; roadworks ahead on a route; the state of one Autobahn end to end; **lorry** parking on the motorways, with occupancy wherever the operator publishes it — see the note below on car parks |
85
+ | **Rail** | next departures from any station with real-time delays and platforms; how punctual public transport is across a region right now (region-wide — never one line, trip or stop); whether a station's lifts and escalators are working right now |
86
+ | **Energy** | charging points by connector type and power; the cheapest fuel near a place |
87
+ | **Places** | a street address to coordinates and back; points of interest; what is at a coordinate, and what is near it — nationwide, all sixteen Länder (measured on 2026-09-27 — [how](SOURCES.md)) |
88
+ | **Weather** | official DWD severe-weather warnings for a place |
89
+ | **Watches** | ask once and be told when a situation changes, instead of asking again |
90
+
91
+ **One thing the parking row does not yet cover: car parks.** The licence for
92
+ station car parks is read and the loader exists, but nothing on the public service
93
+ answers with one today — asked for a `car_park` in Köln, Hamburg and Leipzig on
94
+ 2026-09-27, every facility that came back was a lorry park or an unclassified site,
95
+ and none carried occupancy. So what you get from `find_parking` today is motorway
96
+ lorry parking. The table said "car and lorry parking" until this release; it was
97
+ the same over-claim the Rail row carried about disruptions, and it is corrected here rather
98
+ than left for a user to discover.
99
+
100
+ **No tool names, descriptions or schemas are pasted on this page, on purpose.**
101
+ This file is frozen inside a published tarball and cannot be corrected without a
102
+ release, so a copied catalogue would start rotting the first time a description
103
+ changes on the server. There is one source of truth and it is the running
104
+ server: connect any MCP client and call `tools/list`.
105
+ (<https://viafrei.de> is the live national traffic digest, not a catalogue.)
106
+
107
+ ## Connect in one line
108
+
109
+ The product is a **hosted MCP server**. There is nothing to deploy, no key to
110
+ request and no quota to negotiate — point a client at it and the tools appear.
111
+
112
+ **Streamable HTTP — the address to use.** Most clients speak this, and they need
113
+ none of the rest of this page:
69
114
 
70
- **Claude Desktop** (`claude_desktop_config.json`). The same three lines fit any
71
- client that takes an stdio MCP server, including the `.mcp.json` an IDE reads:
115
+ ```
116
+ https://mcp.viafrei.de/mcp
117
+ ```
118
+
119
+ ```bash
120
+ claude mcp add --transport http viafrei https://mcp.viafrei.de/mcp
121
+ ```
122
+
123
+ **HTTP+SSE — if your client only speaks the older transport**, it is answered too,
124
+ so nobody meets a locked door. It is deprecated in the specification; prefer the
125
+ address above:
126
+
127
+ ```bash
128
+ claude mcp add --transport sse viafrei https://mcp.viafrei.de/sse
129
+ ```
130
+
131
+ **stdio — this package.** Some clients still speak only stdio, and **this bridge
132
+ is for those, and only those.** Node 22 or newer; nothing to install, because
133
+ `npx` fetches it when your client starts it. The same three lines fit any client
134
+ that takes an stdio MCP server, including the `.mcp.json` an IDE reads and
135
+ Claude Desktop's `claude_desktop_config.json`:
72
136
 
73
137
  ```json
74
138
  {
@@ -81,25 +145,35 @@ client that takes an stdio MCP server, including the `.mcp.json` an IDE reads:
81
145
  }
82
146
  ```
83
147
 
84
- Restart the client and ask it one of the questions above. There is no account,
85
- no API key and no sign-up.
86
-
87
- ## Do you actually need this package?
88
-
89
- Probably not — and that is deliberate.
90
-
91
- Most MCP clients speak Streamable HTTP and should connect straight to
92
- `https://mcp.viafrei.de/mcp`. **They do not need this package at all.**
93
-
94
- Clients that speak only the older HTTP+SSE transport do not need it either: the
95
- server answers that transport too, at `https://mcp.viafrei.de/sse`
96
- (`claude mcp add --transport sse`). It is deprecated in the specification and
97
- carried so that nobody meets a locked door; prefer the address above.
98
-
99
- Some clients still speak only stdio. **This bridge is for those** — and only
100
- those: it runs locally, exposes a stdio MCP server, and relays every request to
101
- the public endpoint. It is a transport shim: it holds no data and no
102
- credentials, and it makes no decision about any answer.
148
+ Restart the client and ask it one of the questions above.
149
+
150
+ ## Why build on it
151
+
152
+ - **One endpoint instead of a stack of integrations.**
153
+ [SOURCES.md](SOURCES.md) lists **twenty-one sources from sixteen publishers** —
154
+ Autobahn GmbH, the national access point, Deutsche Bahn, DWD, BKG, GeoNames,
155
+ OpenStreetMap, MTS-K and the rest — each with its own format, its own release
156
+ rhythm and its own licence. They arrive here as one protocol and one set of
157
+ tools. (Count them in that table; one of the sixteen is us.)
158
+ - **The licence work is done and it travels with the answer.** Every result
159
+ carries the attribution line its sources require, and the ones with conditions
160
+ attached say so in the result itself — share-alike is flagged, and the MTS-K
161
+ purpose limit arrives as a sentence you are meant to show. You are not left to
162
+ work out what you owe whom.
163
+ - **It answers in the language of the question**, German or English, rather than
164
+ translating out of one house language.
165
+ - **It is a transport shim and nothing more.** The bridge holds no data and no
166
+ credentials, writes no file outside the OS temp directory, and makes no
167
+ decision about any answer. No telemetry, no analytics, no usage counter, no
168
+ update check — see [What it does not do](#what-it-does-not-do).
169
+ - **Failures are machine-readable.** One line on stderr and a distinct exit code
170
+ per cause, so a supervisor can tell "your network is down" from "you typed the
171
+ flag wrong" without parsing English.
172
+ - **Ask once, be told when it changes.** A watch turns polling into a
173
+ notification for as long as the conversation lives.
174
+
175
+ Free while ViaFrei stabilises, and the limits that exist are written down on this
176
+ page rather than discovered in production.
103
177
 
104
178
  ## Configuration
105
179
 
@@ -110,9 +184,30 @@ credentials, and it makes no decision about any answer.
110
184
  | `--timeout <ms>` | per-request timeout, default 30000. The event stream is never timed out |
111
185
  | `--version`, `--help` | print and exit |
112
186
 
113
- `VIAFREI_MCP_URL` and `VIAFREI_MCP_TIMEOUT_MS` do the same for clients that
114
- pass environment variables rather than arguments. A flag wins over the
115
- variable; the variable wins over the built-in default.
187
+ Many MCP clients can only pass an `env` block, not arguments, so every option
188
+ has an environment variable too:
189
+
190
+ | variable | same as |
191
+ |---|---|
192
+ | `VIAFREI_MCP_URL` | `--url` |
193
+ | `VIAFREI_MCP_HEADER` | `--header`. Several headers are separated by a **newline**, which can never appear in a header name or value, so nothing you might need to send is unrepresentable — a comma, a semicolon and a space all occur inside real header values |
194
+ | `VIAFREI_MCP_TIMEOUT_MS` | `--timeout` |
195
+
196
+ A flag wins over the variable; the variable wins over the built-in default. A
197
+ `--header` of the same name replaces one from the variable, and a `--header` of
198
+ a different name is added alongside it.
199
+
200
+ ```json
201
+ {
202
+ "mcpServers": {
203
+ "viafrei": {
204
+ "command": "npx",
205
+ "args": ["-y", "viafrei"],
206
+ "env": { "VIAFREI_MCP_HEADER": "Authorization: Bearer …" }
207
+ }
208
+ }
209
+ }
210
+ ```
116
211
 
117
212
  **There is no self-hosted ViaFrei.** The server is a hosted service, so `--url`
118
213
  is not a way to run your own — it is there for a proxy or gateway in front of
@@ -125,9 +220,13 @@ want.
125
220
  One line to stderr and an exit code that says what happened. No stack traces:
126
221
 
127
222
  ```
128
- viafrei: cannot reach https://mcp.viafrei.de/mcp: DNS lookup failed (EAI_AGAIN) - check your network connection; --url only if you relay through a proxy
223
+ viafrei: cannot reach https://example.invalid/mcp: host not found (DNS) (ENOTFOUND) - check your network connection; --url only if you relay through a proxy
129
224
  ```
130
225
 
226
+ That line is copied from a run, exit code 3. The same text also goes back to the
227
+ client as a JSON-RPC error, so an assistant can say what went wrong instead of
228
+ going quiet.
229
+
131
230
  | exit | meaning |
132
231
  |---|---|
133
232
  | `0` | clean shutdown (the client closed stdin, or sent SIGINT/SIGTERM) |
@@ -137,6 +236,27 @@ viafrei: cannot reach https://mcp.viafrei.de/mcp: DNS lookup failed (EAI_AGAIN)
137
236
  | `4` | the endpoint answered and this cannot continue: it refused (the line names the HTTP status), it forgot the session, it answered with something that is not MCP, or it redirected to another origin |
138
237
  | `5` | protocol version mismatch; the line names the version the server speaks |
139
238
 
239
+ **A slow call may be a retried call.** A `429`, `502`, `503` or `504` is tried
240
+ again **once** — after the delay the server asked for in `Retry-After`, or 250 ms
241
+ when it asked for none. A connection that fails outright rather than answering
242
+ (reset, broken pipe, socket or connect timeout) is likewise tried once more, after
243
+ the fixed 250 ms; there is no header to read on that path. Two cases are
244
+ deliberately not retried, because waiting would be worse than answering: a `429`
245
+ carrying no usable `Retry-After`, and any delay the server asks for that is longer
246
+ than your timeout. Both come straight back as the ordinary failure line with the
247
+ status in it. An event stream is never retried.
248
+
249
+ **Your timeout bounds each request, not the call.** It is attached per HTTP
250
+ request, so the second attempt gets a fresh one, and so does each hop of a
251
+ same-origin redirect. What you can rely on is per request: no single request
252
+ outlives the timeout, and a delay longer than the timeout is never waited out at
253
+ all. What follows from that is the arithmetic: two attempts plus a delay that may
254
+ itself be as long as the timeout is a worst case of about **three times** what you
255
+ set, and more than that if the endpoint redirects, since every hop is bounded
256
+ separately. **So no setting here caps the whole call** — if you need a hard
257
+ ceiling, enforce it on your side and treat the timeout as the per-request bound it
258
+ is.
259
+
140
260
  An established session is allowed to wobble — a dropped event stream is a
141
261
  warning, not an exit, and the bridge reconnects. It is not allowed to be dead
142
262
  in silence: several failures in a row with nothing succeeding in between end
@@ -186,9 +306,15 @@ This is said plainly so nobody spends an evening looking for the server code.
186
306
 
187
307
  ## Contributing
188
308
 
189
- Yes, please — see [CONTRIBUTING.md](CONTRIBUTING.md). Issues and discussions
190
- are open. The bridge is small and self-contained, which is exactly what makes
191
- it a reasonable thing to send a first patch to.
309
+ Yes, please — see [CONTRIBUTING.md](CONTRIBUTING.md) and
310
+ [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). Issues and discussions are open. The
311
+ bridge is small and self-contained, which is exactly what makes it a reasonable
312
+ thing to send a first patch to.
313
+
314
+ **One issue template is worth knowing about before you need it:** *the answer was
315
+ wrong or useless*. A tool that fails is something the server sees; a tool that
316
+ answers confidently with the wrong thing is not. That report is the one thing we
317
+ cannot get any other way.
192
318
 
193
319
  ## Security
194
320