exchange-rates-mcp-server
Server Details
Convert currencies, get FX rates, and query historical Frankfurter v1 exchange rate data.
- Status
- Healthy
- Uptime
- 100.0% over 74 days
- Last Tested
- Transport
- Streamable HTTP · MCP 2025-11-25
- URL
- Repository
- cyanheads/exchange-rates-mcp-server
- GitHub Stars
- 1
- Server Listing
- @cyanheads/exchange-rates-mcp-server
TDQS
Scored across 7 tools
Each tool has a distinct role: single-rate lookup, bulk snapshot, conversion, timeseries retrieval, currency discovery, and DataCanvas schema/query. The descriptions explicitly clarify the boundary between fx_get_rate and fx_convert_currency, and between singular and plural rate retrieval. No two tools appear to do the same thing.
All tools use a consistent fx_ snake_case prefix with clear verb_noun or verb_resource patterns. The convention is predictable across retrieval, conversion, listing, and DataCanvas operations. There are no mixed casing or naming styles.
Seven tools is a well-scoped set for an exchange-rates server, covering the core read-only workflows without bloat. The two DataCanvas tools are appropriately specialized and conditional. Each tool earns its place.
The surface covers currency discovery, single and bulk rate retrieval, conversion, historical timeseries, and SQL-based analysis of staged data. For a read-only exchange-rate domain, this is complete and avoids dead ends. No obvious missing operation remains for the stated purpose.
Available Tools
7 toolsfx_convert_currencyFx Convert CurrencyARead-onlyIdempotentInspect
Convert an amount between any two currencies at the latest or a historical rate. Returns the converted amount, the rate used, the actual rate date, and whether the date was snapped from a weekend/holiday to the prior business day. Cross-rates are triangulated through EUR automatically.
| Name | Required | Description | Default |
|---|---|---|---|
| date | No | ISO 8601 date (YYYY-MM-DD) for a historical rate. Omit for the latest available rate. Frankfurter v1 data starts 1999-01-04. Future dates are not supported. | |
| amount | Yes | Amount in the base currency to convert. Must be greater than zero. | |
| base_currency | Yes | ISO 4217 source currency code (e.g. USD). Call fx_list_currencies to get valid codes. | |
| quote_currency | Yes | ISO 4217 target currency code (e.g. EUR). The amount will be expressed in this currency. Call fx_list_currencies to get valid codes. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rate | No | Exchange rate used: units of quote currency per 1 unit of base currency. |
| error | No | Present when the call failed. Absent on success. |
| source | No | Always "Frankfurter v1" — the upstream data provider. |
| rate_date | No | Actual date of the rate used for conversion. |
| rate_type | No | Always "Daily rate (Frankfurter v1)" — these are reference rates, not tradeable bid/ask. |
| base_amount | No | The input amount in the base currency. |
| date_snapped | No | True when the API returned a different date than requested — Frankfurter v1 snaps weekend/holiday requests to the prior business day. |
| quote_amount | No | The converted amount in the quote currency, rounded to 6 decimal places. |
| base_currency | No | Source currency code. |
| quote_currency | No | Target currency code. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover readOnly/idempotent/openWorld, yet the description still adds real behavior: weekend/holiday date snapping to the prior business day and automatic EUR triangulation for cross-rates. These are traits an agent could not infer from the schema, though it stops short of stating error behavior or precision/rounding rules.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences with zero filler: purpose first, then return values, then the triangulation caveat. Front-loaded and every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema and full annotations, the description only needs to convey non-obvious semantics, and it does via date snapping and EUR triangulation. Minor gap: no mention of behavior for unsupported dates or rate precision, though the schema covers the former.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema already documents date format, the 'latest' sentinel, amount constraints, and ISO 4217 codes with a pointer to fx_list_currencies. The description adds no new parameter-level meaning beyond reiterating latest vs. historical, so baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: 'Convert an amount between any two currencies.' It implicitly separates itself from fx_get_rate/fx_get_rates by converting an amount rather than returning a bare rate, but it never names those siblings outright, so the differentiation is inferential rather than explicit.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'At the latest or a historical rate' implies when the date parameter applies, but the description gives no explicit when-to-use vs. fx_get_rate or fx_get_timeseries, and no exclusions. Usage is implied rather than guided.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fx_dataframe_describeFx Dataframe DescribeARead-onlyIdempotentInspect
List tables and columns staged on a DataCanvas from a prior fx_get_timeseries call. Required first step before fx_dataframe_query — use it to discover table names and column schemas. Requires DataCanvas, which is on by default — a deployment that turns it off does not list this tool at all, and fx_get_timeseries returns every range inline there.
| Name | Required | Description | Default |
|---|---|---|---|
| canvas_id | Yes | Canvas ID returned by fx_get_timeseries. Re-run fx_get_timeseries to obtain a fresh canvas_id if this one has expired. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | Present when the call failed. Absent on success. |
| tables | No | All tables and views currently staged on this canvas. |
| canvas_id | No | The canvas ID echoed back — use this in fx_dataframe_query. |
| expires_at | No | ISO 8601 timestamp when this canvas will be evicted. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, and non-open-world, so the safety profile is covered. The description adds genuine context beyond that: the DataCanvas dependency, its default-on status, and the fact that deployments without it omit the tool entirely and return ranges inline via fx_get_timeseries.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight sentences, front-loaded with the action and resource, followed by the ordering constraint and the deployment caveat. No filler or repetition of the title.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return-value documentation is unnecessary. Between the description, the fully documented single parameter, and the annotations, an agent has everything needed to decide when and how to call it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and the schema itself documents canvas_id, its format pattern, and the expiry/re-run guidance. The description only implies the canvas provenance already stated in the schema, so it adds little parameter-level meaning.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Specific verb (list) plus resource (tables and columns staged on a DataCanvas) and the provenance of that data (a prior fx_get_timeseries call). It clearly separates itself from fx_dataframe_query, the sibling that consumes what this tool exposes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states it is the required first step before fx_dataframe_query and that its purpose is to discover table names and column schemas. It also explains the deployment condition (DataCanvas disabled) under which this tool is absent and the alternative behavior of fx_get_timeseries.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fx_dataframe_queryFx Dataframe QueryARead-onlyIdempotentInspect
Run a read-only SQL SELECT against DataCanvas tables staged by fx_get_timeseries. Supports aggregations, GROUP BY, window functions, and JOINs across multiple registered tables. Run fx_dataframe_describe first to discover table names and column schemas. Requires DataCanvas, which is on by default — a deployment that turns it off does not list this tool at all, and fx_get_timeseries returns every range inline there.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Read-only SQL SELECT statement. Reference tables by the names returned by fx_dataframe_describe or the table_name field from fx_get_timeseries. Example: SELECT date, rate FROM <table_name> WHERE date > '2024-01-01' ORDER BY date; replace <table_name> with the returned name, including its unique suffix. | |
| canvas_id | Yes | Canvas ID returned by fx_get_timeseries. Re-run fx_get_timeseries to obtain a fresh canvas_id if this one has expired. | |
| row_limit | No | Most rows to return (positive integer, default 150; values above 10000 clamp to 10000 with a notice). When the query produces more, truncated is true — page with ORDER BY <column> LIMIT <n> OFFSET <m> in the SQL, or aggregate, rather than raising this toward the maximum. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | No | Result rows, at most row_limit (default 150). Each key is a column name from the query. |
| error | No | Present when the call failed. Absent on success. |
| notice | No | Reports requested and effective row limits when clamped, and the ORDER BY … LIMIT … OFFSET query shape when the result is truncated. |
| canvas_id | No | The canvas ID used — pass to a subsequent fx_dataframe_query or fx_dataframe_describe call. |
| row_count | No | Rows returned — always the length of rows. When truncated is true this equals row_limit, not the full result size. |
| truncated | No | True when the query produced more rows than row_limit and rows holds only the first row_limit of them. Fetch the rest with ORDER BY <column> LIMIT <n> OFFSET <m> — ORDER BY is required for deterministic paging — or aggregate to shrink the result. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already declare readOnlyHint, idempotentHint, and openWorldHint=false, so the safety profile is covered; the description reinforces this with 'read-only SQL SELECT'. It adds genuinely useful deployment behavior that annotations cannot express: when DataCanvas is off, the tool is absent from the listing entirely. It does not mention query cost, timeouts, or how errors surface, which keeps it below top marks.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with the action and capability set, then prerequisites, then the deployment caveat. Nothing is repeated from the schema and every sentence carries distinct information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present and full parameter documentation, the description only needs to cover the workflow and preconditions, which it does: staging source, prerequisite describe step, and the DataCanvas gating fallback. Minor gap: it doesn't say whether staged tables persist across calls or expire, though canvas_id expiry is hinted at in the schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and each parameter already carries a detailed description, including the canvas_id pattern, expiry handling, row_limit truncation, and the <table_name> substitution example. The description adds only light framing (which sibling supplies table names) and no new syntax or format details, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Run a read-only SQL SELECT against DataCanvas tables staged by fx_get_timeseries') and names the exact capability envelope (aggregations, GROUP BY, window functions, JOINs). A reader can immediately distinguish it from fx_get_timeseries, which produces the data, and fx_dataframe_describe, which provides schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit prerequisite order ('Run fx_dataframe_describe first to discover table names and column schemas') and names the ordinal alternative path ('fx_get_timeseries returns every range inline there') when DataCanvas is disabled. Both when-to-use and what-to-do-instead are stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fx_get_rateFx Get RateARead-onlyIdempotentInspect
Get the exchange rate for a currency pair on a given date (default: latest). Returns the rate, the actual rate date (which may differ from the requested date on weekends/holidays — daily observations may skip non-publication days), and source provenance. Cross-rates are triangulated through EUR automatically. A same-currency pair returns a rate of 1, dated to the same publication day any other pair would report for that date. Use fx_convert_currency when you want the converted amount; use this tool when you only need the rate number.
| Name | Required | Description | Default |
|---|---|---|---|
| date | No | ISO 8601 date (YYYY-MM-DD). Omit for the latest available rate. Frankfurter v1 data starts 1999-01-04. Future dates are not supported. | |
| base_currency | Yes | ISO 4217 base currency code (e.g. USD). Call fx_list_currencies to get valid codes. | |
| quote_currency | Yes | ISO 4217 quote currency code (e.g. EUR). The rate is expressed as "how many quote units per 1 base unit". Call fx_list_currencies to get valid codes. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rate | No | Exchange rate: units of quote currency per 1 unit of base currency. |
| error | No | Present when the call failed. Absent on success. |
| source | No | Always "Frankfurter v1" — the upstream data provider. |
| rate_date | No | Actual date of the rate returned. |
| rate_type | No | Always "Daily rate (Frankfurter v1)" — these are reference rates, not tradeable bid/ask. |
| date_snapped | No | True when the API returned a different date than requested — Frankfurter v1 snaps weekend/holiday requests to the prior business day. |
| base_currency | No | The base currency code. |
| quote_currency | No | The quote currency code. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the readOnly/idempotent/openWorld annotations: it discloses that the returned rate date may differ from the requested date on weekends/holidays because non-publication days are skipped, that cross-rates are triangulated through EUR, and that a same-currency pair returns 1 dated to the normal publication day. These are non-obvious behaviors an agent could otherwise misreport to a user.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose and default, then progressive behavioral detail, then the sibling routing sentence. Slight redundancy in enumerating the return fields (rate, rate date, provenance) when an output schema already exists, but every sentence still carries information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 3-parameter read-only lookup with an output schema and full annotation coverage, the description is complete: it covers the default, the date-mismatch behavior, cross-rate derivation, and the identity-pair edge case. Nothing needed to call it correctly or interpret the result is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is effectively 100%, so date format, default-latest behavior, and base/quote directionality are already documented in the schema. The description adds the cross-rate triangulation and identity-pair semantics, but does not extend parameter meaning much past the baseline where the schema does the heavy lifting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource with scope: exchange rate for a currency pair on a given date, defaulting to latest. The final sentence explicitly contrasts it with fx_convert_currency, so an agent can distinguish rate-only lookup from conversion without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Clear routing rule: 'Use fx_convert_currency when you want the converted amount; use this tool when you only need the rate number.' No guidance is given against fx_get_rates (plural) or fx_get_timeseries, which are the other plausible alternatives for rate retrieval, so the when-not coverage is partial.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fx_get_ratesFx Get RatesARead-onlyIdempotentInspect
Get all available exchange rates for one base currency in a single snapshot. Useful for bulk comparison and seeding downstream tools. Returns a map of quote currency → rate, the actual snapshot date, and whether a historical date was snapped from a weekend/holiday to the prior business day. Optionally filter to a subset of quote currencies via symbols. Listing the base currency itself in symbols is accepted and returns a rate of 1 for it.
| Name | Required | Description | Default |
|---|---|---|---|
| date | No | ISO 8601 date (YYYY-MM-DD). Omit for the latest available rate. Frankfurter v1 data starts 1999-01-04. Future dates are not supported. | |
| symbols | No | Optional list of quote currency codes to filter the response. When provided, must contain at least one currency code — omit the field entirely, not an empty array, to return all ~30 supported currencies (the base is not among them). Including base_currency here is valid — it comes back with a rate of 1. | |
| base_currency | Yes | ISO 4217 base currency code (e.g. USD). Call fx_list_currencies to get valid codes. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | Present when the call failed. Absent on success. |
| rates | No | Map of quote currency code → exchange rate (units of quote per 1 base). |
| source | No | Always "Frankfurter v1" — the upstream data provider. |
| rate_date | No | Actual date of the rates. May differ from requested date on weekends/holidays — daily observations may skip non-publication days; the API silently snaps to the prior business day. |
| rate_type | No | Always "Daily rate (Frankfurter v1)" — these are reference rates, not tradeable bid/ask. |
| date_snapped | No | True when the API returned a different date than requested — Frankfurter v1 snaps weekend/holiday requests to the prior business day. Always false when date is omitted. |
| base_currency | No | The base currency code. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent and open-world, so the description adds value by disclosing the weekend/holiday snap-to-prior-business-day behavior and the actual snapshot date returned — a non-obvious trait not visible in annotations. It does not cover auth or rate-limit behavior, but the key semantic surprise is disclosed.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four tight sentences, front-loaded with purpose, followed by usage, return shape, and the one non-obvious parameter nuance. No filler, though the return-shape sentence overlaps with the existing output schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The output schema plus annotations cover return structure and safety profile, and the description adds the snapshot/snap-date nuance. What remains uncovered is error behavior for invalid currency codes and the data source, which are minor given 100% schema coverage.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and independently documents date, symbols (including the empty-array restriction and base-inclusion behavior), and base_currency. The description restates the symbols behavior rather than extending it, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a precise verb and resource plus scope ('all available exchange rates for one base currency in a single snapshot'), which implicitly but effectively separates it from fx_get_rate (single pair) and fx_get_timeseries (series over time). An agent can tell what this returns without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Useful for bulk comparison and seeding downstream tools' gives a use context, but there is no explicit when-to-use-this-vs-sibling guidance (e.g. use fx_get_rate for a single pair, fx_get_timeseries for history). Usage is only implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fx_get_timeseriesFx Get TimeseriesARead-onlyIdempotentInspect
Get historical daily Frankfurter v1 rates for a currency pair over an inclusive date range. Rates use same-date EUR legs without intermediate rounding; only days with both currencies appear, and a self-pair is 1 on actual observation dates. Historical membership includes retired currencies. Empty non-publication windows succeed with a notice. summary covers every observation in the requested interval before paging or staging: unweighted arithmetic mean, extrema with earliest-date ties, endpoints and percentage change, without prescribing accounting policy. Inline rates page at 500 observations; continue with next_start_date and the same end_date. Long ranges (>90 days by default) stage when they exceed the 40,000-character preview budget. An explicit canvas_id always stages, including short or empty ranges, and fails if unavailable or unknown. Each registration returns a unique table_name and preserves older tables. spilled=true returns canvas_id and table_name; call fx_dataframe_describe then fx_dataframe_query for SQL. Without DataCanvas, omit canvas_id to page inline.
| Name | Required | Description | Default |
|---|---|---|---|
| end_date | Yes | ISO 8601 end date (YYYY-MM-DD). Must be >= start_date. Future dates are not supported. | |
| canvas_id | No | Existing canvas ID from a prior call explicitly requesting staging even for short or empty results. Every call creates a uniquely named table. Omit for automatic staging or inline paging. | |
| start_date | Yes | ISO 8601 start date (YYYY-MM-DD). Frankfurter v1 data starts 1999-01-04. The actual first data point may be later if start_date falls on a weekend/holiday. | |
| base_currency | Yes | ISO 4217 base currency code (e.g. USD). Call fx_list_currencies to get valid codes. | |
| quote_currency | Yes | ISO 4217 quote currency code (e.g. EUR). Call fx_list_currencies to get valid codes. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | Present when the call failed. Absent on success. |
| rates | No | Date → rate map in date order, publication days inside the requested range only. Holds every publication day when truncated is false; otherwise the first 500 of an inline page (continue with next_start_date) or the preview of a spilled series (the full series is on the canvas). Empty when the range contains no publication day at all. |
| notice | No | Explains a result that would otherwise look broken or incomplete: an empty series (no publication day in the range); an inline page cut short (the start_date to continue from, plus why a long range was not staged when DataCanvas is turned off); or a spilled series (the canvas and table it was staged to, and the tools that read it). |
| source | No | Always "Frankfurter v1" — the upstream data provider. |
| spilled | No | True when the complete result was staged on DataCanvas, either explicitly through canvas_id or automatically after exceeding the threshold and preview budget. |
| summary | No | Statistics over the full clipped requested range before paging or staging; omitted for empty ranges. Continuations summarize their own requested interval. |
| end_date | No | Last publication date in the requested range. Always inside the requested range — earlier than the requested end when that day had no Frankfurter v1 fix, and equal to it when the series is empty. Later than the last key in rates when truncated is true. |
| canvas_id | No | Canvas ID — present when spilled is true. Pass to fx_dataframe_describe to inspect the staged table, then to fx_dataframe_query to run SQL. |
| rate_type | No | Always "Daily rate (Frankfurter v1)" — these are reference rates, not tradeable bid/ask. |
| truncated | No | True when rates holds only part of the series: an inline page (next_start_date continues it) or a spilled preview (spilled is true). False when rates holds every publication day in the requested range. |
| rate_count | No | Total publication days in the requested range (the start_date through end_date passed on this call) — not the number of entries in rates, which is smaller when truncated is true. On a continuation call it counts the days from that start_date onward. |
| start_date | No | First publication date in the requested range, and the first key in rates. Always inside the requested range — later than the requested start when that day had no Frankfurter v1 fix, and equal to it when the series is empty. |
| table_name | No | Canvas table name — present when spilled is true. Use it as the FROM target in fx_dataframe_query SQL; fx_dataframe_describe lists its columns. |
| base_currency | No | Base currency code. |
| quote_currency | No | Quote currency code. |
| next_start_date | No | Present only when an inline page was cut short: the first publication date not in rates. Pass it as start_date with the same end_date to get the next page. Absent on the final page. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the readOnly/idempotent annotations with substantive semantics: same-date EUR legs without intermediate rounding, only days with both currencies returned, self-pair equals 1, retired currencies included, empty non-publication windows succeed with a notice, and the 40,000-character preview budget triggering staging. It also discloses failure behavior for an unknown canvas_id and that each registration creates a unique, preserved table_name.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The essential scoping and paging facts are front-loaded in the first two sentences, which is good. However, the remainder is a dense wall of run-on clauses mixing edge cases, staging rules, and follow-up tool names, and lines like 'without prescribing accounting policy' are noise that slows an agent scanning for the call decision.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with a published output schema and full annotation coverage, this description covers the remaining burden: paging continuation, staging thresholds, spilled=true payload shape, and the required fx_dataframe_describe/fx_dataframe_query follow-up. An agent has everything needed to call it correctly in either mode.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real meaning on top: canvas_id always forces staging even for empty ranges and fails if unavailable, and end_date is reused unchanged when continuing with next_start_date. The summary and paging semantics further clarify what the parameters produce.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb, resource, and scope: historical daily Frankfurter v1 rates for a currency pair over an inclusive date range. That scope is concrete enough to distinguish it from fx_get_rate/fx_convert_currency by inference, but no sibling is named explicitly, so the agent has to reason about which tool is the right one.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear operating context: page inline at 500 observations with next_start_date, let long ranges stage automatically, pass canvas_id only when staging is desired, and omit it entirely if no DataCanvas is available. It does not mention when to prefer fx_get_rate or fx_convert_currency instead, so the basic tool-selection decision is left implicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fx_list_currenciesFx List CurrenciesARead-onlyIdempotentInspect
List currently supported ISO 4217 currency codes with full names and as_of, the latest served rate date. Use this to disambiguate dollars (USD vs AUD vs CAD). Historical availability is checked against the requested historical snapshot or range and can include retired currencies absent from current discovery.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| as_of | No | Latest served daily rate date for this current-discovery list; historical availability is checked against historical data. |
| count | No | Total number of supported currencies. |
| error | No | Present when the call failed. Absent on success. |
| source | No | Always "Frankfurter v1" — the upstream data provider. |
| currencies | No | All supported currencies, sorted alphabetically by code. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover read-only, idempotency and open-world behavior, and the description adds non-obvious context: results are scoped to the 'latest served rate date' (as_of) and historical lookups can surface retired currencies absent from current discovery. That caveat is genuinely useful. It stops short of describing snapshot/range staleness behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action and return shape, then the disambiguation guidance in the second sentence. The third sentence about historical availability is a slight topic shift and is wordier than necessary, but it carries real caveat value.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return-shape detail is not required, and the description still names the key fields (code, full name, as_of). Combined with the historical-scope caveat, an agent has what it needs to call this correctly; only the snapshot/range scoping mechanics remain implicit.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so the baseline is 4; there is no parameter surface to document and the description does not waste space inventing one.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('List') and resource ('currently supported ISO 4217 currency codes') plus the returned fields (full names, as_of). It is clearly distinguishable from the rate/conversion siblings, which all operate on specific pairs rather than discovery.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit use case ('use this to disambiguate dollars (USD vs AUD vs CAD)'), which tells an agent when to prefer this over fx_get_rate or fx_convert_currency. No when-not-to-use or prerequisite conditions are stated, keeping it below a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
6 tool updates
- Changed
fx_convert_currency6 fields changed- changed
Input schema / properties / date / descriptionPrevious value: -"ISO 8601 date (YYYY-MM-DD) for a historical rate. Omit for the latest available rate. ECB data starts 1999-01-04. Future dates are not supported."New value: +"ISO 8601 date (YYYY-MM-DD) for a historical rate. Omit for the latest available rate. Frankfurter v1 data starts 1999-01-04. Future dates are not supported." - added
Input schema / properties / date / patternAdded value: +"^(?:\\d{4}-\\d{2}-\\d{2}|latest)$" - changed
Output schema / properties / date_snapped / descriptionPrevious value: -"True when the API returned a different date than requested — ECB silently snaps weekend/holiday requests to the prior business day."New value: +"True when the API returned a different date than requested — Frankfurter v1 snaps weekend/holiday requests to the prior business day." - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `invalid_date_format`: date is not a real calendar date written as YYYY-MM-DD. `unsupported_currency`: base_currency or quote_currency is not in the ECB currency set. `date_out_of_range`: date is before 1999-01-04 or in the future. `upstream_no_data`: Both currencies are supported but the ECB published no rate for this date. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `invalid_date_format`: date is not a real calendar date written as YYYY-MM-DD. `unsupported_currency`: base_currency or quote_currency is not available in the current discovery or served historical data. `date_out_of_range`: date is before 1999-01-04 or in the future. `upstream_no_data`: Both currencies are supported but the Frankfurter v1 published no rate for this date. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / rate_type / descriptionPrevious value: -"Always \"ECB reference (mid-market)\" — these are reference rates, not tradeable bid/ask."New value: +"Always \"Daily rate (Frankfurter v1)\" — these are reference rates, not tradeable bid/ask." - changed
Output schema / properties / source / descriptionPrevious value: -"Always \"ECB via Frankfurter\" — the upstream data provider."New value: +"Always \"Frankfurter v1\" — the upstream data provider."
- Changed
fx_dataframe_query4 fields changed- changed
Input schema / properties / query / descriptionPrevious value: -"Read-only SQL SELECT statement. Reference tables by the names returned by fx_dataframe_describe or the table_name field from fx_get_timeseries. Example: SELECT date, rate FROM fx_usd_eur WHERE date > '2024-01-01' ORDER BY date"New value: +"Read-only SQL SELECT statement. Reference tables by the names returned by fx_dataframe_describe or the table_name field from fx_get_timeseries. Example: SELECT date, rate FROM <table_name> WHERE date > '2024-01-01' ORDER BY date; replace <table_name> with the returned name, including its unique suffix." - changed
Input schema / properties / row_limit / descriptionPrevious value: -"Most rows to return (1–10000, default 150). When the query produces more, truncated is true — page with ORDER BY <column> LIMIT <n> OFFSET <m> in the SQL, or aggregate, rather than raising this toward the maximum."New value: +"Most rows to return (positive integer, default 150; values above 10000 clamp to 10000 with a notice). When the query produces more, truncated is true — page with ORDER BY <column> LIMIT <n> OFFSET <m> in the SQL, or aggregate, rather than raising this toward the maximum." - changed
Input schema / properties / row_limit / maximumPrevious value: -10000New value: +9007199254740991 - changed
Output schema / properties / notice / descriptionPrevious value: -"Present when truncated is true: how many rows came back and the ORDER BY … LIMIT … OFFSET query shape that fetches the next page."New value: +"Reports requested and effective row limits when clamped, and the ORDER BY … LIMIT … OFFSET query shape when the result is truncated."
- Changed
fx_get_rate6 fields changed- changed
Input schema / properties / date / descriptionPrevious value: -"ISO 8601 date (YYYY-MM-DD). Omit for the latest available rate. ECB data starts 1999-01-04. Future dates are not supported."New value: +"ISO 8601 date (YYYY-MM-DD). Omit for the latest available rate. Frankfurter v1 data starts 1999-01-04. Future dates are not supported." - added
Input schema / properties / date / patternAdded value: +"^(?:\\d{4}-\\d{2}-\\d{2}|latest)$" - changed
Output schema / properties / date_snapped / descriptionPrevious value: -"True when the API returned a different date than requested — ECB silently snaps weekend/holiday requests to the prior business day."New value: +"True when the API returned a different date than requested — Frankfurter v1 snaps weekend/holiday requests to the prior business day." - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `invalid_date_format`: date is not a real calendar date written as YYYY-MM-DD. `unsupported_currency`: base_currency or quote_currency is not in the ECB currency set. `date_out_of_range`: date is before 1999-01-04 or in the future. `upstream_no_data`: Both currencies are supported but the ECB published no rate for this date. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `invalid_date_format`: date is not a real calendar date written as YYYY-MM-DD. `unsupported_currency`: base_currency or quote_currency is not available in the current discovery or served historical data. `date_out_of_range`: date is before 1999-01-04 or in the future. `upstream_no_data`: Both currencies are supported but the Frankfurter v1 published no rate for this date. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / rate_type / descriptionPrevious value: -"Always \"ECB reference (mid-market)\" — these are reference rates, not tradeable bid/ask."New value: +"Always \"Daily rate (Frankfurter v1)\" — these are reference rates, not tradeable bid/ask." - changed
Output schema / properties / source / descriptionPrevious value: -"Always \"ECB via Frankfurter\" — the upstream data provider."New value: +"Always \"Frankfurter v1\" — the upstream data provider."
- Changed
fx_get_rates7 fields changed- changed
Input schema / properties / date / descriptionPrevious value: -"ISO 8601 date (YYYY-MM-DD). Omit for the latest available rate. ECB data starts 1999-01-04. Future dates are not supported."New value: +"ISO 8601 date (YYYY-MM-DD). Omit for the latest available rate. Frankfurter v1 data starts 1999-01-04. Future dates are not supported." - added
Input schema / properties / date / patternAdded value: +"^(?:\\d{4}-\\d{2}-\\d{2}|latest)$" - changed
Output schema / properties / date_snapped / descriptionPrevious value: -"True when the API returned a different date than requested — ECB silently snaps weekend/holiday requests to the prior business day. Always false when date is omitted."New value: +"True when the API returned a different date than requested — Frankfurter v1 snaps weekend/holiday requests to the prior business day. Always false when date is omitted." - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `invalid_date_format`: date is not a real calendar date written as YYYY-MM-DD. `unsupported_currency`: base_currency or an entry in symbols is not in the ECB currency set. `date_out_of_range`: date is before 1999-01-04 or in the future. `upstream_no_data`: Every requested currency is supported but the ECB published no rates for this date. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `invalid_date_format`: date is not a real calendar date written as YYYY-MM-DD. `unsupported_currency`: base_currency or an entry in symbols is not available in the current discovery or served historical data. `date_out_of_range`: date is before 1999-01-04 or in the future. `upstream_no_data`: Every requested currency is supported but the Frankfurter v1 published no rates for this date. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / rate_date / descriptionPrevious value: -"Actual date of the rates. May differ from requested date on weekends/holidays — ECB publishes business days only; the API silently snaps to the prior business day."New value: +"Actual date of the rates. May differ from requested date on weekends/holidays — daily observations may skip non-publication days; the API silently snaps to the prior business day." - changed
Output schema / properties / rate_type / descriptionPrevious value: -"Always \"ECB reference (mid-market)\" — these are reference rates, not tradeable bid/ask."New value: +"Always \"Daily rate (Frankfurter v1)\" — these are reference rates, not tradeable bid/ask." - changed
Output schema / properties / source / descriptionPrevious value: -"Always \"ECB via Frankfurter\" — the upstream data provider."New value: +"Always \"Frankfurter v1\" — the upstream data provider."
- Changed
fx_get_timeseries12 fields changed- changed
Input schema / properties / canvas_id / descriptionPrevious value: -"Optional canvas ID from a prior call. Omit on the first call to start a fresh canvas; pass the returned canvas_id to append tables to an existing canvas."New value: +"Existing canvas ID from a prior call explicitly requesting staging even for short or empty results. Every call creates a uniquely named table. Omit for automatic staging or inline paging." - added
Input schema / properties / end_date / patternAdded value: +"^\\d{4}-\\d{2}-\\d{2}$" - changed
Input schema / properties / start_date / descriptionPrevious value: -"ISO 8601 start date (YYYY-MM-DD). ECB data starts 1999-01-04. The actual first data point may be later if start_date falls on a weekend/holiday."New value: +"ISO 8601 start date (YYYY-MM-DD). Frankfurter v1 data starts 1999-01-04. The actual first data point may be later if start_date falls on a weekend/holiday." - added
Input schema / properties / start_date / patternAdded value: +"^\\d{4}-\\d{2}-\\d{2}$" - changed
Output schema / properties / end_date / descriptionPrevious value: -"Last publication date in the requested range. Always inside the requested range — earlier than the requested end when that day had no ECB fix, and equal to it when the series is empty. Later than the last key in rates when truncated is true."New value: +"Last publication date in the requested range. Always inside the requested range — earlier than the requested end when that day had no Frankfurter v1 fix, and equal to it when the series is empty. Later than the last key in rates when truncated is true." - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `invalid_date_format`: start_date or end_date is not a real calendar date written as YYYY-MM-DD. `unsupported_currency`: base_currency or quote_currency is not in the ECB currency set. `date_out_of_range`: start_date is before 1999-01-04 or end_date is in the future. `invalid_range`: start_date is after end_date. `upstream_no_data`: Both currencies are supported but the ECB published no rates across this range. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `invalid_date_format`: start_date or end_date is not a real calendar date written as YYYY-MM-DD. `unsupported_currency`: base_currency or quote_currency is not available in the current discovery or served historical data. `date_out_of_range`: start_date is before 1999-01-04 or end_date is in the future. `invalid_range`: start_date is after end_date. `upstream_no_data`: Both currencies are supported but the Frankfurter v1 published no rates across this range. `canvas_not_found`: An explicitly supplied canvas_id does not exist or has expired. `canvas_unavailable`: canvas_id requests staging on a deployment with DataCanvas disabled. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / error / properties / data / properties / reason / examplesPrevious value: -[ - "invalid_date_format", - "unsupported_currency", - "date_out_of_range", - "invalid_range", - "upstream_no_data" -]New value: +[ + "invalid_date_format", + "unsupported_currency", + "date_out_of_range", + "invalid_range", + "upstream_no_data", + "canvas_not_found", + "canvas_unavailable" +] - changed
Output schema / properties / rate_type / descriptionPrevious value: -"Always \"ECB reference (mid-market)\" — these are reference rates, not tradeable bid/ask."New value: +"Always \"Daily rate (Frankfurter v1)\" — these are reference rates, not tradeable bid/ask." - changed
Output schema / properties / source / descriptionPrevious value: -"Always \"ECB via Frankfurter\" — the upstream data provider."New value: +"Always \"Frankfurter v1\" — the upstream data provider." - changed
Output schema / properties / spilled / descriptionPrevious value: -"True when the range exceeded the staging threshold and the series exceeded the inline preview budget, so the full result was staged on DataCanvas."New value: +"True when the complete result was staged on DataCanvas, either explicitly through canvas_id or automatically after exceeding the threshold and preview budget." - changed
Output schema / properties / start_date / descriptionPrevious value: -"First publication date in the requested range, and the first key in rates. Always inside the requested range — later than the requested start when that day had no ECB fix, and equal to it when the series is empty."New value: +"First publication date in the requested range, and the first key in rates. Always inside the requested range — later than the requested start when that day had no Frankfurter v1 fix, and equal to it when the series is empty." - added
Output schema / properties / summaryAdded value: +{ + "additionalProperties": false, + "description": "Statistics over the full clipped requested range before paging or staging; omitted for empty ranges. Continuations summarize their own requested interval.", + "properties": { + "change_pct": { + "description": "Percentage change from first to last observation.", + "type": "number" + }, + "first": { + "additionalProperties": false, + "description": "Chronologically first observation.", + "properties": { + "date": { + "description": "First publication date.", + "type": "string" + }, + "rate": { + "description": "First rate.", + "type": "number" + } + }, + "required": [ + "rate", + "date" + ], + "type": "object" + }, + "last": { + "additionalProperties": false, + "description": "Chronologically last observation.", + "properties": { + "date": { + "description": "Last publication date.", + "type": "string" + }, + "rate": { + "description": "Last rate.", + "type": "number" + } + }, + "required": [ + "rate", + "date" + ], + "type": "object" + }, + "max": { + "additionalProperties": false, + "description": "Maximum observation.", + "properties": { + "date": { + "description": "Earliest date with the highest rate.", + "type": "string" + }, + "rate": { + "description": "Highest rate.", + "type": "number" + } + }, + "required": [ + "rate", + "date" + ], + "type": "object" + }, + "mean": { + "description": "Unweighted arithmetic mean of all daily observations in the requested range; not an accounting-policy prescription.", + "type": "number" + }, + "min": { + "additionalProperties": false, + "description": "Minimum observation.", + "properties": { + "date": { + "description": "Earliest date with the lowest rate.", + "type": "string" + }, + "rate": { + "description": "Lowest rate.", + "type": "number" + } + }, + "required": [ + "rate", + "date" + ], + "type": "object" + } + }, + "required": [ + "mean", + "min", + "max", + "first", + "last", + "change_pct" + ], + "type": "object" +}
- Changed
fx_list_currencies3 fields changed- changed
Output schema / anyOfPrevious value: -[ - { - "not": { - "required": [ - "error" - ] - }, - "required": [ - "currencies", - "count", - "source" - ] - }, - { - "required": [ - "error" - ] - } -]New value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "currencies", + "count", + "as_of", + "source" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / as_ofAdded value: +{ + "description": "Latest served daily rate date for this current-discovery list; historical availability is checked against historical data.", + "type": "string" +} - changed
Output schema / properties / source / descriptionPrevious value: -"Always \"ECB via Frankfurter\" — the upstream data provider."New value: +"Always \"Frankfurter v1\" — the upstream data provider."
1 tool update
- Changed
fx_get_timeseries2 fields changed- changed
Output schema / properties / notice / descriptionPrevious value: -"Explains a result that would otherwise look broken or incomplete: an empty series (no publication day in the range); an inline page cut short (the start_date to continue from, plus why a long range was not staged when DataCanvas is not configured); or a spilled series (the canvas and table it was staged to, and the tools that read it)."New value: +"Explains a result that would otherwise look broken or incomplete: an empty series (no publication day in the range); an inline page cut short (the start_date to continue from, plus why a long range was not staged when DataCanvas is turned off); or a spilled series (the canvas and table it was staged to, and the tools that read it)." - changed
Output schema / properties / spilled / descriptionPrevious value: -"True when the full result was staged on the DataCanvas (range exceeded threshold)."New value: +"True when the range exceeded the staging threshold and the series exceeded the inline preview budget, so the full result was staged on DataCanvas."
3 tool updates
- Changed
fx_dataframe_describe1 field changed- added
Input schema / properties / canvas_id / patternAdded value: +"^[A-Za-z0-9_-]{10}$"
- Changed
fx_dataframe_query1 field changed- added
Input schema / properties / canvas_id / patternAdded value: +"^[A-Za-z0-9_-]{10}$"
- Changed
fx_get_timeseries1 field changed- added
Input schema / properties / canvas_id / patternAdded value: +"^[A-Za-z0-9_-]{10}$"
2 tool updates
- Changed
fx_dataframe_query5 fields changed- added
Input schema / properties / row_limitAdded value: +{ + "default": 150, + "description": "Most rows to return (1–10000, default 150). When the query produces more, truncated is true — page with ORDER BY <column> LIMIT <n> OFFSET <m> in the SQL, or aggregate, rather than raising this toward the maximum.", + "maximum": 10000, + "minimum": 1, + "type": "integer" +} - added
Output schema / properties / noticeAdded value: +{ + "description": "Present when truncated is true: how many rows came back and the ORDER BY … LIMIT … OFFSET query shape that fetches the next page.", + "type": "string" +} - changed
Output schema / properties / row_count / descriptionPrevious value: -"Rows returned. Equals the materialized row count; when truncated is true this is the row cap, not the full result size. Narrow the SELECT (add WHERE/LIMIT or aggregate) to see all rows."New value: +"Rows returned — always the length of rows. When truncated is true this equals row_limit, not the full result size." - changed
Output schema / properties / rows / descriptionPrevious value: -"Result rows, capped at the canvas row limit (default 10 000). Each key is a column name from the query."New value: +"Result rows, at most row_limit (default 150). Each key is a column name from the query." - changed
Output schema / properties / truncated / descriptionPrevious value: -"True when the query produced more rows than the canvas row cap and the result was capped. Refine the query to materialize the complete result."New value: +"True when the query produced more rows than row_limit and rows holds only the first row_limit of them. Fetch the rest with ORDER BY <column> LIMIT <n> OFFSET <m> — ORDER BY is required for deterministic paging — or aggregate to shrink the result."
- Changed
fx_get_timeseries10 fields changed- changed
Output schema / anyOfPrevious value: -[ - { - "not": { - "required": [ - "error" - ] - }, - "required": [ - "base_currency", - "quote_currency", - "start_date", - "end_date", - "rates", - "rate_count", - "rate_type", - "source", - "spilled" - ] - }, - { - "required": [ - "error" - ] - } -]New value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "base_currency", + "quote_currency", + "start_date", + "end_date", + "rates", + "rate_count", + "truncated", + "rate_type", + "source", + "spilled" + ] + }, + { + "required": [ + "error" + ] + } +] - changed
Output schema / properties / canvas_id / descriptionPrevious value: -"Canvas ID — present when spilled is true. Pass to fx_dataframe_query."New value: +"Canvas ID — present when spilled is true. Pass to fx_dataframe_describe to inspect the staged table, then to fx_dataframe_query to run SQL." - changed
Output schema / properties / end_date / descriptionPrevious value: -"Last date in the returned series. Always inside the requested range — earlier than the requested end when that day had no ECB fix, and equal to it when the series is empty."New value: +"Last publication date in the requested range. Always inside the requested range — earlier than the requested end when that day had no ECB fix, and equal to it when the series is empty. Later than the last key in rates when truncated is true." - added
Output schema / properties / next_start_dateAdded value: +{ + "description": "Present only when an inline page was cut short: the first publication date not in rates. Pass it as start_date with the same end_date to get the next page. Absent on the final page.", + "type": "string" +} - changed
Output schema / properties / notice / descriptionPrevious value: -"Explains a result that would otherwise look broken — an empty series, or a long range that stayed inline because DataCanvas is not configured."New value: +"Explains a result that would otherwise look broken or incomplete: an empty series (no publication day in the range); an inline page cut short (the start_date to continue from, plus why a long range was not staged when DataCanvas is not configured); or a spilled series (the canvas and table it was staged to, and the tools that read it)." - changed
Output schema / properties / rate_count / descriptionPrevious value: -"Total number of data points (publication days) inside the requested range."New value: +"Total publication days in the requested range (the start_date through end_date passed on this call) — not the number of entries in rates, which is smaller when truncated is true. On a continuation call it counts the days from that start_date onward." - changed
Output schema / properties / rates / descriptionPrevious value: -"Date → rate map for the inline result. Publication days inside the requested range only. Truncated to a preview when the result was spilled to canvas; empty when the range contains no publication day at all."New value: +"Date → rate map in date order, publication days inside the requested range only. Holds every publication day when truncated is false; otherwise the first 500 of an inline page (continue with next_start_date) or the preview of a spilled series (the full series is on the canvas). Empty when the range contains no publication day at all." - changed
Output schema / properties / start_date / descriptionPrevious value: -"First date in the returned series. Always inside the requested range — later than the requested start when that day had no ECB fix, and equal to it when the series is empty."New value: +"First publication date in the requested range, and the first key in rates. Always inside the requested range — later than the requested start when that day had no ECB fix, and equal to it when the series is empty." - changed
Output schema / properties / table_name / descriptionPrevious value: -"Canvas table name — present when spilled is true. Use in fx_dataframe_query SQL."New value: +"Canvas table name — present when spilled is true. Use it as the FROM target in fx_dataframe_query SQL; fx_dataframe_describe lists its columns." - added
Output schema / properties / truncatedAdded value: +{ + "description": "True when rates holds only part of the series: an inline page (next_start_date continues it) or a spilled preview (spilled is true). False when rates holds every publication day in the requested range.", + "type": "boolean" +}
3 tool updates
- Changed
fx_convert_currency1 field changed- changed
Input schema / properties / quote_currency / descriptionPrevious value: -"ISO 4217 target currency code (e.g. EUR). The amount will be expressed in this currency."New value: +"ISO 4217 target currency code (e.g. EUR). The amount will be expressed in this currency. Call fx_list_currencies to get valid codes."
- Changed
fx_get_rate1 field changed- changed
Input schema / properties / quote_currency / descriptionPrevious value: -"ISO 4217 quote currency code (e.g. EUR). The rate is expressed as \"how many quote units per 1 base unit\"."New value: +"ISO 4217 quote currency code (e.g. EUR). The rate is expressed as \"how many quote units per 1 base unit\". Call fx_list_currencies to get valid codes."
- Changed
fx_get_rates4 fields changed- changed
Input schema / properties / symbols / descriptionPrevious value: -"Optional list of quote currency codes to filter the response. Omit to return all ~30 supported currencies (the base is not among them). Including base_currency here is valid — it comes back with a rate of 1."New value: +"Optional list of quote currency codes to filter the response. When provided, must contain at least one currency code — omit the field entirely, not an empty array, to return all ~30 supported currencies (the base is not among them). Including base_currency here is valid — it comes back with a rate of 1." - added
Input schema / properties / symbols / minItemsAdded value: +1 - changed
Output schema / anyOfPrevious value: -[ - { - "not": { - "required": [ - "error" - ] - }, - "required": [ - "base_currency", - "rate_date", - "rates", - "rate_type", - "source" - ] - }, - { - "required": [ - "error" - ] - } -]New value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "base_currency", + "rate_date", + "rates", + "date_snapped", + "rate_type", + "source" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / date_snappedAdded value: +{ + "description": "True when the API returned a different date than requested — ECB silently snaps weekend/holiday requests to the prior business day. Always false when date is omitted.", + "type": "boolean" +}
7 tool updates
- Changed
fx_convert_currency6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "base_currency", + "quote_currency", + "base_amount", + "quote_amount", + "rate", + "rate_date", + "date_snapped", + "rate_type", + "source" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `invalid_date_format`: date is not a real calendar date written as YYYY-MM-DD. `unsupported_currency`: base_currency or quote_currency is not in the ECB currency set. `date_out_of_range`: date is before 1999-01-04 or in the future. `upstream_no_data`: Both currencies are supported but the ECB published no rate for this date. Other values are possible when a failure originates below the handler.", + "examples": [ + "invalid_date_format", + "unsupported_currency", + "date_out_of_range", + "upstream_no_data" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "base_currency", - "quote_currency", - "base_amount", - "quote_amount", - "rate", - "rate_date", - "date_snapped", - "rate_type", - "source" -]
- Changed
fx_dataframe_describe6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "canvas_id", + "tables", + "expires_at" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `canvas_not_found`: canvas_id does not exist or has been evicted. Other values are possible when a failure originates below the handler.", + "examples": [ + "canvas_not_found" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "canvas_id", - "tables", - "expires_at" -]
- Changed
fx_dataframe_query6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "rows", + "row_count", + "truncated", + "canvas_id" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `canvas_not_found`: canvas_id does not exist or has been evicted. `missing_table`: The SQL references a table that is not staged on this canvas, or whose TTL expired. `invalid_query`: SQL is not a SELECT, references unknown columns, or has a syntax error. Other values are possible when a failure originates below the handler.", + "examples": [ + "canvas_not_found", + "missing_table", + "invalid_query" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "rows", - "row_count", - "truncated", - "canvas_id" -]
- Changed
fx_get_rate6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "base_currency", + "quote_currency", + "rate", + "rate_date", + "date_snapped", + "rate_type", + "source" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `invalid_date_format`: date is not a real calendar date written as YYYY-MM-DD. `unsupported_currency`: base_currency or quote_currency is not in the ECB currency set. `date_out_of_range`: date is before 1999-01-04 or in the future. `upstream_no_data`: Both currencies are supported but the ECB published no rate for this date. Other values are possible when a failure originates below the handler.", + "examples": [ + "invalid_date_format", + "unsupported_currency", + "date_out_of_range", + "upstream_no_data" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "base_currency", - "quote_currency", - "rate", - "rate_date", - "date_snapped", - "rate_type", - "source" -]
- Changed
fx_get_rates6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "base_currency", + "rate_date", + "rates", + "rate_type", + "source" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `invalid_date_format`: date is not a real calendar date written as YYYY-MM-DD. `unsupported_currency`: base_currency or an entry in symbols is not in the ECB currency set. `date_out_of_range`: date is before 1999-01-04 or in the future. `upstream_no_data`: Every requested currency is supported but the ECB published no rates for this date. Other values are possible when a failure originates below the handler.", + "examples": [ + "invalid_date_format", + "unsupported_currency", + "date_out_of_range", + "upstream_no_data" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "base_currency", - "rate_date", - "rates", - "rate_type", - "source" -]
- Changed
fx_get_timeseries6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "base_currency", + "quote_currency", + "start_date", + "end_date", + "rates", + "rate_count", + "rate_type", + "source", + "spilled" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `invalid_date_format`: start_date or end_date is not a real calendar date written as YYYY-MM-DD. `unsupported_currency`: base_currency or quote_currency is not in the ECB currency set. `date_out_of_range`: start_date is before 1999-01-04 or end_date is in the future. `invalid_range`: start_date is after end_date. `upstream_no_data`: Both currencies are supported but the ECB published no rates across this range. Other values are possible when a failure originates below the handler.", + "examples": [ + "invalid_date_format", + "unsupported_currency", + "date_out_of_range", + "invalid_range", + "upstream_no_data" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "base_currency", - "quote_currency", - "start_date", - "end_date", - "rates", - "rate_count", - "rate_type", - "source", - "spilled" -]
- Changed
fx_list_currencies6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "currencies", + "count", + "source" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode.", + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "currencies", - "count", - "source" -]
3 tool updates
- Changed
fx_convert_currency2 fields changed- changed
Input schema / properties / amount / descriptionPrevious value: -"Amount in the base currency to convert. Must be a positive number."New value: +"Amount in the base currency to convert. Must be greater than zero." - added
Input schema / properties / amount / exclusiveMinimumAdded value: +0
- Changed
fx_get_rates1 field changed- changed
Input schema / properties / symbols / descriptionPrevious value: -"Optional list of quote currency codes to filter the response. Omit to return all ~30 supported currencies."New value: +"Optional list of quote currency codes to filter the response. Omit to return all ~30 supported currencies (the base is not among them). Including base_currency here is valid — it comes back with a rate of 1."
- Changed
fx_get_timeseries5 fields changed- changed
Output schema / properties / end_date / descriptionPrevious value: -"Actual last date in the returned series (may differ from requested if that date had no ECB fix)."New value: +"Last date in the returned series. Always inside the requested range — earlier than the requested end when that day had no ECB fix, and equal to it when the series is empty." - added
Output schema / properties / noticeAdded value: +{ + "description": "Explains a result that would otherwise look broken — an empty series, or a long range that stayed inline because DataCanvas is not configured.", + "type": "string" +} - changed
Output schema / properties / rate_count / descriptionPrevious value: -"Total number of data points (business days) in the range."New value: +"Total number of data points (publication days) inside the requested range." - changed
Output schema / properties / rates / descriptionPrevious value: -"Date → rate map for the inline result. Business days only. Empty when the result was spilled to canvas."New value: +"Date → rate map for the inline result. Publication days inside the requested range only. Truncated to a preview when the result was spilled to canvas; empty when the range contains no publication day at all." - changed
Output schema / properties / start_date / descriptionPrevious value: -"Actual first date in the returned series (may differ from requested if that date had no ECB fix)."New value: +"First date in the returned series. Always inside the requested range — later than the requested start when that day had no ECB fix, and equal to it when the series is empty."
1 tool update
- Changed
fx_dataframe_query3 fields changed- changed
Output schema / properties / row_count / descriptionPrevious value: -"Total rows in the full result set before the row cap was applied."New value: +"Rows returned. Equals the materialized row count; when truncated is true this is the row cap, not the full result size. Narrow the SELECT (add WHERE/LIMIT or aggregate) to see all rows." - added
Output schema / properties / truncatedAdded value: +{ + "description": "True when the query produced more rows than the canvas row cap and the result was capped. Refine the query to materialize the complete result.", + "type": "boolean" +} - changed
Output schema / requiredPrevious value: -[ - "rows", - "row_count", - "canvas_id" -]New value: +[ + "rows", + "row_count", + "truncated", + "canvas_id" +]
7 tool updates
- First observed
fx_convert_currency - First observed
fx_dataframe_describe - First observed
fx_dataframe_query - First observed
fx_get_rate - First observed
fx_get_rates - First observed
fx_get_timeseries - First observed
fx_list_currencies
Related MCP Connectors
Fetch latest and historical currency exchange rates from Frankfurter. Convert amounts between curr…
Latest and historical ECB foreign-exchange reference rates for 30+ currencies, via Frankfurter.
Frankfurter MCP — wraps Frankfurter API (api.frankfurter.dev)
Convert currencies on the daily European Central Bank reference rates. No API key, no signup.
Related MCP Servers
- AlicenseAqualityDmaintenanceProvides access to currency exchange rates and conversion tools using the Frankfurter API, including latest rates, historical data, and time series from sources like the European Central Bank.5MIT
- AlicenseNot gradedqualityBmaintenanceEnables currency exchange rate lookups, amount conversion, historical rate retrieval, and listing of supported currencies via the Frankfurter API.373 npmMIT
- FlicenseNot gradedqualityDmaintenanceProvides real-time and historical foreign exchange rates for 31+ currencies, enabling currency conversion, historical rate lookups, and time series analysis using data from the Frankfurter API.-
- AlicenseAqualityBmaintenanceProvides live and historical foreign-exchange rates from the European Central Bank via Frankfurter API, enabling currency conversion, rate lookup, and trend analysis without API keys.54 npmMIT
Glama MCP Gateway
Add one secure layer between your agents and this server.