---
name: context-handling
description: MCP 컨텍스트 처리 및 전역 컨텍스트 fallback 메커니즘
---
# 컨텍스트 처리 가이드
## 개요
이 skill은 MCP OpenDART 프로젝트의 컨텍스트 처리 메커니즘을 설명합니다. streamable-http와 stdio transport 모두에서 동작하도록 전역 컨텍스트 fallback 시스템이 구현되어 있습니다.
## 사용 시나리오
- Tool에서 API 호출 시 컨텍스트 사용
- streamable-http에서 ctx 주입 실패 시 fallback 이해
- 새로운 tool 개발 시 컨텍스트 사용 패턴
## 아키텍처
### 컨텍스트 흐름
```mermaid
graph TD
A[Tool 호출] --> B{ctx 주입 있음?}
B -->|Yes| C[ctx에서 OpenDartContext 추출]
B -->|No| D[전역 컨텍스트 사용]
C --> E{추출 성공?}
E -->|Yes| F[API 호출]
E -->|No| D
D --> F
F --> G[응답 변환]
G --> H[결과 반환]
```
### 전역 컨텍스트 저장소
`server.py`에서 lifespan 시작 시 전역 컨텍스트 저장:
```python
# server.py
_global_context: Optional['OpenDartContext'] = None
_context_lock = threading.Lock()
def set_global_context(ctx: 'OpenDartContext') -> None:
"""전역 OpenDartContext를 설정합니다."""
global _global_context
with _context_lock:
_global_context = ctx
def get_global_context() -> Optional['OpenDartContext']:
"""전역 OpenDartContext를 가져옵니다."""
global _global_context
with _context_lock:
return _global_context
```
## 구현 가이드
### 1. with_context() 사용
모든 tool에서 `with_context()` 함수 사용:
```python
from mcp_opendart.utils.ctx_helper import with_context
result = with_context(
None, # ctx 파라미터는 항상 None (제거됨)
"tool_name", # 로깅용 도구명
lambda context: context.ds001.get_something(param) # 실제 API 호출
)
```
### 2. Fallback 동작
`with_context()` 함수의 동작 순서:
1. **ctx에서 컨텍스트 추출 시도**
- `ctx.request_context.lifespan_context` 접근
- dict 래핑 케이스 자동 처리
2. **전역 컨텍스트로 fallback**
- ctx 추출 실패 시 `get_global_context()` 호출
- 전역 컨텍스트가 있으면 사용
3. **에러 발생**
- 둘 다 없으면 `ValueError` 발생
- "OpenDART context is required but not provided. Lifespan context not initialized."
### 3. 비동기 버전
비동기 tool에서는 `with_context_async()` 사용:
```python
from mcp_opendart.utils.ctx_helper import with_context_async
result = await with_context_async(
None,
"async_tool_name",
lambda context: context.ds001.async_get_something(param)
)
```
## Transport 차이점
### stdio Transport
- MCP 클라이언트가 ctx를 정상적으로 주입
- `ctx.request_context.lifespan_context` 접근 가능
- 전역 컨텍스트는 fallback으로만 사용
### streamable-http Transport
- 일부 클라이언트가 ctx를 주입하지 않음
- 전역 컨텍스트가 주요 경로
- fallback 메커니즘으로 안정성 확보
## 예제
### 예제 1: 기본 사용
```python
@mcp.tool(
name="get_corporation_info",
description="기업 기본 정보 조회"
)
def get_corporation_info(
corp_code: Annotated[str, "고유번호 (8자리)"],
) -> TextContent:
result = with_context(
None, # ctx 파라미터 제거됨
"get_corporation_info",
lambda context: context.ds001.get_corporation_info(corp_code)
)
return as_json_text(result)
```
### 예제 2: 복잡한 파라미터
```python
@mcp.tool(
name="get_disclosure_list",
description="공시 목록 조회"
)
def get_disclosure_list(
corp_code: Annotated[str, "고유번호 (8자리)"],
bgn_de: Annotated[str, "시작일 (YYYYMMDD)"],
end_de: Annotated[str, "종료일 (YYYYMMDD)"],
) -> TextContent:
result = with_context(
None,
"get_disclosure_list",
lambda context: context.ds001.get_disclosure_list(
corp_code=corp_code,
bgn_de=bgn_de,
end_de=end_de
)
)
return as_json_text(result)
```
### 예제 3: 여러 API 모듈 사용
```python
@mcp.tool(
name="get_comprehensive_info",
description="기업 종합 정보 조회"
)
def get_comprehensive_info(
corp_code: Annotated[str, "고유번호 (8자리)"],
) -> TextContent:
# 여러 API 호출
def fetch_all(context):
return {
"basic_info": context.ds001.get_corporation_info(corp_code),
"financial": context.ds003.get_single_acnt(corp_code, "2024", "11011"),
"ownership": context.ds004.get_major_shareholders(corp_code)
}
result = with_context(None, "get_comprehensive_info", fetch_all)
return as_json_text(result)
```
## 주의사항
1. **ctx 파라미터 절대 사용 금지**
- Tool 시그니처에 `ctx: Optional[Any] = None` 포함하지 않음
- `with_context(None, ...)` 항상 사용
2. **컨텍스트 접근 패턴**
- `context.ds001`, `context.ds002` 등으로 접근
- `context.client`로 직접 클라이언트 접근 가능
3. **응답 변환**
- `with_context()`는 자동으로 키 변환 및 금액 필드 변환 수행
- `transform_response=False`로 비활성화 가능
4. **에러 처리**
- 컨텍스트가 없으면 명확한 에러 메시지
- 로그에서 "전역 컨텍스트 사용 (fallback)" 확인 가능
5. **Thread Safety**
- 전역 컨텍스트는 `threading.Lock()`으로 보호
- 멀티 스레드 환경에서 안전
## 디버깅
### 컨텍스트 상태 확인
```python
from mcp_opendart.server import get_global_context
global_ctx = get_global_context()
if global_ctx:
print("전역 컨텍스트 존재")
print(f"Client: {global_ctx.client}")
else:
print("전역 컨텍스트 없음")
```
### 로그 확인
```bash
# 전역 컨텍스트 사용 확인
docker logs mcp-opendart | grep "전역 컨텍스트"
# 컨텍스트 추출 실패 확인
docker logs mcp-opendart | grep "MCPContext 접근 실패"
```
## 관련 파일
- [src/mcp_opendart/server.py](src/mcp_opendart/server.py) - 전역 컨텍스트 저장소
- [src/mcp_opendart/utils/ctx_helper.py](src/mcp_opendart/utils/ctx_helper.py) - with_context 함수
- [src/mcp_opendart/tools/disclosure_tools.py](src/mcp_opendart/tools/disclosure_tools.py) - 사용 예제