内容
<div align="center">
# 工具列表
**토스증권 Open API를 실제로 호출하는 로컬 실행형 MCP 서버**
공식 OpenAPI 명세의 모든 operation을 읽어 MCP 도구를 자동 생성합니다.
[](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]
> **비공식 프로젝트입니다.** 토스증권의 지원·보증·후원을 받지 않습니다.
>
> **투자 조언 도구가 아닙니다.** 투자 판단과 그 결과에 대한 모든 책임은 사용자에게 있습니다.
> **실제 자산에 영향을 주는 주문 API를 포함합니다.** 기본값은 비활성화 + dry-run이며, 활성화 시 발생하는 손실에 대해 프로젝트는 책임지지 않습니다.
## 主要功能
- **OpenAPI 기반 도구 자동 생성** — 엔드포인트를 하드코딩하지 않습니다. 공식 명세에 API가 추가되면 코드 수정 없이 다음 실행 시 도구가 생깁니다.
- **실제 API 호출** — OAuth 2.0 Client Credentials 자동 발급·만료 관리·동시 발급 방지(single-flight)
- **다층 주문 안전장치** — 실주문 기본 비활성화, `dryRun` 기본 `true`, 8개 조건 충족 시에만 실행, mutation 자동 재시도 전면 금지
- **금융 데이터 정밀도 보존** — 금액·수량·가격을 문자열로 유지해 부동소수점 손실을 방지
- **보안 기본값** — 토큰 메모리 보관, 로그·오류 redaction, 공식 도메인 외 차단(SSRF 방지), path traversal·prototype pollution 방어
- **Docker 지원** — 하드닝된 멀티스테이지 이미지 + Compose 제공
현재 명세(v1.2.5) 기준 **29개 operation**(읽기 23 / mutation 6)과 **관리 도구 7개**를 제공합니다.
## 빠른 시작
```bash
pnpm install
pnpm build
cp .env.example .env # TOSSINVEST_CLIENT_ID / SECRET 입력
pnpm inspect # MCP Inspector로 도구 목록 확인
```
MCP 클라이언트 등록은 [MCP 클라이언트 설정](#mcp-클라이언트-설정)을, 컨테이너 실행은 [Docker로 실행](#docker로-실행)을 참고하세요.
## 공식 문서
- 개발자 문서: <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>
---
## 목차
1. [요구사항](#요구사항)
2. [설치](#설치)
3. [API 키 발급 및 설정](#api-키-발급-및-설정)
4. [허용 IP 등록](#허용-ip-등록)
5. [환경변수](#환경변수)
6. [빌드 및 실행](#빌드-및-실행)
7. [Docker로 실행](#docker로-실행)
8. [MCP 클라이언트 설정](#mcp-클라이언트-설정)
9. [제공 도구](#제공-도구)
10. [계좌 선택 방법](#계좌-선택-방법)
11. [사용 예시](#사용-예시)
12. [실주문 활성화 절차](#실주문-활성화-절차)
13. [조건주문 활성화 절차](#조건주문-활성화-절차)
14. [mutation 자동 재시도 금지 정책](#mutation-자동-재시도-금지-정책)
15. [Rate limit 처리](#rate-limit-처리)
16. [문제 해결](#문제-해결)
17. [보안 주의사항](#보안-주의사항)
18. [테스트](#테스트)
19. [OpenAPI snapshot 갱신](#openapi-snapshot-갱신)
20. [MCP SDK 버전 선택](#mcp-sdk-버전-선택)
21. [프로젝트 구조](#프로젝트-구조)
22. [기여](#기여)
23. [라이선스](#라이선스)
---
## 요구사항
| 항목 | 버전 |
| --- | --- |
| Node.js (실행) | 20.10 이상 |
| Node.js (개발·빌드) | **22.13 이상** — pnpm 11의 요구사항 |
| pnpm | 9 이상 (이 저장소는 11.17.0으로 고정) |
| 토스증권 계좌 | Open API 사용 신청 완료 |
> 빌드된 `dist/`는 Node 20.10+에서 동작하지만, 소스에서 직접 빌드하려면 pnpm 11이 요구하는 Node 22.13 이상이 필요합니다.
> Docker를 사용하면 호스트 Node 버전과 무관하게 실행할 수 있습니다.
## 설치
```bash
git clone https://github.com/Kuco-dev/tossinvest-api-mcp.git
cd tossinvest-api-mcp
pnpm install
pnpm build
```
## API 키 발급 및 설정
1. 토스증권 WTS(웹 트레이딩) 접속
2. **설정 → Open API** 메뉴 진입
3. API 사용 신청 후 `client_id`, `client_secret` 발급
4. `client_secret`은 발급 시점에만 확인 가능하므로 안전한 곳에 보관
5. 프로젝트 루트에 `.env` 생성
```bash
cp .env.example .env
```
```env
TOSSINVEST_CLIENT_ID=발급받은_client_id
TOSSINVEST_CLIENT_SECRET=발급받은_client_secret
```
> `.env`는 `.gitignore`에 등록되어 있습니다. 절대 커밋하지 마세요.
## 허용 IP 등록
토스증권 Open API는 **등록된 IP에서만 호출**할 수 있습니다.
1. WTS → **설정 → Open API → 허용 IP 관리**
2. 현재 사용 중인 **공인 IP**를 등록
등록하지 않으면 토큰 발급 단계에서 `403 access_denied` (`IP address not allowed`)를 받습니다.
이 서버는 해당 오류를 `ip-not-allowed` 코드로 정규화하고 `tossinvest_auth_status`의 `possibleIpAllowlistIssue`를 `true`로 표시합니다.
가정/사무실 회선은 공인 IP가 변경될 수 있으므로, 인증이 갑자기 실패하면 IP부터 확인하세요.
## 环境变量
| 변수 | 기본값 | 설명 |
| --- | --- | --- |
| `TOSSINVEST_CLIENT_ID` | (없음) | OAuth client ID |
| `TOSSINVEST_CLIENT_SECRET` | (없음) | OAuth client secret |
| `TOSSINVEST_DEFAULT_ACCOUNT` | (없음) | 기본 계좌 `accountSeq` |
| `TOSSINVEST_OPENAPI_URL` | 공식 명세 URL | OpenAPI JSON 위치 |
| `TOSSINVEST_BASE_URL` | `https://openapi.tossinvest.com` | API 서버 |
| `TOSSINVEST_ALLOW_CUSTOM_BASE_URL` | `false` | 비공식 base URL 허용 여부 |
| `TOSSINVEST_OPENAPI_STRICT` | `true` | 명세 변환 실패 시 fail-closed |
| `TOSSINVEST_SPEC_CACHE_ENABLED` | `true` | `true`면 원격 명세 우선, 실패 시 snapshot |
| `TOSSINVEST_ENABLE_TRADING` | `false` | 일반 주문 mutation 실행 허용 |
| `TOSSINVEST_ENABLE_CONDITIONAL_ORDERS` | `false` | 조건주문 mutation 실행 허용 |
| `TOSSINVEST_MUTATION_CONFIRMATION` | `I_UNDERSTAND_THIS_PLACES_A_REAL_ORDER` | 실주문 확인 문자열 |
| `TOSSINVEST_TOKEN_EXPIRY_SKEW_SECONDS` | `60` | 토큰 만료 safety skew |
| `TOSSINVEST_REQUEST_TIMEOUT_MS` | `30000` | 요청 타임아웃 |
| `TOSSINVEST_MAX_READ_RETRIES` | `2` | 읽기 요청 최대 재시도 |
| `TOSSINVEST_MAX_RESPONSE_BYTES` | `8388608` | 응답 크기 상한 |
| `TOSSINVEST_LOG_LEVEL` | `info` | `silent`/`error`/`warn`/`info`/`debug` |
| `TOSSINVEST_ENABLE_RAW_REQUESTS` | `false` | raw request 도구 노출 여부 |
인증 정보가 없어도 서버는 정상 기동합니다. 명세 조회·검색·상세 조회·인증 상태 조회는 인증 없이 동작하며,
실제 API 호출 시점에만 `credentials-missing` 오류를 반환합니다.
## 빌드 및 실행
```bash
pnpm install # 의존성 설치
pnpm dev # 개발 모드 (tsx watch)
pnpm build # dist/ 로 컴파일
pnpm start # 빌드 결과 실행 (stdio)
pnpm test # 테스트
pnpm test:coverage # 커버리지 포함 테스트
pnpm lint # ESLint
pnpm typecheck # 타입 검사
pnpm openapi:update # 공식 명세로 snapshot 갱신
pnpm openapi:verify # snapshot 검증
pnpm inspect # MCP Inspector 로 도구 목록 확인
```
`pnpm inspect`를 실행하면 MCP Inspector가 열리며 등록된 전체 도구 목록과 입력 스키마를 확인할 수 있습니다. (`pnpm build` 선행 필요)
## Docker로 실행
Node/pnpm을 설치하지 않고 컨테이너로 실행할 수 있습니다.
> **중요**: 이 서버는 **stdio MCP 서버**입니다. 컨테이너의 stdin/stdout이 곧 MCP 통신 채널이므로
> 반드시 `-i`(interactive) 옵션이 필요하며, `-t`(TTY)는 **주면 안 됩니다.** TTY는 JSON-RPC 프레이밍을 깨뜨립니다.
> 같은 이유로 `docker compose up`은 실사용에 적합하지 않습니다 (stdin이 MCP 클라이언트에 연결되지 않음).
### 1. 이미지 빌드
```bash
docker compose build
# 또는
docker build -t tossinvest-api-mcp:latest .
```
멀티스테이지 빌드로 최종 이미지에는 `dist/`와 **런타임 의존성 3개**(`@modelcontextprotocol/sdk`, `zod`, `dotenv`)만 포함됩니다.
`node` 사용자로 실행되며 root 권한을 사용하지 않습니다.
### 2. 환경변수 설정
인증 정보는 **이미지에 굽지 않고** 실행 시점에 주입합니다. `.dockerignore`가 `.env`를 빌드 컨텍스트에서 제외합니다.
```bash
cp .env.example .env # 값 입력
```
### 3. 실행
```bash
# 대화형 실행 (직접 JSON-RPC를 보내 확인할 때)
docker compose run --rm tossinvest-mcp
# 컨테이너 안의 도구 목록을 Inspector로 확인
docker compose --profile tools run --rm --service-ports inspector
# → http://127.0.0.1:6274 접속
```
package.json 단축 스크립트도 제공합니다.
```bash
pnpm docker:build
pnpm docker:run
pnpm docker:inspect
```
### 4. MCP 클라이언트에 Docker로 등록
MCP 클라이언트는 컨테이너를 직접 띄워야 하므로 `docker compose`가 아니라 `docker run -i`를 사용합니다.
**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"
]
}
}
}
```
`--env-file` 대신 개별 주입도 가능합니다.
```json
"args": [
"run", "--rm", "-i",
"-e", "TOSSINVEST_CLIENT_ID",
"-e", "TOSSINVEST_CLIENT_SECRET",
"-e", "TOSSINVEST_DEFAULT_ACCOUNT",
"tossinvest-api-mcp:latest"
]
```
> `-e KEY`(값 없이)는 호스트의 동일 이름 환경변수를 전달합니다. 설정 파일에 secret을 적지 않아도 됩니다.
### 컨테이너 보안 설정
`docker-compose.yml`에는 다음 하드닝이 적용되어 있습니다.
| 설정 | 이유 |
| --- | --- |
| `user: node` | root 실행 금지 |
| `read_only: true` | 서버는 파일 쓰기가 없음 (토큰은 메모리 보관) |
| `tmpfs: /tmp`, `HOME=/tmp` | 읽기 전용 루트에서 필요한 최소 쓰기 공간만 허용 |
| `cap_drop: ALL` | 모든 리눅스 capability 제거 |
| `no-new-privileges:true` | 권한 상승 차단 |
| `mem_limit`, `pids_limit` | 자원 남용 방지 |
| `stdin_open: true`, `tty: false` | stdio MCP 채널 보장 |
| `tini` (ENTRYPOINT) | SIGTERM 전달 → graceful shutdown |
| 실주문 env 기본 `false` | Dockerfile ENV에서도 안전 기본값 강제 |
Inspector 서비스는 `npx`로 패키지를 내려받아야 해서 `read_only: false`이며, 포트는 `127.0.0.1`에만 게시되어 외부에 노출되지 않습니다.
### 오프라인 동작
이미지에는 OpenAPI snapshot이 포함되어 있어, 원격 명세를 받지 못해도 서버가 기동됩니다.
```bash
# 원격 명세 조회를 끄고 snapshot만 사용
docker run --rm -i -e TOSSINVEST_SPEC_CACHE_ENABLED=false tossinvest-api-mcp:latest
```
다만 **실제 API 호출에는 인터넷 연결과 허용 IP 등록이 필요**합니다.
컨테이너의 아웃바운드 IP가 호스트 공인 IP와 다를 수 있으니(NAT/VPN 환경) 인증 실패 시 IP를 먼저 확인하세요.
## MCP 클라이언트 설정
빌드 후 `dist/index.js`의 **절대 경로**를 사용합니다.
### Claude Desktop
설정 파일 위치
- 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": "사용자 값",
"TOSSINVEST_CLIENT_SECRET": "사용자 값",
"TOSSINVEST_DEFAULT_ACCOUNT": "사용자 값",
"TOSSINVEST_ENABLE_TRADING": "false"
}
}
}
}
```
**Linux / macOS**
```json
{
"mcpServers": {
"tossinvest": {
"command": "node",
"args": ["/absolute/path/tossinvest-api-mcp/dist/index.js"],
"env": {
"TOSSINVEST_CLIENT_ID": "사용자 값",
"TOSSINVEST_CLIENT_SECRET": "사용자 값",
"TOSSINVEST_DEFAULT_ACCOUNT": "사용자 값",
"TOSSINVEST_ENABLE_TRADING": "false"
}
}
}
}
```
### Claude Code
```bash
# Linux / macOS
claude mcp add tossinvest \
--env TOSSINVEST_CLIENT_ID=사용자값 \
--env TOSSINVEST_CLIENT_SECRET=사용자값 \
--env TOSSINVEST_DEFAULT_ACCOUNT=사용자값 \
-- node /absolute/path/tossinvest-api-mcp/dist/index.js
```
```powershell
# Windows PowerShell
claude mcp add tossinvest `
--env TOSSINVEST_CLIENT_ID=사용자값 `
--env TOSSINVEST_CLIENT_SECRET=사용자값 `
--env TOSSINVEST_DEFAULT_ACCOUNT=사용자값 `
-- node C:/absolute/path/tossinvest-api-mcp/dist/index.js
```
또는 프로젝트 루트 `.mcp.json`:
```json
{
"mcpServers": {
"tossinvest": {
"command": "node",
"args": ["/absolute/path/tossinvest-api-mcp/dist/index.js"],
"env": {
"TOSSINVEST_CLIENT_ID": "사용자 값",
"TOSSINVEST_CLIENT_SECRET": "사용자 값"
}
}
}
}
```
### Cursor
`~/.cursor/mcp.json` 또는 프로젝트의 `.cursor/mcp.json`:
```json
{
"mcpServers": {
"tossinvest": {
"command": "node",
"args": ["C:/absolute/path/tossinvest-api-mcp/dist/index.js"],
"env": {
"TOSSINVEST_CLIENT_ID": "사용자 값",
"TOSSINVEST_CLIENT_SECRET": "사용자 값",
"TOSSINVEST_DEFAULT_ACCOUNT": "사용자 값"
}
}
}
}
```
### 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 = "사용자 값"
TOSSINVEST_CLIENT_SECRET = "사용자 값"
TOSSINVEST_DEFAULT_ACCOUNT = "사용자 값"
TOSSINVEST_ENABLE_TRADING = "false"
```
Windows에서는 `args`를 `["C:/absolute/path/tossinvest-api-mcp/dist/index.js"]`로 지정합니다.
### 일반 MCP stdio 클라이언트
```
command: node
args: <절대경로>/dist/index.js
transport: stdio
env: TOSSINVEST_CLIENT_ID, TOSSINVEST_CLIENT_SECRET, ...
```
stdout은 JSON-RPC 전용이며 모든 로그는 stderr로만 출력됩니다.
## 제공 도구
### 관리용 wrapper 도구
| 도구 | 설명 |
| --- | --- |
| `tossinvest_api_overview` | OpenAPI 버전, 문서 버전, 서버 URL, tag, operation 수, 읽기/mutation 수, 실주문 활성화 상태, snapshot/원격 사용 여부 |
| `tossinvest_list_operations` | `tag`/`method`/`path`/`readOnly`/`destructive`/`keyword` 필터로 operation 목록 조회 |
| `tossinvest_search_operations` | operationId·summary·description·path·tag 검색 |
| `tossinvest_get_operation` | 특정 operation 상세 (필수/선택 입력, requestBody, 응답 스키마, 계좌 헤더 필요 여부, mutation 여부, rate limit 그룹, MCP 도구 이름) |
| `tossinvest_call_operation` | operationId 기반 호출 wrapper (mutation guard 동일 적용) |
| `tossinvest_auth_status` | 인증 설정·토큰 준비 상태 (secret/token 미노출) |
| `tossinvest_refresh_auth` | 토큰 명시적 갱신 (`{ ok, expiresIn, expiresAt }`만 반환) |
| `tossinvest_raw_request` | `TOSSINVEST_ENABLE_RAW_REQUESTS=true`일 때만 노출. 공식 명세에 정의된 method+path만 허용 |
### OpenAPI 기반 직접 도구 자동 생성
서버는 시작 시 공식 OpenAPI 명세를 로드하여 **모든 operation을 MCP 도구로 자동 등록**합니다.
엔드포인트 목록을 코드에 하드코딩하지 않으므로, 공식 명세에 새 API가 추가되면 **서버 코드 수정 없이** 다음 시작 시 자동으로 도구가 생깁니다.
- 도구 이름 = OpenAPI `operationId` (MCP 규칙에 맞게 정규화, 충돌 시 서버 시작 실패)
- 설명 = `summary` + `description` + method/path + tag 조합
- 입력 스키마 = parameters와 requestBody에서 생성
- annotation = `readOnlyHint` / `destructiveHint` / `idempotentHint` / `openWorldHint`
입력 형식:
```json
{
"path": {},
"query": {},
"body": {},
"account": "1",
"dryRun": true,
"confirmation": ""
}
```
해당 operation에 필요 없는 필드는 스키마에서 제거됩니다. 예를 들어 `getAccounts`는 입력이 없고,
`getPrices`에는 `account`/`dryRun`/`confirmation`이 없습니다. 임의 헤더 입력은 허용되지 않습니다.
현재 명세(문서 버전 1.2.5) 기준으로 등록되는 도구:
| 분류 | 개수 |
| --- | --- |
| 전체 operation | 29 |
| 읽기 전용 | 23 |
| mutation (주문 3 + 조건주문 3) | 6 |
| 제외 | 1 (`issueOAuth2Token`) |
`issueOAuth2Token`(`POST /oauth2/token`)은 client secret과 access token이 MCP 경계를 넘지 않도록 **의도적으로 도구 등록에서 제외**하고, 서버 내부 인증 계층이 전담합니다. 대신 `tossinvest_auth_status` / `tossinvest_refresh_auth`를 사용하세요.
## 계좌 선택 방법
계좌가 필요한 API는 다음 순서로 계좌를 결정합니다.
1. 도구 입력의 `account`
2. `TOSSINVEST_DEFAULT_ACCOUNT`
3. 둘 다 없으면 **호출하지 않고** 오류 반환
임의로 첫 번째 계좌를 자동 선택하지 않습니다. 먼저 계좌 목록을 조회하세요.
```
getAccounts 도구를 호출해 accountSeq를 확인해줘
```
확인한 `accountSeq`를 `account`에 전달하거나 `.env`에 넣습니다.
```env
TOSSINVEST_DEFAULT_ACCOUNT=1
```
## 사용 예시
### 시세 조회
```
삼성전자와 애플 현재가를 알려줘
```
내부적으로 `getPrices`가 호출됩니다.
```json
{
"name": "getPrices",
"arguments": { "query": { "symbols": "005930,AAPL" } }
}
```
응답:
```json
{
"ok": true,
"operationId": "getPrices",
"httpStatus": 200,
"requestId": "01HXYZ...",
"rateLimit": { "limit": 10, "remaining": 9, "resetSeconds": 1 },
"result": [ { "symbol": "005930", "close": "70000" } ]
}
```
호가, 체결, 캔들도 같은 방식입니다.
```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 } } }
```
### 계좌 조회
```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" } }
```
### 주문 조회
```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 주문 (기본 동작)
```json
{
"name": "createOrder",
"arguments": {
"account": "1",
"body": {
"symbol": "005930",
"side": "BUY",
"orderType": "LIMIT",
"quantity": "10",
"price": "70000"
}
}
}
```
`dryRun`을 생략하면 **기본값 true**이므로 토스증권 서버로 **어떤 네트워크 요청도 보내지 않고** 실행 계획만 반환합니다.
```json
{
"ok": true,
"dryRun": true,
"operationId": "createOrder",
"executed": false,
"summary": {
"operationId": "createOrder",
"method": "POST",
"path": "/api/v1/orders",
"mutationType": "일반 주문 mutation (실제 자산 영향)",
"account": "*",
"symbol": "005930",
"side": "BUY",
"orderType": "LIMIT",
"quantity": "10",
"price": "70000"
},
"missingRequiredFields": [],
"blockers": [
"실주문이 비활성화되어 있습니다 (TOSSINVEST_ENABLE_TRADING=false).",
"dryRun=true 이므로 네트워크 요청을 보내지 않습니다.",
"confirmation 문자열이 비어 있습니다."
],
"requirementsToExecute": [
"TOSSINVEST_ENABLE_TRADING=true",
"도구 입력에 dryRun=false 를 명시",
"confirmation 에 TOSSINVEST_MUTATION_CONFIRMATION 과 동일한 값을 전달"
],
"note": "실제 주문은 전송되지 않았습니다. ..."
}
```
> 가격·수량·금액은 **문자열**로 전달하세요. 부동소수점 변환으로 인한 정밀도 손실을 막기 위해 서버는 이 값을 문자열 그대로 유지합니다.
## 실주문 활성화 절차
실제 주문은 아래 **8가지 조건이 모두** 충족될 때만 실행됩니다. 하나라도 어긋나면 네트워크 요청 자체를 보내지 않습니다.
1. `TOSSINVEST_ENABLE_TRADING=true`
2. 도구 입력 `dryRun=false`
3. `confirmation`이 `TOSSINVEST_MUTATION_CONFIRMATION`과 정확히 일치
4. 인증 정보(`CLIENT_ID`/`CLIENT_SECRET`) 정상
5. 계좌 명시(`account` 또는 `TOSSINVEST_DEFAULT_ACCOUNT`)
6. OpenAPI 스키마 필수값 충족
7. 요청 대상이 공식 API 서버
8. mutation 종류가 명확히 분류됨
절차:
```env
# 1) .env 수정
TOSSINVEST_ENABLE_TRADING=true
TOSSINVEST_MUTATION_CONFIRMATION=I_UNDERSTAND_THIS_PLACES_A_REAL_ORDER
TOSSINVEST_DEFAULT_ACCOUNT=1
```
```bash
# 2) MCP 클라이언트 재시작 (env 반영)
```
```json
// 3) 반드시 먼저 dry-run 으로 내용 확인 후, 실제 실행
{
"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`는 토스증권의 멱등성 키(10분 유효)입니다. 중복 주문 위험을 줄이려면 지정을 권장합니다.
## 조건주문 활성화 절차
조건주문은 위 조건에 더해 별도 플래그가 필요합니다.
```env
TOSSINVEST_ENABLE_TRADING=true
TOSSINVEST_ENABLE_CONDITIONAL_ORDERS=true
```
`TOSSINVEST_ENABLE_TRADING`만 켜고 조건주문을 실행하면 차단됩니다.
## mutation 자동 재시도 금지 정책
## Rate limit 처리
## 문제 해결
## 보안 주의사항
## 테스트
## OpenAPI snapshot 갱신
## MCP SDK 버전 선택
## 프로젝트 구조
## 기여
## 라이선스
## 变异自动重试禁止策略
以下操作**无论在任何情况下都不会自动重试**。
- `createOrder`,`modifyOrder`,`cancelOrder`
- `createConditionalOrder`,`modifyConditionalOrder`,`cancelConditionalOrder`
- 后续规范中新增的与订单相关的变异
timeout,connection reset,500/502/503等**结果不确定**的情况下也不会重试。
因为订单可能已经被受理了。相反,返回以下内容:
```json
{
"ok": false,
"operationId": "createOrder",
"httpStatus": 503,
"error": {
"code": "mutation-result-unknown",
"message": "createOrder 请求的结果不确定。未自动重试。",
"requestId": "01HXYZ...",
"retryable": false,
"details": {
"originalErrorCode": "upstream-error",
"clientOrderId": "my-order-001",
"account": "*",
"nextStep": "请通过订单历史查询 API 确认受理状态。"
}
}
}
```
在这种情况下,**重试前务必**通过 `getOrders` / `getOrder`(条件订单使用 `getConditionalOrders`)确认受理状态。
此外,401 自动刷新也不会应用于变异操作。只有只读 GET 请求会刷新令牌并重试一次。
## 速率限制处理
解析响应头 `X-RateLimit-Limit`、`X-RateLimit-Remaining`、`X-RateLimit-Reset`、`Retry-After`,返回成功的 `rateLimit` 字段:
```json
"rateLimit": { "limit": 10, "remaining": 9, "resetSeconds": 1 }
```
重试策略:
- 只有明确的只读请求(如 GET)会重试
- 禁止变异操作重试
- 如果有 `Retry-After`,优先使用(超过 30 秒不会重试)
- 如果没有,使用指数退避 + 抖动
- 最大重试次数为 `TOSSINVEST_MAX_READ_RETRIES`(默认为 2)
- 身份验证失败、输入错误、权限错误不会重试
- 超时使用 `AbortController` 实现(`TOSSINVEST_REQUEST_TIMEOUT_MS`)
## 问题解决
| 症状 | 原因 / 解决 |
| --- | --- |
| `credentials-missing` | 未设置 `TOSSINVEST_CLIENT_ID` / `TOSSINVEST_CLIENT_SECRET`。请确认 MCP 客户端 `env` 是否正确,重启客户端 |
| `auth-failed` (`invalid_client`) | client ID/secret 输入错误或客户端未激活。不会自动重试,请手动检查 |
| `ip-not-allowed` | WTS → 设置 → Open API → 允许 IP 管理,注册当前公网 IP |
| `account-header-required` | 缺少 `account` 参数或未设置 `TOSSINVEST_DEFAULT_ACCOUNT`。使用 `getAccounts` 确认 `accountSeq` |
| `rate-limit-exceeded` | 调整调用频率。请参考响应中的 `retryAfterSeconds` |
| `trading-disabled` | 未满足 8 个实交易条件。请检查响应中的 `details.requirementsToExecute` |
| `mutation-blocked` | 变异操作分类异常。需要更新 `src/config/constants.ts` 中的策略表 |
| `mutation-result-unknown` | 订单结果不确定。请使用 `getOrders` 确认后判断 |
| `openapi-load-failed` | 网络问题。使用 `pnpm openapi:update` 更新快照 |
| 工具列表不显示 | `pnpm build` 后检查绝对路径。使用 `pnpm inspect` 直接检查 |
| 客户端 JSON 解析错误 | stdout 污染。该服务器仅在 stdout 上输出 JSON-RPC,请检查其他包装脚本是否输出 |
| Docker 立即退出 | 缺少 `-i` 选项。当 stdin 关闭时,stdio 服务器会退出 |
| Docker 中 JSON-RPC 损坏 | 使用 `-t`(TTY)参数。仅使用 `docker run --rm -i` |
| Docker 中 `credentials-missing` | `--env-file` 路径是否为绝对路径,`.env` 文件中是否有值 |
| Docker 中 `ip-not-allowed` | 容器出站 IP 可能与主机不同。请重新检查允许的 IP |
| `docker compose up` 不工作 | 正常。这是 stdio 服务器,请使用 `docker compose run --rm` 或 MCP 客户端中的 `docker run -i` 运行 |
可以通过提高日志级别在 stderr 中查看更详细的信息。机密值即使在 debug 模式下也会被隐藏。
```env
TOSSINVEST_LOG_LEVEL=debug
```
## 安全注意事项
- `.env` 和身份验证文件被 `.gitignore` 处理,请勿提交。
- access token **仅保存在内存中**,不会存储或输出到文件、数据库、日志或 MCP 响应中。
- client secret 和 Authorization 头部在日志和错误对象中会被隐藏。
- 账户标识符在日志和 dry-run 摘要中会被屏蔽。
- 用户无法直接指定 `Authorization`、`Cookie`、`Host`、`Content-Length`、`Proxy-Authorization`、`X-Tossinvest-Account` 头部。
- 默认情况下,仅允许调用官方域名(`openapi.tossinvest.com`)以防止 SSRF。
- 未定义的路径无法调用。
- 防止路径遍历、原型污染、大响应和 JSON 解析错误。
- 实交易默认禁用,dry-run 为默认值。
详细内容请参考 [SECURITY.md](./SECURITY.md)。
## 测试
```bash
pnpm test
pnpm test:coverage
```
所有测试均使用 HTTP 模拟,不会实际调用 Toss Securities API。
测试范围:
- OpenAPI:快照解析、operationId 索引/重复检测、`$ref` 解析、循环引用、requestBody/parameter 转换、required·enum·oneOf/anyOf/allOf·nullable 处理、工具名称冲突、新 operation 自动注册
- 身份验证:身份验证信息缺失、首次颁发、缓存、安全偏移、single-flight 并发颁发防止、401 处理、机密隐藏
- 账户:显式账户、默认账户、缺失时阻止、仅在必要 API 上添加头部、屏蔽
- 请求:路径编码、查询序列化、多符号、JSON/form 正文、超时、429、`Retry-After`、4xx/5xx 规范化、requestId 提取、decimal 精度保持
- 订单安全性:默认禁用、dryRun 默认 true、dry-run 无通信、确认验证、条件订单单独标志、变异不重试、wrapper·raw 绕过不可用
- MCP:initialize、tools/list、管理工具、直接工具、错误响应、**stdout 无污染**、stderr 日志、graceful shutdown
## 更新 OpenAPI 快照
优先使用远程规范,但在网络故障时回退到捆绑快照。
```bash
pnpm openapi:update # 下载官方 JSON → 更新 src/openapi/openapi.snapshot.json
pnpm openapi:verify # 验证操作数、operationId 重复、模式转换可行性
```
`openapi:verify` 输出示例:
```
[openapi:verify] 验证成功
OpenAPI 版本 : 3.1.0
API 文档版本 : 1.2.5
API 服务器 : https://openapi.tossinvest.com
路径数 : 27
已注册操作 : 29
只读操作 : 23
变异操作 : 6
未分类变异操作 : 0
排除操作 : 1
```
## MCP SDK 版本选择
- 选择版本:**`@modelcontextprotocol/sdk` 1.29.0**(固定在 `package.json` 中)
- 选择原因:
- 在实现时,根据 npm `latest` 标签所指向的**稳定版本**。
- 低级 `Server` + `StdioServerTransport` + `setRequestHandler` API 是稳定的,并且未被废弃。
- `structuredContent` 在稳定版本中受支持,因此返回人类可读的文本和机器可处理的格式化结果。
- 未使用 `experimental/*` 路径下的 API。
## 项目结构
```text
tossinvest-api-mcp/
├─ src/
│ ├─ index.ts # stdio 入口,信号处理
│ ├─ server.ts # MCP 服务器配置,工具注册
│ ├─ config/ # 环境变量验证(Zod),常量和策略表
│ ├─ auth/ # OAuth 令牌管理,身份验证错误
│ ├─ openapi/ # 规范加载器、$ref 解析、模式转换、工具生成
│ │ └─ openapi.snapshot.json # 离线回退的捆绑规范
│ ├─ client/ # 请求组装、响应解析、重试、速率限制
│ ├─ tools/ # 变异分类、guard、操作/管理工具
│ ├─ security/ # 隐藏、URL 策略、敏感字段定义
│ ├─ errors/ # AppError、错误规范化
│ └─ utils/ # 记录器(stderr)、single-flight、JSON
├─ tests/ # Vitest(不调用实际 API)
├─ scripts/ # 快照更新、验证、构建资产复制
├─ Dockerfile
└─ docker-compose.yml
```
## 贡献
欢迎提交问题和 PR。请先阅读 [CONTRIBUTING.md](./CONTRIBUTING.md)。
在提交 PR 之前,请确保所有检查均已通过:
```bash
pnpm typecheck && pnpm lint && pnpm test && pnpm build && pnpm openapi:verify
```
> **请勿添加调用实际订单 API 的代码或测试。**
> 安全漏洞请遵循 [SECURITY.md](./SECURITY.md) 中的程序。
## 许可证
[MIT](./LICENSE)
本项目与 Toss Securities 无关,不是金融或投资建议软件。
使用所产生的任何责任均归用户承担。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器