sbb-mcp 0.4.2 → 0.4.3

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.
Files changed (53) hide show
  1. package/README.md +152 -233
  2. package/dist/cache.d.ts +2 -0
  3. package/dist/cache.js +4 -0
  4. package/dist/cache.js.map +1 -1
  5. package/dist/formatters.d.ts +13 -0
  6. package/dist/formatters.js +94 -19
  7. package/dist/formatters.js.map +1 -1
  8. package/dist/http.js +59 -3
  9. package/dist/http.js.map +1 -1
  10. package/dist/index.js +2 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/look2book.d.ts +98 -0
  13. package/dist/look2book.js +212 -0
  14. package/dist/look2book.js.map +1 -0
  15. package/dist/profile.js +15 -8
  16. package/dist/profile.js.map +1 -1
  17. package/dist/shortlink.d.ts +60 -0
  18. package/dist/shortlink.js +122 -0
  19. package/dist/shortlink.js.map +1 -0
  20. package/dist/structured.d.ts +7 -1
  21. package/dist/structured.js +2 -1
  22. package/dist/structured.js.map +1 -1
  23. package/dist/swisstrip.js +21 -5
  24. package/dist/swisstrip.js.map +1 -1
  25. package/dist/tools.d.ts +38 -0
  26. package/dist/tools.js +122 -77
  27. package/dist/tools.js.map +1 -1
  28. package/dist/transport/index.d.ts +1 -0
  29. package/dist/transport/index.js +4 -0
  30. package/dist/transport/index.js.map +1 -1
  31. package/dist/transport/setup.d.ts +1 -0
  32. package/dist/transport/setup.js +59 -0
  33. package/dist/transport/setup.js.map +1 -0
  34. package/dist/transport/smapi-auth.d.ts +14 -2
  35. package/dist/transport/smapi-auth.js +67 -17
  36. package/dist/transport/smapi-auth.js.map +1 -1
  37. package/dist/transport/smapi-client.d.ts +25 -1
  38. package/dist/transport/smapi-client.js +112 -9
  39. package/dist/transport/smapi-client.js.map +1 -1
  40. package/dist/transport/smapi-journey.d.ts +11 -5
  41. package/dist/transport/smapi-journey.js +16 -7
  42. package/dist/transport/smapi-journey.js.map +1 -1
  43. package/dist/transport/smapi-mock.js.map +1 -1
  44. package/dist/transport/smapi-prices.d.ts +43 -9
  45. package/dist/transport/smapi-prices.js +112 -41
  46. package/dist/transport/smapi-prices.js.map +1 -1
  47. package/dist/transport/smapi-types.d.ts +28 -0
  48. package/dist/widgets.d.ts +30 -3
  49. package/dist/widgets.js +78 -14
  50. package/dist/widgets.js.map +1 -1
  51. package/package.json +7 -3
  52. package/web/dist/widgets.css +1 -1
  53. package/web/dist/widgets.js +1 -1
package/README.md CHANGED
@@ -2,49 +2,36 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/sbb-mcp.svg)](https://www.npmjs.com/package/sbb-mcp)
4
4
  [![License: FSL-1.1-MIT](https://img.shields.io/badge/License-FSL--1.1--MIT-blue.svg)](https://fsl.software/)
5
- [![smithery badge](https://smithery.ai/badge/fabsforward2-zhoi/sbb-mcp)](https://smithery.ai/servers/fabsforward2-zhoi/sbb-mcp)
5
+ [![smithery](https://smithery.ai/badge/fabsforward2-zhoi/sbb-mcp)](https://smithery.ai/servers/fabsforward2-zhoi/sbb-mcp)
6
6
 
7
- MCP server for **Swiss Federal Railways** (SBB/CFF/FFS) -- real-time train schedules, prices, and ticket purchase links for any AI assistant.
7
+ > **Swiss trains in any AI assistant.** Real-time schedules, ticket prices with Half-Fare/GA discounts, and direct purchase links for Claude Desktop, ChatGPT, Cursor, and any MCP-compatible client.
8
8
 
9
- Works with Claude Desktop, Claude Code, Cursor, Windsurf, VS Code Copilot, ChatGPT, and any MCP-compatible AI client.
9
+ ```
10
+ You: trains from Bern to Zermatt tomorrow at 9am, with Halbtax
11
+ AI: IC 8 14:32 → 17:24 (2h52, 1 transfer in Visp, Pl. 8) — CHF 42.50
12
+ IC 8 15:02 → 17:54 (2h52, 1 transfer in Visp, Pl. 8) — CHF 42.50
13
+ [Buy on SBB.ch ↗]
14
+ ```
10
15
 
11
- ## Features
16
+ Ask any AI assistant to find Swiss trains. It returns connections, prices with your reduction card applied, and a direct ticket-purchase link to sbb.ch — all through natural language.
12
17
 
13
- - **Search stations** -- find any Swiss train station by name
14
- - **Train connections** -- real-time schedules with departure/arrival times, platforms, transfers
15
- - **Ticket prices** -- 1st/2nd class with Half-Fare (Halbtax) and GA travelcard support
16
- - **Purchase links** -- direct deep links to buy tickets on SBB.ch
17
- - **Pagination** -- browse earlier/later connections
18
- - **Trip details** -- intermediate stops, occupancy, platform changes
19
- - **Journey planning** -- built-in prompt for end-to-end trip planning
20
- - **Destination weather** -- automatic weather forecast for your destination (powered by [swiss-weather-mcp](https://www.npmjs.com/package/swiss-weather-mcp))
21
- - **Multi-traveler** -- family trip pricing when connected to SwissTrip (partner, kids, each with their own reduction card)
22
- - **Multilingual** -- output in 9 languages: de, fr, it, en, es, pt, ru, ar, zh (v0.4.0+)
23
- - **Rich widgets in ChatGPT** -- connection cards, trip timelines, price tables, and ticket cards via the OpenAI Apps SDK (v0.4.1+)
24
- - **Mock mode** -- works without credentials for testing and demos
18
+ > **Not affiliated with SBB.** Independent, community-maintained MCP server built on SBB's public Swiss Mobility API (SMAPI). Not affiliated with or endorsed by SBB (Schweizerische Bundesbahnen).
25
19
 
26
- ## Quick Start
20
+ ---
27
21
 
28
- ### Claude Desktop
22
+ ## Quick install
29
23
 
30
- Add to your `claude_desktop_config.json`:
24
+ ### ChatGPT (no install — hosted)
31
25
 
32
- ```json
33
- {
34
- "mcpServers": {
35
- "sbb": {
36
- "command": "npx",
37
- "args": ["-y", "sbb-mcp"],
38
- "env": {
39
- "SMAPI_CLIENT_ID": "your-client-id",
40
- "SMAPI_CLIENT_SECRET": "your-secret",
41
- "SMAPI_SCOPE": "your-scope",
42
- "SMAPI_CONTRACT_ID": "your-contract-id"
43
- }
44
- }
45
- }
46
- }
47
- ```
26
+ ChatGPT Plus / Pro / Business / Enterprise with Developer Mode enabled:
27
+
28
+ 1. **Settings → Connectors** → enable **Developer Mode**
29
+ 2. Click **Add custom connector**
30
+ 3. **MCP Server URL:** `https://mcp.swisstrip.app/mcp`
31
+ 4. **Authentication:** None
32
+ 5. Save, then enable via the **+** button in the composer
33
+
34
+ Tool responses render as native ChatGPT widgets (connection cards, trip timelines, price tables) via the OpenAI Apps SDK.
48
35
 
49
36
  ### Claude Code
50
37
 
@@ -52,288 +39,220 @@ Add to your `claude_desktop_config.json`:
52
39
  claude mcp add sbb -- npx -y sbb-mcp
53
40
  ```
54
41
 
55
- ### Cursor
42
+ Done — try `/mcp` in Claude to confirm it's loaded, then ask about Swiss trains.
56
43
 
57
- Add to `.cursor/mcp.json`:
44
+ ### Claude Desktop
45
+
46
+ Add to `claude_desktop_config.json`:
58
47
 
59
48
  ```json
60
49
  {
61
50
  "mcpServers": {
62
51
  "sbb": {
63
52
  "command": "npx",
64
- "args": ["-y", "sbb-mcp"],
65
- "env": {
66
- "SMAPI_CLIENT_ID": "your-client-id",
67
- "SMAPI_CLIENT_SECRET": "your-secret",
68
- "SMAPI_SCOPE": "your-scope",
69
- "SMAPI_CONTRACT_ID": "your-contract-id"
70
- }
53
+ "args": ["-y", "sbb-mcp"]
71
54
  }
72
55
  }
73
56
  }
74
57
  ```
75
58
 
76
- ### Windsurf
59
+ Restart Claude Desktop. That's it — works in **mock mode** out of the box with realistic Swiss station data. Add credentials later for live SBB data (see below).
77
60
 
78
- Add to `~/.codeium/windsurf/mcp_config.json`:
61
+ ### Cursor / Windsurf / VS Code Copilot
79
62
 
80
- ```json
81
- {
82
- "mcpServers": {
83
- "sbb": {
84
- "command": "npx",
85
- "args": ["-y", "sbb-mcp"],
86
- "env": {
87
- "SMAPI_CLIENT_ID": "your-client-id",
88
- "SMAPI_CLIENT_SECRET": "your-secret",
89
- "SMAPI_SCOPE": "your-scope",
90
- "SMAPI_CONTRACT_ID": "your-contract-id"
91
- }
92
- }
93
- }
94
- }
63
+ Same `mcpServers` block as Claude Desktop, in the respective config file:
64
+
65
+ | Client | Config path |
66
+ |---|---|
67
+ | Cursor | `.cursor/mcp.json` |
68
+ | Windsurf | `~/.codeium/windsurf/mcp_config.json` |
69
+ | VS Code Copilot | `.vscode/mcp.json` (use top-level `servers` instead of `mcpServers`) |
70
+
71
+ ### Try it without installing
72
+
73
+ ```bash
74
+ npx sbb-mcp
95
75
  ```
96
76
 
97
- ### VS Code Copilot
77
+ Runs in mock mode with realistic Swiss station data — no setup, no credentials. Useful for testing the protocol or building demos.
78
+
79
+ ---
80
+
81
+ ## Live SBB data (optional)
82
+
83
+ Mock mode works out of the box. For real-time schedules and prices, get credentials from the [SBB Developer Portal](https://developer.sbb.ch) (free signup, OAuth 2.0 + business contract).
98
84
 
99
- Add to `.vscode/mcp.json`:
85
+ Add to your config's `env` block:
100
86
 
101
87
  ```json
102
88
  {
103
- "servers": {
89
+ "mcpServers": {
104
90
  "sbb": {
105
91
  "command": "npx",
106
92
  "args": ["-y", "sbb-mcp"],
107
93
  "env": {
108
- "SMAPI_CLIENT_ID": "your-client-id",
109
- "SMAPI_CLIENT_SECRET": "your-secret",
110
- "SMAPI_SCOPE": "your-scope",
111
- "SMAPI_CONTRACT_ID": "your-contract-id"
94
+ "SMAPI_JOURNEY_CLIENT_ID": "...",
95
+ "SMAPI_JOURNEY_CLIENT_SECRET": "...",
96
+ "SMAPI_JOURNEY_SCOPE": "api://.../.default",
97
+ "SMAPI_TICKETING_CLIENT_ID": "...",
98
+ "SMAPI_TICKETING_CLIENT_SECRET": "...",
99
+ "SMAPI_TICKETING_SCOPE": "api://.../.default",
100
+ "SMAPI_CONTRACT_ID": "..."
112
101
  }
113
102
  }
114
103
  }
115
104
  }
116
105
  ```
117
106
 
118
- ### Smithery
107
+ SBB issues separate AAD apps per API. The Journey API is for schedules/stations; the Ticketing API for prices/booking links. Set whichever you have approved — the server falls back to mock data per family if a credential set is missing.
119
108
 
120
- Install via [Smithery](https://smithery.ai/servers/fabsforward2-zhoi/sbb-mcp):
109
+ For backwards compatibility, single-app setups can use the generic `SMAPI_CLIENT_ID` / `SMAPI_CLIENT_SECRET` / `SMAPI_SCOPE` for both APIs.
121
110
 
122
- ```bash
123
- npx @smithery/cli mcp add fabsforward2-zhoi/sbb-mcp
124
- ```
111
+ ---
125
112
 
126
- ### Demo Mode (No Credentials)
113
+ ## What it does
127
114
 
128
- Run without any environment variables to use mock data:
115
+ - **Search stations** — find any Swiss train station by name, get UIC IDs
116
+ - **Train connections** — schedules with departure/arrival times, platforms, transfer info, occupancy forecasts
117
+ - **Ticket prices** — 1st/2nd class with Half-Fare (Halbtax) and GA travelcard support
118
+ - **Purchase links** — direct deep links to buy on SBB.ch (opens the SBB app on mobile with discounts applied)
119
+ - **Pagination** — browse earlier/later connections without losing context
120
+ - **Trip details** — intermediate stops, platforms, real-time status
121
+ - **Destination weather** — auto-included in connection results via [`swiss-weather-mcp`](https://www.npmjs.com/package/swiss-weather-mcp)
122
+ - **Multilingual** — output in 9 languages (`de fr it en es pt ru ar zh`)
123
+ - **ChatGPT widgets** — connection cards, price tables, ticket cards rendered as native UI via OpenAI Apps SDK
124
+ - **Mock mode** — full demo data without credentials
129
125
 
130
- ```bash
131
- npx sbb-mcp
132
- ```
126
+ ---
133
127
 
134
- This lets you test the MCP server with realistic Swiss station data without SBB API credentials.
128
+ ## Tools
135
129
 
136
- ## Multilingual output (v0.4.0+)
130
+ 9 MCP tools, all support an optional `language` argument.
137
131
 
138
- Every tool accepts an optional `language` argument — one of `de`, `fr`, `it`, `en`, `es`, `pt`, `ru`, `ar`, `zh`. This controls the output language for labels, date/time formatting, and the ticket-buy button text. It also sets the `Accept-Language` header on SBB API calls so station names come back in the requested language where the SBB API supports it (de/fr/it/en).
132
+ | Tool | What it does |
133
+ |---|---|
134
+ | `search_stations` | Find stations by name → UIC IDs |
135
+ | `search_connections` | Schedules between two stations (returns trip IDs) |
136
+ | `get_trip_details` | Stop-by-stop info, platforms, occupancy |
137
+ | `get_more_connections` | Earlier/later trains via pagination |
138
+ | `get_prices` | Ticket prices with reduction card applied |
139
+ | `get_ticket_link` | Direct purchase link to SBB.ch (mobile-app deep link) |
140
+ | `save_profile` | Save user's reduction card + DOB locally for auto-apply |
141
+ | `get_profile` | Read saved profile |
142
+ | `list_travelers` | Multi-traveler family pricing (requires SwissTrip account) |
139
143
 
140
- Resolution order: `language` arg saved profile language (`save_profile`) → English.
144
+ Plus a `plan_journey` prompt for end-to-end trip planning.
141
145
 
142
- ```jsonc
143
- // Example: French output
144
- { "from": "Zurich HB", "to": "Bern", "language": "fr" }
145
- ```
146
+ ### Multilingual
146
147
 
147
- The SBB deep link path uses `de/fr/it/en` where supported and falls back to `en` for other languages (SBB.ch serves those four).
148
-
149
- Save a default language once per user:
148
+ Every tool accepts `language: "de" | "fr" | "it" | "en" | "es" | "pt" | "ru" | "ar" | "zh"`. Sets `Accept-Language` on SBB API calls so station names come back in the requested language; localizes the buy-button text on deep links.
150
149
 
151
150
  ```jsonc
152
- { "tool": "save_profile", "language": "de" }
151
+ // French output
152
+ { "from": "Zürich HB", "to": "Genève", "language": "fr" }
153
153
  ```
154
154
 
155
- ## Tools
156
-
157
- ### search_stations
158
-
159
- Search for Swiss train stations by name. Returns station IDs needed for other tools.
160
-
161
- **Input:**
162
- - `query` (string, required) -- Station name, e.g. "Zurich", "Bern", "Interlaken"
163
- - `limit` (number, optional) -- Max results (default: 10)
164
-
165
- **Example:** "Find stations matching Luzern"
166
-
167
- ### search_connections
168
-
169
- Find train connections between two stations. Automatically resolves station names to IDs.
170
-
171
- **Input:**
172
- - `from` (string, required) -- Origin station name or ID (e.g. "Zurich HB" or "8503000")
173
- - `to` (string, required) -- Destination station name or ID
174
- - `date` (string, optional) -- Travel date YYYY-MM-DD
175
- - `time` (string, optional) -- Departure time HH:MM
176
- - `arrival_time` (boolean, optional) -- Treat time as arrival time
177
-
178
- **Example:** "Show me trains from Zurich to Bern tomorrow at 9am"
179
-
180
- Results automatically include destination weather when coordinates are available (e.g. **Bern weather:** 6-18°C, mostly sunny, 10% rain).
181
-
182
- ### get_trip_details
183
-
184
- Get detailed stop-by-stop information for a connection including intermediate stops, platforms, and occupancy forecasts.
185
-
186
- **Input:**
187
- - `trip_id` (string, required) -- Trip ID from search_connections
188
-
189
- ### get_more_connections
190
-
191
- Load earlier or later trains for a previous search.
192
-
193
- **Input:**
194
- - `collection_id` (string, required) -- Collection ID from search_connections
195
- - `direction` ("next" | "previous") -- Later or earlier trains
196
-
197
- ### get_prices
198
-
199
- Get ticket prices with Swiss reduction card support. Supports multi-traveler family pricing when connected to SwissTrip.
200
-
201
- **Input:**
202
- - `trip_ids` (string[], required) -- Trip IDs from search_connections
203
- - `traveler_type` ("ADULT" | "CHILD", default: "ADULT") -- used when no traveler_names given
204
- - `reduction_card` ("HALF_FARE" | "GA" | "NONE", default: "HALF_FARE") -- used when no traveler_names given
205
- - `traveler_names` (string[], optional) -- SwissTrip traveler names for family pricing (requires `SWISSTRIP_TOKEN`)
206
-
207
- **Example:** "How much for Zurich to Zermatt for Fabian and Anna?" (with SwissTrip connected, each traveler's reduction card is applied automatically)
208
-
209
- ### get_ticket_link
210
-
211
- Get a direct purchase link to buy the ticket on SBB.ch. On mobile with the SBB app installed, opens directly in the app with Halbtax/GA applied automatically. Supports multi-traveler tickets when connected to SwissTrip.
212
-
213
- **Input:**
214
- - `trip_id` (string, required) -- Trip ID to purchase
215
- - `from_name` (string, required) -- Origin station name
216
- - `from_id` (string, required) -- Origin station ID
217
- - `to_name` (string, required) -- Destination station name
218
- - `to_id` (string, required) -- Destination station ID
219
- - `date` (string, required) -- Travel date YYYY-MM-DD
220
- - `time` (string, required) -- Departure time HH:MM
221
- - `traveler_type` ("ADULT" | "CHILD", default: "ADULT") -- used when no traveler_names given
222
- - `reduction_card` ("HALF_FARE" | "GA" | "NONE", default: "HALF_FARE") -- used when no traveler_names given
223
- - `traveler_names` (string[], optional) -- SwissTrip traveler names for family tickets (requires `SWISSTRIP_TOKEN`)
224
-
225
- ### list_travelers
226
-
227
- List all travelers in the user's SwissTrip account (self, partner, kids). Each traveler has their own reduction card. Requires `SWISSTRIP_TOKEN`.
155
+ Save a default once via `save_profile` — subsequent calls use it automatically.
228
156
 
229
- **Example:** "Who are my travelers?" → shows all saved travelers with their reduction cards
157
+ ---
230
158
 
231
- ## Prompts
159
+ ## Hosted MCP endpoint
232
160
 
233
- ### plan_journey
161
+ A public HTTPS endpoint is live at `https://mcp.swisstrip.app/mcp` for clients that prefer remote MCP over local stdio (ChatGPT, Claude Pro remote MCPs, etc.). Same tools, plus rich widgets for ChatGPT.
234
162
 
235
- End-to-end journey planning prompt. Searches connections, compares prices, and provides ticket links.
163
+ ```bash
164
+ # Test from your terminal
165
+ curl -X POST https://mcp.swisstrip.app/mcp \
166
+ -H 'Content-Type: application/json' \
167
+ -H 'Accept: application/json, text/event-stream' \
168
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
169
+ ```
236
170
 
237
- **Input:**
238
- - `from` (string, required) -- Origin station
239
- - `to` (string, required) -- Destination station
240
- - `date` (string, optional) -- Travel date
171
+ ---
241
172
 
242
- ## Environment Variables
173
+ ## Environment variables
243
174
 
244
175
  | Variable | Required | Description |
245
- |----------|----------|-------------|
246
- | `SMAPI_CLIENT_ID` | Yes* | OAuth 2.0 client ID from SBB Developer Portal |
247
- | `SMAPI_CLIENT_SECRET` | Yes* | OAuth 2.0 client secret |
248
- | `SMAPI_SCOPE` | Yes* | OAuth scope |
249
- | `SMAPI_CONTRACT_ID` | Yes* | SBB business contract ID |
250
- | `SMAPI_ENV` | No | `int` (default) or `prod` |
251
- | `SMAPI_TENANT_ID` | No | Azure AD tenant (defaults to SBB tenant) |
252
- | `SWISSTRIP_TOKEN` | No | SwissTrip auth token for cloud profiles and multi-traveler support |
253
- | `SWISSTRIP_URL` | No | SwissTrip API base URL (defaults to `https://swisstrip.ch`) |
176
+ |---|---|---|
177
+ | `SMAPI_JOURNEY_CLIENT_ID` / `_CLIENT_SECRET` / `_SCOPE` | For schedules + stations | OAuth credentials for SBB Journey API |
178
+ | `SMAPI_TICKETING_CLIENT_ID` / `_CLIENT_SECRET` / `_SCOPE` | For prices + booking links | OAuth credentials for SBB Ticketing API (b2p) |
179
+ | `SMAPI_CLIENT_ID` / `_CLIENT_SECRET` / `_SCOPE` | Fallback | Single-app credentials, used for any family without specific vars |
180
+ | `SMAPI_CONTRACT_ID` | With any creds | SBB partner contract identifier |
181
+ | `SMAPI_ENV` | No | `int` (default) \| `prod` \| `mock` |
182
+ | `SMAPI_TENANT_ID` | No | Azure AD tenant override (defaults to SBB's) |
183
+ | `SWISSTRIP_TOKEN` | No | SwissTrip account token for cloud-synced multi-traveler profiles |
254
184
 
255
- *Without SMAPI credentials, the server runs in mock mode with realistic demo data.
185
+ Without any SMAPI credentials mock mode (realistic demo data).
256
186
 
257
- ## SwissTrip Integration (Optional)
187
+ ---
258
188
 
259
- Set `SWISSTRIP_TOKEN` to connect sbb-mcp to your [SwissTrip](https://swisstrip.ch) account. This enables:
189
+ ## SwissTrip integration (optional)
260
190
 
261
- - **Cloud-synced profiles** -- your travel profile (name, DOB, reduction card) syncs from SwissTrip instead of local `~/.sbb-mcp/profile.json`
262
- - **Multi-traveler pricing** -- add your partner, kids, or travel companions on SwissTrip and get per-person pricing with correct reduction cards
263
- - **Family tickets** -- pass `traveler_names` to `get_prices` and `get_ticket_link` for group pricing
191
+ Connect your [SwissTrip](https://swisstrip.app) account via `SWISSTRIP_TOKEN` to enable:
264
192
 
265
- ```json
266
- {
267
- "mcpServers": {
268
- "sbb": {
269
- "command": "npx",
270
- "args": ["-y", "sbb-mcp"],
271
- "env": {
272
- "SMAPI_CLIENT_ID": "your-client-id",
273
- "SMAPI_CLIENT_SECRET": "your-secret",
274
- "SMAPI_SCOPE": "your-scope",
275
- "SWISSTRIP_TOKEN": "your-swisstrip-auth-token"
276
- }
277
- }
278
- }
279
- }
280
- ```
193
+ - Cloud-synced travel profile (name, DOB, reduction card) instead of local file
194
+ - Multi-traveler family pricing (partner, kids, each with their own card)
195
+ - Mint a token at https://swisstrip.app/settings/developer
281
196
 
282
- Without `SWISSTRIP_TOKEN`, sbb-mcp uses local file-based profiles as before -- no SwissTrip account required.
197
+ ---
283
198
 
284
- ## Available on
199
+ ## Available everywhere
285
200
 
286
201
  - [npm](https://www.npmjs.com/package/sbb-mcp)
287
202
  - [Official MCP Registry](https://registry.modelcontextprotocol.io)
288
203
  - [Smithery](https://smithery.ai/servers/fabsforward2-zhoi/sbb-mcp)
204
+ - [PulseMCP](https://www.pulsemcp.com/servers/fabsbags-sbb)
205
+ - [LobeHub](https://lobehub.com/mcp)
206
+ - ChatGPT custom connector at `https://mcp.swisstrip.app/mcp`
207
+
208
+ Also published under alias names that all forward to `sbb-mcp`: [swiss-rail-mcp](https://www.npmjs.com/package/swiss-rail-mcp), [sbb-cff-ffs-mcp](https://www.npmjs.com/package/sbb-cff-ffs-mcp), [swiss-train-mcp](https://www.npmjs.com/package/swiss-train-mcp), [swiss-railways-mcp](https://www.npmjs.com/package/swiss-railways-mcp).
289
209
 
290
- Also available as: [sbb-mcp-official](https://www.npmjs.com/package/sbb-mcp-official) | [swiss-rail-mcp](https://www.npmjs.com/package/swiss-rail-mcp) | [sbb-cff-ffs-mcp](https://www.npmjs.com/package/sbb-cff-ffs-mcp) | [swiss-train-mcp](https://www.npmjs.com/package/swiss-train-mcp) | [swiss-railways-mcp](https://www.npmjs.com/package/swiss-railways-mcp)
210
+ ---
291
211
 
292
212
  ## About
293
213
 
294
- Built on the official SBB Swiss Mobility API (SMAPI) with OSDM-compliant journey planning and pricing. Covers the entire Swiss public transport network including SBB, BLS, SOB, and regional operators. Weather data powered by [swiss-weather-mcp](https://www.npmjs.com/package/swiss-weather-mcp) (MeteoSwiss + Open-Meteo).
214
+ Built on SBB's commercial Swiss Mobility API (SMAPI) with OSDM-compliant journey planning and Azure AD OAuth. Covers SBB plus regional operators (BLS, SOB, MOB, RhB, etc.) the entire Swiss public transport network with 10,000+ daily connections.
295
215
 
296
- **SBB** (Schweizerische Bundesbahnen) / **CFF** (Chemins de fer federaux suisses) / **FFS** (Ferrovie federali svizzere) -- Swiss Federal Railways operates one of the densest rail networks in the world with over 10,000 daily connections.
216
+ Weather data via [`swiss-weather-mcp`](https://www.npmjs.com/package/swiss-weather-mcp) (MeteoSwiss + Open-Meteo).
297
217
 
298
- ## ChatGPT (custom connector)
218
+ Repository: [Fabsbags/swisstrip-web](https://github.com/Fabsbags/swisstrip-web/tree/main/packages/sbb-mcp). Issues: [tracker](https://github.com/Fabsbags/swisstrip-web/issues).
299
219
 
300
- A hosted HTTPS endpoint is available at `https://mcp.swisstrip.app/mcp` for use as a custom connector in ChatGPT (requires Plus/Pro/Business/Enterprise with Developer Mode enabled).
220
+ **SBB** (Schweizerische Bundesbahnen) / **CFF** (Chemins de fer fédéraux suisses) / **FFS** (Ferrovie federali svizzere) Swiss Federal Railways.
301
221
 
302
- 1. ChatGPT → **Settings → Connectors** → enable **Developer Mode**
303
- 2. Click **Add custom connector**
304
- 3. **MCP Server URL:** `https://mcp.swisstrip.app/mcp`
305
- 4. **Authentication:** None
306
- 5. Save, then enable the connector via the **+** button in the composer
222
+ ---
223
+
224
+ ## License
307
225
 
308
- Tool responses render as interactive widgets (connection cards, trip details, price tables, ticket cards) using the OpenAI Apps SDK.
226
+ [FSL-1.1-MIT](https://fsl.software/) Functional Source License. Free for any non-competing purpose. Converts to MIT after 2 years. See [LICENSE](LICENSE).
227
+
228
+ ---
309
229
 
310
230
  ## Changelog
311
231
 
232
+ ### v0.4.3 — README polish
233
+
234
+ Launch-focused README rewrite: install-first structure, ChatGPT custom connector promoted to top, mock-mode-by-default messaging, 9-tool reference table, per-family SBB credential env vars documented. No runtime changes — `sbb-mcp@0.4.2` behavior is identical to `0.4.3`.
235
+
312
236
  ### v0.4.2 — Repository metadata
313
237
 
314
- - Point `repository`, `homepage`, and `bugs` fields at the monorepo source of truth (`Fabsbags/swisstrip-web`, directory `packages/sbb-mcp`) so npm and MCP registries link to the active code. No runtime or API changes.
238
+ Point `repository`, `homepage`, and `bugs` at the monorepo source of truth. No runtime changes.
315
239
 
316
240
  ### v0.4.1 — Apps SDK widgets
317
241
 
318
- - Five Preact widgets registered as `ui://widget/*.html` resources: stations list, connection list, trip details, prices table, ticket card.
319
- - Tool responses now include `structuredContent` + `_meta["openai/outputTemplate"]` so ChatGPT renders rich UI instead of plain markdown.
320
- - Hosted endpoint live at `https://mcp.swisstrip.app/mcp` for use as a ChatGPT custom connector.
321
- - Widget bundle built separately via Vite under `web/` (single IIFE, inlined CSS, ~25 KB total).
322
- - Server rendering is unchanged for non-Apps-SDK clients (Claude Desktop, Cursor, etc.) — they see the same markdown output as before.
242
+ - Five Preact widgets registered as `ui://widget/*.html` resources: stations list, connection list, trip details, prices table, ticket card
243
+ - Tool responses include `structuredContent` + `_meta["openai/outputTemplate"]` so ChatGPT renders rich UI
244
+ - Hosted endpoint live at `https://mcp.swisstrip.app/mcp` for use as a ChatGPT custom connector
245
+ - Widget bundle ~25KB (single IIFE, inlined CSS) built via Vite under `web/`
246
+ - Server rendering unchanged for stdio clients
323
247
 
324
248
  ### v0.4.0 — Multilingual
325
249
 
326
- - Every tool now accepts an optional `language` parameter (`de | fr | it | en | es | pt | ru | ar | zh`).
327
- - Labels, date/time formatting, and the SBB ticket-buy button text are translated.
328
- - `Accept-Language` is passed to SBB SMAPI so station names come back in the requested language where supported (de/fr/it/en).
329
- - `save_profile` stores a preferred language; later tool calls default to it when `language` is omitted.
330
- - **Breaking:** default output locale with no profile and no tool arg is now `en` (was implicitly `de-CH`). Set a profile or pass `language: 'de'` to restore the old behavior.
331
- - Translations shared with the WhatsApp/Telegram bots via the new [`sbb-i18n`](https://www.npmjs.com/package/sbb-i18n) package.
250
+ - Optional `language` argument on every tool (9 languages)
251
+ - `Accept-Language` passed to SBB SMAPI for localized station names
252
+ - `save_profile` stores preferred language as default
253
+ - **Breaking:** default output locale with no profile is now `en` (was `de-CH`). Set a profile or pass `language: 'de'` to restore previous behavior.
254
+ - Translations shared with WhatsApp/Telegram bots via [`sbb-i18n`](https://www.npmjs.com/package/sbb-i18n)
332
255
 
333
256
  ### v0.3.0 — Destination weather
334
257
 
335
- - Automatic weather forecast appended to `search_connections` results via [`swiss-weather-mcp`](https://www.npmjs.com/package/swiss-weather-mcp).
336
-
337
- ## License
338
-
339
- [FSL-1.1-MIT](https://fsl.software/) -- Functional Source License. Free to use for any non-competing purpose. Converts to MIT after 2 years. See [LICENSE](LICENSE) for details.
258
+ Auto-include weather forecast in `search_connections` results via [`swiss-weather-mcp`](https://www.npmjs.com/package/swiss-weather-mcp).
package/dist/cache.d.ts CHANGED
@@ -10,3 +10,5 @@ export declare const TTL: {
10
10
  readonly PRICES: number;
11
11
  readonly TRIP_DETAILS: number;
12
12
  };
13
+ /** Test-only: clear all entries. No effect in production. */
14
+ export declare function __clearCacheForTesting(): void;
package/dist/cache.js CHANGED
@@ -55,4 +55,8 @@ export const TTL = {
55
55
  PRICES: 2 * 60 * 1000, // 2 minutes
56
56
  TRIP_DETAILS: 5 * 60 * 1000, // 5 minutes
57
57
  };
58
+ /** Test-only: clear all entries. No effect in production. */
59
+ export function __clearCacheForTesting() {
60
+ store.clear();
61
+ }
58
62
  //# sourceMappingURL=cache.js.map
package/dist/cache.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,KAAK,GAAG,IAAI,GAAG,EAA+B,CAAA;AAEpD,MAAM,WAAW,GAAG,KAAK,CAAA;AAEzB,SAAS,YAAY;IACnB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACtB,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;QACjC,IAAI,GAAG,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YAC3B,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACjB,OAAO,EAAE,CAAA;QACX,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,SAAS,WAAW,CAAC,KAAa;IAChC,wDAAwD;IACxD,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QAC/B,IAAI,OAAO,IAAI,KAAK;YAAE,MAAK;QAC3B,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACjB,OAAO,EAAE,CAAA;IACX,CAAC;AACH,CAAC;AAED,MAAM,UAAU,QAAQ,CAAI,GAAW;IACrC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC5B,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAA;IAC5B,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QAClC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACjB,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,qDAAqD;IACrD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IACjB,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IACrB,OAAO,KAAK,CAAC,IAAS,CAAA;AACxB,CAAC;AAED,MAAM,UAAU,QAAQ,CAAI,GAAW,EAAE,IAAO,EAAE,KAAa;IAC7D,IAAI,KAAK,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,YAAY,EAAE,CAAA;QAC9B,0DAA0D;QAC1D,IAAI,OAAO,GAAG,WAAW,GAAG,GAAG,EAAE,CAAC;YAChC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC,CAAC,CAAA;QAC5C,CAAC;IACH,CAAC;IACD,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,CAAA;AACzD,CAAC;AAED,OAAO;AACP,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,QAAQ,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAK,WAAW;IAC7C,WAAW,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,EAAS,YAAY;IAC/C,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,EAAc,YAAY;IAC/C,YAAY,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,EAAQ,YAAY;CACvC,CAAA"}
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,KAAK,GAAG,IAAI,GAAG,EAA+B,CAAA;AAEpD,MAAM,WAAW,GAAG,KAAK,CAAA;AAEzB,SAAS,YAAY;IACnB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACtB,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;QACjC,IAAI,GAAG,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YAC3B,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACjB,OAAO,EAAE,CAAA;QACX,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,SAAS,WAAW,CAAC,KAAa;IAChC,wDAAwD;IACxD,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QAC/B,IAAI,OAAO,IAAI,KAAK;YAAE,MAAK;QAC3B,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACjB,OAAO,EAAE,CAAA;IACX,CAAC;AACH,CAAC;AAED,MAAM,UAAU,QAAQ,CAAI,GAAW;IACrC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC5B,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAA;IAC5B,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QAClC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACjB,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,qDAAqD;IACrD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IACjB,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IACrB,OAAO,KAAK,CAAC,IAAS,CAAA;AACxB,CAAC;AAED,MAAM,UAAU,QAAQ,CAAI,GAAW,EAAE,IAAO,EAAE,KAAa;IAC7D,IAAI,KAAK,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,YAAY,EAAE,CAAA;QAC9B,0DAA0D;QAC1D,IAAI,OAAO,GAAG,WAAW,GAAG,GAAG,EAAE,CAAC;YAChC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC,CAAC,CAAA;QAC5C,CAAC;IACH,CAAC;IACD,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,CAAA;AACzD,CAAC;AAED,OAAO;AACP,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,QAAQ,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAK,WAAW;IAC7C,WAAW,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,EAAS,YAAY;IAC/C,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,EAAc,YAAY;IAC/C,YAAY,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,EAAQ,YAAY;CACvC,CAAA;AAEV,6DAA6D;AAC7D,MAAM,UAAU,sBAAsB;IACpC,KAAK,CAAC,KAAK,EAAE,CAAA;AACf,CAAC"}
@@ -1,5 +1,18 @@
1
1
  import type { SmapiPlace, SmapiTrip, SmapiTripsCollection, SmapiPriceResult } from './transport/smapi-types.js';
2
2
  import type { Lang } from './i18n.js';
3
+ /**
4
+ * Terse one-line summary used when the client renders the Apps SDK widget
5
+ * (see `isWidgetRenderingClient`). Widget clients already show a full card
6
+ * list — returning the long markdown table here just causes the LLM to
7
+ * paraphrase it into a redundant narration right next to the iframe. The
8
+ * structured content still carries all the data the model needs to answer
9
+ * follow-up questions; this string is purely for the chat bubble above the
10
+ * widget.
11
+ */
12
+ export declare function formatStationsTerse(stations: SmapiPlace[], _lang?: Lang): string;
13
+ export declare function formatConnectionsTerse(collection: SmapiTripsCollection, lang?: Lang): string;
14
+ export declare function formatTripDetailsTerse(trip: SmapiTrip, lang?: Lang): string;
15
+ export declare function formatPricesTerse(results: SmapiPriceResult[], lang?: Lang): string;
3
16
  export declare function formatStations(stations: SmapiPlace[], lang?: Lang): string;
4
17
  export declare function formatConnections(collection: SmapiTripsCollection, lang?: Lang): string;
5
18
  export declare function formatTripDetails(trip: SmapiTrip, lang?: Lang): string;