sbb-mcp 0.4.3 → 0.5.0
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/LICENSE +50 -57
- package/README.md +25 -214
- package/dist/index.js +47 -19
- package/package.json +10 -33
- package/dist/auth.d.ts +0 -2
- package/dist/auth.js +0 -44
- package/dist/auth.js.map +0 -1
- package/dist/cache.d.ts +0 -14
- package/dist/cache.js +0 -62
- package/dist/cache.js.map +0 -1
- package/dist/client.d.ts +0 -17
- package/dist/client.js +0 -70
- package/dist/client.js.map +0 -1
- package/dist/formatters.d.ts +0 -35
- package/dist/formatters.js +0 -285
- package/dist/formatters.js.map +0 -1
- package/dist/http.d.ts +0 -2
- package/dist/http.js +0 -117
- package/dist/http.js.map +0 -1
- package/dist/i18n.d.ts +0 -22
- package/dist/i18n.js +0 -36
- package/dist/i18n.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js.map +0 -1
- package/dist/journey.d.ts +0 -5
- package/dist/journey.js +0 -67
- package/dist/journey.js.map +0 -1
- package/dist/look2book.d.ts +0 -98
- package/dist/look2book.js +0 -212
- package/dist/look2book.js.map +0 -1
- package/dist/prices.d.ts +0 -3
- package/dist/prices.js +0 -51
- package/dist/prices.js.map +0 -1
- package/dist/profile.d.ts +0 -16
- package/dist/profile.js +0 -84
- package/dist/profile.js.map +0 -1
- package/dist/rate-limit.d.ts +0 -5
- package/dist/rate-limit.js +0 -44
- package/dist/rate-limit.js.map +0 -1
- package/dist/shortlink.d.ts +0 -60
- package/dist/shortlink.js +0 -122
- package/dist/shortlink.js.map +0 -1
- package/dist/structured.d.ts +0 -125
- package/dist/structured.js +0 -134
- package/dist/structured.js.map +0 -1
- package/dist/swisstrip.d.ts +0 -41
- package/dist/swisstrip.js +0 -135
- package/dist/swisstrip.js.map +0 -1
- package/dist/tools.d.ts +0 -40
- package/dist/tools.js +0 -509
- package/dist/tools.js.map +0 -1
- package/dist/transport/index.d.ts +0 -10
- package/dist/transport/index.js +0 -13
- package/dist/transport/index.js.map +0 -1
- package/dist/transport/setup.d.ts +0 -1
- package/dist/transport/setup.js +0 -59
- package/dist/transport/setup.js.map +0 -1
- package/dist/transport/smapi-auth.d.ts +0 -14
- package/dist/transport/smapi-auth.js +0 -89
- package/dist/transport/smapi-auth.js.map +0 -1
- package/dist/transport/smapi-client.d.ts +0 -46
- package/dist/transport/smapi-client.js +0 -186
- package/dist/transport/smapi-client.js.map +0 -1
- package/dist/transport/smapi-journey.d.ts +0 -29
- package/dist/transport/smapi-journey.js +0 -91
- package/dist/transport/smapi-journey.js.map +0 -1
- package/dist/transport/smapi-mock.d.ts +0 -9
- package/dist/transport/smapi-mock.js +0 -151
- package/dist/transport/smapi-mock.js.map +0 -1
- package/dist/transport/smapi-prices.d.ts +0 -48
- package/dist/transport/smapi-prices.js +0 -144
- package/dist/transport/smapi-prices.js.map +0 -1
- package/dist/transport/smapi-types.d.ts +0 -181
- package/dist/transport/smapi-types.js +0 -2
- package/dist/transport/smapi-types.js.map +0 -1
- package/dist/types.d.ts +0 -139
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- package/dist/widgets.d.ts +0 -60
- package/dist/widgets.js +0 -184
- package/dist/widgets.js.map +0 -1
- package/web/dist/widgets.css +0 -1
- package/web/dist/widgets.js +0 -1
package/LICENSE
CHANGED
|
@@ -1,57 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
We hereby irrevocably grant you an additional license to use the Software under the MIT license that is effective on the second anniversary of the date we make the Software available. On or after that date, you may use the Software under the MIT license, in which case the following will apply:
|
|
52
|
-
|
|
53
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
54
|
-
|
|
55
|
-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
56
|
-
|
|
57
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
1
|
+
Copyright (c) 2026 F. Weinhappl / SwissTrip
|
|
2
|
+
All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
This software and its source code are proprietary and confidential.
|
|
5
|
+
|
|
6
|
+
No license is granted, express or implied, to use, copy, modify, merge,
|
|
7
|
+
publish, distribute, sublicense, or sell copies of this software, in whole
|
|
8
|
+
or in part, except as explicitly permitted below.
|
|
9
|
+
|
|
10
|
+
PERMITTED USE
|
|
11
|
+
You may install and run unmodified copies of the published npm package
|
|
12
|
+
("sbb-mcp") solely as a runtime client to connect AI assistants to the
|
|
13
|
+
SwissTrip SBB MCP service hosted at https://mcp.swisstrip.app for
|
|
14
|
+
personal, non-commercial use.
|
|
15
|
+
|
|
16
|
+
PROHIBITED USE
|
|
17
|
+
The following are not permitted without prior written agreement signed by
|
|
18
|
+
F. Weinhappl on behalf of SwissTrip:
|
|
19
|
+
- Redistribution, mirroring, or republication of the source code or
|
|
20
|
+
compiled output, in whole or in part.
|
|
21
|
+
- Use of the source code, in whole or in part, in any other software,
|
|
22
|
+
product, or service.
|
|
23
|
+
- Hosting, operating, or offering a service based on, derived from,
|
|
24
|
+
or substantially similar to this software.
|
|
25
|
+
- Reverse engineering, decompilation, or disassembly, except to the
|
|
26
|
+
extent expressly permitted by mandatory applicable law.
|
|
27
|
+
- Removal, alteration, or obscuring of this license notice or any
|
|
28
|
+
copyright, trademark, or attribution notices.
|
|
29
|
+
|
|
30
|
+
NO WARRANTY
|
|
31
|
+
This software is provided "as is", without warranty of any kind, express
|
|
32
|
+
or implied, including but not limited to the warranties of
|
|
33
|
+
merchantability, fitness for a particular purpose, and non-infringement.
|
|
34
|
+
In no event shall the author or copyright holder be liable for any claim,
|
|
35
|
+
damages, or other liability arising from, out of, or in connection with
|
|
36
|
+
this software or its use.
|
|
37
|
+
|
|
38
|
+
GOVERNING LAW
|
|
39
|
+
This License shall be governed by and construed in accordance with the
|
|
40
|
+
laws of Switzerland, without regard to its conflict of law provisions.
|
|
41
|
+
Any disputes arising under or in connection with this License shall be
|
|
42
|
+
subject to the exclusive jurisdiction of the courts of Zurich,
|
|
43
|
+
Switzerland.
|
|
44
|
+
|
|
45
|
+
COMMERCIAL LICENSING
|
|
46
|
+
For commercial licensing, redistribution rights, or any use not covered
|
|
47
|
+
by the permitted use above, contact:
|
|
48
|
+
|
|
49
|
+
F. Weinhappl
|
|
50
|
+
fabsforward2@gmail.com
|
package/README.md
CHANGED
|
@@ -1,45 +1,10 @@
|
|
|
1
1
|
# sbb-mcp
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://fsl.software/)
|
|
5
|
-
[](https://smithery.ai/servers/fabsforward2-zhoi/sbb-mcp)
|
|
3
|
+
MCP client for the SwissTrip SBB MCP service. Connects AI assistants to live Swiss Federal Railways (SBB / CFF / FFS) data: train schedules, station search, ticket prices, and direct ticket purchase links.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
The package is a thin client. All SBB API access, ticketing, and profile sync run on the hosted SwissTrip server at `https://mcp.swisstrip.app/mcp`.
|
|
8
6
|
|
|
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
|
-
```
|
|
15
|
-
|
|
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.
|
|
17
|
-
|
|
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).
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## Quick install
|
|
23
|
-
|
|
24
|
-
### ChatGPT (no install — hosted)
|
|
25
|
-
|
|
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.
|
|
35
|
-
|
|
36
|
-
### Claude Code
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
claude mcp add sbb -- npx -y sbb-mcp
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Done — try `/mcp` in Claude to confirm it's loaded, then ask about Swiss trains.
|
|
7
|
+
## Install
|
|
43
8
|
|
|
44
9
|
### Claude Desktop
|
|
45
10
|
|
|
@@ -56,203 +21,49 @@ Add to `claude_desktop_config.json`:
|
|
|
56
21
|
}
|
|
57
22
|
```
|
|
58
23
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
### Cursor / Windsurf / VS Code Copilot
|
|
62
|
-
|
|
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
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Runs in mock mode with realistic Swiss station data — no setup, no credentials. Useful for testing the protocol or building demos.
|
|
78
|
-
|
|
79
|
-
---
|
|
24
|
+
### Cursor
|
|
80
25
|
|
|
81
|
-
|
|
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).
|
|
84
|
-
|
|
85
|
-
Add to your config's `env` block:
|
|
26
|
+
Add to `~/.cursor/mcp.json`:
|
|
86
27
|
|
|
87
28
|
```json
|
|
88
29
|
{
|
|
89
30
|
"mcpServers": {
|
|
90
31
|
"sbb": {
|
|
91
32
|
"command": "npx",
|
|
92
|
-
"args": ["-y", "sbb-mcp"]
|
|
93
|
-
"env": {
|
|
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": "..."
|
|
101
|
-
}
|
|
33
|
+
"args": ["-y", "sbb-mcp"]
|
|
102
34
|
}
|
|
103
35
|
}
|
|
104
36
|
}
|
|
105
37
|
```
|
|
106
38
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
For backwards compatibility, single-app setups can use the generic `SMAPI_CLIENT_ID` / `SMAPI_CLIENT_SECRET` / `SMAPI_SCOPE` for both APIs.
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
## What it does
|
|
114
|
-
|
|
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
|
|
125
|
-
|
|
126
|
-
---
|
|
127
|
-
|
|
128
|
-
## Tools
|
|
129
|
-
|
|
130
|
-
9 MCP tools, all support an optional `language` argument.
|
|
131
|
-
|
|
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) |
|
|
39
|
+
### ChatGPT (Plus / Pro / Business / Enterprise with Developer Mode)
|
|
143
40
|
|
|
144
|
-
|
|
41
|
+
No install required. Add a custom connector pointing at:
|
|
145
42
|
|
|
146
|
-
### Multilingual
|
|
147
|
-
|
|
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.
|
|
149
|
-
|
|
150
|
-
```jsonc
|
|
151
|
-
// French output
|
|
152
|
-
{ "from": "Zürich HB", "to": "Genève", "language": "fr" }
|
|
153
43
|
```
|
|
154
|
-
|
|
155
|
-
Save a default once via `save_profile` — subsequent calls use it automatically.
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
## Hosted MCP endpoint
|
|
160
|
-
|
|
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.
|
|
162
|
-
|
|
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"}'
|
|
44
|
+
https://mcp.swisstrip.app/mcp
|
|
169
45
|
```
|
|
170
46
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
## Environment variables
|
|
174
|
-
|
|
175
|
-
| Variable | Required | Description |
|
|
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 |
|
|
184
|
-
|
|
185
|
-
Without any SMAPI credentials → mock mode (realistic demo data).
|
|
186
|
-
|
|
187
|
-
---
|
|
188
|
-
|
|
189
|
-
## SwissTrip integration (optional)
|
|
190
|
-
|
|
191
|
-
Connect your [SwissTrip](https://swisstrip.app) account via `SWISSTRIP_TOKEN` to enable:
|
|
192
|
-
|
|
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
|
|
196
|
-
|
|
197
|
-
---
|
|
198
|
-
|
|
199
|
-
## Available everywhere
|
|
200
|
-
|
|
201
|
-
- [npm](https://www.npmjs.com/package/sbb-mcp)
|
|
202
|
-
- [Official MCP Registry](https://registry.modelcontextprotocol.io)
|
|
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).
|
|
209
|
-
|
|
210
|
-
---
|
|
211
|
-
|
|
212
|
-
## About
|
|
213
|
-
|
|
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.
|
|
215
|
-
|
|
216
|
-
Weather data via [`swiss-weather-mcp`](https://www.npmjs.com/package/swiss-weather-mcp) (MeteoSwiss + Open-Meteo).
|
|
47
|
+
## Tools
|
|
217
48
|
|
|
218
|
-
|
|
49
|
+
- `search_stations` — find Swiss stations, addresses, and points of interest
|
|
50
|
+
- `search_connections` — train schedules between two stations
|
|
51
|
+
- `get_trip_details` — detailed trip with all intermediate stops
|
|
52
|
+
- `get_more_connections` — earlier or later trains for a previous search
|
|
53
|
+
- `get_prices` — ticket prices with Halbtax / GA discounts
|
|
54
|
+
- `get_ticket_link` — direct purchase link to SBB.ch
|
|
55
|
+
- `save_profile` / `get_profile` — save your travel profile locally
|
|
56
|
+
- `list_travelers` — multi-traveler family pricing (requires `SWISSTRIP_TOKEN`)
|
|
219
57
|
|
|
220
|
-
|
|
58
|
+
## Optional configuration
|
|
221
59
|
|
|
222
|
-
|
|
60
|
+
| Environment variable | Purpose |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `SWISSTRIP_TOKEN` | Sync your traveler profile from your SwissTrip account. Mint at https://swisstrip.app/settings/developer. |
|
|
63
|
+
| `SBB_MCP_URL` | Override the hosted server URL. Default: `https://mcp.swisstrip.app/mcp`. |
|
|
223
64
|
|
|
224
65
|
## License
|
|
225
66
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
---
|
|
229
|
-
|
|
230
|
-
## Changelog
|
|
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
|
-
|
|
236
|
-
### v0.4.2 — Repository metadata
|
|
237
|
-
|
|
238
|
-
Point `repository`, `homepage`, and `bugs` at the monorepo source of truth. No runtime changes.
|
|
239
|
-
|
|
240
|
-
### v0.4.1 — Apps SDK widgets
|
|
241
|
-
|
|
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
|
|
247
|
-
|
|
248
|
-
### v0.4.0 — Multilingual
|
|
249
|
-
|
|
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)
|
|
255
|
-
|
|
256
|
-
### v0.3.0 — Destination weather
|
|
67
|
+
Proprietary. See [LICENSE](./LICENSE). © 2026 SwissTrip.
|
|
257
68
|
|
|
258
|
-
|
|
69
|
+
For commercial licensing inquiries: fabsforward2@gmail.com.
|
package/dist/index.js
CHANGED
|
@@ -1,27 +1,55 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* sbb-mcp — stdio→HTTPS proxy.
|
|
4
|
+
*
|
|
5
|
+
* Forwards every MCP request from a local stdio transport (Claude Desktop,
|
|
6
|
+
* Cursor, etc.) to the hosted SwissTrip SBB MCP server. All SBB API access,
|
|
7
|
+
* ticketing, profile sync, and rendering happen on the server. This client
|
|
8
|
+
* holds no credentials and no business logic.
|
|
9
|
+
*
|
|
10
|
+
* Override the server URL with SBB_MCP_URL.
|
|
11
|
+
* Pass an optional SwissTrip account token via SWISSTRIP_TOKEN to enable
|
|
12
|
+
* cloud-synced profiles and multi-traveler pricing.
|
|
13
|
+
*/
|
|
14
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
15
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
16
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
17
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
18
|
+
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, SubscribeRequestSchema, UnsubscribeRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
19
|
+
const REMOTE_URL = process.env.SBB_MCP_URL ?? 'https://mcp.swisstrip.app/mcp';
|
|
20
|
+
const NAME = 'sbb-mcp';
|
|
21
|
+
const VERSION = '0.5.0';
|
|
7
22
|
async function main() {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
await server.connect(transport);
|
|
12
|
-
if (!isSmapiConfigured()) {
|
|
13
|
-
console.error('[sbb-mcp] Running in MOCK mode (no SMAPI credentials configured)');
|
|
14
|
-
console.error('[sbb-mcp] Set SMAPI_CLIENT_ID, SMAPI_CLIENT_SECRET, SMAPI_SCOPE for live data');
|
|
15
|
-
}
|
|
16
|
-
else {
|
|
17
|
-
console.error('[sbb-mcp] Connected to SBB SMAPI');
|
|
18
|
-
}
|
|
19
|
-
if (isSwissTripConfigured()) {
|
|
20
|
-
console.error('[sbb-mcp] SwissTrip cloud profiles enabled (SWISSTRIP_TOKEN set)');
|
|
23
|
+
const headers = {};
|
|
24
|
+
if (process.env.SWISSTRIP_TOKEN) {
|
|
25
|
+
headers.Authorization = `Bearer ${process.env.SWISSTRIP_TOKEN}`;
|
|
21
26
|
}
|
|
27
|
+
// Upstream client — connects to the hosted MCP server over HTTPS.
|
|
28
|
+
// Client capabilities advertise what THIS side supports; we don't expose
|
|
29
|
+
// sampling or roots, so an empty bag is correct.
|
|
30
|
+
const upstream = new Client({ name: `${NAME}-proxy`, version: VERSION }, { capabilities: {} });
|
|
31
|
+
await upstream.connect(new StreamableHTTPClientTransport(new URL(REMOTE_URL), {
|
|
32
|
+
requestInit: { headers },
|
|
33
|
+
}));
|
|
34
|
+
// Local stdio server — what Claude Desktop / Cursor talk to.
|
|
35
|
+
const server = new Server({ name: NAME, version: VERSION }, { capabilities: { tools: {}, resources: { subscribe: true }, prompts: {} } });
|
|
36
|
+
// Forward every MCP request type to the upstream server.
|
|
37
|
+
server.setRequestHandler(ListToolsRequestSchema, () => upstream.listTools());
|
|
38
|
+
server.setRequestHandler(CallToolRequestSchema, (req) => upstream.callTool(req.params));
|
|
39
|
+
server.setRequestHandler(ListResourcesRequestSchema, () => upstream.listResources());
|
|
40
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, () => upstream.listResourceTemplates());
|
|
41
|
+
server.setRequestHandler(ReadResourceRequestSchema, (req) => upstream.readResource(req.params));
|
|
42
|
+
server.setRequestHandler(SubscribeRequestSchema, (req) => upstream.subscribeResource(req.params));
|
|
43
|
+
server.setRequestHandler(UnsubscribeRequestSchema, (req) => upstream.unsubscribeResource(req.params));
|
|
44
|
+
server.setRequestHandler(ListPromptsRequestSchema, () => upstream.listPrompts());
|
|
45
|
+
server.setRequestHandler(GetPromptRequestSchema, (req) => upstream.getPrompt(req.params));
|
|
46
|
+
// Forward upstream notifications (progress, list_changed, etc.) back to the agent.
|
|
47
|
+
upstream.fallbackNotificationHandler = async (n) => {
|
|
48
|
+
await server.notification(n);
|
|
49
|
+
};
|
|
50
|
+
await server.connect(new StdioServerTransport());
|
|
22
51
|
}
|
|
23
52
|
main().catch((err) => {
|
|
24
|
-
console.error('[sbb-mcp]
|
|
53
|
+
console.error('[sbb-mcp]', err instanceof Error ? err.message : err);
|
|
25
54
|
process.exit(1);
|
|
26
55
|
});
|
|
27
|
-
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,35 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sbb-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "MCP server for Swiss Federal Railways (SBB/CFF/FFS) — real-time train schedules, prices, and ticket purchase links for any AI assistant",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "MCP client for the SwissTrip SBB MCP service — connect AI assistants to live Swiss Federal Railways (SBB/CFF/FFS) data: schedules, prices, and ticket links.",
|
|
6
5
|
"type": "module",
|
|
7
6
|
"main": "dist/index.js",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": "./dist/index.js",
|
|
10
|
-
"./transport": "./dist/transport/index.js"
|
|
11
|
-
},
|
|
12
7
|
"bin": {
|
|
13
8
|
"sbb-mcp": "dist/index.js"
|
|
14
9
|
},
|
|
15
10
|
"files": [
|
|
16
11
|
"dist",
|
|
17
|
-
"web/dist",
|
|
18
12
|
"README.md",
|
|
19
13
|
"LICENSE"
|
|
20
14
|
],
|
|
21
15
|
"scripts": {
|
|
22
|
-
"
|
|
23
|
-
"sync:transport": "node scripts/sync-transport.cjs",
|
|
24
|
-
"check:transport": "node scripts/sync-transport.cjs --check",
|
|
25
|
-
"build:widgets": "npm --prefix web install --no-audit --no-fund && npm --prefix web run build",
|
|
26
|
-
"build:server": "tsc -p tsconfig.build.json",
|
|
27
|
-
"build": "npm run ensure:i18n && npm run sync:transport && npm run build:widgets && npm run build:server",
|
|
28
|
-
"dev": "tsc -w -p tsconfig.build.json",
|
|
29
|
-
"start:http": "node dist/http.js",
|
|
30
|
-
"test": "vitest run",
|
|
31
|
-
"smoke:int": "node scripts/smoke-int.mjs",
|
|
32
|
-
"prepack": "npm run sync:transport && npm run build",
|
|
16
|
+
"build": "tsc",
|
|
33
17
|
"prepublishOnly": "npm run build"
|
|
34
18
|
},
|
|
35
19
|
"keywords": [
|
|
@@ -49,31 +33,24 @@
|
|
|
49
33
|
"cursor",
|
|
50
34
|
"model-context-protocol"
|
|
51
35
|
],
|
|
52
|
-
"author": "
|
|
53
|
-
"license": "
|
|
36
|
+
"author": "F. Weinhappl <fabsforward2@gmail.com>",
|
|
37
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
54
38
|
"repository": {
|
|
55
39
|
"type": "git",
|
|
56
|
-
"url": "https://github.com/Fabsbags/
|
|
57
|
-
"directory": "packages/sbb-mcp"
|
|
40
|
+
"url": "git+https://github.com/Fabsbags/sbb-mcp.git"
|
|
58
41
|
},
|
|
59
|
-
"homepage": "https://
|
|
42
|
+
"homepage": "https://swisstrip.app",
|
|
60
43
|
"bugs": {
|
|
61
|
-
"url": "https://github.com/Fabsbags/
|
|
44
|
+
"url": "https://github.com/Fabsbags/sbb-mcp/issues"
|
|
62
45
|
},
|
|
63
46
|
"engines": {
|
|
64
47
|
"node": ">=18"
|
|
65
48
|
},
|
|
66
49
|
"dependencies": {
|
|
67
|
-
"@modelcontextprotocol/sdk": "^1.12.1"
|
|
68
|
-
"express": "^5.1.0",
|
|
69
|
-
"sbb-i18n": "^0.1.1",
|
|
70
|
-
"swiss-weather-mcp": "^0.1.1",
|
|
71
|
-
"zod": "^3.24.4"
|
|
50
|
+
"@modelcontextprotocol/sdk": "^1.12.1"
|
|
72
51
|
},
|
|
73
52
|
"devDependencies": {
|
|
74
|
-
"@types/express": "^5.0.2",
|
|
75
53
|
"@types/node": "^22.15.3",
|
|
76
|
-
"typescript": "^5.8.3"
|
|
77
|
-
"vitest": "^4.1.0"
|
|
54
|
+
"typescript": "^5.8.3"
|
|
78
55
|
}
|
|
79
56
|
}
|
package/dist/auth.d.ts
DELETED
package/dist/auth.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
// OAuth 2.0 Client Credentials flow for SBB SMAPI
|
|
2
|
-
// Token endpoint: Microsoft Azure AD
|
|
3
|
-
// Tokens cached in-memory with 55-min TTL (5-min safety margin on 60-min expiry)
|
|
4
|
-
let cachedToken = null;
|
|
5
|
-
export function isSmapiConfigured() {
|
|
6
|
-
return !!(process.env.SMAPI_CLIENT_ID &&
|
|
7
|
-
process.env.SMAPI_CLIENT_SECRET &&
|
|
8
|
-
process.env.SMAPI_SCOPE);
|
|
9
|
-
}
|
|
10
|
-
export async function getAccessToken() {
|
|
11
|
-
if (cachedToken && Date.now() < cachedToken.expiresAt) {
|
|
12
|
-
return cachedToken.token;
|
|
13
|
-
}
|
|
14
|
-
const clientId = process.env.SMAPI_CLIENT_ID;
|
|
15
|
-
const clientSecret = process.env.SMAPI_CLIENT_SECRET;
|
|
16
|
-
const scope = process.env.SMAPI_SCOPE;
|
|
17
|
-
if (!clientId || !clientSecret || !scope) {
|
|
18
|
-
throw new Error('SMAPI credentials not configured. Set SMAPI_CLIENT_ID, SMAPI_CLIENT_SECRET, and SMAPI_SCOPE.');
|
|
19
|
-
}
|
|
20
|
-
const tenantId = process.env.SMAPI_TENANT_ID || '2cda5d11-f0ac-46b3-967d-af1b2e1bd01a';
|
|
21
|
-
const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
|
|
22
|
-
const body = new URLSearchParams({
|
|
23
|
-
grant_type: 'client_credentials',
|
|
24
|
-
client_id: clientId,
|
|
25
|
-
client_secret: clientSecret,
|
|
26
|
-
scope,
|
|
27
|
-
});
|
|
28
|
-
const res = await fetch(tokenUrl, {
|
|
29
|
-
method: 'POST',
|
|
30
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
31
|
-
body: body.toString(),
|
|
32
|
-
});
|
|
33
|
-
if (!res.ok) {
|
|
34
|
-
const text = await res.text();
|
|
35
|
-
throw new Error(`SMAPI token request failed: ${res.status} ${text}`);
|
|
36
|
-
}
|
|
37
|
-
const data = await res.json();
|
|
38
|
-
cachedToken = {
|
|
39
|
-
token: data.access_token,
|
|
40
|
-
expiresAt: Date.now() + 55 * 60 * 1000,
|
|
41
|
-
};
|
|
42
|
-
return cachedToken.token;
|
|
43
|
-
}
|
|
44
|
-
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,qCAAqC;AACrC,iFAAiF;AAEjF,IAAI,WAAW,GAAgD,IAAI,CAAA;AAEnE,MAAM,UAAU,iBAAiB;IAC/B,OAAO,CAAC,CAAC,CACP,OAAO,CAAC,GAAG,CAAC,eAAe;QAC3B,OAAO,CAAC,GAAG,CAAC,mBAAmB;QAC/B,OAAO,CAAC,GAAG,CAAC,WAAW,CACxB,CAAA;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,IAAI,WAAW,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,WAAW,CAAC,SAAS,EAAE,CAAC;QACtD,OAAO,WAAW,CAAC,KAAK,CAAA;IAC1B,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;IAC5C,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAA;IACpD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAA;IAErC,IAAI,CAAC,QAAQ,IAAI,CAAC,YAAY,IAAI,CAAC,KAAK,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CACb,8FAA8F,CAC/F,CAAA;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,sCAAsC,CAAA;IACtF,MAAM,QAAQ,GAAG,qCAAqC,QAAQ,oBAAoB,CAAA;IAElF,MAAM,IAAI,GAAG,IAAI,eAAe,CAAC;QAC/B,UAAU,EAAE,oBAAoB;QAChC,SAAS,EAAE,QAAQ;QACnB,aAAa,EAAE,YAAY;QAC3B,KAAK;KACN,CAAC,CAAA;IAEF,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;QAChC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;QAChE,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE;KACtB,CAAC,CAAA;IAEF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;QAC7B,MAAM,IAAI,KAAK,CAAC,+BAA+B,GAAG,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC,CAAA;IACtE,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;IAE7B,WAAW,GAAG;QACZ,KAAK,EAAE,IAAI,CAAC,YAAY;QACxB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI;KACvC,CAAA;IAED,OAAO,WAAW,CAAC,KAAK,CAAA;AAC1B,CAAC"}
|
package/dist/cache.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simple in-memory TTL cache with LRU eviction to reduce SMAPI calls.
|
|
3
|
-
* Station searches cached 24h, connections cached 5min, prices cached 2min.
|
|
4
|
-
*/
|
|
5
|
-
export declare function cacheGet<T>(key: string): T | undefined;
|
|
6
|
-
export declare function cacheSet<T>(key: string, data: T, ttlMs: number): void;
|
|
7
|
-
export declare const TTL: {
|
|
8
|
-
readonly STATIONS: number;
|
|
9
|
-
readonly CONNECTIONS: number;
|
|
10
|
-
readonly PRICES: number;
|
|
11
|
-
readonly TRIP_DETAILS: number;
|
|
12
|
-
};
|
|
13
|
-
/** Test-only: clear all entries. No effect in production. */
|
|
14
|
-
export declare function __clearCacheForTesting(): void;
|