@cyanheads/exchange-rates-mcp-server 0.3.2 → 0.4.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/AGENTS.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** exchange-rates-mcp-server
4
- **Version:** 0.3.2
4
+ **Version:** 0.4.0
5
5
  **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `0.12.8`
6
6
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0
@@ -246,14 +246,16 @@ src/
246
246
  fx-convert-currency.tool.ts # Convert amount between two currencies
247
247
  fx-get-rate.tool.ts # Exchange rate for a currency pair
248
248
  fx-get-rates.tool.ts # All rates for a base currency snapshot
249
- fx-get-timeseries.tool.ts # Historical daily rates; spills to DataCanvas for long ranges
249
+ fx-get-timeseries.tool.ts # Historical daily rates; inline pages of 500 days, spills to DataCanvas for long ranges
250
250
  fx-list-currencies.tool.ts # All supported ISO 4217 currencies
251
251
  fx-dataframe-describe.tool.ts # List DataCanvas tables from a prior timeseries call
252
- fx-dataframe-query.tool.ts # SQL SELECT against DataCanvas tables
252
+ fx-dataframe-query.tool.ts # SQL SELECT against DataCanvas tables, bounded by row_limit
253
253
  fx-dataframe-drop.tool.ts # Remove one staged table; opt-in via FX_ENABLE_CANVAS_DROP
254
254
  resources/definitions/
255
255
  fx-currencies.resource.ts # fx://currencies — stable currencies reference document
256
256
  fx-rates-latest.resource.ts # fx://rates/latest/{base} — latest rates snapshot
257
+ utils/
258
+ escape-markdown-table-cell.ts # Escapes a value for one GFM table cell (fx_dataframe_query format())
257
259
  ```
258
260
 
259
261
  ---
package/CLAUDE.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** exchange-rates-mcp-server
4
- **Version:** 0.3.2
4
+ **Version:** 0.4.0
5
5
  **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `0.12.8`
6
6
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0
@@ -246,14 +246,16 @@ src/
246
246
  fx-convert-currency.tool.ts # Convert amount between two currencies
247
247
  fx-get-rate.tool.ts # Exchange rate for a currency pair
248
248
  fx-get-rates.tool.ts # All rates for a base currency snapshot
249
- fx-get-timeseries.tool.ts # Historical daily rates; spills to DataCanvas for long ranges
249
+ fx-get-timeseries.tool.ts # Historical daily rates; inline pages of 500 days, spills to DataCanvas for long ranges
250
250
  fx-list-currencies.tool.ts # All supported ISO 4217 currencies
251
251
  fx-dataframe-describe.tool.ts # List DataCanvas tables from a prior timeseries call
252
- fx-dataframe-query.tool.ts # SQL SELECT against DataCanvas tables
252
+ fx-dataframe-query.tool.ts # SQL SELECT against DataCanvas tables, bounded by row_limit
253
253
  fx-dataframe-drop.tool.ts # Remove one staged table; opt-in via FX_ENABLE_CANVAS_DROP
254
254
  resources/definitions/
255
255
  fx-currencies.resource.ts # fx://currencies — stable currencies reference document
256
256
  fx-rates-latest.resource.ts # fx://rates/latest/{base} — latest rates snapshot
257
+ utils/
258
+ escape-markdown-table-cell.ts # Escapes a value for one GFM table cell (fx_dataframe_query format())
257
259
  ```
258
260
 
259
261
  ---
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-0.3.2-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/exchange-rates-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/exchange-rates-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/exchange-rates-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.0-blueviolet.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-0.4.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/exchange-rates-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/exchange-rates-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/exchange-rates-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.0-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
12
12
  </div>
13
13
 
@@ -31,7 +31,7 @@
31
31
 
32
32
  Eight tools for working with ECB FX rate data — currency lookup and disambiguation, point-in-time rates and conversions, historical time-series retrieval, and SQL analytics over the DataCanvas workspace that long time-series calls produce. Five are advertised by default; the three `fx_dataframe_*` tools need `CANVAS_PROVIDER_TYPE=duckdb`, and the destructive one among them additionally needs `FX_ENABLE_CANVAS_DROP=true`.
33
33
 
34
- The three `fx_dataframe_*` tools require DataCanvas. With `CANVAS_PROVIDER_TYPE` unset (the default) they are not advertised in `tools/list` at all, so a client never sees a tool it cannot call; the HTTP landing page still lists them as disabled cards hinting `CANVAS_PROVIDER_TYPE=duckdb`, so operators can tell they exist. In that mode `fx_get_timeseries` returns every range inline:
34
+ The three `fx_dataframe_*` tools require DataCanvas. With `CANVAS_PROVIDER_TYPE` unset (the default) they are not advertised in `tools/list` at all, so a client never sees a tool it cannot call; the HTTP landing page still lists them as disabled cards hinting `CANVAS_PROVIDER_TYPE=duckdb`, so operators can tell they exist. In that mode `fx_get_timeseries` returns every range inline, paged at 500 publication days:
35
35
 
36
36
  | Tool | Description |
37
37
  |:-----|:------------|
@@ -39,9 +39,9 @@ The three `fx_dataframe_*` tools require DataCanvas. With `CANVAS_PROVIDER_TYPE`
39
39
  | `fx_get_rates` | Snapshot of all available rates for a base currency at latest or a historical date. Surfaces `date_snapped` when a weekend/holiday request returns the prior business-day snapshot. Optional `symbols` filter for smaller responses; listing the base itself returns a rate of 1 for it. |
40
40
  | `fx_get_rate` | Exchange rate for a single currency pair at latest or a historical date. Surfaces `date_snapped` when a weekend/holiday request returns the prior business-day rate. |
41
41
  | `fx_convert_currency` | Convert an amount between any two currencies at latest or a historical rate. Cross-rates are triangulated through EUR. Returns converted amount, rate used, rate date, and whether the date was snapped. |
42
- | `fx_get_timeseries` | Historical daily rates for a currency pair over a date range, never including a date outside it. Short ranges (≤90 days) are returned inline; when DataCanvas is enabled, long ranges spill to it with a `canvas_id` for SQL follow-up. |
42
+ | `fx_get_timeseries` | Historical daily rates for a currency pair over a date range, never including a date outside it. Inline results come back in pages of 500 publication days, continued with `next_start_date`; when DataCanvas is enabled, long ranges (>90 days) spill to it with a `canvas_id` for SQL follow-up instead. |
43
43
  | `fx_dataframe_describe` | List DataCanvas tables and their columns from a prior `fx_get_timeseries` call. Required first step before `fx_dataframe_query`. Needs `CANVAS_PROVIDER_TYPE=duckdb`. |
44
- | `fx_dataframe_query` | Run a read-only SQL SELECT against a DataCanvas table produced by `fx_get_timeseries`. Supports aggregations, GROUP BY, window functions, and JOINs across multiple registered tables. Needs `CANVAS_PROVIDER_TYPE=duckdb`. |
44
+ | `fx_dataframe_query` | Run a read-only SQL SELECT against a DataCanvas table produced by `fx_get_timeseries`. Supports aggregations, GROUP BY, window functions, and JOINs across multiple registered tables. Returns at most `row_limit` rows (default 150, max 10,000). Needs `CANVAS_PROVIDER_TYPE=duckdb`. |
45
45
  | `fx_dataframe_drop` | Permanently remove one staged table or view from a DataCanvas. Deletes staged analytical data only — ECB rate data is untouched and the series can be re-staged. Needs `CANVAS_PROVIDER_TYPE=duckdb` and `FX_ENABLE_CANVAS_DROP=true`; disabled otherwise. |
46
46
 
47
47
  ### `fx_list_currencies`
@@ -92,7 +92,9 @@ Historical rate series and DataCanvas SQL analytics.
92
92
  `fx_get_timeseries` returns a date-keyed series (business days only — ECB publishes once per business day):
93
93
 
94
94
  - Short ranges (≤ `FX_TIMESERIES_CANVAS_THRESHOLD_DAYS`, default 90 days) → inline `rates` map + metadata
95
- - Long ranges, **when DataCanvas is enabled** → first N rows inline + `canvas_id`, `table_name`, and `spilled: true` the full series is registered as a DuckDB-backed table. Without `CANVAS_PROVIDER_TYPE=duckdb` a long range comes back inline with `spilled: false` and a `notice` saying the threshold was crossed but no canvas was configured
95
+ - Long ranges, **when DataCanvas is enabled** → a preview inline + `canvas_id`, `table_name`, `spilled: true`, and a `notice` naming `fx_dataframe_describe` then `fx_dataframe_query` the full series is registered as a DuckDB-backed table
96
+ - Long ranges **without DataCanvas** → returned inline with `spilled: false`, paged (below), and a `notice` saying the threshold was crossed but no canvas was configured
97
+ - Inline results are paged at 500 publication days. `rate_count` is always the total for the requested range; when a page is cut short the response carries `truncated: true` and `next_start_date`. Call again with `start_date` set to `next_start_date` and the same `end_date` for the next page — the final page has `truncated: false` and no `next_start_date`
96
98
  - Requesting the same currency on both sides returns a rate of 1 on each publication day in the range, taken from the ECB's real calendar rather than a synthetic Mon–Fri loop
97
99
 
98
100
  The response never carries a date outside the requested range. Frankfurter snaps a range that opens on a weekend or bank holiday back to the prior publication day; those rows are dropped, so `start_date` and `end_date` always sit inside the window you asked for. A range covering only non-publication days therefore returns an empty `rates` map with `rate_count: 0` and a `notice` explaining that the ECB published nothing in that window — distinguishable from an error.
@@ -100,7 +102,7 @@ The response never carries a date outside the requested range. Frankfurter snaps
100
102
  Once a `canvas_id` is in hand:
101
103
 
102
104
  1. **`fx_dataframe_describe`** — list the tables and columns on the canvas (required before `fx_dataframe_query`)
103
- 2. **`fx_dataframe_query`** — run arbitrary SQL SELECT against the registered table; supports aggregations, GROUP BY, window functions, JOINs across tables from multiple `fx_get_timeseries` calls
105
+ 2. **`fx_dataframe_query`** — run arbitrary SQL SELECT against the registered table; supports aggregations, GROUP BY, window functions, JOINs across tables from multiple `fx_get_timeseries` calls. Returns at most `row_limit` rows (default 150, max 10,000) on both response surfaces; past that, `truncated: true` and a `notice` give the `ORDER BY <column> LIMIT <n> OFFSET <m>` shape for the next page (`ORDER BY` is required for stable paging). Cell values are escaped in the Markdown table so pipes, angle brackets, and line breaks stay inside their cell; `structuredContent` keeps the raw values
104
106
 
105
107
  The canvas uses a session-scoped TTL. To continue working with a prior series, call `fx_get_timeseries` again with the same parameters to obtain a fresh `canvas_id`.
106
108
 
@@ -143,7 +145,8 @@ Agent-friendly output:
143
145
  - Rate provenance on every snapshot, rate, and conversion response — `rate_type`, `source`, `rate_date`, and `date_snapped` so agents can reason about trust and freshness
144
146
  - Structured error contracts — typed `reason` fields (`unsupported_currency`, `date_out_of_range`, `invalid_query`, …) let callers branch on failure type, not string parsing
145
147
  - Discriminated DataCanvas output — `spilled: true` plus `canvas_id` signal when a time-series was staged for SQL follow-up rather than returned inline
146
- - Success-path `notice` enrichment explains an empty series or a long range that stayed inline, so a legitimate zero-result never reads as a failure
148
+ - Bounded responsesinline time-series pages continue from `next_start_date`, and SQL results cap at `row_limit`, so no call returns an unbounded payload
149
+ - Success-path `notice` enrichment — explains an empty series, where to continue a paged series, or which tools read a staged one, so a legitimate zero-result never reads as a failure
147
150
 
148
151
  ---
149
152
 
@@ -342,6 +345,7 @@ The Dockerfile defaults to HTTP transport, stateless session mode, and logs to `
342
345
  | `src/mcp-server/resources/` | Resource definitions — `fx://currencies` and `fx://rates/latest/{base}`. |
343
346
  | `src/services/frankfurter/` | Frankfurter HTTP client, retry logic, and domain types. |
344
347
  | `src/services/canvas/` | Module-level DataCanvas accessor for `fx_get_timeseries` spillover. |
348
+ | `src/utils/` | Output helpers — Markdown table-cell escaping for `fx_dataframe_query`. |
345
349
  | `tests/` | Unit and integration tests mirroring `src/`. |
346
350
  | `docs/` | Design document and idea notes. |
347
351
 
@@ -0,0 +1,22 @@
1
+ ---
2
+ summary: "fx_get_timeseries pages inline results at 500 publication days and fx_dataframe_query defaults row_limit to 150 (max 10,000) — both breaking; spilled fx_get_timeseries responses name both dataframe tools, and query results escape Markdown table cells."
3
+ breaking: true
4
+ security: false
5
+ ---
6
+
7
+ # 0.4.0 — 2026-09-12
8
+
9
+ ## Added
10
+
11
+ - **`fx_get_timeseries` gains `truncated` and `next_start_date`** — inline results page at 500 publication days; pass `next_start_date` back as `start_date` for the next page. ([#11](https://github.com/cyanheads/exchange-rates-mcp-server/issues/11))
12
+ - **`fx_dataframe_query` gains `row_limit`** (1–10,000, default 150) — passed through to DataCanvas's `query()`; a capped result's `notice` gives the `ORDER BY <column> LIMIT <n> OFFSET <m>` paging shape. ([#12](https://github.com/cyanheads/exchange-rates-mcp-server/issues/12))
13
+
14
+ ## Changed
15
+
16
+ - **`fx_get_timeseries` inline results are now bounded** — a canvas-disabled or under-threshold range returns at most 500 publication days per call instead of the full series; `rate_count` stays the total for the requested range. **Breaking**: a caller relying on one call returning an unbounded series must page with `next_start_date`. ([#11](https://github.com/cyanheads/exchange-rates-mcp-server/issues/11))
17
+ - **`fx_dataframe_query`'s default response drops from up to 10,000 rows to 150** — `format()` no longer independently slices `content` to 50 rows, so both surfaces always carry the same row set. **Breaking**: a caller relying on more than 150 rows per call must pass a larger `row_limit` or page with `ORDER BY … LIMIT … OFFSET …`. ([#12](https://github.com/cyanheads/exchange-rates-mcp-server/issues/12))
18
+ - **Spilled `fx_get_timeseries` responses name both dataframe tools** — the spill `notice`, the `canvas_id`/`table_name` descriptions, `format()`, and the server `instructions` all point to `fx_dataframe_describe` before `fx_dataframe_query`. ([#16](https://github.com/cyanheads/exchange-rates-mcp-server/issues/16))
19
+
20
+ ## Fixed
21
+
22
+ - **`fx_dataframe_query` escapes Markdown table cells** — a value containing `|`, `<`, `>`, `*`, `_`, a backtick, brackets, tildes, `&`, or an embedded line break no longer breaks the table or is misread as Markdown/HTML; `structuredContent` is unaffected. ([#13](https://github.com/cyanheads/exchange-rates-mcp-server/issues/13))
package/dist/index.js CHANGED
@@ -40,7 +40,9 @@ await createApp({
40
40
  '- Use fx_list_currencies first to disambiguate "dollars" (USD/AUD/CAD/HKD/SGD).\n' +
41
41
  '- Cross-rates (e.g. USD→JPY) are triangulated through EUR automatically.\n' +
42
42
  '- Long time-series (>90 days) spill to DataCanvas when it is enabled (CANVAS_PROVIDER_TYPE=duckdb);\n' +
43
- ' use fx_dataframe_query for SQL. Without it, long ranges return inline and the dataframe tools are not listed.',
43
+ ' call fx_dataframe_describe for the staged columns, then fx_dataframe_query for SQL.\n' +
44
+ ' Without it, long ranges return inline in pages of 500 publication days (continue from next_start_date)\n' +
45
+ ' and the dataframe tools are not listed.',
44
46
  setup(core) {
45
47
  setCanvas(core.canvas);
46
48
  },
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,OAAO,EACL,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,eAAe,EACf,gBAAgB,GACjB,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAC;AAEjE;;;;;;GAMG;AACH,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,KAAK,MAAM,CAAC;AAC5D,MAAM,UAAU,GAAG;IACjB,IAAI,EAAE,6BAA6B;IACnC,MAAM,EAAE,wFAAwF;CACxF,CAAC;AAEX,MAAM,SAAS,CAAC;IACd,IAAI,EAAE,2BAA2B;IACjC,KAAK,EAAE,2BAA2B;IAClC,KAAK,EAAE;QACL,gBAAgB;QAChB,UAAU;QACV,SAAS;QACT,iBAAiB;QACjB,eAAe;QACf,aAAa,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,YAAY,CAAC,mBAAmB,EAAE,UAAU,CAAC;QACnF,aAAa,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,YAAY,CAAC,gBAAgB,EAAE,UAAU,CAAC;QAC7E,aAAa,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,YAAY,CAAC,eAAe,EAAE,UAAU,CAAC;KAC5E;IACD,SAAS,EAAE,CAAC,oBAAoB,EAAE,qBAAqB,CAAC;IACxD,OAAO,EAAE,EAAE;IAEX,YAAY,EACV,4FAA4F;QAC5F,uEAAuE;QACvE,mFAAmF;QACnF,4EAA4E;QAC5E,uGAAuG;QACvG,iHAAiH;IAEnH,KAAK,CAAC,IAAI;QACR,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzB,CAAC;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,OAAO,EACL,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,eAAe,EACf,gBAAgB,GACjB,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAC;AAEjE;;;;;;GAMG;AACH,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,KAAK,MAAM,CAAC;AAC5D,MAAM,UAAU,GAAG;IACjB,IAAI,EAAE,6BAA6B;IACnC,MAAM,EAAE,wFAAwF;CACxF,CAAC;AAEX,MAAM,SAAS,CAAC;IACd,IAAI,EAAE,2BAA2B;IACjC,KAAK,EAAE,2BAA2B;IAClC,KAAK,EAAE;QACL,gBAAgB;QAChB,UAAU;QACV,SAAS;QACT,iBAAiB;QACjB,eAAe;QACf,aAAa,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,YAAY,CAAC,mBAAmB,EAAE,UAAU,CAAC;QACnF,aAAa,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,YAAY,CAAC,gBAAgB,EAAE,UAAU,CAAC;QAC7E,aAAa,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,YAAY,CAAC,eAAe,EAAE,UAAU,CAAC;KAC5E;IACD,SAAS,EAAE,CAAC,oBAAoB,EAAE,qBAAqB,CAAC;IACxD,OAAO,EAAE,EAAE;IAEX,YAAY,EACV,4FAA4F;QAC5F,uEAAuE;QACvE,mFAAmF;QACnF,4EAA4E;QAC5E,uGAAuG;QACvG,yFAAyF;QACzF,4GAA4G;QAC5G,2CAA2C;IAE7C,KAAK,CAAC,IAAI;QACR,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzB,CAAC;CACF,CAAC,CAAC"}
@@ -7,6 +7,7 @@ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
7
7
  export declare const fxDataframeQuery: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
8
8
  canvas_id: z.ZodString;
9
9
  query: z.ZodString;
10
+ row_limit: z.ZodDefault<z.ZodNumber>;
10
11
  }, z.core.$strip>, z.ZodObject<{
11
12
  rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
12
13
  row_count: z.ZodNumber;
@@ -27,5 +28,7 @@ export declare const fxDataframeQuery: import("@cyanheads/mcp-ts-core").ToolDefi
27
28
  readonly code: JsonRpcErrorCode.ValidationError;
28
29
  readonly when: "SQL is not a SELECT, references unknown columns, or has a syntax error.";
29
30
  readonly recovery: "Run fx_dataframe_describe first to verify table and column names.";
30
- }], undefined>;
31
+ }], {
32
+ readonly notice: z.ZodOptional<z.ZodString>;
33
+ }>;
31
34
  //# sourceMappingURL=fx-dataframe-query.tool.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fx-dataframe-query.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/fx-dataframe-query.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAgC,MAAM,+BAA+B,CAAC;AAG/F,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;cAyL3B,CAAC"}
1
+ {"version":3,"file":"fx-dataframe-query.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/fx-dataframe-query.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAgC,MAAM,+BAA+B,CAAC;AAa/F,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;EAwN3B,CAAC"}
@@ -5,6 +5,15 @@
5
5
  import { tool, z } from '@cyanheads/mcp-ts-core';
6
6
  import { JsonRpcErrorCode, McpError, serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
7
7
  import { getCanvas } from '../../../services/canvas/canvas-accessor.js';
8
+ import { escapeMarkdownTableCell } from '../../../utils/escape-markdown-table-cell.js';
9
+ /**
10
+ * Rows returned when the caller sets no row_limit. Every returned row renders on both
11
+ * response surfaces (~125 B/row serialized for the staged date/rate table), so 150 rows
12
+ * is ~19 KB on the wire — inside the framework's 24 KB outline budget, where 200 is not.
13
+ */
14
+ const DEFAULT_ROW_LIMIT = 150;
15
+ /** DataCanvas's own materialization ceiling — raising row_limit can never exceed an unbounded query. */
16
+ const MAX_ROW_LIMIT = 10_000;
8
17
  export const fxDataframeQuery = tool('fx_dataframe_query', {
9
18
  description: 'Run a read-only SQL SELECT against DataCanvas tables staged by fx_get_timeseries. ' +
10
19
  'Supports aggregations, GROUP BY, window functions, and JOINs across multiple registered tables. ' +
@@ -26,25 +35,42 @@ export const fxDataframeQuery = tool('fx_dataframe_query', {
26
35
  .describe('Read-only SQL SELECT statement. Reference tables by the names returned by fx_dataframe_describe ' +
27
36
  'or the table_name field from fx_get_timeseries. ' +
28
37
  "Example: SELECT date, rate FROM fx_usd_eur WHERE date > '2024-01-01' ORDER BY date"),
38
+ row_limit: z
39
+ .number()
40
+ .int()
41
+ .min(1)
42
+ .max(MAX_ROW_LIMIT)
43
+ .default(DEFAULT_ROW_LIMIT)
44
+ .describe(`Most rows to return (1–${MAX_ROW_LIMIT}, default ${DEFAULT_ROW_LIMIT}). When the query ` +
45
+ 'produces more, truncated is true — page with ORDER BY <column> LIMIT <n> OFFSET <m> in the ' +
46
+ 'SQL, or aggregate, rather than raising this toward the maximum.'),
29
47
  }),
30
48
  output: z.object({
31
49
  rows: z
32
50
  .array(z
33
51
  .record(z.string(), z.unknown())
34
52
  .describe('One result row — column-name → value pairs matching the SELECT columns.'))
35
- .describe('Result rows, capped at the canvas row limit (default 10 000). Each key is a column name from the query.'),
53
+ .describe(`Result rows, at most row_limit (default ${DEFAULT_ROW_LIMIT}). Each key is a column name from the query.`),
36
54
  row_count: z
37
55
  .number()
38
- .describe('Rows returned. Equals the materialized row count; when truncated is true this is the row cap, ' +
39
- 'not the full result size. Narrow the SELECT (add WHERE/LIMIT or aggregate) to see all rows.'),
56
+ .describe('Rows returned always the length of rows. When truncated is true this equals row_limit, ' +
57
+ 'not the full result size.'),
40
58
  truncated: z
41
59
  .boolean()
42
- .describe('True when the query produced more rows than the canvas row cap and the result was capped. ' +
43
- 'Refine the query to materialize the complete result.'),
60
+ .describe('True when the query produced more rows than row_limit and rows holds only the first ' +
61
+ 'row_limit of them. Fetch the rest with ORDER BY <column> LIMIT <n> OFFSET <m> — ORDER BY is ' +
62
+ 'required for deterministic paging — or aggregate to shrink the result.'),
44
63
  canvas_id: z
45
64
  .string()
46
65
  .describe('The canvas ID used — pass to a subsequent fx_dataframe_query or fx_dataframe_describe call.'),
47
66
  }),
67
+ enrichment: {
68
+ notice: z
69
+ .string()
70
+ .optional()
71
+ .describe('Present when truncated is true: how many rows came back and the ORDER BY … LIMIT … OFFSET ' +
72
+ 'query shape that fetches the next page.'),
73
+ },
48
74
  errors: [
49
75
  {
50
76
  reason: 'canvas_not_found',
@@ -86,7 +112,10 @@ export const fxDataframeQuery = tool('fx_dataframe_query', {
86
112
  }
87
113
  let result;
88
114
  try {
89
- result = await instance.query(input.query, { signal: ctx.signal });
115
+ result = await instance.query(input.query, {
116
+ rowLimit: input.row_limit,
117
+ signal: ctx.signal,
118
+ });
90
119
  }
91
120
  catch (err) {
92
121
  const reason = reasonOf(err);
@@ -116,21 +145,28 @@ export const fxDataframeQuery = tool('fx_dataframe_query', {
116
145
  }
117
146
  throw err;
118
147
  }
148
+ const truncated = result.truncated ?? false;
119
149
  ctx.log.info('Executed dataframe query', {
120
150
  canvasId: input.canvas_id,
121
151
  rowCount: result.rowCount,
122
- truncated: result.truncated ?? false,
152
+ truncated,
123
153
  });
154
+ if (truncated) {
155
+ ctx.enrich.notice(`The query produced more than ${input.row_limit} rows; rows holds the first ${input.row_limit}. ` +
156
+ `For the next page re-run it with ORDER BY <column> LIMIT ${input.row_limit} OFFSET ${input.row_limit} ` +
157
+ '(raise OFFSET by the limit each page). ORDER BY is required: without it DuckDB does not ' +
158
+ 'guarantee the same row order across pages. Aggregating or filtering shrinks the result instead.');
159
+ }
124
160
  return {
125
161
  rows: result.rows,
126
162
  row_count: result.rowCount,
127
- truncated: result.truncated ?? false,
163
+ truncated,
128
164
  canvas_id: input.canvas_id,
129
165
  };
130
166
  },
131
167
  format: (result) => {
132
168
  const capNote = result.truncated
133
- ? '\n⚠️ *truncated: hit the canvas row cap refine the query (WHERE/LIMIT/aggregate) to materialize the full set.*'
169
+ ? '\n⚠️ *truncated: yes the query produced more rows than row_limit; only these were returned.*'
134
170
  : '\n*truncated: no*';
135
171
  if (result.rows.length === 0) {
136
172
  return [
@@ -141,19 +177,16 @@ export const fxDataframeQuery = tool('fx_dataframe_query', {
141
177
  ];
142
178
  }
143
179
  const cols = Object.keys(result.rows[0] ?? {});
144
- const header = `| ${cols.join(' | ')} |`;
145
- const sep = `| ${cols.map(() => '---').join(' | ')} |`;
146
- const rowLines = result.rows
147
- .slice(0, 50)
148
- .map((r) => `| ${cols.map((c) => String(r[c] ?? '')).join(' | ')} |`);
149
- const shown = Math.min(result.rows.length, 50);
150
- const total = result.truncated ? `${result.row_count}+ (capped)` : `${result.row_count}`;
151
- const note = result.row_count > shown ? `\n*Showing ${shown} of ${total} rows*` : `\n*${total} rows*`;
152
- const table = [header, sep, ...rowLines].join('\n');
180
+ const toRow = (cells) => `| ${cells.map(escapeMarkdownTableCell).join(' | ')} |`;
181
+ const table = [
182
+ toRow(cols),
183
+ `| ${cols.map(() => '---').join(' | ')} |`,
184
+ ...result.rows.map((r) => toRow(cols.map((c) => String(r[c] ?? '')))),
185
+ ].join('\n');
153
186
  return [
154
187
  {
155
188
  type: 'text',
156
- text: `${table}${note} · canvas \`${result.canvas_id}\`${capNote}`,
189
+ text: `${table}\n*${result.row_count} rows* · canvas \`${result.canvas_id}\`${capNote}`,
157
190
  },
158
191
  ];
159
192
  },
@@ -1 +1 @@
1
- {"version":3,"file":"fx-dataframe-query.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/fx-dataframe-query.tool.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fx-dataframe-query.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/fx-dataframe-query.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAC/F,OAAO,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAEhF;;;;GAIG;AACH,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAC9B,wGAAwG;AACxG,MAAM,aAAa,GAAG,MAAM,CAAC;AAE7B,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC,oBAAoB,EAAE;IACzD,WAAW,EACT,oFAAoF;QACpF,kGAAkG;QAClG,8EAA8E;QAC9E,gGAAgG;QAChG,mDAAmD;IACrD,WAAW,EAAE;QACX,YAAY,EAAE,IAAI;QAClB,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,KAAK;KACrB;IACD,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC;QACd,SAAS,EAAE,CAAC;aACT,MAAM,EAAE;aACR,QAAQ,CACP,2CAA2C;YACzC,+EAA+E,CAClF;QACH,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,QAAQ,CACP,kGAAkG;YAChG,kDAAkD;YAClD,oFAAoF,CACvF;QACH,SAAS,EAAE,CAAC;aACT,MAAM,EAAE;aACR,GAAG,EAAE;aACL,GAAG,CAAC,CAAC,CAAC;aACN,GAAG,CAAC,aAAa,CAAC;aAClB,OAAO,CAAC,iBAAiB,CAAC;aAC1B,QAAQ,CACP,0BAA0B,aAAa,aAAa,iBAAiB,oBAAoB;YACvF,6FAA6F;YAC7F,iEAAiE,CACpE;KACJ,CAAC;IACF,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,IAAI,EAAE,CAAC;aACJ,KAAK,CACJ,CAAC;aACE,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;aAC/B,QAAQ,CAAC,yEAAyE,CAAC,CACvF;aACA,QAAQ,CACP,2CAA2C,iBAAiB,8CAA8C,CAC3G;QACH,SAAS,EAAE,CAAC;aACT,MAAM,EAAE;aACR,QAAQ,CACP,2FAA2F;YACzF,2BAA2B,CAC9B;QACH,SAAS,EAAE,CAAC;aACT,OAAO,EAAE;aACT,QAAQ,CACP,sFAAsF;YACpF,8FAA8F;YAC9F,wEAAwE,CAC3E;QACH,SAAS,EAAE,CAAC;aACT,MAAM,EAAE;aACR,QAAQ,CACP,6FAA6F,CAC9F;KACJ,CAAC;IAEF,UAAU,EAAE;QACV,MAAM,EAAE,CAAC;aACN,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,4FAA4F;YAC1F,yCAAyC,CAC5C;KACJ;IAED,MAAM,EAAE;QACN;YACE,MAAM,EAAE,kBAAkB;YAC1B,IAAI,EAAE,gBAAgB,CAAC,QAAQ;YAC/B,IAAI,EAAE,+CAA+C;YACrD,QAAQ,EAAE,uDAAuD;SAClE;QACD;YACE,MAAM,EAAE,eAAe;YACvB,IAAI,EAAE,gBAAgB,CAAC,QAAQ;YAC/B,IAAI,EAAE,qFAAqF;YAC3F,QAAQ,EACN,oFAAoF;SACvF;QACD;YACE,MAAM,EAAE,eAAe;YACvB,IAAI,EAAE,gBAAgB,CAAC,eAAe;YACtC,IAAI,EAAE,yEAAyE;YAC/E,QAAQ,EAAE,mEAAmE;SAC9E;KACF;IAED,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG;QACtB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,kBAAkB,CAAC,6DAA6D,CAAC,CAAC;QAC1F,CAAC;QAED,oGAAoG;QACpG,MAAM,QAAQ,GAAG,CAAC,GAAY,EAAsB,EAAE,CACpD,GAAG,YAAY,QAAQ,CAAC,CAAC,CAAE,GAAG,CAAC,IAAwC,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;QAE9F,IAAI,QAAwB,CAAC;QAC7B,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QACxD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,kBAAkB,EAAE,CAAC;gBACzC,MAAM,GAAG,CAAC,IAAI,CACZ,kBAAkB,EAClB,WAAW,KAAK,CAAC,SAAS,6BAA6B,EACvD;oBACE,GAAG,GAAG,CAAC,WAAW,CAAC,kBAAkB,CAAC;iBACvC,CACF,CAAC;YACJ,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,IAAI,MAAmB,CAAC;QACxB,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE;gBACzC,QAAQ,EAAE,KAAK,CAAC,SAAS;gBACzB,MAAM,EAAE,GAAG,CAAC,MAAM;aACnB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC7B,IAAI,MAAM,KAAK,kBAAkB,EAAE,CAAC;gBAClC,MAAM,GAAG,CAAC,IAAI,CACZ,kBAAkB,EAClB,WAAW,KAAK,CAAC,SAAS,6BAA6B,EACvD;oBACE,GAAG,GAAG,CAAC,WAAW,CAAC,kBAAkB,CAAC;iBACvC,CACF,CAAC;YACJ,CAAC;YACD,IAAI,MAAM,KAAK,eAAe,EAAE,CAAC;gBAC/B,MAAM,GAAG,CAAC,IAAI,CAAC,eAAe,EAAG,GAAa,CAAC,OAAO,EAAE;oBACtD,GAAG,GAAG,CAAC,WAAW,CAAC,eAAe,CAAC;iBACpC,CAAC,CAAC;YACL,CAAC;YACD,qGAAqG;YACrG,IAAI,MAAM,KAAK,WAAW;gBAAE,MAAM,GAAG,CAAC;YACtC,MAAM,GAAG,GAAI,GAAa,CAAC,OAAO,IAAI,EAAE,CAAC;YACzC,kFAAkF;YAClF,IACE,MAAM,KAAK,SAAS;gBACpB,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBACtB,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBACtB,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC;gBAC7B,GAAyB,CAAC,IAAI,KAAK,iBAAiB,EACrD,CAAC;gBACD,MAAM,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE,uBAAuB,GAAG,EAAE,EAAE;oBAC5D,GAAG,GAAG,CAAC,WAAW,CAAC,eAAe,CAAC;iBACpC,CAAC,CAAC;YACL,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,KAAK,CAAC;QAC5C,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,0BAA0B,EAAE;YACvC,QAAQ,EAAE,KAAK,CAAC,SAAS;YACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,SAAS;SACV,CAAC,CAAC;QAEH,IAAI,SAAS,EAAE,CAAC;YACd,GAAG,CAAC,MAAM,CAAC,MAAM,CACf,gCAAgC,KAAK,CAAC,SAAS,+BAA+B,KAAK,CAAC,SAAS,IAAI;gBAC/F,4DAA4D,KAAK,CAAC,SAAS,WAAW,KAAK,CAAC,SAAS,GAAG;gBACxG,0FAA0F;gBAC1F,iGAAiG,CACpG,CAAC;QACJ,CAAC;QAED,OAAO;YACL,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,SAAS,EAAE,MAAM,CAAC,QAAQ;YAC1B,SAAS;YACT,SAAS,EAAE,KAAK,CAAC,SAAS;SAC3B,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE;QACjB,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS;YAC9B,CAAC,CAAC,gGAAgG;YAClG,CAAC,CAAC,mBAAmB,CAAC;QACxB,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,OAAO;gBACL;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,mCAAmC,MAAM,CAAC,SAAS,OAAO,OAAO,EAAE;iBAC1E;aACF,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC/C,MAAM,KAAK,GAAG,CAAC,KAAe,EAAE,EAAE,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;QAC3F,MAAM,KAAK,GAAG;YACZ,KAAK,CAAC,IAAI,CAAC;YACX,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI;YAC1C,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;SACtE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,OAAO;YACL;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,GAAG,KAAK,MAAM,MAAM,CAAC,SAAS,qBAAqB,MAAM,CAAC,SAAS,KAAK,OAAO,EAAE;aACxF;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
@@ -17,6 +17,8 @@ export declare const fxGetTimeseries: import("@cyanheads/mcp-ts-core").ToolDefin
17
17
  end_date: z.ZodString;
18
18
  rates: z.ZodRecord<z.ZodString, z.ZodNumber>;
19
19
  rate_count: z.ZodNumber;
20
+ truncated: z.ZodBoolean;
21
+ next_start_date: z.ZodOptional<z.ZodString>;
20
22
  rate_type: z.ZodString;
21
23
  source: z.ZodString;
22
24
  spilled: z.ZodBoolean;
@@ -1 +1 @@
1
- {"version":3,"file":"fx-get-timeseries.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/fx-get-timeseries.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAWjE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoV1B,CAAC"}
1
+ {"version":3,"file":"fx-get-timeseries.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/fx-get-timeseries.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAkBjE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0X1B,CAAC"}
@@ -9,17 +9,26 @@ import { getServerConfig } from '../../../config/server-config.js';
9
9
  import { getCanvas } from '../../../services/canvas/canvas-accessor.js';
10
10
  import { failureOf, unsupportedCurrencyCodesOf } from '../../../services/frankfurter/errors.js';
11
11
  import { ECB_START_DATE, getFrankfurterService, isIsoDate, } from '../../../services/frankfurter/frankfurter-service.js';
12
+ /**
13
+ * Most publication days one inline response carries — roughly two ECB years, ~21 KB
14
+ * serialized across both response surfaces. Rows are date-keyed, so the continuation
15
+ * is the next start_date rather than an offset.
16
+ */
17
+ const INLINE_PAGE_SIZE = 500;
12
18
  export const fxGetTimeseries = tool('fx_get_timeseries', {
13
19
  description: 'Get historical daily exchange rates for a currency pair over a date range. ' +
14
20
  'ECB publishes on business days only — weekends and holidays produce no entry, and no date ' +
15
21
  'outside the requested range is ever returned, so a range covering only non-publication days ' +
16
22
  'comes back with an empty rates map and a notice explaining why. ' +
17
23
  'A same-currency pair returns a rate of 1 on each publication day in the range. ' +
18
- 'Short ranges (≤90 days by default) are returned inline as a date→rate map. ' +
19
- 'When DataCanvas is enabled (CANVAS_PROVIDER_TYPE=duckdb) long ranges spill to it: the response ' +
20
- 'carries spilled=true, a canvas_id, and a table_name — call fx_dataframe_describe to inspect the ' +
21
- 'staged table, then fx_dataframe_query to run SQL against it. ' +
22
- 'Without DataCanvas long ranges stay inline (spilled=false) and the notice says so.',
24
+ `Inline results are returned as a date→rate map paged at ${INLINE_PAGE_SIZE} publication days: ` +
25
+ 'rate_count is always the total for the requested range, and when a page is cut short the ' +
26
+ 'response carries truncated=true and next_start_date — call again with start_date set to ' +
27
+ 'next_start_date and the same end_date for the next page. ' +
28
+ 'When DataCanvas is enabled (CANVAS_PROVIDER_TYPE=duckdb) long ranges (>90 days by default) spill ' +
29
+ 'to it instead: the response carries spilled=true, a canvas_id, and a table_name — call ' +
30
+ 'fx_dataframe_describe to inspect the staged table, then fx_dataframe_query to run SQL against it. ' +
31
+ 'Without DataCanvas long ranges are paged inline (spilled=false) and the notice says so.',
23
32
  annotations: {
24
33
  readOnlyHint: true,
25
34
  idempotentHint: true,
@@ -50,20 +59,35 @@ export const fxGetTimeseries = tool('fx_get_timeseries', {
50
59
  quote_currency: z.string().describe('Quote currency code.'),
51
60
  start_date: z
52
61
  .string()
53
- .describe('First date in the returned series. Always inside the requested range later than the ' +
54
- 'requested start when that day had no ECB fix, and equal to it when the series is empty.'),
62
+ .describe('First publication date in the requested range, and the first key in rates. Always inside ' +
63
+ 'the requested range — later than the requested start when that day had no ECB fix, and ' +
64
+ 'equal to it when the series is empty.'),
55
65
  end_date: z
56
66
  .string()
57
- .describe('Last date in the returned series. Always inside the requested range — earlier than the ' +
58
- 'requested end when that day had no ECB fix, and equal to it when the series is empty.'),
67
+ .describe('Last publication date in the requested range. Always inside the requested range — earlier ' +
68
+ 'than the requested end when that day had no ECB fix, and equal to it when the series is ' +
69
+ 'empty. Later than the last key in rates when truncated is true.'),
59
70
  rates: z
60
71
  .record(z.string(), z.number())
61
- .describe('Date → rate map for the inline result. Publication days inside the requested range only. ' +
62
- 'Truncated to a preview when the result was spilled to canvas; empty when the range ' +
63
- 'contains no publication day at all.'),
72
+ .describe('Date → rate map in date order, publication days inside the requested range only. Holds ' +
73
+ `every publication day when truncated is false; otherwise the first ${INLINE_PAGE_SIZE} of an ` +
74
+ 'inline page (continue with next_start_date) or the preview of a spilled series (the full ' +
75
+ 'series is on the canvas). Empty when the range contains no publication day at all.'),
64
76
  rate_count: z
65
77
  .number()
66
- .describe('Total number of data points (publication days) inside the requested range.'),
78
+ .describe('Total publication days in the requested range (the start_date through end_date passed on ' +
79
+ 'this call) — not the number of entries in rates, which is smaller when truncated is true. ' +
80
+ 'On a continuation call it counts the days from that start_date onward.'),
81
+ truncated: z
82
+ .boolean()
83
+ .describe('True when rates holds only part of the series: an inline page (next_start_date continues ' +
84
+ 'it) or a spilled preview (spilled is true). False when rates holds every publication day ' +
85
+ 'in the requested range.'),
86
+ next_start_date: z
87
+ .string()
88
+ .optional()
89
+ .describe('Present only when an inline page was cut short: the first publication date not in rates. ' +
90
+ 'Pass it as start_date with the same end_date to get the next page. Absent on the final page.'),
67
91
  rate_type: z
68
92
  .string()
69
93
  .describe('Always "ECB reference (mid-market)" — these are reference rates, not tradeable bid/ask.'),
@@ -74,18 +98,22 @@ export const fxGetTimeseries = tool('fx_get_timeseries', {
74
98
  canvas_id: z
75
99
  .string()
76
100
  .optional()
77
- .describe('Canvas ID — present when spilled is true. Pass to fx_dataframe_query.'),
101
+ .describe('Canvas ID — present when spilled is true. Pass to fx_dataframe_describe to inspect the ' +
102
+ 'staged table, then to fx_dataframe_query to run SQL.'),
78
103
  table_name: z
79
104
  .string()
80
105
  .optional()
81
- .describe('Canvas table name — present when spilled is true. Use in fx_dataframe_query SQL.'),
106
+ .describe('Canvas table name — present when spilled is true. Use it as the FROM target in ' +
107
+ 'fx_dataframe_query SQL; fx_dataframe_describe lists its columns.'),
82
108
  }),
83
109
  enrichment: {
84
110
  notice: z
85
111
  .string()
86
112
  .optional()
87
- .describe('Explains a result that would otherwise look broken an empty series, or a long range ' +
88
- 'that stayed inline because DataCanvas is not configured.'),
113
+ .describe('Explains a result that would otherwise look broken or incomplete: an empty series (no ' +
114
+ 'publication day in the range); an inline page cut short (the start_date to continue ' +
115
+ 'from, plus why a long range was not staged when DataCanvas is not configured); or a ' +
116
+ 'spilled series (the canvas and table it was staged to, and the tools that read it).'),
89
117
  },
90
118
  errors: [
91
119
  {
@@ -181,23 +209,7 @@ export const fxGetTimeseries = tool('fx_get_timeseries', {
181
209
  });
182
210
  const canvas = getCanvas();
183
211
  const exceedsThreshold = dayCount > config.timeseriesCanvasThresholdDays;
184
- const shouldSpill = canvas != null && exceedsThreshold;
185
- /**
186
- * An empty series and a long range that never spilled both look like failures
187
- * from the outside. Say which one happened and why, on both client surfaces.
188
- */
189
- if (rows.length === 0) {
190
- ctx.enrich.notice(`The ECB published no ${input.base_currency.toUpperCase()}/${input.quote_currency.toUpperCase()} rate between ` +
191
- `${input.start_date} and ${input.end_date}. Reference rates are published on TARGET business days ` +
192
- 'only, so a range covering just a weekend or a bank holiday is legitimately empty rather than ' +
193
- 'broken. Widen the range — a window spanning several weekdays will contain a publication day.');
194
- }
195
- else if (exceedsThreshold && canvas == null) {
196
- ctx.enrich.notice(`This ${dayCount}-day range is past the ${config.timeseriesCanvasThresholdDays}-day spill threshold, but ` +
197
- 'DataCanvas is not configured on this server, so the full series is inline and spilled is false. ' +
198
- 'Set CANVAS_PROVIDER_TYPE=duckdb to stage long ranges for SQL instead.');
199
- }
200
- /** Fields every branch below returns identically — only rates/count/spill differ. */
212
+ /** Fields every branch below returns identically — only rates/count/paging/spill differ. */
201
213
  const envelope = {
202
214
  base_currency: input.base_currency.toUpperCase(),
203
215
  quote_currency: input.quote_currency.toUpperCase(),
@@ -206,72 +218,92 @@ export const fxGetTimeseries = tool('fx_get_timeseries', {
206
218
  rate_type: 'ECB reference (mid-market)',
207
219
  source: 'ECB via Frankfurter',
208
220
  };
209
- // Inline path short range, or long range with no canvas to spill to
210
- if (!shouldSpill) {
211
- const rateMap = {};
212
- for (const row of rows)
213
- rateMap[row.date] = row.rate;
214
- return { ...envelope, rates: rateMap, rate_count: rows.length, spilled: false };
221
+ if (canvas != null && exceedsThreshold) {
222
+ const instance = await canvas.acquire(input.canvas_id, ctx);
223
+ const tableName = `fx_${input.base_currency.toLowerCase()}_${input.quote_currency.toLowerCase()}`;
224
+ const spillResult = await spillover({
225
+ canvas: instance,
226
+ source: rows,
227
+ tableName,
228
+ previewChars: 40_000, // ~10k tokens preview
229
+ signal: ctx.signal,
230
+ });
231
+ ctx.log.info('Spilled to canvas', {
232
+ canvasId: instance.canvasId,
233
+ tableName,
234
+ rowCount: rows.length,
235
+ spilled: spillResult.spilled,
236
+ });
237
+ if (spillResult.spilled) {
238
+ const { rowCount, tableName: stagedTable } = spillResult.handle;
239
+ ctx.enrich.notice(`Staged ${rowCount} rows to table "${stagedTable}" on canvas ${instance.canvasId} — call ` +
240
+ 'fx_dataframe_describe to inspect its columns, then fx_dataframe_query to run SQL over the ' +
241
+ 'full series. rates holds only a preview.');
242
+ return {
243
+ ...envelope,
244
+ rates: Object.fromEntries(spillResult.previewRows.map((row) => [row.date, row.rate])),
245
+ rate_count: rowCount,
246
+ truncated: true,
247
+ spilled: true,
248
+ canvas_id: instance.canvasId,
249
+ table_name: stagedTable,
250
+ };
251
+ }
252
+ // Every row fit the preview budget, so nothing was staged — page it inline below.
215
253
  }
216
- // Canvas spillover path
217
- const instance = await canvas.acquire(input.canvas_id, ctx);
218
- const tableName = `fx_${input.base_currency.toLowerCase()}_${input.quote_currency.toLowerCase()}`;
219
- const spillResult = await spillover({
220
- canvas: instance,
221
- source: rows,
222
- tableName,
223
- previewChars: 40_000, // ~10k tokens preview
224
- signal: ctx.signal,
225
- });
226
- ctx.log.info('Spilled to canvas', {
227
- canvasId: instance.canvasId,
228
- tableName,
229
- rowCount: rows.length,
230
- spilled: spillResult.spilled,
231
- });
232
- // Build inline rate map from preview rows for the response
233
- const previewRates = {};
234
- for (const row of spillResult.previewRows) {
235
- const r = row;
236
- previewRates[r.date] = r.rate;
254
+ const page = rows.slice(0, INLINE_PAGE_SIZE);
255
+ const nextRow = rows[INLINE_PAGE_SIZE];
256
+ /**
257
+ * An empty series, a page cut short, and a long range that never spilled all look
258
+ * incomplete or broken from the outside. Say which one happened, on both surfaces.
259
+ * The dataframe tools are unregistered without a canvas, so nothing here names them.
260
+ */
261
+ const unstaged = exceedsThreshold && canvas == null
262
+ ? `This ${dayCount}-day range is past the ${config.timeseriesCanvasThresholdDays}-day spill threshold, but ` +
263
+ 'DataCanvas is not configured on this server, so the series is returned inline and spilled is false. ' +
264
+ 'Set CANVAS_PROVIDER_TYPE=duckdb to stage long ranges for SQL instead.'
265
+ : undefined;
266
+ if (rows.length === 0) {
267
+ ctx.enrich.notice(`The ECB published no ${envelope.base_currency}/${envelope.quote_currency} rate between ` +
268
+ `${input.start_date} and ${input.end_date}. Reference rates are published on TARGET business days ` +
269
+ 'only, so a range covering just a weekend or a bank holiday is legitimately empty rather than ' +
270
+ 'broken. Widen the range a window spanning several weekdays will contain a publication day.');
271
+ }
272
+ else if (nextRow) {
273
+ const pageNote = `rates holds the first ${page.length} of ${rows.length} publication days ` +
274
+ `(${page[0]?.date} to ${page.at(-1)?.date}). Call fx_get_timeseries again with ` +
275
+ `start_date=${nextRow.date} and end_date=${input.end_date} for the next page.`;
276
+ ctx.enrich.notice(unstaged ? `${pageNote} ${unstaged}` : pageNote);
237
277
  }
238
- if (spillResult.spilled) {
239
- return {
240
- ...envelope,
241
- rates: previewRates,
242
- rate_count: spillResult.handle.rowCount,
243
- spilled: true,
244
- canvas_id: instance.canvasId,
245
- table_name: spillResult.handle.tableName,
246
- };
278
+ else if (unstaged) {
279
+ ctx.enrich.notice(unstaged);
247
280
  }
248
- // Fell under budget even at canvas threshold — return inline
249
281
  return {
250
282
  ...envelope,
251
- rates: previewRates,
252
- rate_count: spillResult.previewRows.length,
283
+ rates: Object.fromEntries(page.map((row) => [row.date, row.rate])),
284
+ rate_count: rows.length,
285
+ truncated: nextRow !== undefined,
286
+ ...(nextRow && { next_start_date: nextRow.date }),
253
287
  spilled: false,
254
288
  };
255
289
  },
256
290
  format: (result) => {
291
+ const rateEntries = Object.entries(result.rates).sort(([a], [b]) => a.localeCompare(b));
257
292
  const lines = [
258
293
  `**${result.base_currency}/${result.quote_currency} time series** — ${result.start_date} to ${result.end_date}`,
259
- `*${result.rate_count} publication-day data points · ${result.rate_type} · ${result.source} · spilled: ${result.spilled}*`,
294
+ `*${result.rate_count} publication-day data points in range · ${result.rate_type} · ${result.source} · spilled: ${result.spilled}*`,
295
+ `*${rateEntries.length} shown · truncated: ${result.truncated}` +
296
+ (result.next_start_date ? ` · next_start_date: ${result.next_start_date}*` : '*'),
260
297
  ];
261
298
  if (result.spilled) {
262
- lines.push(`\n📊 **Result staged on DataCanvas** (large range)`, `Canvas ID: \`${result.canvas_id}\``, `Table: \`${result.table_name}\``, `Use \`fx_dataframe_query\` with this canvas_id to run SQL. Preview (first entries below):`);
299
+ lines.push(`\n📊 **Result staged on DataCanvas** (large range)`, `Canvas ID: \`${result.canvas_id}\``, `Table: \`${result.table_name}\``, 'Call `fx_dataframe_describe` with this canvas_id to inspect the table columns, then ' +
300
+ '`fx_dataframe_query` to run SQL over the full series. Preview (first entries below):');
263
301
  }
264
- const rateEntries = Object.entries(result.rates)
265
- .sort(([a], [b]) => a.localeCompare(b))
266
- .slice(0, 20);
267
302
  if (rateEntries.length > 0) {
268
303
  lines.push('');
269
304
  for (const [date, rate] of rateEntries) {
270
305
  lines.push(`${date}: ${rate}`);
271
306
  }
272
- if (Object.keys(result.rates).length > 20) {
273
- lines.push(`... (${Object.keys(result.rates).length - 20} more entries)`);
274
- }
275
307
  }
276
308
  return [{ type: 'text', text: lines.join('\n') }];
277
309
  },
@@ -1 +1 @@
1
- {"version":3,"file":"fx-get-timeseries.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/fx-get-timeseries.tool.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fx-get-timeseries.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/fx-get-timeseries.tool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @fileoverview Escapes a value for one cell of a GFM Markdown table.
3
+ * @module utils/escape-markdown-table-cell
4
+ */
5
+ /**
6
+ * Renders `value` as the literal text of a single table cell: every metacharacter is
7
+ * backslash-escaped and each line break becomes `<br>`, so the value never leaves its
8
+ * row. A literal `<br>` in the data is escaped first and stays distinguishable from one.
9
+ */
10
+ export declare function escapeMarkdownTableCell(value: string): string;
11
+ //# sourceMappingURL=escape-markdown-table-cell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escape-markdown-table-cell.d.ts","sourceRoot":"","sources":["../../src/utils/escape-markdown-table-cell.ts"],"names":[],"mappings":"AAAA;;;GAGG;AASH;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7D"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @fileoverview Escapes a value for one cell of a GFM Markdown table.
3
+ * @module utils/escape-markdown-table-cell
4
+ */
5
+ /**
6
+ * Markdown and HTML metacharacters that would split a table row (`|`), open a tag
7
+ * (`<`, `>`), or turn data into emphasis, code, links, strikethrough, or an entity.
8
+ * Backslash is in the set so an escape already present in the data stays literal.
9
+ */
10
+ const METACHARACTERS = /[\\|<>*_`[\]~&]/g;
11
+ /**
12
+ * Renders `value` as the literal text of a single table cell: every metacharacter is
13
+ * backslash-escaped and each line break becomes `<br>`, so the value never leaves its
14
+ * row. A literal `<br>` in the data is escaped first and stays distinguishable from one.
15
+ */
16
+ export function escapeMarkdownTableCell(value) {
17
+ return value.replace(METACHARACTERS, '\\$&').replace(/\r\n|\r|\n/g, '<br>');
18
+ }
19
+ //# sourceMappingURL=escape-markdown-table-cell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escape-markdown-table-cell.js","sourceRoot":"","sources":["../../src/utils/escape-markdown-table-cell.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AACH,MAAM,cAAc,GAAG,kBAAkB,CAAC;AAE1C;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAa;IACnD,OAAO,KAAK,CAAC,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;AAC9E,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/exchange-rates-mcp-server",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "mcpName": "io.github.cyanheads/exchange-rates-mcp-server",
5
5
  "description": "Convert currencies, get FX rates, and query historical ECB exchange rate data via MCP. STDIO or Streamable HTTP.",
6
6
  "type": "module",
package/server.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "url": "https://github.com/cyanheads/exchange-rates-mcp-server",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.3.2",
9
+ "version": "0.4.0",
10
10
  "remotes": [
11
11
  {
12
12
  "type": "streamable-http",
@@ -19,7 +19,7 @@
19
19
  "registryBaseUrl": "https://registry.npmjs.org",
20
20
  "identifier": "@cyanheads/exchange-rates-mcp-server",
21
21
  "runtimeHint": "bun",
22
- "version": "0.3.2",
22
+ "version": "0.4.0",
23
23
  "packageArguments": [
24
24
  {
25
25
  "type": "positional",
@@ -69,7 +69,7 @@
69
69
  "registryBaseUrl": "https://registry.npmjs.org",
70
70
  "identifier": "@cyanheads/exchange-rates-mcp-server",
71
71
  "runtimeHint": "bun",
72
- "version": "0.3.2",
72
+ "version": "0.4.0",
73
73
  "packageArguments": [
74
74
  {
75
75
  "type": "positional",