Stage 0: SAP(ZAA_ICF)에서 패키지 단위로 프로그램 소스 수집 (ingest/from_sap.py)
- sap/: abap-mcp 의 catalog/sap_client/mcp_server 복사. import 와 .env 탐색만 이 저장소에 맞춤 - ingest/from_sap.py: 패키지명 → GET_PROGRAM_LIST → GET_PROGRAM_SOURCE(+Include) → data/raw/*.txt (normalize 가 읽는 수집 JSON 형식 그대로). 받은 파일은 건너뛰고 --force 로 재수집 - normalize: 응답의 TCODE_LIST 를 tcodes.jsonl 로 (from_dir 와 같은 모양, 로더가 적재) - .env.example 에 SAP_URL/SAP_USER/SAP_PASS, tests/test_from_sap.py Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
11ae3629b2
commit
921b8ce229
@@ -0,0 +1,99 @@
|
||||
# ZAA_ICF MCP 서버 (abap-sap)
|
||||
|
||||
SAP `ZCL_ZAA_AGENT_API` 의 16개 메서드를 LLM 에 붙이는 MCP 서버. 전부 조회만 한다.
|
||||
브라우저에서 같은 API 를 호출해 보는 웹 테스터와 Bruno 연동은 형제 프로젝트 **`../abap-api-tester`** 에 있다.
|
||||
API 정의(`catalog.py`)와 SAP 호출 클라이언트(`sap_client.py`)는 이 폴더가 원본이고, 테스터가 여기서 가져다 쓴다.
|
||||
|
||||
## 실행
|
||||
|
||||
```
|
||||
cp .env.example .env # SAP_USER / SAP_PASS 채우기
|
||||
pip install -r requirements.txt # mcp>=2.1 (Python 3.10+)
|
||||
python mcp_server.py # stdio (Claude Code / Claude Desktop)
|
||||
python mcp_server.py --http 8766 # streamable-http http://127.0.0.1:8766/mcp
|
||||
python tools/test_mcp.py [--shrink] # 툴 목록 + 실제 호출 점검
|
||||
```
|
||||
|
||||
접속 정보는 `.env` 의 `SAP_URL` / `SAP_USER` / `SAP_PASS`. `.env` 는 스크립트 위치 기준으로 읽으므로 작업 디렉터리와 무관하다.
|
||||
로그는 stderr 로만 나간다 (stdout 은 프로토콜 채널이라 print 금지).
|
||||
|
||||
## 파일
|
||||
|
||||
| 파일 | 역할 |
|
||||
|---|---|
|
||||
| `mcp_server.py` | MCP 서버. `catalog.py` 의 16개 메서드를 툴로 노출 (stdio / streamable-http) |
|
||||
| `catalog.py` | 메서드 설명, 파라미터, 샘플, 필드 사전, 코드값 맵, 셀 링크. **테스터와 공유 (원본)** |
|
||||
| `sap_client.py` | SAP 호출·응답 정규화(BOM/utf-16 선언/제어문자 제거, asXML→JSON). **테스터와 공유 (원본)** |
|
||||
| `.mcp.json` | Claude Code 프로젝트 MCP 설정 (이 폴더를 열면 `abap-sap` 서버가 등록됨) |
|
||||
| `tools/test_mcp.py` | MCP 서버 점검 (툴 목록 + 실제 호출 + 잘라내기) |
|
||||
|
||||
카탈로그를 고치면 툴 정의도, 테스터 화면도, Bruno 컬렉션(테스터가 생성)도 같이 바뀐다.
|
||||
|
||||
## 연결
|
||||
|
||||
- **Claude Code**: 이 폴더에 `.mcp.json` 이 있어 프로젝트를 열면 `abap-sap` 서버가 등록된다 (처음 한 번 승인).
|
||||
다른 폴더에서도 쓰려면 사용자 범위로 등록한다.
|
||||
```
|
||||
claude mcp add --transport stdio --scope user abap-sap -- python C:\Users\<me>\EdgeCenter\samsung\abap-mcp\mcp_server.py
|
||||
```
|
||||
- **Claude Desktop**: `%APPDATA%\Claude\claude_desktop_config.json`
|
||||
```json
|
||||
{"mcpServers": {"abap-sap": {"type": "stdio", "command": "python",
|
||||
"args": ["C:\Users\<me>\EdgeCenter\samsung\abap-mcp\mcp_server.py"]}}}
|
||||
```
|
||||
`.env` 대신 환경변수로 넘기려면 `"env": {"SAP_USER": "...", "SAP_PASS": "..."}`.
|
||||
|
||||
## 툴
|
||||
|
||||
툴 정의(이름·설명·파라미터·예시)는 `catalog.py` 에서 **자동 생성**된다.
|
||||
|
||||
| 툴 | 비고 |
|
||||
|---|---|
|
||||
| `get_package_list` … `get_tcode_info`, `get_object_type` (17개, 메서드명 소문자) | 파라미터는 `IV_` 를 뗀 소문자 (`IV_MAX_ROWS` → `max_rows`). 플래그는 boolean, 날짜는 `YYYY-MM-DD` |
|
||||
| `explain_fields` | 응답 필드의 한국어 뜻과 코드값 표 (`catalog.py` 의 FIELDS / VALUE_MAPS) |
|
||||
| `sap_connection_info` | 접속 대상·호출 규약 확인 (비밀번호 제외) |
|
||||
|
||||
응답은 `{"return": {"type", "message", "total_rows"}, …결과, "_meta": {sap_method, sap_params, ms, raw_bytes}}`.
|
||||
`RETURN.TYPE=E` 도 툴 오류가 아니라 정상 응답으로 돌려준다 (LLM 이 메시지를 읽고 판단한다). 네트워크·인증·XML 파싱 실패만 툴 오류.
|
||||
서버 `instructions` 에 탐색 흐름(패키지 → 오브젝트 → 소스, T코드 → 프로그램, 용어 → 필드)과 응답 읽는 법이 들어 있다.
|
||||
|
||||
### LLM 컨텍스트 보호
|
||||
|
||||
| 동작 | 기본값 | 환경변수 |
|
||||
|---|---|---|
|
||||
| 목록 툴에서 `max_rows` 생략 시 상한. 잘리면 `return.type=W` + `total_rows`. `0` 이면 전건(SAP 안전 상한 50,000) | 200 | `ABAP_MCP_DEFAULT_MAX_ROWS` |
|
||||
| 응답 JSON 최대 문자 수. 넘치면 가장 큰 소스/목록부터 절반씩 줄이고 `_truncated` 에 무엇을 얼마나 잘랐는지 적는다 | 120,000 | `ABAP_MCP_MAX_OUTPUT_CHARS` |
|
||||
| SAP 호출 타임아웃(초) | 180 | `ABAP_MCP_TIMEOUT` |
|
||||
|
||||
소스를 주는 툴(`get_program_source`, `get_version_source`, `get_function_detail`)에는 `line_from` / `line_to` 가 추가돼 있어
|
||||
긴 소스를 나눠 읽는다. 응답에 `SOURCE_CODE_LINES`(전체 줄 수)와 `SOURCE_CODE_RANGE` 가 붙는다. Include/화면 안의 소스도 같은 범위로 잘린다.
|
||||
|
||||
실측(2026-09-07): `SAPMV45A` + Include(원문 4.2MB, include 1,822개)를 상한 15,000자로 부르면 14,378자 + 안내 3줄로 돌아온다.
|
||||
|
||||
## 호출 규약 (실측)
|
||||
|
||||
- `POST /sap/bc/ZAA_ICF/{METHOD}`, `Content-Type: application/json`, 바디 `{"INPUT": {"IV_...": ...}}`
|
||||
- 응답 asXML. XML 선언은 `utf-16` 이지만 실제 UTF-8 → 선언 제거 후 파싱
|
||||
- 소스코드 안 제어문자(0x0C)가 XML 을 깨뜨림 → 파싱 전 제거
|
||||
- 날짜는 `YYYY-MM-DD`
|
||||
- 목록 API 의 `IV_MAX_ROWS` 는 생략하면 전건(안전 상한 50,000). 지정해서 잘리면 `RETURN.TYPE=W`, 메시지에 `(전체 M건)`, `RETURN.TOTAL_ROWS=M` (2026-09-04 A안. 그 전엔 기본 200/최대 2,000)
|
||||
|
||||
## 서버측 결함 이력 (2026-09-04 기준 모두 해결)
|
||||
|
||||
- ~~`GET_VERSION_SOURCE` 과거 버전 → HTTP 500~~ 2026-09-04 해결. `SVRS_GET_REPS_FROM_OBJECT` 로 교체 (REPS 만 지원)
|
||||
- ~~`GET_FUNCTION_DETAIL` 의 `FUNC_SOURCE` 항상 빈값~~ 2026-09-04 해결. 함수 Include 를 통째로 읽도록 교체 (출처가 `RETURN.MESSAGE` 에 찍힘)
|
||||
- ~~`GET_PROGRAM_SOURCE` include 목록에 클래스 include 노이즈 혼입~~ 2026-09-04 해결. `=` 포함 또는 30자 초과 이름 제거
|
||||
- ~~`GET_OBJECT_LIST_BY_PACKAGE` 상한에서 잘려도 `TYPE=S`~~ 2026-09-04 해결. n+1 건을 읽어 초과 시 W (실측 ZFI01 PROG 1,132건)
|
||||
- ~~`GET_FIELD_LIST_BY_TEXT` USED_TABLES 20건 상한이 조용히 잘림 / 후보 0건이면 응답 본문 0바이트~~ 2026-09-04 해결. W + 잘린 엘리먼트 이름, 0건은 빈 RESULT XML
|
||||
|
||||
- ~~목록 API 기본 상한 200 / 최대 2,000~~ 2026-09-04 A안으로 제거. 생략 시 전건(안전 상한 50,000), `RETURN.TOTAL_ROWS` 추가. 실측: 테이블 `*` 전건 822,899건 → 50,000건 W, 8.7MB, 8초
|
||||
|
||||
- ~~`GET_FIELD_LIST_BY_TEXT` USED_TABLES 엘리먼트당 20건 고정 상한~~ 2026-09-04 제거. 후보 500개씩 청크 조회, 사용 테이블 총량 50,000 넘으면 중단 + W. 실측 '회사코드' 전건 499후보/6,949건 0.8MB, '코드' 14,000후보 → 안전 상한 W 10MB/4초
|
||||
|
||||
수정 소스는 `~/Downloads/CLASS ZCL_ZAA_AGENT_API DEFINITION.txt` (전체) 와 `METHOD *.txt`. API 명세서(메서드당 1시트)는 `~/Downloads/ABAP_API_명세서_ZAA_ICF_v1.4_2026-09-04.xlsx` (생성기는 `../abap-api-tester/tools/spec/`).
|
||||
|
||||
## 응답 인코딩 (2026-09-04 확인)
|
||||
|
||||
응답 HTTP 헤더는 `Content-Type: text/html; charset=utf-8` 이고 본문은 UTF-8 BOM 으로 시작한다.
|
||||
XML 선언만 `encoding="utf-16"` 으로 잘못 찍혀 있다. Bruno 는 헤더 charset 으로 디코딩하므로 Bruno 단계에서는 문제없고,
|
||||
직접 파싱하는 클라이언트만 BOM 과 XML 선언을 떼고 UTF-8 로 읽으면 된다.
|
||||
Reference in New Issue
Block a user