@dangahagan/weather-mcp 1.35.3 → 1.35.4

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
@@ -3,7 +3,7 @@
3
3
  [![npm version](https://badge.fury.io/js/@dangahagan%2Fweather-mcp.svg)](https://www.npmjs.com/package/@dangahagan/weather-mcp)
4
4
  [![MCP Registry](https://img.shields.io/badge/MCP-Registry-blue)](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.dgahagan/weather-mcp)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
- [![Tests](https://img.shields.io/badge/tests-4%2C752%20passing-brightgreen)](./docs/testing/TEST_SUITE_README.md)
6
+ [![Tests](https://img.shields.io/badge/tests-4%2C800%20passing-brightgreen)](./docs/testing/TEST_SUITE_README.md)
7
7
  [![Node](https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js&logoColor=white)](https://nodejs.org)
8
8
 
9
9
  **Give your AI assistant real weather data — 17 tools, zero API keys, zero signup, zero cost.**
@@ -58,7 +58,7 @@ Choose this one if you want:
58
58
 
59
59
  - **Genuinely free** — every data source is a free public API. No trial that expires, no credit card, no rate-limited "free tier" bait.
60
60
  - **No API keys** — install to first forecast in under a minute. Nothing to configure, nothing to leak into a repo. ([Four optional keys](#optional-api-keys) add extras if you want them; the default configuration needs none.)
61
- - **Fully open source** — MIT licensed, readable TypeScript, 4,752 tests. Audit it, fork it, fix it.
61
+ - **Fully open source** — MIT licensed, readable TypeScript, 4,800 tests. Audit it, fork it, fix it.
62
62
  - **Privacy-respecting** — your queries go directly from your machine to public weather APIs. No middleman server, and no telemetry unless you opt in.
63
63
  - **Breadth** — 17 tools covering weather, safety hazards (lightning, floods, wildfires), marine conditions, air quality, and historical data back to 1940. Most weather MCPs stop at forecasts.
64
64
 
@@ -274,6 +274,7 @@ Supported on `get_forecast`, `get_current_conditions`, and `get_historical_weath
274
274
  | `CACHE_ENABLED` | `true` | Enable/disable response caching |
275
275
  | `CACHE_MAX_SIZE` | `1000` | Max cache entries (100–10000) |
276
276
  | `API_TIMEOUT_MS` | `30000` | Upstream request timeout, end to end on a wall clock — connect, proxy and response body (5000–120000). A few clients keep a timeout of their own, such as geocoding at 10 s |
277
+ | `HTTPS_PROXY` / `NO_PROXY` | — | Send the weather API requests through an HTTP CONNECT proxy (`ALL_PROXY` and lowercase names also work). Credentials go in the URL, percent-encoded: `http://user:p%40ss@proxy:3128` sends the password `p@ss`, so write a literal `%` as `%25` |
277
278
  | `WEATHER_LIGHTNING_PREWARM` | `true` | Subscribe saved locations' geohashes at startup so lightning has coverage before the first query, and keep them subscribed for as long as they stay saved. The saved list is re-read every 30 minutes: a location saved from another client is picked up, and a removed one stops being watched within about an hour, with no restart. Follows the lightning tool (`get_lightning_activity`), not `get_weather_summary`: no effect when the lightning tool is disabled, as it is in the default `basic` preset — use `ENABLED_TOOLS=basic,+lightning` to opt in. A saved location that does not fit the 50-subscription limit is skipped rather than displacing an area you queried. Set `false` to skip pre-warm and the persistent MQTT connection it opens. |
278
279
  | `LOG_LEVEL` | `1` | `0`/`DEBUG`, `1`/`INFO`, `2`/`WARN`, `3`/`ERROR` — number or name, names case-insensitive. An unrecognized value warns on stderr and falls back to `INFO`. Logs go to stderr. `0`/`DEBUG` adds detail, never location text |
279
280
  | `LOG_PII` | — | Set `true` to log full-precision coordinates, geocoding queries, saved-location aliases, and the error text of failed tool calls. By default these are left out and coordinates are rounded to about 1 km. The saved place name and any MQTT broker credentials are never logged. For local debugging only — not for shared or centrally collected logs |
@@ -393,7 +394,7 @@ Being honest about what free public data can and can't do:
393
394
  ```bash
394
395
  npm run build # Compile TypeScript
395
396
  npm run dev # Run in development mode
396
- npm test # Run all 4,752 tests
397
+ npm test # Run all 4,800 tests
397
398
  npm run test:coverage # Coverage report
398
399
  npm run audit # Dependency vulnerability scan
399
400
  ```
@@ -14,14 +14,21 @@
14
14
  * `transitional.clarifyTimeoutError`, so neither is honoured.
15
15
  * - It races the inner adapter rather than only signalling it (G148): the promise settles at the
16
16
  * deadline whether or not the inner adapter honours the abort, and the abort then releases the
17
- * inner request. A tunnelling agent's pending CONNECT socket (axios's bundled https-proxy-agent)
18
- * has no cancel hook and stays open until the proxy closes it. Whatever the inner adapter
19
- * does afterwards is ignored (settle-once).
17
+ * inner request. Whatever the inner adapter does afterwards is ignored (settle-once).
18
+ * - The abort also releases a pending proxy socket. On an `https:` request that `proxy-from-env`
19
+ * routes through a proxy, the inner adapter receives `proxy: false` and a per-request tunnel
20
+ * agent (`proxyTunnel.ts`) hooked on **this** signal, so the deadline destroys the CONNECT socket
21
+ * that axios's bundled https-proxy-agent would hold until the proxy closed it. The hook is the
22
+ * signal, not the request: Node emits no event on a request destroyed before it has a socket.
23
+ * `proxy: false` is load-bearing: without it axios's `setProxy` (`lib/adapters/http.js`) swaps
24
+ * any non-https-proxy-agent `httpsAgent` for its own tunnel. A request with no timeout has no
25
+ * signal and keeps axios's own tunnel; every client in `src/` sets a timeout.
20
26
  * - A caller's own `signal` is chained in, and a caller's cancel stays axios's `ERR_CANCELED`.
21
27
  * - The deadline timer is ref'd (G114) and cleared on every settle path, with the caller-signal
22
28
  * listener, so the timer itself holds a process for at most `timeout`.
23
29
  *
24
- * This module reads no runtime axios export at module scope or in `withRequestDeadline()` itself:
30
+ * This module reads no runtime axios export at module scope or in `withRequestDeadline()` itself
31
+ * (the `proxyTunnel.js` import is ours, not axios's):
25
32
  * thirteen unit-test mocks of `axios` expose only `create`, and they construct services that call
26
33
  * this at construction. Only the returned adapter, when a request runs, reads `axios.*`.
27
34
  *
@@ -1 +1 @@
1
- {"version":3,"file":"httpDeadline.d.ts","sourceRoot":"","sources":["../../src/utils/httpDeadline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAGH,OAAO,KAAK,EAAE,YAAY,EAA6C,MAAM,OAAO,CAAC;AAmBrF;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,CAAC,EAAE,YAAY,GAAG,YAAY,CAyEtE"}
1
+ {"version":3,"file":"httpDeadline.d.ts","sourceRoot":"","sources":["../../src/utils/httpDeadline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAGH,OAAO,KAAK,EAAE,YAAY,EAA6C,MAAM,OAAO,CAAC;AAqCrF;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,CAAC,EAAE,YAAY,GAAG,YAAY,CAkFtE"}
@@ -14,22 +14,47 @@
14
14
  * `transitional.clarifyTimeoutError`, so neither is honoured.
15
15
  * - It races the inner adapter rather than only signalling it (G148): the promise settles at the
16
16
  * deadline whether or not the inner adapter honours the abort, and the abort then releases the
17
- * inner request. A tunnelling agent's pending CONNECT socket (axios's bundled https-proxy-agent)
18
- * has no cancel hook and stays open until the proxy closes it. Whatever the inner adapter
19
- * does afterwards is ignored (settle-once).
17
+ * inner request. Whatever the inner adapter does afterwards is ignored (settle-once).
18
+ * - The abort also releases a pending proxy socket. On an `https:` request that `proxy-from-env`
19
+ * routes through a proxy, the inner adapter receives `proxy: false` and a per-request tunnel
20
+ * agent (`proxyTunnel.ts`) hooked on **this** signal, so the deadline destroys the CONNECT socket
21
+ * that axios's bundled https-proxy-agent would hold until the proxy closed it. The hook is the
22
+ * signal, not the request: Node emits no event on a request destroyed before it has a socket.
23
+ * `proxy: false` is load-bearing: without it axios's `setProxy` (`lib/adapters/http.js`) swaps
24
+ * any non-https-proxy-agent `httpsAgent` for its own tunnel. A request with no timeout has no
25
+ * signal and keeps axios's own tunnel; every client in `src/` sets a timeout.
20
26
  * - A caller's own `signal` is chained in, and a caller's cancel stays axios's `ERR_CANCELED`.
21
27
  * - The deadline timer is ref'd (G114) and cleared on every settle path, with the caller-signal
22
28
  * listener, so the timer itself holds a process for at most `timeout`.
23
29
  *
24
- * This module reads no runtime axios export at module scope or in `withRequestDeadline()` itself:
30
+ * This module reads no runtime axios export at module scope or in `withRequestDeadline()` itself
31
+ * (the `proxyTunnel.js` import is ours, not axios's):
25
32
  * thirteen unit-test mocks of `axios` expose only `create`, and they construct services that call
26
33
  * this at construction. Only the returned adapter, when a request runs, reads `axios.*`.
27
34
  *
28
35
  * Node 18 compatible: no `AbortSignal.any`, no `AbortSignal.timeout`.
29
36
  */
30
37
  import axios from 'axios';
38
+ import { createTunnelAgent, tunnelProxyFor } from './proxyTunnel.js';
31
39
  /** The abort reason that marks our own deadline, as distinct from a caller's cancel. */
32
40
  const DEADLINE = Symbol('httpDeadline.deadline');
41
+ /**
42
+ * The proxy to tunnel `config` through: an `https:` target, no caller `proxy`, and a proxy that
43
+ * `proxy-from-env` selects. Anything else, including a URL that does not parse, is no tunnel.
44
+ */
45
+ function tunnelProxy(config) {
46
+ if (config.proxy !== undefined) {
47
+ return undefined;
48
+ }
49
+ let href;
50
+ try {
51
+ href = new URL(config.url ?? '', config.baseURL).href;
52
+ }
53
+ catch {
54
+ return undefined;
55
+ }
56
+ return href.startsWith('https:') ? tunnelProxyFor(href) : undefined;
57
+ }
33
58
  function deadlineError(timeout, config, request) {
34
59
  return new axios.AxiosError('timeout of ' + timeout + 'ms exceeded', 'ECONNABORTED', config, request);
35
60
  }
@@ -77,7 +102,16 @@ export function withRequestDeadline(inner) {
77
102
  }, timeout);
78
103
  let pending;
79
104
  try {
80
- pending = adapter({ ...config, signal: controller.signal });
105
+ const proxy = tunnelProxy(config);
106
+ pending =
107
+ proxy === undefined
108
+ ? adapter({ ...config, signal: controller.signal })
109
+ : adapter({
110
+ ...config,
111
+ signal: controller.signal,
112
+ proxy: false,
113
+ httpsAgent: createTunnelAgent(proxy, controller.signal)
114
+ });
81
115
  }
82
116
  catch (err) {
83
117
  pending = Promise.reject(err);
@@ -1 +1 @@
1
- {"version":3,"file":"httpDeadline.js","sourceRoot":"","sources":["../../src/utils/httpDeadline.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"httpDeadline.js","sourceRoot":"","sources":["../../src/utils/httpDeadline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * A per-request CONNECT tunnel agent that the request deadline can cancel.
3
+ *
4
+ * Lifecycle contract (design plan `plan-proxy-socket-release`):
5
+ * - The cancel hook is the deadline's own `AbortSignal`, handed to an agent built for that one
6
+ * request — not the request. Node emits no event (`close`, `error`, `abort`, `socket`) on an
7
+ * `http.ClientRequest` destroyed before it is assigned a socket, so an agent that listens to
8
+ * the request never learns that the CONNECT it is waiting on is no longer wanted. Measured: that
9
+ * agent, axios's bundled https-proxy-agent 5, https-proxy-agent 9 and Node's native env proxy
10
+ * all leave the proxy socket open behind a proxy that never answers.
11
+ * - `createConnection` settles through its callback (the agent-base 7 shape): Node's
12
+ * `Agent.createSocket` accepts a returned socket or the callback, so its request bookkeeping is
13
+ * unchanged. Every path settles the callback exactly once.
14
+ * - A 200 answer is upgraded to TLS to the origin, as https-proxy-agent 5 does
15
+ * (`dist/agent.js:118-128`). Any other answer is replayed to the request as that HTTP response,
16
+ * on a fake socket, after the proxy socket is destroyed — hpa 5's replay (`agent.js:130-152`),
17
+ * so a 407 reaches every service's error mapper as `response.status` 407, and no origin request
18
+ * byte reaches the proxy. A proxy that closes before answering is `ECONNRESET`; a socket error
19
+ * keeps its own code (G138) under a fixed message, because Node's names the proxy endpoint.
20
+ * - No pooling: `keepAlive: false`. The tunnel it replaces sent `Connection: close` too.
21
+ * - It logs nothing and never stringifies the proxy URL, which can carry credentials. No error it
22
+ * produces names the proxy host, port or userinfo.
23
+ *
24
+ * Node 18 compatible: no `AbortSignal.any`, no `AbortSignal.timeout`.
25
+ */
26
+ import https from 'node:https';
27
+ /**
28
+ * The proxy axios would use for `url`, from the same `proxy-from-env` function axios calls
29
+ * (`HTTPS_PROXY`, `ALL_PROXY`, `NO_PROXY`, either case). `undefined` for no proxy, or for a proxy
30
+ * value that is not a URL.
31
+ */
32
+ export declare function tunnelProxyFor(url: string): URL | undefined;
33
+ /** The CONNECT request for `host:port`, with `Proxy-Authorization` when the proxy URL has userinfo. */
34
+ export declare function buildConnectRequest(host: string, port: number, proxy: URL): string;
35
+ /**
36
+ * The proxy's answer, once its header block is complete: the status code and the byte offset just
37
+ * past the blank line. `undefined` while incomplete. A first line that is not `HTTP/`, or a header
38
+ * block past 64 KiB, is `statusCode: 0` — the caller treats it as a reset.
39
+ */
40
+ export declare function parseConnectResponse(buffered: Buffer): {
41
+ statusCode: number;
42
+ headerEnd: number;
43
+ } | undefined;
44
+ /**
45
+ * An agent for one request that tunnels through `proxy`, and destroys its pending proxy socket
46
+ * when `signal` aborts.
47
+ */
48
+ export declare function createTunnelAgent(proxy: URL, signal: AbortSignal): https.Agent;
49
+ //# sourceMappingURL=proxyTunnel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxyTunnel.d.ts","sourceRoot":"","sources":["../../src/utils/proxyTunnel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,MAAM,YAAY,CAAC;AAc/B;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS,CAU3D;AAWD,uGAAuG;AACvG,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,MAAM,CAQlF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,GACf;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAYvD;AAkKD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,GAAG,KAAK,CAAC,KAAK,CAE9E"}
@@ -0,0 +1,230 @@
1
+ /**
2
+ * A per-request CONNECT tunnel agent that the request deadline can cancel.
3
+ *
4
+ * Lifecycle contract (design plan `plan-proxy-socket-release`):
5
+ * - The cancel hook is the deadline's own `AbortSignal`, handed to an agent built for that one
6
+ * request — not the request. Node emits no event (`close`, `error`, `abort`, `socket`) on an
7
+ * `http.ClientRequest` destroyed before it is assigned a socket, so an agent that listens to
8
+ * the request never learns that the CONNECT it is waiting on is no longer wanted. Measured: that
9
+ * agent, axios's bundled https-proxy-agent 5, https-proxy-agent 9 and Node's native env proxy
10
+ * all leave the proxy socket open behind a proxy that never answers.
11
+ * - `createConnection` settles through its callback (the agent-base 7 shape): Node's
12
+ * `Agent.createSocket` accepts a returned socket or the callback, so its request bookkeeping is
13
+ * unchanged. Every path settles the callback exactly once.
14
+ * - A 200 answer is upgraded to TLS to the origin, as https-proxy-agent 5 does
15
+ * (`dist/agent.js:118-128`). Any other answer is replayed to the request as that HTTP response,
16
+ * on a fake socket, after the proxy socket is destroyed — hpa 5's replay (`agent.js:130-152`),
17
+ * so a 407 reaches every service's error mapper as `response.status` 407, and no origin request
18
+ * byte reaches the proxy. A proxy that closes before answering is `ECONNRESET`; a socket error
19
+ * keeps its own code (G138) under a fixed message, because Node's names the proxy endpoint.
20
+ * - No pooling: `keepAlive: false`. The tunnel it replaces sent `Connection: close` too.
21
+ * - It logs nothing and never stringifies the proxy URL, which can carry credentials. No error it
22
+ * produces names the proxy host, port or userinfo.
23
+ *
24
+ * Node 18 compatible: no `AbortSignal.any`, no `AbortSignal.timeout`.
25
+ */
26
+ import https from 'node:https';
27
+ import net from 'node:net';
28
+ import tls from 'node:tls';
29
+ import { getProxyForUrl } from 'proxy-from-env';
30
+ /** A CONNECT answer whose header block has not ended by this many bytes is treated as a reset. */
31
+ const MAX_CONNECT_RESPONSE_BYTES = 64 * 1024;
32
+ const HEADER_TERMINATOR = '\r\n\r\n';
33
+ /**
34
+ * The proxy axios would use for `url`, from the same `proxy-from-env` function axios calls
35
+ * (`HTTPS_PROXY`, `ALL_PROXY`, `NO_PROXY`, either case). `undefined` for no proxy, or for a proxy
36
+ * value that is not a URL.
37
+ */
38
+ export function tunnelProxyFor(url) {
39
+ const raw = getProxyForUrl(url);
40
+ if (!raw) {
41
+ return undefined;
42
+ }
43
+ try {
44
+ return new URL(raw);
45
+ }
46
+ catch {
47
+ return undefined;
48
+ }
49
+ }
50
+ /** Percent-decode URL userinfo; a malformed escape (`%ZZ`) goes out literally instead of throwing. */
51
+ function decodeUserinfo(value) {
52
+ try {
53
+ return decodeURIComponent(value);
54
+ }
55
+ catch {
56
+ return value;
57
+ }
58
+ }
59
+ /** The CONNECT request for `host:port`, with `Proxy-Authorization` when the proxy URL has userinfo. */
60
+ export function buildConnectRequest(host, port, proxy) {
61
+ const authority = `${net.isIPv6(host) ? `[${host}]` : host}:${port}`;
62
+ let request = `CONNECT ${authority} HTTP/1.1\r\nHost: ${authority}\r\nConnection: close\r\n`;
63
+ if (proxy.username || proxy.password) {
64
+ const credentials = `${decodeUserinfo(proxy.username)}:${decodeUserinfo(proxy.password)}`;
65
+ request += `Proxy-Authorization: Basic ${Buffer.from(credentials).toString('base64')}\r\n`;
66
+ }
67
+ return request + '\r\n';
68
+ }
69
+ /**
70
+ * The proxy's answer, once its header block is complete: the status code and the byte offset just
71
+ * past the blank line. `undefined` while incomplete. A first line that is not `HTTP/`, or a header
72
+ * block past 64 KiB, is `statusCode: 0` — the caller treats it as a reset.
73
+ */
74
+ export function parseConnectResponse(buffered) {
75
+ const end = buffered.indexOf(HEADER_TERMINATOR);
76
+ if (end === -1) {
77
+ if (buffered.length > MAX_CONNECT_RESPONSE_BYTES) {
78
+ return { statusCode: 0, headerEnd: buffered.length };
79
+ }
80
+ return undefined;
81
+ }
82
+ const headerEnd = end + HEADER_TERMINATOR.length;
83
+ const firstLine = buffered.subarray(0, buffered.indexOf('\r\n')).toString('latin1');
84
+ const match = /^HTTP\/1\.[01] (\d{3})(?: |$)/.exec(firstLine);
85
+ return { statusCode: match ? Number(match[1]) : 0, headerEnd };
86
+ }
87
+ function abortError() {
88
+ return Object.assign(new Error('proxy CONNECT aborted'), { code: 'ECONNABORTED' });
89
+ }
90
+ function resetError(message) {
91
+ return Object.assign(new Error(message), { code: 'ECONNRESET' });
92
+ }
93
+ class TunnelAgent extends https.Agent {
94
+ proxy;
95
+ signal;
96
+ request;
97
+ constructor(proxy, signal) {
98
+ super({ keepAlive: false, maxSockets: Infinity });
99
+ this.proxy = proxy;
100
+ this.signal = signal;
101
+ }
102
+ /** Stash the request: a non-200 answer is replayed onto it, and `createConnection` never sees it. */
103
+ addRequest(req, ...rest) {
104
+ this.request = req;
105
+ https.Agent.prototype.addRequest.call(this, req, ...rest);
106
+ }
107
+ createConnection(options, callback) {
108
+ // Node's callback reads the stream only when `err` is null.
109
+ const settle = (err, stream) => {
110
+ callback?.(err, stream);
111
+ };
112
+ const { proxy, signal } = this;
113
+ const req = this.request;
114
+ const originHost = options.host ?? 'localhost';
115
+ const originPort = Number(options.port ?? 443);
116
+ if (signal.aborted) {
117
+ settle(abortError());
118
+ return undefined;
119
+ }
120
+ // URL.hostname keeps IPv6 brackets (G109); a socket dial needs the bare address.
121
+ const proxyHost = proxy.hostname.replace(/^\[|\]$/g, '');
122
+ const proxyPort = Number(proxy.port || (proxy.protocol === 'https:' ? 443 : 80));
123
+ const secureProxy = proxy.protocol === 'https:';
124
+ const socket = secureProxy
125
+ ? tls.connect({
126
+ host: proxyHost,
127
+ port: proxyPort,
128
+ servername: net.isIP(proxyHost) ? undefined : proxyHost
129
+ })
130
+ : net.connect({ host: proxyHost, port: proxyPort });
131
+ let done = false;
132
+ let buffered = Buffer.alloc(0);
133
+ const detach = () => {
134
+ signal.removeEventListener('abort', onAbort);
135
+ socket.removeListener('readable', onReadable);
136
+ socket.removeListener('error', onError);
137
+ socket.removeListener('close', onClose);
138
+ socket.removeListener('end', onClose);
139
+ socket.removeListener(secureProxy ? 'secureConnect' : 'connect', onConnect);
140
+ };
141
+ const fail = (err) => {
142
+ if (done)
143
+ return;
144
+ done = true;
145
+ detach();
146
+ // A late error from the destroyed socket must not become an uncaught exception.
147
+ socket.on('error', () => undefined);
148
+ socket.destroy();
149
+ settle(err);
150
+ };
151
+ // Attached in the same synchronous run as the dial (G20): an abort during connect setup lands.
152
+ function onAbort() {
153
+ fail(abortError());
154
+ }
155
+ // Node's dial error names the proxy host and port: keep its code (G138), never its message.
156
+ function onError(err) {
157
+ fail(Object.assign(new Error('proxy connection failed'), { code: err.code }));
158
+ }
159
+ function onClose() {
160
+ fail(resetError('proxy closed before answering CONNECT'));
161
+ }
162
+ function onConnect() {
163
+ socket.write(buildConnectRequest(originHost, originPort, proxy));
164
+ }
165
+ function onReadable() {
166
+ let chunk;
167
+ while ((chunk = socket.read()) !== null) {
168
+ buffered = Buffer.concat([buffered, chunk]);
169
+ }
170
+ const answer = parseConnectResponse(buffered);
171
+ if (answer === undefined || done) {
172
+ return;
173
+ }
174
+ if (answer.statusCode === 0) {
175
+ fail(resetError('proxy sent an unreadable CONNECT answer'));
176
+ return;
177
+ }
178
+ done = true;
179
+ detach();
180
+ if (answer.statusCode === 200) {
181
+ // The request owns the socket from here, and axios's own abort path destroys it.
182
+ const rest = buffered.subarray(answer.headerEnd);
183
+ if (rest.length > 0) {
184
+ socket.unshift(rest);
185
+ }
186
+ // Every caller TLS option rides through; the origin's address is the tunnel, not a dial.
187
+ const tlsOptions = { ...options };
188
+ for (const key of ['host', 'hostname', 'path', 'port']) {
189
+ delete tlsOptions[key];
190
+ }
191
+ // Truthy fallback, as hpa 5 (`agent.js:124`): Node sets servername '' for an IP origin,
192
+ // and an empty name would let TLS identity fall through to the proxy's host.
193
+ const servername = options.servername || originHost;
194
+ settle(null, tls.connect({ ...tlsOptions, socket, servername }));
195
+ return;
196
+ }
197
+ // Any other answer: destroy the proxy socket before a request byte is written, then replay
198
+ // the proxy's own response to the request so its status reaches axios.
199
+ socket.on('error', () => undefined);
200
+ socket.destroy();
201
+ if (req === undefined) {
202
+ settle(resetError('proxy refused CONNECT'));
203
+ return;
204
+ }
205
+ const response = buffered;
206
+ const fake = new net.Socket({ writable: false });
207
+ fake.readable = true;
208
+ req.once('socket', (s) => {
209
+ s.push(response);
210
+ s.push(null);
211
+ });
212
+ settle(null, fake);
213
+ }
214
+ signal.addEventListener('abort', onAbort, { once: true });
215
+ socket.on('error', onError);
216
+ socket.on('close', onClose);
217
+ socket.on('end', onClose);
218
+ socket.on(secureProxy ? 'secureConnect' : 'connect', onConnect);
219
+ socket.on('readable', onReadable);
220
+ return undefined;
221
+ }
222
+ }
223
+ /**
224
+ * An agent for one request that tunnels through `proxy`, and destroys its pending proxy socket
225
+ * when `signal` aborts.
226
+ */
227
+ export function createTunnelAgent(proxy, signal) {
228
+ return new TunnelAgent(proxy, signal);
229
+ }
230
+ //# sourceMappingURL=proxyTunnel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxyTunnel.js","sourceRoot":"","sources":["../../src/utils/proxyTunnel.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dangahagan/weather-mcp",
3
- "version": "1.35.3",
3
+ "version": "1.35.4",
4
4
  "description": "MCP server giving AI assistants 17 weather tools with zero API keys: global forecasts, current conditions, alerts, air quality, marine conditions, lightning detection, radar imagery, river levels, wildfire tracking, and historical weather back to 1940. Built entirely on free public APIs (NOAA, Open-Meteo, USGS, NIFC).",
5
5
  "mcpName": "io.github.dgahagan/weather-mcp",
6
6
  "main": "dist/index.js",
@@ -83,6 +83,7 @@
83
83
  "jose": "^6.1.3",
84
84
  "luxon": "^3.7.2",
85
85
  "pngjs": "^7.0.0",
86
+ "proxy-from-env": "^2.1.0",
86
87
  "tz-lookup": "6.1.25"
87
88
  },
88
89
  "optionalDependencies": {
@@ -98,6 +99,7 @@
98
99
  "@types/ngeohash": "^0.6.8",
99
100
  "@types/node": "^26.6.5",
100
101
  "@types/pngjs": "^6.0.5",
102
+ "@types/proxy-from-env": "^1.0.4",
101
103
  "@types/tz-lookup": "^6.1.2",
102
104
  "@typescript/native": "npm:typescript@^7.0.2",
103
105
  "@vitest/coverage-v8": "^5.0.3",