snowsure-mcp-server 3.0.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.
package/README.md ADDED
@@ -0,0 +1,165 @@
1
+ # SnowSure MCP Server — ski resort snow for Cursor, Claude & AI agents
2
+
3
+ **`npx snowsure-mcp-server`** — [Model Context Protocol](https://modelcontextprotocol.io/) server for **real-time powder rankings**, **14-day multi-model snow forecasts**, and **500+ ski resorts** via [SnowSure](https://www.snowsure.ai). Use it when building agents that answer: *where is the best snow*, *fresh powder in Japan/the Alps*, *compare two resorts*, or *trip planning by conditions*.
4
+
5
+ - Docs: [snowsure.ai/developers](https://www.snowsure.ai/developers) · [llms.txt](https://www.snowsure.ai/llms.txt) · [OpenAPI](https://www.snowsure.ai/openapi.json)
6
+
7
+ ## Features
8
+
9
+ - **11 AI Tools** for querying snow conditions, forecasts, and resort information
10
+ - **4 Resources** for direct data access
11
+ - **500+ ski resorts** worldwide
12
+ - **7 weather models** for forecast comparison
13
+ - **Frequent updates** (weather pipeline ~15 min; see [methodology](https://www.snowsure.ai/methodology))
14
+
15
+ ## Available Tools
16
+
17
+ | Tool | Description |
18
+ |------|-------------|
19
+ | `get_snow_report` | Global snow rankings by score, forecast, or recent snowfall |
20
+ | `get_resort` | Comprehensive resort details including AI analysis |
21
+ | `search_resorts` | Find resorts by name, country, or region |
22
+ | `find_best_powder` | Resorts with freshest powder (24h snowfall) |
23
+ | `compare_forecasts` | Multi-model forecast comparison with confidence |
24
+ | `get_weather_forecast` | Day-by-day weather for up to 14 days |
25
+ | `find_resorts_by_criteria` | Advanced filtering (depth, elevation, runs, score) |
26
+ | `get_snow_history` | Historical data and season comparisons |
27
+ | `plan_ski_trip` | AI-powered trip recommendations |
28
+ | `get_webcam_status` | Live webcam links for a resort |
29
+ | `get_regional_summary` | Regional statistics and top resorts |
30
+
31
+ ## Available Resources
32
+
33
+ | URI | Description |
34
+ |-----|-------------|
35
+ | `snowsure://snow-report` | Current global snow report (top 50) |
36
+ | `snowsure://resorts` | All 500+ resorts with conditions |
37
+ | `snowsure://regions` | Region/country breakdown |
38
+ | `snowsure://api-docs` | API documentation |
39
+
40
+ ## Installation
41
+
42
+ ### For Claude Desktop
43
+
44
+ Add to your `claude_desktop_config.json`:
45
+
46
+ ```json
47
+ {
48
+ "mcpServers": {
49
+ "snowsure": {
50
+ "command": "npx",
51
+ "args": ["-y", "snowsure-mcp-server"]
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ### For Cursor
58
+
59
+ Add to your MCP settings:
60
+
61
+ ```json
62
+ {
63
+ "snowsure": {
64
+ "command": "npx",
65
+ "args": ["-y", "snowsure-mcp-server"]
66
+ }
67
+ }
68
+ ```
69
+
70
+ ### Manual Installation
71
+
72
+ ```bash
73
+ # Clone and build
74
+ git clone https://github.com/mikeslone/snowsure-web.git
75
+ cd snowsure-web/mcp-server
76
+ npm install
77
+ npm run build
78
+
79
+ # Run
80
+ node dist/index.js
81
+ ```
82
+
83
+ ## Example Queries
84
+
85
+ Once connected, you can ask your AI assistant:
86
+
87
+ - "What are the best ski conditions right now?"
88
+ - "Tell me about Niseko's snow conditions"
89
+ - "Find me powder in Japan"
90
+ - "Compare forecasts for Matterhorn"
91
+ - "Plan a ski trip to the Alps in February"
92
+ - "Which resorts have the deepest snow base?"
93
+ - "What's the weather forecast for Jackson Hole this week?"
94
+
95
+ ## Tool Usage Examples
96
+
97
+ ### Get Snow Report
98
+ ```
99
+ Query: "Show me the top 5 resorts in Europe right now"
100
+ Tool: get_snow_report
101
+ Args: { "region": "europe", "limit": 5, "sort": "snowsure" }
102
+ ```
103
+
104
+ ### Find Best Powder
105
+ ```
106
+ Query: "Where is it dumping in North America?"
107
+ Tool: find_best_powder
108
+ Args: { "region": "north-america", "minSnowfall": 15 }
109
+ ```
110
+
111
+ ### Compare Forecasts
112
+ ```
113
+ Query: "How reliable is the Whistler forecast?"
114
+ Tool: compare_forecasts
115
+ Args: { "slug": "whistler-blackcomb" }
116
+ ```
117
+
118
+ ### Plan Trip
119
+ ```
120
+ Query: "Recommend a ski trip for an advanced skier in Japan"
121
+ Tool: plan_ski_trip
122
+ Args: { "region": "asia", "level": "advanced" }
123
+ ```
124
+
125
+ ## API Reference
126
+
127
+ The MCP server connects to the SnowSure API at `https://lux.ski/api/v1`.
128
+
129
+ ### Endpoints Used
130
+ - `GET /resorts` - List all resorts
131
+ - `GET /resorts/{slug}` - Get resort details
132
+ - `GET /snow-report` - Get ranked snow report
133
+
134
+ ## Development
135
+
136
+ ```bash
137
+ # Install dependencies
138
+ npm install
139
+
140
+ # Run in development mode
141
+ npm run dev
142
+
143
+ # Build for production
144
+ npm run build
145
+
146
+ # Watch mode
147
+ npm run watch
148
+ ```
149
+
150
+ ## Data Sources
151
+
152
+ - **Weather Models:** ECMWF, GFS, GEM, JMA, ICON, Météo-France, Met Norway
153
+ - **Conditions:** Resort weather pipeline ~15 min cadence (production)
154
+ - **Historical:** 30 years of snowfall records
155
+ - **SnowSure Score:** AI-powered rating combining depth, recent snow, forecast, and historical reliability
156
+
157
+ ## License
158
+
159
+ MIT License - see [LICENSE](LICENSE) for details.
160
+
161
+ ## Support
162
+
163
+ - Website: [snowsure.ai](https://www.snowsure.ai)
164
+ - API Docs: [snowsure.ai/developers](https://www.snowsure.ai/developers)
165
+ - Email: support@snowsure.ai
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SnowSure MCP Server
4
+ *
5
+ * Provides AI agents with comprehensive access to real-time snow conditions,
6
+ * forecasts, historical data, and resort information from SnowSure.ai
7
+ *
8
+ * Tools:
9
+ * - get_snow_report: Global snow rankings
10
+ * - get_resort: Detailed resort info
11
+ * - search_resorts: Find resorts by name/country
12
+ * - find_best_powder: Fresh snow rankings
13
+ * - compare_forecasts: Multi-model comparison
14
+ * - get_weather_forecast: Daily forecast data
15
+ * - find_resorts_by_criteria: Advanced filtering
16
+ * - get_snow_history: Historical snowfall data
17
+ * - plan_ski_trip: Trip recommendations
18
+ * - get_webcam_status: Live webcam info
19
+ *
20
+ * Resources:
21
+ * - snowsure://snow-report: Live snow report
22
+ * - snowsure://resorts: All resorts list
23
+ * - snowsure://regions: Available regions
24
+ */
25
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,891 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SnowSure MCP Server
4
+ *
5
+ * Provides AI agents with comprehensive access to real-time snow conditions,
6
+ * forecasts, historical data, and resort information from SnowSure.ai
7
+ *
8
+ * Tools:
9
+ * - get_snow_report: Global snow rankings
10
+ * - get_resort: Detailed resort info
11
+ * - search_resorts: Find resorts by name/country
12
+ * - find_best_powder: Fresh snow rankings
13
+ * - compare_forecasts: Multi-model comparison
14
+ * - get_weather_forecast: Daily forecast data
15
+ * - find_resorts_by_criteria: Advanced filtering
16
+ * - get_snow_history: Historical snowfall data
17
+ * - plan_ski_trip: Trip recommendations
18
+ * - get_webcam_status: Live webcam info
19
+ *
20
+ * Resources:
21
+ * - snowsure://snow-report: Live snow report
22
+ * - snowsure://resorts: All resorts list
23
+ * - snowsure://regions: Available regions
24
+ */
25
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
26
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
27
+ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
28
+ const API_BASE = 'https://www.snowsure.ai/api/v1';
29
+ // Helper to fetch from API with error handling
30
+ async function fetchAPI(endpoint) {
31
+ try {
32
+ const res = await fetch(`${API_BASE}${endpoint}`, {
33
+ headers: {
34
+ 'User-Agent': 'SnowSure-MCP/3.0.1',
35
+ },
36
+ });
37
+ if (!res.ok) {
38
+ throw new Error(`API error: ${res.status} ${res.statusText}`);
39
+ }
40
+ return res.json();
41
+ }
42
+ catch (error) {
43
+ throw new Error(`Failed to fetch ${endpoint}: ${error instanceof Error ? error.message : 'Unknown error'}`);
44
+ }
45
+ }
46
+ // Format date helper
47
+ function formatDate(dateStr) {
48
+ if (!dateStr)
49
+ return 'Unknown';
50
+ const date = new Date(dateStr);
51
+ return date.toLocaleDateString('en-US', {
52
+ weekday: 'short',
53
+ month: 'short',
54
+ day: 'numeric'
55
+ });
56
+ }
57
+ // Create the MCP server
58
+ const server = new Server({
59
+ name: 'snowsure-mcp',
60
+ version: '3.0.1',
61
+ }, {
62
+ capabilities: {
63
+ resources: {},
64
+ tools: {},
65
+ },
66
+ });
67
+ // List available tools - comprehensive set for AI agents
68
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
69
+ tools: [
70
+ // === CORE TOOLS ===
71
+ {
72
+ name: 'get_snow_report',
73
+ description: 'Get the global snow report with top-ranked resorts by snow conditions. Returns resorts sorted by SnowSure score, forecast, or recent snowfall. Use this for "where has the best snow?" or "top ski resorts right now" queries.',
74
+ inputSchema: {
75
+ type: 'object',
76
+ properties: {
77
+ sort: {
78
+ type: 'string',
79
+ enum: ['snowsure', 'forecast', 'recent', 'depth'],
80
+ description: 'Sort order: snowsure (AI rating), forecast (14-day snow), recent (24h snowfall), depth (current base)',
81
+ },
82
+ limit: {
83
+ type: 'number',
84
+ description: 'Number of resorts to return (default: 10, max: 50)',
85
+ },
86
+ region: {
87
+ type: 'string',
88
+ enum: ['europe', 'north-america', 'asia', 'oceania', 'south-america'],
89
+ description: 'Filter by region',
90
+ },
91
+ },
92
+ },
93
+ },
94
+ {
95
+ name: 'get_resort',
96
+ description: 'Get comprehensive information about a specific ski resort including current conditions, multi-model forecasts, historical data, webcams, and AI analysis. Use for "tell me about [resort]" or "[resort] conditions" queries.',
97
+ inputSchema: {
98
+ type: 'object',
99
+ properties: {
100
+ slug: {
101
+ type: 'string',
102
+ description: 'Resort slug identifier (e.g., "niseko-hanazono-resort", "matterhorn-ski-paradise", "jackson-hole-mountain-resort")',
103
+ },
104
+ },
105
+ required: ['slug'],
106
+ },
107
+ },
108
+ {
109
+ name: 'search_resorts',
110
+ description: 'Search for ski resorts by name, country, or region. Returns matching resorts with basic conditions. Use for "find resorts in [location]" or "search [name]" queries.',
111
+ inputSchema: {
112
+ type: 'object',
113
+ properties: {
114
+ query: {
115
+ type: 'string',
116
+ description: 'Search query - can be resort name, country, or partial match',
117
+ },
118
+ country: {
119
+ type: 'string',
120
+ description: 'Filter by exact country name (e.g., "Japan", "Switzerland", "United States")',
121
+ },
122
+ limit: {
123
+ type: 'number',
124
+ description: 'Maximum results to return (default: 20)',
125
+ },
126
+ },
127
+ },
128
+ },
129
+ {
130
+ name: 'find_best_powder',
131
+ description: 'Find resorts with the freshest powder snow right now. Returns resorts sorted by 24-hour snowfall. Use for "where is it snowing?" or "fresh powder" queries.',
132
+ inputSchema: {
133
+ type: 'object',
134
+ properties: {
135
+ minSnowfall: {
136
+ type: 'number',
137
+ description: 'Minimum 24h snowfall in cm to include (default: 5)',
138
+ },
139
+ region: {
140
+ type: 'string',
141
+ enum: ['europe', 'north-america', 'asia', 'oceania'],
142
+ description: 'Filter by region',
143
+ },
144
+ limit: {
145
+ type: 'number',
146
+ description: 'Number of results (default: 10)',
147
+ },
148
+ },
149
+ },
150
+ },
151
+ {
152
+ name: 'compare_forecasts',
153
+ description: 'Compare 14-day snow forecasts across 7 weather models (ECMWF, GFS, GEM, JMA, ICON, Météo-France, Met Norway) for a resort. Shows model agreement and uncertainty. Use for forecast reliability queries.',
154
+ inputSchema: {
155
+ type: 'object',
156
+ properties: {
157
+ slug: {
158
+ type: 'string',
159
+ description: 'Resort slug to compare forecasts for',
160
+ },
161
+ },
162
+ required: ['slug'],
163
+ },
164
+ },
165
+ // === ADVANCED TOOLS ===
166
+ {
167
+ name: 'get_weather_forecast',
168
+ description: 'Get detailed day-by-day weather forecast for a resort including temperature, snowfall, wind, and conditions for each of the next 14 days.',
169
+ inputSchema: {
170
+ type: 'object',
171
+ properties: {
172
+ slug: {
173
+ type: 'string',
174
+ description: 'Resort slug',
175
+ },
176
+ days: {
177
+ type: 'number',
178
+ description: 'Number of days to forecast (default: 7, max: 14)',
179
+ },
180
+ },
181
+ required: ['slug'],
182
+ },
183
+ },
184
+ {
185
+ name: 'find_resorts_by_criteria',
186
+ description: 'Find resorts matching specific criteria like minimum snow depth, elevation range, number of runs, or SnowSure rating. Advanced filtering for trip planning.',
187
+ inputSchema: {
188
+ type: 'object',
189
+ properties: {
190
+ minScore: {
191
+ type: 'number',
192
+ description: 'Minimum SnowSure score (0-100)',
193
+ },
194
+ minDepth: {
195
+ type: 'number',
196
+ description: 'Minimum snow depth in cm',
197
+ },
198
+ minElevation: {
199
+ type: 'number',
200
+ description: 'Minimum summit elevation in meters',
201
+ },
202
+ minRuns: {
203
+ type: 'number',
204
+ description: 'Minimum number of ski runs',
205
+ },
206
+ country: {
207
+ type: 'string',
208
+ description: 'Filter by country',
209
+ },
210
+ region: {
211
+ type: 'string',
212
+ enum: ['europe', 'north-america', 'asia'],
213
+ description: 'Filter by region',
214
+ },
215
+ limit: {
216
+ type: 'number',
217
+ description: 'Max results (default: 20)',
218
+ },
219
+ },
220
+ },
221
+ },
222
+ {
223
+ name: 'get_snow_history',
224
+ description: 'Get historical snowfall data for a resort including season totals, comparison to 5-year and 30-year averages, and best months to visit.',
225
+ inputSchema: {
226
+ type: 'object',
227
+ properties: {
228
+ slug: {
229
+ type: 'string',
230
+ description: 'Resort slug',
231
+ },
232
+ },
233
+ required: ['slug'],
234
+ },
235
+ },
236
+ {
237
+ name: 'plan_ski_trip',
238
+ description: 'Get AI-powered ski trip recommendations based on dates, preferences, and conditions. Suggests best resorts for a given time period.',
239
+ inputSchema: {
240
+ type: 'object',
241
+ properties: {
242
+ region: {
243
+ type: 'string',
244
+ enum: ['europe', 'north-america', 'asia', 'any'],
245
+ description: 'Preferred region (default: any)',
246
+ },
247
+ dates: {
248
+ type: 'string',
249
+ description: 'Travel dates or month (e.g., "February", "next week", "Jan 15-22")',
250
+ },
251
+ level: {
252
+ type: 'string',
253
+ enum: ['beginner', 'intermediate', 'advanced', 'expert'],
254
+ description: 'Skier ability level',
255
+ },
256
+ priorities: {
257
+ type: 'array',
258
+ items: {
259
+ type: 'string',
260
+ enum: ['powder', 'groomed', 'terrain-variety', 'short-lift-lines', 'apres-ski', 'family-friendly', 'budget'],
261
+ },
262
+ description: 'Trip priorities',
263
+ },
264
+ },
265
+ },
266
+ },
267
+ {
268
+ name: 'get_webcam_status',
269
+ description: 'Get live webcam information and links for a resort to see current conditions visually.',
270
+ inputSchema: {
271
+ type: 'object',
272
+ properties: {
273
+ slug: {
274
+ type: 'string',
275
+ description: 'Resort slug',
276
+ },
277
+ },
278
+ required: ['slug'],
279
+ },
280
+ },
281
+ {
282
+ name: 'get_regional_summary',
283
+ description: 'Get a summary of snow conditions across an entire region or country with statistics and top resorts.',
284
+ inputSchema: {
285
+ type: 'object',
286
+ properties: {
287
+ region: {
288
+ type: 'string',
289
+ enum: ['europe', 'north-america', 'asia', 'alps', 'rockies', 'japan'],
290
+ description: 'Region to summarize',
291
+ },
292
+ country: {
293
+ type: 'string',
294
+ description: 'Specific country (alternative to region)',
295
+ },
296
+ },
297
+ },
298
+ },
299
+ ],
300
+ }));
301
+ // Handle tool calls
302
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
303
+ const { name, arguments: args } = request.params;
304
+ try {
305
+ switch (name) {
306
+ // === CORE TOOLS ===
307
+ case 'get_snow_report': {
308
+ const params = new URLSearchParams();
309
+ if (args?.sort)
310
+ params.set('sort', args.sort);
311
+ if (args?.limit)
312
+ params.set('limit', String(args.limit));
313
+ if (args?.region)
314
+ params.set('region', args.region);
315
+ const data = await fetchAPI(`/snow-report?${params}`);
316
+ const resorts = data.data?.resorts || data.data || [];
317
+ const summary = `# 🏔️ Global Snow Report\n\n` +
318
+ `**Updated:** ${new Date().toLocaleString()}\n\n` +
319
+ `## Top Resorts\n\n` +
320
+ resorts.slice(0, args?.limit || 10).map((r, i) => `${i + 1}. **${r.name}** (${r.country})\n` +
321
+ ` - SnowSure Score: ${r.snowSure?.score || 'N/A'}/100 (${r.snowSure?.rating || 'N/A'})\n` +
322
+ ` - Snow Depth: ${r.conditions?.snowDepthCm || 'N/A'}cm\n` +
323
+ ` - 24h Snowfall: ${r.conditions?.snowfall24hCm || 0}cm\n` +
324
+ ` - 14-Day Forecast: ${Math.round(r.conditions?.forecast14dCm || 0)}cm\n`).join('\n');
325
+ return {
326
+ content: [{ type: 'text', text: summary }],
327
+ };
328
+ }
329
+ case 'get_resort': {
330
+ const data = await fetchAPI(`/resorts/${args?.slug}`);
331
+ const resort = data.data;
332
+ if (!resort) {
333
+ return {
334
+ content: [{ type: 'text', text: `Resort "${args?.slug}" not found. Try searching for resorts first.` }],
335
+ };
336
+ }
337
+ const summary = `# ${resort.name}\n\n` +
338
+ `**Location:** ${resort.country}${resort.region ? ` (${resort.region})` : ''}\n` +
339
+ `**Website:** ${resort.links?.website || 'N/A'}\n\n` +
340
+ `## SnowSure™ Score: ${resort.snowSure?.score || 'N/A'}/100\n` +
341
+ `**Rating:** ${resort.snowSure?.rating || 'N/A'}\n` +
342
+ `**Trend:** ${resort.snowSure?.trend || 'stable'}\n\n` +
343
+ `${resort.snowSure?.analysis || resort.snowSure?.tagline || ''}\n\n` +
344
+ `### Score Breakdown\n` +
345
+ `- Depth: ${resort.snowSure?.breakdown?.depth || 'N/A'}/25\n` +
346
+ `- Recent Snow: ${resort.snowSure?.breakdown?.recent || 'N/A'}/25\n` +
347
+ `- Forecast: ${resort.snowSure?.breakdown?.forecast || 'N/A'}/25\n` +
348
+ `- Historic: ${resort.snowSure?.breakdown?.historic || 'N/A'}/25\n\n` +
349
+ `## Current Conditions\n` +
350
+ `- **Temperature:** ${resort.currentConditions?.temperature?.celsius || 'N/A'}°C\n` +
351
+ `- **Conditions:** ${resort.currentConditions?.conditions || 'N/A'}\n` +
352
+ `- **Wind:** ${resort.currentConditions?.wind?.speed || 'N/A'} km/h\n` +
353
+ `- **Snow Depth:** ${resort.snow?.depth?.cm || 'N/A'}cm\n` +
354
+ `- **Last 24h:** ${resort.snow?.last24h?.cm || 0}cm\n` +
355
+ `- **Last 7 Days:** ${resort.snow?.last7d?.cm || 0}cm\n` +
356
+ `- **Season Total:** ${resort.snow?.seasonTotal?.cm || 'N/A'}cm\n` +
357
+ `- **Last Updated:** ${formatDate(resort.currentConditions?.lastUpdated)}\n\n` +
358
+ `## 14-Day Forecast (Multi-Model)\n` +
359
+ `- **SnowSure Average:** ${resort.forecast?.total14d?.average || 'N/A'}cm\n` +
360
+ `- **ECMWF:** ${resort.forecast?.total14d?.ecmwf || 'N/A'}cm\n` +
361
+ `- **GFS:** ${resort.forecast?.total14d?.gfs || 'N/A'}cm\n` +
362
+ `- **GEM:** ${resort.forecast?.total14d?.gem || 'N/A'}cm\n` +
363
+ `- **JMA:** ${resort.forecast?.total14d?.jma || 'N/A'}cm\n` +
364
+ `- **ICON:** ${resort.forecast?.total14d?.icon || 'N/A'}cm\n\n` +
365
+ `## Resort Info\n` +
366
+ `- **Summit:** ${resort.elevation?.summit || 'N/A'}m\n` +
367
+ `- **Base:** ${resort.elevation?.base || 'N/A'}m\n` +
368
+ `- **Vertical Drop:** ${resort.elevation?.vertical || 'N/A'}m\n` +
369
+ `- **Runs:** ${resort.stats?.runs || 'N/A'}\n` +
370
+ `- **Lifts:** ${resort.stats?.lifts || 'N/A'}\n` +
371
+ `- **Skiable Area:** ${resort.stats?.skiableAcres || 'N/A'} acres\n` +
372
+ `- **Annual Snowfall:** ${resort.stats?.annualSnowfall || 'N/A'}\n\n` +
373
+ `${resort.description ? `## About\n${resort.description}\n\n` : ''}` +
374
+ `${resort.topApresSki ? `## Top Après-Ski\n${resort.topApresSki}\n\n` : ''}` +
375
+ `---\n` +
376
+ `*View more at: https://snowsure.ai/resorts/${resort.slug}*`;
377
+ return {
378
+ content: [{ type: 'text', text: summary }],
379
+ };
380
+ }
381
+ case 'search_resorts': {
382
+ const data = await fetchAPI('/resorts?limit=200');
383
+ let results = data.data || [];
384
+ if (args?.query) {
385
+ const query = args.query.toLowerCase();
386
+ results = results.filter((r) => r.name?.toLowerCase().includes(query) ||
387
+ r.country?.toLowerCase().includes(query) ||
388
+ r.region?.toLowerCase().includes(query) ||
389
+ r.slug?.toLowerCase().includes(query));
390
+ }
391
+ if (args?.country) {
392
+ const country = args.country.toLowerCase();
393
+ results = results.filter((r) => r.country?.toLowerCase() === country);
394
+ }
395
+ const limit = args?.limit || 20;
396
+ const formatted = results.slice(0, limit).map((r) => `- **${r.name}** (${r.country})\n` +
397
+ ` Slug: \`${r.slug}\` | Score: ${r.snowSure?.score || 'N/A'} | Depth: ${r.conditions?.snowDepthCm || 'N/A'}cm`).join('\n');
398
+ return {
399
+ content: [
400
+ {
401
+ type: 'text',
402
+ text: `# Search Results\n\nFound **${results.length}** resorts${args?.query ? ` matching "${args.query}"` : ''}${args?.country ? ` in ${args.country}` : ''}:\n\n${formatted}\n\n*Use the slug value with get_resort for detailed info.*`,
403
+ },
404
+ ],
405
+ };
406
+ }
407
+ case 'find_best_powder': {
408
+ const data = await fetchAPI('/snow-report?sort=recent&limit=50');
409
+ let results = data.data?.resorts || data.data || [];
410
+ const minSnow = args?.minSnowfall || 0;
411
+ results = results.filter((r) => (r.conditions?.snowfall24hCm || 0) >= minSnow);
412
+ if (args?.region) {
413
+ const regionMap = {
414
+ 'europe': ['Switzerland', 'France', 'Austria', 'Italy', 'Germany', 'Norway', 'Sweden'],
415
+ 'north-america': ['United States', 'Canada'],
416
+ 'asia': ['Japan'],
417
+ 'oceania': ['New Zealand', 'Australia'],
418
+ };
419
+ const countries = regionMap[args.region] || [];
420
+ if (countries.length > 0) {
421
+ results = results.filter((r) => countries.includes(r.country));
422
+ }
423
+ }
424
+ const limit = args?.limit || 10;
425
+ const formatted = results.slice(0, limit).map((r, i) => `${i + 1}. 🏔️ **${r.name}** (${r.country})\n` +
426
+ ` ❄️ **+${r.conditions?.snowfall24hCm || 0}cm** in last 24 hours\n` +
427
+ ` 📏 Total Depth: ${r.conditions?.snowDepthCm || 'N/A'}cm\n` +
428
+ ` 🎯 SnowSure Score: ${r.snowSure?.score || 'N/A'}/100`).join('\n\n');
429
+ return {
430
+ content: [
431
+ {
432
+ type: 'text',
433
+ text: `# 🎿 Fresh Powder Report\n\n${formatted || 'No resorts with fresh powder found matching your criteria.'}\n\n*Data updated: ${new Date().toLocaleString()}*`,
434
+ },
435
+ ],
436
+ };
437
+ }
438
+ case 'compare_forecasts': {
439
+ const data = await fetchAPI(`/resorts/${args?.slug}`);
440
+ const resort = data.data;
441
+ if (!resort?.forecast?.total14d) {
442
+ return {
443
+ content: [{ type: 'text', text: 'Forecast data not available for this resort.' }],
444
+ };
445
+ }
446
+ const f = resort.forecast.total14d;
447
+ const models = [
448
+ { name: 'ECMWF', value: f.ecmwf, desc: 'European model (most accurate)' },
449
+ { name: 'GFS', value: f.gfs, desc: 'US Global Forecast System' },
450
+ { name: 'GEM', value: f.gem, desc: 'Canadian model' },
451
+ { name: 'JMA', value: f.jma, desc: 'Japanese model (best for Asia)' },
452
+ { name: 'ICON', value: f.icon, desc: 'German model' },
453
+ { name: 'Météo-Fr', value: f.meteoFrance, desc: 'French model' },
454
+ { name: 'Met Norway', value: f.metNorway, desc: 'Norwegian model' },
455
+ ].filter(m => m.value !== undefined && m.value !== null);
456
+ const avg = f.average || 0;
457
+ const max = Math.max(...models.map(m => m.value || 0), 1);
458
+ const min = Math.min(...models.map(m => m.value || 0));
459
+ const spread = max - min;
460
+ const chart = models.map(m => {
461
+ const pct = max > 0 ? Math.round((m.value || 0) / max * 20) : 0;
462
+ const bar = '█'.repeat(pct) + '░'.repeat(20 - pct);
463
+ const diff = (m.value || 0) - avg;
464
+ const diffStr = diff >= 0 ? `+${diff.toFixed(0)}` : diff.toFixed(0);
465
+ return `${m.name.padEnd(10)} ${bar} ${(m.value || 0).toString().padStart(3)}cm (${diffStr})`;
466
+ }).join('\n');
467
+ const confidence = spread < 10 ? 'HIGH' : spread < 25 ? 'MEDIUM' : 'LOW';
468
+ const confidenceEmoji = confidence === 'HIGH' ? '🟢' : confidence === 'MEDIUM' ? '🟡' : '🔴';
469
+ return {
470
+ content: [
471
+ {
472
+ type: 'text',
473
+ text: `# 14-Day Forecast Comparison\n## ${resort.name}\n\n` +
474
+ `**SnowSure™ Average:** ${avg}cm\n` +
475
+ `**Model Spread:** ${spread.toFixed(0)}cm (${min}-${max}cm)\n` +
476
+ `**Confidence:** ${confidenceEmoji} ${confidence}\n\n` +
477
+ `\`\`\`\n${chart}\n\`\`\`\n\n` +
478
+ `### Model Notes\n` +
479
+ `- **ECMWF** is typically most accurate globally\n` +
480
+ `- **JMA** excels for Japan/Asia forecasts\n` +
481
+ `- **GEM** is strongest for North America\n` +
482
+ `- SnowSure uses weighted averages based on historical accuracy\n\n` +
483
+ `*${confidence === 'HIGH' ? 'Models agree closely - forecast is reliable' : confidence === 'MEDIUM' ? 'Some model disagreement - check closer to date' : 'High uncertainty - conditions may change significantly'}*`,
484
+ },
485
+ ],
486
+ };
487
+ }
488
+ // === ADVANCED TOOLS ===
489
+ case 'get_weather_forecast': {
490
+ const data = await fetchAPI(`/resorts/${args?.slug}`);
491
+ const resort = data.data;
492
+ if (!resort?.forecast?.daily) {
493
+ return {
494
+ content: [{ type: 'text', text: 'Daily forecast not available for this resort.' }],
495
+ };
496
+ }
497
+ const days = Math.min(args?.days || 7, 14);
498
+ const forecast = resort.forecast.daily.slice(0, days);
499
+ const formatted = forecast.map((day, i) => {
500
+ const date = new Date();
501
+ date.setDate(date.getDate() + i);
502
+ const dayName = i === 0 ? 'Today' : i === 1 ? 'Tomorrow' : date.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
503
+ return `### ${dayName}\n` +
504
+ `- High: ${day.tempMax || day.temp || 'N/A'}°C | Low: ${day.tempMin || 'N/A'}°C\n` +
505
+ `- Conditions: ${day.conditions || 'N/A'}\n` +
506
+ `- Snowfall: ${day.snowfall || 0}cm\n` +
507
+ `- Wind: ${day.windSpeed || 'N/A'} km/h`;
508
+ }).join('\n\n');
509
+ return {
510
+ content: [
511
+ {
512
+ type: 'text',
513
+ text: `# ${days}-Day Forecast: ${resort.name}\n\n${formatted}`,
514
+ },
515
+ ],
516
+ };
517
+ }
518
+ case 'find_resorts_by_criteria': {
519
+ const data = await fetchAPI('/resorts?limit=200');
520
+ let results = data.data || [];
521
+ // Apply filters
522
+ if (args?.minScore != null) {
523
+ const min = Number(args.minScore);
524
+ results = results.filter((r) => (r.snowSure?.score || 0) >= min);
525
+ }
526
+ if (args?.minDepth != null) {
527
+ const min = Number(args.minDepth);
528
+ results = results.filter((r) => (r.conditions?.snowDepthCm || 0) >= min);
529
+ }
530
+ if (args?.minElevation != null) {
531
+ const min = Number(args.minElevation);
532
+ results = results.filter((r) => (r.elevation?.summit || 0) >= min);
533
+ }
534
+ if (args?.minRuns != null) {
535
+ const min = Number(args.minRuns);
536
+ results = results.filter((r) => (r.stats?.runs || 0) >= min);
537
+ }
538
+ if (args?.country) {
539
+ results = results.filter((r) => r.country?.toLowerCase() === args.country.toLowerCase());
540
+ }
541
+ if (args?.region) {
542
+ const regionMap = {
543
+ 'europe': ['Switzerland', 'France', 'Austria', 'Italy', 'Germany', 'Norway'],
544
+ 'north-america': ['United States', 'Canada'],
545
+ 'asia': ['Japan'],
546
+ };
547
+ const countries = regionMap[args.region] || [];
548
+ if (countries.length > 0) {
549
+ results = results.filter((r) => countries.includes(r.country));
550
+ }
551
+ }
552
+ // Sort by score
553
+ results.sort((a, b) => (b.snowSure?.score || 0) - (a.snowSure?.score || 0));
554
+ const limit = args?.limit || 20;
555
+ const formatted = results.slice(0, limit).map((r, i) => `${i + 1}. **${r.name}** (${r.country})\n` +
556
+ ` Score: ${r.snowSure?.score || 'N/A'} | Depth: ${r.conditions?.snowDepthCm || 'N/A'}cm | ` +
557
+ `Summit: ${r.elevation?.summit || 'N/A'}m | Runs: ${r.stats?.runs || 'N/A'}`).join('\n');
558
+ const filterDesc = [
559
+ args?.minScore && `Score ≥ ${args.minScore}`,
560
+ args?.minDepth && `Depth ≥ ${args.minDepth}cm`,
561
+ args?.minElevation && `Summit ≥ ${args.minElevation}m`,
562
+ args?.minRuns && `Runs ≥ ${args.minRuns}`,
563
+ args?.country && `Country: ${args.country}`,
564
+ args?.region && `Region: ${args.region}`,
565
+ ].filter(Boolean).join(', ');
566
+ return {
567
+ content: [
568
+ {
569
+ type: 'text',
570
+ text: `# Resorts Matching Criteria\n\n**Filters:** ${filterDesc || 'None'}\n**Results:** ${results.length} resorts found\n\n${formatted || 'No resorts match your criteria.'}`,
571
+ },
572
+ ],
573
+ };
574
+ }
575
+ case 'get_snow_history': {
576
+ const data = await fetchAPI(`/resorts/${args?.slug}`);
577
+ const resort = data.data;
578
+ if (!resort) {
579
+ return {
580
+ content: [{ type: 'text', text: `Resort "${args?.slug}" not found.` }],
581
+ };
582
+ }
583
+ const historic = resort.historic || {};
584
+ const snow = resort.snow || {};
585
+ const fiveYearAvg = historic.fiveYearAverage || 'N/A';
586
+ const thirtyYearAvg = historic.thirtyYearAverage || 'N/A';
587
+ const seasonTotal = snow.seasonTotal?.cm || 0;
588
+ let comparison = '';
589
+ if (typeof fiveYearAvg === 'number' && seasonTotal > 0) {
590
+ const pct5 = Math.round((seasonTotal / fiveYearAvg) * 100);
591
+ comparison += `- vs 5-Year Avg: ${pct5}% (${pct5 > 100 ? '+' : ''}${seasonTotal - fiveYearAvg}cm)\n`;
592
+ }
593
+ if (typeof thirtyYearAvg === 'number' && seasonTotal > 0) {
594
+ const pct30 = Math.round((seasonTotal / thirtyYearAvg) * 100);
595
+ comparison += `- vs 30-Year Avg: ${pct30}% (${pct30 > 100 ? '+' : ''}${seasonTotal - thirtyYearAvg}cm)\n`;
596
+ }
597
+ return {
598
+ content: [
599
+ {
600
+ type: 'text',
601
+ text: `# Snow History: ${resort.name}\n\n` +
602
+ `## This Season\n` +
603
+ `- **Season Total:** ${seasonTotal}cm\n` +
604
+ `- **Last 7 Days:** ${snow.last7d?.cm || 0}cm\n` +
605
+ `- **Last 24h:** ${snow.last24h?.cm || 0}cm\n\n` +
606
+ `## Historical Averages\n` +
607
+ `- **5-Year Average:** ${fiveYearAvg}cm\n` +
608
+ `- **30-Year Average:** ${thirtyYearAvg}cm\n\n` +
609
+ `## Season Comparison\n${comparison || 'Not enough data for comparison.'}\n\n` +
610
+ `## Best Months to Visit\n` +
611
+ `${historic.bestMonths?.join(', ') || 'January, February, March (typical)'}\n\n` +
612
+ `${historic.trend ? `## Historical Trend\n${historic.trend}` : ''}`
613
+ },
614
+ ],
615
+ };
616
+ }
617
+ case 'plan_ski_trip': {
618
+ const data = await fetchAPI('/snow-report?sort=snowsure&limit=50');
619
+ let resorts = data.data?.resorts || data.data || [];
620
+ // Filter by region
621
+ if (args?.region && args.region !== 'any') {
622
+ const regionMap = {
623
+ 'europe': ['Switzerland', 'France', 'Austria', 'Italy', 'Germany', 'Norway'],
624
+ 'north-america': ['United States', 'Canada'],
625
+ 'asia': ['Japan'],
626
+ };
627
+ const countries = regionMap[args.region] || [];
628
+ if (countries.length > 0) {
629
+ resorts = resorts.filter((r) => countries.includes(r.country));
630
+ }
631
+ }
632
+ // Get top recommendations
633
+ const recommendations = resorts.slice(0, 5);
634
+ const formatted = recommendations.map((r, i) => `### ${i + 1}. ${r.name} (${r.country})\n` +
635
+ `- **SnowSure Score:** ${r.snowSure?.score || 'N/A'}/100 - ${r.snowSure?.rating || 'N/A'}\n` +
636
+ `- **Current Depth:** ${r.conditions?.snowDepthCm || 'N/A'}cm\n` +
637
+ `- **14-Day Forecast:** ${Math.round(r.conditions?.forecast14dCm || 0)}cm expected\n` +
638
+ `- **Why:** ${r.snowSure?.tagline || 'Great conditions'}`).join('\n\n');
639
+ return {
640
+ content: [
641
+ {
642
+ type: 'text',
643
+ text: `# 🎿 Ski Trip Recommendations\n\n` +
644
+ `**Region:** ${args?.region || 'Worldwide'}\n` +
645
+ `**Dates:** ${args?.dates || 'Flexible'}\n` +
646
+ `**Level:** ${args?.level || 'All levels'}\n\n` +
647
+ `## Top Picks\n\n${formatted}\n\n` +
648
+ `## Tips\n` +
649
+ `- Book accommodation 2-4 weeks ahead for best rates\n` +
650
+ `- Check forecasts 7 days before departure\n` +
651
+ `- Consider mid-week dates for shorter lift lines\n` +
652
+ `- SnowSure scores above 50 indicate excellent conditions`,
653
+ },
654
+ ],
655
+ };
656
+ }
657
+ case 'get_webcam_status': {
658
+ const data = await fetchAPI(`/resorts/${args?.slug}`);
659
+ const resort = data.data;
660
+ if (!resort) {
661
+ return {
662
+ content: [{ type: 'text', text: `Resort "${args?.slug}" not found.` }],
663
+ };
664
+ }
665
+ const webcams = resort.webcams || [];
666
+ const webcamPage = resort.links?.webcamPage;
667
+ if (!webcams.length && !webcamPage) {
668
+ return {
669
+ content: [{ type: 'text', text: `No webcam data available for ${resort.name}.` }],
670
+ };
671
+ }
672
+ const formatted = webcams.map((cam) => `- **${cam.name || 'Webcam'}**\n` +
673
+ ` URL: ${cam.url || cam.embedUrl || 'N/A'}\n` +
674
+ ` ${cam.thumbnailUrl ? `Thumbnail: ${cam.thumbnailUrl}` : ''}`).join('\n');
675
+ return {
676
+ content: [
677
+ {
678
+ type: 'text',
679
+ text: `# Webcams: ${resort.name}\n\n` +
680
+ `${webcams.length ? formatted : 'Individual webcams not indexed.'}\n\n` +
681
+ `${webcamPage ? `**Resort Webcam Page:** ${webcamPage}` : ''}\n\n` +
682
+ `*View live at: https://snowsure.ai/resorts/${resort.slug}#webcams*`,
683
+ },
684
+ ],
685
+ };
686
+ }
687
+ case 'get_regional_summary': {
688
+ const data = await fetchAPI('/resorts?limit=200');
689
+ let resorts = data.data || [];
690
+ // Filter by region or country
691
+ if (args?.region) {
692
+ const regionMap = {
693
+ 'europe': ['Switzerland', 'France', 'Austria', 'Italy', 'Germany', 'Norway', 'Sweden'],
694
+ 'north-america': ['United States', 'Canada'],
695
+ 'asia': ['Japan', 'South Korea'],
696
+ 'alps': ['Switzerland', 'France', 'Austria', 'Italy'],
697
+ 'rockies': ['United States', 'Canada'],
698
+ 'japan': ['Japan'],
699
+ };
700
+ const countries = regionMap[args.region] || [];
701
+ if (countries.length > 0) {
702
+ resorts = resorts.filter((r) => countries.includes(r.country));
703
+ }
704
+ }
705
+ if (args?.country) {
706
+ resorts = resorts.filter((r) => r.country?.toLowerCase() === args.country.toLowerCase());
707
+ }
708
+ // Calculate stats
709
+ const totalResorts = resorts.length;
710
+ const withSnow = resorts.filter((r) => (r.conditions?.snowDepthCm || 0) > 0).length;
711
+ const avgDepth = resorts.reduce((sum, r) => sum + (r.conditions?.snowDepthCm || 0), 0) / totalResorts;
712
+ const avgScore = resorts.reduce((sum, r) => sum + (r.snowSure?.score || 0), 0) / totalResorts;
713
+ const totalFresh = resorts.reduce((sum, r) => sum + (r.conditions?.snowfall24hCm || 0), 0);
714
+ // Top resorts
715
+ const topByScore = [...resorts].sort((a, b) => (b.snowSure?.score || 0) - (a.snowSure?.score || 0)).slice(0, 5);
716
+ const topBySnow = [...resorts].sort((a, b) => (b.conditions?.snowfall24hCm || 0) - (a.conditions?.snowfall24hCm || 0)).slice(0, 5);
717
+ return {
718
+ content: [
719
+ {
720
+ type: 'text',
721
+ text: `# Regional Summary: ${args?.region || args?.country || 'Global'}\n\n` +
722
+ `## Statistics\n` +
723
+ `- **Total Resorts:** ${totalResorts}\n` +
724
+ `- **Resorts with Snow:** ${withSnow}\n` +
725
+ `- **Average Depth:** ${avgDepth.toFixed(0)}cm\n` +
726
+ `- **Average SnowSure Score:** ${avgScore.toFixed(0)}/100\n` +
727
+ `- **Fresh Snow (24h total):** ${totalFresh.toFixed(0)}cm across region\n\n` +
728
+ `## Top by SnowSure Score\n` +
729
+ topByScore.map((r, i) => `${i + 1}. ${r.name} - ${r.snowSure?.score || 'N/A'}/100`).join('\n') +
730
+ `\n\n## Most Fresh Snow (24h)\n` +
731
+ topBySnow.filter((r) => r.conditions?.snowfall24hCm > 0).map((r, i) => `${i + 1}. ${r.name} - +${r.conditions?.snowfall24hCm}cm`).join('\n') || 'No fresh snow reported',
732
+ },
733
+ ],
734
+ };
735
+ }
736
+ default:
737
+ throw new Error(`Unknown tool: ${name}`);
738
+ }
739
+ }
740
+ catch (error) {
741
+ return {
742
+ content: [
743
+ {
744
+ type: 'text',
745
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
746
+ },
747
+ ],
748
+ isError: true,
749
+ };
750
+ }
751
+ });
752
+ // List available resources
753
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
754
+ resources: [
755
+ {
756
+ uri: 'snowsure://snow-report',
757
+ name: 'Global Snow Report',
758
+ description: 'Current snow conditions and rankings for top 50 resorts worldwide',
759
+ mimeType: 'application/json',
760
+ },
761
+ {
762
+ uri: 'snowsure://resorts',
763
+ name: 'All Resorts',
764
+ description: 'Complete list of 500+ ski resorts with current conditions',
765
+ mimeType: 'application/json',
766
+ },
767
+ {
768
+ uri: 'snowsure://regions',
769
+ name: 'Available Regions',
770
+ description: 'List of regions and countries with resort counts',
771
+ mimeType: 'application/json',
772
+ },
773
+ {
774
+ uri: 'snowsure://api-docs',
775
+ name: 'API Documentation',
776
+ description: 'OpenAPI specification for the SnowSure API',
777
+ mimeType: 'application/json',
778
+ },
779
+ ],
780
+ }));
781
+ // Read resources
782
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
783
+ const { uri } = request.params;
784
+ switch (uri) {
785
+ case 'snowsure://snow-report': {
786
+ const data = await fetchAPI('/snow-report?limit=50');
787
+ return {
788
+ contents: [
789
+ {
790
+ uri,
791
+ mimeType: 'application/json',
792
+ text: JSON.stringify(data.data, null, 2),
793
+ },
794
+ ],
795
+ };
796
+ }
797
+ case 'snowsure://resorts': {
798
+ const data = await fetchAPI('/resorts?limit=500');
799
+ return {
800
+ contents: [
801
+ {
802
+ uri,
803
+ mimeType: 'application/json',
804
+ text: JSON.stringify(data.data, null, 2),
805
+ },
806
+ ],
807
+ };
808
+ }
809
+ case 'snowsure://regions': {
810
+ const data = await fetchAPI('/resorts?limit=500');
811
+ const resorts = data.data || [];
812
+ // Group by country and region
813
+ const byCountry = {};
814
+ const byRegion = {
815
+ 'europe': [],
816
+ 'north-america': [],
817
+ 'asia': [],
818
+ 'oceania': [],
819
+ };
820
+ resorts.forEach((r) => {
821
+ byCountry[r.country] = (byCountry[r.country] || 0) + 1;
822
+ // Map to regions
823
+ if (['Switzerland', 'France', 'Austria', 'Italy', 'Germany', 'Norway', 'Sweden'].includes(r.country)) {
824
+ if (!byRegion['europe'].includes(r.country))
825
+ byRegion['europe'].push(r.country);
826
+ }
827
+ else if (['United States', 'Canada'].includes(r.country)) {
828
+ if (!byRegion['north-america'].includes(r.country))
829
+ byRegion['north-america'].push(r.country);
830
+ }
831
+ else if (['Japan'].includes(r.country)) {
832
+ if (!byRegion['asia'].includes(r.country))
833
+ byRegion['asia'].push(r.country);
834
+ }
835
+ else if (['New Zealand', 'Australia'].includes(r.country)) {
836
+ if (!byRegion['oceania'].includes(r.country))
837
+ byRegion['oceania'].push(r.country);
838
+ }
839
+ });
840
+ return {
841
+ contents: [
842
+ {
843
+ uri,
844
+ mimeType: 'application/json',
845
+ text: JSON.stringify({
846
+ totalResorts: resorts.length,
847
+ byCountry,
848
+ byRegion,
849
+ }, null, 2),
850
+ },
851
+ ],
852
+ };
853
+ }
854
+ case 'snowsure://api-docs': {
855
+ return {
856
+ contents: [
857
+ {
858
+ uri,
859
+ mimeType: 'application/json',
860
+ text: JSON.stringify({
861
+ openapi: '3.1.0',
862
+ info: {
863
+ title: 'SnowSure API',
864
+ version: '3.0.0',
865
+ description: 'Real-time snow conditions for 220+ ski resorts worldwide. Data sourced from SnowSure\'s Sanity database with multi-model weather forecasts.',
866
+ },
867
+ servers: [{ url: 'https://www.snowsure.ai/api/v1' }],
868
+ endpoints: {
869
+ '/resorts': 'List all resorts with current conditions',
870
+ '/resorts/{slug}': 'Get detailed resort information including forecasts',
871
+ '/snow-report': 'Get global snow rankings sorted by various criteria',
872
+ },
873
+ website: 'https://www.snowsure.ai',
874
+ }, null, 2),
875
+ },
876
+ ],
877
+ };
878
+ }
879
+ default:
880
+ throw new Error(`Unknown resource: ${uri}`);
881
+ }
882
+ });
883
+ // Start the server
884
+ async function main() {
885
+ const transport = new StdioServerTransport();
886
+ await server.connect(transport);
887
+ console.error('SnowSure MCP server v3.0 running');
888
+ console.error('API: https://www.snowsure.ai/api/v1');
889
+ console.error('Tools: 11 | Resources: 4');
890
+ }
891
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "snowsure-mcp-server",
3
+ "version": "3.0.1",
4
+ "description": "MCP server for ski resort snow: real-time powder rankings, 14-day forecasts, 500+ resorts — SnowSure.ai (Cursor, Claude, AI agents)",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "snowsure-mcp": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js",
13
+ "dev": "tsx src/index.ts",
14
+ "watch": "tsc --watch"
15
+ },
16
+ "keywords": [
17
+ "mcp",
18
+ "model-context-protocol",
19
+ "cursor",
20
+ "claude",
21
+ "openai",
22
+ "ski",
23
+ "ski-resort",
24
+ "snow",
25
+ "powder",
26
+ "weather",
27
+ "forecast",
28
+ "snowsure"
29
+ ],
30
+ "author": "SnowSure",
31
+ "license": "MIT",
32
+ "homepage": "https://www.snowsure.ai/developers",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/mikeslone/snowsure-web.git",
36
+ "directory": "mcp-server"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/mikeslone/snowsure-web/issues"
40
+ },
41
+ "dependencies": {
42
+ "@modelcontextprotocol/sdk": "^1.0.0"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^20.11.0",
46
+ "tsx": "^4.7.0",
47
+ "typescript": "^5.3.3"
48
+ },
49
+ "engines": {
50
+ "node": ">=18.0.0"
51
+ },
52
+ "files": [
53
+ "dist",
54
+ "README.md"
55
+ ]
56
+ }