Files
ABAP-Indexing/sap/README.md
T
byeongwook.choiandClaude Fable 5.1 e23140b7af sap_client: ZAA_ICF JSON 규약 대응 (2026-09-14 XML→JSON 전환)
- 요청은 INPUT 래퍼 없이 입력 필드만, 응답은 JSON {"RETURN","RESULT"}. 첫 글자로 asXML 구버전도 자동 판별
- 실측: ZMP_ICF 에서 ZFI01 목록 177건, 프로그램 2본(include 10, T코드 2) 수집 → normalize 정상
- sap/README.md 규약 갱신, tests: JSON/XML 파서·요청 본문 검증

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 14:41:14 +09:00

111 lines
7.6 KiB
Markdown

# 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줄로 돌아온다.
## 호출 규약 (실측)
**2026-09-14 부터 JSON** (ZCL_ZAI_API, ICF 노드 `/sap/bc/ZMP_ICF`). 구버전 asXML 은 아래 그대로이며, `sap_client` 는
응답 첫 글자(`{` / `<`)로 둘을 자동 판별한다.
- 요청: `POST /sap/bc/ZMP_ICF/{METHOD}`, 바디는 **래퍼 없이** `{"IV_PACKAGE":"ZFI01","IV_MAX_ROWS":100}` (키 대문자, 숫자는 JSON 숫자, 플래그는 `"X"`, 일자 `YYYY-MM-DD` 가능)
- 응답: `{"RETURN":{"TYPE","MESSAGE","TOTAL_ROWS"},"RESULT":[...]}` — TOTAL_ROWS 는 숫자, 빈 목록은 `[]`, 소스 줄바꿈은 `
`
- 필터 없는 목록 호출은 S+안내가 아니라 **E** (`IV_PACKAGE 또는 IV_PATTERN 중 하나는 필수입니다`)
- 실측 2026-09-21: `GET_PROGRAM_LIST ZFI01` 177건 / `GET_PROGRAM_SOURCE` include·T코드 정상
### 구버전 (asXML, ~2026-09-13)
- `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 로 읽으면 된다.