Content
<div align="center">
# Tool List
**A local execution-type MCP server that actually calls Toss Securities Open API**
Automatically generates MCP tools by reading all operations from the official OpenAPI specification.
[](https://github.com/Kuco-dev/tossinvest-api-mcp/actions/workflows/ci.yml)
[](./LICENSE)
[](https://nodejs.org)
[](https://www.typescriptlang.org/)
[](https://github.com/modelcontextprotocol/typescript-sdk)
</div>
> [!WARNING]
> **This is an unofficial project.** It does not receive support, guarantee, or sponsorship from Toss Securities.
>
> **Not an investment advice tool.** All responsibility for investment decisions and their results lies with the user.
>
> **Includes order APIs that affect real assets.** The default is disabled + dry-run, and the project is not responsible for losses incurred when enabled.
## Key Features
- **Automatic tool generation based on OpenAPI** — No hardcoding of endpoints. If APIs are added to the official specification, tools are generated automatically at the next execution.
- **Actual API calls** — OAuth 2.0 Client Credentials automatic issuance, expiration management, and concurrent issuance prevention (single-flight)
- **Multi-layer order safety features** — Real orders are disabled by default, `dryRun` is `true` by default, executed only when 8 conditions are met, and mutation automatic retries are completely prohibited
- **Preservation of financial data precision** — Maintaining amounts, quantities, and prices as strings to prevent floating-point loss
- **Security defaults** — Token memory storage, log and error redaction, official domain blocking (SSRF prevention), path traversal and prototype pollution defense
- **Docker support** — Hardened multi-stage images and Compose provided
Currently, based on the specification (v1.2.5), **29 operations** (23 reads / 6 mutations) and **7 management tools** are provided.
## Quick Start
```bash
pnpm install
pnpm build
cp .env.example .env # Enter TOSSINVEST_CLIENT_ID and SECRET
pnpm inspect # Check tool list with MCP Inspector
```
For MCP client registration, refer to [MCP Client Configuration](#mcp-client-configuration), and for container execution, refer to [Running with Docker](#running-with-docker).
## Official Documents
- Developer documentation: <https://developers.tossinvest.com/docs>
- OpenAPI JSON: <https://openapi.tossinvest.com/openapi-docs/latest/openapi.json>
- MCP TypeScript SDK: <https://github.com/modelcontextprotocol/typescript-sdk>
---
## Table of Contents
1. [Requirements](#requirements)
2. [Installation](#installation)
3. [API Key Issuance and Configuration](#api-key-issuance-and-configuration)
4. [Allowed IP Registration](#allowed-ip-registration)
5. [Environment Variables](#environment-variables)
6. [Build and Execution](#build-and-execution)
7. [Running with Docker](#running-with-docker)
8. [MCP Client Configuration](#mcp-client-configuration)
9. [Provided Tools](#provided-tools)
10. [Account Selection Method](#account-selection-method)
11. [Usage Examples](#usage-examples)
12. [Real Order Activation Procedure](#real-order-activation-procedure)
13. [Conditional Order Activation Procedure](#conditional-order-activation-procedure)
14. [Mutation Automatic Retry Prohibition Policy](#mutation-automatic-retry-prohibition-policy)
15. [Rate Limit Handling](#rate-limit-handling)
16. [Troubleshooting](#troubleshooting)
17. [Security Precautions](#security-precautions)
18. [Tests](#tests)
19. [OpenAPI Snapshot Update](#openapi-snapshot-update)
20. [MCP SDK Version Selection](#mcp-sdk-version-selection)
21. [Project Structure](#project-structure)
22. [Contributions](#contributions)
23. [License](#license)
---
## Requirements
| Item | Version |
| --- | --- |
| Node.js (execution) | 20.10 or higher |
| Node.js (development/build) | **22.13 or higher** — required by pnpm 11 |
| pnpm | 9 or higher (this repository is fixed at 11.17.0) |
| Toss Securities account | Open API usage application completed |
> The built `dist/` works on Node 20.10+, but building from source requires Node 22.13+ due to pnpm 11 requirements.
> Using Docker allows execution regardless of the host Node version.
## Installation
```bash
git clone https://github.com/Kuco-dev/tossinvest-api-mcp.git
cd tossinvest-api-mcp
pnpm install
pnpm build
```
## API Key Issuance and Configuration
1. Access Toss Securities WTS (Web Trading)
2. Go to **Settings → Open API** menu
3. Apply for API usage and obtain `client_id` and `client_secret`
4. Store `client_secret` securely as it is only visible at issuance
5. Create `.env` in the project root
```bash
cp .env.example .env
```
```env
TOSSINVEST_CLIENT_ID=issued_client_id
TOSSINVEST_CLIENT_SECRET=issued_client_secret
```
> `.env` is included in `.gitignore`. Never commit it.
## Allowed IP Registration
Toss Securities Open API can only be called from **registered IPs**.
1. WTS → **Settings → Open API → Allowed IP Management**
2. Register the current **public IP**
If not registered, a `403 access_denied` (`IP address not allowed`) error is received during token issuance.
This server normalizes the error to `ip-not-allowed` code and marks `possibleIpAllowlistIssue` as `true` in `tossinvest_auth_status`.
## Environment Variables
| Variable | Default Value | Description |
| --- | --- | --- |
| `TOSSINVEST_CLIENT_ID` | (none) | OAuth client ID |
| `TOSSINVEST_CLIENT_SECRET` | (none) | OAuth client secret |
| `TOSSINVEST_DEFAULT_ACCOUNT` | (none) | Default account `accountSeq` |
| `TOSSINVEST_OPENAPI_URL` | Official specification URL | OpenAPI JSON location |
| `TOSSINVEST_BASE_URL` | `https://openapi.tossinvest.com` | API server |
| `TOSSINVEST_ALLOW_CUSTOM_BASE_URL` | `false` | Allow unofficial base URL |
| `TOSSINVEST_OPENAPI_STRICT` | `true` | Fail-closed on specification conversion failure |
| `TOSSINVEST_SPEC_CACHE_ENABLED` | `true` | Use remote specification first, then snapshot on failure |
| `TOSSINVEST_ENABLE_TRADING` | `false` | Allow execution of general order mutations |
| `TOSSINVEST_ENABLE_CONDITIONAL_ORDERS` | `false` | Allow execution of conditional order mutations |
| `TOSSINVEST_MUTATION_CONFIRMATION` | `I_UNDERSTAND_THIS_PLACES_A_REAL_ORDER` | Real order confirmation string |
| `TOSSINVEST_TOKEN_EXPIRY_SKEW_SECONDS` | `60` | Token expiration safety skew |
| `TOSSINVEST_REQUEST_TIMEOUT_MS` | `30000` | Request timeout |
| `TOSSINVEST_MAX_READ_RETRIES` | `2` | Maximum read request retries |
| `TOSSINVEST_MAX_RESPONSE_BYTES` | `8388608` | Response size limit |
| `TOSSINVEST_LOG_LEVEL` | `info` | `silent`/`error`/`warn`/`info`/`debug` |
| `TOSSINVEST_ENABLE_RAW_REQUESTS` | `false` | Exposure of raw request tools |
The server starts normally even without authentication information. Specification inquiry, search, detailed inquiry, and authentication status inquiry work without authentication,
and return `credentials-missing` error only at actual API call.
## Build and Execution
```bash
pnpm install # Dependency installation
pnpm dev # Development mode (tsx watch)
pnpm build # Compile to `dist/`
pnpm start # Execute built result (stdio)
pnpm test # Tests
pnpm test:coverage # Tests with coverage
pnpm lint # ESLint
pnpm typecheck # Type check
pnpm openapi:update # Update snapshot from official specification
pnpm openapi:verify # Verify snapshot
pnpm inspect # Check tool list with MCP Inspector
```
Running `pnpm inspect` opens MCP Inspector, showing the entire registered tool list and input schema. (`pnpm build` is required)
## Running with Docker
The server can be executed as a container without installing Node/pnpm.
> **Important**: This server is a **stdio MCP server**. The container's stdin/stdout is the MCP communication channel,
> so `-i` (interactive) option is required, and `-t` (TTY) should **not** be given. TTY breaks JSON-RPC framing.
> For the same reason, `docker compose up` is not suitable for actual use (stdin is not connected to MCP client).
### 1. Build Image
```bash
docker compose build
# or
docker build -t tossinvest-api-mcp:latest .
```
The multi-stage build includes only `dist/` and **3 runtime dependencies** (`@modelcontextprotocol/sdk`, `zod`, `dotenv`) in the final image.
It runs as `node` user and does not use root privileges.
### 2. Environment Variable Configuration
Authentication information is **not baked into the image** but injected at runtime. `.dockerignore` excludes `.env` from the build context.
```bash
cp .env.example .env # Enter values
```
### 3. Execution
```bash
# Interactive execution (for direct JSON-RPC confirmation)
docker compose run --rm tossinvest-mcp
# Check tool list with Inspector in the container
docker compose --profile tools run --rm --service-ports inspector
# → Access http://127.0.0.1:6274
```
Package.json shortcut scripts are also provided.
```bash
pnpm docker:build
pnpm docker:run
pnpm docker:inspect
```
### 4. Registering Docker Container to MCP Client
MCP client requires direct container execution, so use `docker run -i` instead of `docker compose`.
**Claude Desktop / Cursor (Linux · macOS)**
```json
{
"mcpServers": {
"tossinvest": {
"command": "docker",
"args": [
"run", "--rm", "-i",
"--env-file", "/absolute/path/tossinvest-api-mcp/.env",
"tossinvest-api-mcp:latest"
]
}
}
}
```
**Windows**
```json
{
"mcpServers": {
"tossinvest": {
"command": "docker",
"args": [
"run", "--rm", "-i",
"--env-file", "C:/absolute/path/tossinvest-api-mcp/.env",
"tossinvest-api-mcp:latest"
]
}
}
}
```
Individual injection is also possible.
```json
"args": [
"run", "--rm", "-i",
"-e", "TOSSINVEST_CLIENT_ID",
"-e", "TOSSINVEST_CLIENT_SECRET",
"-e", "TOSSINVEST_DEFAULT_ACCOUNT",
"tossinvest-api-mcp:latest"
]
```
> `-e KEY` (without value) passes the host's environment variable with the same name. No need to write secrets in the configuration file.
### Container Security Configuration
`docker-compose.yml` includes the following hardening.
| Setting | Reason |
| --- | --- |
| `user: node` | Prohibits root execution |
| `read_only: true` | Server does not write files (tokens are stored in memory) |
| `tmpfs: /tmp`, `HOME=/tmp` | Allow minimal write space needed for read-only root |
| `cap_drop: ALL` | Remove all Linux capabilities |
| `no-new-privileges:true` | Prevent privilege escalation |
| `mem_limit`, `pids_limit` | Prevent resource abuse |
| `stdin_open: true`, `tty: false` | Guarantee stdio MCP channel |
| `tini` (ENTRYPOINT) | Deliver SIGTERM → graceful shutdown |
| Default `false` for real order env | Enforce safe default in Dockerfile ENV |
The Inspector service has `read_only: false` because it needs to download packages with `npx`, and its port is only exposed to `127.0.0.1`, not to the outside.
### Offline Operation
The image includes an OpenAPI snapshot, allowing the server to start without remote specification.
```bash
# Disable remote specification query, use only snapshot
docker run --rm -i -e TOSSINVEST_SPEC_CACHE_ENABLED=false tossinvest-api-mcp:latest
```
However, **internet connection and allowed IP registration are required for actual API calls**.
If authentication fails, check the IP first, as the container's outbound IP may differ from the host's public IP (NAT/VPN environment).
## MCP Client Configuration
Use the absolute path of `dist/index.js` after building.
### Claude Desktop
Configuration File Location
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows**
```json
{
"mcpServers": {
"tossinvest": {
"command": "node",
"args": ["C:/absolute/path/tossinvest-api-mcp/dist/index.js"],
"env": {
"TOSSINVEST_CLIENT_ID": "User Value",
"TOSSINVEST_CLIENT_SECRET": "User Value",
"TOSSINVEST_DEFAULT_ACCOUNT": "User Value",
"TOSSINVEST_ENABLE_TRADING": "false"
}
}
}
}
```
**Linux / macOS**
```json
{
"mcpServers": {
"tossinvest": {
"command": "node",
"args": ["/absolute/path/tossinvest-api-mcp/dist/index.js"],
"env": {
"TOSSINVEST_CLIENT_ID": "User Value",
"TOSSINVEST_CLIENT_SECRET": "User Value",
"TOSSINVEST_DEFAULT_ACCOUNT": "User Value",
"TOSSINVEST_ENABLE_TRADING": "false"
}
}
}
}
```
### Claude Code
```bash
# Linux / macOS
claude mcp add tossinvest \
--env TOSSINVEST_CLIENT_ID=UserValue \
--env TOSSINVEST_CLIENT_SECRET=UserValue \
--env TOSSINVEST_DEFAULT_ACCOUNT=UserValue \
-- node /absolute/path/tossinvest-api-mcp/dist/index.js
```
```powershell
# Windows PowerShell
claude mcp add tossinvest `
--env TOSSINVEST_CLIENT_ID=UserValue `
--env TOSSINVEST_CLIENT_SECRET=UserValue `
--env TOSSINVEST_DEFAULT_ACCOUNT=UserValue `
-- node C:/absolute/path/tossinvest-api-mcp/dist/index.js
```
Or `.mcp.json` in project root:
```json
{
"mcpServers": {
"tossinvest": {
"command": "node",
"args": ["/absolute/path/tossinvest-api-mcp/dist/index.js"],
"env": {
"TOSSINVEST_CLIENT_ID": "User Value",
"TOSSINVEST_CLIENT_SECRET": "User Value"
}
}
}
}
```
### Cursor
`~/.cursor/mcp.json` or `.cursor/mcp.json` in project:
```json
{
"mcpServers": {
"tossinvest": {
"command": "node",
"args": ["C:/absolute/path/tossinvest-api-mcp/dist/index.js"],
"env": {
"TOSSINVEST_CLIENT_ID": "User Value",
"TOSSINVEST_CLIENT_SECRET": "User Value",
"TOSSINVEST_DEFAULT_ACCOUNT": "User Value"
}
}
}
}
```
### Codex CLI
`~/.codex/config.toml`:
```toml
[mcp_servers.tossinvest]
command = "node"
args = ["/absolute/path/tossinvest-api-mcp/dist/index.js"]
[mcp_servers.tossinvest.env]
TOSSINVEST_CLIENT_ID = "User Value"
TOSSINVEST_CLIENT_SECRET = "User Value"
TOSSINVEST_DEFAULT_ACCOUNT = "User Value"
TOSSINVEST_ENABLE_TRADING = "false"
```
On Windows, specify `args` as `["C:/absolute/path/tossinvest-api-mcp/dist/index.js"]`.
### General MCP stdio Client
```
command: node
args: <absolute_path>/dist/index.js
transport: stdio
env: TOSSINVEST_CLIENT_ID, TOSSINVEST_CLIENT_SECRET, ...
```
stdout is for JSON-RPC only, and all logs are output to stderr.
## Provided Tools
### Administrative Wrapper Tools
| Tool | Description |
| --- | --- |
| `tossinvest_api_overview` | OpenAPI version, documentation version, server URL, tag, operation count, read/mutation count, trading enabled status, snapshot/remote usage status |
| `tossinvest_list_operations` | List operations with `tag`/`method`/`path`/`readOnly`/`destructive`/`keyword` filters |
| `tossinvest_search_operations` | Search by operationId, summary, description, path, tag |
| `tossinvest_get_operation` | Detailed information on a specific operation (required/optional input, requestBody, response schema, account header requirements, mutation status, rate limit group, MCP tool name) |
| `tossinvest_call_operation` | Operation call wrapper with mutation guard |
| `tossinvest_auth_status` | Authentication configuration and token readiness status (secret/token not exposed) |
| `tossinvest_refresh_auth` | Explicit token refresh (`{ ok, expiresIn, expiresAt }` only) |
| `tossinvest_raw_request` | Exposed only when `TOSSINVEST_ENABLE_RAW_REQUESTS=true`. Only official specification-defined methods and paths are allowed |
### Direct Tool Auto-Generation based on OpenAPI
The server loads the official OpenAPI specification at startup and **automatically registers all operations as MCP tools**.
No endpoint list is hardcoded in the code; if a new API is added to the official specification, **the tool is automatically generated at the next server startup**.
- Tool name = OpenAPI `operationId` (normalized according to MCP rules; server startup failure if conflicts occur)
- Description = `summary` + `description` + method/path + tag combination
- Input schema = generated from parameters and requestBody
- Annotation = `readOnlyHint` / `destructiveHint` / `idempotentHint` / `openWorldHint`
Input format:
```json
{
"path": {},
"query": {},
"body": {},
"account": "1",
"dryRun": true,
"confirmation": ""
}
```
Fields not required for the operation are removed from the schema. For example, `getAccounts` has no input,
and `getPrices` does not have `account`/`dryRun`/`confirmation`. Arbitrary header input is not allowed.
Currently registered tools based on specification (document version 1.2.5):
| Classification | Count |
| --- | --- |
| Total operations | 29 |
| Read-only | 23 |
| Mutations (orders 3 + conditional orders 3) | 6 |
| Excluded | 1 (`issueOAuth2Token`) |
`issueOAuth2Token` (`POST /oauth2/token`) is **intentionally excluded from tool registration** to prevent client secret and access token from crossing the MCP boundary; use `tossinvest_auth_status` / `tossinvest_refresh_auth` instead.
## Account Selection Method
APIs that require an account determine the account in the following order:
1. `account` in tool input
2. `TOSSINVEST_DEFAULT_ACCOUNT`
3. If neither is present, **return an error without calling**
Do not automatically select the first account. First, query the account list:
```
Call getAccounts to confirm accountSeq
```
Pass the confirmed `accountSeq` to `account` or set it in `.env`:
```env
TOSSINVEST_DEFAULT_ACCOUNT=1
```
## Usage Examples
### Price Inquiry
```
Tell me the current prices of Samsung Electronics and Apple
```
Internally, `getPrices` is called:
```json
{
"name": "getPrices",
"arguments": { "query": { "symbols": "005930,AAPL" } }
}
```
Response:
```json
{
"ok": true,
"operationId": "getPrices",
"httpStatus": 200,
"requestId": "01HXYZ...",
"rateLimit": { "limit": 10, "remaining": 9, "resetSeconds": 1 },
"result": [ { "symbol": "005930", "close": "70000" } ]
}
```
Bid, trades, and candles are handled similarly:
```json
{ "name": "getOrderbook", "arguments": { "query": { "symbol": "005930" } } }
{ "name": "getTrades", "arguments": { "query": { "symbol": "005930", "count": 20 } } }
{ "name": "getCandles", "arguments": { "query": { "symbol": "005930", "interval": "1d", "count": 30 } } }
```
### Account Inquiry
```json
{ "name": "getAccounts", "arguments": {} }
{ "name": "getHoldings", "arguments": { "account": "1" } }
{ "name": "getBuyingPower", "arguments": { "account": "1", "query": { "currency": "KRW" } } }
{ "name": "getSellableQuantity", "arguments": { "account": "1", "query": { "symbol": "005930" } } }
{ "name": "getCommissions", "arguments": { "account": "1" } }
```
### Order Inquiry
```json
{ "name": "getOrders", "arguments": { "account": "1", "query": { "symbol": "005930", "limit": 20 } } }
{ "name": "getOrder", "arguments": { "account": "1", "path": { "orderId": "0d5QIH..." } } }
{ "name": "getConditionalOrders", "arguments": { "account": "1" } }
```
### Dry-run Order (default behavior)
```json
{
"name": "createOrder",
"arguments": {
"account": "1",
"body": {
"symbol": "005930",
"side": "BUY",
"orderType": "LIMIT",
"quantity": "10",
"price": "70000"
}
}
}
```
Omitting `dryRun` **defaults to true**, so no network request is sent to Toss Securities server:
```json
{
"ok": true,
"dryRun": true,
"operationId": "createOrder",
"executed": false,
"summary": {
"operationId": "createOrder",
"method": "POST",
"path": "/api/v1/orders",
"mutationType": "Regular order mutation (actually affects assets)",
"account": "*",
"symbol": "005930",
"side": "BUY",
"orderType": "LIMIT",
"quantity": "10",
"price": "70000"
},
"missingRequiredFields": [],
"blockers": [
"Trading is disabled (TOSSINVEST_ENABLE_TRADING=false).",
"dryRun=true, so no network request is sent.",
"The confirmation string is empty."
],
"requirementsToExecute": [
"TOSSINVEST_ENABLE_TRADING=true",
"Specify dryRun=false in tool input",
"Pass the same value as TOSSINVEST_MUTATION_CONFIRMATION in confirmation"
],
"note": "No actual order was sent. ..."
}
```
> Pass **string** values for price, quantity, and amount. Server maintains these values as strings to prevent precision loss due to floating-point conversion.
## Enabling Real Orders
Real orders are executed only when all **8 conditions are met**. If any condition is not met, no network request is sent.
1. `TOSSINVEST_ENABLE_TRADING=true`
2. Tool input `dryRun=false`
3. `confirmation` exactly matches `TOSSINVEST_MUTATION_CONFIRMATION`
4. Authentication information (`CLIENT_ID`/`CLIENT_SECRET`) is valid
5. Account specified (`account` or `TOSSINVEST_DEFAULT_ACCOUNT`)
6. OpenAPI schema requirements met
7. Request targets the official API server
8. Mutation type is clearly classified
Procedure:
```env
# 1) Modify .env
TOSSINVEST_ENABLE_TRADING=true
TOSSINVEST_MUTATION_CONFIRMATION=I_UNDERSTAND_THIS_PLACES_A_REAL_ORDER
TOSSINVEST_DEFAULT_ACCOUNT=1
```
```bash
# 2) Restart MCP client (to reflect env)
```
```json
// 3) Confirm with dry-run first, then execute
{
"name": "createOrder",
"arguments": {
"account": "1",
"body": {
"symbol": "005930",
"side": "BUY",
"orderType": "LIMIT",
"quantity": "10",
"price": "70000",
"clientOrderId": "my-order-001"
},
"dryRun": false,
"confirmation": "I_UNDERSTAND_THIS_PLACES_A_REAL_ORDER"
}
}
```
`clientOrderId` is Toss Securities' idempotency key (valid for 10 minutes). Specifying it is recommended to reduce duplicate order risks.
## Enabling Conditional Orders
Conditional orders require an additional flag:
```env
TOSSINVEST_ENABLE_TRADING=true
TOSSINVEST_ENABLE_CONDITIONAL_ORDERS=true
```
If only `TOSSINVEST_ENABLE_TRADING` is enabled and a conditional order is executed, it will be blocked.
## Mutation Automatic Retry Prohibition Policy
The following operations **never retry automatically**, under any circumstances.
- `createOrder`, `modifyOrder`, `cancelOrder`
- `createConditionalOrder`, `modifyConditionalOrder`, `cancelConditionalOrder`
- Future additions to the order-related mutations
In cases of **uncertain results**, such as timeouts, connection resets, or 500/502/503 errors, retries are not attempted. This is because the order may have already been processed. Instead, the following response is returned:
```json
{
"ok": false,
"operationId": "createOrder",
"httpStatus": 503,
"error": {
"code": "mutation-result-unknown",
"message": "The result of the createOrder request is uncertain. Automatic retry was not attempted. ...",
"requestId": "01HXYZ...",
"retryable": false,
"details": {
"originalErrorCode": "upstream-error",
"clientOrderId": "my-order-001",
"account": "*",
"nextStep": "Check the order status using the Get Orders API."
}
}
}
```
In such cases, **before re-requesting**, use `getOrders` / `getOrder` (or `getConditionalOrders` for conditional orders) to confirm whether the order was processed.
Also, 401 automatic token renewal does not apply to mutations. Only read-only GET requests refresh the token once and retry once.
## Rate Limit Handling
Parse the response headers `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` to return them as the `rateLimit` field in the successful response.
```json
"rateLimit": { "limit": 10, "remaining": 9, "resetSeconds": 1 }
```
Retry policy:
- Only retry explicit read-only requests like GET
- Prohibit retries for mutations
- If `Retry-After` is present, use it first (do not retry if it exceeds 30 seconds)
- If not, use exponential backoff + jitter
- Maximum retries are defined by `TOSSINVEST_MAX_READ_RETRIES` (default 2)
- Do not retry authentication failures, input errors, or permission errors
- Timeouts are implemented using `AbortController` (`TOSSINVEST_REQUEST_TIMEOUT_MS`)
## Troubleshooting
| Symptom | Cause / Solution |
| --- | --- |
| `credentials-missing` | `TOSSINVEST_CLIENT_ID` / `TOSSINVEST_CLIENT_SECRET` not set. Check if set in MCP client `env` and restart the client |
| `auth-failed` (`invalid_client`) | Client ID/secret typo or client inactivity. Do not retry automatically; verify values directly |
| `ip-not-allowed` | Register current public IP in WTS → Settings → Open API → Allowed IP Management |
| `account-header-required` | Set `account` parameter or `TOSSINVEST_DEFAULT_ACCOUNT`. Verify `accountSeq` using `getAccounts` |
| `rate-limit-exceeded` | Adjust call frequency. Refer to `retryAfterSeconds` in the response |
| `trading-disabled` | 8 conditions for real orders not met. Check `details.requirementsToExecute` in the response |
| `mutation-blocked` | Mutation classification not possible due to specification changes. Update policy table in `src/config/constants.ts` |
| `mutation-result-unknown` | Order result uncertain. Verify using `getOrders` and then determine |
| `openapi-load-failed` | Network issue. Update snapshot using `pnpm openapi:update` |
| Tool list not visible | Check absolute path after `pnpm build`. Directly inspect using `pnpm inspect` |
| Client JSON parsing error | stdout pollution. This server only outputs JSON-RPC to stdout; check if another wrapper script is outputting |
| Immediate termination in Docker | Missing `-i` option. Stdio server exits when stdin is closed |
| JSON-RPC corruption in Docker | Given `-t` (TTY). Use only `docker run --rm -i` |
| `credentials-missing` in Docker | Check if `--env-file` path is absolute and values are in `.env` |
| `ip-not-allowed` in Docker | Container outbound IP may differ from host. Reconfirm allowed IP |
| Not working with `docker compose up` | Normal. Stdio server; use `docker compose run --rm` or `docker run -i` in MCP client |
Increase log level for more detailed information in stderr. Secret values are redacted even in debug mode.
```env
TOSSINVEST_LOG_LEVEL=debug
```
## Security Precautions
- `.env` and authentication files are `.gitignore`d. Never commit them.
- Access tokens are stored **only in memory** and not saved or output in files, DB, logs, or MCP responses.
- Client secrets and Authorization headers are redacted in logs and error objects.
- Account identifiers are masked in logs and dry-run summaries.
- Users cannot directly specify `Authorization`, `Cookie`, `Host`, `Content-Length`, `Proxy-Authorization`, and `X-Tossinvest-Account` headers.
- Only the official domain (`openapi.tossinvest.com`) can be called by default (SSRF prevention).
- Paths not defined in the official OpenAPI cannot be called.
- Defenses against path traversal, prototype pollution, oversized responses, and JSON parsing errors.
- Real orders are disabled by default, and dry-run is the default.
See [SECURITY.md](./SECURITY.md) for details.
## Testing
```bash
pnpm test
pnpm test:coverage
```
All tests use HTTP mocking and do not call the actual Toss Securities API.
**There are no tests that call the actual order API.**
Test coverage:
- OpenAPI: snapshot parsing, operationId indexing/duplication detection, `$ref` resolution, circular references, requestBody/parameter conversion, required·enum·oneOf/anyOf/allOf·nullable handling, tool name conflicts, automatic registration of new operations
- Authentication: authentication information missing, initial issuance, caching, safety skew, single-flight concurrent issuance prevention, 401 handling, secret redaction
- Accounts: explicit account, default account, blocking on omission, adding headers only to necessary APIs, masking
- Requests: path encoding, query serialization, multiple symbols, JSON/form body, timeout, 429, `Retry-After`, 4xx/5xx normalization, requestId extraction, decimal precision maintenance
- Order safety: default inactivity, dryRun default true, dry-run no communication, confirmation verification, conditional order separate flag, mutation no retry, wrapper·raw bypass prevention
- MCP: initialize, tools/list, management tools, direct tools, error responses, **stdout non-pollution**, stderr logging, graceful shutdown
## OpenAPI Snapshot Update
Prefer remote specifications but fall back to bundled snapshots in case of network failures.
```bash
pnpm openapi:update # Download official JSON → Update src/openapi/openapi.snapshot.json
pnpm openapi:verify # Verify operation count, operationId duplication, schema conversion possibility
```
Example `openapi:verify` output:
```
[openapi:verify] Verification successful
OpenAPI version : 3.1.0
API document version : 1.2.5
API server : https://openapi.tossinvest.com
Path count : 27
Registered operations: 29
Read-only : 23
Mutations : 6
Unclassified mutations: 0
Excluded operations : 1
```
## MCP SDK Version Selection
- Selected version: **`@modelcontextprotocol/sdk` 1.29.0** (fixed to exact version in `package.json`)
- Reason for selection:
- Stable release indicated by npm `latest` tag at implementation time; did not use beta/rc tags.
- Low-level `Server` + `StdioServerTransport` + `setRequestHandler` APIs are stable and not deprecated.
- `structuredContent` is supported in stable versions; returns both human-readable text and machine-processable structured results.
- Did not use APIs under `experimental/*` path.
## Project Structure
```text
tossinvest-api-mcp/
├─ src/
│ ├─ index.ts # stdio entry point, signal handling
│ ├─ server.ts # MCP server configuration, tool registration
│ ├─ config/ # environment variable validation (Zod), constants/policy tables
│ ├─ auth/ # OAuth token management, authentication errors
│ ├─ openapi/ # specification loader, $ref resolution, schema conversion, tool generation
│ │ └─ openapi.snapshot.json # bundled specification for offline fallback
│ ├─ client/ # request assembly, response parsing, retry, rate limit
│ ├─ tools/ # mutation classification/guard, operation/management tools
│ ├─ security/ # redaction, URL policy, sensitive field definitions
│ ├─ errors/ # AppError, error normalization
│ └─ utils/ # logger(stderr), single-flight, JSON
├─ tests/ # Vitest (no actual API calls)
├─ scripts/ # snapshot update/verification, build asset copying
├─ Dockerfile
└─ docker-compose.yml
```
## Contributing
Issues and PRs are welcome. Read [CONTRIBUTING.md](./CONTRIBUTING.md) first.
Before PRs, ensure all checks pass:
```bash
pnpm typecheck && pnpm lint && pnpm test && pnpm build && pnpm openapi:verify
```
> **Never add code or tests that call the actual order API.**
> Follow the procedure in [SECURITY.md](./SECURITY.md) for security vulnerabilities.
## License
[MIT](./LICENSE)
This project is an unofficial project unrelated to Toss Securities and is not a financial or investment advisory software.
Users are responsible for all risks associated with use.
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available