Content
<div align="center">
# Tool List
### Open-source solution to connect Toss Securities Open API to AI·Quant
Decimal·Rate Limiting·Idempotence handled properly **SDK** + **MCP Server** to safely manage accounts for LLM



[](https://github.com/ongjin/tossinvest/actions/workflows/ci.yml)


</div>

> ⚠️ **Unofficial Client** — Not affiliated with Toss Securities, and no trademark/endorsement is involved. Toss Open API can be used by issuing keys from WTS `Settings > Open API` (limits and policies may change without prior notice). However, it is designed to allow for paper mode and mock testing without live keys.
---
## Overview
- **For Quant Users** → `pip install pytossinvest`, directly query balances and place orders in Python. *(SDK · MIT)*
- **For AI Users** → Install MCP client (Claude Desktop·Cursor) with `pytossinvest-mcp` to enable *"Show me my balance / Buy 5 shares of Samsung Electronics / Analyze exchange rate trends"* directly. *(MCP Server · Apache-2.0)*
The boundary is clearly separated between **SDK ↔ MCP**. Quant users only use the SDK, while AI users receive an additional safety model.
---
## Why Another Client?
Automatically generated wrappers do not prevent pitfalls in Toss API. Therefore, we manually implement:
| | |
|---|---|
| 💸 **Money is never a float** | Amounts and quantities are fully string/Decimal. float will raise a `TypeError` immediately — no rounding errors. |
| 🚦 **Client-side rate limiter** | Grouped token buckets + **9:00 AM opening 10-minute ORDER/ORDER_INFO halved** (6→3) reflection. **`X-RateLimit-*` headers for bucket synchronization** (headers are authoritative) + **429 bounded automatic retry** (`Retry-After`/backoff+jitter, 5xx·timeout are not retried). |
| 🔁 **Idempotence** | Automatic management of `clientOrderId` — prevents duplicate orders even if the response is not received due to network disconnection. |
| 🧩 **Error handling by `code`** | `message` can be empty. The server **will not break even if an unknown code/enum is added**. |
| 🔐 **Token lifecycle management** | Updates before expiration, memory caching, and 1-time reissue after `401 expired-token` + retry. |
| ✅ **Designed without live keys** | `git clone && uv sync && pytest` → **238 tests passed**. Contribution barrier is 0. |
---
## 🔒 Core — Safely handing over securities accounts to LLM
This is the key selling point. **"What if AI recklessly manages my account?"** is prevented not by client-side trust but by **server-side guardrails**.

1. **Mode gate — Default is safe.** `read_only` (order tool not registered) / **`paper` (default · local simulation trading, no real orders)** / `live` (real orders). live mode requires both `TOSSINVEST_MODE=live` and `TOSSINVEST_ALLOW_LIVE=1`. Changing modes alone will not trigger any action (fail-closed).
2. **2-step ordering — human-in-the-loop.** `preview_order` checks the guardrails and issues a **short-lived token** with expected costs. `place_order` is rejected without the token. LLM cannot place orders in one step. Modifications (`modify_order`) also go through the same gate.
3. **Guardrails — for both paper and live modes.** Order and daily cumulative amount limits (**by currency** — KRW/USD separated) · stock allow/deny · **explicit confirmation for large transactions** (KRW 1 billion / USD $100k↑) · **hard ceiling and immediate rejection** (KRW 3 billion / USD $3M↑) · trading hours (live).
> **Invariant:** The trading path **always** goes through guardrails. Tokens are issued only in `preview_order`, and there is no bypass.
---
## 2-step Ordering Flow — preview → place, and idempotence

Tokens are consumed **only when successful**. If `place_order` fails in the middle, the token remains valid, and **safe retries with the same `clientOrderId`** are possible — preventing duplicate orders. If successful, the token is consumed, and duplicate orders are not possible.
---
## Quick Start
### SDK — Directly in Python
```python
from pytossinvest import TossInvestClient
with TossInvestClient(client_id="...", client_secret="...") as c:
c.get_accounts() # accountSeq automatically cached (ACCOUNT 1/s)
prices = c.get_prices(["005930"])
print(prices[0].last_price) # Decimal("70000") — never float
# Orders use string-decimal + idempotence (clientOrderId assigned directly)
c.place_order(symbol="005930", side="BUY", order_type="LIMIT",
price="70000", quantity="10", client_order_id="my-001")
```
### MCP — Connecting to Claude Desktop
`claude_desktop_config.json`:
```json
{
"mcpServers": {
"tossinvest": {
"command": "uv",
"args": ["run", "--directory", "/path/to/tossinvest",
"--package", "pytossinvest-mcp", "pytossinvest-mcp"],
"env": {
"TOSSINVEST_MODE": "paper",
"TOSSINVEST_CLIENT_ID": "...",
"TOSSINVEST_CLIENT_SECRET": "..."
}
}
}
}
```
Default `paper` mode means no real orders — feel free to ask *"Show me 10 shares of Samsung Electronics"* first. For live trading, set `TOSSINVEST_MODE=live` and `TOSSINVEST_ALLOW_LIVE=1`.
> ⚠️ **Do not use live mode with auto-approved MCP clients.** The 2-step preview→place process assumes **human approval for each call**. If clients auto-approve tool calls, LLM can fire preview→place consecutively without human intervention. Additional defense can be set using `pytossinvest-mcp`'s `LIVE_CONFIRM_MIN_DELAY_SEC`.
---
## Modes at a Glance
| Mode | Ordering | Behavior | How to enable |
|---|:---:|---|---|
| `read_only` | ✗ | Read-only, no order tools registered | `TOSSINVEST_MODE=read_only` |
| **`paper`** *(default)* | ○ | Local simulation portfolio trading, **no real orders** | (default) |
| `live` | ○ | Real orders | `MODE=live` **+** `ALLOW_LIVE=1` |
## MCP Tools (14)
- **Read (always):** `get_accounts` · `get_holdings` · `get_quote` · `get_candles` · `get_stock_info` · `get_market_info` · `list_orders` · `get_order`
- **Write (paper·live):** `get_order_readiness` · **`preview_order` → `place_order`** · **`preview_modify` → `modify_order`** · `cancel_order`
Guardrail limits, stock allow/deny, starting cash, etc., are all configurable via env → [`pytossinvest-mcp/README.md`](pytossinvest-mcp/README.md).
---
## One Repo, Two Packages
| Package | Role | License | Dependency |
|---|---|:---:|---|
| [`pytossinvest`](pytossinvest/) | Toss Open API Python SDK | **MIT** | — |
| [`pytossinvest-mcp`](pytossinvest-mcp/) | MCP server with safety model | **Apache-2.0** | `pytossinvest` |
The `uv` workspace monorepo allows for separate builds and deployments.
```bash
# Tests — no live keys needed
uv run --package pytossinvest --extra dev pytest pytossinvest/tests # 59 passing
uv run --package pytossinvest-mcp pytest pytossinvest-mcp/tests # 179 passing
```
Deeper documentation is available in [`docs/claude/`](docs/claude/) — [API Reference](docs/claude/tossinvest-open-api.md) · [SDK Internal Structure](docs/claude/pytossinvest-sdk.md) · [MCP Safety Model](docs/claude/pytossinvest-mcp.md).
---
## Status & Disclaimer
- **Provided** — Toss Open API keys can be issued from WTS `Settings > Open API`. Limits, policies, and endpoints may change without prior notice. Reference documents are snapshots, and the canonical `openapi.json` is the truth.
- **Personal tool for personal accounts** — not for proxy trading or paid signal services (not targeting regulatory evasion).
- Investment decisions and results are solely the user's responsibility.
## License
SDK (`pytossinvest`) = **MIT** · MCP Server (`pytossinvest-mcp`) = **Apache-2.0**. See `LICENSE` in each package directory.