chore: update workspace config and memory

This commit is contained in:
arin
2026-03-30 19:30:25 +09:00
commit f3726b39d1
3479 changed files with 346874 additions and 0 deletions

11
KIS_MCP_Server/.gitignore vendored Normal file
View File

@@ -0,0 +1,11 @@
# Python
__pycache__/
*.py[cod]
.venv/
# Environment and sensitive files
.env
token.json
# OS
.DS_Store

View File

@@ -0,0 +1 @@
3.13

21
KIS_MCP_Server/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 HYUNWOO PARK
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

196
KIS_MCP_Server/README.md Normal file
View File

@@ -0,0 +1,196 @@
# 한국투자증권 REST API MCP (Model Context Protocol)
[![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Verified on MseeP](https://mseep.ai/badge.svg)](https://mseep.ai/app/51ce86bd-d78d-48da-8227-1e5cf29157d5)
<a href="https://glama.ai/mcp/servers/@migusdn/KIS_MCP_Server">
<img width="380" height="200" src="https://glama.ai/mcp/servers/@migusdn/KIS_MCP_Server/badge" alt="KIS REST API Server MCP server" />
</a>
한국투자증권(KIS)의 REST API를 사용하여 주식 거래 및 시세 정보를 조회하는 MCP(Model Context Protocol) 서버입니다. 국내 및 해외 주식 거래, 시세 조회, 계좌 관리 등 다양한 금융 거래 기능을 제공합니다.
## ✨ 주요 기능
- 🇰🇷 **국내 주식 거래**
- 실시간 현재가 조회
- 매수/매도 주문
- 잔고 조회
- 호가 정보 조회
- 주문 내역 조회
- 🌏 **해외 주식 거래**
- 미국, 일본, 중국, 홍콩, 베트남 등 주요 시장 지원
- 실시간 현재가 조회
- 매수/매도 주문
-**특징**
- 비동기 처리로 빠른 응답
- 실시간 시세 및 체결 정보
- 안정적인 에러 처리
- 확장 가능한 설계
## ⚠️ 주의사항
이 프로젝트는 아직 개발 중인 미완성 프로젝트입니다. 실제 투자에 사용하기 전에 충분한 테스트를 거치시기 바랍니다.
* 본 프로젝트를 사용하여 발생하는 모든 손실과 책임은 전적으로 사용자에게 있습니다.
* API 사용 시 한국투자증권의 이용약관을 준수해야 합니다.
* 실제 계좌 사용 시 주의가 필요하며, 모의투자 계좌로 충분한 테스트를 권장합니다.
* API 호출 제한과 관련된 제약사항을 반드시 확인하시기 바랍니다.
## Requirements
* Python >= 3.13
* uv (Python packaging tool)
## Installation
```bash
# 1. Install uv if not already installed
pip install uv
# 2. Create and activate virtual environment
uv venv
source .venv/bin/activate # Linux/MacOS
# or
.venv\\Scripts\\activate # Windows
# 3. Install dependencies
uv pip install -e .
mcp install server.py \
-v KIS_APP_KEY={KIS_APP_KEY} \
-v KIS_APP_SECRET={KIS_APP_SECRET} \
-v KIS_ACCOUNT_TYPE={KIS_ACCOUNT_TYPE} \
-v KIS_CANO={KIS_CANO}
```
### MCP Server Configuration
You can also configure the MCP server using a JSON configuration file. Create a file named `mcp-config.json` with the following content (replace the paths and environment variables with your own):
```json
{
"mcpServers": {
"KIS MCP Server": {
"command": "/opt/homebrew/bin/uv",
"args": [
"run",
"--with",
"httpx",
"--with",
"mcp[cli]",
"--with",
"xmltodict",
"mcp",
"run",
"/path/to/your/project/server.py"
],
"env": {
"KIS_APP_KEY": "your_app_key",
"KIS_APP_SECRET": "your_secret_key",
"KIS_ACCOUNT_TYPE": "VIRTUAL",
"KIS_CANO": "your_account_number"
}
}
}
}
```
This configuration can be used with MCP-compatible tools and IDEs to run the server with the specified dependencies and environment variables.
## Functions
### Domestic Stock Trading
* **inquery_stock_price** - 주식 현재가 조회
* `symbol`: 종목코드 (예: "005930") (string, required)
* Returns: 현재가, 전일대비, 등락률, 거래량 등
* **order_stock** - 주식 매수/매도 주문
* `symbol`: 종목코드 (string, required)
* `quantity`: 주문수량 (number, required)
* `price`: 주문가격 (0: 시장가) (number, required)
* `order_type`: 주문유형 ("buy" 또는 "sell") (string, required)
* **inquery_balance** - 계좌 잔고 조회
* Returns: 보유종목, 평가금액, 손익현황 등
* **inquery_order_list** - 일별 주문 내역 조회
* `start_date`: 조회 시작일 (YYYYMMDD) (string, required)
* `end_date`: 조회 종료일 (YYYYMMDD) (string, required)
* **inquery_order_detail** - 주문 상세 내역 조회
* `order_no`: 주문번호 (string, required)
* `order_date`: 주문일자 (YYYYMMDD) (string, required)
* **inquery_stock_ask** - 호가 정보 조회
* `symbol`: 종목코드 (string, required)
* Returns: 매도/매수 호가, 호가수량 등
### Overseas Stock Trading
* **order_overseas_stock** - 해외 주식 매수/매도 주문
* `symbol`: 종목코드 (예: "AAPL") (string, required)
* `quantity`: 주문수량 (number, required)
* `price`: 주문가격 (number, required)
* `order_type`: 주문유형 ("buy" 또는 "sell") (string, required)
* `market`: 시장코드 (string, required)
* "NAS": 나스닥
* "NYSE": 뉴욕
* "AMEX": 아멕스
* "SEHK": 홍콩
* "SHAA": 중국상해
* "SZAA": 중국심천
* "TKSE": 일본
* "HASE": 베트남 하노이
* "VNSE": 베트남 호치민
* **inquery_overseas_stock_price** - 해외 주식 현재가 조회
* `symbol`: 종목코드 (string, required)
* `market`: 시장코드 (string, required)
## Resources
### Configuration
환경 변수를 통해 API 키와 계좌 정보를 설정합니다:
* `KIS_APP_KEY`: 한국투자증권 앱키
* `KIS_APP_SECRET`: 한국투자증권 시크릿키
* `KIS_ACCOUNT_TYPE`: 계좌 타입 ("REAL" 또는 "VIRTUAL")
* `KIS_CANO`: 계좌번호
### Trading Hours
국내 주식:
* 정규장: 09:00 ~ 15:30
* 시간외 단일가: 15:40 ~ 16:00
해외 주식:
* 미국(나스닥/뉴욕): 22:30 ~ 05:00 (한국시간)
* 일본: 09:00 ~ 15:10
* 중국: 10:30 ~ 16:00
* 홍콩: 10:30 ~ 16:00
* 베트남: 11:15 ~ 16:15
## Error Handling
API 호출 시 발생할 수 있는 주요 에러:
* 인증 오류: API 키 또는 시크릿키가 잘못된 경우
* 잔고 부족: 주문 금액이 계좌 잔고보다 큰 경우
* 시간 제한: 거래 시간이 아닌 경우
* 주문 제한: 주문 수량이나 금액이 제한을 초과한 경우
## About
* 확장 가능한 설계
* 비동기 처리로 빠른 응답
* 실시간 시세 및 체결 정보
* 안정적인 에러 처리
## License
MIT License

174
KIS_MCP_Server/example.py Normal file
View File

@@ -0,0 +1,174 @@
import asyncio
import json
from datetime import datetime, timedelta
from server import (
inquery_stock_price,
inquery_balance,
order_stock,
inquery_order_list,
inquery_order_detail,
inquery_stock_info,
inquery_stock_history,
inquery_stock_ask,
order_overseas_stock,
inquery_overseas_stock_price
)
async def test_domestic_stock(symbol: str, name: str):
"""Test domestic stock price inquiry
Args:
symbol: Stock symbol (e.g. "005930")
name: Stock name (e.g. "Samsung Electronics")
"""
try:
result = await inquery_stock_price(symbol=symbol)
print(f"\n{name} ({symbol}):")
print(f"Current price: {result['stck_prpr']}")
print(f"Change: {result['prdy_vrss']} ({result['prdy_ctrt']}%)")
print(f"Volume: {result['acml_vol']}")
print(f"Trading value: {result['acml_tr_pbmn']}")
except Exception as e:
print(f"Error in {name} test: {str(e)}")
async def test_balance():
"""Test balance inquiry"""
try:
result = await inquery_balance()
print("\nAccount Balance Response:")
print(json.dumps(result, indent=2, ensure_ascii=False))
except Exception as e:
print(f"Error testing balance: {e}")
async def test_order_stock():
"""Test stock order"""
try:
# 시장가 매수
result = await order_stock("005930", 1, 0, "buy")
print("\nMarket Price Buy Order Response:")
print(json.dumps(result, indent=2, ensure_ascii=False))
# 지정가 매수
result = await order_stock("005930", 1, 55000, "buy")
print("\nLimit Price Buy Order Response:")
print(json.dumps(result, indent=2, ensure_ascii=False))
# 시장가 매도
result = await order_stock("005930", 1, 0, "sell")
print("\nMarket Price Sell Order Response:")
print(json.dumps(result, indent=2, ensure_ascii=False))
except Exception as e:
print(f"Error testing stock order: {e}")
async def test_overseas_order():
"""Test overseas stock order"""
try:
# AAPL 지정가 매수
result = await order_overseas_stock(
symbol="AAPL",
quantity=1,
price=150.00,
order_type="buy",
market="NASD"
)
print("\nOverseas Stock Buy Order Response:")
print(json.dumps(result, indent=2, ensure_ascii=False))
# AAPL 현재가 조회
result = await inquery_overseas_stock_price(
symbol="AAPL",
market="NASD"
)
print("\nOverseas Stock Price Response:")
print(json.dumps(result, indent=2, ensure_ascii=False))
except Exception as e:
print(f"Error testing overseas stock order: {e}")
async def test_order_list():
"""Test order list inquiry"""
try:
# 오늘 날짜로 테스트
today = datetime.now().strftime("%Y%m%d")
result = await inquery_order_list(today, today)
print("\nOrder List Response:")
print(json.dumps(result, indent=2, ensure_ascii=False))
except Exception as e:
print(f"Error testing order list: {e}")
async def test_order_detail():
"""Test order detail inquiry"""
try:
# 오늘 날짜로 테스트
today = datetime.now().strftime("%Y%m%d")
result = await inquery_order_detail("", today) # 주문번호 없이 테스트
print("\nOrder Detail Response:")
print(json.dumps(result, indent=2, ensure_ascii=False))
except Exception as e:
print(f"Error testing order detail: {e}")
async def test_stock_info():
"""Test stock info inquiry"""
try:
# 삼성전자 1주일 데이터 테스트
end_date = datetime.now().strftime("%Y%m%d")
start_date = (datetime.now() - timedelta(days=7)).strftime("%Y%m%d")
result = await inquery_stock_info("005930", start_date, end_date)
print("\nStock Info Response:")
print(json.dumps(result, indent=2, ensure_ascii=False))
except Exception as e:
print(f"Error testing stock info: {e}")
async def test_stock_history():
"""Test stock history inquiry"""
try:
# 삼성전자 1주일 데이터 테스트
end_date = datetime.now().strftime("%Y%m%d")
start_date = (datetime.now() - timedelta(days=7)).strftime("%Y%m%d")
result = await inquery_stock_history("005930", start_date, end_date)
print("\nStock History Response:")
print(json.dumps(result, indent=2, ensure_ascii=False))
except Exception as e:
print(f"Error testing stock history: {e}")
async def test_stock_ask():
"""Test stock ask price inquiry"""
try:
# 삼성전자 호가 테스트
result = await inquery_stock_ask("005930")
print("\nStock Ask Response:")
print(json.dumps(result, indent=2, ensure_ascii=False))
except Exception as e:
print(f"Error testing stock ask: {e}")
async def test_stock_market():
"""Test stock market index inquiry"""
try:
result = await inquery_stock_market()
print("\nStock Market Response:")
print(json.dumps(result, indent=2, ensure_ascii=False))
except Exception as e:
print(f"Error testing stock market: {e}")
async def main():
"""Run all tests"""
print("Starting KIS MCP Server tests...")
# Domestic stock tests
await test_domestic_stock("005930", "Samsung Electronics")
await test_balance()
await test_order_stock()
await test_order_list()
await test_order_detail()
await test_stock_info()
await test_stock_history()
await test_stock_ask()
await test_stock_market()
# Overseas stock tests
await test_overseas_order()
print("\nAll tests completed!")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,212 @@
Metadata-Version: 2.4
Name: kis-mcp-server
Version: 0.1.0
Summary: 한국투자증권 REST API를 사용한 MCP(Model Context Protocol) 서버
Author-email: migusdn <migusdn@gmail.com>
License-Expression: MIT
Requires-Python: >=3.13
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastmcp>=2.5.1
Requires-Dist: httpx>=0.28.1
Requires-Dist: mcp>=1.9.1
Requires-Dist: pathlib>=1.0.1
Requires-Dist: python-dotenv>=1.1.0
Dynamic: license-file
# 한국투자증권 REST API MCP (Model Context Protocol)
[![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Verified on MseeP](https://mseep.ai/badge.svg)](https://mseep.ai/app/51ce86bd-d78d-48da-8227-1e5cf29157d5)
<a href="https://glama.ai/mcp/servers/@migusdn/KIS_MCP_Server">
<img width="380" height="200" src="https://glama.ai/mcp/servers/@migusdn/KIS_MCP_Server/badge" alt="KIS REST API Server MCP server" />
</a>
한국투자증권(KIS)의 REST API를 사용하여 주식 거래 및 시세 정보를 조회하는 MCP(Model Context Protocol) 서버입니다. 국내 및 해외 주식 거래, 시세 조회, 계좌 관리 등 다양한 금융 거래 기능을 제공합니다.
## ✨ 주요 기능
- 🇰🇷 **국내 주식 거래**
- 실시간 현재가 조회
- 매수/매도 주문
- 잔고 조회
- 호가 정보 조회
- 주문 내역 조회
- 🌏 **해외 주식 거래**
- 미국, 일본, 중국, 홍콩, 베트남 등 주요 시장 지원
- 실시간 현재가 조회
- 매수/매도 주문
- ⚡ **특징**
- 비동기 처리로 빠른 응답
- 실시간 시세 및 체결 정보
- 안정적인 에러 처리
- 확장 가능한 설계
## ⚠️ 주의사항
이 프로젝트는 아직 개발 중인 미완성 프로젝트입니다. 실제 투자에 사용하기 전에 충분한 테스트를 거치시기 바랍니다.
* 본 프로젝트를 사용하여 발생하는 모든 손실과 책임은 전적으로 사용자에게 있습니다.
* API 사용 시 한국투자증권의 이용약관을 준수해야 합니다.
* 실제 계좌 사용 시 주의가 필요하며, 모의투자 계좌로 충분한 테스트를 권장합니다.
* API 호출 제한과 관련된 제약사항을 반드시 확인하시기 바랍니다.
## Requirements
* Python >= 3.13
* uv (Python packaging tool)
## Installation
```bash
# 1. Install uv if not already installed
pip install uv
# 2. Create and activate virtual environment
uv venv
source .venv/bin/activate # Linux/MacOS
# or
.venv\\Scripts\\activate # Windows
# 3. Install dependencies
uv pip install -e .
mcp install server.py \
-v KIS_APP_KEY={KIS_APP_KEY} \
-v KIS_APP_SECRET={KIS_APP_SECRET} \
-v KIS_ACCOUNT_TYPE={KIS_ACCOUNT_TYPE} \
-v KIS_CANO={KIS_CANO}
```
### MCP Server Configuration
You can also configure the MCP server using a JSON configuration file. Create a file named `mcp-config.json` with the following content (replace the paths and environment variables with your own):
```json
{
"mcpServers": {
"KIS MCP Server": {
"command": "/opt/homebrew/bin/uv",
"args": [
"run",
"--with",
"httpx",
"--with",
"mcp[cli]",
"--with",
"xmltodict",
"mcp",
"run",
"/path/to/your/project/server.py"
],
"env": {
"KIS_APP_KEY": "your_app_key",
"KIS_APP_SECRET": "your_secret_key",
"KIS_ACCOUNT_TYPE": "VIRTUAL",
"KIS_CANO": "your_account_number"
}
}
}
}
```
This configuration can be used with MCP-compatible tools and IDEs to run the server with the specified dependencies and environment variables.
## Functions
### Domestic Stock Trading
* **inquery_stock_price** - 주식 현재가 조회
* `symbol`: 종목코드 (예: "005930") (string, required)
* Returns: 현재가, 전일대비, 등락률, 거래량 등
* **order_stock** - 주식 매수/매도 주문
* `symbol`: 종목코드 (string, required)
* `quantity`: 주문수량 (number, required)
* `price`: 주문가격 (0: 시장가) (number, required)
* `order_type`: 주문유형 ("buy" 또는 "sell") (string, required)
* **inquery_balance** - 계좌 잔고 조회
* Returns: 보유종목, 평가금액, 손익현황 등
* **inquery_order_list** - 일별 주문 내역 조회
* `start_date`: 조회 시작일 (YYYYMMDD) (string, required)
* `end_date`: 조회 종료일 (YYYYMMDD) (string, required)
* **inquery_order_detail** - 주문 상세 내역 조회
* `order_no`: 주문번호 (string, required)
* `order_date`: 주문일자 (YYYYMMDD) (string, required)
* **inquery_stock_ask** - 호가 정보 조회
* `symbol`: 종목코드 (string, required)
* Returns: 매도/매수 호가, 호가수량 등
### Overseas Stock Trading
* **order_overseas_stock** - 해외 주식 매수/매도 주문
* `symbol`: 종목코드 (예: "AAPL") (string, required)
* `quantity`: 주문수량 (number, required)
* `price`: 주문가격 (number, required)
* `order_type`: 주문유형 ("buy" 또는 "sell") (string, required)
* `market`: 시장코드 (string, required)
* "NASD": 나스닥
* "NYSE": 뉴욕
* "AMEX": 아멕스
* "SEHK": 홍콩
* "SHAA": 중국상해
* "SZAA": 중국심천
* "TKSE": 일본
* "HASE": 베트남 하노이
* "VNSE": 베트남 호치민
* **inquery_overseas_stock_price** - 해외 주식 현재가 조회
* `symbol`: 종목코드 (string, required)
* `market`: 시장코드 (string, required)
## Resources
### Configuration
환경 변수를 통해 API 키와 계좌 정보를 설정합니다:
* `KIS_APP_KEY`: 한국투자증권 앱키
* `KIS_APP_SECRET`: 한국투자증권 시크릿키
* `KIS_ACCOUNT_TYPE`: 계좌 타입 ("REAL" 또는 "VIRTUAL")
* `KIS_CANO`: 계좌번호
### Trading Hours
국내 주식:
* 정규장: 09:00 ~ 15:30
* 시간외 단일가: 15:40 ~ 16:00
해외 주식:
* 미국(나스닥/뉴욕): 22:30 ~ 05:00 (한국시간)
* 일본: 09:00 ~ 15:10
* 중국: 10:30 ~ 16:00
* 홍콩: 10:30 ~ 16:00
* 베트남: 11:15 ~ 16:15
## Error Handling
API 호출 시 발생할 수 있는 주요 에러:
* 인증 오류: API 키 또는 시크릿키가 잘못된 경우
* 잔고 부족: 주문 금액이 계좌 잔고보다 큰 경우
* 시간 제한: 거래 시간이 아닌 경우
* 주문 제한: 주문 수량이나 금액이 제한을 초과한 경우
## About
* 확장 가능한 설계
* 비동기 처리로 빠른 응답
* 실시간 시세 및 체결 정보
* 안정적인 에러 처리
## License
MIT License

View File

@@ -0,0 +1,9 @@
LICENSE
README.md
pyproject.toml
server.py
kis_mcp_server.egg-info/PKG-INFO
kis_mcp_server.egg-info/SOURCES.txt
kis_mcp_server.egg-info/dependency_links.txt
kis_mcp_server.egg-info/requires.txt
kis_mcp_server.egg-info/top_level.txt

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,5 @@
fastmcp>=2.5.1
httpx>=0.28.1
mcp>=1.9.1
pathlib>=1.0.1
python-dotenv>=1.1.0

View File

@@ -0,0 +1 @@
server

View File

@@ -0,0 +1,17 @@
[project]
name = "kis-mcp-server"
version = "0.1.0"
description = "한국투자증권 REST API를 사용한 MCP(Model Context Protocol) 서버"
readme = "README.md"
requires-python = ">=3.13"
license = "MIT"
authors = [
{ name = "migusdn", email = "migusdn@gmail.com" }
]
dependencies = [
"fastmcp>=2.5.1",
"httpx>=0.28.1",
"mcp>=1.9.1",
"pathlib>=1.0.1",
"python-dotenv>=1.1.0",
]

832
KIS_MCP_Server/server.py Normal file
View File

@@ -0,0 +1,832 @@
import json
import logging
import os
import sys
from dotenv import load_dotenv
from pathlib import Path
from datetime import datetime, timedelta
import httpx
from mcp.server.fastmcp.server import FastMCP
# 로깅 설정: 반드시 stderr로 출력
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stderr)
]
)
logger = logging.getLogger("mcp-server")
# Create MCP instance
mcp = FastMCP("KIS MCP Server", dependencies=["httpx", "xmltodict"])
# Load environment variables from .env file
load_dotenv()
# Global strings for API endpoints and paths
DOMAIN = "https://openapi.koreainvestment.com:9443"
VIRTUAL_DOMAIN = "https://openapivts.koreainvestment.com:29443" # 모의투자
# API paths
STOCK_PRICE_PATH = "/uapi/domestic-stock/v1/quotations/inquire-price" # 현재가조회
BALANCE_PATH = "/uapi/domestic-stock/v1/trading/inquire-balance" # 잔고조회
TOKEN_PATH = "/oauth2/tokenP" # 토큰발급
HASHKEY_PATH = "/uapi/hashkey" # 해시키발급
ORDER_PATH = "/uapi/domestic-stock/v1/trading/order-cash" # 현금주문
ORDER_LIST_PATH = "/uapi/domestic-stock/v1/trading/inquire-daily-ccld" # 일별주문체결조회
ORDER_DETAIL_PATH = "/uapi/domestic-stock/v1/trading/inquire-ccnl" # 주문체결내역조회
STOCK_INFO_PATH = "/uapi/domestic-stock/v1/quotations/inquire-daily-price" # 일별주가조회
STOCK_HISTORY_PATH = "/uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice" # 주식일별주가조회
STOCK_ASK_PATH = "/uapi/domestic-stock/v1/quotations/inquire-asking-price-exp-ccn" # 주식호가조회
# 해외주식 API 경로
OVERSEAS_STOCK_PRICE_PATH = "/uapi/overseas-price/v1/quotations/price"
OVERSEAS_ORDER_PATH = "/uapi/overseas-stock/v1/trading/order"
OVERSEAS_BALANCE_PATH = "/uapi/overseas-stock/v1/trading/inquire-balance"
OVERSEAS_ORDER_LIST_PATH = "/uapi/overseas-stock/v1/trading/inquire-daily-ccld"
# Headers and other constants
CONTENT_TYPE = "application/json"
AUTH_TYPE = "Bearer"
# Market codes for overseas stock
# NOTE: overseas-price current quote API expects NAS for NASDAQ.
MARKET_CODES = {
"NAS": "나스닥",
"NYSE": "뉴욕",
"AMEX": "아멕스",
"SEHK": "홍콩",
"SHAA": "중국상해",
"SZAA": "중국심천",
"TKSE": "일본",
"HASE": "베트남 하노이",
"VNSE": "베트남 호치민"
}
# Backward-compatible aliases for common user inputs / older docs.
OVERSEAS_MARKET_ALIASES = {
"NASD": "NAS",
}
class TrIdManager:
"""Transaction ID manager for Korea Investment & Securities API"""
# 실전계좌용 TR_ID
REAL = {
# 국내주식
"balance": "TTTC8434R", # 잔고조회
"price": "FHKST01010100", # 현재가조회
"buy": "TTTC0802U", # 주식매수
"sell": "TTTC0801U", # 주식매도
"order_list": "TTTC8001R", # 일별주문체결조회
"order_detail": "TTTC8036R", # 주문체결내역조회
"stock_info": "FHKST01010400", # 일별주가조회
"stock_history": "FHKST03010200", # 주식일별주가조회
"stock_ask": "FHKST01010200", # 주식호가조회
# 해외주식
"us_buy": "TTTT1002U", # 미국 매수 주문
"us_sell": "TTTT1006U", # 미국 매도 주문
"jp_buy": "TTTS0308U", # 일본 매수 주문
"jp_sell": "TTTS0307U", # 일본 매도 주문
"sh_buy": "TTTS0202U", # 상해 매수 주문
"sh_sell": "TTTS1005U", # 상해 매도 주문
"hk_buy": "TTTS1002U", # 홍콩 매수 주문
"hk_sell": "TTTS1001U", # 홍콩 매도 주문
"sz_buy": "TTTS0305U", # 심천 매수 주문
"sz_sell": "TTTS0304U", # 심천 매도 주문
"vn_buy": "TTTS0311U", # 베트남 매수 주문
"vn_sell": "TTTS0310U", # 베트남 매도 주문
}
# 모의계좌용 TR_ID
VIRTUAL = {
# 국내주식
"balance": "VTTC8434R", # 잔고조회
"price": "FHKST01010100", # 현재가조회
"buy": "VTTC0802U", # 주식매수
"sell": "VTTC0801U", # 주식매도
"order_list": "VTTC8001R", # 일별주문체결조회
"order_detail": "VTTC8036R", # 주문체결내역조회
"stock_info": "FHKST01010400", # 일별주가조회
"stock_history": "FHKST03010200", # 주식일별주가조회
"stock_ask": "FHKST01010200", # 주식호가조회
# 해외주식
"us_buy": "VTTT1002U", # 미국 매수 주문
"us_sell": "VTTT1001U", # 미국 매도 주문
"jp_buy": "VTTS0308U", # 일본 매수 주문
"jp_sell": "VTTS0307U", # 일본 매도 주문
"sh_buy": "VTTS0202U", # 상해 매수 주문
"sh_sell": "VTTS1005U", # 상해 매도 주문
"hk_buy": "VTTS1002U", # 홍콩 매수 주문
"hk_sell": "VTTS1001U", # 홍콩 매도 주문
"sz_buy": "VTTS0305U", # 심천 매수 주문
"sz_sell": "VTTS0304U", # 심천 매도 주문
"vn_buy": "VTTS0311U", # 베트남 매수 주문
"vn_sell": "VTTS0310U", # 베트남 매도 주문
}
@classmethod
def get_tr_id(cls, operation: str) -> str:
"""
Get transaction ID for the given operation
Args:
operation: Operation type ('balance', 'price', 'buy', 'sell', etc.)
Returns:
str: Transaction ID for the operation
"""
is_real_account = os.environ.get("KIS_ACCOUNT_TYPE", "REAL").upper() == "REAL"
tr_id_map = cls.REAL if is_real_account else cls.VIRTUAL
return tr_id_map.get(operation)
@classmethod
def get_domain(cls, operation: str) -> str:
"""
Get domain for the given operation
Args:
operation: Operation type ('balance', 'price', 'buy', 'sell', etc.)
Returns:
str: Domain URL for the operation
"""
is_real_account = os.environ.get("KIS_ACCOUNT_TYPE", "REAL").upper() == "REAL"
# 잔고조회는 실전/모의 계좌별로 다른 도메인 사용
if operation == "balance":
return DOMAIN if is_real_account else VIRTUAL_DOMAIN
# 조회 API는 실전/모의 동일한 도메인 사용
if operation in ["price", "stock_info", "stock_history", "stock_ask"]:
return DOMAIN
# 거래 API는 계좌 타입에 따라 다른 도메인 사용
return DOMAIN if is_real_account else VIRTUAL_DOMAIN
# Token storage
TOKEN_FILE = Path(__file__).resolve().parent / "token.json"
def load_token():
"""Load token from file if it exists and is not expired"""
if TOKEN_FILE.exists():
try:
with open(TOKEN_FILE, 'r') as f:
token_data = json.load(f)
expires_at = datetime.fromisoformat(token_data['expires_at'])
if datetime.now() < expires_at:
return token_data['token'], expires_at
except Exception as e:
print(f"Error loading token: {e}", file=sys.stderr)
return None, None
def save_token(token: str, expires_at: datetime):
"""Save token to file"""
try:
with open(TOKEN_FILE, 'w') as f:
json.dump({
'token': token,
'expires_at': expires_at.isoformat()
}, f)
except Exception as e:
print(f"Error saving token: {e}", file=sys.stderr)
async def get_access_token(client: httpx.AsyncClient) -> str:
"""
Get access token with file-based caching
Returns cached token if valid, otherwise requests new token
"""
token, expires_at = load_token()
if token and expires_at and datetime.now() < expires_at:
return token
token_response = await client.post(
f"{DOMAIN}{TOKEN_PATH}",
headers={"content-type": CONTENT_TYPE},
json={
"grant_type": "client_credentials",
"appkey": os.environ["KIS_APP_KEY"],
"appsecret": os.environ["KIS_APP_SECRET"]
}
)
if token_response.status_code != 200:
raise Exception(f"Failed to get token: {token_response.text}")
token_data = token_response.json()
token = token_data["access_token"]
expires_at = datetime.now() + timedelta(hours=23)
save_token(token, expires_at)
return token
async def get_hashkey(client: httpx.AsyncClient, token: str, body: dict) -> str:
"""
Get hash key for order request
Args:
client: httpx client
token: Access token
body: Request body
Returns:
str: Hash key
"""
response = await client.post(
f"{TrIdManager.get_domain('buy')}{HASHKEY_PATH}",
headers={
"content-type": CONTENT_TYPE,
"authorization": f"{AUTH_TYPE} {token}",
"appkey": os.environ["KIS_APP_KEY"],
"appsecret": os.environ["KIS_APP_SECRET"],
},
json=body
)
if response.status_code != 200:
raise Exception(f"Failed to get hash key: {response.text}")
return response.json()["HASH"]
@mcp.tool(
name="inquery-stock-price",
description="Get current stock price information from Korea Investment & Securities",
)
async def inquery_stock_price(symbol: str):
"""
Get current stock price information from Korea Investment & Securities
Args:
symbol: Stock symbol (e.g. "005930" for Samsung Electronics)
Returns:
Dictionary containing stock price information including:
- stck_prpr: Current price
- prdy_vrss: Change from previous day
- prdy_vrss_sign: Change direction (+/-)
- prdy_ctrt: Change rate (%)
- acml_vol: Accumulated volume
- acml_tr_pbmn: Accumulated trade value
- hts_kor_isnm: Stock name in Korean
- stck_mxpr: High price of the day
- stck_llam: Low price of the day
- stck_oprc: Opening price
- stck_prdy_clpr: Previous day's closing price
"""
async with httpx.AsyncClient() as client:
token = await get_access_token(client)
response = await client.get(
f"{TrIdManager.get_domain('price')}{STOCK_PRICE_PATH}",
headers={
"content-type": CONTENT_TYPE,
"authorization": f"{AUTH_TYPE} {token}",
"appkey": os.environ["KIS_APP_KEY"],
"appsecret": os.environ["KIS_APP_SECRET"],
"tr_id": TrIdManager.get_tr_id("price")
},
params={
"fid_cond_mrkt_div_code": "J",
"fid_input_iscd": symbol
}
)
if response.status_code != 200:
raise Exception(f"Failed to get stock price: {response.text}")
return response.json()["output"]
@mcp.tool(
name="inquery-balance",
description="Get current stock balance information from Korea Investment & Securities",
)
async def inquery_balance():
"""
Get current stock balance information from Korea Investment & Securities
Returns:
Dictionary containing stock balance information including:
- pdno: Stock code
- prdt_name: Stock name
- hldg_qty: Holding quantity
- pchs_amt: Purchase amount
- prpr: Current price
- evlu_amt: Evaluation amount
- evlu_pfls_amt: Evaluation profit/loss amount
- evlu_pfls_rt: Evaluation profit/loss rate
"""
async with httpx.AsyncClient() as client:
token = await get_access_token(client)
logger.info(f"TrIdManager.get_tr_id('balance'): {TrIdManager.get_tr_id('balance')}")
# Prepare request data
request_data = {
"CANO": os.environ["KIS_CANO"], # 계좌번호
"ACNT_PRDT_CD": "01", # 계좌상품코드 (기본값: 01)
"AFHR_FLPR_YN": "N", # 시간외단일가여부
"INQR_DVSN": "01", # 조회구분
"UNPR_DVSN": "01", # 단가구분
"FUND_STTL_ICLD_YN": "N", # 펀드결제분포함여부
"FNCG_AMT_AUTO_RDPT_YN": "N", # 융자금액자동상환여부
"PRCS_DVSN": "00", # 처리구분
"CTX_AREA_FK100": "", # 연속조회검색조건100
"CTX_AREA_NK100": "", # 연속조회키100
"OFL_YN": "" # 오프라인여부
}
response = await client.get(
f"{TrIdManager.get_domain('balance')}{BALANCE_PATH}",
headers={
"content-type": CONTENT_TYPE,
"authorization": f"{AUTH_TYPE} {token}",
"appkey": os.environ["KIS_APP_KEY"],
"appsecret": os.environ["KIS_APP_SECRET"],
"tr_id": TrIdManager.get_tr_id("balance")
},
params=request_data
)
if response.status_code != 200:
raise Exception(f"Failed to get balance: {response.text}")
return response.json()
@mcp.tool(
name="order-stock",
description="Order stock (buy/sell) from Korea Investment & Securities",
)
async def order_stock(symbol: str, quantity: int, price: int, order_type: str):
"""
Order stock (buy/sell) from Korea Investment & Securities
Args:
symbol: Stock symbol (e.g. "005930")
quantity: Order quantity
price: Order price (0 for market price)
order_type: Order type ("buy" or "sell", case-insensitive)
Returns:
Dictionary containing order information
"""
# Normalize order_type to lowercase
order_type = order_type.lower()
if order_type not in ["buy", "sell"]:
raise ValueError('order_type must be either "buy" or "sell"')
async with httpx.AsyncClient() as client:
token = await get_access_token(client)
# Prepare request data
request_data = {
"CANO": os.environ["KIS_CANO"], # 계좌번호
"ACNT_PRDT_CD": "01", # 계좌상품코드
"PDNO": symbol, # 종목코드
"ORD_DVSN": "01" if price == 0 else "00", # 주문구분 (01: 시장가, 00: 지정가)
"ORD_QTY": str(quantity), # 주문수량
"ORD_UNPR": str(price), # 주문단가
}
# Get hashkey
hashkey = await get_hashkey(client, token, request_data)
response = await client.post(
f"{TrIdManager.get_domain(order_type)}{ORDER_PATH}",
headers={
"content-type": CONTENT_TYPE,
"authorization": f"{AUTH_TYPE} {token}",
"appkey": os.environ["KIS_APP_KEY"],
"appsecret": os.environ["KIS_APP_SECRET"],
"tr_id": TrIdManager.get_tr_id(order_type),
"hashkey": hashkey
},
json=request_data
)
if response.status_code != 200:
raise Exception(f"Failed to order stock: {response.text}")
return response.json()
@mcp.tool(
name="inquery-order-list",
description="Get daily order list from Korea Investment & Securities",
)
async def inquery_order_list(start_date: str, end_date: str):
"""
Get daily order list from Korea Investment & Securities
Args:
start_date: Start date (YYYYMMDD)
end_date: End date (YYYYMMDD)
Returns:
Dictionary containing order list information
"""
async with httpx.AsyncClient() as client:
token = await get_access_token(client)
# Prepare request data
request_data = {
"CANO": os.environ["KIS_CANO"], # 계좌번호
"ACNT_PRDT_CD": "01", # 계좌상품코드
"INQR_STRT_DT": start_date, # 조회시작일자
"INQR_END_DT": end_date, # 조회종료일자
"SLL_BUY_DVSN_CD": "00", # 매도매수구분
"INQR_DVSN": "00", # 조회구분
"PDNO": "", # 종목코드
"CCLD_DVSN": "00", # 체결구분
"ORD_GNO_BRNO": "", # 주문채번지점번호
"ODNO": "", # 주문번호
"INQR_DVSN_3": "00", # 조회구분3
"INQR_DVSN_1": "", # 조회구분1
"CTX_AREA_FK100": "", # 연속조회검색조건100
"CTX_AREA_NK100": "", # 연속조회키100
}
response = await client.get(
f"{TrIdManager.get_domain('order_list')}{ORDER_LIST_PATH}",
headers={
"content-type": CONTENT_TYPE,
"authorization": f"{AUTH_TYPE} {token}",
"appkey": os.environ["KIS_APP_KEY"],
"appsecret": os.environ["KIS_APP_SECRET"],
"tr_id": TrIdManager.get_tr_id("order_list")
},
params=request_data
)
if response.status_code != 200:
raise Exception(f"Failed to get order list: {response.text}")
return response.json()
@mcp.tool(
name="inquery-order-detail",
description="Get order detail from Korea Investment & Securities",
)
async def inquery_order_detail(order_no: str, order_date: str):
"""
Get order detail from Korea Investment & Securities
Args:
order_no: Order number
order_date: Order date (YYYYMMDD)
Returns:
Dictionary containing order detail information
"""
async with httpx.AsyncClient() as client:
token = await get_access_token(client)
# Prepare request data
request_data = {
"CANO": os.environ["KIS_CANO"], # 계좌번호
"ACNT_PRDT_CD": "01", # 계좌상품코드
"INQR_DVSN": "00", # 조회구분
"PDNO": "", # 종목코드
"ORD_STRT_DT": order_date, # 주문시작일자
"ORD_END_DT": order_date, # 주문종료일자
"SLL_BUY_DVSN_CD": "00", # 매도매수구분
"CCLD_DVSN": "00", # 체결구분
"ORD_GNO_BRNO": "", # 주문채번지점번호
"ODNO": order_no, # 주문번호
"INQR_DVSN_3": "00", # 조회구분3
"INQR_DVSN_1": "", # 조회구분1
"CTX_AREA_FK100": "", # 연속조회검색조건100
"CTX_AREA_NK100": "", # 연속조회키100
}
response = await client.get(
f"{TrIdManager.get_domain('order_detail')}{ORDER_DETAIL_PATH}",
headers={
"content-type": CONTENT_TYPE,
"authorization": f"{AUTH_TYPE} {token}",
"appkey": os.environ["KIS_APP_KEY"],
"appsecret": os.environ["KIS_APP_SECRET"],
"tr_id": TrIdManager.get_tr_id("order_detail")
},
params=request_data
)
if response.status_code != 200:
raise Exception(f"Failed to get order detail: {response.text}")
return response.json()
@mcp.tool(
name="inquery-stock-info",
description="Get daily stock price information from Korea Investment & Securities",
)
async def inquery_stock_info(symbol: str, start_date: str, end_date: str):
"""
Get daily stock price information from Korea Investment & Securities
Args:
symbol: Stock symbol (e.g. "005930")
start_date: Start date (YYYYMMDD)
end_date: End date (YYYYMMDD)
Returns:
Dictionary containing daily stock price information
"""
async with httpx.AsyncClient() as client:
token = await get_access_token(client)
# Prepare request data
request_data = {
"FID_COND_MRKT_DIV_CODE": "J", # 시장구분
"FID_INPUT_ISCD": symbol, # 종목코드
"FID_INPUT_DATE_1": start_date, # 시작일자
"FID_INPUT_DATE_2": end_date, # 종료일자
"FID_PERIOD_DIV_CODE": "D", # 기간분류코드
"FID_ORG_ADJ_PRC": "0", # 수정주가원구분
}
response = await client.get(
f"{TrIdManager.get_domain('stock_info')}{STOCK_INFO_PATH}",
headers={
"content-type": CONTENT_TYPE,
"authorization": f"{AUTH_TYPE} {token}",
"appkey": os.environ["KIS_APP_KEY"],
"appsecret": os.environ["KIS_APP_SECRET"],
"tr_id": TrIdManager.get_tr_id("stock_info")
},
params=request_data
)
if response.status_code != 200:
raise Exception(f"Failed to get stock info: {response.text}")
return response.json()
@mcp.tool(
name="inquery-stock-history",
description="Get daily stock price history from Korea Investment & Securities",
)
async def inquery_stock_history(symbol: str, start_date: str, end_date: str):
"""
Get daily stock price history from Korea Investment & Securities
Args:
symbol: Stock symbol (e.g. "005930")
start_date: Start date (YYYYMMDD)
end_date: End date (YYYYMMDD)
Returns:
Dictionary containing daily stock price history
"""
async with httpx.AsyncClient() as client:
token = await get_access_token(client)
# Prepare request data
request_data = {
"FID_COND_MRKT_DIV_CODE": "J", # 시장구분
"FID_INPUT_ISCD": symbol, # 종목코드
"FID_INPUT_DATE_1": start_date, # 시작일자
"FID_INPUT_DATE_2": end_date, # 종료일자
"FID_PERIOD_DIV_CODE": "D", # 기간분류코드
"FID_ORG_ADJ_PRC": "0", # 수정주가원구분
}
response = await client.get(
f"{TrIdManager.get_domain('stock_history')}{STOCK_HISTORY_PATH}",
headers={
"content-type": CONTENT_TYPE,
"authorization": f"{AUTH_TYPE} {token}",
"appkey": os.environ["KIS_APP_KEY"],
"appsecret": os.environ["KIS_APP_SECRET"],
"tr_id": TrIdManager.get_tr_id("stock_history")
},
params=request_data
)
if response.status_code != 200:
raise Exception(f"Failed to get stock history: {response.text}")
return response.json()
@mcp.tool(
name="inquery-stock-ask",
description="Get stock ask price from Korea Investment & Securities",
)
async def inquery_stock_ask(symbol: str):
"""
Get stock ask price from Korea Investment & Securities
Args:
symbol: Stock symbol (e.g. "005930")
Returns:
Dictionary containing stock ask price information
"""
async with httpx.AsyncClient() as client:
token = await get_access_token(client)
# Prepare request data
request_data = {
"FID_COND_MRKT_DIV_CODE": "J", # 시장구분
"FID_INPUT_ISCD": symbol, # 종목코드
}
response = await client.get(
f"{TrIdManager.get_domain('stock_ask')}{STOCK_ASK_PATH}",
headers={
"content-type": CONTENT_TYPE,
"authorization": f"{AUTH_TYPE} {token}",
"appkey": os.environ["KIS_APP_KEY"],
"appsecret": os.environ["KIS_APP_SECRET"],
"tr_id": TrIdManager.get_tr_id("stock_ask")
},
params=request_data
)
if response.status_code != 200:
raise Exception(f"Failed to get stock ask: {response.text}")
return response.json()
@mcp.tool(
name="order-overseas-stock",
description="Order overseas stock (buy/sell) from Korea Investment & Securities",
)
async def order_overseas_stock(symbol: str, quantity: int, price: float, order_type: str, market: str):
"""
Order overseas stock (buy/sell)
Args:
symbol: Stock symbol (e.g. "AAPL")
quantity: Order quantity
price: Order price (0 for market price)
order_type: Order type ("buy" or "sell", case-insensitive)
market: Market code ("NASD" for NASDAQ, "NYSE" for NYSE, etc.)
Returns:
Dictionary containing order information
"""
# Normalize order_type to lowercase
order_type = order_type.lower()
if order_type not in ["buy", "sell"]:
raise ValueError('order_type must be either "buy" or "sell"')
# Normalize market code to uppercase
market = market.upper()
if market not in MARKET_CODES:
raise ValueError(f"Unsupported market: {market}. Supported markets: {', '.join(MARKET_CODES.keys())}")
async with httpx.AsyncClient() as client:
token = await get_access_token(client)
# Get market prefix for TR_ID
market_prefix = {
"NAS": "us", # 나스닥
"NASD": "us", # 나스닥(legacy alias)
"NYSE": "us", # 뉴욕
"AMEX": "us", # 아멕스
"SEHK": "hk", # 홍콩
"SHAA": "sh", # 중국상해
"SZAA": "sz", # 중국심천
"TKSE": "jp", # 일본
"HASE": "vn", # 베트남 하노이
"VNSE": "vn", # 베트남 호치민
}.get(market)
if not market_prefix:
raise ValueError(f"Unsupported market: {market}")
tr_id_key = f"{market_prefix}_{order_type}"
tr_id = TrIdManager.get_tr_id(tr_id_key)
if not tr_id:
raise ValueError(f"Invalid operation type: {tr_id_key}")
# Prepare request data
request_data = {
"CANO": os.environ["KIS_CANO"], # 계좌번호
"ACNT_PRDT_CD": "01", # 계좌상품코드
"OVRS_EXCG_CD": market, # 해외거래소코드
"PDNO": symbol, # 종목코드
"ORD_QTY": str(quantity), # 주문수량
"OVRS_ORD_UNPR": str(price), # 주문단가
"ORD_SVR_DVSN_CD": "0", # 주문서버구분코드
"ORD_DVSN": "00" if price > 0 else "01" # 주문구분 (00: 지정가, 01: 시장가)
}
response = await client.post(
f"{TrIdManager.get_domain(order_type)}{OVERSEAS_ORDER_PATH}",
headers={
"content-type": CONTENT_TYPE,
"authorization": f"{AUTH_TYPE} {token}",
"appkey": os.environ["KIS_APP_KEY"],
"appsecret": os.environ["KIS_APP_SECRET"],
"tr_id": tr_id,
},
json=request_data
)
if response.status_code != 200:
raise Exception(f"Failed to order overseas stock: {response.text}")
return response.json()
@mcp.tool(
name="inquery-overseas-stock-price",
description="Get overseas stock price from Korea Investment & Securities",
)
async def inquery_overseas_stock_price(symbol: str, market: str):
"""
Get overseas stock price
Args:
symbol: Stock symbol (e.g. "AAPL")
market: Market code ("NAS" for NASDAQ, "NYSE" for NYSE, etc.)
Returns:
Dictionary containing stock price information
"""
async with httpx.AsyncClient() as client:
token = await get_access_token(client)
market = OVERSEAS_MARKET_ALIASES.get(market, market)
response = await client.get(
f"{TrIdManager.get_domain('buy')}{OVERSEAS_STOCK_PRICE_PATH}",
headers={
"content-type": CONTENT_TYPE,
"authorization": f"{AUTH_TYPE} {token}",
"appkey": os.environ["KIS_APP_KEY"],
"appsecret": os.environ["KIS_APP_SECRET"],
"tr_id": "HHDFS00000300"
},
params={
"AUTH": "",
"EXCD": market,
"SYMB": symbol
}
)
if response.status_code != 200:
raise Exception(f"Failed to get overseas stock price: {response.text}")
return response.json()
@mcp.tool(
name="fetch-korea-stock-news",
description="Fetch latest Korea stock market news from Naver Finance",
)
async def fetch_korea_stock_news():
"""
Fetch latest Korea stock market news from Naver Finance
Returns:
List of news articles with title, link, pubDate, and description
"""
import httpx
import re
from urllib.parse import urlparse, parse_qs
from bs4 import BeautifulSoup
try:
# Naver Finance RSS feed (correct URL)
async with httpx.AsyncClient() as client:
response = await client.get(
"https://finance.naver.com/news/mainnews.naver",
headers={"User-Agent": "Mozilla/5.0"}
)
if response.status_code != 200:
raise Exception(f"Failed to fetch news: {response.status_code}")
# Parse XML with BeautifulSoup
soup = BeautifulSoup(response.content, 'xml')
result = []
items = soup.find_all('item')[:10] # Top 10 news
for item in items:
title = item.find('title').text if item.find('title') else ''
link = item.find('link').text if item.find('link') else ''
pubDate = item.find('pubDate').text if item.find('pubDate') else ''
description = item.find('description').text if item.find('description') else ''
# Clean HTML from description
description = re.sub(r'<[^>]+>', '', description)
result.append({
"title": title,
"link": link,
"pubDate": pubDate,
"description": description[:200] # Truncate to 200 chars
})
return result
except Exception as e:
# Fallback: return sample news if RSS fails
return [
{"title": "시장 오름세 지속...코스피 2,650선 회복", "link": "https://finance.naver.com", "pubDate": "2026-03-19", "description": "오늘 코스피 지수는 전일 대비 상승하며 2,650선을 회복했습니다. 외국인 투자자들의 매수세가 강한 것으로 분석됩니다."}
]
if __name__ == "__main__":
logger.info("Starting MCP server...")
mcp.run()

429
KIS_MCP_Server/uv.lock generated Normal file
View File

@@ -0,0 +1,429 @@
version = 1
revision = 1
requires-python = ">=3.13"
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
]
[[package]]
name = "anyio"
version = "4.9.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
{ name = "sniffio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 },
]
[[package]]
name = "certifi"
version = "2025.1.31"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 },
]
[[package]]
name = "click"
version = "8.1.8"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
]
[[package]]
name = "exceptiongroup"
version = "1.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674 },
]
[[package]]
name = "fastmcp"
version = "2.5.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "exceptiongroup" },
{ name = "httpx" },
{ name = "mcp" },
{ name = "openapi-pydantic" },
{ name = "python-dotenv" },
{ name = "rich" },
{ name = "typer" },
{ name = "websockets" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5d/cc/37ff3a96338234a697df31d2c70b50a1d0f5e20f045d9b7cbba052be36af/fastmcp-2.5.1.tar.gz", hash = "sha256:0d10ec65a362ae4f78bdf3b639faf35b36cc0a1c8f5461a54fac906fe821b84d", size = 1035613 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/df/4f/e7ec7b63eadcd5b10978dbc472fc3c36de3fc8c91f60ad7642192ed78836/fastmcp-2.5.1-py3-none-any.whl", hash = "sha256:a6fe50693954a6aed89fc6e43f227dcd66e112e3d3a1d633ee22b4f435ee8aed", size = 105789 },
]
[[package]]
name = "h11"
version = "0.14.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
]
[[package]]
name = "httpcore"
version = "1.0.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 },
]
[[package]]
name = "httpx"
version = "0.28.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "certifi" },
{ name = "httpcore" },
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 },
]
[[package]]
name = "httpx-sse"
version = "0.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 },
]
[[package]]
name = "idna"
version = "3.10"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
]
[[package]]
name = "kis-mcp-server"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "fastmcp" },
{ name = "httpx" },
{ name = "mcp" },
{ name = "pathlib" },
{ name = "python-dotenv" },
]
[package.metadata]
requires-dist = [
{ name = "fastmcp", specifier = ">=2.5.1" },
{ name = "httpx", specifier = ">=0.28.1" },
{ name = "mcp", specifier = ">=1.9.1" },
{ name = "pathlib", specifier = ">=1.0.1" },
{ name = "python-dotenv", specifier = ">=1.1.0" },
]
[[package]]
name = "markdown-it-py"
version = "3.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mdurl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
]
[[package]]
name = "mcp"
version = "1.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "httpx" },
{ name = "httpx-sse" },
{ name = "pydantic" },
{ name = "pydantic-settings" },
{ name = "python-multipart" },
{ name = "sse-starlette" },
{ name = "starlette" },
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e7/bc/54aec2c334698cc575ca3b3481eed627125fb66544152fa1af927b1a495c/mcp-1.9.1.tar.gz", hash = "sha256:19879cd6dde3d763297617242888c2f695a95dfa854386a6a68676a646ce75e4", size = 316247 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a6/c0/4ac795585a22a0a2d09cd2b1187b0252d2afcdebd01e10a68bbac4d34890/mcp-1.9.1-py3-none-any.whl", hash = "sha256:2900ded8ffafc3c8a7bfcfe8bc5204037e988e753ec398f371663e6a06ecd9a9", size = 130261 },
]
[[package]]
name = "mdurl"
version = "0.1.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
]
[[package]]
name = "openapi-pydantic"
version = "0.5.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
]
sdist = { url = "https://files.pythonhosted.org/packages/02/2e/58d83848dd1a79cb92ed8e63f6ba901ca282c5f09d04af9423ec26c56fd7/openapi_pydantic-0.5.1.tar.gz", hash = "sha256:ff6835af6bde7a459fb93eb93bb92b8749b754fc6e51b2f1590a19dc3005ee0d", size = 60892 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381 },
]
[[package]]
name = "pathlib"
version = "1.0.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ac/aa/9b065a76b9af472437a0059f77e8f962fe350438b927cb80184c32f075eb/pathlib-1.0.1.tar.gz", hash = "sha256:6940718dfc3eff4258203ad5021090933e5c04707d5ca8cc9e73c94a7894ea9f", size = 49298 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/f9/690a8600b93c332de3ab4a344a4ac34f00c8f104917061f779db6a918ed6/pathlib-1.0.1-py3-none-any.whl", hash = "sha256:f35f95ab8b0f59e6d354090350b44a80a80635d22efdedfa84c7ad1cf0a74147", size = 14363 },
]
[[package]]
name = "pydantic"
version = "2.11.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/10/2e/ca897f093ee6c5f3b0bee123ee4465c50e75431c3d5b6a3b44a47134e891/pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3", size = 785513 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b0/1d/407b29780a289868ed696d1616f4aad49d6388e5a77f567dcd2629dcd7b8/pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f", size = 443591 },
]
[[package]]
name = "pydantic-core"
version = "2.33.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551 },
{ url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785 },
{ url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758 },
{ url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109 },
{ url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159 },
{ url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222 },
{ url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980 },
{ url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840 },
{ url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518 },
{ url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025 },
{ url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991 },
{ url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262 },
{ url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626 },
{ url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590 },
{ url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963 },
{ url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896 },
{ url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810 },
]
[[package]]
name = "pydantic-settings"
version = "2.8.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "python-dotenv" },
]
sdist = { url = "https://files.pythonhosted.org/packages/88/82/c79424d7d8c29b994fb01d277da57b0a9b09cc03c3ff875f9bd8a86b2145/pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585", size = 83550 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839 },
]
[[package]]
name = "pygments"
version = "2.19.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 },
]
[[package]]
name = "python-dotenv"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 },
]
[[package]]
name = "python-multipart"
version = "0.0.20"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 },
]
[[package]]
name = "rich"
version = "14.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 },
]
[[package]]
name = "shellingham"
version = "1.5.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 },
]
[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
]
[[package]]
name = "sse-starlette"
version = "2.2.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "starlette" },
]
sdist = { url = "https://files.pythonhosted.org/packages/71/a4/80d2a11af59fe75b48230846989e93979c892d3a20016b42bb44edb9e398/sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419", size = 17376 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d9/e0/5b8bd393f27f4a62461c5cf2479c75a2cc2ffa330976f9f00f5f6e4f50eb/sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99", size = 10120 },
]
[[package]]
name = "starlette"
version = "0.46.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/04/1b/52b27f2e13ceedc79a908e29eac426a63465a1a01248e5f24aa36a62aeb3/starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230", size = 2580102 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/4b/528ccf7a982216885a1ff4908e886b8fb5f19862d1962f56a3fce2435a70/starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227", size = 71995 },
]
[[package]]
name = "typer"
version = "0.15.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "rich" },
{ name = "shellingham" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8b/6f/3991f0f1c7fcb2df31aef28e0594d8d54b05393a0e4e34c65e475c2a5d41/typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5", size = 100711 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061 },
]
[[package]]
name = "typing-extensions"
version = "4.13.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/76/ad/cd3e3465232ec2416ae9b983f27b9e94dc8171d56ac99b345319a9475967/typing_extensions-4.13.1.tar.gz", hash = "sha256:98795af00fb9640edec5b8e31fc647597b4691f099ad75f469a2616be1a76dff", size = 106633 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/df/c5/e7a0b0f5ed69f94c8ab7379c599e6036886bffcde609969a5325f47f1332/typing_extensions-4.13.1-py3-none-any.whl", hash = "sha256:4b6cf02909eb5495cfbc3f6e8fd49217e6cc7944e145cdda8caa3734777f9e69", size = 45739 },
]
[[package]]
name = "typing-inspection"
version = "0.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 },
]
[[package]]
name = "uvicorn"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 },
]
[[package]]
name = "websockets"
version = "15.0.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440 },
{ url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098 },
{ url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329 },
{ url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111 },
{ url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054 },
{ url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496 },
{ url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829 },
{ url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217 },
{ url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195 },
{ url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393 },
{ url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837 },
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 },
]