open-meteo-mcp-server 1.3.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -167,6 +167,19 @@ TRANSPORT=http PORT=3000 npx open-meteo-mcp-server
167
167
 
168
168
  This starts an Express server on the specified port (default: 3000) with the MCP endpoint at `/mcp`. The HTTP transport supports session management with unique session IDs per client.
169
169
 
170
+ For production deployments, enable authentication and rate limiting:
171
+
172
+ ```bash
173
+ API_KEY=your-secret-key RATE_LIMIT_RPM=60 TRANSPORT=http PORT=3000 npx open-meteo-mcp-server
174
+ ```
175
+
176
+ Clients must then include the key in every request:
177
+ ```
178
+ Authorization: Bearer your-secret-key
179
+ # or
180
+ X-API-Key: your-secret-key
181
+ ```
182
+
170
183
  #### Using npm scripts
171
184
 
172
185
  ```bash
@@ -284,6 +297,12 @@ All environment variables are optional and have sensible defaults:
284
297
  - `TRANSPORT` - Transport mode: `http` for Streamable HTTP, omit for stdio (default: stdio)
285
298
  - `PORT` - HTTP server port when using HTTP transport (default: 3000)
286
299
 
300
+ #### HTTP Transport Security (optional)
301
+
302
+ - `API_KEY` - When set, all requests to `/mcp` must include this key via `Authorization: Bearer <key>` or `X-API-Key: <key>`. Leave unset for open access (local/dev mode).
303
+ - `RATE_LIMIT_RPM` - Maximum requests per minute per IP (default: `60`). HTTP transport only.
304
+ - `TRUSTED_PROXIES` - Comma-separated list of trusted proxy IPs or CIDR ranges (e.g. `10.0.0.0/8,172.16.0.0/12`). When set, `X-Forwarded-For` is honoured only for requests originating from these addresses. Leave unset to always use the direct connection IP.
305
+
287
306
  ## Usage Examples
288
307
 
289
308
  ### Geocoding and Location Search
@@ -443,7 +462,8 @@ src/
443
462
  ├── index.ts # MCP server entry point
444
463
  ├── client.ts # HTTP client for Open-Meteo API
445
464
  ├── tools.ts # MCP tool definitions
446
- └── types.ts # Zod validation schemas
465
+ ├── types.ts # Zod validation schemas
466
+ └── security.ts # Auth middleware, rate limiter, IP extraction
447
467
  ```
448
468
 
449
469
  ## API Coverage
@@ -485,7 +505,6 @@ The server provides comprehensive error handling with detailed error messages fo
485
505
  ## Performance
486
506
 
487
507
  - Efficient HTTP client with connection pooling
488
- - Request caching for repeated queries
489
508
  - Optimized data serialization
490
509
  - Minimal memory footprint
491
510
 
package/dist/client.d.ts CHANGED
@@ -9,7 +9,7 @@ export declare class OpenMeteoClient {
9
9
  private geocodingClient;
10
10
  private floodClient;
11
11
  private climateClient;
12
- constructor(baseURL?: string);
12
+ constructor(baseURL?: string, version?: string);
13
13
  private buildParams;
14
14
  getForecast(params: ForecastParams): Promise<WeatherResponse>;
15
15
  getArchive(params: ArchiveParams): Promise<WeatherResponse>;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,gBAAgB,CAAgB;IACxC,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,eAAe,CAAgB;IACvC,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,aAAa,CAAgB;gBAEzB,OAAO,GAAE,MAAuE;IAoC5F,OAAO,CAAC,WAAW;IAgBb,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAO7D,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC;IAO3D,UAAU,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAO5D,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAOxD,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAOhE,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC;IAOvD,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAOxD,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAO1D,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAOxD,aAAa,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IAOjE,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,eAAe,CAAC;IAOzD,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAO7D,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAOjE,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC;IAOvD,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAO7D,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC;IAO3D,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAMxE"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,gBAAgB,CAAgB;IACxC,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,eAAe,CAAgB;IACvC,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,aAAa,CAAgB;gBAGnC,OAAO,GAAE,MAAuE,EAChF,OAAO,SAAY;IAqCrB,OAAO,CAAC,WAAW;IAgBb,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAO7D,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC;IAO3D,UAAU,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAO5D,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAOxD,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAOhE,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC;IAOvD,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAOxD,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAO1D,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAOxD,aAAa,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IAOjE,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,eAAe,CAAC;IAOzD,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAO7D,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAOjE,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC;IAOvD,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAO7D,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC;IAO3D,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAMxE"}
package/dist/client.js CHANGED
@@ -9,12 +9,12 @@ export class OpenMeteoClient {
9
9
  geocodingClient;
10
10
  floodClient;
11
11
  climateClient;
12
- constructor(baseURL = process.env.OPEN_METEO_API_URL || 'https://api.open-meteo.com') {
12
+ constructor(baseURL = process.env.OPEN_METEO_API_URL || 'https://api.open-meteo.com', version = 'unknown') {
13
13
  const config = {
14
14
  timeout: 30000,
15
15
  headers: {
16
16
  Accept: 'application/json',
17
- 'User-Agent': 'Open-Meteo-MCP-Server/1.0.0',
17
+ 'User-Agent': `Open-Meteo-MCP-Server/${version}`,
18
18
  },
19
19
  };
20
20
  // Utilisation des variables d'environnement avec des valeurs par défaut
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAA6B,MAAM,OAAO,CAAC;AAkBlD,MAAM,OAAO,eAAe;IAClB,MAAM,CAAgB;IACtB,gBAAgB,CAAgB;IAChC,YAAY,CAAgB;IAC5B,aAAa,CAAgB;IAC7B,cAAc,CAAgB;IAC9B,cAAc,CAAgB;IAC9B,eAAe,CAAgB;IAC/B,WAAW,CAAgB;IAC3B,aAAa,CAAgB;IAErC,YAAY,UAAkB,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,4BAA4B;QAC1F,MAAM,MAAM,GAAG;YACb,OAAO,EAAE,KAAK;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,kBAAkB;gBAC1B,YAAY,EAAE,6BAA6B;aAC5C;SACF,CAAC;QAEF,wEAAwE;QACxE,MAAM,aAAa,GACjB,OAAO,CAAC,GAAG,CAAC,8BAA8B,IAAI,wCAAwC,CAAC;QACzF,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,mCAAmC,CAAC;QAC/F,MAAM,UAAU,GACd,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,oCAAoC,CAAC;QACjF,MAAM,WAAW,GACf,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,qCAAqC,CAAC;QACnF,MAAM,WAAW,GACf,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,qCAAqC,CAAC;QACnF,MAAM,YAAY,GAChB,OAAO,CAAC,GAAG,CAAC,4BAA4B,IAAI,sCAAsC,CAAC;QACrF,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,kCAAkC,CAAC;QAC5F,MAAM,UAAU,GACd,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,oCAAoC,CAAC;QAEjF,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;QACnD,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;QAC5E,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;QACpE,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;QACtE,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;QACxE,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;QACxE,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;QAC1E,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;QAClE,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;IACxE,CAAC;IAEO,WAAW,CAAC,MAA+B;QACjD,MAAM,MAAM,GAA2B,EAAE,CAAC;QAE1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAClD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBAC1C,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;oBACzB,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAChC,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;gBAC9B,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,MAAsB;QACtC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,EAAE;YACrD,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;SACjC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,MAAqB;QACpC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,aAAa,EAAE;YAC3D,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;SACjC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,MAAsB;QACrC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,EAAE;YACrD,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;SACjC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,MAAsB;QACjC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE;YAChD,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;SACjC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,MAAsB;QACzC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,iBAAiB,EAAE;YACxD,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;SACjC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,MAAmB;QAChC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE;YAClD,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;SACjC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,MAAsB;QACjC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE;YAChD,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;SACjC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,MAAsB;QACnC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE;YAClD,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;SACjC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,MAAsB;QACjC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE;YAChD,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;SACjC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,MAAwB;QAC1C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,iBAAiB,EAAE;YAClE,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;SACjC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,MAAoB;QAClC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,YAAY,EAAE;YACzD,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;SACjC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,MAAsB;QACtC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,cAAc,EAAE;YAC7D,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;SACjC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,MAAuB;QACxC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE;YACtD,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;SACjC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,MAAmB;QAChC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,WAAW,EAAE;YACvD,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;SACjC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,MAAsB;QACtC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,cAAc,EAAE;YAC7D,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;SACjC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,MAAqB;QACpC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,aAAa,EAAE;YAC3D,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;SACjC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,MAAuB;QACxC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,YAAY,EAAE;YAC5D,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;SACjC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;CACF"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import 'dotenv/config';
3
+ export declare class OpenMeteoMCPServer {
4
+ private client;
5
+ private sessionServers;
6
+ private static readonly SESSION_TTL_MS;
7
+ private static readonly MAX_SESSIONS;
8
+ private static readonly CLEANUP_INTERVAL_MS;
9
+ constructor();
10
+ private createServer;
11
+ private getSession;
12
+ private startCleanupTimer;
13
+ private buildExpressApp;
14
+ private startHttpTransport;
15
+ run(): Promise<void>;
16
+ }
3
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAC;AAgDvB,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,cAAc,CAGR;IAEd,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAkB;IACxD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAO;IAC3C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAiB;;IAO5D,OAAO,CAAC,YAAY;IAsIpB,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,eAAe;IAkQvB,OAAO,CAAC,kBAAkB;IAapB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAiB3B"}
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/
9
9
  import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
10
10
  import express from 'express';
11
11
  import { OpenMeteoClient } from './client.js';
12
+ import { createAuthMiddleware, createRateLimiter, generateSessionId, getClientIp, sanitizeErrorMessage, } from './security.js';
12
13
  import { ALL_TOOLS } from './tools.js';
13
14
  import { AirQualityParamsSchema, ArchiveParamsSchema, ClimateParamsSchema, EcmwfParamsSchema, ElevationParamsSchema, EnsembleParamsSchema, FloodParamsSchema, ForecastParamsSchema, GeocodingParamsSchema, MarineParamsSchema, SeasonalParamsSchema, } from './types.js';
14
15
  const __filename = fileURLToPath(import.meta.url);
@@ -18,17 +19,7 @@ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-
18
19
  function log(level, event, data = {}) {
19
20
  process.stderr.write(`${JSON.stringify({ timestamp: new Date().toISOString(), level, event, ...data })}\n`);
20
21
  }
21
- // Extracts real client IP, respecting X-Forwarded-For for CDN/proxy setups
22
- function getClientIp(req) {
23
- const xff = req.headers['x-forwarded-for'];
24
- if (xff) {
25
- const first = Array.isArray(xff) ? xff[0] : xff;
26
- const raw = first ?? '';
27
- return (raw.split(',')[0] ?? raw).trim();
28
- }
29
- return req.ip ?? req.socket?.remoteAddress ?? 'unknown';
30
- }
31
- class OpenMeteoMCPServer {
22
+ export class OpenMeteoMCPServer {
32
23
  client;
33
24
  sessionServers = new Map();
34
25
  static SESSION_TTL_MS = 60 * 60 * 1000; // 1 hour idle timeout
@@ -36,7 +27,7 @@ class OpenMeteoMCPServer {
36
27
  static CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // run cleanup every 5 minutes
37
28
  constructor() {
38
29
  const baseURL = process.env.OPEN_METEO_API_URL || 'https://api.open-meteo.com';
39
- this.client = new OpenMeteoClient(baseURL);
30
+ this.client = new OpenMeteoClient(baseURL, pkg.version);
40
31
  }
41
32
  createServer() {
42
33
  const server = new Server({
@@ -183,168 +174,267 @@ class OpenMeteoMCPServer {
183
174
  // Don't keep the process alive just for cleanup
184
175
  timer.unref();
185
176
  }
186
- async run() {
187
- const useHttp = process.env.TRANSPORT === 'http';
188
- if (useHttp) {
189
- this.startCleanupTimer();
190
- const app = express();
191
- app.use(express.json());
192
- // Health check endpoint
193
- app.get('/health', (_req, res) => {
194
- res.status(200).json({ status: 'ok' });
195
- });
196
- app.use((req, _res, next) => {
197
- const acceptHeader = req.headers.accept;
198
- const tokens = acceptHeader
199
- ? acceptHeader
200
- .split(',')
201
- .map((value) => value.trim())
202
- .filter(Boolean)
203
- : [];
204
- const normalized = new Set(tokens.map((value) => value.toLowerCase()));
205
- const ensureHeader = (value) => {
206
- if (!normalized.has(value)) {
207
- tokens.push(value);
208
- normalized.add(value);
209
- }
210
- };
211
- ensureHeader('application/json');
212
- ensureHeader('text/event-stream');
213
- req.headers.accept = tokens.join(', ');
214
- next();
215
- });
216
- // Generate unique session IDs for each client
217
- const sessionIdGenerator = () => {
218
- const timestamp = Date.now().toString(36);
219
- const random1 = Math.random().toString(36).substring(2, 15);
220
- const random2 = Math.random().toString(36).substring(2, 15);
221
- const random3 = Math.random().toString(36).substring(2, 15);
222
- return `${timestamp}-${random1}-${random2}-${random3}`;
177
+ buildExpressApp() {
178
+ const app = express();
179
+ app.use(express.json());
180
+ // Health check endpoint
181
+ app.get('/health', (_req, res) => {
182
+ res.status(200).json({ status: 'ok' });
183
+ });
184
+ app.use((req, _res, next) => {
185
+ const acceptHeader = req.headers.accept;
186
+ const tokens = acceptHeader
187
+ ? acceptHeader
188
+ .split(',')
189
+ .map((value) => value.trim())
190
+ .filter(Boolean)
191
+ : [];
192
+ const normalized = new Set(tokens.map((value) => value.toLowerCase()));
193
+ const ensureHeader = (value) => {
194
+ if (!normalized.has(value)) {
195
+ tokens.push(value);
196
+ normalized.add(value);
197
+ }
223
198
  };
224
- app.post('/mcp', async (req, res) => {
225
- const remoteIp = getClientIp(req);
226
- const userAgent = req.headers['user-agent'] ?? 'unknown';
227
- try {
228
- const method = req.body?.method || 'unknown';
229
- // Extract session ID from headers
230
- const sessionId = (req.headers['mcp-session-id'] || req.headers['Mcp-Session-Id']);
231
- log('info', 'http_request', {
232
- method,
233
- session_id: sessionId ? sessionId.substring(0, 8) : null,
199
+ ensureHeader('application/json');
200
+ ensureHeader('text/event-stream');
201
+ req.headers.accept = tokens.join(', ');
202
+ next();
203
+ });
204
+ // GET /mcp SSE streaming for server-to-client notifications
205
+ app.get('/mcp', async (req, res) => {
206
+ const remoteIp = getClientIp(req);
207
+ const userAgent = req.headers['user-agent'] ?? 'unknown';
208
+ try {
209
+ const sessionId = req.headers['mcp-session-id'];
210
+ log('info', 'http_request', {
211
+ method: 'GET',
212
+ session_id: sessionId ? sessionId.substring(0, 8) : null,
213
+ remote_ip: remoteIp,
214
+ user_agent: userAgent,
215
+ });
216
+ if (!sessionId) {
217
+ res.status(400).json({
218
+ jsonrpc: '2.0',
219
+ error: { code: -32600, message: 'Invalid Request: Session ID required' },
220
+ id: null,
221
+ });
222
+ return;
223
+ }
224
+ const session = this.getSession(sessionId);
225
+ if (!session) {
226
+ log('warn', 'session_not_found', {
227
+ session_id: sessionId.substring(0, 8),
228
+ remote_ip: remoteIp,
229
+ });
230
+ res.status(404).json({
231
+ jsonrpc: '2.0',
232
+ error: { code: -32600, message: 'Session not found' },
233
+ id: null,
234
+ });
235
+ return;
236
+ }
237
+ await session.transport.handleRequest(req, res);
238
+ }
239
+ catch (err) {
240
+ const errorMessage = err instanceof Error ? err.message : String(err);
241
+ log('error', 'request_error', { error: errorMessage, remote_ip: remoteIp });
242
+ if (!res.headersSent) {
243
+ res.status(500).json({
244
+ jsonrpc: '2.0',
245
+ error: { code: -32603, message: sanitizeErrorMessage(err) },
246
+ id: null,
247
+ });
248
+ }
249
+ }
250
+ });
251
+ // DELETE /mcp — session termination
252
+ app.delete('/mcp', async (req, res) => {
253
+ const remoteIp = getClientIp(req);
254
+ const userAgent = req.headers['user-agent'] ?? 'unknown';
255
+ try {
256
+ const sessionId = req.headers['mcp-session-id'];
257
+ log('info', 'http_request', {
258
+ method: 'DELETE',
259
+ session_id: sessionId ? sessionId.substring(0, 8) : null,
260
+ remote_ip: remoteIp,
261
+ user_agent: userAgent,
262
+ });
263
+ if (!sessionId) {
264
+ res.status(400).json({
265
+ jsonrpc: '2.0',
266
+ error: { code: -32600, message: 'Invalid Request: Session ID required' },
267
+ id: null,
268
+ });
269
+ return;
270
+ }
271
+ // Use direct map access — lastActivity is irrelevant for a session about to be destroyed
272
+ const session = this.sessionServers.get(sessionId);
273
+ if (!session) {
274
+ log('warn', 'session_not_found', {
275
+ session_id: sessionId.substring(0, 8),
234
276
  remote_ip: remoteIp,
235
- user_agent: userAgent,
236
277
  });
237
- // If no session ID and it's an initialize request, create a new session
238
- if (!sessionId && req.body?.method === 'initialize') {
239
- if (this.sessionServers.size >= OpenMeteoMCPServer.MAX_SESSIONS) {
240
- log('warn', 'session_limit_reached', {
241
- current: this.sessionServers.size,
242
- max: OpenMeteoMCPServer.MAX_SESSIONS,
243
- remote_ip: remoteIp,
244
- });
245
- res.status(503).json({
246
- jsonrpc: '2.0',
247
- error: { code: -32603, message: 'Server at session capacity, try again later' },
248
- id: req.body?.id || null,
249
- });
250
- return;
251
- }
252
- // Generate a new session ID
253
- const newSessionId = sessionIdGenerator();
254
- log('info', 'session_created', { session_id: newSessionId.substring(0, 8) });
255
- // Create server and transport for this new session
256
- const server = this.createServer();
257
- const transport = new StreamableHTTPServerTransport({
258
- enableJsonResponse: true,
259
- sessionIdGenerator: () => newSessionId,
278
+ res.status(404).json({
279
+ jsonrpc: '2.0',
280
+ error: { code: -32600, message: 'Session not found' },
281
+ id: null,
282
+ });
283
+ return;
284
+ }
285
+ await session.transport.close();
286
+ res.status(200).json({ message: 'Session terminated' });
287
+ }
288
+ catch (err) {
289
+ const errorMessage = err instanceof Error ? err.message : String(err);
290
+ log('error', 'request_error', { error: errorMessage, remote_ip: remoteIp });
291
+ if (!res.headersSent) {
292
+ res.status(500).json({
293
+ jsonrpc: '2.0',
294
+ error: { code: -32603, message: sanitizeErrorMessage(err) },
295
+ id: null,
296
+ });
297
+ }
298
+ }
299
+ });
300
+ app.use(createRateLimiter());
301
+ app.use(createAuthMiddleware());
302
+ app.post('/mcp', async (req, res) => {
303
+ const remoteIp = getClientIp(req);
304
+ const userAgent = req.headers['user-agent'] ?? 'unknown';
305
+ try {
306
+ const method = req.body?.method || 'unknown';
307
+ // Extract session ID from headers (Express normalises headers to lowercase)
308
+ const sessionId = req.headers['mcp-session-id'];
309
+ log('info', 'http_request', {
310
+ method,
311
+ session_id: sessionId ? sessionId.substring(0, 8) : null,
312
+ remote_ip: remoteIp,
313
+ user_agent: userAgent,
314
+ });
315
+ // If no session ID and it's an initialize request, create a new session
316
+ if (!sessionId && req.body?.method === 'initialize') {
317
+ if (this.sessionServers.size >= OpenMeteoMCPServer.MAX_SESSIONS) {
318
+ log('warn', 'session_limit_reached', {
319
+ current: this.sessionServers.size,
320
+ max: OpenMeteoMCPServer.MAX_SESSIONS,
321
+ remote_ip: remoteIp,
322
+ });
323
+ res.status(503).json({
324
+ jsonrpc: '2.0',
325
+ error: { code: -32603, message: 'Server at session capacity, try again later' },
326
+ id: req.body?.id || null,
260
327
  });
261
- server.oninitialized = () => {
262
- log('info', 'session_initialized', { session_id: newSessionId.substring(0, 8) });
263
- };
264
- server.onclose = () => {
265
- this.sessionServers.delete(newSessionId);
266
- log('info', 'session_closed', { session_id: newSessionId.substring(0, 8) });
267
- };
268
- await server.connect(transport);
269
- this.sessionServers.set(newSessionId, { server, transport, lastActivity: Date.now() });
270
- // Set session ID in response header before handling request
271
- res.setHeader('mcp-session-id', newSessionId);
272
- // Handle the initialize request
273
- await transport.handleRequest(req, res, req.body);
274
328
  return;
275
329
  }
276
- if (sessionId) {
277
- const session = this.getSession(sessionId);
278
- if (!session) {
279
- log('warn', 'session_not_found', {
280
- session_id: sessionId.substring(0, 8),
281
- remote_ip: remoteIp,
282
- });
283
- res.status(404).json({
284
- jsonrpc: '2.0',
285
- error: { code: -32600, message: 'Session not found' },
286
- id: req.body?.id || null,
287
- });
288
- return;
289
- }
290
- const { transport } = session;
291
- await transport.handleRequest(req, res, req.body);
292
- }
293
- else {
294
- // No session ID and not an initialize request - error
295
- log('warn', 'invalid_request', {
296
- reason: 'missing_session_id',
297
- method,
330
+ // Generate a new session ID
331
+ const newSessionId = generateSessionId();
332
+ log('info', 'session_created', { session_id: newSessionId.substring(0, 8) });
333
+ // Create server and transport for this new session
334
+ const server = this.createServer();
335
+ const transport = new StreamableHTTPServerTransport({
336
+ enableJsonResponse: true,
337
+ sessionIdGenerator: () => newSessionId,
338
+ });
339
+ server.oninitialized = () => {
340
+ log('info', 'session_initialized', { session_id: newSessionId.substring(0, 8) });
341
+ };
342
+ server.onclose = () => {
343
+ this.sessionServers.delete(newSessionId);
344
+ log('info', 'session_closed', { session_id: newSessionId.substring(0, 8) });
345
+ };
346
+ await server.connect(transport);
347
+ this.sessionServers.set(newSessionId, { server, transport, lastActivity: Date.now() });
348
+ // Set session ID in response header before handling request
349
+ res.setHeader('mcp-session-id', newSessionId);
350
+ // Handle the initialize request
351
+ await transport.handleRequest(req, res, req.body);
352
+ return;
353
+ }
354
+ if (sessionId) {
355
+ const session = this.getSession(sessionId);
356
+ if (!session) {
357
+ log('warn', 'session_not_found', {
358
+ session_id: sessionId.substring(0, 8),
298
359
  remote_ip: remoteIp,
299
360
  });
300
- res.status(400).json({
361
+ res.status(404).json({
301
362
  jsonrpc: '2.0',
302
- error: {
303
- code: -32600,
304
- message: 'Invalid Request: Session ID required for non-initialize requests',
305
- },
363
+ error: { code: -32600, message: 'Session not found' },
306
364
  id: req.body?.id || null,
307
365
  });
366
+ return;
308
367
  }
368
+ const { transport } = session;
369
+ await transport.handleRequest(req, res, req.body);
309
370
  }
310
- catch (err) {
311
- const errorMessage = err instanceof Error ? err.message : String(err);
312
- log('error', 'request_error', { error: errorMessage, remote_ip: remoteIp });
313
- res.status(500).json({
371
+ else {
372
+ // No session ID and not an initialize request - error
373
+ log('warn', 'invalid_request', {
374
+ reason: 'missing_session_id',
375
+ method,
376
+ remote_ip: remoteIp,
377
+ });
378
+ res.status(400).json({
314
379
  jsonrpc: '2.0',
315
380
  error: {
316
- code: -32603,
317
- message: `Internal error: ${errorMessage}`,
381
+ code: -32600,
382
+ message: 'Invalid Request: Session ID required for non-initialize requests',
318
383
  },
319
384
  id: req.body?.id || null,
320
385
  });
321
386
  }
322
- });
323
- const port = parseInt(process.env.PORT || '3000', 10);
324
- app
325
- .listen(port, () => {
326
- log('info', 'server_start', { transport: 'http', port });
327
- })
328
- .on('error', (err) => {
329
- log('error', 'server_error', { error: err instanceof Error ? err.message : String(err) });
330
- process.exit(1);
331
- });
387
+ }
388
+ catch (err) {
389
+ const errorMessage = err instanceof Error ? err.message : String(err);
390
+ log('error', 'request_error', { error: errorMessage, remote_ip: remoteIp });
391
+ res.status(500).json({
392
+ jsonrpc: '2.0',
393
+ error: {
394
+ code: -32603,
395
+ message: sanitizeErrorMessage(err),
396
+ },
397
+ id: req.body?.id || null,
398
+ });
399
+ }
400
+ });
401
+ return app;
402
+ }
403
+ startHttpTransport() {
404
+ const app = this.buildExpressApp();
405
+ const port = parseInt(process.env.PORT || '3000', 10);
406
+ app
407
+ .listen(port, () => {
408
+ log('info', 'server_start', { transport: 'http', port });
409
+ })
410
+ .on('error', (err) => {
411
+ log('error', 'server_error', { error: err instanceof Error ? err.message : String(err) });
412
+ process.exit(1);
413
+ });
414
+ }
415
+ async run() {
416
+ const transport = process.env.TRANSPORT || 'stdio';
417
+ if (transport === 'http') {
418
+ this.startCleanupTimer();
419
+ this.startHttpTransport();
332
420
  }
333
421
  else {
334
422
  // For stdio mode, create a single server instance
335
423
  const server = this.createServer();
336
- const transport = new StdioServerTransport();
424
+ const stdioTransport = new StdioServerTransport();
337
425
  server.oninitialized = () => {
338
426
  log('info', 'session_initialized', { transport: 'stdio' });
339
427
  };
340
- await server.connect(transport);
428
+ await server.connect(stdioTransport);
341
429
  log('info', 'server_start', { transport: 'stdio' });
342
430
  }
343
431
  }
344
432
  }
345
- const server = new OpenMeteoMCPServer();
346
- server.run().catch((err) => {
347
- log('error', 'server_error', { error: err instanceof Error ? err.message : String(err) });
348
- process.exit(1);
349
- });
433
+ if (process.env.NODE_ENV !== 'test') {
434
+ const server = new OpenMeteoMCPServer();
435
+ server.run().catch((err) => {
436
+ log('error', 'server_error', { error: err instanceof Error ? err.message : String(err) });
437
+ process.exit(1);
438
+ });
439
+ }
350
440
  //# sourceMappingURL=index.js.map