Metadata-Version: 2.5
Name: tossinvest-mcp
Version: 0.5.0
Summary: MCP server for Toss Securities (토스증권) Open API: Korean stock quotes, holdings, orders, technical analysis
Project-URL: Homepage, https://github.com/hypn4/tossinvest-mcp
Project-URL: Repository, https://github.com/hypn4/tossinvest-mcp
Project-URL: Issues, https://github.com/hypn4/tossinvest-mcp/issues
Project-URL: Changelog, https://github.com/hypn4/tossinvest-mcp/releases
Author-email: hypn4 <khs951211@gmail.com>
License-Expression: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Keywords: ai-agent,claude,fastmcp,korean-stocks,krx,llm,mcp,mcp-server,model-context-protocol,stock-api,stock-market,technical-analysis,toss-securities,tossinvest,trading,토스증권,한국주식
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.12
Requires-Dist: fastmcp<5,>=4.0.5
Requires-Dist: httpx2<3,>=2.13.0
Requires-Dist: mcp<3,>=2.2.0
Requires-Dist: pydantic-settings<3,>=2.15.0
Requires-Dist: pydantic<3,>=2.13.5
Requires-Dist: talipp<3,>=2.7.0
Description-Content-Type: text/markdown

# 토스증권 MCP

[![PyPI](https://img.shields.io/pypi/v/tossinvest-mcp)](https://pypi.org/project/tossinvest-mcp/)
[![Python](https://img.shields.io/pypi/pyversions/tossinvest-mcp)](https://pypi.org/project/tossinvest-mcp/)
[![License](https://img.shields.io/github/license/hypn4/tossinvest-mcp)](LICENSE)
[![CI](https://github.com/hypn4/tossinvest-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/hypn4/tossinvest-mcp/actions/workflows/ci.yml)

토스증권 Open API를 [MCP](https://modelcontextprotocol.io) 도구로 노출하는 서버입니다.
Claude 같은 AI 에이전트가 국내·미국 주식의 시세/보유 종목/주문/기술적 분석을 도구로 사용할 수 있습니다.

> **English**: MCP server exposing the Toss Securities (토스증권) Open API as tools for Claude and other AI agents. Covers Korean and US stock quotes, market-wide stock lists, Korean investor/program/short-selling/credit/lending trends, holdings, order execution, conditional orders (SINGLE/OCO/OTO), market rankings, index indicators, and a session-aware technical-analysis dashboard (MACD/RSI/Bollinger/VWAP). Read-only by default; opt-in trading. (Korean docs below.)

> **실거래 경고**: 주문 실행 툴은 실제 주문/실자금을 발생시킵니다.
> 기본값은 **읽기 전용**이며, `TOSS_ENABLE_TRADING=true`일 때만 주문 실행 툴이 켜집니다.

## 할 수 있는 일

- **시세**: 현재가, 호가, 체결 내역, 캔들 차트, 상/하한가
- **종목/시장**: 마켓별 전체 종목 목록(상태·유형·보통주 필터), 종목 정보, 매수 유의사항, 환율, 국내/해외 장 운영시간
- **국내 수급**: 투자자별 매매동향, 프로그램매매, 공매도, 신용거래, 대차거래 시계열
- **랭킹/지표**: 거래대금/거래량/등락률 랭킹, 시장 지표(국내 지수·국채 시세/캔들/투자자별 매매대금)
- **기술적 분석**: 지표 대시보드(이동평균/MACD/RSI/볼린저밴드/ATR/OBV/MFI 등), 장중 VWAP, 호재/악재 데이터 신호
- **계좌/조회**: 계좌 목록, 보유 종목, 주문·조건주문 내역, 매수 가능 금액, 매도 가능 수량, 수수료
- **주문 실행** *(거래 활성화 시)*: 주문 생성/정정/취소, 조건주문 등록/수정/취소 (단일·OCO·OTO)
- **워크플로 프롬프트**: 포트폴리오 분석, 차트 분석, 매수 전 체크리스트 등 바로 쓰는 분석 템플릿

## API 지원 범위

2026-09-17에 확인한 토스증권 Open API **v1.2.17**을 고정해 사용합니다.
읽기 전용 도구는 **32개**, 거래 활성화 시 **38개**입니다. 런타임에 사양을 자동 교체하지 않습니다.
원본 URL·검증 해시·버전별 변경점은 [`spec/SOURCE.md`](tossinvest_mcp/spec/SOURCE.md)에 기록합니다.

원본 OpenAPI 파일은 수정하지 않습니다. `load_spec()`가 메모리에 읽은 component schema의
레거시 `nullable: true`만 JSON Schema의 `anyOf: [기존 제약, {type: null}]`로 보정합니다.
현재 v1.2.17에서는 조건주문 생성·수정·조회 모델의 `second` 세 곳이 대상입니다.
따라서 `SINGLE` 조회의 `second: null`을 데이터 변경 없이 처리하면서, `first`의 필수 여부와
비-null 객체의 필드·enum 검증은 유지합니다. 예제·기본값 데이터에는 이 변환을 적용하지 않으며,
이미 표준 null 허용 표현을 쓰는 스키마도 그대로 둡니다. 출력 검증을 끄는 우회는 사용하지 않습니다.

이 서버는 **REST API와 계산형 분석**을 지원합니다. 토스증권의 별도 AsyncAPI 기반
WebSocket 실시간 구독·이벤트 스트리밍은 아직 구현하지 않았습니다.

## 의존성과 FastMCP 4

2026-09-18에 PyPI의 최신 안정 릴리스를 확인해 런타임·개발·빌드 의존성과 `uv.lock`의
간접 의존성을 함께 갱신했습니다. 미검증된 다음 주버전이 자동 설치되지 않도록 직접 의존성에
주버전 상한을 두되, 현재 지원 주버전의 새 안정 릴리스는 허용합니다. 정확한 재현에는
`uv sync --frozen`을 사용합니다.

잠금 파일의 외부 패키지 80개 중 79개는 조사 시점의 최신 안정 릴리스입니다.
`pydantic-core`는 최신 개별 릴리스가 아니라 Pydantic 2.13.5가 정확히 요구하는
`2.46.5`를 사용합니다. Pydantic과 분리해 강제로 올리지 않습니다.

| 역할 | 기준 버전 |
|---|---|
| REST 자동 생성 | FastMCP 4.0.5, MCP SDK 2.2.0 |
| HTTP·인증·재시도 | HTTPX2 2.13.0 |
| 설정·검증 | Pydantic 2.13.5, pydantic-settings 2.15.0 |
| 지표 계산 | talipp 2.7.0 |
| 테스트 | pytest 9.1.1, pytest-asyncio 1.4.0, jsonschema 4.26.0, PyYAML 6.0.3 |
| 정적 검사 | Ruff 0.16.8, Pyright 1.1.414 |
| 빌드 | Hatchling 1.32.3, hatch-vcs 0.5.0 |
| 의존성 해석·CI | uv 0.12.16 |

`FastMCP.from_openapi()` 기반 자동 생성은 유지합니다. Python의 MCP 모델 필드는
`read_only_hint` 등의 snake_case를 사용하지만, 프로토콜 JSON과 토스 API 필드명은 바꾸지 않습니다.
외부 HTTP 클라이언트를 주입할 때는 기존 `httpx.AsyncClient` 대신 `httpx2.AsyncClient`를
사용해야 합니다. 인증·재시도·수명주기·분석·스펙 갱신·테스트 모두 HTTPX2로 통일했으며
구형 HTTPX 호환 별칭이나 중복 REST 모델은 두지 않습니다.

HTTPX2의 기본 TLS 검증은 `truststore`를 통해 운영체제 인증서 저장소를 사용합니다.
`SSL_CERT_FILE`·`SSL_CERT_DIR` 또는 명시적인 SSLContext 설정을 점검하고, 인증서 오류를
`verify=False`로 우회하지 마세요. HTTP 로거 이름도 `httpx2`·`httpcore2`로 변경됩니다.

SDK v2 클라이언트는 잘못된 structured content에 대해 `RuntimeError`를 발생시킬 수 있습니다.
`raise_on_error=False`는 도구의 오류 결과를 반환하도록 하는 옵션이지 출력 스키마 검증을
끄는 옵션이 아닙니다. 조건주문의 정상 `second: null`과 잘못된 객체·필수값 누락을
현재 프로토콜(`2026-07-28`)에서 검사합니다. 프로젝트는 구형 프로토콜 전용 분기와
호환성 테스트를 유지하지 않습니다. FastMCP 자체의 프로토콜 협상 코드는 수정하지 않습니다.

`tossinvest-mcp` 실행 진입점은 FastMCP의 Python camelCase 호환 브리지를 끕니다.
`build_server()`를 다른 애플리케이션에 임베딩할 때는 호스트의 전역 FastMCP 설정을 변경하지
않지만, 이 프로젝트 코드는 네이티브 snake_case 필드만 사용합니다. 토스 원본 스펙의
`nullable`·계좌 헤더 보정은 현재 응답을 처리하기 위한 어댑터이므로 유지합니다.

## 빠른 시작

1. [토스증권 OpenAPI 콘솔](https://developers.tossinvest.com/docs)에서 `client_id`/`client_secret` 발급
2. 설정 후 실행:

```bash
cp .env.example .env   # 발급받은 키 입력
uv sync
uv run tossinvest-mcp
```

설정할 수 있는 모든 값은 [`.env.example`](.env.example)에 정리되어 있습니다.

## Claude Code에 등록

### 추천: `uvx` (설치/클론 없이 어디서나)

`uvx`로 바로 실행할 수 있습니다. 자격증명은 `--env`로 전달하므로 `.env` 파일이 필요 없습니다:

```bash
claude mcp add tossinvest \
  --env TOSS_CLIENT_ID=발급받은_CLIENT_ID \
  --env TOSS_CLIENT_SECRET=발급받은_CLIENT_SECRET \
  -- uvx tossinvest-mcp
```

주문 실행까지 쓰려면 `--env TOSS_ENABLE_TRADING=true`를 추가하세요.

### 로컬 체크아웃 (개발용)

저장소를 클론해 수정하며 쓸 때. 자격증명은 `.env`로 설정합니다:

```bash
claude mcp add tossinvest -- uv --directory /path/to/tossinvest-mcp run tossinvest-mcp
```

## 거래 활성화

기본은 안전하게 **읽기 전용**입니다. 주문 실행까지 쓰려면 `.env`에 추가하세요:

```bash
TOSS_ENABLE_TRADING=true
```

AI가 직접 실주문을 낼 수 있으니 신뢰할 수 있는 환경에서만 켜세요.

## 계산값과 입력 검증

MFI의 숫자와 해석용 상태를 구분합니다. TA-Lib 0.6.4의 계산 규칙에 맞춰 해당 기간의
상승·하락 자금흐름 합계가 1 미만이면 `mfi=0`을 반환하지만, 이 경우는 하락 우위를 의미하지
않으므로 `mfi_state=null`로 두고 `mfi_note`에 상태 판정을 생략한 이유를 표시합니다.
거래량이 0이거나 전형가격이 변하지 않는 기간도 여기에 해당합니다. 실제로 하락 자금흐름만
존재하는 기간의 `mfi=0`과는 구분합니다. 기간보다 봉이 부족하면 기존처럼 `mfi=null`입니다.

캔들 OHLCV에는 유한한 숫자만 허용합니다. `NaN`, 무한대, 숫자 변환 오버플로, 불리언 및
음수 거래량을 포함한 응답은 분석에 사용하지 않으며, 요청을 오류로 종료하고 기존 캐시를
보존합니다. 숫자가 아닌 문자열도 거부합니다. 명시적으로 결측인 OHLCV(`null` 또는 키 없음)
행을 제외하는 기존 동작은 유지하지만, 같은 행의 다른 필드에 비정상 숫자가 있으면 거부합니다.

가격 0 자체는 제거하거나 다른 값으로 바꾸지 않습니다. 등락률의 기준 가격이 0이면
해당 `change.pct_1bar` 또는 `change.pct_period`를 `null`로 반환하고,
`pct_1bar_note` / `pct_period_note`로 이유를 알립니다. 기준 가격이 양수이고 현재 가격만
0이면 정상적으로 `-100%`입니다. 비교할 봉이 부족한 경우도 계산 불가 사유를 구분합니다.

캔들 시각은 명시적인 시간대(`Z` 또는 `±HH:MM`)가 있는 날짜·시간만 허용합니다.
소수초는 최대 6자리까지 지원하며, 시간대 누락·잘못된 날짜·윤초·더 높은 정밀도는
임의 보정이나 잘림 없이 오류로 반환합니다. OHLCV가 결측인 행의 시각도 검증합니다.
시각 비교·중복 제거·SQLite 키는 고정 정밀도의 UTC 식별자를 사용하지만, 응답의
`timestamp`는 원본 표기를 유지하므로 현지 자정 일봉의 거래일이 UTC 날짜로 바뀌지 않습니다.
같은 순간의 중복 행은 입력의 마지막 행을 사용합니다. 직접 계산도 같은 정렬·중복 제거를 적용합니다.
검증된 캔들은 내부에서 변경 불가능한 값으로 전달하고 UTC 식별자를 재사용합니다. 외부 응답과
DB 복원 경계에서는 검증하되, 지표·세션 계산마다 같은 숫자와 시각을 다시 변환하지 않습니다.
체결 흐름도 UTC 시각순으로 계산합니다. 같은 시각에 발생한 별개의 체결은 합치지 않으며,
잘못된 체결 시각·숫자·음수 거래량은 선택적 신호 표본에서 제외합니다.

캔들 DB는 UTC 식별자 `instant`를 사용하는 현재 스키마만 지원합니다. 구형 DB의 시각 키를
변환하거나 테이블을 교체하는 자동 이전 코드는 없습니다. 지원하지 않는 스키마는 경고와 함께
영속 캐시를 비활성화하고, 기존 가용성 정책에 따라 인메모리로 동작합니다. 기존 파일을 자동으로
삭제하거나 변환하지 않습니다. 영속 캐시가 필요하면 **새 `TOSS_CACHE_DB_PATH`를 지정해 다시
시작하세요.** 공유 캐시는 모든 프로세스를 같은 버전과 새 경로로 맞춰야 합니다.
새 스키마 생성도 하나의 트랜잭션으로 수행하며, 실패·중단 시 롤백하고 연결을 닫습니다.

같은 숫자·시각 검증을 직접 계산, 메모리/SQLite 저장 및 SQLite 복원에도 적용합니다. 현재 스키마에
비정상 값이 발견되면 경고를 남기고 해당 연결의 영속 캐시를 비활성화한 뒤 인메모리로
전환합니다. 손상된 파일은 자동 삭제하거나 덮어쓰지 않습니다. 정상 API 응답을 다시 받으면
분석을 재개할 수 있습니다. 체결의 비정상 숫자·시각은 신호 표본에서 제외합니다.

MACD·Stochastic·ADX는 별도의 고정 봉 수 조건 대신 계산 라이브러리의 마지막 출력에서
각 지표의 필수 값이 모두 준비됐는지 확인합니다. MACD는 MACD·시그널·히스토그램,
Stochastic은 K·D, ADX는 ADX·+DI·−DI가 모두 준비되어야 반환합니다. 일부 값만 준비된
경우 지표와 관련 상태는 `null`로 유지하고, 완성된 값은 0이어도 지연 없이 반환합니다.
따라서 `lookback=30`을 요청했어도 실제 이력이 그보다 짧으면 확보된 봉으로 계산 가능한
지표를 반환합니다. 계산식과 출력 필드 구성은 기존 라이브러리·대시보드 계약을 유지합니다.

`get_market_signals`는 각 조회의 HTTP 오류뿐 아니라 계산에 필요한 객체·배열 구조도 개별적으로
검증합니다. 잘못된 응답이나 비정상 호가 잔량 때문에 다른 정상 신호를 버리지 않습니다.
실패한 항목은 `unavailable`에 기록하고, 그 정보로만 만드는 결과 부분은 `null`로 반환합니다.
호가 실패를 잔량 0으로 표시하지 않습니다. 정상적인 빈 호가·체결 배열은 실패와 구분합니다.
이것은 분석에 쓰는 구조의 검증이며, 전체 OpenAPI 응답 스키마의 재검증은 아닙니다.

가격제한의 존재와 현재가 대비 근접도는 별개입니다. `limit.upper_limit_price`와
`limit.lower_limit_price`는 수신한 제한 가격을 보존합니다. 현재가 목록이 비었거나 요청한 종목의
유효한 양수 현재가가 없으면 `unavailable`에 `prices`를 기록하고, 근접도(`pct_to_upper/lower`,
`near_upper/lower`)만 `null`로 둡니다. 현재가 조회 오류도 이미 받은 제한 가격을 지우지 않습니다.
상·하한가가 모두 명시적으로 `null`일 때만 가격제한 없음으로 표시하며, 제한 가격 정보가
미제공된 경우와 구분합니다. 가격제한 조회 자체가 실패하면 `limit=null`입니다.

세 분석 도구의 `symbol`은 단일 종목만 받습니다. 공통 입력 제약은 고정 스펙의 `SymbolQuery`와
일치하며 테스트로 동기화를 확인합니다. 빈 문자열·쉼표·공백·경로 구분자는 HTTP 호출 전에 거부하고,
`005930`, `0101N0`, `aapl`, `BRK.B`, `BRK-B` 같은 유효한 표기는 그대로 허용합니다.
자동 생성된 `getPrices`·`getStocks` 등의 복수 종목 `symbols` 인자는 변경하지 않습니다.

## 세션 분석 기준

`analyze_indicators(interval="1m")`와 `intraday_vwap`의 `session="auto"`는
**최신 분봉이 속한 세션**을 선택합니다. `day/pre/regular/after`를 명시하면
**현재 시각 이전에 시작한 가장 최근 해당 세션**을 선택하며, 그 세션에 봉이 없으면
이전 날짜 데이터로 대체하지 않습니다. `active_session`과 `in_progress`는 실제 현재 시각과
해당 날짜의 세션 구간으로 판단합니다. 데이터가 지연돼도 과거 세션을 진행 중으로 표시하지 않습니다.

`day/pre/regular/after`는 US 전용입니다. 국내 종목에 지정하거나 캘린더 조회·해석에 실패하면
다른 세션으로 대체하지 않고 오류를 반환합니다. 세션은 확인됐지만 봉이 없는 경우에는 정상적인
빈 결과를 반환합니다.

`auto`에서만 국내 종목이나 캘린더 조회 실패 시 30분 초과 분봉 간격을 세션 경계로 추정합니다.
거래가 드문 종목의 장중 공백도 경계가 될 수 있으며, 거래소 캘린더에 의한 완전한 판정은 아닙니다.
두 분봉 분석 도구의 `session_resolution`은 `calendar`(캘린더로 확인),
`gap_heuristic`(간격으로 추정), `no_data`(봉이 없어 자동 선택 불가)로 결과의 근거를 구분합니다.

캔들은 시각별로 중복 제거·정렬합니다. 최신 봉 시각이 이미 캐시된 확정 봉보다 늦지 않은 응답은
지연 스냅샷으로 무시하며, 그 응답의 과거 페이지 커서도 사용하지 않습니다.
겹치는 확정 봉 중 하나라도 OHLC가 변경되면 재조정 범위를 알 수 없으므로 별도 스냅샷을 재구성합니다.
거래량만 정정된 경우에는 가격 시리즈를 유지하고 정정값을 저장합니다.
동일 종목·봉 단위의 조회는 직렬화하되 다른 종목은 병렬 조회할 수 있습니다. 필요한 과거 페이지를
모두 수집한 뒤 한 번에 캐시에 반영하므로 조회 실패·취소가 부분 결과를 남기지 않습니다.

모든 캔들 조회는 동일한 수집 경로에서 `candles` 배열과 `nextBefore` 형식을 검증합니다.
잘못된 응답을 빈 마지막 페이지로 해석하지 않습니다. 페이지가 비어 있거나 결측 OHLCV만 있어도
다음 커서가 있으면 조회를 이어가며, 원시 행 수가 아닌 유효한 고유 봉 수를 기준으로 수집합니다.
과거 페이지는 앞서 받은 페이지보다 오래된 봉만 추가하므로 겹치는 행이 최신 값을 덮어쓰지 않습니다.
필요한 데이터가 남았는데 커서가 반복되거나 조회 한도(최신 페이지 1회 + 추가 10회)에 도달하면
오류로 종료하고 기존 캐시를 보존합니다. 반대로 다음 커서가 없는 정상적인 마지막 페이지는
봉 수가 요청보다 적더라도 정상 결과로 반환합니다.

SQLite를 공유하는 프로세스는 저장 직전에 조회 시작 시점의 원본과 현재 저장값을 비교합니다.
다른 프로세스가 갱신했으면 오래된 결과를 폐기하고 새로 조회하며, 3회 연속 충돌하면 오류를 반환합니다.
비교·교체는 하나의 SQLite 트랜잭션이고 HTTP를 기다리는 동안 데이터베이스 잠금을 잡지 않습니다.
디스크에서 복원한 시리즈도 메모리의 최대 보관 수 제한을 따릅니다. SQLite 연결은 최초 비동기 접근에
생성하고, 각 영속 저장 작업은 내부 대기열·연결 초기화·SQLite 잠금 재시도에 공통 5초 대기 기한을
적용합니다. 대기열에서 차례를 얻어도 기한이 새로 시작되지 않으며, 만료 후 쓰기를 시작하지 않습니다.
이는 잠금 대기 예산이며, 이미 실행 중인 동기 SQLite 작업을 강제로 중단하는 제한은 아닙니다. 잠금 대기 중
취소하거나 시간이 초과되면 기존 스냅샷과 DB를 유지하며, 단순 잠금 충돌로 영속 저장을 포기하지 않습니다.
손상·잠금 외 저장 오류 시에는 연결을 닫고 인메모리 모드로 전환합니다. 행 쓰기 함수가 중간에
커밋하지 않으며, 스냅샷 교체·캘린더 저장·스키마 이전 각각이 자신의 트랜잭션을 소유합니다.
공유 파일은 같은 버전의 서버끼리 로컬 디스크에서 사용하세요.

캘린더 캐시도 복원·저장 전에 객체 형식과 유한한 수집 시각을 검증합니다. 손상된 캐시 행은
메모리에 넣지 않고 위의 영속 저장 비활성 정책을 따르며, 원본 DB 파일은 보존합니다.
캐시의 세션 구간을 해석할 수 없으면 호출당 한 번 새 캘린더를 조회합니다. 세션 자체가 없거나
`null`인 경우와, 세션 객체는 있으나 시작·종료 시각 일부가 빠진 경우를 구분합니다. 후자는 손상으로
처리하므로 전날 구간이 정상이어도 오늘의 불완전한 구간을 조용히 제외하지 않습니다.
제공된 모든 구간의 시작·종료 시각과 순서를 확인한 뒤 재사용하며, 새 조회도 실패하면 명시 세션은 오류를 반환하고
`auto`만 기존 간격 추정을 허용합니다. 반복 재조회로 요청이 끝나지 않는 동작은 하지 않습니다.

서버 시작 때마다 자체 HTTP 클라이언트를 새로 만들고 종료 시 SQLite와 함께 닫습니다.
동일 서버 객체를 재시작해도 도구들이 새 클라이언트를 사용하며, 서버 조립만으로는 연결을 만들지 않습니다.
`build_server(client=...)`로 외부 클라이언트를 주입한 경우에는 호출자가 해당 클라이언트를 닫아야 합니다.

## REST 요청 재시도

응답 헤더뿐 아니라 JSON 본문 수신까지 한 번의 시도로 처리합니다. GET/HEAD/OPTIONS의
네트워크·본문 수신 오류와 5xx는 설정된 횟수까지 재시도하지만, 주문 POST의 불확실한 실패는
재전송하지 않습니다. 429는 요청 거절로 처리해 모든 메서드에 재시도를 허용합니다.
재시도 전에는 이전 응답을 닫으며, 요청 취소는 재시도하지 않고 그대로 전파합니다.

유효한 `Retry-After`의 초 또는 HTTP-date 값을 우선 적용합니다. 30초 상한은 자체 지수 백오프에만
적용되며 서버가 지정한 대기를 줄이지 않습니다. 잘못된 헤더는 자체 백오프로 처리합니다.
`TOSS_REQUEST_TIMEOUT`은 각 HTTP I/O의 타임아웃이며 재시도 대기를 포함한 전체 실행 기한은 아닙니다.
긴 서버 지정 대기 중에도 호출을 취소할 수 있습니다. 이 전송 계층은 본문을 버퍼링하는 REST 전용이며
WebSocket이나 무한 스트림에는 사용하지 않습니다.

## 여러 MCP 서버에서 동일한 자격증명 사용

토스증권은 클라이언트당 유효한 access token을 하나만 허용하므로, 별도 서버가 각각
토큰을 발급하면 서로의 토큰이 무효화될 수 있습니다. 기본 인메모리 인증은 단일 서버용입니다.
같은 컴퓨터에서 여러 MCP 프로세스를 사용할 때는 **모든 프로세스에 동일한 절대 경로**를 지정하세요:

```dotenv
TOSS_TOKEN_CACHE_PATH=~/.cache/tossinvest/oauth-tokens.db
```

`~`는 사용자 홈으로 확장됩니다. 이 옵션은 SQLite로 토큰과 재발급 잠금을 공유하고,
401 응답에도 다른 프로세스가 이미 갱신한 토큰을 먼저 재사용합니다. 잠금 대기는 HTTP 요청
타임아웃을 기준으로 제한되며, 캐시 손상·권한 오류·잠금 실패 시 별도 토큰을 발급하지 않고 오류를 반환합니다.

**보안:** access token은 이 파일에 **평문 저장**됩니다. client secret은 저장하지 않습니다.
POSIX에서는 사용자 소유의 전용 파일(새 파일은 `0600`)만 허용하며, 다른 사용자가 쓸 수 있는
디렉터리와 심볼릭 링크는 거부합니다. Windows에서는 사용자 전용 디렉터리의 ACL을 직접 확인하세요.
신뢰할 수 있는 로컬 파일시스템만 사용하고, 네트워크 드라이브·클라우드 동기화·Git에는 보관하지 마세요.
기본값은 미설정이며 디스크에 토큰을 저장하지 않습니다. 기존 `.env`는 자동 변경하지 않습니다.

다른 컴퓨터 또는 이 캐시를 사용하지 않는 프로그램에서 같은 키를 재발급하는 상황은 조정하지 못합니다.
키를 교체할 때는 같은 키를 사용하는 서버를 모두 함께 갱신하세요.
캔들·캘린더 캐시인 `TOSS_CACHE_DB_PATH`와는 별도 파일을 사용하세요.

## 스펙 갱신과 테스트

```bash
# 공식 문서와 차이만 확인: 같으면 0, 다르면 1. 파일은 변경하지 않음.
uv run python scripts/refresh_spec.py --check

# 두 문서를 모두 다운로드하고 기본 구조·참조를 검증한 뒤 교체.
uv run python scripts/refresh_spec.py

# 기본 실행은 E2E를 건너뛰고 실제 HTTP 전송을 차단합니다.
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run pyright

# 실계좌 읽기 전용 E2E: 명시적 허용과 자격증명이 모두 필요합니다.
uv run pytest --run-e2e -m e2e
```

`-m e2e`만으로는 실계좌 테스트가 실행되지 않습니다. `--run-e2e`가 있어도 `e2e` 마커가 없는
일반 테스트의 실제 HTTP 전송은 계속 차단됩니다. 일반 테스트에서는 `TOSS_*` 환경변수와 기본 `.env`
로딩도 격리하므로 사용자 DB·토큰 경로·거래 설정을 상속하지 않습니다. 테스트별 입력은 가짜 자격증명과
임시 경로로 명시하며, 이 격리 장치 자체도 별도 프로세스에서 검증합니다. 명시적으로 허용한 E2E만
설정 소스를 유지합니다. E2E는 주문을 실행하지 않지만 토큰 발급·계좌 조회는 수행하므로,
실행 중인 서버와 같은 자격증명을 사용할 때는 토큰 공유 설정도 확인하세요.
CI는 Ubuntu·macOS·Windows에서 Python 3.12·3.13·3.14의 9가지 조합으로 기본 테스트를 실행하며,
pytest 설정은 지원 중단·자원 정리 경고를 포함한 모든 경고를 실패로 처리합니다.
테스트 시작 전 `FASTMCP_MCP_CAMELCASE_COMPAT=false`를 설정해 구형 Python 필드명의
호환 처리에 의존하지 않는지도 확인합니다. Ruff의 공급자 문서 자동 포맷은 제외해 원본을 보존합니다.

CI는 재사용 가능한 워크플로입니다. Ruff·포맷 검사는 별도 작업에서 한 번 실행하고,
타입 검사는 지원하는 Python 버전과 Linux·Darwin·Windows 분기를 각각 지정해 검증합니다.
별도 `latest` 작업은 `uv lock --upgrade`로 지원 범위 내 최신 의존성을 다시 해석해 전체 테스트를
실행합니다. 고정 잠금 파일만 통과하고 새 설치는 깨지는 상황을 방지하기 위한 검사입니다.
9개 런타임 조합·정적 검증·최신 의존성 검사를 모두 통과한 뒤 배포물을 한 번만 빌드해
`dist` 아티팩트로 저장합니다.
`v*` 태그 배포는 그 태그의 동일 커밋에서 이 검증을 실행하고, 생성된 동일 아티팩트를
PyPI 배포와 GitHub Release에서 재사용합니다. 과거 `main`의 성공 결과를 대신 사용하거나
배포 직전에 별도 재빌드하지 않습니다. 검증 작업은 읽기 권한만 가지며,
PyPI OIDC 권한은 검증·빌드를 통과한 배포 작업에만 부여합니다.

스펙 갱신 도중 파일 교체가 실패하면 원본 복원을 시도합니다. 복원도 실패하면 복구 파일을 남기고
그 경로를 오류로 알려줍니다. 여러 파일을 한 번에 교체하는 트랜잭션은 아니므로 전원 장애·SIGKILL에는
완전한 원자성을 보장하지 않으며, 갱신 스크립트를 동시에 실행하지 마세요.
갱신 후에는 diff를 검토하고 `tossinvest_mcp/spec/SOURCE.md`의 버전·검증일·해시도 갱신하세요.

## 개발 구조

`candles.py`는 검증된 캔들 값·UTC 식별을, `indicators.py`는 순수 지표·체결 계산을,
`sessions.py`는 세션 선택·판정 결과를, `candle_cache.py`는 스냅샷 저장과 현재 SQLite 스키마 초기화를 담당합니다.
`analytics.py`는 REST 조회와 MCP 도구를 연결하며 두 분봉 도구는 같은 세션 준비 결과를 사용합니다.
외부 공개 인터페이스는 `build_server()`이며, 내부 캐시 API는 `await snapshot()` / `await publish()`
흐름으로 통일했습니다. 이전 `extend_candles()` / `invalidate()`와 별도 무캐시 수집 경로는 제거했습니다.

테스트는 계산·세션·캐시·동시성·인증·재시도·수명주기 등 책임별로 나눕니다. `tests/factories.py`의
작은 생성 함수를 공유하며, 잘못된 입력의 모든 조합을 모든 저장 방식과 MCP 도구에 반복하지 않습니다.
공통 경계의 폭넓은 단위 검사와 대표 통합 검사를 조합하되, 수치 오라클·프로세스 경합·트랜잭션 롤백·
취소·주문 재시도 금지 등 서로 다른 위험을 검증하는 테스트는 유지합니다.

## 라이선스

[`Apache-2.0`](LICENSE)

[`NOTICE`](NOTICE)
