viafrei 1.3.22 → 1.4.8
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/API.md +44 -8
- package/CHANGELOG.md +269 -0
- package/README.md +4 -4
- package/SOURCES.md +5 -5
- package/dist/bridge.d.ts +0 -12
- package/dist/bridge.js +43 -22
- package/dist/config.d.ts +0 -6
- package/dist/config.js +92 -52
- package/package.json +3 -1
package/API.md
CHANGED
|
@@ -9,7 +9,7 @@ the server's own text, reproduced verbatim, because that text is what an
|
|
|
9
9
|
assistant reads when it decides which tool to call; paraphrasing it here would
|
|
10
10
|
document a different server.
|
|
11
11
|
|
|
12
|
-
**It is a dated snapshot, taken on 2026-09-
|
|
12
|
+
**It is a dated snapshot, taken on 2026-09-29.** Generating this file makes
|
|
13
13
|
it impossible for the document and the snapshot to disagree — CI regenerates and
|
|
14
14
|
compares — but it cannot keep the snapshot from ageing against the live server,
|
|
15
15
|
because a capture is a point in time. **The source of truth is the running
|
|
@@ -17,12 +17,12 @@ server:** connect any MCP client and call `tools/list`.
|
|
|
17
17
|
|
|
18
18
|
| | |
|
|
19
19
|
| --- | --- |
|
|
20
|
-
| Server | `viafrei` 1.
|
|
20
|
+
| Server | `viafrei` 1.4.8 |
|
|
21
21
|
| MCP protocol | `2025-06-18` |
|
|
22
22
|
| Streamable HTTP | https://mcp.viafrei.de/mcp |
|
|
23
23
|
| Legacy HTTP+SSE | https://mcp.viafrei.de/sse |
|
|
24
|
-
| Captured from | `https://mcp.viafrei.de/mcp` on 2026-09-
|
|
25
|
-
| Surface |
|
|
24
|
+
| Captured from | `https://mcp.viafrei.de/mcp` on 2026-09-29 |
|
|
25
|
+
| Surface | 19 tools, 10 resources, 2 resource templates, 9 prompts |
|
|
26
26
|
| Parameter schemas | JSON Schema draft-07 |
|
|
27
27
|
| Capabilities | `tools`, `resources`, `prompts`, `logging` |
|
|
28
28
|
|
|
@@ -31,7 +31,7 @@ No API key. No account. No sign-up.
|
|
|
31
31
|
## Contents
|
|
32
32
|
|
|
33
33
|
- [How to read a result](#how-to-read-a-result)
|
|
34
|
-
- [Tools](#tools) —
|
|
34
|
+
- [Tools](#tools) — 19
|
|
35
35
|
- [Resources](#resources) — 10
|
|
36
36
|
- [Resource templates](#resource-templates) — 2
|
|
37
37
|
- [Prompts](#prompts) — 9
|
|
@@ -86,7 +86,7 @@ The server's own instructions to a connecting client, verbatim:
|
|
|
86
86
|
|
|
87
87
|
**Read-only** — it changes nothing. Reaches a third-party source (open world). Idempotent: true. Destructive: false.
|
|
88
88
|
|
|
89
|
-
> Returns the cheapest
|
|
89
|
+
> Returns the cheapest stations for one fuel grade near a place or coordinate: price per litre, brand, address, distance, open state. Use when the user asks where to fill up or what fuel costs. Do NOT use for charging an electric car (call find_charging_station), price history, or traffic (call check_autobahn_traffic). Radius ≤ 25 km, at most 10 stations. For a brand, a name, open now or at a named time, or nearest-first, call find_fuel_station. Each line names the age of its price and opening-hours claim. Prices are consumer information only; the attribution line and MTS-K note must be shown.
|
|
90
90
|
|
|
91
91
|
| parameter | type | required | default | constraints |
|
|
92
92
|
| --- | --- | --- | --- | --- |
|
|
@@ -106,6 +106,42 @@ The server's own instructions to a connecting client, verbatim:
|
|
|
106
106
|
- **`place`** — Where to look, as free text: a city ("München", "Munich"), a district or Kreis ("Kreis Fulda"), a Bundesland, a station or stop ("Hamburg Hbf"), a motorway ("A7"), or a street address with a house number ("Hauptstraße 12, 36037 Fulda"). Use this instead of coordinates whenever the person named a place. An address needs its town or postcode — a street and a number alone exist in many towns. Give either place OR lat+lon, never both.
|
|
107
107
|
- **`radius_km`** — Search radius around the place in kilometres (1–25, default 5). The provider's terms cap it at 25 km — a larger circle is a dataset request, not a consumer question.
|
|
108
108
|
|
|
109
|
+
### `find_fuel_station` — Find a filling station
|
|
110
|
+
|
|
111
|
+
**Read-only** — it changes nothing. Reaches a third-party source (open world). Idempotent: true. Destructive: false.
|
|
112
|
+
|
|
113
|
+
> Finds filling stations around a place or coordinate under any combination of filters — grade, brand, name, open now, open at a time you name, open 24 h — sorted by distance, price or name. Use when the question is WHICH station: the closest diesel to a stop, an ARAL open tonight, what one forecourt sells. Do NOT use for the plain "where is fuel cheapest" question (call find_cheapest_fuel) or for charging an electric car (call find_charging_station). Every result carries the attribution and the MTS-K note, and says how old each price and opening-hours claim is.
|
|
114
|
+
|
|
115
|
+
| parameter | type | required | default | constraints |
|
|
116
|
+
| --- | --- | --- | --- | --- |
|
|
117
|
+
| `brand` | string | no | — | min length 1 |
|
|
118
|
+
| `fuel` | string | no | — | one of `"e5"`, `"e10"`, `"diesel"` |
|
|
119
|
+
| `language` | string | no | `"de"` | one of `"de"`, `"en"` |
|
|
120
|
+
| `lat` | number | no | — | min -90; max 90 |
|
|
121
|
+
| `limit` | integer | no | `5` | min 1; max 10 |
|
|
122
|
+
| `lon` | number | no | — | min -180; max 180 |
|
|
123
|
+
| `name` | string | no | — | min length 1 |
|
|
124
|
+
| `open_at` | string | no | — | pattern `^(?:\d{1,2}:\d{2}\|\d{4}-\d{2}-\d{2}[T ]\d{1,2}:\d{2})$` |
|
|
125
|
+
| `open_now` | boolean | no | `false` | — |
|
|
126
|
+
| `place` | string | no | — | min length 1 |
|
|
127
|
+
| `radius_km` | number | no | `5` | min 1; max 25 |
|
|
128
|
+
| `sort` | string | no | `"distance"` | one of `"distance"`, `"price"`, `"name"` |
|
|
129
|
+
| `whole_day` | boolean | no | `false` | — |
|
|
130
|
+
|
|
131
|
+
- **`brand`** — Only stations of this brand, matched case-insensitively anywhere in the brand field: "ARAL", "Shell", "TotalEnergies", "JET". Use it when the person named a chain ("die ARAL an der B1"). Free stations often carry no brand at all and are then not matched by any brand.
|
|
132
|
+
- **`fuel`** — Only stations with a current price for this grade: "e5" (Super E5), "e10" (Super E10) or "diesel". Omit to get every station near the place whatever it sells — the answer then lists all the grades it holds a price for. Required when sort is "price", because a price ordering needs a grade.
|
|
133
|
+
- **`language`** — Set this on every call to the language the person is writing in: "en" if they wrote English, "de" if they wrote German. Do not leave it out because it has a default — the default is only the fallback when the language is genuinely unclear, and an English question answered in German is a wrong answer. Place names, station names and road numbers are never translated in either language; in English the German term is kept in parentheses so the person recognises it on signs and in local apps.
|
|
134
|
+
- **`lat`** — Latitude in WGS 84, e.g. 48.137. Use with lon when the caller already holds coordinates; otherwise use place.
|
|
135
|
+
- **`limit`** — How many stations to return (1–10, default 5).
|
|
136
|
+
- **`lon`** — Longitude in WGS 84, e.g. 11.576. Use with lat; otherwise use place.
|
|
137
|
+
- **`name`** — Part of the station's own name or brand, case-insensitive: "Autohof", "Raststätte Fulda". Use it when the person named a specific forecourt rather than a chain. Combine with place to keep the search local.
|
|
138
|
+
- **`open_at`** — Only stations open at that time in Germany (Europe/Berlin): "23:30" means the next time the clock shows 23:30, and "2026-09-30 06:15" a specific local date and time. Use it for "is it still open tonight". Do not pass it together with open_now — they ask the same question about two different clocks.
|
|
139
|
+
- **`open_now`** — When true, only stations the published opening hours say are open at this moment. Default false. A station whose hours we have never read is NOT returned by this filter and is counted in the answer instead — the result never guesses that an unknown station is open.
|
|
140
|
+
- **`place`** — Where to look, as free text: a city ("München", "Munich"), a district or Kreis ("Kreis Fulda"), a Bundesland, a station or stop ("Hamburg Hbf"), a motorway ("A7"), or a street address with a house number ("Hauptstraße 12, 36037 Fulda"). Use this instead of coordinates whenever the person named a place. An address needs its town or postcode — a street and a number alone exist in many towns. Give either place OR lat+lon, never both.
|
|
141
|
+
- **`radius_km`** — Search radius around the place in kilometres (1–25, default 5). 25 km is the provider's own ceiling — a larger circle is a dataset request, not a consumer question.
|
|
142
|
+
- **`sort`** — Order of the answer: "distance" (nearest first, the default — use it for "closest diesel to Hamburg Hbf"), "price" (cheapest first, needs fuel), or "name" (alphabetical, for a person scanning a list of a brand's forecourts).
|
|
143
|
+
- **`whole_day`** — When true, only stations the provider flags as open around the clock (24/7). Default false. Use it for a night drive; it is a stricter filter than open_now, which is satisfied by a station that closes at 22:00.
|
|
144
|
+
|
|
109
145
|
### `find_parking` — Parking nearby
|
|
110
146
|
|
|
111
147
|
**Read-only** — it changes nothing. Answers from data this service already holds (closed world). Idempotent: true. Destructive: false.
|
|
@@ -334,7 +370,7 @@ The server's own instructions to a connecting client, verbatim:
|
|
|
334
370
|
|
|
335
371
|
**Read-only** — it changes nothing. Answers from data this service already holds (closed world). Idempotent: true. Destructive: false.
|
|
336
372
|
|
|
337
|
-
> Returns how punctual public transport is right now in one German region: the share of distinct trips more than 5 minutes late
|
|
373
|
+
> Returns how punctual public transport is right now in one German region: the share of distinct trips at least once more than 5 minutes late, trips with a cancelled stop, the trend against the previous window, and how many trips that rests on. Use when the user asks whether buses and trains are running normally, or whether a strike or storm is disrupting local transport. Do NOT use for one line, trip or station — per-line realtime is unavailable; the region's figures are the answer. Region-wide aggregates only; window ≤ 120 min. CC BY-SA (share-alike): show the attribution line to the user.
|
|
338
374
|
|
|
339
375
|
| parameter | type | required | default | constraints |
|
|
340
376
|
| --- | --- | --- | --- | --- |
|
|
@@ -619,5 +655,5 @@ single call.
|
|
|
619
655
|
---
|
|
620
656
|
|
|
621
657
|
Generated from `catalogue.json` by `scripts/gen-api-doc.mjs`. The snapshot was
|
|
622
|
-
read from `https://mcp.viafrei.de/mcp` on 2026-09-
|
|
658
|
+
read from `https://mcp.viafrei.de/mcp` on 2026-09-29; no tool was invoked to
|
|
623
659
|
produce it, so no data provider was contacted.
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,275 @@ a Changelog and the versions follow Semantic Versioning.
|
|
|
7
7
|
|
|
8
8
|
Nothing yet.
|
|
9
9
|
|
|
10
|
+
## [1.4.8] - 2026-09-29
|
|
11
|
+
|
|
12
|
+
**A version-sync release.** The bridge is published at the version the ViaFrei server
|
|
13
|
+
is serving, so that `npx viafrei@X.Y.Z` and the endpoint it relays to are named by one
|
|
14
|
+
number. `mcp.viafrei.de` moved to `1.4.8`, so the bridge follows. 1.4.7 is absent from
|
|
15
|
+
the registry for the same reason 1.4.0–1.4.5 were: that platform version carried no
|
|
16
|
+
bridge change.
|
|
17
|
+
|
|
18
|
+
**Nothing in the bridge changed at all** — not the runtime, not the tooling, not a
|
|
19
|
+
gate. The diff against 1.4.6 is the three version fields plus the re-captured API
|
|
20
|
+
reference. If 1.4.6 works for you, this is the same code under a number that matches
|
|
21
|
+
the server.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **The API reference is re-captured at 1.4.8** (`API.md`, `catalogue.json`). Unlike
|
|
26
|
+
1.4.6's re-capture, this one is only a date and a version string: the server's
|
|
27
|
+
surface did **not** change between 1.4.6 and 1.4.8. Verified rather than assumed —
|
|
28
|
+
the same 19 tools, 10 resources, 2 resource templates and 9 prompts, with no tool
|
|
29
|
+
added or removed, no description altered and no `required` list moved, compared
|
|
30
|
+
field-by-field against the stored snapshot before the new one was written.
|
|
31
|
+
|
|
32
|
+
That distinction is the whole reason this entry says so out loud. At 1.4.6 the
|
|
33
|
+
shipped reference was *wrong* — it omitted a tool the server exposed. Here it was
|
|
34
|
+
merely *old*. The two need different remedies and only one of them is honest in each
|
|
35
|
+
case, so which one applied is recorded rather than left for a reader to guess.
|
|
36
|
+
|
|
37
|
+
Captured read-only: `initialize`, `tools/list`, `resources/list`,
|
|
38
|
+
`resources/templates/list`, `prompts/list`. No tool was invoked, so no data provider
|
|
39
|
+
was contacted, and the session was deleted afterwards. The two substitutions the
|
|
40
|
+
snapshot documents are unchanged and asserted on the way in and out: 19 of 19
|
|
41
|
+
`$schema` URIs dropped, exactly 2 long patterns stored as `patternLength`.
|
|
42
|
+
|
|
43
|
+
## [1.4.6] - 2026-09-29
|
|
44
|
+
|
|
45
|
+
**The version number is prod's, not this package's own count.** The bridge is released
|
|
46
|
+
at the version the ViaFrei server is actually serving — `mcp.viafrei.de` reports
|
|
47
|
+
`1.4.6` — so that `npx viafrei@X.Y.Z` and the endpoint it relays to can be named by
|
|
48
|
+
one number. That is why this release skips from 1.3.22 to 1.4.6 with no 1.4.0 through
|
|
49
|
+
1.4.5 on the registry: those platform versions carried no bridge change.
|
|
50
|
+
|
|
51
|
+
The shipped API reference is re-captured from that same server, so the package, the
|
|
52
|
+
endpoint and the documentation all name 1.4.6. It had been left at a 1.3.22 capture,
|
|
53
|
+
which review caught.
|
|
54
|
+
|
|
55
|
+
Nothing in the bridge's own runtime behaviour changed. Every flag, every environment
|
|
56
|
+
variable, every exit code and every message is what 1.3.22 shipped. What changed is the
|
|
57
|
+
machinery around it — two gates the 1.3.22 release wanted and could not have, one because
|
|
58
|
+
the failure it prevents happened *during* that release, and one that had been kept in step
|
|
59
|
+
by hand since 1.3.15 — plus seven refactors and one documentation fix.
|
|
60
|
+
|
|
61
|
+
### Added
|
|
62
|
+
|
|
63
|
+
- **Every external program these scripts run now has a deadline** (`runTool` in
|
|
64
|
+
`scripts/tools.mjs`, `npm run test:tools`), because on 2026-09-28 one of them stopped
|
|
65
|
+
returning and nothing noticed.
|
|
66
|
+
|
|
67
|
+
A `git` call inside the tarball gate's self-test hung on a GitHub runner. The job had
|
|
68
|
+
no timeout either, so it ran for **1 hour 49 minutes** before being cancelled by hand;
|
|
69
|
+
the publish job hit the same stall and sat for 15 minutes with a tag already pushed.
|
|
70
|
+
Both stopped after the same case and the Node 22 job of the same commit passed, so it
|
|
71
|
+
reproduces rather than being a one-off (#25).
|
|
72
|
+
|
|
73
|
+
**A hang is the one failure mode everything else here is built to prevent.** These
|
|
74
|
+
scripts refuse by name, prove their controls can say no, and treat a check that read
|
|
75
|
+
nothing as a failure — and a hung subprocess defeats all of it at once, because it
|
|
76
|
+
cannot be told apart from work in progress: no exit code, no message, and a log that
|
|
77
|
+
simply stops. There is now no unbounded external program in `scripts/`: every one goes
|
|
78
|
+
through a single wrapper with a 120-second default, roughly 120 times the slowest
|
|
79
|
+
legitimate call here. No count is written down — the bare-name sweep is the instrument,
|
|
80
|
+
and it fails if a call appears that does not go through the wrapper. (This branch
|
|
81
|
+
CONVERTED 22 sites across six files; that is a figure about the change, not an
|
|
82
|
+
inventory of the tree, and an earlier draft of this sentence used it as both.)
|
|
83
|
+
|
|
84
|
+
Only a **timeout** is translated, into a refusal naming the program, its arguments and
|
|
85
|
+
the limit. Every other failure is re-thrown untouched, because each gate decides
|
|
86
|
+
"refused" from `status`, `stdout` and `stderr` on the thrown error, and wrapping those
|
|
87
|
+
would break the thing the gates measure. Both halves are asserted.
|
|
88
|
+
|
|
89
|
+
**Renaming the call sites nearly disabled the gate that watches them.** The bare-name
|
|
90
|
+
sweep looked for `execFileSync(`; routing 22 calls through `runTool(` would have left
|
|
91
|
+
it green over the whole set while covering none of it — the exact silent-hole failure
|
|
92
|
+
that file exists to refuse. It is caught structurally now: the function names live in
|
|
93
|
+
one `SPAWNERS` array that both the sweep and its own precondition are derived from, so
|
|
94
|
+
the two cannot drift again. The only reason this was noticed is that the precondition
|
|
95
|
+
went red on its own when it found zero spawning files.
|
|
96
|
+
|
|
97
|
+
- **A gate on the manifest and lockfile versions** (`scripts/check-versions.mjs`,
|
|
98
|
+
`npm run check:versions`), in **both** workflows (#18).
|
|
99
|
+
|
|
100
|
+
Cutting 1.3.15 left `package.json` at `1.3.15` while `package-lock.json` still said
|
|
101
|
+
`1.3.12`, on a tree seven review rounds had confirmed, and every gate passed. The
|
|
102
|
+
measured reason: **`npm ci` does not compare the root `version` field at all** —
|
|
103
|
+
checked on npm 11.19.1 and on the 12.0.2 the publish workflow pins — so nothing in
|
|
104
|
+
build, test, pack or the tarball gate reads that pair. It has been kept in step by hand
|
|
105
|
+
ever since, and 1.3.16 and 1.3.22 each said so in their commit messages. "Done
|
|
106
|
+
deliberately" is the failure mode, not the remedy.
|
|
107
|
+
|
|
108
|
+
It lives in `ci.yml` as well as `publish.yml` so the drift surfaces on the push that
|
|
109
|
+
introduces it rather than at the tag, when the only remedy is a new version. Its exit
|
|
110
|
+
codes are distinct on purpose — **2** could not run, **1** ran and disagreed — so "I
|
|
111
|
+
could not read the lockfile" can never look like "I read it and was satisfied". Its
|
|
112
|
+
self-test covers the mutant #18 asks for and the one that would otherwise agree about
|
|
113
|
+
nothing — three ABSENT fields are all equal to each other. No case count is written
|
|
114
|
+
here; `npm run test:versions` prints the one to trust.
|
|
115
|
+
|
|
116
|
+
### Changed
|
|
117
|
+
|
|
118
|
+
- **All seven `javascript:S3776` cognitive-complexity findings are gone** (#19), measured
|
|
119
|
+
on the push rather than claimed: seven CRITICAL findings on `main` before, **zero**
|
|
120
|
+
after, and zero new issues of any rule. `parseOptions` 22, `remote.onmessage` 18,
|
|
121
|
+
`checkManifest` 20, the tarball gate's `main` 31, `tokenCandidates` 18, `decodings` 18
|
|
122
|
+
and `scanFile` 27 are each split by concern, and every extraction MOVED lines rather
|
|
123
|
+
than rewriting them. No behaviour change — that is the point of the entry being here
|
|
124
|
+
and not under Fixed.
|
|
125
|
+
|
|
126
|
+
The refactor exposed five things nothing was testing, each proved by deleting it and
|
|
127
|
+
watching the suite stay green: `-h` had no case at all; `setProtocolVersion` had none,
|
|
128
|
+
so the negotiate-down test asserted that the bridge *complains* about a version and
|
|
129
|
+
never that it *applies* it; half of one error sentence was unreachable because every
|
|
130
|
+
fixture carried a `supported` list; a dependency check's early exit could be deleted
|
|
131
|
+
because every private-scope fixture also happened to be a valid range; and
|
|
132
|
+
`"is this a listed private name?"` was written out by hand **three** times in
|
|
133
|
+
`rules.mjs`, in the file that decides whether a commit may be published. All five are
|
|
134
|
+
closed. The gate self-test went from 107 cases to 110.
|
|
135
|
+
|
|
136
|
+
- **Both workflow jobs carry `timeout-minutes: 15`** (#25). There was no timeout on any
|
|
137
|
+
job, so GitHub's default six hours applied — which is how a hung step ran for nearly
|
|
138
|
+
two. Fifteen minutes is nine times the observed duration of a successful run (~100 s
|
|
139
|
+
for CI, ~95 s for publish), so it cannot fire on a slow-but-working build, and it ends
|
|
140
|
+
a hung one in minutes.
|
|
141
|
+
|
|
142
|
+
- **`publish.yml` refuses a tag whose commit `main` does not contain** (#18). A tag alone
|
|
143
|
+
decides what is published, so a tag pushed from any branch would have published from
|
|
144
|
+
it. The ancestry is answerable because the checkout is already full-depth, and the ref
|
|
145
|
+
is fetched explicitly so an unresolvable `origin/main` is an error rather than a skip.
|
|
146
|
+
|
|
147
|
+
- **The `npm` deployment environment now says what it gates and why it has no protection
|
|
148
|
+
rule** (#18): it is the OIDC subject npm's trusted publisher is configured against, so
|
|
149
|
+
it is part of the credential, and it has no required reviewer deliberately — the
|
|
150
|
+
release is already gated by a verdict covering HEAD and by every gate running against
|
|
151
|
+
the exact tarball uploaded. Recorded so "no rule needed" is distinguishable from
|
|
152
|
+
"nobody looked".
|
|
153
|
+
|
|
154
|
+
### Fixed
|
|
155
|
+
|
|
156
|
+
- **The shipped API reference described the wrong server.** `API.md` and
|
|
157
|
+
`catalogue.json` both ship, and both still said `viafrei 1.3.22` from a 2026-09-28
|
|
158
|
+
capture — in a package published as 1.4.6, whose whole point is that the package and
|
|
159
|
+
the endpoint are named by one number. Found in review, and it was not merely dated:
|
|
160
|
+
re-capturing from `mcp.viafrei.de` shows the surface genuinely moved. The server
|
|
161
|
+
exposes a nineteenth tool, `find_fuel_station`, that the reference did not mention at
|
|
162
|
+
all, and `find_cheapest_fuel` and `check_transit_disruption` have new descriptions.
|
|
163
|
+
|
|
164
|
+
Re-captured read-only — `initialize`, `tools/list`, `resources/list`,
|
|
165
|
+
`resources/templates/list`, `prompts/list`, no tool invoked, so no data provider was
|
|
166
|
+
contacted — and the session was closed afterwards. The two substitutions the snapshot
|
|
167
|
+
documents are unchanged and were asserted rather than assumed: every tool's `$schema`
|
|
168
|
+
URI is dropped (19 of 19), and the two 288-character date patterns are stored as
|
|
169
|
+
`patternLength`.
|
|
170
|
+
|
|
171
|
+
**No gate could have caught this**, which is the part worth keeping. `check:docs`
|
|
172
|
+
passed throughout: it proves `API.md` matches `catalogue.json`, so a stale pair passes
|
|
173
|
+
together. Nothing in this repository compares the snapshot against the running server,
|
|
174
|
+
and `npm ci`-style version agreement cannot see it either. The reference's currency is
|
|
175
|
+
checked by a person at the cut, and that is now a step rather than a habit.
|
|
176
|
+
|
|
177
|
+
- **`SOURCES.md` printed an attribution string the server had stopped sending** (#332).
|
|
178
|
+
The shipped document is what a reader consults to know whose data they are looking at,
|
|
179
|
+
so a stale attribution line there is wrong in the one place it matters.
|
|
180
|
+
|
|
181
|
+
- **Every push ran CI twice** (#23). `push: branches: ['**']` and `pull_request` both
|
|
182
|
+
fired for a branch with an open pull request, so one push ran the whole matrix twice —
|
|
183
|
+
four `build-and-test` jobs for two Node versions, doubling the wait and the minutes for
|
|
184
|
+
no added signal. `push` is now `main` only; everything else arrives through its pull
|
|
185
|
+
request. The cost is named in the workflow rather than discovered later: a branch pushed
|
|
186
|
+
with **no** pull request now gets no CI. That is the right trade here — the flow is
|
|
187
|
+
push-then-open-immediately, the reviewer reads local commits before the push, and the
|
|
188
|
+
publish workflow re-runs every gate against the tag regardless.
|
|
189
|
+
|
|
190
|
+
- **Four README links resolved on the package page but not inside the tarball** (#23).
|
|
191
|
+
`README.md` ships and linked to `CODE_OF_CONDUCT.md`, `CONTRIBUTING.md`, `SECURITY.md`
|
|
192
|
+
and `SUPPORT.md`, none of which do. Three were pre-existing — verified in the published
|
|
193
|
+
1.3.15 tarball — and `SUPPORT.md` was added by 1.3.16, so that release made an existing
|
|
194
|
+
condition one worse. npm rewrites relative links in the rendered README to the
|
|
195
|
+
repository, so it only bit someone reading an unpacked tarball. All four are now
|
|
196
|
+
absolute, which is the honest form: a link that means "the repository" says so, and it
|
|
197
|
+
survives any packaging change. The two that remain relative, `API.md` and `SOURCES.md`,
|
|
198
|
+
are files the tarball carries — and that is now **asserted by the tarball gate**, which
|
|
199
|
+
reads the README *inside* the built tarball, extracts every relative Markdown link and
|
|
200
|
+
fails if one is not among the shipped paths. So the fifth such link is caught rather
|
|
201
|
+
than noticed three releases later. An earlier draft of this sentence said "asserted"
|
|
202
|
+
when nothing asserted it, which is the third time an entry in this file has claimed a
|
|
203
|
+
mechanism the tree did not contain; this time the mechanism was written instead of the
|
|
204
|
+
sentence being softened.
|
|
205
|
+
|
|
206
|
+
The rule then arrived with **no case in the gate's own self-test**, and the suite's case
|
|
207
|
+
count stayed where it was, which is how the review found it: a count that does not move
|
|
208
|
+
when a rule is added is the suite saying so. It now has two cases, because the rule's
|
|
209
|
+
first draft could not see an ANCHORED link — `](CONTRIBUTING.md#merging)` to a file the
|
|
210
|
+
tarball does not carry passed silently, and that is the likeliest fifth link there is.
|
|
211
|
+
Each was proved red on its own: neutering the rule fails both, and restoring the
|
|
212
|
+
anchor-blind capture fails only the anchored one.
|
|
213
|
+
|
|
214
|
+
- **Two comments carried counts that read as inventories** (#23). `scripts/tools.mjs` said
|
|
215
|
+
"TWENTY sites were changed in all" where twenty was what one commit changed, not what
|
|
216
|
+
the tree holds; it now says so. `scripts/tools.test.mjs` said a third caller of the
|
|
217
|
+
shared precondition arrived "within the week" when it arrived the **same day**, which is
|
|
218
|
+
the harder version of its own point.
|
|
219
|
+
|
|
220
|
+
### Deliberately not done
|
|
221
|
+
|
|
222
|
+
- **`javascript:S2187` on every self-test** (#23, item 1) — *cannot be done from the
|
|
223
|
+
repository.* SonarCloud reads `scripts/*.test.mjs` as test files, finds no framework
|
|
224
|
+
assertions, and reports "add some tests to this file or delete it" at BLOCKER on each.
|
|
225
|
+
There are **five** as of this branch, because the version gate above brings its own — so
|
|
226
|
+
this work adds a file to the class it is declaring unfixable, which is the part worth
|
|
227
|
+
knowing before the next self-test is written.
|
|
228
|
+
Measured: analysis here is **Automatic** (no scanner step in any workflow) and
|
|
229
|
+
`api/settings/values` returns no `sonar.tests` or `sonar.test.inclusions`, so the test
|
|
230
|
+
patterns live in SonarCloud's own UI and changing them needs a token this repository
|
|
231
|
+
does not hold. A `sonar-project.properties` was **not** added, because Automatic
|
|
232
|
+
Analysis may ignore it and a config file that silently does nothing is worse than the
|
|
233
|
+
finding. The two real options are a UI change to the test patterns, or renaming the
|
|
234
|
+
convention to `*.selftest.mjs` — which touches `package.json` scripts, CI steps and the
|
|
235
|
+
sweep that counts them. The quality gate passes on all five conditions either way.
|
|
236
|
+
|
|
237
|
+
- **The four remaining bare `.sort()` calls** — left, and named so the next person is not
|
|
238
|
+
ambushed. SonarCloud raised `javascript:S2871` (CRITICAL, type BUG) on the `.sort()` in
|
|
239
|
+
`relativeMarkdownLinks` and took the new-code reliability rating to D against an A
|
|
240
|
+
threshold, which is a required check. The line's behaviour was never wrong — every
|
|
241
|
+
element is a string and code-unit order is what is wanted — but the round-3 refactor
|
|
242
|
+
moved the expression into a new exported function, so a pattern older than this branch
|
|
243
|
+
became *new code* and failed a gate it had never been measured by. That is the trap, and
|
|
244
|
+
it is still loaded four times over:
|
|
245
|
+
|
|
246
|
+
git grep -n '\.sort()' -- '*.mjs' '*.ts' | grep -v '^\S*:[0-9]*: \*'
|
|
247
|
+
|
|
248
|
+
finds them in `scripts/check-tarball.mjs`, twice in `scripts/tools.test.mjs`, and in
|
|
249
|
+
`test/relay.test.ts`. All four are string arrays and all four are correct today; none is
|
|
250
|
+
in this PR's new-code period, so none fails the gate now. **Converting them here would
|
|
251
|
+
add three files to a diff that is already fifteen**, so they are recorded instead: the
|
|
252
|
+
next PR that so much as moves one of those lines should expect a CRITICAL BUG on code it
|
|
253
|
+
only touched, and should fix it in that PR rather than discovering it from a red required
|
|
254
|
+
check after the push, which is how this one was found.
|
|
255
|
+
|
|
256
|
+
Not `localeCompare`, whichever PR does it. It is what the rule suggests and it is wrong
|
|
257
|
+
here twice: it reorders (`B a` becomes `a B`) and it is locale-dependent (`ä` sorts before
|
|
258
|
+
`z` under `en`/`de` and after it under `sv`), so a gate's output would depend on the
|
|
259
|
+
runner. The comparator added here says only what the default already did, and the order
|
|
260
|
+
is pinned by a case in the self-test.
|
|
261
|
+
|
|
262
|
+
- **A shared assertion harness for the self-tests** (#23, item 5) — declined, on the
|
|
263
|
+
issue's own condition. Each self-test defines its own `check()`/`refuse()` pair, and the
|
|
264
|
+
issue says to unify them only if it can be done without weakening the per-file refusal
|
|
265
|
+
wording. It cannot, cheaply: the refusals deliberately name different builders and exit
|
|
266
|
+
by different routes, and that difference is load-bearing — it is what tells a reader
|
|
267
|
+
which fixture failed. The duplication is seven small functions across five files — four
|
|
268
|
+
define `check()`, three define `refuse()` — not the eleven-line block that caused the
|
|
269
|
+
3.1% duplication failure `fixture-root.mjs` was extracted to fix.
|
|
270
|
+
|
|
271
|
+
### Note on a count in the 1.3.16 entry below
|
|
272
|
+
|
|
273
|
+
That entry says the tool sweep reads "thirty-two source files". It now reads **34**,
|
|
274
|
+
because this work adds two. The published entry is **left alone**: it was measured at
|
|
275
|
+
that release and editing a shipped block to keep a number current is how a changelog
|
|
276
|
+
stops being a record. The figure to trust is the one `npm run test:tools` prints, which
|
|
277
|
+
is why nothing here states a total either.
|
|
278
|
+
|
|
10
279
|
## [1.3.22] - 2026-09-28
|
|
11
280
|
|
|
12
281
|
**A fresh snapshot of the production server, and the version number that goes with it.
|
package/README.md
CHANGED
|
@@ -161,7 +161,7 @@ Restart the client and ask it one of the questions above.
|
|
|
161
161
|
| **[API.md](API.md)** | Every tool with its parameters, types, defaults and constraints, plus the resources, resource templates and prompts. Generated from a dated snapshot of the running server, so the descriptions are the server's own words — which is what your assistant actually reads when it picks a tool. |
|
|
162
162
|
| **[Wiki](https://github.com/mavrovde/viafrei-bridge/wiki)** | The prose half: [connecting your assistant](https://github.com/mavrovde/viafrei-bridge/wiki/Connecting-your-assistant), [tools at a glance](https://github.com/mavrovde/viafrei-bridge/wiki/Tools-at-a-glance), the [roadmap](https://github.com/mavrovde/viafrei-bridge/wiki/Roadmap) and an [FAQ](https://github.com/mavrovde/viafrei-bridge/wiki/FAQ). |
|
|
163
163
|
| **[SOURCES.md](SOURCES.md)** | Every publisher, what it covers, its licence, and the attribution line to reproduce — including the conditions that are licence breaches rather than style problems. |
|
|
164
|
-
| **[SUPPORT.md](SUPPORT.md)** | Where a question, a bad answer or a security report should go, and what makes a report easy to act on. |
|
|
164
|
+
| **[SUPPORT.md](https://github.com/mavrovde/viafrei-bridge/blob/main/SUPPORT.md)** | Where a question, a bad answer or a security report should go, and what makes a report easy to act on. |
|
|
165
165
|
|
|
166
166
|
### Worked use cases
|
|
167
167
|
|
|
@@ -339,8 +339,8 @@ This is said plainly so nobody spends an evening looking for the server code.
|
|
|
339
339
|
|
|
340
340
|
## Contributing
|
|
341
341
|
|
|
342
|
-
Yes, please — see [CONTRIBUTING.md](CONTRIBUTING.md) and
|
|
343
|
-
[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). Issues and discussions are open. The
|
|
342
|
+
Yes, please — see [CONTRIBUTING.md](https://github.com/mavrovde/viafrei-bridge/blob/main/CONTRIBUTING.md) and
|
|
343
|
+
[CODE_OF_CONDUCT.md](https://github.com/mavrovde/viafrei-bridge/blob/main/CODE_OF_CONDUCT.md). Issues and discussions are open. The
|
|
344
344
|
bridge is small and self-contained, which is exactly what makes it a reasonable
|
|
345
345
|
thing to send a first patch to.
|
|
346
346
|
|
|
@@ -352,7 +352,7 @@ cannot get any other way.
|
|
|
352
352
|
## Security
|
|
353
353
|
|
|
354
354
|
Never open a public issue for a key, a token or anything that looks like one.
|
|
355
|
-
See [SECURITY.md](SECURITY.md) for the private reporting path.
|
|
355
|
+
See [SECURITY.md](https://github.com/mavrovde/viafrei-bridge/blob/main/SECURITY.md) for the private reporting path.
|
|
356
356
|
|
|
357
357
|
## Licence
|
|
358
358
|
|
package/SOURCES.md
CHANGED
|
@@ -310,13 +310,13 @@ sourceNote : null
|
|
|
310
310
|
```
|
|
311
311
|
|
|
312
312
|
So GovData's harvest is faithful and there is no versioned statement anywhere in
|
|
313
|
-
the chain
|
|
314
|
-
|
|
315
|
-
rather than by rewording this page — what this page
|
|
316
|
-
actually sends.
|
|
313
|
+
the chain, and the attribution string the server emits says exactly that: the
|
|
314
|
+
licence is CC BY-SA and the version is unstated. It named 4.0 until v1.4.1, which
|
|
315
|
+
corrected it at the source rather than by rewording this page — what this page
|
|
316
|
+
documents is what the server actually sends.
|
|
317
317
|
|
|
318
318
|
```
|
|
319
|
-
Echtzeitdaten: DELFI e.V. via Mobilithek, CC BY-SA
|
|
319
|
+
Echtzeitdaten: DELFI e.V. via Mobilithek, CC BY-SA
|
|
320
320
|
```
|
|
321
321
|
|
|
322
322
|
Share-alike. See [the obligation above](#public-transport-realtime-is-share-alike):
|
package/dist/bridge.d.ts
CHANGED
|
@@ -8,16 +8,4 @@ export interface BridgeHooks {
|
|
|
8
8
|
export interface BridgeHandle {
|
|
9
9
|
close: () => Promise<void>;
|
|
10
10
|
}
|
|
11
|
-
/**
|
|
12
|
-
* Relay every JSON-RPC message between a local stdio client and a remote
|
|
13
|
-
* Streamable-HTTP MCP endpoint, in both directions, unchanged.
|
|
14
|
-
*
|
|
15
|
-
* The bridge is deliberately a *message* relay and not a client/server pair
|
|
16
|
-
* that re-implements the protocol: it never has an opinion about a method it
|
|
17
|
-
* has not heard of, so a tool added on the server works here the same day
|
|
18
|
-
* without a release. The only messages it looks inside are `initialize` and its
|
|
19
|
-
* answer - because the session's protocol version has to end up on the HTTP
|
|
20
|
-
* headers, and because a version the server cannot speak deserves a sentence
|
|
21
|
-
* rather than a stack trace.
|
|
22
|
-
*/
|
|
23
11
|
export declare function startBridge(options: Options, hooks: BridgeHooks): Promise<BridgeHandle>;
|
package/dist/bridge.js
CHANGED
|
@@ -52,17 +52,44 @@ function readSupportedVersions(data) {
|
|
|
52
52
|
return [...new Set(versions)];
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
55
|
+
* Reconcile the server's answer to `initialize`, and return the line that must
|
|
56
|
+
* end the session — AFTER the answer has been relayed, never instead of it, which
|
|
57
|
+
* is why this returns the line rather than exiting.
|
|
57
58
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
59
|
+
* Extracted from `remote.onmessage`, which was cognitive complexity 18 (#19) with
|
|
60
|
+
* this nested four deep inside the relay path. Nothing here decides anything new:
|
|
61
|
+
* a usable version is applied through `onNegotiated`, a mismatch warns and relays
|
|
62
|
+
* unchanged, a rejection returns the fatal line, and any other shape is relayed
|
|
63
|
+
* with no comment.
|
|
64
|
+
*
|
|
65
|
+
* Two of the three behaviours this moved were NOT covered when the move was made,
|
|
66
|
+
* measured by deleting them: `setProtocolVersion` and the "did not say which
|
|
67
|
+
* versions" half of the sentence both survived with the whole suite green. Both
|
|
68
|
+
* now have a case. The third — `initialized = true` — is still ungated: it is
|
|
69
|
+
* observable only through a LATER failure's exit code, and it sits inside the
|
|
70
|
+
* `onNegotiated` callback whose invocation the new header case does gate. So a
|
|
71
|
+
* callback that stops firing is caught; a callback that fires and drops that one
|
|
72
|
+
* assignment is not. Said here rather than left for someone to assume otherwise.
|
|
65
73
|
*/
|
|
74
|
+
function reconcileInitialize(message, session) {
|
|
75
|
+
if (isJSONRPCResultResponse(message)) {
|
|
76
|
+
const served = readProtocolVersion(message.result);
|
|
77
|
+
if (served === undefined) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
session.onNegotiated(served);
|
|
81
|
+
if (session.requested !== undefined && served !== session.requested) {
|
|
82
|
+
session.warn(`viafrei: ${session.url} speaks MCP protocol ${served}, this client asked for ${session.requested}; relaying the server's answer unchanged`);
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
if (isJSONRPCErrorResponse(message)) {
|
|
87
|
+
const supported = readSupportedVersions(message.error.data);
|
|
88
|
+
const spoken = supported.length > 0 ? `the server speaks ${supported.join(', ')}` : `the server did not say which versions it speaks`;
|
|
89
|
+
return `viafrei: ${session.url} rejected MCP protocol version ${session.requested ?? '(unspecified)'}; ${spoken}`;
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
66
93
|
export async function startBridge(options, hooks) {
|
|
67
94
|
const remote = new StreamableHTTPClientTransport(new URL(options.url), {
|
|
68
95
|
fetch: createFetch(options.timeoutMs),
|
|
@@ -153,21 +180,15 @@ export async function startBridge(options, hooks) {
|
|
|
153
180
|
let fatalAfterRelay;
|
|
154
181
|
if (pending !== undefined && id !== undefined) {
|
|
155
182
|
pendingInitialize.delete(id);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
183
|
+
fatalAfterRelay = reconcileInitialize(message, {
|
|
184
|
+
requested: pending.requested,
|
|
185
|
+
url: options.url,
|
|
186
|
+
onNegotiated: served => {
|
|
159
187
|
remote.setProtocolVersion?.(served);
|
|
160
188
|
initialized = true;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
else if (isJSONRPCErrorResponse(message)) {
|
|
167
|
-
const supported = readSupportedVersions(message.error.data);
|
|
168
|
-
const spoken = supported.length > 0 ? `the server speaks ${supported.join(', ')}` : `the server did not say which versions it speaks`;
|
|
169
|
-
fatalAfterRelay = `viafrei: ${options.url} rejected MCP protocol version ${pending.requested ?? '(unspecified)'}; ${spoken}`;
|
|
170
|
-
}
|
|
189
|
+
},
|
|
190
|
+
warn: hooks.warn
|
|
191
|
+
});
|
|
171
192
|
}
|
|
172
193
|
void local.send(message).then(() => {
|
|
173
194
|
if (fatalAfterRelay !== undefined) {
|
package/dist/config.d.ts
CHANGED
|
@@ -59,11 +59,5 @@ export interface Options {
|
|
|
59
59
|
}
|
|
60
60
|
export declare class UsageError extends Error {
|
|
61
61
|
}
|
|
62
|
-
/**
|
|
63
|
-
* Parse argv (without `node` and the script) plus the environment.
|
|
64
|
-
*
|
|
65
|
-
* Precedence is the one people expect: a flag beats an environment variable,
|
|
66
|
-
* an environment variable beats the built-in default.
|
|
67
|
-
*/
|
|
68
62
|
export declare function parseOptions(argv: readonly string[], env?: NodeJS.ProcessEnv): Options;
|
|
69
63
|
export declare function helpText(version: string): string;
|
package/dist/config.js
CHANGED
|
@@ -103,6 +103,95 @@ function validateUrl(raw, source) {
|
|
|
103
103
|
* Precedence is the one people expect: a flag beats an environment variable,
|
|
104
104
|
* an environment variable beats the built-in default.
|
|
105
105
|
*/
|
|
106
|
+
/**
|
|
107
|
+
* Flags that take no value, and which boolean each one sets.
|
|
108
|
+
*
|
|
109
|
+
* A table rather than a chain of `===` comparisons, so an alias is a KEY: adding
|
|
110
|
+
* `-?` is one line and cannot be added to one spelling and forgotten in another.
|
|
111
|
+
* The risk a table carries is the opposite one - a key silently absent - so the
|
|
112
|
+
* aliases are pinned in `config.test.ts` as equivalences to their long forms
|
|
113
|
+
* rather than one case each. `-h` had no case at all before this table existed,
|
|
114
|
+
* and deleting it from the old chain left the whole suite green.
|
|
115
|
+
*/
|
|
116
|
+
const BOOLEAN_FLAGS = new Map([
|
|
117
|
+
['--help', 'showHelp'],
|
|
118
|
+
['-h', 'showHelp'],
|
|
119
|
+
['--version', 'showVersion'],
|
|
120
|
+
['-V', 'showVersion']
|
|
121
|
+
]);
|
|
122
|
+
const VALUE_FLAGS = [
|
|
123
|
+
{
|
|
124
|
+
names: ['--url'],
|
|
125
|
+
apply: (options, value, source) => {
|
|
126
|
+
options.url = validateUrl(value, source);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
names: ['--header', '-H'],
|
|
131
|
+
apply: (options, value, source) => {
|
|
132
|
+
const [name, headerValue] = parseHeader(value, source);
|
|
133
|
+
options.headers[name] = headerValue;
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
names: ['--timeout'],
|
|
138
|
+
apply: (options, value, source) => {
|
|
139
|
+
options.timeoutMs = parseTimeout(value, source);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
];
|
|
143
|
+
/**
|
|
144
|
+
* The environment, applied before argv so that a flag beats a variable by
|
|
145
|
+
* POSITION rather than by a rule — and so a `--header` of the same name
|
|
146
|
+
* overwrites this one while a `--header` of a different name joins it.
|
|
147
|
+
*/
|
|
148
|
+
function applyEnvironment(options, env) {
|
|
149
|
+
const url = env[URL_ENV_VAR]?.trim();
|
|
150
|
+
if (url !== undefined && url !== '') {
|
|
151
|
+
options.url = validateUrl(url, URL_ENV_VAR);
|
|
152
|
+
}
|
|
153
|
+
const timeout = env[TIMEOUT_ENV_VAR]?.trim();
|
|
154
|
+
if (timeout !== undefined && timeout !== '') {
|
|
155
|
+
options.timeoutMs = parseTimeout(timeout, TIMEOUT_ENV_VAR);
|
|
156
|
+
}
|
|
157
|
+
const headers = env[HEADER_ENV_VAR];
|
|
158
|
+
if (headers === undefined || headers.trim() === '') {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
for (const line of headers.split(HEADER_ENV_SEPARATOR)) {
|
|
162
|
+
// A blank line is skipped rather than refused, so a trailing newline in
|
|
163
|
+
// the variable is not an error.
|
|
164
|
+
if (line.trim() !== '') {
|
|
165
|
+
const [name, value] = parseHeader(line, HEADER_ENV_VAR);
|
|
166
|
+
options.headers[name] = value;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* One argument. `readValue` consumes the NEXT argv entry and is called only for a
|
|
172
|
+
* flag that needs it, so `--url` at the end of argv still refuses by the same
|
|
173
|
+
* route it always did.
|
|
174
|
+
*/
|
|
175
|
+
function applyArgument(options, argument, readValue) {
|
|
176
|
+
const booleanTarget = BOOLEAN_FLAGS.get(argument);
|
|
177
|
+
if (booleanTarget !== undefined) {
|
|
178
|
+
options[booleanTarget] = true;
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
for (const flag of VALUE_FLAGS) {
|
|
182
|
+
const [canonical] = flag.names;
|
|
183
|
+
if (flag.names.includes(argument)) {
|
|
184
|
+
flag.apply(options, readValue(), canonical);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const inline = `${canonical}=`;
|
|
188
|
+
if (argument.startsWith(inline)) {
|
|
189
|
+
flag.apply(options, argument.slice(inline.length), canonical);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
throw new UsageError(`unknown argument ${JSON.stringify(argument)} - run "viafrei --help" for the list`);
|
|
194
|
+
}
|
|
106
195
|
export function parseOptions(argv, env = process.env) {
|
|
107
196
|
const options = {
|
|
108
197
|
url: DEFAULT_MCP_URL,
|
|
@@ -111,66 +200,17 @@ export function parseOptions(argv, env = process.env) {
|
|
|
111
200
|
showVersion: false,
|
|
112
201
|
showHelp: false
|
|
113
202
|
};
|
|
114
|
-
|
|
115
|
-
if (urlFromEnv !== undefined && urlFromEnv.trim() !== '') {
|
|
116
|
-
options.url = validateUrl(urlFromEnv.trim(), URL_ENV_VAR);
|
|
117
|
-
}
|
|
118
|
-
const timeoutFromEnv = env[TIMEOUT_ENV_VAR];
|
|
119
|
-
if (timeoutFromEnv !== undefined && timeoutFromEnv.trim() !== '') {
|
|
120
|
-
options.timeoutMs = parseTimeout(timeoutFromEnv.trim(), TIMEOUT_ENV_VAR);
|
|
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
|
-
}
|
|
203
|
+
applyEnvironment(options, env);
|
|
135
204
|
for (let index = 0; index < argv.length; index += 1) {
|
|
136
205
|
const argument = argv[index];
|
|
137
|
-
|
|
206
|
+
applyArgument(options, argument, () => {
|
|
138
207
|
const value = argv[index + 1];
|
|
139
208
|
if (value === undefined || value.startsWith('--')) {
|
|
140
209
|
throw new UsageError(`${argument} expects a value`);
|
|
141
210
|
}
|
|
142
211
|
index += 1;
|
|
143
212
|
return value;
|
|
144
|
-
};
|
|
145
|
-
if (argument === '--help' || argument === '-h') {
|
|
146
|
-
options.showHelp = true;
|
|
147
|
-
}
|
|
148
|
-
else if (argument === '--version' || argument === '-V') {
|
|
149
|
-
options.showVersion = true;
|
|
150
|
-
}
|
|
151
|
-
else if (argument === '--url') {
|
|
152
|
-
options.url = validateUrl(next(), '--url');
|
|
153
|
-
}
|
|
154
|
-
else if (argument.startsWith('--url=')) {
|
|
155
|
-
options.url = validateUrl(argument.slice('--url='.length), '--url');
|
|
156
|
-
}
|
|
157
|
-
else if (argument === '--header' || argument === '-H') {
|
|
158
|
-
const [name, value] = parseHeader(next());
|
|
159
|
-
options.headers[name] = value;
|
|
160
|
-
}
|
|
161
|
-
else if (argument.startsWith('--header=')) {
|
|
162
|
-
const [name, value] = parseHeader(argument.slice('--header='.length));
|
|
163
|
-
options.headers[name] = value;
|
|
164
|
-
}
|
|
165
|
-
else if (argument === '--timeout') {
|
|
166
|
-
options.timeoutMs = parseTimeout(next(), '--timeout');
|
|
167
|
-
}
|
|
168
|
-
else if (argument.startsWith('--timeout=')) {
|
|
169
|
-
options.timeoutMs = parseTimeout(argument.slice('--timeout='.length), '--timeout');
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
throw new UsageError(`unknown argument ${JSON.stringify(argument)} - run "viafrei --help" for the list`);
|
|
173
|
-
}
|
|
213
|
+
});
|
|
174
214
|
}
|
|
175
215
|
return options;
|
|
176
216
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viafrei",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.8",
|
|
4
4
|
"description": "German traffic, rail, parking, charging, fuel, address and weather data in your AI assistant - MCP, no API key. stdio bridge to the hosted ViaFrei MCP server.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -69,8 +69,10 @@
|
|
|
69
69
|
"test:leaks": "node scripts/check-leaks.test.mjs",
|
|
70
70
|
"test:tools": "node scripts/tools.test.mjs",
|
|
71
71
|
"test:docs": "node scripts/gen-api-doc.test.mjs",
|
|
72
|
+
"test:versions": "node scripts/check-versions.test.mjs",
|
|
72
73
|
"check:tarball": "node scripts/check-tarball.mjs",
|
|
73
74
|
"check:leaks": "node scripts/check-leaks.mjs",
|
|
75
|
+
"check:versions": "node scripts/check-versions.mjs",
|
|
74
76
|
"docs:api": "node scripts/gen-api-doc.mjs",
|
|
75
77
|
"check:docs": "node scripts/gen-api-doc.mjs --check",
|
|
76
78
|
"rules:show": "node scripts/show-rules.mjs"
|