io.github.kcw2034/toss-invest-mcp
# Toss Securities Open API — MCP Server
<!-- mcp-name: io.github.kcw2034/toss-invest-mcp -->
An MCP server exposing the [Toss Securities Open API](https://developers.tossinvest.com/docs)
(20 endpoints) as LLM tools, with backend-enforced trading guardrails.
Published on the [MCP Registry](https://registry.modelcontextprotocol.io) as
`io.github.kcw2034/toss-invest-mcp` and installable from PyPI via `uvx toss-invest-mcp`.
Built with Python + [FastMCP](https://github.com/jlowin/fastmcp), `httpx`, and `pydantic`.
## Tools
| Category | Tools |
|---|---|
| Market data | `get_orderbook`, `get_prices`, `get_trades`, `get_price_limits`, `get_candles` |
| Stock info | `get_stocks`, `get_stock_warnings` |
| Market info | `get_exchange_rate`, `get_market_calendar_kr`, `get_market_calendar_us` |
| Account / asset | `get_accounts`, `get_holdings` |
| Order info | `get_buying_power`, `get_sellable_quantity`, `get_commissions` |
| Order history | `list_orders`, `get_order` |
| Order execution | `create_order`, `modify_order`, `cancel_order` |
## Install
```bash
pip install -e .
```
(Development extras for the test suite: `pip install -e ".[dev]"`.)
## Configure
Credentials are read from the environment (OAuth2 client-credentials grant):
```bash
export TOSS_CLIENT_ID=...
export TOSS_CLIENT_SECRET=...
export TOSS_DEFAULT_ACCOUNT=12345 # optional fallback account number
```
Account-scoped tools accept an optional `account_id`. Resolution order:
explicit `account_id` → `TOSS_DEFAULT_ACCOUNT` → error `"Account ID required."`.
### Trading guardrails (secure by default)
Guardrails are enforced in code, not just in tool descriptions. They are **active even
if their env var is unset** — a missing setting falls back to a conservative default, so a
misconfigured deployment fails closed (order rejected) rather than open. Set a cap to `off`
to disable it.
| Var | Default | Meaning |
|---|---|---|
| `TOSS_MAX_ORDER_NOTIONAL_KRW` | `1000000` | reject KRW orders above this notional |
| `TOSS_MAX_ORDER_NOTIONAL_USD` | `1000` | reject USD orders above this notional |
| `TOSS_MAX_PRICE_DEVIATION_PCT` | `5.0` | reject LIMIT orders this far from market (fat-finger guard) |
| `TOSS_HIGH_VALUE_NOTIONAL` | `100000000` | notional treated as high-value (Tier-2) |
| `TOSS_REQUIRE_CONFIRM_HIGH_RISK` | `true` | require `confirm=true` for full liquidation / high-value orders |
| `TOSS_BASE_URL` | `https://openapi.tossinvest.com` | override the API base URL |
## Safety model
`create_order` runs a read-only pre-flight (current price; sellable quantity for sells),
then evaluates the order before placing it:
- **Tier 1 — absolute (non-bypassable).** Order notional and price-deviation caps reject
unconditionally; `confirm=true` cannot override them. If a sized order's notional cannot be
determined (e.g. a MARKET order with no usable market price), it is rejected — never placed
uncapped.
- **Tier 2 — confirmation-gated.** Full-position liquidation (SELL ≥ sellable quantity) and
high-value orders return a structured `confirmation_required` preview instead of executing.
The agent must re-call with `confirm=true` after the human sees the preview. Confirmed
high-value orders pass the API's own `confirmHighValueOrder` flag.
- `cancel_order` executes immediately — cancelling only reduces exposure.
A blocked order returns a structured result (`status`, `reason`, `preview`, `message`) and
performs **no** write to the API.
## Run with Claude
Add to your MCP client config (e.g. Claude Desktop / Claude Code `mcpServers`):
```json
{
"mcpServers": {
"toss-invest": {
"command": "python",
"args": ["-m", "toss_invest_mcp.server"],
"env": {
"TOSS_CLIENT_ID": "...",
"TOSS_CLIENT_SECRET": "...",
"TOSS_DEFAULT_ACCOUNT": "12345"
}
}
}
}
```
## Development
```bash
uv run pytest # or: pytest
```
The test suite mocks all HTTP with `respx` — it makes no live API calls.
> **Note:** the pre-flight field extractors (`_extract_price`, `_extract_sellable` in
> `tools/order.py`) infer response field names from the documented API and should be
> confirmed against live responses before production trading — a wrong field name would
> cause an order to be rejected (fail-closed), not placed uncapped.
TDQS
Scored across 20 tools
Each tool targets a distinct resource: market data tools for orderbook, prices, trades, candles, etc., and account/order tools for holdings, buying power, and order lifecycle. There is no overlap between get_orderbook, get_trades, get_prices, or get_order, and list_orders vs get_order are clearly list/detail.
All tools follow a consistent verb_noun pattern with verbs get, list, create, modify, and cancel. While list_orders uses 'list' instead of 'get', the pattern is predictable and uniform across the set.
20 tools is slightly above the ideal range, but the server covers two clear domains: market data (10 tools) and account/order management (10 tools). Each tool serves a distinct purpose, so the count feels justified rather than bloated.
The tool set covers the full trading lifecycle: market data retrieval, account information, and order management with create, modify, cancel, and query. No critical gaps exist; even sellable quantity and buying power are provided to support order placement.