Skip to main content
Glama
SiftingIO

siftingio-mcp-python

Official
README.md
# siftingio-mcp (Python)

[![siftingio-mcp-python MCP server](https://glama.ai/mcp/servers/SiftingIO/siftingio-mcp-python/badges/score.svg)](https://glama.ai/mcp/servers/SiftingIO/siftingio-mcp-python)

A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that exposes the
[SiftingIO market-data SDK](https://github.com/SiftingIO/sdk-python) (`siftingio`) to your
AI assistant. With it running, the model can pull live prices, read SEC/EDGAR fundamentals,
fetch OHLCV bars, look up 13F holdings, check market status, and scan the macro economic
calendar.

It's a Python port of [`siftingio-mcp`](https://github.com/SiftingIO/siftingio-mcp) (the
TypeScript server), built on [FastMCP](https://github.com/modelcontextprotocol/python-sdk).

## Setup

Requires Python 3.13+.

```bash
python -m venv .venv && source .venv/bin/activate
pip install -e .
```

You'll need an API key from <https://sifting.io>:

```bash
export SIFTING_API_KEY=sft_...
```

To point at a different backend, override the defaults with `SIFTING_BASE_URL` and
`SIFTING_WS_URL`.

For local development, copy `.env.example` to `.env` and load it however you like (e.g.
`set -a; . ./.env; set +a`, or `direnv`). When you wire this into a real MCP client, pass
the key through the server's `env` block instead of a file — there's an example below.

## Run

The commands:

- `siftingio-mcp` — run the server over **stdio** (the default transport).
- `siftingio-mcp-http` — run it over **Streamable HTTP**.
- `python -m siftingio_mcp` — same as `siftingio-mcp`, without the console script.
- `pytest` — run the tests.
- `ruff check` / `ruff format` — lint and format.
- `mypy` — type-check.

CI (`.github/workflows/ci.yml`) runs the same steps on every push and PR: format check,
lint, typecheck, test.

Note that the server speaks JSON-RPC over **stdio**, so stdout is reserved for the protocol.
Diagnostics go to stderr.

### Inspect interactively

The MCP inspector is the easiest way to try the server by hand:

```bash
SIFTING_API_KEY=sft_... npx @modelcontextprotocol/inspector siftingio-mcp
```

### HTTP (Streamable HTTP) transport

For a remote or hosted deployment, use MCP's Streamable HTTP transport instead of stdio:

```bash
SIFTING_API_KEY=sft_... PORT=3000 siftingio-mcp-http
# → MCP endpoint at http://127.0.0.1:3000/mcp  (POST messages, GET SSE, DELETE session)
```

It's **stateful**: each client gets its own session (tracked by the `mcp-session-id`
header), while the upstream SiftingIO connection is shared across the process. It only binds
to **loopback** and rejects non-local browser `Origin`s for DNS-rebinding protection. Set
the port with `PORT` (or `MCP_HTTP_PORT`); the default is `3000`.

For auth, set **`MCP_AUTH_TOKEN`** and the server will require `Authorization: Bearer
<token>` on every request (a missing or wrong token gets a 401). Put a reverse proxy in
front to handle TLS and the token, and you can expose the server past localhost:

```bash
MCP_AUTH_TOKEN=s3cret SIFTING_API_KEY=sft_... siftingio-mcp-http
```

Then point any HTTP-capable MCP client at `http://127.0.0.1:3000/mcp`:

```bash
claude mcp add --transport http siftingio http://127.0.0.1:3000/mcp
```

## Use with an MCP client

Add this to your client config — Claude Desktop's `claude_desktop_config.json`, for
example, or use `claude mcp add` on Claude Code:

```json
{
  "mcpServers": {
    "siftingio": {
      "command": "siftingio-mcp",
      "env": { "SIFTING_API_KEY": "sft_..." }
    }
  }
}
```

If `siftingio-mcp` isn't on the client's `PATH`, use the absolute path to the console script
(`/path/to/.venv/bin/siftingio-mcp`) or `python -m siftingio_mcp`.

## Tools

The server exposes 36 tools:

| Namespace | Tools |
| --- | --- |
| Live (snapshot) | `last_trade`, `last_quote`, `last_tvl` |
| Stocks | `stocks_search`, `stocks_profile`, `stocks_filings`, `stocks_filing`, `stocks_sections`, `stocks_section`, `stocks_risk_factors_diff`, `stocks_ratios`, `stocks_earnings`, `stocks_financials`, `stocks_financial_concept`, `stocks_insiders`, `stocks_ownership`, `stocks_events`, `stocks_compensation`, `stocks_screener`, `stocks_bars` |
| Crypto / Forex | `crypto_bars`, `forex_bars` |
| DEX | `dex_wallet` |
| Markets | `markets_list`, `markets_status_all`, `markets_status`, `markets_hours`, `markets_calendar` |
| Filers | `filers_holdings` |
| Macro | `economic_calendar_list` |
| Live (stream) | `ws_subscribe`, `ws_unsubscribe`, `ws_poll`, `ws_collect`, `ws_status`, `ws_disconnect` |

A few patterns to know about:

Paginated tools take `cursor`/`limit` and return a `meta.next_cursor` to fetch the next
page. The `stocks_*` list tools — `stocks_filings`, `stocks_earnings`, `stocks_insiders`,
`stocks_ownership`, `stocks_events`, `stocks_compensation` — also accept **`max_items`**:
set it and they auto-paginate, gathering up to that many items across pages in one call.

The high-traffic tools (`last_trade`, `last_quote`, `last_tvl`, `stocks_profile`,
`stocks_search`) have an **output schema** and return `structuredContent` alongside the
human-readable text, so clients can read them machine-side too.

Every tool also carries MCP **annotations**. The data tools are `readOnlyHint: true` (and
`openWorldHint: true`, since they reach an external API), while the WebSocket tools that
change connection state are `readOnlyHint: false`, `destructiveHint: false`.

Results are **size-capped** at roughly 60k characters (see `MAX_RESULT_CHARS` in
`siftingio_mcp/util.py`). When a heavy endpoint — full XBRL financials, screeners, OHLCV
bars — returns more than that, the server trims its largest array and adds a `_truncated`
note explaining how to narrow the query.

> **Note on `from`/`to`:** since `from` is a reserved word in Python, the date lower-bound
> input field is named **`from_`** (matching the SiftingIO SDK's own convention). `to` is
> unchanged.

### Live WebSocket streaming

Streaming doesn't fit a single request/response, so the server keeps **one persistent
WebSocket** open, buffers frames as they arrive, and the tools read from that buffer.
Channels (the `product` field) are `cex` (crypto), `dex` (DEX trades), `fx` (forex), `us`
(US stocks), and `tvl` (DEX pool TVL).

There are two ways to use it:

- **Subscribe + poll**, for ongoing streams: call `ws_subscribe` once, then keep calling
  `ws_poll`. The first poll returns a recent tail; feed the returned `next_seq` back in as
  `after_seq` to get only newer frames after that. `ws_status` shows the connection and
  current subscriptions, and `ws_disconnect` shuts it down.
- **Collect**, for a quick one-shot: `ws_collect` subscribes, waits up to `duration_ms` (or
  until it has seen `max` frames), returns what it caught, and cleans up any subscription it
  created. Good for grabbing a few seconds of BTCUSD.

The connection reconnects on its own and replays your subscriptions when it does. The buffer
is a rolling window, so the oldest frames eventually fall off; when they do, you'll see it
reported through `dropped`/`gap`.

## Prompts

These are guided, multi-tool workflows your client can surface as slash-commands:

- **`company_snapshot`** `(ticker)` — pulls `stocks_profile`, `stocks_ratios`, the latest
  `stocks_filings`, and `last_trade` into one briefing.
- **`compare_companies`** `(tickers)` — lines up several tickers side by side across the key
  ratios.
- **`market_now`** — what's open and closed right now, plus the high-impact macro events
  coming up.

## Logging & shutdown

The server logs structured records about the connection (WebSocket
open/close/reconnect/error) and shutdown to **stderr** (stdout is reserved for the protocol
on the stdio transport).

On **SIGINT or SIGTERM**, it closes the live WebSocket and shuts down cleanly before
exiting.

## License

MIT