weather-agent-mcp 0.1.5 → 0.1.6

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
@@ -58,7 +58,7 @@ sudo launchctl config user path "$HOME/.volta/bin:/usr/bin:/bin:/usr/sbin:/sbin"
58
58
 
59
59
  Без `sudo`: можно `launchctl setenv PATH "$HOME/.volta/bin:/usr/bin:/bin:/usr/sbin:/sbin"` и перезапустить Cursor (сбросится после reboot).
60
60
 
61
- Reload MCP. Tools: `geocode`, `getCurrentWeather`, `getForecast`, `getHistoricalWeather`, `compareWeather`.
61
+ Reload MCP. Tools: `geocode`, `getCurrentWeather`, `getForecast`, `getHistoricalWeather`, `compareWeather`. Для ряда температур (прогноз, архив одного дня по годам, сравнение) к ответу прилагается PNG-график.
62
62
 
63
63
  ### GitHub Copilot / VS Code
64
64
 
@@ -25217,12 +25217,12 @@ var ReadBuffer = class {
25217
25217
  constructor(options) {
25218
25218
  this._maxBufferSize = options?.maxBufferSize ?? STDIO_DEFAULT_MAX_BUFFER_SIZE;
25219
25219
  }
25220
- append(chunk) {
25221
- if ((this._buffer?.length ?? 0) + chunk.length > this._maxBufferSize) {
25220
+ append(chunk2) {
25221
+ if ((this._buffer?.length ?? 0) + chunk2.length > this._maxBufferSize) {
25222
25222
  this.clear();
25223
25223
  throw new Error(`ReadBuffer exceeded maximum size of ${this._maxBufferSize} bytes`);
25224
25224
  }
25225
- this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk;
25225
+ this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk2]) : chunk2;
25226
25226
  }
25227
25227
  readMessage() {
25228
25228
  while (this._buffer) {
@@ -33905,9 +33905,9 @@ var StdioServerTransport = class {
33905
33905
  onclose;
33906
33906
  onerror;
33907
33907
  onmessage;
33908
- _ondata = (chunk) => {
33908
+ _ondata = (chunk2) => {
33909
33909
  try {
33910
- this._readBuffer.append(chunk);
33910
+ this._readBuffer.append(chunk2);
33911
33911
  this.processReadBuffer();
33912
33912
  } catch (error62) {
33913
33913
  this.onerror?.(error62);
@@ -34956,6 +34956,293 @@ async function compareWeather(locations, startDate, endDate, config2 = {}) {
34956
34956
  return { startDate, endDate, locations: results };
34957
34957
  }
34958
34958
 
34959
+ // src/weather/temperature-chart.ts
34960
+ import { deflateSync } from "node:zlib";
34961
+ var CHART_WIDTH = 720;
34962
+ var CHART_HEIGHT = 360;
34963
+ var BG = [247, 244, 239];
34964
+ var PLOT = [255, 252, 247];
34965
+ var GRID = [220, 214, 204];
34966
+ var INK = [70, 64, 56];
34967
+ var SERIES_COLORS = [
34968
+ [196, 90, 58],
34969
+ [47, 111, 163],
34970
+ [62, 122, 90],
34971
+ [140, 90, 160],
34972
+ [176, 132, 48]
34973
+ ];
34974
+ var PAD_L = 56;
34975
+ var PAD_R = 20;
34976
+ var PAD_T = 40;
34977
+ var PAD_B = 44;
34978
+ function pickAxisLabels(count, maxLabels = 6) {
34979
+ if (count <= 0) return [];
34980
+ if (count <= maxLabels) return Array.from({ length: count }, (_, i) => i);
34981
+ const indices = /* @__PURE__ */ new Set([0, count - 1]);
34982
+ const inner = maxLabels - 1;
34983
+ for (let i = 1; i < inner; i++) {
34984
+ indices.add(Math.round(i * (count - 1) / inner));
34985
+ }
34986
+ return [...indices].sort((a, b) => a - b);
34987
+ }
34988
+ function renderTemperatureChart(labels, series) {
34989
+ if (labels.length < 2 || series.length === 0) return null;
34990
+ const aligned = series.map((s) => ({ name: s.name, values: s.values.slice(0, labels.length) })).filter((s) => finiteCount(s.values) >= 2);
34991
+ if (aligned.length === 0) return null;
34992
+ const finite = aligned.flatMap((s) => s.values.filter((v) => isFiniteNumber(v)));
34993
+ let yMin = Math.min(...finite);
34994
+ let yMax = Math.max(...finite);
34995
+ if (yMax - yMin < 1) {
34996
+ yMin -= 1;
34997
+ yMax += 1;
34998
+ } else {
34999
+ const pad = (yMax - yMin) * 0.08;
35000
+ yMin -= pad;
35001
+ yMax += pad;
35002
+ }
35003
+ const pixels = new Uint8Array(CHART_WIDTH * CHART_HEIGHT * 3);
35004
+ fill(pixels, BG);
35005
+ const plotW = CHART_WIDTH - PAD_L - PAD_R;
35006
+ const plotH = CHART_HEIGHT - PAD_T - PAD_B;
35007
+ fillRect(pixels, PAD_L, PAD_T, plotW, plotH, PLOT);
35008
+ const yTicks = 4;
35009
+ for (let t = 0; t <= yTicks; t++) {
35010
+ const y = PAD_T + Math.round(plotH * t / yTicks);
35011
+ hLine(pixels, PAD_L, PAD_L + plotW, y, GRID);
35012
+ const value = yMax - (yMax - yMin) * t / yTicks;
35013
+ drawText(pixels, formatTemp(value), 4, y - 4, INK, 1);
35014
+ }
35015
+ vLine(pixels, PAD_L, PAD_T, PAD_T + plotH, INK);
35016
+ hLine(pixels, PAD_L, PAD_L + plotW, PAD_T + plotH, INK);
35017
+ const xIndex = (i) => PAD_L + Math.round(i * plotW / Math.max(labels.length - 1, 1));
35018
+ const yOf = (v) => PAD_T + Math.round(plotH * (1 - (v - yMin) / (yMax - yMin)));
35019
+ for (const index of pickAxisLabels(labels.length)) {
35020
+ const x = xIndex(index);
35021
+ vLine(pixels, x, PAD_T + plotH, PAD_T + plotH + 4, INK);
35022
+ const caption = labels[index] ?? "";
35023
+ drawText(pixels, caption, x - textWidth(caption, 1) / 2, PAD_T + plotH + 8, INK, 1);
35024
+ }
35025
+ aligned.forEach((s, seriesIndex) => {
35026
+ const color = SERIES_COLORS[seriesIndex % SERIES_COLORS.length];
35027
+ let prev = null;
35028
+ for (let i = 0; i < labels.length; i++) {
35029
+ const value = s.values[i];
35030
+ if (!isFiniteNumber(value)) {
35031
+ prev = null;
35032
+ continue;
35033
+ }
35034
+ const point = { x: xIndex(i), y: yOf(value) };
35035
+ if (prev) drawLine(pixels, prev.x, prev.y, point.x, point.y, color);
35036
+ fillRect(pixels, point.x - 2, point.y - 2, 5, 5, color);
35037
+ prev = point;
35038
+ }
35039
+ });
35040
+ let legendX = PAD_L;
35041
+ for (let i = 0; i < aligned.length; i++) {
35042
+ const color = SERIES_COLORS[i % SERIES_COLORS.length];
35043
+ const name = aligned[i].name;
35044
+ fillRect(pixels, legendX, 14, 14, 8, color);
35045
+ drawText(pixels, name, legendX + 18, 12, INK, 2);
35046
+ legendX += 18 + textWidth(name, 2) + 16;
35047
+ }
35048
+ return encodePng(pixels, CHART_WIDTH, CHART_HEIGHT);
35049
+ }
35050
+ function isFiniteNumber(value) {
35051
+ return typeof value === "number" && Number.isFinite(value);
35052
+ }
35053
+ function finiteCount(values) {
35054
+ return values.filter((v) => isFiniteNumber(v)).length;
35055
+ }
35056
+ function formatTemp(value) {
35057
+ const rounded = Math.round(value);
35058
+ return `${rounded}`;
35059
+ }
35060
+ function fill(pixels, color) {
35061
+ for (let i = 0; i < pixels.length; i += 3) {
35062
+ pixels[i] = color[0];
35063
+ pixels[i + 1] = color[1];
35064
+ pixels[i + 2] = color[2];
35065
+ }
35066
+ }
35067
+ function setPixel(pixels, x, y, color) {
35068
+ if (x < 0 || y < 0 || x >= CHART_WIDTH || y >= CHART_HEIGHT) return;
35069
+ const i = (y * CHART_WIDTH + x) * 3;
35070
+ pixels[i] = color[0];
35071
+ pixels[i + 1] = color[1];
35072
+ pixels[i + 2] = color[2];
35073
+ }
35074
+ function fillRect(pixels, x, y, w, h, color) {
35075
+ for (let yy = y; yy < y + h; yy++) {
35076
+ for (let xx = x; xx < x + w; xx++) setPixel(pixels, xx, yy, color);
35077
+ }
35078
+ }
35079
+ function hLine(pixels, x0, x1, y, color) {
35080
+ const from = Math.min(x0, x1);
35081
+ const to = Math.max(x0, x1);
35082
+ for (let x = from; x <= to; x++) setPixel(pixels, x, y, color);
35083
+ }
35084
+ function vLine(pixels, x, y0, y1, color) {
35085
+ const from = Math.min(y0, y1);
35086
+ const to = Math.max(y0, y1);
35087
+ for (let y = from; y <= to; y++) setPixel(pixels, x, y, color);
35088
+ }
35089
+ function drawLine(pixels, x0, y0, x1, y1, color) {
35090
+ let x = x0;
35091
+ let y = y0;
35092
+ const dx = Math.abs(x1 - x0);
35093
+ const sx = x0 < x1 ? 1 : -1;
35094
+ const dy = -Math.abs(y1 - y0);
35095
+ const sy = y0 < y1 ? 1 : -1;
35096
+ let err = dx + dy;
35097
+ for (; ; ) {
35098
+ fillRect(pixels, x - 1, y - 1, 3, 3, color);
35099
+ if (x === x1 && y === y1) break;
35100
+ const e2 = 2 * err;
35101
+ if (e2 >= dy) {
35102
+ err += dy;
35103
+ x += sx;
35104
+ }
35105
+ if (e2 <= dx) {
35106
+ err += dx;
35107
+ y += sy;
35108
+ }
35109
+ }
35110
+ }
35111
+ var FONT = {
35112
+ " ": [0, 0, 0, 0, 0, 0, 0],
35113
+ "-": [0, 0, 0, 31, 0, 0, 0],
35114
+ ".": [0, 0, 0, 0, 0, 12, 12],
35115
+ "/": [1, 2, 4, 8, 16, 0, 0],
35116
+ "0": [14, 17, 19, 21, 25, 17, 14],
35117
+ "1": [4, 12, 4, 4, 4, 4, 14],
35118
+ "2": [14, 17, 1, 2, 4, 8, 31],
35119
+ "3": [30, 1, 1, 14, 1, 1, 30],
35120
+ "4": [2, 6, 10, 18, 31, 2, 2],
35121
+ "5": [31, 16, 16, 30, 1, 1, 30],
35122
+ "6": [14, 16, 16, 30, 17, 17, 14],
35123
+ "7": [31, 1, 2, 4, 8, 8, 8],
35124
+ "8": [14, 17, 17, 14, 17, 17, 14],
35125
+ "9": [14, 17, 17, 15, 1, 1, 14],
35126
+ A: [14, 17, 17, 31, 17, 17, 17],
35127
+ B: [30, 17, 17, 30, 17, 17, 30],
35128
+ C: [14, 17, 16, 16, 16, 17, 14],
35129
+ D: [30, 17, 17, 17, 17, 17, 30],
35130
+ E: [31, 16, 16, 30, 16, 16, 31],
35131
+ F: [31, 16, 16, 30, 16, 16, 16],
35132
+ G: [14, 17, 16, 23, 17, 17, 14],
35133
+ H: [17, 17, 17, 31, 17, 17, 17],
35134
+ I: [14, 4, 4, 4, 4, 4, 14],
35135
+ J: [7, 2, 2, 2, 2, 18, 12],
35136
+ K: [17, 18, 20, 24, 20, 18, 17],
35137
+ L: [16, 16, 16, 16, 16, 16, 31],
35138
+ M: [17, 27, 21, 21, 17, 17, 17],
35139
+ N: [17, 25, 21, 19, 17, 17, 17],
35140
+ O: [14, 17, 17, 17, 17, 17, 14],
35141
+ P: [30, 17, 17, 30, 16, 16, 16],
35142
+ Q: [14, 17, 17, 17, 21, 18, 13],
35143
+ R: [30, 17, 17, 30, 20, 18, 17],
35144
+ S: [15, 16, 16, 14, 1, 1, 30],
35145
+ T: [31, 4, 4, 4, 4, 4, 4],
35146
+ U: [17, 17, 17, 17, 17, 17, 14],
35147
+ V: [17, 17, 17, 17, 17, 10, 4],
35148
+ W: [17, 17, 17, 21, 21, 21, 10],
35149
+ X: [17, 17, 10, 4, 10, 17, 17],
35150
+ Y: [17, 17, 10, 4, 4, 4, 4],
35151
+ Z: [31, 1, 2, 4, 8, 16, 31],
35152
+ a: [0, 0, 14, 1, 15, 17, 15],
35153
+ b: [16, 16, 30, 17, 17, 17, 30],
35154
+ c: [0, 0, 14, 16, 16, 17, 14],
35155
+ d: [1, 1, 15, 17, 17, 17, 15],
35156
+ e: [0, 0, 14, 17, 31, 16, 14],
35157
+ f: [6, 8, 28, 8, 8, 8, 8],
35158
+ g: [0, 0, 15, 17, 15, 1, 14],
35159
+ h: [16, 16, 22, 25, 17, 17, 17],
35160
+ i: [4, 0, 12, 4, 4, 4, 14],
35161
+ j: [2, 0, 6, 2, 2, 18, 12],
35162
+ k: [16, 16, 18, 20, 24, 20, 18],
35163
+ l: [12, 4, 4, 4, 4, 4, 14],
35164
+ m: [0, 0, 26, 21, 21, 17, 17],
35165
+ n: [0, 0, 22, 25, 17, 17, 17],
35166
+ o: [0, 0, 14, 17, 17, 17, 14],
35167
+ p: [0, 0, 30, 17, 30, 16, 16],
35168
+ q: [0, 0, 15, 17, 15, 1, 1],
35169
+ r: [0, 0, 22, 25, 16, 16, 16],
35170
+ s: [0, 0, 15, 16, 14, 1, 30],
35171
+ t: [8, 8, 28, 8, 8, 9, 6],
35172
+ u: [0, 0, 17, 17, 17, 19, 13],
35173
+ v: [0, 0, 17, 17, 17, 10, 4],
35174
+ w: [0, 0, 17, 17, 21, 21, 10],
35175
+ x: [0, 0, 17, 10, 4, 10, 17],
35176
+ y: [0, 0, 17, 17, 15, 1, 14],
35177
+ z: [0, 0, 31, 2, 4, 8, 31]
35178
+ };
35179
+ function glyph(ch) {
35180
+ return FONT[ch];
35181
+ }
35182
+ function textWidth(text, scale) {
35183
+ let width = 0;
35184
+ for (const ch of text) {
35185
+ if (!glyph(ch)) continue;
35186
+ width += 6 * scale;
35187
+ }
35188
+ return width;
35189
+ }
35190
+ function drawText(pixels, text, x, y, color, scale) {
35191
+ let cursor = Math.round(x);
35192
+ for (const ch of text) {
35193
+ const rows = glyph(ch);
35194
+ if (!rows) continue;
35195
+ for (let row = 0; row < rows.length; row++) {
35196
+ const bits = rows[row] ?? 0;
35197
+ for (let col = 0; col < 5; col++) {
35198
+ if ((bits & 1 << 4 - col) === 0) continue;
35199
+ fillRect(pixels, cursor + col * scale, y + row * scale, scale, scale, color);
35200
+ }
35201
+ }
35202
+ cursor += 6 * scale;
35203
+ }
35204
+ }
35205
+ function encodePng(pixels, width, height) {
35206
+ const stride = width * 3;
35207
+ const raw = Buffer.alloc((stride + 1) * height);
35208
+ for (let y = 0; y < height; y++) {
35209
+ const offset = y * (stride + 1);
35210
+ raw[offset] = 0;
35211
+ Buffer.from(pixels.buffer, pixels.byteOffset + y * stride, stride).copy(raw, offset + 1);
35212
+ }
35213
+ const compressed = deflateSync(raw);
35214
+ const ihdr = Buffer.alloc(13);
35215
+ ihdr.writeUInt32BE(width, 0);
35216
+ ihdr.writeUInt32BE(height, 4);
35217
+ ihdr[8] = 8;
35218
+ ihdr[9] = 2;
35219
+ return Buffer.concat([
35220
+ Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
35221
+ chunk("IHDR", ihdr),
35222
+ chunk("IDAT", compressed),
35223
+ chunk("IEND", Buffer.alloc(0))
35224
+ ]);
35225
+ }
35226
+ function chunk(type, data) {
35227
+ const length = Buffer.alloc(4);
35228
+ length.writeUInt32BE(data.length, 0);
35229
+ const name = Buffer.from(type, "ascii");
35230
+ const crc = Buffer.alloc(4);
35231
+ crc.writeUInt32BE(crc32(Buffer.concat([name, data])), 0);
35232
+ return Buffer.concat([length, name, data, crc]);
35233
+ }
35234
+ function crc32(data) {
35235
+ let crc = 4294967295;
35236
+ for (const byte of data) {
35237
+ crc ^= byte;
35238
+ for (let i = 0; i < 8; i++) {
35239
+ const mask = -(crc & 1);
35240
+ crc = crc >>> 1 ^ 3988292384 & mask;
35241
+ }
35242
+ }
35243
+ return (crc ^ 4294967295) >>> 0;
35244
+ }
35245
+
34959
35246
  // src/mcp-server/error-envelope.ts
34960
35247
  function toErrorText(err) {
34961
35248
  const envelope = isAppError(err) ? err.toEnvelope() : {
@@ -34965,9 +35252,13 @@ function toErrorText(err) {
34965
35252
  };
34966
35253
  return JSON.stringify(envelope);
34967
35254
  }
34968
- function okResult(output2) {
35255
+ function okResult(output2, image) {
35256
+ const content = [{ type: "text", text: JSON.stringify(output2) }];
35257
+ if (image) {
35258
+ content.push({ type: "image", data: image.data, mimeType: image.mimeType });
35259
+ }
34969
35260
  return {
34970
- content: [{ type: "text", text: JSON.stringify(output2) }],
35261
+ content,
34971
35262
  structuredContent: output2
34972
35263
  };
34973
35264
  }
@@ -35157,7 +35448,7 @@ function createWeatherMcpServer() {
35157
35448
  async ({ latitude, longitude, startDate, endDate }) => {
35158
35449
  try {
35159
35450
  const result = await getForecast(latitude, longitude, startDate, endDate);
35160
- return okResult(result);
35451
+ return okResult(result, chartForForecast(result));
35161
35452
  } catch (err) {
35162
35453
  return errResult(err);
35163
35454
  }
@@ -35181,7 +35472,7 @@ function createWeatherMcpServer() {
35181
35472
  startYear,
35182
35473
  endYear
35183
35474
  );
35184
- return okResult(result);
35475
+ return okResult(result, chartForHistory(result));
35185
35476
  } catch (err) {
35186
35477
  return errResult(err);
35187
35478
  }
@@ -35198,7 +35489,7 @@ function createWeatherMcpServer() {
35198
35489
  async ({ locations, startDate, endDate }) => {
35199
35490
  try {
35200
35491
  const result = await compareWeather(locations, startDate, endDate);
35201
- return okResult(result);
35492
+ return okResult(result, chartForCompare(result));
35202
35493
  } catch (err) {
35203
35494
  return errResult(err);
35204
35495
  }
@@ -35206,6 +35497,50 @@ function createWeatherMcpServer() {
35206
35497
  );
35207
35498
  return server;
35208
35499
  }
35500
+ function pngBlock(png) {
35501
+ if (!png) return void 0;
35502
+ return { data: png.toString("base64"), mimeType: "image/png" };
35503
+ }
35504
+ function shortDate(iso) {
35505
+ return iso.slice(5);
35506
+ }
35507
+ function chartForForecast(result) {
35508
+ return pngBlock(
35509
+ renderTemperatureChart(
35510
+ result.days.map((day) => shortDate(day.date)),
35511
+ [
35512
+ { name: "min", values: result.days.map((day) => day.tempMinC) },
35513
+ { name: "max", values: result.days.map((day) => day.tempMaxC) }
35514
+ ]
35515
+ )
35516
+ );
35517
+ }
35518
+ function chartForHistory(result) {
35519
+ return pngBlock(
35520
+ renderTemperatureChart(
35521
+ result.years.map((year) => String(year.year)),
35522
+ [
35523
+ { name: "min", values: result.years.map((year) => year.tempMinC) },
35524
+ { name: "mean", values: result.years.map((year) => year.tempMeanC) },
35525
+ { name: "max", values: result.years.map((year) => year.tempMaxC) }
35526
+ ]
35527
+ )
35528
+ );
35529
+ }
35530
+ function chartForCompare(result) {
35531
+ const labels = result.locations[0]?.forecast.days.map((day) => day.date) ?? [];
35532
+ return pngBlock(
35533
+ renderTemperatureChart(
35534
+ labels.map(shortDate),
35535
+ result.locations.map((location) => ({
35536
+ name: location.name,
35537
+ values: labels.map(
35538
+ (date5) => location.forecast.days.find((day) => day.date === date5)?.tempMaxC ?? null
35539
+ )
35540
+ }))
35541
+ )
35542
+ );
35543
+ }
35209
35544
 
35210
35545
  // src/mcp-server/index.ts
35211
35546
  void serveStdio(() => createWeatherMcpServer(), {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weather-agent-mcp",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Model-agnostic AI agent with MCP tools over Open-Meteo",
5
5
  "type": "module",
6
6
  "engines": {