sbb-mcp 0.4.3 → 0.5.1

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 (83) hide show
  1. package/LICENSE +50 -57
  2. package/README.md +25 -214
  3. package/dist/index.js +47 -19
  4. package/package.json +10 -33
  5. package/dist/auth.d.ts +0 -2
  6. package/dist/auth.js +0 -44
  7. package/dist/auth.js.map +0 -1
  8. package/dist/cache.d.ts +0 -14
  9. package/dist/cache.js +0 -62
  10. package/dist/cache.js.map +0 -1
  11. package/dist/client.d.ts +0 -17
  12. package/dist/client.js +0 -70
  13. package/dist/client.js.map +0 -1
  14. package/dist/formatters.d.ts +0 -35
  15. package/dist/formatters.js +0 -285
  16. package/dist/formatters.js.map +0 -1
  17. package/dist/http.d.ts +0 -2
  18. package/dist/http.js +0 -117
  19. package/dist/http.js.map +0 -1
  20. package/dist/i18n.d.ts +0 -22
  21. package/dist/i18n.js +0 -36
  22. package/dist/i18n.js.map +0 -1
  23. package/dist/index.d.ts +0 -2
  24. package/dist/index.js.map +0 -1
  25. package/dist/journey.d.ts +0 -5
  26. package/dist/journey.js +0 -67
  27. package/dist/journey.js.map +0 -1
  28. package/dist/look2book.d.ts +0 -98
  29. package/dist/look2book.js +0 -212
  30. package/dist/look2book.js.map +0 -1
  31. package/dist/prices.d.ts +0 -3
  32. package/dist/prices.js +0 -51
  33. package/dist/prices.js.map +0 -1
  34. package/dist/profile.d.ts +0 -16
  35. package/dist/profile.js +0 -84
  36. package/dist/profile.js.map +0 -1
  37. package/dist/rate-limit.d.ts +0 -5
  38. package/dist/rate-limit.js +0 -44
  39. package/dist/rate-limit.js.map +0 -1
  40. package/dist/shortlink.d.ts +0 -60
  41. package/dist/shortlink.js +0 -122
  42. package/dist/shortlink.js.map +0 -1
  43. package/dist/structured.d.ts +0 -125
  44. package/dist/structured.js +0 -134
  45. package/dist/structured.js.map +0 -1
  46. package/dist/swisstrip.d.ts +0 -41
  47. package/dist/swisstrip.js +0 -135
  48. package/dist/swisstrip.js.map +0 -1
  49. package/dist/tools.d.ts +0 -40
  50. package/dist/tools.js +0 -509
  51. package/dist/tools.js.map +0 -1
  52. package/dist/transport/index.d.ts +0 -10
  53. package/dist/transport/index.js +0 -13
  54. package/dist/transport/index.js.map +0 -1
  55. package/dist/transport/setup.d.ts +0 -1
  56. package/dist/transport/setup.js +0 -59
  57. package/dist/transport/setup.js.map +0 -1
  58. package/dist/transport/smapi-auth.d.ts +0 -14
  59. package/dist/transport/smapi-auth.js +0 -89
  60. package/dist/transport/smapi-auth.js.map +0 -1
  61. package/dist/transport/smapi-client.d.ts +0 -46
  62. package/dist/transport/smapi-client.js +0 -186
  63. package/dist/transport/smapi-client.js.map +0 -1
  64. package/dist/transport/smapi-journey.d.ts +0 -29
  65. package/dist/transport/smapi-journey.js +0 -91
  66. package/dist/transport/smapi-journey.js.map +0 -1
  67. package/dist/transport/smapi-mock.d.ts +0 -9
  68. package/dist/transport/smapi-mock.js +0 -151
  69. package/dist/transport/smapi-mock.js.map +0 -1
  70. package/dist/transport/smapi-prices.d.ts +0 -48
  71. package/dist/transport/smapi-prices.js +0 -144
  72. package/dist/transport/smapi-prices.js.map +0 -1
  73. package/dist/transport/smapi-types.d.ts +0 -181
  74. package/dist/transport/smapi-types.js +0 -2
  75. package/dist/transport/smapi-types.js.map +0 -1
  76. package/dist/types.d.ts +0 -139
  77. package/dist/types.js +0 -3
  78. package/dist/types.js.map +0 -1
  79. package/dist/widgets.d.ts +0 -60
  80. package/dist/widgets.js +0 -184
  81. package/dist/widgets.js.map +0 -1
  82. package/web/dist/widgets.css +0 -1
  83. package/web/dist/widgets.js +0 -1
package/dist/cache.js DELETED
@@ -1,62 +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
- const store = new Map();
6
- const MAX_ENTRIES = 10000;
7
- function evictExpired() {
8
- const now = Date.now();
9
- let evicted = 0;
10
- for (const [key, entry] of store) {
11
- if (now >= entry.expiresAt) {
12
- store.delete(key);
13
- evicted++;
14
- }
15
- }
16
- return evicted;
17
- }
18
- function evictOldest(count) {
19
- // Map iteration order is insertion order — oldest first
20
- let removed = 0;
21
- for (const key of store.keys()) {
22
- if (removed >= count)
23
- break;
24
- store.delete(key);
25
- removed++;
26
- }
27
- }
28
- export function cacheGet(key) {
29
- const entry = store.get(key);
30
- if (!entry)
31
- return undefined;
32
- if (Date.now() >= entry.expiresAt) {
33
- store.delete(key);
34
- return undefined;
35
- }
36
- // Move to end for LRU (re-insert refreshes position)
37
- store.delete(key);
38
- store.set(key, entry);
39
- return entry.data;
40
- }
41
- export function cacheSet(key, data, ttlMs) {
42
- if (store.size >= MAX_ENTRIES) {
43
- const evicted = evictExpired();
44
- // If expired eviction didn't free enough, drop oldest 10%
45
- if (evicted < MAX_ENTRIES * 0.1) {
46
- evictOldest(Math.floor(MAX_ENTRIES * 0.1));
47
- }
48
- }
49
- store.set(key, { data, expiresAt: Date.now() + ttlMs });
50
- }
51
- // TTLs
52
- export const TTL = {
53
- STATIONS: 24 * 60 * 60 * 1000, // 24 hours
54
- CONNECTIONS: 5 * 60 * 1000, // 5 minutes
55
- PRICES: 2 * 60 * 1000, // 2 minutes
56
- TRIP_DETAILS: 5 * 60 * 1000, // 5 minutes
57
- };
58
- /** Test-only: clear all entries. No effect in production. */
59
- export function __clearCacheForTesting() {
60
- store.clear();
61
- }
62
- //# sourceMappingURL=cache.js.map
package/dist/cache.js.map DELETED
@@ -1 +0,0 @@
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"}
package/dist/client.d.ts DELETED
@@ -1,17 +0,0 @@
1
- import type { SmapiProblem } from './types.js';
2
- export declare function getJourneyBaseUrl(): string;
3
- export declare function getTicketingBaseUrl(): string;
4
- export declare class SmapiError extends Error {
5
- problem?: SmapiProblem;
6
- status: number;
7
- constructor(message: string, status: number, problem?: SmapiProblem);
8
- get displayMessages(): string[];
9
- }
10
- type RequestOptions = {
11
- method?: 'GET' | 'POST';
12
- body?: unknown;
13
- conversationId?: string;
14
- acceptLanguage?: string;
15
- };
16
- export declare function smapiRequest<T>(baseUrl: string, path: string, options?: RequestOptions): Promise<T>;
17
- export {};
package/dist/client.js DELETED
@@ -1,70 +0,0 @@
1
- import { getAccessToken } from './auth.js';
2
- const JOURNEY_URLS = {
3
- int: 'https://smapi-osdm-journey-int.api.sbb.ch',
4
- prod: 'https://smapi-osdm-journey.api.sbb.ch',
5
- };
6
- const TICKETING_URLS = {
7
- int: 'https://b2p-int.api.sbb.ch',
8
- prod: 'https://b2p.api.sbb.ch',
9
- };
10
- function getEnv() {
11
- return process.env.SMAPI_ENV || 'int';
12
- }
13
- export function getJourneyBaseUrl() {
14
- return JOURNEY_URLS[getEnv()];
15
- }
16
- export function getTicketingBaseUrl() {
17
- return TICKETING_URLS[getEnv()];
18
- }
19
- export class SmapiError extends Error {
20
- problem;
21
- status;
22
- constructor(message, status, problem) {
23
- super(message);
24
- this.name = 'SmapiError';
25
- this.status = status;
26
- this.problem = problem;
27
- }
28
- get displayMessages() {
29
- return this.problem?.displayMessages ?? [];
30
- }
31
- }
32
- export async function smapiRequest(baseUrl, path, options = {}) {
33
- const { method = 'GET', body, conversationId, acceptLanguage = 'en' } = options;
34
- const token = await getAccessToken();
35
- const contractId = process.env.SMAPI_CONTRACT_ID || '';
36
- const headers = {
37
- Authorization: `Bearer ${token}`,
38
- 'x-contract-id': contractId,
39
- 'x-conversation-id': conversationId || crypto.randomUUID(),
40
- Accept: 'application/json',
41
- 'Accept-Language': acceptLanguage,
42
- 'Cache-Control': 'no-cache',
43
- };
44
- if (body) {
45
- headers['Content-Type'] = 'application/json';
46
- }
47
- const res = await fetch(`${baseUrl}${path}`, {
48
- method,
49
- headers,
50
- body: body ? JSON.stringify(body) : undefined,
51
- });
52
- if (!res.ok) {
53
- let problem;
54
- try {
55
- const contentType = res.headers.get('content-type') || '';
56
- if (contentType.includes('json')) {
57
- problem = await res.json();
58
- }
59
- }
60
- catch {
61
- // ignore parse errors
62
- }
63
- throw new SmapiError(problem?.title || `SMAPI request failed: ${res.status}`, res.status, problem);
64
- }
65
- if (res.status === 204) {
66
- return undefined;
67
- }
68
- return res.json();
69
- }
70
- //# sourceMappingURL=client.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -1,35 +0,0 @@
1
- import type { SmapiPlace, SmapiTrip, SmapiTripsCollection, SmapiPriceResult } from './transport/smapi-types.js';
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;
16
- export declare function formatStations(stations: SmapiPlace[], lang?: Lang): string;
17
- export declare function formatConnections(collection: SmapiTripsCollection, lang?: Lang): string;
18
- export declare function formatTripDetails(trip: SmapiTrip, lang?: Lang): string;
19
- export declare function formatPrices(results: SmapiPriceResult[], lang?: Lang): string;
20
- /**
21
- * Build an SBB.ch deep link URL with connection pre-filled.
22
- * SBB.ch URLs support de/fr/it/en; other languages fall back to `en`.
23
- */
24
- export declare function buildSbbDeepLink(params: {
25
- fromName: string;
26
- fromId: string;
27
- toName: string;
28
- toId: string;
29
- date: string;
30
- time: string;
31
- lang?: Lang;
32
- }): string;
33
- export declare function formatTicketLink(tripId: string, deepLink?: string, sbbLink?: string, lang?: Lang): string;
34
- /** Append destination weather to formatted connections */
35
- export declare function appendWeatherToConnections(formatted: string, destinationLat: number, destinationLon: number, date: string, destinationName: string): Promise<string>;
@@ -1,285 +0,0 @@
1
- import { getWeatherSummary } from 'swiss-weather-mcp';
2
- import { toLocale, toSbbLinkLang, t } from './i18n.js';
3
- import { buildShortLink } from './shortlink.js';
4
- function formatTime(iso, lang) {
5
- const d = new Date(iso);
6
- return d.toLocaleTimeString(toLocale(lang), {
7
- hour: '2-digit',
8
- minute: '2-digit',
9
- timeZone: 'Europe/Zurich',
10
- });
11
- }
12
- function formatDate(iso, lang) {
13
- const d = new Date(iso);
14
- return d.toLocaleDateString(toLocale(lang), {
15
- weekday: 'short',
16
- day: '2-digit',
17
- month: '2-digit',
18
- year: 'numeric',
19
- timeZone: 'Europe/Zurich',
20
- });
21
- }
22
- function parseDuration(iso) {
23
- const match = iso.match(/PT(?:(\d+)H)?(?:(\d+)M)?/);
24
- if (!match)
25
- return iso;
26
- const h = match[1] ? `${match[1]}h` : '';
27
- const m = match[2] ? `${match[2]}m` : '';
28
- return `${h}${h && m ? ' ' : ''}${m}` || '0m';
29
- }
30
- function transfersLabel(n, lang) {
31
- const tr = t(lang);
32
- if (n === 0)
33
- return tr.direct;
34
- if (n === 1)
35
- return tr.oneChange;
36
- return tr.nChanges(n);
37
- }
38
- /**
39
- * Terse one-line summary used when the client renders the Apps SDK widget
40
- * (see `isWidgetRenderingClient`). Widget clients already show a full card
41
- * list — returning the long markdown table here just causes the LLM to
42
- * paraphrase it into a redundant narration right next to the iframe. The
43
- * structured content still carries all the data the model needs to answer
44
- * follow-up questions; this string is purely for the chat bubble above the
45
- * widget.
46
- */
47
- export function formatStationsTerse(stations, _lang = 'en') {
48
- if (stations.length === 0) {
49
- return 'No stations found.';
50
- }
51
- const names = stations.slice(0, 3).map(s => s.name).join(', ');
52
- const more = stations.length > 3 ? ` (+${stations.length - 3} more)` : '';
53
- return `Found ${stations.length} station${stations.length === 1 ? '' : 's'}: ${names}${more}.`;
54
- }
55
- export function formatConnectionsTerse(collection, lang = 'en') {
56
- const trips = collection.trips;
57
- const tr = t(lang);
58
- if (trips.length === 0)
59
- return tr.noConnections;
60
- const first = trips[0];
61
- const date = formatDate(first.startTime, lang);
62
- const dep = formatTime(first.startTime, lang);
63
- const arr = formatTime(first.endTime, lang);
64
- const dur = parseDuration(first.duration);
65
- const changes = transfersLabel(first.transfers, lang);
66
- return `${trips.length} connection${trips.length === 1 ? '' : 's'} ${first.origin.name} → ${first.destination.name} · ${date}. Next: ${dep} → ${arr} (${dur}, ${changes}).`;
67
- }
68
- export function formatTripDetailsTerse(trip, lang = 'en') {
69
- const date = formatDate(trip.startTime, lang);
70
- const dep = formatTime(trip.startTime, lang);
71
- const arr = formatTime(trip.endTime, lang);
72
- const dur = parseDuration(trip.duration);
73
- const changes = transfersLabel(trip.transfers, lang);
74
- return `${trip.origin.name} → ${trip.destination.name} · ${date} · ${dep}–${arr} (${dur}, ${changes}).`;
75
- }
76
- export function formatPricesTerse(results, lang = 'en') {
77
- const tr = t(lang);
78
- if (results.length === 0)
79
- return 'No price information available.';
80
- const cheapest = results
81
- .map(r => r.prices.find(p => p.class === '2')?.amount)
82
- .filter((a) => typeof a === 'number')
83
- .sort((a, b) => a - b)[0];
84
- const from = typeof cheapest === 'number' ? ` from CHF ${cheapest.toFixed(2)}` : '';
85
- return `${tr.prices} for ${results.length} trip${results.length === 1 ? '' : 's'}${from}.`;
86
- }
87
- export function formatStations(stations, lang = 'en') {
88
- if (stations.length === 0) {
89
- return 'No stations found.';
90
- }
91
- const lines = [`Found ${stations.length} station(s):`, ''];
92
- lines.push('| Station | ID | Coordinates |');
93
- lines.push('|---|---|---|');
94
- for (const s of stations) {
95
- const coords = s.geoPosition
96
- ? `${s.geoPosition.latitude.toFixed(4)}, ${s.geoPosition.longitude.toFixed(4)}`
97
- : '-';
98
- lines.push(`| ${s.name} | \`${s.id}\` | ${coords} |`);
99
- }
100
- return lines.join('\n');
101
- }
102
- /**
103
- * Convert an ISO timestamp into Europe/Zurich-local YYYY-MM-DD + HH:MM pair.
104
- * Matches `toZurichDateTime` in structured.ts. Used to build SBB deep links
105
- * in the markdown text path.
106
- */
107
- function toZurichDateTime(iso) {
108
- const d = new Date(iso);
109
- const date = d.toLocaleDateString('en-CA', { timeZone: 'Europe/Zurich' });
110
- const time = d.toLocaleTimeString('en-GB', {
111
- timeZone: 'Europe/Zurich',
112
- hour: '2-digit',
113
- minute: '2-digit',
114
- });
115
- return { date, time };
116
- }
117
- export function formatConnections(collection, lang = 'en') {
118
- const trips = collection.trips;
119
- const tr = t(lang);
120
- if (trips.length === 0) {
121
- return tr.noConnections;
122
- }
123
- const first = trips[0];
124
- const date = formatDate(first.startTime, lang);
125
- //
126
- // Markdown table format.
127
- //
128
- // We tried a prose bullet list first (expecting ChatGPT's rewrite to keep
129
- // the links intact) but ChatGPT ignores the text entirely when it has
130
- // structuredContent — it paraphrases from the JSON and picks just one
131
- // connection to feature. The table format is here as the canonical text
132
- // response for MCP clients that DO render markdown (Claude Desktop, the
133
- // WhatsApp bot, our own web UI) — which want all 5 options side-by-side.
134
- //
135
- // The booking URL is intentionally hidden behind "Book here" in the last
136
- // column (not exposed as a bare URL) so rendering is clean in every
137
- // markdown-capable client.
138
- //
139
- const lines = [
140
- `**${first.origin.name} → ${first.destination.name}** · ${date}`,
141
- '',
142
- `| ${tr.dep} | ${tr.arr} | ${tr.dur} | ${tr.changes} | |`,
143
- '|---|---|---|---|---|',
144
- ];
145
- for (const trip of trips) {
146
- const dep = formatTime(trip.startTime, lang);
147
- const arr = formatTime(trip.endTime, lang);
148
- const dur = parseDuration(trip.duration);
149
- const { date: d, time } = toZurichDateTime(trip.startTime);
150
- const bookingUrl = buildShortLink({
151
- tripId: trip.id,
152
- fromId: trip.origin.id,
153
- fromName: trip.origin.name,
154
- toId: trip.destination.id,
155
- toName: trip.destination.name,
156
- date: d,
157
- time,
158
- lang,
159
- });
160
- lines.push(`| ${dep} | ${arr} | ${dur} | ${transfersLabel(trip.transfers, lang)} | [${tr.bookHere}](${bookingUrl}) |`);
161
- }
162
- lines.push('');
163
- lines.push(`*Collection ID: \`${collection.id}\` — pass to \`get_more_connections\` for earlier/later trains.*`);
164
- return lines.join('\n');
165
- }
166
- export function formatTripDetails(trip, lang = 'en') {
167
- const tr = t(lang);
168
- const lines = [
169
- `**Trip Details** | ${formatDate(trip.startTime, lang)}`,
170
- `${trip.origin.name} -> ${trip.destination.name} | ${parseDuration(trip.duration)} | ${transfersLabel(trip.transfers, lang)}`,
171
- `Status: ${trip.tripStatus}`,
172
- '',
173
- ];
174
- for (let i = 0; i < trip.legs.length; i++) {
175
- const leg = trip.legs[i];
176
- if (leg.type === 'timed') {
177
- const tl = leg;
178
- const line = tl.service.publishedLineName || 'Train';
179
- const operator = tl.service.operatorName || '';
180
- const boardPlatform = tl.board.platform ? ` | ${tr.platform} ${tl.board.platform}` : '';
181
- const alightPlatform = tl.alight.platform ? ` | ${tr.platform} ${tl.alight.platform}` : '';
182
- lines.push(`### Leg ${i + 1}: ${line} ${operator}`.trim());
183
- lines.push(`- **${formatTime(tl.board.departureTime, lang)}** ${tl.board.stopPlace.name}${boardPlatform}`);
184
- if (tl.intermediateStops?.length) {
185
- for (const stop of tl.intermediateStops) {
186
- const time = stop.arrivalTime ? formatTime(stop.arrivalTime, lang) : '';
187
- lines.push(` - ${time} ${stop.stopPlace.name}`);
188
- }
189
- }
190
- lines.push(`- **${formatTime(tl.alight.arrivalTime, lang)}** ${tl.alight.stopPlace.name}${alightPlatform}`);
191
- if (tl.occupancy) {
192
- const occ = [];
193
- if (tl.occupancy.firstClass)
194
- occ.push(`1st: ${tl.occupancy.firstClass}`);
195
- if (tl.occupancy.secondClass)
196
- occ.push(`2nd: ${tl.occupancy.secondClass}`);
197
- if (occ.length)
198
- lines.push(` Occupancy: ${occ.join(', ')}`);
199
- }
200
- lines.push(` ${tr.dur}: ${parseDuration(tl.duration)}`);
201
- }
202
- else if (leg.type === 'transfer') {
203
- const tfer = leg;
204
- lines.push(`### ${tr.transfer}: Walk ${parseDuration(tfer.duration)}`);
205
- }
206
- lines.push('');
207
- }
208
- lines.push(`*Use \`get_prices\` with trip ID \`${trip.id}\` to see ticket prices.*`);
209
- return lines.join('\n');
210
- }
211
- export function formatPrices(results, lang = 'en') {
212
- if (results.length === 0) {
213
- return 'No price information available.';
214
- }
215
- const tr = t(lang);
216
- const lines = [`**${tr.prices}**`, ''];
217
- lines.push(`| Trip ID | 2nd Class | 1st Class |`);
218
- lines.push('|---------|-----------|-----------|');
219
- for (const r of results) {
220
- const second = r.prices.find(p => p.class === '2');
221
- const first = r.prices.find(p => p.class === '1');
222
- const secondStr = second ? `CHF ${second.amount.toFixed(2)}` : '-';
223
- const firstStr = first ? `CHF ${first.amount.toFixed(2)}` : '-';
224
- lines.push(`| \`${r.tripId}\` | ${secondStr} | ${firstStr} |`);
225
- }
226
- lines.push('');
227
- lines.push('*Prices are estimates. Use `get_ticket_link` to get the purchase link with final pricing.*');
228
- return lines.join('\n');
229
- }
230
- /**
231
- * Build an SBB.ch deep link URL with connection pre-filled.
232
- * SBB.ch URLs support de/fr/it/en; other languages fall back to `en`.
233
- */
234
- export function buildSbbDeepLink(params) {
235
- const { fromName, fromId, toName, toId, date, time } = params;
236
- const linkLang = params.lang ? toSbbLinkLang(params.lang) : 'en';
237
- const stops = JSON.stringify([
238
- { value: fromId, type: 'ID', label: fromName },
239
- { value: toId, type: 'ID', label: toName },
240
- ]);
241
- const url = new URL(`https://www.sbb.ch/${linkLang}`);
242
- url.searchParams.set('stops', stops);
243
- url.searchParams.set('date', `"${date}"`);
244
- url.searchParams.set('time', `"${time}"`);
245
- url.searchParams.set('moment', '"DEPARTURE"');
246
- url.searchParams.set('selected_trip', '0');
247
- return url.toString();
248
- }
249
- export function formatTicketLink(tripId, deepLink, sbbLink, lang = 'en') {
250
- const tr = t(lang);
251
- const links = [];
252
- if (sbbLink) {
253
- links.push(`[${tr.buyTicket}](${sbbLink})`);
254
- }
255
- if (deepLink && deepLink !== sbbLink) {
256
- links.push(`[Buy via affiliate](${deepLink})`);
257
- }
258
- if (links.length === 0) {
259
- return `No ticket link available for trip \`${tripId}\`. The trip may have expired or is not bookable online.`;
260
- }
261
- return [
262
- `**${tr.buyTicket}**`,
263
- '',
264
- `Trip: \`${tripId}\``,
265
- '',
266
- ...links,
267
- '',
268
- `*${tr.opensInApp}*`,
269
- ].join('\n');
270
- }
271
- /** Append destination weather to formatted connections */
272
- export async function appendWeatherToConnections(formatted, destinationLat, destinationLon, date, destinationName) {
273
- // Weather stays English for now — swiss-weather-mcp getWeatherSummary() doesn't accept a language yet.
274
- // TODO(v0.5.0): thread lang through once swiss-weather-mcp exposes it.
275
- try {
276
- const summary = await getWeatherSummary(destinationLat, destinationLon, date);
277
- if (!summary)
278
- return formatted;
279
- return formatted + `\n\n**${destinationName} weather:** ${summary}`;
280
- }
281
- catch {
282
- return formatted;
283
- }
284
- }
285
- //# sourceMappingURL=formatters.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"formatters.js","sourceRoot":"","sources":["../src/formatters.ts"],"names":[],"mappings":"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"}
package/dist/http.d.ts DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
package/dist/http.js DELETED
@@ -1,117 +0,0 @@
1
- #!/usr/bin/env node
2
- import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
3
- import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';
4
- import { isSmapiConfigured } from './transport/index.js';
5
- import { installSbbTransportAdapters } from './transport/setup.js';
6
- import { isSwissTripConfigured } from './swisstrip.js';
7
- import { createSbbMcpServer } from './tools.js';
8
- import { isRateLimited } from './rate-limit.js';
9
- import { decodeShortlink, resolveTargetUrl } from './shortlink.js';
10
- import { recordClick, getRatioSnapshot } from './look2book.js';
11
- const useMock = !isSmapiConfigured();
12
- async function main() {
13
- installSbbTransportAdapters();
14
- const port = parseInt(process.env.PORT || '3001');
15
- const app = createMcpExpressApp({ host: '0.0.0.0' });
16
- app.post('/mcp', async (req, res) => {
17
- const ip = req.ip || req.socket.remoteAddress || 'unknown';
18
- if (isRateLimited(ip)) {
19
- res.status(429).json({ error: 'Too many requests. Max 60 per minute.' });
20
- return;
21
- }
22
- const server = createSbbMcpServer();
23
- const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
24
- try {
25
- await server.connect(transport);
26
- await transport.handleRequest(req, res, req.body);
27
- }
28
- finally {
29
- await transport.close();
30
- await server.close();
31
- }
32
- });
33
- app.get('/health', (_req, res) => {
34
- // Expose Look2Book state so readiness probes + dashboards can surface
35
- // contractual ratio violations without parsing stdout logs.
36
- res.json({
37
- status: 'ok',
38
- mode: useMock ? 'mock' : 'live',
39
- look2book: getRatioSnapshot(),
40
- });
41
- });
42
- // ─── Booking redirect ─────────────────────────────────────────────────
43
- //
44
- // Every booking URL in our tool responses points at this endpoint. We
45
- // log the click (for SBB Look2Book telemetry + channel attribution),
46
- // then 302 to the real sbb.ch deep link. Stateless: everything needed
47
- // is encoded in `:token`.
48
- //
49
- // Log format is intentionally a single JSON line per click so Railway's
50
- // log drain (or future log forwarder) can ingest it directly.
51
- app.get('/r/:token', (req, res) => {
52
- const { token } = req.params;
53
- try {
54
- const payload = decodeShortlink(token);
55
- const target = resolveTargetUrl(payload);
56
- const log = {
57
- event: 'sbb_booking_click',
58
- ts: new Date().toISOString(),
59
- tripId: payload.tid,
60
- origin: payload.o,
61
- destination: payload.d,
62
- date: payload.dt,
63
- time: payload.t,
64
- lang: payload.l,
65
- ip: req.ip || req.socket.remoteAddress || 'unknown',
66
- ua: req.get('user-agent') || 'unknown',
67
- referer: req.get('referer') || '',
68
- };
69
- // One-line JSON for easy grep/ingest. Railway captures stdout.
70
- console.log(JSON.stringify(log));
71
- // Credit the Look2Book ratio — this represents real booking intent
72
- // and offsets SMAPI calls in both the trips and offers windows.
73
- recordClick({ tripId: payload.tid });
74
- res.redirect(302, target);
75
- }
76
- catch (err) {
77
- const msg = err instanceof Error ? err.message : String(err);
78
- console.warn(JSON.stringify({
79
- event: 'sbb_booking_click_invalid',
80
- ts: new Date().toISOString(),
81
- reason: msg,
82
- token: token.slice(0, 32), // truncate in case it's garbage
83
- }));
84
- res.status(400).type('text/plain').send(`Invalid booking link: ${msg}`);
85
- }
86
- });
87
- app.get('/.well-known/mcp/server-card.json', (_req, res) => {
88
- res.json({
89
- name: 'sbb-mcp',
90
- description: 'Swiss Federal Railways (SBB/CFF/FFS) — train schedules, prices, and ticket links',
91
- homepage: 'https://github.com/Fabsbags/sbb-mcp',
92
- configSchema: {
93
- type: 'object',
94
- properties: {
95
- SMAPI_CLIENT_ID: { type: 'string', description: 'SBB SMAPI OAuth 2.0 client ID' },
96
- SMAPI_CLIENT_SECRET: { type: 'string', description: 'SBB SMAPI OAuth 2.0 client secret' },
97
- SMAPI_SCOPE: { type: 'string', description: 'SBB SMAPI OAuth scope' },
98
- SMAPI_CONTRACT_ID: { type: 'string', description: 'SBB business contract ID' },
99
- },
100
- },
101
- });
102
- });
103
- app.listen(port, '0.0.0.0', () => {
104
- console.log(`[sbb-mcp] HTTP server listening on port ${port}`);
105
- console.log(`[sbb-mcp] MCP endpoint: http://0.0.0.0:${port}/mcp`);
106
- console.log(`[sbb-mcp] Mode: ${useMock ? 'MOCK' : 'LIVE'}`);
107
- console.log(`[sbb-mcp] SwissTrip cloud: ${isSwissTripConfigured() ? 'enabled' : 'disabled'}`);
108
- console.log(`[sbb-mcp] Rate limit: 60 req/min per IP`);
109
- console.log(`[sbb-mcp] Booking redirects: /r/:token (logs to stdout)`);
110
- console.log(`[sbb-mcp] Look2Book telemetry: 1h window, caps trips=100:1 offers=50:1`);
111
- });
112
- }
113
- main().catch((err) => {
114
- console.error('[sbb-mcp] Fatal error:', err);
115
- process.exit(1);
116
- });
117
- //# sourceMappingURL=http.js.map
package/dist/http.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}