Files
Claude 9ab60f21e6 Add plain-text/CSV formats, SVG price badges, and agent endpoints
Lean into coinbin's actual differentiator — ergonomics — since the data is now
a commodity. All additive; JSON stays the default so existing clients are
unaffected.

- Content negotiation: ?format=text returns a bare number (great for shells),
  ?format=csv returns CSV (great for Google Sheets IMPORTDATA); also honored
  via the Accept header. Applied to coin, value, and conversion endpoints;
  /coins gets a full CSV table.
- SVG price badges at /<coin>/badge.svg (shields.io-style, colored by 24h
  change, supports ?vs= and ?label=).
- LLM/agent support: /openapi.json spec, /llms.txt guide, and a standalone
  MCP server (mcp_server.py) exposing get_price/convert/list_coins behind a
  new optional `agent` extra.
2026-06-17 02:33:43 +00:00

84 lines
2.2 KiB
Python

"""Coinbin MCP server.
Exposes the same keyless crypto data as MCP tools so LLM agents can use it
directly. Run it over stdio:
uv run --extra agent python mcp_server.py
Requires the optional `agent` dependency group (the `mcp` SDK).
"""
from scraper import Coin, get_coins, price_in, convert_to_decimal
try:
from mcp.server.fastmcp import FastMCP
except ImportError: # pragma: no cover
raise SystemExit(
"The MCP SDK is not installed. Install the agent extra:\n"
" uv sync --extra agent"
)
mcp = FastMCP("coinbin")
@mcp.tool()
def get_price(coin: str, vs: str = "usd") -> dict:
"""Current price and market data for a coin (e.g. 'btc'), in USD by default.
Pass `vs` (e.g. 'eur', 'gbp', 'btc') to also get the price in another
currency.
"""
c = Coin(coin.lower())
result = {
"ticker": c.ticker,
"name": c.name,
"rank": c.rank,
"usd": float(c.usd) if c.usd is not None else None,
"btc": float(c.btc) if c.btc is not None else None,
"market_cap": c.market_cap,
"volume_24h": c.volume_24h,
"change_24h": c.change_24h,
"ath": c.ath,
}
vs = vs.lower()
if vs != "usd":
p = price_in(coin.lower(), vs)
if p is not None:
result["vs"] = vs
result["vs_price"] = float(convert_to_decimal(p))
return result
@mcp.tool()
def convert(amount: float, from_coin: str, to_coin: str) -> dict:
"""Convert an amount of one coin into another (e.g. 2 btc -> eth)."""
c = Coin(from_coin.lower())
rate = c.value(to_coin.lower())
return {
"from": from_coin.lower(),
"to": to_coin.lower(),
"amount": amount,
"exchange_rate": float(rate),
"value": float(convert_to_decimal(rate * convert_to_decimal(amount))),
}
@mcp.tool()
def list_coins(limit: int = 50) -> list:
"""List the top coins by market cap (ticker, name, rank, usd)."""
coins = list(get_coins().values())[:limit]
return [
{
"ticker": c["ticker"],
"name": c["name"],
"rank": c["rank"],
"usd": float(c["usd"]) if c["usd"] is not None else None,
}
for c in coins
]
if __name__ == "__main__":
mcp.run()