Skip to main content
Glama
andresFLZ

mcp-trm-colombia

by andresFLZ
README.md
# mcp-trm-colombia

**The problem:** ask any LLM what a dollar is worth in Colombian pesos and it will confidently make one up — a number from its training data, months stale, with no source.

**What this does:** gives the model the *official* rate. An MCP server over Colombia's TRM (*Tasa Representativa del Mercado*), published by the Superintendencia Financiera as open data. Every answer carries the exact rate used and the dates it is valid for, so it can be audited.

**Try it:**

```bash
uvx --from git+https://github.com/andresFLZ/mcp-trm-colombia mcp-trm-colombia
```

https://github.com/user-attachments/assets/a2e5ba43-9727-4638-a4df-f68cbd58c455

---

## Why an MCP server instead of a plain API call

Because the awkward parts are not the HTTP request. They are the rules a model
gets wrong on its own:

- **One rate covers several days.** The rate published on a Friday is normally
  valid through Sunday, and holidays stretch it further. Asking "the rate on
  Saturday" and getting nothing back is a bug; this returns the rate *in force*,
  with its window.
- **"Today" is ambiguous.** Colombia is UTC-5 with no DST. A model reasoning in
  UTC asks for tomorrow and gets "not published yet". Handled here, once.
- **Old rates never change; today's still can.** History is cached forever, the
  current one never is.
- **Every number needs provenance.** A conversion that does not say which rate
  it used cannot be checked by anyone.

## Tools

| Tool | What it answers |
|---|---|
| `get_trm(date?)` | The official rate in force on a date, with its validity window |
| `convert(amount, currency, date?)` | USD ⇄ COP at the rate for that date, reporting the rate used |
| `trm_series(start_date, end_date)` | Every rate in a period, plus min, max, average and the change across it |
| `today()` | Today's date *in Colombia* and today's rate |

All dates are ISO `YYYY-MM-DD`. `date` defaults to today in Bogotá.

### Errors are answers, not stack traces

A tool that throws leaves the model guessing. Every failure comes back as data,
including the one field it actually needs — whether trying again could help:

```json
{
  "ok": false,
  "error": "source_unavailable",
  "message": "datos.gov.co did not respond in time. It is an open-data portal and it is occasionally slow; retrying usually works.",
  "retryable": true
}
```

| `error` | Means | `retryable` |
|---|---|---|
| `invalid_input` | Bad date, unknown currency, negative amount | `false` |
| `not_published` | Valid date, no rate published for it | `false` |
| `source_unavailable` | datos.gov.co timed out, errored, or sent something unreadable | `true` |

## Install

Requires Python 3.10+ and the MCP Python SDK 2.x.

```bash
git clone https://github.com/andresFLZ/mcp-trm-colombia
cd mcp-trm-colombia
pip install -e ".[dev]"
```

### Use it from an MCP client

This repo ships a `.mcp.json`, so a client opened in this directory finds the
server with no configuration at all. For Claude Desktop, add this to
`claude_desktop_config.json` and restart it:

```json
{
  "mcpServers": {
    "trm-colombia": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/andresFLZ/mcp-trm-colombia", "mcp-trm-colombia"]
    }
  }
}
```

<details>
<summary>Running from a local clone instead</summary>

```json
{
  "mcpServers": {
    "trm-colombia": {
      "command": "python",
      "args": ["-m", "mcp_trm_colombia.server"],
      "env": { "PYTHONPATH": "/absolute/path/to/mcp-trm-colombia/src" }
    }
  }
}
```
</details>

Then ask, in plain language:

> *"I invoiced USD 1,850 on August 15th. How many pesos was that at the official rate?"*
> *"How much did the peso move against the dollar this month?"*

## Data source

[datos.gov.co dataset `32sa-8pi3`](https://www.datos.gov.co/resource/32sa-8pi3.json) —
*Tasa de Cambio Representativa del Mercado Histórico*, published by the
Superintendencia Financiera de Colombia. Public, no API key, series starts
**1991-12-02**.

## Tests

31 tests. 28 run offline against a mock transport, so the suite is
deterministic and does not depend on a government portal being up:

```bash
pytest              # 28 offline tests
pytest -m live      # 3 more, against the real dataset
```

The offline set deliberately covers the failure paths, not just the happy one:
HTTP 503, timeouts, malformed bodies, rows with missing fields, dates before the
series begins, dates too far ahead, reversed ranges, negative amounts, unknown
currencies — and that history is cached while today's rate is not.

## Design notes

- **`trm.py` has no MCP import.** The domain logic is plain Python and is tested
  on its own; `server.py` only adapts it. Swapping the transport touches one file.
- **`Decimal`, never `float`, for money.** Rounding happens once, at the edge.
- **The cache follows the data.** A settled past rate is immutable, so it is kept
  forever. Today's can still be superseded, so it is re-read every time.
- **Errors are a taxonomy, not one catch-all.** "You asked for something
  impossible" and "the source is down" need different reactions from the caller.
- **Built against MCP SDK 2.x** (`MCPServer`). The 1.x name for the same class
  was `FastMCP`.

## License

MIT — see [LICENSE](LICENSE).

TDQS

A4.2/5.0

Scored across 4 tools

Disambiguation4/5

Most tools are clearly distinct: get_trm fetches a single rate, convert performs currency conversion, and trm_series returns period statistics. There is mild overlap between get_trm and today, since both can supply today's rate, but the descriptions clarify that today is a convenience wrapper for date anchoring.

Naming Consistency3/5

The names are readable and lowercase, but they do not follow a single pattern: get_trm uses verb_noun, convert is a bare verb, trm_series is a noun phrase, and today is a standalone time reference. This mixed style is understandable but not fully consistent.

Tool Count5/5

Four tools is well-scoped for a specialized TRM server. Each tool addresses a distinct need: single rate lookup, conversion, historical series analysis, and current date context, with no redundant bulk.

Completeness5/5

The server covers the core domain completely: fetching current or historical rates, converting between USD and COP, and obtaining series summaries with statistics. The inclusion of today's date fills a practical gap, and there are no obvious missing operations for this narrow purpose.

Maintenance

ActivityMaintained
ResponsivenessNo issues