sifting/io
Developer Tutorials
6 min readSiftingIO Team

How to give an AI agent market data: MCP server, REST tools, and WebSocket streaming

How to give an AI agent live and historical market data: an MCP server for Claude and Cursor, REST tool definitions, and WebSocket streaming for monitors.

How to give an AI agent market data: MCP server, REST tools, and WebSocket streaming

How do you give an AI agent market data it can trust? A model like Claude or GPT can reason about prices, but it can't know them. Training data ends months before the question gets asked, and nothing inside the model updates when EURUSD moves. Ask an agent without a data tool for the current bid on BTCUSD and it will either decline or, worse, produce a plausible-looking number from memory. Any agent that answers market questions, watches a portfolio, or drafts research notes needs a feed it can call at the moment of the question.

There are three practical ways to wire that in: an MCP server that connects desktop AI tools in a few minutes, REST tools you define yourself for agents running as services, and a WebSocket stream for standing monitors. This post covers all three against the SiftingIO API, with a worked example you can copy.

Why the feed has to be callable, not pasted#

Pasting a price into the prompt goes stale the moment you paste it. Stuffing a CSV of historical bars into context burns tokens and caps how much history the agent can see. The shape that works is a tool: the model decides it needs a number, invokes a function, and reads the response. That maps cleanly onto two transports. On-demand lookups ("what's the latest quote for AAPL") are request and response, which is REST. Standing monitors ("tell me when BTCUSD moves 2%") are push, which is WebSocket.

Agents also multiply the multi-vendor problem. A separate vendor per asset class means a separate tool schema per vendor, and every extra schema is more surface for the model to misuse. SiftingIO serves stocks, forex, crypto, commodities, and on-chain DEX data through one normalized API, so a single tool definition with a venue parameter covers all of them. Each symbol carries one consensus price aggregated from multiple independent venues, with an explicit quality flag when the value is degraded. That matters more for an agent than for a human: a person eyeballing a chart notices a frozen feed, a model reading JSON doesn't unless the feed says so. Treat it as a reference price for analysis and monitoring; it isn't an execution feed.

The market data MCP server: the fastest route#

MCP (Model Context Protocol) is the open standard that lets AI tools call external APIs as model-invokable tools. SiftingIO ships an open-source MCP server that runs locally through npx and works with Claude Desktop, Claude Code, Cursor, and VS Code. For Claude Desktop, one config block:

{
  "mcpServers": {
    "siftingio": {
      "command": "npx",
      "args": ["-y", "siftingio-mcp"],
      "env": { "SIFTING_API_KEY": "sft_..." }
    }
  }
}

For Claude Code it's a one-liner:

claude mcp add siftingio -e SIFTING_API_KEY=sft_... -- npx -y siftingio-mcp

The server exposes 36 tools: live price snapshots for stocks, forex, crypto, and commodities, historical OHLCV bars, SEC filings and XBRL financials, Form 4 insider transactions, 13F institutional holdings, market hours, the economic calendar, and DEX pools and wallet portfolios. The API key sits in your local environment and is never handed to a hosted middleman. A free-tier key works, so "what did AAPL close at yesterday" becomes a verifiable tool call instead of a guess, at no cost.

Defining your own tool: a worked example#

Desktop assistants get MCP. An agent that runs as a service, say a portfolio assistant or a research pipeline, needs tools defined in code. Every major agent framework accepts the same pair: a JSON schema the model sees and a function that runs when the model calls it. The API groups endpoints by purpose, which keeps the functions short: /v1/last/* for snapshots, /v1/hist/* for bars, /v1/fnd/* for fundamentals.

import gzip, json, os, urllib.request

BASE = "https://api.sifting.io/v1"
KEY = os.environ["SIFTING_KEY"]

def _get(path):
    req = urllib.request.Request(
        BASE + path,
        headers={"X-API-Key": KEY, "Accept-Encoding": "gzip"},
    )
    with urllib.request.urlopen(req) as r:
        body = r.read()
        if r.headers.get("Content-Encoding") == "gzip":
            body = gzip.decompress(body)
        return json.loads(body)

def get_quote(venue, symbol):
    """Latest bid/ask. venue: stocks | forex | crypto | commodities."""
    return _get(f"/last/quote/{venue}/{symbol}")

def get_daily_bars(ticker, limit=30):
    """Recent daily OHLCV bars for a US stock."""
    return _get(f"/hist/stocks/{ticker}/bars?interval=1d&limit={limit}")

The schema handed to the model for the first function:

{
  "name": "get_quote",
  "description": "Latest bid/ask quote for a market symbol.",
  "input_schema": {
    "type": "object",
    "properties": {
      "venue": { "type": "string", "enum": ["stocks", "forex", "crypto", "commodities"] },
      "symbol": { "type": "string", "description": "AAPL, EURUSD, BTCUSD, XAUUSD" }
    },
    "required": ["venue", "symbol"]
  }
}

Asked "what's the spread on EURUSD right now", the model calls get_quote("forex", "EURUSD") and reads back bid, ask, and a timestamp. One habit pays off immediately: pass that timestamp through and instruct the agent to state data age in its answers. Live fields carry Unix epoch milliseconds in t. An agent that says "as of 14:02:11 UTC" can be audited; an agent that states a bare number can't.

Streaming for standing monitors#

Watching a symbol is a different job. Polling get_quote every two seconds from an agent loop burns through the free tier's 10,000 monthly calls in a few hours, and a monitor watching several symbols crosses the 60 requests-per-minute ceiling faster still. The WebSocket stream is built for this: connect to wss://stream.sifting.io/ws/v1?key=$SIFTING_KEY and send

{ "op": "subscribe", "product": "cex", "symbols": ["BTCUSD", "ETHUSD"] }

The server replies with the last cached value first, then live ticks, so a monitor has a price immediately instead of waiting for the next trade. Send a ping frame at least every 60 seconds; the server closes any connection that stays silent for 90. The pattern that holds up in production: don't keep the socket inside the agent's reasoning loop. Run a small side process that subscribes and keeps the latest values in memory, then expose a read tool to the agent. The model asks "where is BTCUSD now" and reads local state without spending an API call, while the socket does the real-time work. The free tier allows 1 connection with 5 symbol subscriptions; paid tiers raise both.

Common pitfalls#

Three failures show up repeatedly in agent integrations, and all three look confusing from the model's side of the tool boundary.

A 406 on history but not on quotes. Historical bar endpoints require gzip and return 406 gzip_required without it. Python's requests library sends Accept-Encoding: gzip on its own; raw urllib and some fetch wrappers don't. The symptom is an agent that answers live-price questions fine and fails only on "over the past month" questions.

Retry storms on 429. A model told that a tool failed will often just try again, at machine speed. Handle 429 inside the function instead: honor the Retry-After header, check X-RateLimit-Remaining before bursts, and return plain text like "rate limited, retry after 20 seconds" so the model can relay the situation to the user rather than looping.

Treating 503 stale_snapshot as an outage. This code means the live value is older than the freshness threshold, and the response body includes last_t and server_now so you can compute the age. Surface both to the model. "The last price is 45 seconds old" is a useful answer for a user; a generic tool error leaves the agent guessing again.

An agent with a feed it can call stops inventing numbers. The MCP server covers desktop assistants in one config block, two REST functions cover a service, and a WebSocket side process covers monitors, all on one API key and one schema family. The free tier needs no credit card. Start building free

Keep reading

Related posts

How to give an AI agent market data: MCP server, REST tools, and WebSocket streaming · SiftingIO