급여 보험료 조회 (MCP 도구 2종)
건강보험, 국민연금, 고용보험의 부과고지를 조회해 사업장 단위 보험료 금액을 받는 MCP 도구 두 개의 준비, 요청, 응답, 오류 안내입니다.선택으로 엑셀(xlsx) 파일과 pack_run_id 재조회도 안내합니다.
이 도구가 하는 일
건강보험, 국민연금, 고용보험의 부과고지를 기관에서 조회해 사업장의 보험료 금액을 한 번에 받는 MCP 도구 두 개입니다.건강보험, 노인장기요양, 국민연금, 고용보험(실업급여) 네 가지 금액을 사업장 단위로 돌려줍니다(도구마다 채우는 금액 칸이 다릅니다).근로자별 금액이나 급여대장이 아니고, 소득세와 지방소득세는 계산하지 않으며, 산재보험 금액은 계산 칸에 담지 않습니다.
이 문서에서는 도구가 부르는 기관 조회 하나하나를 leg 이라고 부릅니다.먼저 검색 leg 이 조회할 고지를 고르고, 그 결과로 인자를 만드는 leg(요약, 상세)이 뒤에 이어집니다. 응답의 legs 에는 leg 마다 원래 조회의 결과가 그대로 담기고, computed 에는 그 결과에서 뽑아 계산한 금액이 담깁니다.arguments 에 output_formats: ["xlsx"] 를 넣으면 같은 금액 표가 엑셀 파일로도 artifacts 에 오고, 응답의 pack_run_id 로 링크가 만료된 뒤에도 결과를 다시 읽을 수 있습니다.
| 도구 | 2개(아래 표) |
|---|---|
| 호출 방식 | tools/call (JSON-RPC 2.0) |
| 엔드포인트 | POST https://api.xdata.kr/mcp |
| 인증 | Authorization: Basic |
| 환경 헤더 | X-MCP-Env-Scope (선택) |
| 최대 대기 | 조회 하나 60초, 호출 전체 90초 |
이 도구들은 MCP 로 부를 수 있습니다. REST 로 부르는 법은 아직 이 페이지에 없습니다(준비 중입니다).주소, 인증, 환경 헤더는 MCP 연동 개요와 같습니다.
도구 두 개
| 도구 | 언제 쓰나 | 연동할 기관 계정 | 채우는 금액 칸 |
|---|---|---|---|
| payroll_pack__premium_summary_v1__generate | 건강보험과 국민연금의 고지 금액이 필요할 때 | 국민건강보험공단 EDI, 국민연금공단 EDI | health_insurance_won, long_term_care_won, national_pension_won, combined_insurance_premium_won, deduction_rows[].amount_won |
| payroll_pack__employment_insurance_summary_v1__generate | 고용보험(실업급여) 금액만 필요할 때 | 근로복지공단 | employment_insurance_won, deduction_rows[].amount_won |
금액의 뜻을 정확히 읽으세요. 모든 금액은 각 기관이 사업장에 고지한 합계이고 근로자별 금액이 아닙니다.고용보험 금액(employment_insurance_won)은 실업급여 보험료만 담고, 고용안정과 직업능력개발사업 보험료는 더하지 않습니다. 세액은 이 도구가 계산하지 않고 computed.missing_fields 에 이름만 남습니다.
부르기 전에 준비할 것
- 기관 계정을 연동합니다
콘솔 자격증명 등록에서 도구가 쓰는 기관의 계정을 연결합니다(도구마다 연결할 계정이 다르니 위 표를 보세요).API 키로 부르면 이 도구는 그 키를 만든 사용자가 연동한 계정을 기관마다 자동으로 고릅니다. 대표 연동이 있으면 그것을 쓰고, 없으면 활성 연동이 하나뿐일 때 그것을 씁니다.활성 연동이 여러 개인데 대표가 없으면 그 기관의 조회가 MULTIPLE_ACTIVE_ACCOUNT_LINKS 로 거절됩니다. 이 도구는 account_link_id 를 받지 않습니다.문의는 서비스문의로 하면 됩니다.
- 기관마다 로그인해 세션을 엽니다
이 도구는 기관에 로그인하지 않습니다. 기관에 세션이 없거나 만료되면 그 기관의 leg 이 실패하고 오류의 error.data.next_action 에 부를 로그인 도구 이름이 옵니다.같은 연동 계정(account_link_id)으로 그 로그인 도구를 부른 뒤 이 도구를 다시 부르면 됩니다. 이때 idempotency_key 를 넣었다면 새 값으로 바꾸거나 빼세요.같은 키는 이전 결과를 그대로 돌려줍니다(아래 「같은 요청을 다시 보낼 때」). 로그인 도구는 아래 표와 같습니다.
- MCP 서버를 연결합니다
MCP 연동 개요의 순서대로 AI 앱이나 MCP 클라이언트에 XBOSS MCP 서버를 연결합니다.
기관별 로그인 도구
| 기관 | 로그인 도구 | 이 기관을 쓰는 도구 |
|---|---|---|
| 국민건강보험공단 EDI | nhis_edi__session__login | payroll_pack__premium_summary_v1__generate |
| 국민연금공단 EDI | nps_edi__auth__npki_session__login | payroll_pack__premium_summary_v1__generate |
| 근로복지공단 | comwel__auth__npki_session__login | payroll_pack__employment_insurance_summary_v1__generate |
요청
도구 이름과 arguments 를 담아 tools/call 을 보냅니다.같은 인자가 검색 leg 에 그대로 전달되고 각 leg 은 필요한 칸을 씁니다.notice_year_month 는 건강보험과 국민연금 leg, goji_round 는 건강보험 leg, installment_round 는 국민연금 leg, billing_year 와 billing_month 는 고용산재 leg 이 씁니다.선택 인자 output_formats 에 ["xlsx"] 를 넣으면 금액 표(computed.deduction_rows)를 엑셀 파일로도 받습니다(사용 토큰 추가 없음).금액 표가 없는 호출(계산에 필요한 leg 자료가 없거나 호출 전체 마감으로 계산을 건너뛰어 computed 가 null 인 호출)은 엑셀 파일 없이 받은 자료만 담아 성공으로 끝납니다(아래 「엑셀 파일을 청했는데 금액 표를 만들 수 없을 때」).도구마다 받는 인자가 다르니 아래 표에서 그 도구의 것을 보세요.
payroll_pack__premium_summary_v1__generate
| 필드 | 타입 | 필수 | 설명 | 예시 |
|---|---|---|---|---|
| notice_year_month | string | 필수 | 조회할 고지연월입니다. YYYYMM 형식의 문자열입니다(예: 202608). 건강보험과 국민연금 고지에서 이 달의 고지를 찾습니다. | 202608 |
| installment_round | integer | 필수 | 국민연금 고지 차수입니다. 1 은 1차 고지(ED02), 2 는 2차 고지(ED08)입니다. | 1 |
| goji_round | integer | 선택 | 건강보험 고지차수입니다. 생략하면 고지연월에 맞는 고지가 하나일 때 그 고지를 씁니다(같은 고지를 작성회차만 달리 여러 번 발급했으면 가장 최근 작성분을 씁니다). 그래도 여럿이면 하나로 정해지지 않아 요약 leg 이 건너뛰어집니다. | — |
| nhis_document_id | string | 선택 | 건강보험 요약에 쓸 고지서 종류입니다. BMBB_030 은 정기 고지(사업장고지내역서 건강), BMBB_050 은 경정 고지(사업장고지내역서 건강/경정)입니다. 생략하면 BMBB_030 입니다. | — |
| nhis_row_key | string | 선택 | 건강보험 고지 검색 결과에서 고지 한 건을 정하는 행 키입니다. 조건에 맞는 고지가 여럿이라 하나로 정해지지 않을 때만 씁니다. | — |
| nps_row_key | string | 선택 | 국민연금 고지 검색 결과에서 고지 한 건을 정하는 행 키입니다. 조건에 맞는 고지가 여럿이라 하나로 정해지지 않을 때만 씁니다. | — |
| output_formats | array | 선택 | 이 결과를 엑셀(xlsx) 파일로도 받으려면 적습니다. 예: ["xlsx"]. 파일은 artifacts 에 함께 오고, 빼면 데이터만 옵니다. 사용 토큰은 추가로 쓰지 않습니다. | — |
| API_KEY | - | 받지 않음 | account_link_id 의 다른 철자입니다. 서버가 같은 칸으로 보고 똑같이 처리합니다. | — |
| accountLinkId | - | 받지 않음 | account_link_id 의 다른 철자입니다. 서버가 같은 칸으로 보고 똑같이 처리합니다. | — |
| account_link_id | - | 받지 않음 | 받지 않는 칸입니다. 보내도 무시됩니다. 연동한 기관 계정은 서버가 leg 마다 그 기관의 연동으로 자동으로 고릅니다(대표 연동, 없으면 활성 연동이 하나일 때 그것). 어느 기관에 활성 연동이 여럿인데 대표가 없으면 고르지 못해 그 기관의 leg 이 거절됩니다. | — |
| api_key | - | 받지 않음 | account_link_id 의 다른 철자입니다. 서버가 같은 칸으로 보고 똑같이 처리합니다. | — |
| idempotency_key | string | 선택 | 이 실행의 멱등 키입니다(비어 있지 않은 문자열, 앞뒤 공백은 지우고 128자까지 씁니다). 같은 키와 같은 조회 조건으로 다시 부르면 새 작업을 만들지 않고 이전 작업을 그대로 돌려받아 같은 결과가 옵니다. 같은 키로 조회 조건을 바꾸면 조회를 하나도 시작하지 않고 호출이 오류 번호 -32602(error_code IDEMPOTENCY_KEY_CONFLICT)로 끝나니, 같은 요청을 다시 보내는 것이면 처음 보낸 인자 그대로 보내고 새 요청이면 새 키를 쓰세요. 넣지 않으면 서버가 호출마다 새 키를 만들어 매번 새 작업으로 처리합니다. | payroll-premium-2026-08 |
payroll_pack__employment_insurance_summary_v1__generate
| 필드 | 타입 | 필수 | 설명 | 예시 |
|---|---|---|---|---|
| billing_year | string | 필수 | 고용산재 부과고지의 부과 연도입니다. 네 자리 연도의 문자열입니다(예: 2026). | 2026 |
| billing_month | string | integer | 필수 | 고용산재 부과고지의 부과 월입니다. 1 부터 12 까지의 정수이거나 그 숫자를 담은 문자열입니다(예: 8). | 8 |
| business_management_no | string | 선택 | 근로복지공단 사업장관리번호입니다. 생략하면 로그인한 사업장의 번호를 씁니다. | — |
| output_formats | array | 선택 | 이 결과를 엑셀(xlsx) 파일로도 받으려면 적습니다. 예: ["xlsx"]. 파일은 artifacts 에 함께 오고, 빼면 데이터만 옵니다. 사용 토큰은 추가로 쓰지 않습니다. | — |
| API_KEY | - | 받지 않음 | account_link_id 의 다른 철자입니다. 서버가 같은 칸으로 보고 똑같이 처리합니다. | — |
| accountLinkId | - | 받지 않음 | account_link_id 의 다른 철자입니다. 서버가 같은 칸으로 보고 똑같이 처리합니다. | — |
| account_link_id | - | 받지 않음 | 받지 않는 칸입니다. 보내도 무시됩니다. 연동한 기관 계정은 서버가 leg 마다 그 기관의 연동으로 자동으로 고릅니다(대표 연동, 없으면 활성 연동이 하나일 때 그것). 어느 기관에 활성 연동이 여럿인데 대표가 없으면 고르지 못해 그 기관의 leg 이 거절됩니다. | — |
| api_key | - | 받지 않음 | account_link_id 의 다른 철자입니다. 서버가 같은 칸으로 보고 똑같이 처리합니다. | — |
| idempotency_key | string | 선택 | 이 실행의 멱등 키입니다(비어 있지 않은 문자열, 앞뒤 공백은 지우고 128자까지 씁니다). 같은 키와 같은 조회 조건으로 다시 부르면 새 작업을 만들지 않고 이전 작업을 그대로 돌려받아 같은 결과가 옵니다. 같은 키로 조회 조건을 바꾸면 조회를 하나도 시작하지 않고 호출이 오류 번호 -32602(error_code IDEMPOTENCY_KEY_CONFLICT)로 끝나니, 같은 요청을 다시 보내는 것이면 처음 보낸 인자 그대로 보내고 새 요청이면 새 키를 쓰세요. 넣지 않으면 서버가 호출마다 새 키를 만들어 매번 새 작업으로 처리합니다. | payroll-employment-2026-08 |
# 보험료 요약 호출(다른 도구는 name 과 arguments 만 바꿉니다)
# echo -n "<client_id>:<client_secret>" | base64
export MCP_URL="https://api.xdata.kr/mcp"
export MCP_AUTH_B64="<base64_결과_붙여넣기>"
curl -sS -X POST "$MCP_URL" \
-H "Authorization: Basic $MCP_AUTH_B64" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "payroll_pack__premium_summary_v1__generate",
"arguments": {
"notice_year_month": "202608",
"installment_round": 1,
"idempotency_key": "payroll-premium-2026-08"
}
}
}'응답 읽기
호출이 성공하면 result.content[0].text 에 응답 본문이 JSON 문자열로 담겨 옵니다.JSON.parse 같은 함수로 풀어 씁니다.사용 토큰을 세는 환경(데모, 그리고 하루 한도가 정해진 요금제의 정식 환경)에서는 남은 사용 토큰이 두 번째 content 블록과 _meta 에 함께 실립니다(MCP 연동 개요의 「응답에 실리는 _meta」).봉투의 칸은 두 도구가 같고, 계산 칸(computed)만 도구마다 다릅니다. 아래 첫 표가 공통 칸이고 그 뒤에 도구별 계산 칸이 이어집니다. pack_run_id 는 이 실행의 아이디입니다.artifacts[].download_url 이 만료되면 MCP 도구 xdata__job__result 에 그 값을 pack_run_id 로 보내 같은 결과를 다시 읽습니다(새 조회를 만들지 않고 사용 토큰도 쓰지 않습니다).output_formats 를 넣지 않은 호출의 artifacts 는 빈 목록입니다.
공통 칸
| 필드 | 타입 | 있음 | 설명 |
|---|---|---|---|
| pack_run_id | string | 항상 | 이 실행의 팩 실행 아이디입니다. 링크가 만료되면 MCP 도구 xdata__job__result 에 이 값을 pack_run_id 로 보내 같은 결과를 다시 읽습니다(새 조회를 만들지 않고 사용 토큰도 쓰지 않습니다). |
| composite | boolean | 항상 | 항상 true 입니다. 일반 도구 결과와 이 칸으로 구분합니다. |
| tool_name | string | 항상 | 호출한 MCP 도구 이름입니다. |
| provider | string | 항상 | 팩의 provider 이름입니다. |
| action | string | 항상 | 팩의 action 이름입니다. |
| idempotency_key | string | 항상 | 이 실행의 멱등 키입니다. arguments 에 idempotency_key 를 넣었으면 그 값(앞뒤 공백을 지우고 128자까지), 넣지 않았으면 서버가 만든 값입니다. |
| partial | boolean | 항상 | 보고가 완전하지 않으면 true 입니다. leg 하나라도 성공하지 못했거나 계산을 하지 못했을 때입니다. true 여도 받은 자료는 그대로 쓸 수 있습니다 - 무엇이 비었는지는 legs 와 computed 가 알려 줍니다. |
| legs | array | 항상 | 이 도구가 실행한 leg 의 원본 결과입니다. 정의 순서로 옵니다(조회할 행을 고르는 검색 leg 이 먼저, 그 행으로 인자를 만드는 leg 이 뒤에 옵니다). |
| legs[].leg_index | number | 항상 | leg 의 순번입니다(0 부터, 정의 순서). |
| legs[].provider | string | 항상 | 이 leg 이 부른 기관입니다. |
| legs[].action | string | 항상 | 이 leg 이 실행한 원자 액션입니다. |
| legs[].label_ko | string | 항상 | 이 leg 을 화면에 보일 한국어 이름입니다. |
| legs[].status | string | 항상 | leg 의 결과 상태입니다. SUCCEEDED, FAILED, SKIPPED 중 하나입니다. SUCCEEDED 는 그 조회가 끝났다는 뜻이지 찾은 자료가 있다는 뜻이 아닙니다. 건강보험이나 국민연금 검색 leg 이 고지를 하나도 못 찾아도 SUCCEEDED 이고, 그때 결과의 options 는 비어 있으며 error_code 에 이유가 적힙니다. 고용산재 검색 leg 이 성공해도 결과에 고용보험 상세 후보(detail_candidates)가 없으면 상세 leg 은 실행되지 않습니다. SKIPPED 는 앞 leg 의 결과로 인자를 만들어야 하는 leg 이 그 결과를 쓰지 못해 기관에 보내지 않은 것입니다(앞 leg 이 실패했거나, 앞 leg 이 고른 자료가 없거나 하나로 정해지지 않은 경우). SKIPPED leg 은 job_id 가 null 이고 error.data.error_code 가 COMPOSITE_DEPENDENCY_UNAVAILABLE 이며 error.message 는 한국어 안내 문장입니다. failed_count 는 FAILED 와 SKIPPED 를 함께 셉니다. |
| legs[].job_id | string | null | 항상 | 이 leg 이 실행한 개별 작업의 아이디입니다. 제출하지 못한 leg 은 null 입니다. |
| legs[].result | object | null | 항상 | 성공한 leg 의 결과 본문입니다 - 그 원자 액션이 돌려주는 값만 담고, 파일은 legs[].artifacts 에 따로 옵니다. 예시에는 이 도구가 읽는 칸 위주로 실었고 실제 응답에는 칸이 더 있습니다. 실패했거나 건너뛴 leg 은 null 입니다. |
| legs[].artifacts | array | null | 항상 | 성공한 leg 의 작업이 낸 파일 목록입니다. 파일을 내지 않았으면 빈 목록이고, 실패했거나 건너뛴 leg 은 null 입니다. 파일은 legs[].result 와 섞이지 않고 이 칸에만 있습니다. |
| legs[].error | object | null | 항상 | 실패한 leg 의 오류입니다. 성공했으면 null 입니다. |
| legs[].error.code | number | 조건부 | 오류 번호입니다(JSON-RPC 코드). |
| legs[].error.message | string | 조건부 | 이 leg 의 오류 문장입니다. 프로그램의 분기에는 code 와 data.error_code 를 쓰고, 문장은 사람이 읽는 용도로만 쓰세요. |
| legs[].error.data | object | null | 조건부 | 오류 종류별 부가 정보입니다. 보통 error_code 가 들어 있고 종류에 따라 다른 칸이 더해집니다. 부가 정보가 없는 오류는 이 칸이 없거나 null 입니다. 시간 초과일 때의 칸은 이 페이지의 시간 초과 절 표에 있습니다. |
| succeeded_count | number | 항상 | 성공한 leg 수입니다. |
| failed_count | number | 항상 | 성공하지 못한 leg 수입니다. |
| computed_error | object | null | 항상 | 계산을 하지 못했을 때만 값이 있고(computed 는 null), 그 밖에는 null 입니다. |
| computed_error.reason | string | 조건부 | 계산하지 못한 이유입니다. 값마다 뜻과 결과(보고가 partial 로 오는지, 오류로 끝나는지)는 「시간 초과와 계산 실패」 절의 표에 있습니다. |
| computed_error.message | string | 조건부 | 이유를 설명하는 문장입니다. 마감으로 계산을 건너뛴 봉투(deadline_exhausted)는 한국어 안내 문장이고, 계산에 필요한 leg 자료가 없는 봉투(dependency_missing)는 어느 leg 의 결과가 없는지 알리는 영어 진단 문장입니다. 사람이 읽는 문장이니 프로그램의 분기에는 reason 을 쓰세요. |
| artifacts | array | 항상 | 이 실행이 만든 파일 목록입니다. arguments 에 output_formats 로 형식을 청했을 때만 항목이 생기고, 청하지 않았으면 빈 목록입니다. 항목마다 download_url, name, size_bytes, sha256, expires_at 이 있습니다. 칸의 뜻과 내려받기 주소의 유효 시간은 작업 처리 흐름의 결과 조회(result.artifacts)와 같습니다. 링크가 만료되면 pack_run_id 로 xdata__job__result 를 다시 부릅니다. |
payroll_pack__premium_summary_v1__generate 의 계산 칸입니다.
| 필드 | 타입 | 있음 | 설명 |
|---|---|---|---|
| computed | object | null | 항상 | 두 기관의 요약 결과를 모아 계산한 보험료 재료입니다. 건강보험 고지 합계, 장기요양 고지 합계, 국민연금 최종 보험료와 그 세 금액의 합을 채웁니다. 건강보험과 국민연금 요약 leg 이 모두 성공해야 계산하고, 하나라도 없으면 null 이며 이유는 computed_error 에 있습니다(받은 자료는 legs 에 그대로 있습니다). |
| computed.health_insurance_won | number | 조건부 | 건강보험 고지 합계 금액(원)입니다. 건강보험료 요약 결과의 summary.health_notice_total 그대로이고, 사업장 단위 금액입니다(근로자별 금액이 아닙니다). |
| computed.long_term_care_won | number | 조건부 | 노인장기요양 고지 합계 금액(원)입니다. 건강보험료 요약 결과의 summary.care_notice_total 그대로이며, health_insurance_won 에 들어 있지 않은 별도 금액입니다. |
| computed.national_pension_won | number | 조건부 | 국민연금 최종 연금보험료(원)입니다. 국민연금보험료 요약 결과의 summary.final_pension_premium 그대로입니다. |
| computed.employment_insurance_won | null | 조건부 | 이 도구는 고용보험을 조회하지 않아 항상 null 입니다. |
| computed.combined_insurance_premium_won | number | 조건부 | 건강보험, 장기요양, 국민연금 세 금액의 합(원)입니다. 고용보험은 더하지 않습니다. |
| computed.total_insurance_deduction_won | null | 조건부 | 세 기관을 모두 조회하는 4대보험 공제 재료 도구만 채웁니다. 이 도구는 항상 null 입니다. |
| computed.completeness | string | 조건부 | 계산 결과에 붙는 표지입니다. 이 도구는 항상 full_insurance_subset 입니다(고용보험 요약 도구는 employment_insurance_only 입니다). 이 값이 모든 금액 칸이 채워졌다는 보증은 아닙니다 - 채워진 칸은 각 금액 칸이 null 인지로 확인하세요. |
| computed.missing_fields | array | 조건부 | 이 도구가 채우지 않는 항목의 이름 목록입니다. 도구 스키마에 있는 입력만 보내면 income_tax_won 과 local_income_tax_won 두 이름이 들어 있습니다 - 세액은 이 도구의 입력 스키마에 없어 채워지지 않습니다. |
| computed.deduction_rows | array | 조건부 | 엑셀 표에 쓰는 항목과 금액 행 목록입니다(computed 스칼라 금액에서 값이 있는 칸만). output_formats 로 xlsx 를 청하면 이 행이 파일의 시트가 됩니다. JSON 응답의 스칼라 금액 칸은 그대로 둡니다. |
| computed.deduction_rows[].item | string | 조건부 | 표의 항목 이름입니다(예: 건강보험, 고용보험(실업급여)). |
| computed.deduction_rows[].amount_won | number | 조건부 | 그 항목의 금액(원)입니다. |
payroll_pack__employment_insurance_summary_v1__generate 의 계산 칸입니다.
| 필드 | 타입 | 있음 | 설명 |
|---|---|---|---|
| computed | object | null | 항상 | 고용산재 부과고지 상세 결과에서 뽑은 보험료 재료입니다. 고용보험 중 실업급여 보험료 합계 하나를 채웁니다. 상세 leg 이 성공해야 계산하고, 아니면 null 이며 이유는 computed_error 에 있습니다(받은 자료는 legs 에 그대로 있습니다). |
| computed.health_insurance_won | null | 조건부 | 이 도구는 건강보험을 조회하지 않아 항상 null 입니다. |
| computed.long_term_care_won | null | 조건부 | 이 도구는 건강보험을 조회하지 않아 항상 null 입니다. |
| computed.national_pension_won | null | 조건부 | 이 도구는 국민연금을 조회하지 않아 항상 null 입니다. |
| computed.employment_insurance_won | number | 조건부 | 고용보험 중 실업급여 보험료 합계(원)입니다. 고용산재 부과고지 상세 결과의 reconciliation.page_unemployment_total 그대로입니다. 고용안정과 직업능력개발사업 보험료(같은 결과의 reconciliation.page_stability_total)는 더하지 않습니다. |
| computed.combined_insurance_premium_won | null | 조건부 | 건강보험과 국민연금을 함께 조회하는 도구만 채웁니다. 이 도구는 항상 null 입니다. |
| computed.total_insurance_deduction_won | null | 조건부 | 세 기관을 모두 조회하는 4대보험 공제 재료 도구만 채웁니다. 이 도구는 항상 null 입니다. |
| computed.completeness | string | 조건부 | 계산 결과에 붙는 표지입니다. 이 도구는 항상 employment_insurance_only 입니다(다른 두 도구는 full_insurance_subset 입니다). 이 도구는 고용보험 금액 하나만 채웁니다. |
| computed.missing_fields | array | 조건부 | 이 도구가 채우지 않는 항목의 이름 목록입니다. 도구 스키마에 있는 입력만 보내면 income_tax_won 과 local_income_tax_won 두 이름이 들어 있습니다 - 세액은 이 도구의 입력 스키마에 없어 채워지지 않습니다. |
| computed.deduction_rows | array | 조건부 | 엑셀 표에 쓰는 항목과 금액 행 목록입니다(computed 스칼라 금액에서 값이 있는 칸만). output_formats 로 xlsx 를 청하면 이 행이 파일의 시트가 됩니다. JSON 응답의 스칼라 금액 칸은 그대로 둡니다. |
| computed.deduction_rows[].item | string | 조건부 | 표의 항목 이름입니다(예: 건강보험, 고용보험(실업급여)). |
| computed.deduction_rows[].amount_won | number | 조건부 | 그 항목의 금액(원)입니다. |
아래는 payroll_pack__premium_summary_v1__generate 의 4 leg 이 모두 성공했을 때의 응답 본문입니다.legs[].result 에는 그 조회가 돌려주는 값만 담기고 파일은 legs[].artifacts 에 따로 옵니다. 예시에서는 legs[].result 의 속을 줄여 적었습니다. 실제 응답에는 예시에 없는 칸이 더 있습니다.
{
"pack_run_id": "prun_01JC8YH8R0E1ABCDEFGHJKMN0P",
"composite": true,
"tool_name": "payroll_pack__premium_summary_v1__generate",
"provider": "payroll_pack",
"action": "payroll_pack.premium_summary_v1.generate",
"idempotency_key": "payroll-premium-2026-08",
"partial": false,
"legs": [
{
"leg_index": 0,
"provider": "nhis_edi",
"action": "nhis_edi.hr_payroll.premium_notices.search",
"label_ko": "건강보험 고지 검색",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN01",
"result": {
"...": "nhis_edi.hr_payroll.premium_notices.search 조회가 돌려주는 값입니다"
},
"artifacts": [],
"error": null
},
{
"leg_index": 1,
"provider": "nps_edi",
"action": "nps_edi.b1.pension_notices.search",
"label_ko": "국민연금 고지 검색",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN02",
"result": {
"...": "nps_edi.b1.pension_notices.search 조회가 돌려주는 값입니다"
},
"artifacts": [],
"error": null
},
{
"leg_index": 2,
"provider": "nhis_edi",
"action": "nhis_edi.hr_payroll.health_premiums.summary",
"label_ko": "건강보험료 요약",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN03",
"result": {
"...": "nhis_edi.hr_payroll.health_premiums.summary 조회가 돌려주는 값입니다"
},
"artifacts": [],
"error": null
},
{
"leg_index": 3,
"provider": "nps_edi",
"action": "nps_edi.b1.national_pension_premiums.summary",
"label_ko": "국민연금보험료 요약",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN04",
"result": {
"...": "nps_edi.b1.national_pension_premiums.summary 조회가 돌려주는 값입니다"
},
"artifacts": [],
"error": null
}
],
"succeeded_count": 4,
"failed_count": 0,
"computed": {
"health_insurance_won": 1243500,
"long_term_care_won": 163390,
"national_pension_won": 1890000,
"employment_insurance_won": null,
"combined_insurance_premium_won": 3296890,
"total_insurance_deduction_won": null,
"completeness": "full_insurance_subset",
"missing_fields": [
"income_tax_won",
"local_income_tax_won"
],
"deduction_rows": [
{
"item": "건강보험",
"amount_won": 1243500
},
{
"item": "노인장기요양",
"amount_won": 163390
},
{
"item": "국민연금",
"amount_won": 1890000
},
{
"item": "건강보험·요양·연금 합계",
"amount_won": 3296890
}
]
},
"computed_error": null,
"artifacts": []
}payroll_pack__employment_insurance_summary_v1__generate 가 성공했을 때 봉투의 계산 부분입니다. 나머지는 위 예시와 같은 모양입니다.
{
"partial": false,
"succeeded_count": 2,
"failed_count": 0,
"computed": {
"health_insurance_won": null,
"long_term_care_won": null,
"national_pension_won": null,
"employment_insurance_won": 378000,
"combined_insurance_premium_won": null,
"total_insurance_deduction_won": null,
"completeness": "employment_insurance_only",
"missing_fields": [
"income_tax_won",
"local_income_tax_won"
],
"deduction_rows": [
{
"item": "고용보험(실업급여)",
"amount_won": 378000
}
]
}
}일부만 실패했을 때
한 leg 이 실패해도 나머지 결과는 그대로 옵니다.이때 응답은 오류가 아니라 성공이고 partial 이 true 입니다(output_formats 를 넣은 호출도 같습니다. 다만 금액 표가 없으면 엑셀 파일은 만들어지지 않습니다 - 아래 「엑셀 파일을 청했는데 금액 표를 만들 수 없을 때」).실패한 leg 은 legs[].status 가 FAILED 이고 legs[].error 에 원인이 실립니다.앞 leg 의 결과가 있어야 인자를 만들 수 있는 leg(요약, 상세)은 앞 leg 이 실패하면 기관에 보내지 않고 SKIPPED 로 옵니다.그 오류의 error.data.error_code 는 COMPOSITE_DEPENDENCY_UNAVAILABLE 이고 error.message 는 한국어 안내 문장입니다.영어 진단 문장은 error.data.detail 에 따로 담깁니다. failed_count 는 FAILED 와 SKIPPED 를 함께 셉니다. computed 는 필요한 leg 의 결과가 모두 있어야 계산됩니다.하나라도 없으면 null 이고 이유는 computed_error.reason 에 실립니다. 받은 leg 의 결과는 그대로 쓸 수 있습니다. 아래는 국민연금 세션이 없어 그 검색 leg 이 실패하고 요약 leg 이 건너뛰어진 예시입니다.
{
"pack_run_id": "prun_01JC8YH8R0E1ABCDEFGHJKMN0P",
"composite": true,
"tool_name": "payroll_pack__premium_summary_v1__generate",
"provider": "payroll_pack",
"action": "payroll_pack.premium_summary_v1.generate",
"idempotency_key": "payroll-premium-2026-08",
"partial": true,
"legs": [
{
"leg_index": 0,
"provider": "nhis_edi",
"action": "nhis_edi.hr_payroll.premium_notices.search",
"label_ko": "건강보험 고지 검색",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN01",
"result": {
"...": "nhis_edi.hr_payroll.premium_notices.search 조회가 돌려주는 값입니다"
},
"artifacts": [],
"error": null
},
{
"leg_index": 1,
"provider": "nps_edi",
"action": "nps_edi.b1.pension_notices.search",
"label_ko": "국민연금 고지 검색",
"status": "FAILED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN02",
"result": null,
"artifacts": null,
"error": {
"code": -32004,
"message": "세션이 만료됐거나 없습니다(오류 AUTH_REQUIRED). 같은 account_link_id 로 'nps_edi__auth__npki_session__login' 을 먼저 불러 세션을 연 뒤 이 도구를 다시 부르세요.",
"data": {
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN02",
"error_code": "AUTH_REQUIRED",
"next_action": "nps_edi__auth__npki_session__login",
"safe_to_retry": true
}
}
},
{
"leg_index": 2,
"provider": "nhis_edi",
"action": "nhis_edi.hr_payroll.health_premiums.summary",
"label_ko": "건강보험료 요약",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN03",
"result": {
"...": "nhis_edi.hr_payroll.health_premiums.summary 조회가 돌려주는 값입니다"
},
"artifacts": [],
"error": null
},
{
"leg_index": 3,
"provider": "nps_edi",
"action": "nps_edi.b1.national_pension_premiums.summary",
"label_ko": "국민연금보험료 요약",
"status": "SKIPPED",
"job_id": null,
"result": null,
"artifacts": null,
"error": {
"code": -32004,
"message": "이 작업에 필요한 앞 단계 결과를 쓸 수 없습니다. 앞 단계 조회 결과를 확인한 뒤 다시 시도해 주세요.",
"data": {
"error_code": "COMPOSITE_DEPENDENCY_UNAVAILABLE",
"leg_index": 3,
"detail": "nps_edi nps_edi.b1.pension_notices.search did not produce a successful result"
}
}
}
],
"succeeded_count": 2,
"failed_count": 2,
"computed": null,
"computed_error": {
"reason": "dependency_missing",
"message": "nps_edi nps_edi.b1.national_pension_premiums.summary did not produce a successful result"
},
"artifacts": []
}- partial 을 봅니다
false 면 모든 leg 이 성공한 보고입니다. true 면 아래를 이어서 봅니다.
- 성공하지 못한 leg 을 찾습니다
legs[].status 가 FAILED 또는 SKIPPED 인 leg 을 봅니다.분기에는 error.code 와 error.data.error_code 를 쓰고, error.message 는 사람이 읽는 문장으로만 씁니다.
- 세션 문제라면 로그인합니다
error.data.error_code 가 AUTH_REQUIRED(세션 없음) 또는 PROVIDER_SESSION_EXPIRED(세션 만료)면 error.data.next_action 의 로그인 도구를 부른 뒤 다시 부릅니다.이때 idempotency_key 는 새 값으로 바꾸거나 빼세요(같은 키는 이전 결과를 그대로 돌려줍니다). error.data.safe_to_retry 가 true 입니다.
- 건너뛴 leg 은 앞 leg 을 봅니다
앞 leg 이 FAILED 면 그것부터 해결합니다.앞 leg 이 SUCCEEDED 인데 legs[].result.options 가 비어 있으면 조건에 맞는 고지를 찾지 못한 것이니 notice_year_month 와 차수를 확인합니다(아래 예시).조건에 맞는 고지가 여럿이면 요약 leg 이 건너뛰어지고 후보가 error.data.candidates 에 담기며 보낼 인자 이름은 error.data.row_key_param 에 있습니다.검색 leg 의 result.options[].row_key 에서 하나를 골라 nhis_row_key 또는 nps_row_key 로 다시 보내세요. 이때도 idempotency_key 는 새 값으로 바꾸거나 빼세요.같은 키에 인자를 바꿔 보내면 조회를 시작하지 않고 거절됩니다.
- computed 를 확인합니다
null 이면 필요한 leg 이 하나라도 없는 것입니다. 그때는 받은 leg 의 결과를 직접 쓰세요(예: legs[].result.summary.health_notice_total).
검색 leg 이 고지를 하나도 찾지 못해도 그 leg 은 SUCCEEDED 입니다.결과의 options 가 비어 있고 error_code 에 이유가 적힙니다. 그 결과로 인자를 만들 요약 leg 이 건너뛰어집니다. 아래는 건강보험 고지를 찾지 못한 예시입니다.
{
"pack_run_id": "prun_01JC8YH8R0E1ABCDEFGHJKMN0P",
"composite": true,
"tool_name": "payroll_pack__premium_summary_v1__generate",
"provider": "payroll_pack",
"action": "payroll_pack.premium_summary_v1.generate",
"idempotency_key": "payroll-premium-2026-08",
"partial": true,
"legs": [
{
"leg_index": 0,
"provider": "nhis_edi",
"action": "nhis_edi.hr_payroll.premium_notices.search",
"label_ko": "건강보험 고지 검색",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN01",
"result": {
"success": false,
"row_count": 0,
"options": [],
"error_code": "PREMIUM_NOTICE_SEARCH_EMPTY",
"implementation_status": "premium_notice_search_empty"
},
"artifacts": [],
"error": null
},
{
"leg_index": 1,
"provider": "nps_edi",
"action": "nps_edi.b1.pension_notices.search",
"label_ko": "국민연금 고지 검색",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN02",
"result": {
"...": "nps_edi.b1.pension_notices.search 조회가 돌려주는 값입니다"
},
"artifacts": [],
"error": null
},
{
"leg_index": 2,
"provider": "nhis_edi",
"action": "nhis_edi.hr_payroll.health_premiums.summary",
"label_ko": "건강보험료 요약",
"status": "SKIPPED",
"job_id": null,
"result": null,
"artifacts": null,
"error": {
"code": -32004,
"message": "이 작업에 필요한 앞 단계 결과를 쓸 수 없습니다. 앞 단계 조회 결과를 확인한 뒤 다시 시도해 주세요.",
"data": {
"error_code": "COMPOSITE_DEPENDENCY_UNAVAILABLE",
"leg_index": 2,
"detail": "NHIS premium notice search returned no selectable options"
}
}
},
{
"leg_index": 3,
"provider": "nps_edi",
"action": "nps_edi.b1.national_pension_premiums.summary",
"label_ko": "국민연금보험료 요약",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN04",
"result": {
"...": "nps_edi.b1.national_pension_premiums.summary 조회가 돌려주는 값입니다"
},
"artifacts": [],
"error": null
}
],
"succeeded_count": 3,
"failed_count": 1,
"computed": null,
"computed_error": {
"reason": "dependency_missing",
"message": "nhis_edi nhis_edi.hr_payroll.health_premiums.summary did not produce a successful result"
},
"artifacts": []
}모든 leg 이 성공하지 못했을 때
성공한 leg 이 하나도 없으면 보고를 만들지 않고 호출이 오류로 끝납니다.검색 leg 이 모두 실패하면 요약 leg 은 건너뛰어지므로 모든 leg 이 성공하지 못한 것이 됩니다. 오류의 번호와 문장은 정의 순서로 첫 번째 leg 의 오류를 따르고, leg 별 원인은 오류 본문의 legs 에 그대로 있습니다.오류가 돌아오는 자리는 그 오류 번호가 정합니다. 시간 초과(-32003)와 작업 실패(-32004)는 result.isError 로, 그 밖의 번호(-32602 등)는 JSON-RPC error 로 옵니다.두 자리의 규칙은 MCP 오류 코드와 같습니다.
연동이 없을 때 - 조회를 제출하기 전에 막힙니다.검색 leg 이 각자 자기 기관의 연동이 없다는 오류로 실패하고 JSON-RPC error(번호 -32602)로 옵니다. 오류의 문장은 첫 번째 leg 기관의 것입니다.원인 코드와 연동 화면 주소는 error.data.legs[].error.data 의 error_code(PRIMARY_ACCOUNT_LINK_NOT_FOUND)와 link_url 입니다.제출하지 못한 leg 과 건너뛴 leg 은 job_id 가 null 입니다.
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "국민건강보험공단 EDI 연동 계정이 없습니다. https://console.xdata.kr/credentials?link=1&priority=nhis_edi 에서 인증서를 연결한 뒤 이 도구를 다시 호출하세요. 연결은 호출할 때마다 새로 확인하므로 따로 다시 시작할 필요가 없습니다.",
"data": {
"composite": true,
"tool_name": "payroll_pack__premium_summary_v1__generate",
"idempotency_key": "payroll-premium-2026-08",
"legs": [
{
"leg_index": 0,
"provider": "nhis_edi",
"action": "nhis_edi.hr_payroll.premium_notices.search",
"label_ko": "건강보험 고지 검색",
"status": "FAILED",
"job_id": null,
"result": null,
"artifacts": null,
"error": {
"code": -32602,
"message": "국민건강보험공단 EDI 연동 계정이 없습니다. https://console.xdata.kr/credentials?link=1&priority=nhis_edi 에서 인증서를 연결한 뒤 이 도구를 다시 호출하세요. 연결은 호출할 때마다 새로 확인하므로 따로 다시 시작할 필요가 없습니다.",
"data": {
"error_code": "PRIMARY_ACCOUNT_LINK_NOT_FOUND",
"link_url": "https://console.xdata.kr/credentials?link=1&priority=nhis_edi"
}
}
},
{
"leg_index": 1,
"provider": "nps_edi",
"action": "nps_edi.b1.pension_notices.search",
"label_ko": "국민연금 고지 검색",
"status": "FAILED",
"job_id": null,
"result": null,
"artifacts": null,
"error": {
"code": -32602,
"message": "국민연금공단 EDI 연동 계정이 없습니다. https://console.xdata.kr/credentials?link=1&priority=nps_edi 에서 인증서를 연결한 뒤 이 도구를 다시 호출하세요. 연결은 호출할 때마다 새로 확인하므로 따로 다시 시작할 필요가 없습니다.",
"data": {
"error_code": "PRIMARY_ACCOUNT_LINK_NOT_FOUND",
"link_url": "https://console.xdata.kr/credentials?link=1&priority=nps_edi"
}
}
},
{
"leg_index": 2,
"provider": "nhis_edi",
"action": "nhis_edi.hr_payroll.health_premiums.summary",
"label_ko": "건강보험료 요약",
"status": "SKIPPED",
"job_id": null,
"result": null,
"artifacts": null,
"error": {
"code": -32004,
"message": "이 작업에 필요한 앞 단계 결과를 쓸 수 없습니다. 앞 단계 조회 결과를 확인한 뒤 다시 시도해 주세요.",
"data": {
"error_code": "COMPOSITE_DEPENDENCY_UNAVAILABLE",
"leg_index": 2,
"detail": "nhis_edi nhis_edi.hr_payroll.premium_notices.search did not produce a successful result"
}
}
},
{
"leg_index": 3,
"provider": "nps_edi",
"action": "nps_edi.b1.national_pension_premiums.summary",
"label_ko": "국민연금보험료 요약",
"status": "SKIPPED",
"job_id": null,
"result": null,
"artifacts": null,
"error": {
"code": -32004,
"message": "이 작업에 필요한 앞 단계 결과를 쓸 수 없습니다. 앞 단계 조회 결과를 확인한 뒤 다시 시도해 주세요.",
"data": {
"error_code": "COMPOSITE_DEPENDENCY_UNAVAILABLE",
"leg_index": 3,
"detail": "nps_edi nps_edi.b1.pension_notices.search did not produce a successful result"
}
}
}
]
}
}
}작업을 시작한 뒤 모두 실패한 경우 - 예를 들어 어느 기관에도 로그인하지 않았을 때입니다.result.isError 가 true 이고 첫 번째 content 블록에 오류 문장이, 두 번째 블록에 legs 를 담은 JSON 이 문자열로 들어 있습니다.
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "세션이 만료됐거나 없습니다(오류 AUTH_REQUIRED). 같은 account_link_id 로 'nhis_edi__session__login' 을 먼저 불러 세션을 연 뒤 이 도구를 다시 부르세요."
},
{
"type": "text",
"text": "(아래 JSON 을 문자열로 담은 값)"
}
],
"isError": true
}
}두 번째 블록의 text 를 JSON 으로 풀면 아래와 같습니다.
{
"composite": true,
"tool_name": "payroll_pack__premium_summary_v1__generate",
"idempotency_key": "payroll-premium-2026-08",
"legs": [
{
"leg_index": 0,
"provider": "nhis_edi",
"action": "nhis_edi.hr_payroll.premium_notices.search",
"label_ko": "건강보험 고지 검색",
"status": "FAILED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN01",
"result": null,
"artifacts": null,
"error": {
"code": -32004,
"message": "세션이 만료됐거나 없습니다(오류 AUTH_REQUIRED). 같은 account_link_id 로 'nhis_edi__session__login' 을 먼저 불러 세션을 연 뒤 이 도구를 다시 부르세요.",
"data": {
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN01",
"error_code": "AUTH_REQUIRED",
"next_action": "nhis_edi__session__login",
"safe_to_retry": true
}
}
},
{
"leg_index": 1,
"provider": "nps_edi",
"action": "nps_edi.b1.pension_notices.search",
"label_ko": "국민연금 고지 검색",
"status": "FAILED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN02",
"result": null,
"artifacts": null,
"error": {
"code": -32004,
"message": "세션이 만료됐거나 없습니다(오류 AUTH_REQUIRED). 같은 account_link_id 로 'nps_edi__auth__npki_session__login' 을 먼저 불러 세션을 연 뒤 이 도구를 다시 부르세요.",
"data": {
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN02",
"error_code": "AUTH_REQUIRED",
"next_action": "nps_edi__auth__npki_session__login",
"safe_to_retry": true
}
}
},
{
"leg_index": 2,
"provider": "nhis_edi",
"action": "nhis_edi.hr_payroll.health_premiums.summary",
"label_ko": "건강보험료 요약",
"status": "SKIPPED",
"job_id": null,
"result": null,
"artifacts": null,
"error": {
"code": -32004,
"message": "이 작업에 필요한 앞 단계 결과를 쓸 수 없습니다. 앞 단계 조회 결과를 확인한 뒤 다시 시도해 주세요.",
"data": {
"error_code": "COMPOSITE_DEPENDENCY_UNAVAILABLE",
"leg_index": 2,
"detail": "nhis_edi nhis_edi.hr_payroll.premium_notices.search did not produce a successful result"
}
}
},
{
"leg_index": 3,
"provider": "nps_edi",
"action": "nps_edi.b1.national_pension_premiums.summary",
"label_ko": "국민연금보험료 요약",
"status": "SKIPPED",
"job_id": null,
"result": null,
"artifacts": null,
"error": {
"code": -32004,
"message": "이 작업에 필요한 앞 단계 결과를 쓸 수 없습니다. 앞 단계 조회 결과를 확인한 뒤 다시 시도해 주세요.",
"data": {
"error_code": "COMPOSITE_DEPENDENCY_UNAVAILABLE",
"leg_index": 3,
"detail": "nps_edi nps_edi.b1.pension_notices.search did not produce a successful result"
}
}
}
]
}시간 초과와 계산 실패
leg 하나는 제출한 뒤 최대 60초까지 기다리고, 호출 전체는 최대 90초 안에 끝납니다.요약과 상세 leg 은 검색 leg 이 끝난 뒤에 이어지므로 호출 하나가 수십 초 걸릴 수 있습니다(2026-09-20 운영 실측에서 고용보험 요약 약 24초, 보험료 요약 약 48초). 클라이언트의 대기 시간을 호출 전체 한도보다 길게 두세요.60초 안에 끝나지 않은 leg 은 그 leg 만 시간 초과로 실패하고 나머지 결과는 그대로 옵니다. 그 leg 의 error.code 는 -32003 이고 error.data 에 다음 칸이 옵니다.다른 도구의 시간 초과와 같은 모양입니다. 이 도구의 조회는 모두 읽기라 다시 불러도 됩니다.
| error.data 의 칸 | 뜻 |
|---|---|
| job_id | 시간 안에 끝나지 않은 작업의 아이디입니다. 작업은 서버에서 계속 돌 수 있습니다. |
| timeout_seconds | 이 조회를 기다린 한도(초)입니다. 보통 60 이고, 호출 전체 마감(90초)이 가까웠으면 남은 시간이라 더 작습니다(소수일 수 있습니다). |
| safe_to_retry | 다시 불러도 되는지 알려 줍니다. 이 도구의 조회는 모두 읽기라 true 입니다. |
| status_url | 그 작업의 결과를 GET 으로 읽을 수 있는 주소입니다(REST 로 연동한 서비스에서 같은 API 키를 씁니다). |
| result_tool_call | 그 작업의 결과를 읽는 MCP 도구 호출입니다(name 과 arguments 를 그대로 부르면 됩니다). 작업을 다시 돌리지 않고 사용 토큰도 쓰지 않으며, 아직 돌고 있으면 잠시 기다렸다가 알려 줍니다. |
{
"code": -32003,
"message": "요청이 60초 안에 끝나지 않았습니다(작업 번호 job_01JC8YH8R0E1ABCDEFGHJKMN01). 서버가 바쁠 수 있습니다. 결과는 xdata__job__result 도구에 이 job_id 를 넣어 읽을 수 있습니다(작업을 다시 돌리지 않고 사용 토큰도 쓰지 않습니다). 같은 API 키로 연동한 서비스는 GET https://api.xdata.kr/v1/jobs/job_01JC8YH8R0E1ABCDEFGHJKMN01 로도 읽을 수 있습니다. 잠시 뒤 다시 시도하거나, 요청 범위를 줄일 수 있으면 줄이세요.",
"data": {
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN01",
"timeout_seconds": 60,
"safe_to_retry": true,
"status_url": "https://api.xdata.kr/v1/jobs/job_01JC8YH8R0E1ABCDEFGHJKMN01",
"result_tool_call": {
"name": "xdata__job__result",
"arguments": {
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN01"
}
}
}
}호출 전체 마감(90초)에 걸리면 두 모양 중 하나로 옵니다.leg 을 기다리던 중이면 위와 같은 모양이고, error.data 에 error_code 가 없으며 timeout_seconds 는 남은 시간이라 60 보다 작을 수 있습니다.leg 을 제출하거나 기다리기 시작하기 전에 마감이 이미 지났으면 그 leg 의 error.data.error_code 가 COMPOSITE_TIMEOUT 이고 error.data 에 다음 칸이 옵니다.앞 leg 이 늦어져 뒤 leg 을 시작하지 못하는 경우가 이렇습니다. 두 모양 모두 error.message 는 사람이 읽는 한국어 문장입니다. 두 모양을 가릴 때는 문장이 아니라 error.data.error_code 를 보세요.
| error.data 의 칸 | 뜻 |
|---|---|
| error_code | 항상 COMPOSITE_TIMEOUT 입니다. 이 값으로 호출 전체 마감을 가려냅니다. |
| timeout_seconds | 호출 전체 대기 한도(초)입니다. |
| phase | 마감이 된 단계의 이름입니다(진단용 - 분기에 쓰지 않습니다). |
| job_id | 제출한 작업의 아이디입니다. 제출하지 못했으면 이 칸이 없습니다. |
| result_tool_call | 그 작업의 결과를 읽는 MCP 도구 호출입니다(name 과 arguments 를 그대로 부르면 됩니다). 작업을 다시 돌리지 않고 사용 토큰도 쓰지 않습니다. job_id 가 있을 때만 옵니다. |
| submitted | 그 조회의 작업을 이미 제출했는지 알려 줍니다. true 면 job_id 가 함께 옵니다. |
성공한 leg 이 하나라도 있으면 결과를 합쳐 computed 를 만듭니다.이 계산이 실패하면 computed 는 null 이 되고 이유가 computed_error.reason 에 실립니다. 이유에 따라 보고가 그대로 오기도 하고 오류로 끝나기도 합니다.아래 표의 「보고가 오고 partial」 은 output_formats 를 넣은 호출에도 같습니다. 다만 computed 가 null 이면 엑셀 파일은 만들어지지 않습니다(「엑셀 파일을 청했는데 금액 표를 만들 수 없을 때」).
| computed_error.reason | 결과 | 뜻 |
|---|---|---|
| deadline_exhausted | 보고가 오고 partial | 호출 전체 마감 시간이 다 돼 계산을 건너뛰었습니다. 받은 자료는 legs 에 있고 봉투는 partial 로 옵니다(computed 는 null). |
| dependency_missing | 보고가 오고 partial | 계산에 필요한 leg 자료가 없습니다. 받은 자료는 legs 에 있고 봉투는 partial 로 옵니다. |
| validation_failed | 오류로 끝남 | 계산 중 값 검증에서 실패했습니다(우리 쪽 결함). 봉투를 주지 않고 오류로 끝나며 받은 leg 결과는 오류의 data 에 실립니다. |
| builder_raised | 오류로 끝남 | 계산 중 예외가 났습니다(우리 쪽 결함). 봉투를 주지 않고 오류로 끝나며 받은 leg 결과는 오류의 data 에 실립니다. |
엑셀 파일을 청했는데 금액 표를 만들 수 없을 때
output_formats: ["xlsx"] 를 넣었는데 금액 표(computed.deduction_rows)를 만들 수 없으면 엑셀 파일은 만들어지지 않지만 호출은 오류가 아니라 성공으로 끝납니다.응답의 artifacts 는 빈 배열이고 artifacts_note 에 엑셀 파일을 만들지 못한 이유가 실립니다(일부 조회가 끝나지 않아 파일을 만들 수 없었고 모든 조회가 끝난 뒤 다시 요청하면 파일이 만들어진다는 안내입니다).문장은 바뀔 수 있으니 분기에는 artifacts 가 비었는지와 partial 을 쓰세요.금액 표를 만들 수 없는 때는 computed 가 null 일 때, 곧 계산에 필요한 leg 자료가 없거나(dependency_missing - leg 이 FAILED 이거나 SKIPPED 이거나, 성공한 leg 의 결과에 계산할 금액이 빠졌을 때) 호출 전체 마감으로 계산을 건너뛴(deadline_exhausted) 때입니다.받은 자료는 버리지 않고 같은 JSON 에 legs, partial, computed_error 와 함께 그대로 담깁니다.
모든 조회가 끝나도록 원인을 해결한 뒤 새 idempotency_key 로 다시 부르면 파일이 만들어집니다.같은 키로 다시 부르면 이미 낸 조회의 결과를 다시 읽어 올 뿐 엑셀 파일은 만들어지지 않습니다.원인은 legs 의 FAILED 와 SKIPPED 그리고 computed_error 에서 찾습니다(호출 전체 마감 때문이면 조건은 그대로 두고 새 키로 부르면 됩니다).파일이 필요 없는 호출이면 output_formats 를 빼고 새 idempotency_key 로 부르세요. 받는 금액 표와 partial 은 같고 파일만 없습니다. 새 작업이라 사용 토큰을 세는 환경에서는 사용 토큰을 다시 씁니다.
조회와 계산이 끝났는데 파일을 만들거나 저장하지 못했을 때는 호출이 오류로 끝납니다.이 오류는 작업 실패 갈래라 result.isError 로 오고 오류 번호는 응답에 실리지 않습니다.두 번째 content 블록의 JSON 에서 error_code 는 INTERNAL_ERROR, safe_to_retry 는 false 이고 문장은 「서버 내부 오류로 작업을 마치지 못했습니다」 로 시작합니다.이때는 computed 가 null 이 아니고 금액 표가 함께 담겨 있으니 금액 표는 그대로 쓰고, 파일이 필요하면 잠시 뒤 새 idempotency_key 로 다시 부르세요.파일 저장소가 준비되지 않았을 때는 조회를 시작하기 전에 「문서 파일 저장소가 준비되지 않아 작업을 시작하지 않았습니다」 로 시작하는 오류로 끝나며 legs 도 없고 사용 토큰도 쓰지 않습니다.
같은 요청을 다시 보낼 때
arguments 에 idempotency_key 를 문자열로 넣으면 그 값이 이 실행의 멱등 키가 됩니다(앞뒤 공백은 지우고 128자까지 씁니다).같은 키와 같은 조회 조건으로 다시 부르면 새 작업을 만들지 않고 이전 작업을 그대로 돌려받아 같은 결과가 옵니다. 이때 각 leg 의 job_id 가 이전 호출과 같습니다.이전 작업이 실패했다면 실패한 결과가 그대로 오고, 아직 진행 중이면 그 작업을 이어서 기다립니다. 원인(연동, 로그인, 인자)을 고친 뒤 다시 시도할 때는 새 키를 쓰거나 키를 빼세요.연동이 없거나 대표 연동이 정해지지 않아(MULTIPLE_ACTIVE_ACCOUNT_LINKS) 조회를 하나도 제출하지 못하고 끝난 호출도 마찬가지입니다.고친 뒤 같은 키로 다시 부르면 조회가 새로 제출되어 사용 토큰을 쓰는데도, 조회가 성공해도 결과 대신 이전 오류가 옵니다.
같은 키로 조회 조건(인자)을 바꿔 부르면 조회를 하나도 시작하지 않고 호출이 JSON-RPC error(번호 -32602)로 끝납니다.오류의 data.error_code 는 IDEMPOTENCY_KEY_CONFLICT 이고 leg 별 결과(legs)는 없습니다.같은 요청을 다시 보내는 것이면 처음 보낸 인자 그대로 보내고, 새 요청이면 idempotency_key 를 새 값으로 바꾸거나 넣지 마세요(넣지 않으면 서버가 만듭니다).오류의 safe_to_retry 는 false 입니다 - 같은 인자로 다시 불러도 같은 오류가 납니다.
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "같은 idempotency_key 로 내용이 다른 요청이 이미 들어왔습니다. 같은 요청을 다시 보내는 것이면 처음 보낸 인자 그대로 보내고, 새 요청이면 idempotency_key 를 새 값으로 바꾸거나 아예 넣지 마세요(넣지 않으면 서버가 만듭니다).",
"data": {
"error_code": "IDEMPOTENCY_KEY_CONFLICT",
"safe_to_retry": false
}
}
}인자는 같은데 서버가 고른 기관 연동이 바뀌어 검색 leg 의 요청 내용이 달라진 경우에는 그 leg 이 제출에서 같은 오류로 실패합니다.검색 leg 이 모두 이렇게 실패하면 요약 leg 은 건너뛰어지고 호출이 같은 자리로 끝나며 leg 별 오류는 error.data.legs[] 에 있습니다.
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "같은 idempotency_key 로 내용이 다른 요청이 이미 들어왔습니다. 같은 요청을 다시 보내는 것이면 처음 보낸 인자 그대로 보내고, 새 요청이면 idempotency_key 를 새 값으로 바꾸거나 아예 넣지 마세요(넣지 않으면 서버가 만듭니다).",
"data": {
"composite": true,
"tool_name": "payroll_pack__premium_summary_v1__generate",
"idempotency_key": "payroll-premium-2026-08",
"legs": [
{
"leg_index": 0,
"provider": "nhis_edi",
"action": "nhis_edi.hr_payroll.premium_notices.search",
"label_ko": "건강보험 고지 검색",
"status": "FAILED",
"job_id": null,
"result": null,
"artifacts": null,
"error": {
"code": -32602,
"message": "같은 idempotency_key 로 내용이 다른 요청이 이미 들어왔습니다. 같은 요청을 다시 보내는 것이면 처음 보낸 인자 그대로 보내고, 새 요청이면 idempotency_key 를 새 값으로 바꾸거나 아예 넣지 마세요(넣지 않으면 서버가 만듭니다).",
"data": {
"error_code": "IDEMPOTENCY_KEY_CONFLICT",
"safe_to_retry": false
}
}
},
{
"leg_index": 1,
"provider": "nps_edi",
"action": "nps_edi.b1.pension_notices.search",
"label_ko": "국민연금 고지 검색",
"status": "FAILED",
"job_id": null,
"result": null,
"artifacts": null,
"error": {
"code": -32602,
"message": "같은 idempotency_key 로 내용이 다른 요청이 이미 들어왔습니다. 같은 요청을 다시 보내는 것이면 처음 보낸 인자 그대로 보내고, 새 요청이면 idempotency_key 를 새 값으로 바꾸거나 아예 넣지 마세요(넣지 않으면 서버가 만듭니다).",
"data": {
"error_code": "IDEMPOTENCY_KEY_CONFLICT",
"safe_to_retry": false
}
}
},
{
"leg_index": 2,
"provider": "nhis_edi",
"action": "nhis_edi.hr_payroll.health_premiums.summary",
"label_ko": "건강보험료 요약",
"status": "SKIPPED",
"job_id": null,
"result": null,
"artifacts": null,
"error": {
"code": -32004,
"message": "이 작업에 필요한 앞 단계 결과를 쓸 수 없습니다. 앞 단계 조회 결과를 확인한 뒤 다시 시도해 주세요.",
"data": {
"error_code": "COMPOSITE_DEPENDENCY_UNAVAILABLE",
"leg_index": 2,
"detail": "nhis_edi nhis_edi.hr_payroll.premium_notices.search did not produce a successful result"
}
}
},
{
"leg_index": 3,
"provider": "nps_edi",
"action": "nps_edi.b1.national_pension_premiums.summary",
"label_ko": "국민연금보험료 요약",
"status": "SKIPPED",
"job_id": null,
"result": null,
"artifacts": null,
"error": {
"code": -32004,
"message": "이 작업에 필요한 앞 단계 결과를 쓸 수 없습니다. 앞 단계 조회 결과를 확인한 뒤 다시 시도해 주세요.",
"data": {
"error_code": "COMPOSITE_DEPENDENCY_UNAVAILABLE",
"leg_index": 3,
"detail": "nps_edi nps_edi.b1.pension_notices.search did not produce a successful result"
}
}
}
]
}
}
}키를 넣지 않으면 서버가 호출마다 새 키를 만들어 매번 새 작업으로 처리합니다. 응답의 idempotency_key 에는 실제로 쓴 키가 실립니다.
사용 토큰과 진행 표시
사용 토큰을 세는 환경(데모, 그리고 하루 한도가 정해진 요금제의 정식 환경)에서는 이 도구를 한 번 부를 때 새 작업이 만들어지면 사용 토큰을 씁니다.leg 이 여럿이어도 한 번에 씁니다. 남은 양은 응답의 remaining 으로 확인합니다. 새 작업이 만들어지지 않은 호출은 세지 않습니다(제출 전에 모두 막혔거나 같은 멱등 키로 이전 작업을 돌려받은 경우).Accept: text/event-stream 으로 부르면 leg 마다 진행 상황을 이벤트로 받습니다(MCP 연동 개요의 「전송 방식」).
결과를 코드로 읽기
const response = await fetch(process.env.MCP_URL, {
method: 'POST',
headers: {
Authorization: 'Basic ' + Buffer.from(`${process.env.XBOSS_CLIENT_ID}:${process.env.XBOSS_CLIENT_SECRET}`).toString('base64'),
'Content-Type': 'application/json',
},
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'tools/call',
params: {
name: 'payroll_pack__premium_summary_v1__generate',
arguments: {"notice_year_month":"202608","installment_round":1},
},
}),
});
const rpc = await response.json();
if (rpc.error) {
// 요청 단계에서 막혔습니다(연동 없음, 형식 오류, 한도 등). 모든 leg 이 막힌 경우 leg 별 원인은 rpc.error.data.legs 에 있습니다.
throw new Error(`${rpc.error.code} ${rpc.error.message}`);
}
const { content, isError } = rpc.result;
if (isError) {
// 작업을 시작한 뒤 모두 실패했습니다. 두 번째 블록의 JSON 에 leg 별 오류(legs)가 담겨 있습니다.
const detail = content[1] ? JSON.parse(content[1].text) : {};
throw new Error(content[0].text + ' ' + JSON.stringify(detail.legs ?? []));
}
const report = JSON.parse(content[0].text);
if (report.partial) {
// 성공하지 못한 leg 이 있습니다. 로그인 도구 이름은 error.data.next_action 에 있습니다(세션이 없거나 만료된 기관).
const notReady = report.legs
.filter((leg) => leg.status !== 'SUCCEEDED')
.map((leg) => ({ action: leg.action, status: leg.status, code: leg.error?.data?.error_code, login: leg.error?.data?.next_action }));
console.warn('성공하지 못한 leg:', notReady);
}
if (report.computed) {
console.log(report.computed.combined_insurance_premium_won);
} else {
// 계산에 필요한 leg 이 없습니다. 받은 leg 의 결과는 report.legs 에 그대로 있습니다.
console.log(report.computed_error?.reason);
}
// output_formats 로 xlsx 를 청했을 때만 artifacts 에 파일이 있습니다. 링크가 만료되면 pack_run_id 로 xdata__job__result 를 부릅니다.
for (const file of report.artifacts ?? []) {
console.log(file.name, file.download_url);
}import base64
import json
import os
import requests
auth = base64.b64encode(
f"{os.environ['XBOSS_CLIENT_ID']}:{os.environ['XBOSS_CLIENT_SECRET']}".encode()
).decode()
response = requests.post(
os.environ["MCP_URL"],
headers={"Authorization": f"Basic {auth}", "Content-Type": "application/json"},
json={
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "payroll_pack__premium_summary_v1__generate",
"arguments": {"notice_year_month":"202608","installment_round":1},
},
},
timeout=120,
)
rpc = response.json()
if "error" in rpc:
# 요청 단계에서 막혔습니다(연동 없음, 형식 오류, 한도 등). 모든 leg 이 막힌 경우 leg 별 원인은 rpc["error"]["data"]["legs"] 에 있습니다.
raise RuntimeError(f"{rpc['error']['code']} {rpc['error']['message']}")
content = rpc["result"]["content"]
if rpc["result"]["isError"]:
# 작업을 시작한 뒤 모두 실패했습니다. 두 번째 블록의 JSON 에 leg 별 오류(legs)가 담겨 있습니다.
detail = json.loads(content[1]["text"]) if len(content) > 1 else {}
raise RuntimeError(content[0]["text"] + " " + json.dumps(detail.get("legs", []), ensure_ascii=False))
report = json.loads(content[0]["text"])
if report["partial"]:
# 성공하지 못한 leg 이 있습니다. 로그인 도구 이름은 error.data.next_action 에 있습니다(세션이 없거나 만료된 기관).
not_ready = [
{
"action": leg["action"],
"status": leg["status"],
"code": ((leg.get("error") or {}).get("data") or {}).get("error_code"),
"login": ((leg.get("error") or {}).get("data") or {}).get("next_action"),
}
for leg in report["legs"]
if leg["status"] != "SUCCEEDED"
]
print("성공하지 못한 leg:", not_ready)
computed = report.get("computed")
if computed:
print(computed["combined_insurance_premium_won"])
else:
# 계산에 필요한 leg 이 없습니다. 받은 leg 의 결과는 report["legs"] 에 그대로 있습니다.
print((report.get("computed_error") or {}).get("reason"))
# output_formats 로 xlsx 를 청했을 때만 artifacts 에 파일이 있습니다. 링크가 만료되면 pack_run_id 로 xdata__job__result 를 부릅니다.
for file in report.get("artifacts") or []:
print(file.get("name"), file.get("download_url"))