mcp-swiss 0.1.7 โ 0.2.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/README.md +57 -2
- package/dist/index.js +25 -0
- package/dist/modules/airquality.d.ts +25 -0
- package/dist/modules/airquality.js +131 -0
- package/dist/modules/avalanche.d.ts +33 -0
- package/dist/modules/avalanche.js +191 -0
- package/dist/modules/holidays.d.ts +33 -0
- package/dist/modules/holidays.js +163 -0
- package/dist/modules/parliament.d.ts +103 -0
- package/dist/modules/parliament.js +323 -0
- package/dist/modules/post.d.ts +66 -0
- package/dist/modules/post.js +276 -0
- package/mcp-manifest.json +33 -0
- package/package.json +1 -1
- package/server.json +7 -5
- package/src/index.ts +20 -0
- package/src/modules/airquality.ts +184 -0
- package/src/modules/avalanche.ts +233 -0
- package/src/modules/holidays.ts +216 -0
- package/src/modules/parliament.ts +473 -0
- package/src/modules/post.ts +392 -0
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
`mcp-swiss` is a [Model Context Protocol](https://modelcontextprotocol.io) server that gives any AI assistant direct access to Swiss open data โ trains, weather, rivers, maps, and companies.
|
|
21
21
|
|
|
22
|
-
**
|
|
22
|
+
**37 tools. No API keys. No registration. No server to run. Just `npx mcp-swiss`.**
|
|
23
23
|
|
|
24
24
|
```
|
|
25
25
|
๐ Transport โ SBB, PostBus, trams, live departures, journey planning
|
|
@@ -27,6 +27,11 @@
|
|
|
27
27
|
๐ Hydrology โ BAFU river & lake levels (great for Aare swimming!)
|
|
28
28
|
๐บ๏ธ Geodata โ swisstopo geocoding, solar potential, geographic layers
|
|
29
29
|
๐ข Companies โ ZEFIX federal registry, all 700K+ Swiss companies
|
|
30
|
+
๐ Holidays โ Swiss public & school holidays by canton
|
|
31
|
+
๐๏ธ Parliament โ Bills, votes, councillors, session schedule
|
|
32
|
+
๐๏ธ Avalanche โ SLF danger bulletins and warning regions
|
|
33
|
+
๐จ Air Quality โ NABEL stations, Swiss legal limits (LRV)
|
|
34
|
+
๐ฎ Swiss Post โ Postcode lookup and parcel tracking
|
|
30
35
|
```
|
|
31
36
|
|
|
32
37
|
---
|
|
@@ -225,12 +230,17 @@ Once connected, try asking your AI:
|
|
|
225
230
|
| *"Live departures from Bern HB"* | `get_departures` |
|
|
226
231
|
| *"What rivers are near Thun?"* | `list_hydro_stations` + `get_water_level` |
|
|
227
232
|
| *"Plan my Saturday: train to Interlaken, check weather"* | Multiple tools chained |
|
|
233
|
+
| *"Is next Monday a holiday in Zรผrich?"* | `get_public_holidays` |
|
|
234
|
+
| *"What did the Swiss parliament vote on recently?"* | `get_latest_votes` |
|
|
235
|
+
| *"What's the avalanche danger level in the Bernese Alps?"* | `get_avalanche_bulletin` |
|
|
236
|
+
| *"What's the postcode for Zermatt?"* | `search_postcode` |
|
|
237
|
+
| *"Track my Swiss Post parcel 99.12.345678.12345678"* | `track_parcel` |
|
|
228
238
|
|
|
229
239
|
---
|
|
230
240
|
|
|
231
241
|
## Tools
|
|
232
242
|
|
|
233
|
-
> Full specifications: [`docs/tool-specs.md`](docs/tool-specs.md) ยท Machine-readable: [`docs/tools.schema.json`](docs/tools.schema.json)
|
|
243
|
+
> 37 tools across 9 modules. Full specifications: [`docs/tool-specs.md`](docs/tool-specs.md) ยท Machine-readable: [`docs/tools.schema.json`](docs/tools.schema.json)
|
|
234
244
|
|
|
235
245
|
### ๐ Transport (5 tools)
|
|
236
246
|
|
|
@@ -274,6 +284,46 @@ Once connected, try asking your AI:
|
|
|
274
284
|
| `list_cantons` | All 26 Swiss cantons |
|
|
275
285
|
| `list_legal_forms` | AG, GmbH, and all Swiss legal forms |
|
|
276
286
|
|
|
287
|
+
### ๐ Holidays (3 tools)
|
|
288
|
+
|
|
289
|
+
| Tool | Description |
|
|
290
|
+
|------|-------------|
|
|
291
|
+
| `get_public_holidays` | Swiss public holidays by year, optionally filtered by canton |
|
|
292
|
+
| `get_school_holidays` | School holiday periods by year and canton |
|
|
293
|
+
| `is_holiday_today` | Quick check if today is a public holiday |
|
|
294
|
+
|
|
295
|
+
### ๐๏ธ Parliament (4 tools)
|
|
296
|
+
|
|
297
|
+
| Tool | Description |
|
|
298
|
+
|------|-------------|
|
|
299
|
+
| `search_parliament_business` | Search bills, motions, interpellations |
|
|
300
|
+
| `get_latest_votes` | Recent parliamentary vote results |
|
|
301
|
+
| `search_councillors` | Find members of the National/States Council |
|
|
302
|
+
| `get_sessions` | Parliamentary session schedule |
|
|
303
|
+
|
|
304
|
+
### ๐๏ธ Avalanche (2 tools)
|
|
305
|
+
|
|
306
|
+
| Tool | Description |
|
|
307
|
+
|------|-------------|
|
|
308
|
+
| `get_avalanche_bulletin` | Current avalanche bulletin with danger levels and PDF links |
|
|
309
|
+
| `list_avalanche_regions` | All 22 Swiss avalanche warning regions |
|
|
310
|
+
|
|
311
|
+
### ๐จ Air Quality (2 tools)
|
|
312
|
+
|
|
313
|
+
| Tool | Description |
|
|
314
|
+
|------|-------------|
|
|
315
|
+
| `list_air_quality_stations` | All 14 NABEL monitoring stations |
|
|
316
|
+
| `get_air_quality` | Station info, Swiss legal limits (LRV), and BAFU data links |
|
|
317
|
+
|
|
318
|
+
### ๐ฎ Swiss Post (4 tools)
|
|
319
|
+
|
|
320
|
+
| Tool | Description |
|
|
321
|
+
|------|-------------|
|
|
322
|
+
| `lookup_postcode` | PLZ โ locality, canton, coordinates |
|
|
323
|
+
| `search_postcode` | City name โ matching postcodes |
|
|
324
|
+
| `list_postcodes_in_canton` | All postcodes in a canton |
|
|
325
|
+
| `track_parcel` | Generate Swiss Post tracking URL for a parcel |
|
|
326
|
+
|
|
277
327
|
---
|
|
278
328
|
|
|
279
329
|
## Data Sources
|
|
@@ -286,6 +336,11 @@ All official Swiss open data โ no API keys required:
|
|
|
286
336
|
| [api.existenz.ch](https://api.existenz.ch) | MeteoSwiss weather + BAFU hydrology | [API docs](https://api.existenz.ch) |
|
|
287
337
|
| [api3.geo.admin.ch](https://api3.geo.admin.ch) | swisstopo federal geodata | [API docs](https://api3.geo.admin.ch/api/doc.html) |
|
|
288
338
|
| [zefix.admin.ch](https://www.zefix.admin.ch) | Federal company registry | [Swagger](https://www.zefix.admin.ch/ZefixREST/swagger-ui.html) |
|
|
339
|
+
| [openholidaysapi.org](https://openholidaysapi.org) | Swiss public & school holidays | [API docs](https://openholidaysapi.org/swagger) |
|
|
340
|
+
| [ws.parlament.ch](https://ws.parlament.ch) | Swiss Parliament OData (bills, votes, councillors) | [OData docs](https://ws.parlament.ch/odata.svc/$metadata) |
|
|
341
|
+
| [whiterisk.ch](https://whiterisk.ch) / [aws.slf.ch](https://aws.slf.ch) | SLF/WSL avalanche bulletins | [SLF](https://www.slf.ch/en/avalanche-bulletin-and-snow-situation.html) |
|
|
342
|
+
| [geo.admin.ch](https://api3.geo.admin.ch) โ BAFU/NABEL | Swiss air quality monitoring stations | [BAFU NABEL](https://www.bafu.admin.ch/bafu/en/home/topics/air/state/data/nabel.html) |
|
|
343
|
+
| [geo.admin.ch](https://api3.geo.admin.ch) โ swisstopo | Swiss postcodes (Amtliches Ortschaftenverzeichnis) | [geo.admin.ch](https://api3.geo.admin.ch/api/doc.html) |
|
|
289
344
|
|
|
290
345
|
---
|
|
291
346
|
|
package/dist/index.js
CHANGED
|
@@ -8,12 +8,22 @@ const transport_js_1 = require("./modules/transport.js");
|
|
|
8
8
|
const weather_js_1 = require("./modules/weather.js");
|
|
9
9
|
const geodata_js_1 = require("./modules/geodata.js");
|
|
10
10
|
const companies_js_1 = require("./modules/companies.js");
|
|
11
|
+
const holidays_js_1 = require("./modules/holidays.js");
|
|
12
|
+
const parliament_js_1 = require("./modules/parliament.js");
|
|
13
|
+
const avalanche_js_1 = require("./modules/avalanche.js");
|
|
14
|
+
const airquality_js_1 = require("./modules/airquality.js");
|
|
15
|
+
const post_js_1 = require("./modules/post.js");
|
|
11
16
|
const server = new index_js_1.Server({ name: "mcp-swiss", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
12
17
|
const allTools = [
|
|
13
18
|
...transport_js_1.transportTools,
|
|
14
19
|
...weather_js_1.weatherTools,
|
|
15
20
|
...geodata_js_1.geodataTools,
|
|
16
21
|
...companies_js_1.companiesTools,
|
|
22
|
+
...holidays_js_1.holidaysTools,
|
|
23
|
+
...parliament_js_1.parliamentTools,
|
|
24
|
+
...avalanche_js_1.avalancheTools,
|
|
25
|
+
...airquality_js_1.airqualityTools,
|
|
26
|
+
...post_js_1.postTools,
|
|
17
27
|
];
|
|
18
28
|
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
19
29
|
tools: allTools,
|
|
@@ -35,6 +45,21 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
35
45
|
else if (companies_js_1.companiesTools.some((t) => t.name === name)) {
|
|
36
46
|
result = await (0, companies_js_1.handleCompanies)(name, safeArgs);
|
|
37
47
|
}
|
|
48
|
+
else if (holidays_js_1.holidaysTools.some((t) => t.name === name)) {
|
|
49
|
+
result = await (0, holidays_js_1.handleHolidays)(name, safeArgs);
|
|
50
|
+
}
|
|
51
|
+
else if (parliament_js_1.parliamentTools.some((t) => t.name === name)) {
|
|
52
|
+
result = await (0, parliament_js_1.handleParliament)(name, safeArgs);
|
|
53
|
+
}
|
|
54
|
+
else if (avalanche_js_1.avalancheTools.some((t) => t.name === name)) {
|
|
55
|
+
result = await (0, avalanche_js_1.handleAvalanche)(name, safeArgs);
|
|
56
|
+
}
|
|
57
|
+
else if (airquality_js_1.airqualityTools.some((t) => t.name === name)) {
|
|
58
|
+
result = await (0, airquality_js_1.handleAirQuality)(name, safeArgs);
|
|
59
|
+
}
|
|
60
|
+
else if (post_js_1.postTools.some((t) => t.name === name)) {
|
|
61
|
+
result = await (0, post_js_1.handlePost)(name, safeArgs);
|
|
62
|
+
}
|
|
38
63
|
else {
|
|
39
64
|
throw new Error(`Unknown tool: ${name}`);
|
|
40
65
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare const airqualityTools: ({
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: string;
|
|
6
|
+
properties: {
|
|
7
|
+
station?: undefined;
|
|
8
|
+
};
|
|
9
|
+
required?: undefined;
|
|
10
|
+
};
|
|
11
|
+
} | {
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: string;
|
|
16
|
+
required: string[];
|
|
17
|
+
properties: {
|
|
18
|
+
station: {
|
|
19
|
+
type: string;
|
|
20
|
+
description: string;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
})[];
|
|
25
|
+
export declare function handleAirQuality(name: string, args: Record<string, unknown>): Promise<string>;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.airqualityTools = void 0;
|
|
4
|
+
exports.handleAirQuality = handleAirQuality;
|
|
5
|
+
const http_js_1 = require("../utils/http.js");
|
|
6
|
+
const GEO_ADMIN = "https://api3.geo.admin.ch/rest/services/api/MapServer";
|
|
7
|
+
const NABELSTATIONEN_LAYER = "ch.bafu.nabelstationen";
|
|
8
|
+
// โโ Known NABEL stations (BAFU/EMPA national monitoring network) โโโโโโโโโโโโโ
|
|
9
|
+
// Source: geo.admin.ch ch.bafu.nabelstationen layer
|
|
10
|
+
// These are the official NABEL (Nationales Beobachtungsnetz fรผr Luftfremdstoffe) stations
|
|
11
|
+
const NABEL_STATIONS = {
|
|
12
|
+
BAS: { name: "Basel-Binningen", canton: "BS", lat: 47.541081, lon: 7.583264, altitude_m: 316, environment: "urban" },
|
|
13
|
+
BER: { name: "Bern-Bollwerk", canton: "BE", lat: 46.950993, lon: 7.440866, altitude_m: 540, environment: "urban" },
|
|
14
|
+
CHA: { name: "Chaumont", canton: "NE", lat: 47.049465, lon: 6.979204, altitude_m: 1136, environment: "rural-elevated" },
|
|
15
|
+
DAV: { name: "Davos", canton: "GR", lat: 46.815199, lon: 9.855859, altitude_m: 1590, environment: "alpine" },
|
|
16
|
+
DUE: { name: "Duebendorf", canton: "ZH", lat: 47.404842, lon: 8.608474, altitude_m: 432, environment: "suburban" },
|
|
17
|
+
HAE: { name: "Haerkingen", canton: "SO", lat: 47.311911, lon: 7.8205, altitude_m: 430, environment: "rural-roadside" },
|
|
18
|
+
LAU: { name: "Lausanne", canton: "VD", lat: 46.522018, lon: 6.639701, altitude_m: 540, environment: "urban" },
|
|
19
|
+
LUG: { name: "Lugano", canton: "TI", lat: 46.011117, lon: 8.957165, altitude_m: 273, environment: "urban" },
|
|
20
|
+
MAG: { name: "Magadino-Cadenazzo", canton: "TI", lat: 46.160376, lon: 8.933939, altitude_m: 203, environment: "rural" },
|
|
21
|
+
PAY: { name: "Payerne", canton: "VD", lat: 46.813057, lon: 6.944473, altitude_m: 490, environment: "rural" },
|
|
22
|
+
RIG: { name: "Rigi-Seebodenalp", canton: "SZ", lat: 47.06741, lon: 8.46333, altitude_m: 1030, environment: "alpine" },
|
|
23
|
+
SIO: { name: "Sion-Aerodrome", canton: "VS", lat: 46.220201, lon: 7.341966, altitude_m: 482, environment: "urban" },
|
|
24
|
+
TAE: { name: "Taenikon", canton: "TG", lat: 47.479771, lon: 8.904686, altitude_m: 540, environment: "rural" },
|
|
25
|
+
ZUE: { name: "Zรผrich-Kaserne", canton: "ZH", lat: 47.3769, lon: 8.5417, altitude_m: 409, environment: "urban" },
|
|
26
|
+
};
|
|
27
|
+
// โโ Swiss legal air quality limits (LRV - Luftreinhalteordnung, Swiss Clean Air Act) โโ
|
|
28
|
+
// Immissionsgrenzwerte (IGW) โ annual mean limits in ยตg/mยณ
|
|
29
|
+
const SWISS_LIMITS = {
|
|
30
|
+
PM10: { annual_mean_ยตg_m3: 20, daily_mean_ยตg_m3: 50, who_note: "WHO 2021 guideline: 15 ยตg/mยณ annual, 45 ยตg/mยณ daily" },
|
|
31
|
+
PM2_5: { annual_mean_ยตg_m3: 10, who_note: "WHO 2021 guideline: 5 ยตg/mยณ annual" },
|
|
32
|
+
O3: { hourly_mean_ยตg_m3: 120, who_note: "Peak season 8h: 60 ยตg/mยณ (WHO 2021)" },
|
|
33
|
+
NO2: { annual_mean_ยตg_m3: 30, hourly_mean_ยตg_m3: 100, who_note: "WHO 2021 guideline: 10 ยตg/mยณ annual" },
|
|
34
|
+
SO2: { annual_mean_ยตg_m3: 30, daily_mean_ยตg_m3: 100 },
|
|
35
|
+
};
|
|
36
|
+
// โโ Tool definitions โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
37
|
+
exports.airqualityTools = [
|
|
38
|
+
{
|
|
39
|
+
name: "list_air_quality_stations",
|
|
40
|
+
description: "List all official Swiss NABEL (Nationales Beobachtungsnetz fรผr Luftfremdstoffe) air quality monitoring stations operated by BAFU/EMPA. Returns station codes, names, cantons, coordinates, and environment types.",
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "get_air_quality",
|
|
48
|
+
description: "Get information about a Swiss NABEL air quality monitoring station, including location, environment type, Swiss legal limits (LRV), and a direct link to the BAFU live data portal. Use station codes from list_air_quality_stations (e.g. BER=Bern, ZUE=Zรผrich, LUG=Lugano).",
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: "object",
|
|
51
|
+
required: ["station"],
|
|
52
|
+
properties: {
|
|
53
|
+
station: {
|
|
54
|
+
type: "string",
|
|
55
|
+
description: "NABEL station code (e.g. BER, ZUE, LUG, BAS, DAV). Use list_air_quality_stations for all codes.",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
// โโ Helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
62
|
+
function normalizeStationCode(code) {
|
|
63
|
+
return code.trim().toUpperCase();
|
|
64
|
+
}
|
|
65
|
+
async function fetchStationFromApi(stationCode) {
|
|
66
|
+
const url = `${GEO_ADMIN}/${NABELSTATIONEN_LAYER}/${encodeURIComponent(stationCode)}?returnGeometry=true&sr=4326`;
|
|
67
|
+
try {
|
|
68
|
+
const data = await (0, http_js_1.fetchJSON)(url);
|
|
69
|
+
return data?.feature ?? null;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// โโ Handler โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
76
|
+
async function handleAirQuality(name, args) {
|
|
77
|
+
switch (name) {
|
|
78
|
+
case "list_air_quality_stations": {
|
|
79
|
+
// Build compact station dict from our hardcoded registry (confirmed via geo.admin.ch API)
|
|
80
|
+
const stations = {};
|
|
81
|
+
for (const [code, info] of Object.entries(NABEL_STATIONS)) {
|
|
82
|
+
stations[code] = `${info.name} (${info.canton}) โ ${info.environment}`;
|
|
83
|
+
}
|
|
84
|
+
return JSON.stringify({
|
|
85
|
+
count: Object.keys(stations).length,
|
|
86
|
+
network: "NABEL โ Nationales Beobachtungsnetz fรผr Luftfremdstoffe",
|
|
87
|
+
operator: "BAFU (Swiss Federal Office for the Environment) / EMPA",
|
|
88
|
+
source: "geo.admin.ch ch.bafu.nabelstationen",
|
|
89
|
+
data_portal: "https://www.bafu.admin.ch/bafu/en/home/topics/air/state/data/nabel.html",
|
|
90
|
+
stations,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
case "get_air_quality": {
|
|
94
|
+
const code = normalizeStationCode(args.station);
|
|
95
|
+
const local = NABEL_STATIONS[code];
|
|
96
|
+
if (!local) {
|
|
97
|
+
const knownCodes = Object.keys(NABEL_STATIONS).join(", ");
|
|
98
|
+
throw new Error(`Unknown NABEL station code "${code}". Known stations: ${knownCodes}. ` +
|
|
99
|
+
`Use list_air_quality_stations to see all options.`);
|
|
100
|
+
}
|
|
101
|
+
// Optionally enrich from live geo.admin.ch API (non-blocking fallback)
|
|
102
|
+
let apiName;
|
|
103
|
+
try {
|
|
104
|
+
const feature = await fetchStationFromApi(code);
|
|
105
|
+
apiName = feature?.attributes?.name;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// continue with local data
|
|
109
|
+
}
|
|
110
|
+
const stationName = apiName ?? local.name;
|
|
111
|
+
return JSON.stringify({
|
|
112
|
+
station: code,
|
|
113
|
+
name: stationName,
|
|
114
|
+
canton: local.canton,
|
|
115
|
+
coordinates: { lat: local.lat, lon: local.lon },
|
|
116
|
+
altitude_m: local.altitude_m,
|
|
117
|
+
environment: local.environment,
|
|
118
|
+
network: "NABEL",
|
|
119
|
+
operator: "BAFU / EMPA",
|
|
120
|
+
source: "geo.admin.ch โ ch.bafu.nabelstationen",
|
|
121
|
+
data_note: "Live NABEL measurements (PM10, PM2.5, O3, NO2, SO2) are published on the BAFU data portal. " +
|
|
122
|
+
"No public REST API for real-time values โ use the portal link below.",
|
|
123
|
+
live_data_portal: "https://www.bafu.admin.ch/bafu/en/home/topics/air/state/data/nabel.html",
|
|
124
|
+
swiss_legal_limits_lrv: SWISS_LIMITS,
|
|
125
|
+
limits_reference: "LRV (Luftreinhalteordnung / Swiss Clean Air Act, Annex 7)",
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
default:
|
|
129
|
+
throw new Error(`Unknown air quality tool: ${name}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swiss Avalanche Bulletin module
|
|
3
|
+
*
|
|
4
|
+
* Data source: SLF (WSL Institute for Snow and Avalanche Research)
|
|
5
|
+
* The SLF publishes the official Swiss avalanche bulletin daily at ~08:00 and 17:00.
|
|
6
|
+
*
|
|
7
|
+
* API situation: The SLF JSON API used by the White Risk app (whiterisk.ch) requires
|
|
8
|
+
* authentication. The public-facing data is available as:
|
|
9
|
+
* - PDF bulletins at aws.slf.ch (no auth, verified working)
|
|
10
|
+
* - Interactive maps at whiterisk.ch
|
|
11
|
+
* - EAWS CAAMLv6 feed (requires auth)
|
|
12
|
+
*
|
|
13
|
+
* This module provides:
|
|
14
|
+
* - get_avalanche_bulletin: current bulletin URLs + danger level summary links
|
|
15
|
+
* - list_avalanche_regions: all SLF warning regions with IDs and locations
|
|
16
|
+
*/
|
|
17
|
+
export interface AvalancheTool {
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: string;
|
|
22
|
+
properties: Record<string, unknown>;
|
|
23
|
+
required?: string[];
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export declare const SWISS_AVALANCHE_REGIONS: {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
canton: string;
|
|
30
|
+
elevation_m: number;
|
|
31
|
+
}[];
|
|
32
|
+
export declare const avalancheTools: AvalancheTool[];
|
|
33
|
+
export declare function handleAvalanche(name: string, args: Record<string, string>): Promise<string>;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Swiss Avalanche Bulletin module
|
|
4
|
+
*
|
|
5
|
+
* Data source: SLF (WSL Institute for Snow and Avalanche Research)
|
|
6
|
+
* The SLF publishes the official Swiss avalanche bulletin daily at ~08:00 and 17:00.
|
|
7
|
+
*
|
|
8
|
+
* API situation: The SLF JSON API used by the White Risk app (whiterisk.ch) requires
|
|
9
|
+
* authentication. The public-facing data is available as:
|
|
10
|
+
* - PDF bulletins at aws.slf.ch (no auth, verified working)
|
|
11
|
+
* - Interactive maps at whiterisk.ch
|
|
12
|
+
* - EAWS CAAMLv6 feed (requires auth)
|
|
13
|
+
*
|
|
14
|
+
* This module provides:
|
|
15
|
+
* - get_avalanche_bulletin: current bulletin URLs + danger level summary links
|
|
16
|
+
* - list_avalanche_regions: all SLF warning regions with IDs and locations
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.avalancheTools = exports.SWISS_AVALANCHE_REGIONS = void 0;
|
|
20
|
+
exports.handleAvalanche = handleAvalanche;
|
|
21
|
+
// โโ Region data (official SLF/EAWS regions for Switzerland) โโโโโโโโโโโโโโโโโโโ
|
|
22
|
+
exports.SWISS_AVALANCHE_REGIONS = [
|
|
23
|
+
{ id: "CH-1", name: "Jura", canton: "JU/NE/VD", elevation_m: 800 },
|
|
24
|
+
{ id: "CH-2", name: "Mittelland", canton: "BE/ZH/AG", elevation_m: 700 },
|
|
25
|
+
{ id: "CH-3", name: "Alps North of the Rhone", canton: "BE/VD/VS/FR", elevation_m: 1500 },
|
|
26
|
+
{ id: "CH-4", name: "Western Alps", canton: "VS", elevation_m: 2500 },
|
|
27
|
+
{ id: "CH-5", name: "Central Alps", canton: "VS/OW/NW", elevation_m: 2500 },
|
|
28
|
+
{ id: "CH-6", name: "Bernese Alps North", canton: "BE", elevation_m: 2000 },
|
|
29
|
+
{ id: "CH-7", name: "Bernese Alps South", canton: "BE/VS", elevation_m: 2500 },
|
|
30
|
+
{ id: "CH-8", name: "Glarner Alps", canton: "GL/SG/GR", elevation_m: 2000 },
|
|
31
|
+
{ id: "CH-9", name: "Central Graubรผnden", canton: "GR", elevation_m: 2500 },
|
|
32
|
+
{ id: "CH-10", name: "Prรคttigau & Davos", canton: "GR", elevation_m: 2000 },
|
|
33
|
+
{ id: "CH-11", name: "Silvretta", canton: "GR/GL", elevation_m: 2500 },
|
|
34
|
+
{ id: "CH-12", name: "Surselva", canton: "GR", elevation_m: 2000 },
|
|
35
|
+
{ id: "CH-13", name: "Engadine & Samnaun", canton: "GR", elevation_m: 2500 },
|
|
36
|
+
{ id: "CH-14", name: "Val Mรผstair", canton: "GR", elevation_m: 2000 },
|
|
37
|
+
{ id: "CH-15", name: "Bernina & Bregaglia", canton: "GR", elevation_m: 2500 },
|
|
38
|
+
{ id: "CH-16", name: "Valposchiavo", canton: "GR", elevation_m: 2000 },
|
|
39
|
+
{ id: "CH-17", name: "Mesolcina & Calanca", canton: "GR", elevation_m: 1800 },
|
|
40
|
+
{ id: "CH-18", name: "Ticino North", canton: "TI", elevation_m: 2000 },
|
|
41
|
+
{ id: "CH-19", name: "Ticino South", canton: "TI", elevation_m: 1500 },
|
|
42
|
+
{ id: "CH-20", name: "Alps North of the Rhine", canton: "GL/SG/AI/AR", elevation_m: 1800 },
|
|
43
|
+
{ id: "CH-21", name: "Uri Alps", canton: "UR/OW/NW", elevation_m: 2500 },
|
|
44
|
+
{ id: "CH-22", name: "Schwyzer Alps", canton: "SZ/ZG", elevation_m: 1800 },
|
|
45
|
+
];
|
|
46
|
+
const DANGER_LEVELS = {
|
|
47
|
+
1: "Low (1/5) โ No special precautions needed",
|
|
48
|
+
2: "Moderate (2/5) โ Careful route selection on steep slopes",
|
|
49
|
+
3: "Considerable (3/5) โ Careful assessment required; natural and human-triggered avalanches possible",
|
|
50
|
+
4: "High (4/5) โ Very careful assessment; spontaneous avalanches likely",
|
|
51
|
+
5: "Very High (5/5) โ Extraordinary situation; avoid all avalanche terrain",
|
|
52
|
+
};
|
|
53
|
+
const SUPPORTED_LANGUAGES = ["de", "en", "fr", "it"];
|
|
54
|
+
// โโ Helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
55
|
+
function todayISO() {
|
|
56
|
+
return new Date().toISOString().slice(0, 10);
|
|
57
|
+
}
|
|
58
|
+
function bulletinPdfUrl(lang, _period = "morning") {
|
|
59
|
+
// aws.slf.ch serves official PDF bulletins โ publicly accessible without auth
|
|
60
|
+
// Pattern: https://aws.slf.ch/api/bulletin/document/regional/<lang>/<period>
|
|
61
|
+
const validLang = SUPPORTED_LANGUAGES.includes(lang) ? lang : "en";
|
|
62
|
+
return `https://aws.slf.ch/api/bulletin/document/full/${validLang}`;
|
|
63
|
+
}
|
|
64
|
+
function whiteRiskUrl(lang) {
|
|
65
|
+
const validLang = SUPPORTED_LANGUAGES.includes(lang) ? lang : "en";
|
|
66
|
+
return `https://whiterisk.ch/${validLang}/conditions`;
|
|
67
|
+
}
|
|
68
|
+
// โโ Tool definitions โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
69
|
+
exports.avalancheTools = [
|
|
70
|
+
{
|
|
71
|
+
name: "get_avalanche_bulletin",
|
|
72
|
+
description: "Get the current Swiss avalanche danger bulletin from SLF (WSL Institute for Snow and Avalanche Research). " +
|
|
73
|
+
"Returns current bulletin URLs, danger level descriptions, and links to the interactive map. " +
|
|
74
|
+
"The bulletin is published daily at ~08:00 and updated at ~17:00 Swiss time (OctoberโMay).",
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: "object",
|
|
77
|
+
properties: {
|
|
78
|
+
region: {
|
|
79
|
+
type: "string",
|
|
80
|
+
description: "Optional region ID (e.g. CH-9 for Central Graubรผnden) or region name. " +
|
|
81
|
+
"Use list_avalanche_regions to see all options. If omitted, returns national overview.",
|
|
82
|
+
},
|
|
83
|
+
language: {
|
|
84
|
+
type: "string",
|
|
85
|
+
enum: ["de", "en", "fr", "it"],
|
|
86
|
+
description: "Language for bulletin links: de (German), en (English), fr (French), it (Italian). Default: en",
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "list_avalanche_regions",
|
|
93
|
+
description: "List all Swiss avalanche warning regions as defined by SLF/EAWS. " +
|
|
94
|
+
"Returns region IDs, names, cantons, and typical elevations. " +
|
|
95
|
+
"Use region IDs with get_avalanche_bulletin.",
|
|
96
|
+
inputSchema: {
|
|
97
|
+
type: "object",
|
|
98
|
+
properties: {
|
|
99
|
+
canton: {
|
|
100
|
+
type: "string",
|
|
101
|
+
description: "Filter regions by canton abbreviation (e.g. GR, VS, BE). Optional.",
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
// โโ Handlers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
108
|
+
async function handleGetAvalancheBulletin(args) {
|
|
109
|
+
const lang = SUPPORTED_LANGUAGES.includes(args.language) ? args.language : "en";
|
|
110
|
+
const today = todayISO();
|
|
111
|
+
// Find matching region if specified
|
|
112
|
+
let matchedRegion;
|
|
113
|
+
if (args.region) {
|
|
114
|
+
const query = args.region.trim().toLowerCase();
|
|
115
|
+
matchedRegion = exports.SWISS_AVALANCHE_REGIONS.find((r) => r.id.toLowerCase() === query ||
|
|
116
|
+
r.name.toLowerCase().includes(query) ||
|
|
117
|
+
r.canton.toLowerCase().includes(query));
|
|
118
|
+
}
|
|
119
|
+
const pdfUrl = bulletinPdfUrl(lang);
|
|
120
|
+
const mapUrl = whiteRiskUrl(lang);
|
|
121
|
+
const result = {
|
|
122
|
+
date: today,
|
|
123
|
+
source: "SLF โ WSL Institute for Snow and Avalanche Research",
|
|
124
|
+
bulletin_url: {
|
|
125
|
+
interactive_map: mapUrl,
|
|
126
|
+
pdf_full: pdfUrl,
|
|
127
|
+
pdf_regions: {
|
|
128
|
+
de: "https://aws.slf.ch/api/bulletin/document/full/de",
|
|
129
|
+
en: "https://aws.slf.ch/api/bulletin/document/full/en",
|
|
130
|
+
fr: "https://aws.slf.ch/api/bulletin/document/full/fr",
|
|
131
|
+
it: "https://aws.slf.ch/api/bulletin/document/full/it",
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
danger_scale: DANGER_LEVELS,
|
|
135
|
+
schedule: {
|
|
136
|
+
morning_bulletin: "~08:00 CET/CEST",
|
|
137
|
+
afternoon_update: "~17:00 CET/CEST",
|
|
138
|
+
season: "October to May (daily). Summer bulletins are occasional.",
|
|
139
|
+
},
|
|
140
|
+
note: "The SLF JSON API requires authentication (used by the White Risk app). " +
|
|
141
|
+
"Current danger levels are available via the interactive map at whiterisk.ch " +
|
|
142
|
+
"or as PDF bulletins at the URLs above. " +
|
|
143
|
+
"For programmatic access, contact SLF at lawinfo@slf.ch.",
|
|
144
|
+
};
|
|
145
|
+
if (matchedRegion) {
|
|
146
|
+
result.region = {
|
|
147
|
+
id: matchedRegion.id,
|
|
148
|
+
name: matchedRegion.name,
|
|
149
|
+
canton: matchedRegion.canton,
|
|
150
|
+
typical_elevation_m: matchedRegion.elevation_m,
|
|
151
|
+
bulletin_link: `${mapUrl}#region=${matchedRegion.id}`,
|
|
152
|
+
};
|
|
153
|
+
result.tip = `Check ${matchedRegion.name} (${matchedRegion.id}) on the interactive map: ${mapUrl}`;
|
|
154
|
+
}
|
|
155
|
+
else if (args.region) {
|
|
156
|
+
result.region_not_found = args.region;
|
|
157
|
+
result.tip = `Use list_avalanche_regions to see valid region IDs. Or visit ${mapUrl} for the full map.`;
|
|
158
|
+
}
|
|
159
|
+
return JSON.stringify(result);
|
|
160
|
+
}
|
|
161
|
+
async function handleListAvalancheRegions(args) {
|
|
162
|
+
let regions = exports.SWISS_AVALANCHE_REGIONS;
|
|
163
|
+
if (args.canton) {
|
|
164
|
+
const cantonQuery = args.canton.trim().toUpperCase();
|
|
165
|
+
regions = exports.SWISS_AVALANCHE_REGIONS.filter((r) => r.canton.toUpperCase().includes(cantonQuery));
|
|
166
|
+
}
|
|
167
|
+
const result = {
|
|
168
|
+
count: regions.length,
|
|
169
|
+
source: "SLF/EAWS Swiss Avalanche Warning Regions",
|
|
170
|
+
regions: regions.map((r) => ({
|
|
171
|
+
id: r.id,
|
|
172
|
+
name: r.name,
|
|
173
|
+
canton: r.canton,
|
|
174
|
+
typical_elevation_m: r.elevation_m,
|
|
175
|
+
})),
|
|
176
|
+
usage: "Pass region ID (e.g. 'CH-9') to get_avalanche_bulletin for region-specific bulletin link",
|
|
177
|
+
bulletin_map: "https://whiterisk.ch/en/conditions",
|
|
178
|
+
};
|
|
179
|
+
return JSON.stringify(result);
|
|
180
|
+
}
|
|
181
|
+
// โโ Main dispatcher โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
182
|
+
async function handleAvalanche(name, args) {
|
|
183
|
+
switch (name) {
|
|
184
|
+
case "get_avalanche_bulletin":
|
|
185
|
+
return handleGetAvalancheBulletin(args);
|
|
186
|
+
case "list_avalanche_regions":
|
|
187
|
+
return handleListAvalancheRegions(args);
|
|
188
|
+
default:
|
|
189
|
+
throw new Error(`Unknown avalanche tool: ${name}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export declare const holidaysTools: ({
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: string;
|
|
6
|
+
required: string[];
|
|
7
|
+
properties: {
|
|
8
|
+
year: {
|
|
9
|
+
type: string;
|
|
10
|
+
description: string;
|
|
11
|
+
};
|
|
12
|
+
canton: {
|
|
13
|
+
type: string;
|
|
14
|
+
description: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
} | {
|
|
19
|
+
name: string;
|
|
20
|
+
description: string;
|
|
21
|
+
inputSchema: {
|
|
22
|
+
type: string;
|
|
23
|
+
properties: {
|
|
24
|
+
canton: {
|
|
25
|
+
type: string;
|
|
26
|
+
description: string;
|
|
27
|
+
};
|
|
28
|
+
year?: undefined;
|
|
29
|
+
};
|
|
30
|
+
required?: undefined;
|
|
31
|
+
};
|
|
32
|
+
})[];
|
|
33
|
+
export declare function handleHolidays(name: string, args: Record<string, unknown>): Promise<string>;
|