외부 API를 MCP 도구로 만들기 (셀프서비스)
외부 REST/RSS API를 별도 수집기 개발이나 서버 준비 없이 GenD에서 수집하고, 그 결과를 곧바로 MCP 도구로 노출하는 절차입니다. 화면은 실제 prod(gend.genon.ai)에서 법제처 국가법령정보 OPEN API를 붙이며 캡처했습니다.
이 튜토리얼을 마치면 AI 에이전트(Claude, 사내 제나 등)가 query_law 같은 도구로 해당 데이터를 조회할 수 있습니다.
무엇이 필요하고, 무엇이 필요 없나
| 필요 없음 | 필요함 |
|---|---|
| VM·서버 준비 | GenD 계정 (워크스페이스 소속 필수) |
| 수집기 코드 개발 | analyst 이상 역할 |
| 배포 파이프라인 구성 | 대상 API의 응답 구조 파악 (JSON/XML 경로) |
| 스케줄러 직접 운영 | API 키 (해당 API가 요구할 때만) |
상단바에 워크스페이스 이름(예: "금융 인텔리전스")이 보여야 합니다. "워크스페이스 없음"이면 이후 단계가 에러 없이 조용히 무시됩니다(등록 버튼을 눌러도 목록이 비어 있음, #2465). 관리자에게 워크스페이스 배정을 요청하고, 배정 후에는 재로그인하세요(역할·그룹은 JWT에 담기므로 기존 세션에는 반영되지 않습니다).
1. 대상 API의 응답 구조 파악
가장 먼저 할 일은 레코드 배열이 응답 어디에 있는지 확인하는 것입니다. 이 값(records_path)을 잘못 잡으면 수집이 0건으로 끝납니다.
curl -s "https://www.law.go.kr/DRF/lawSearch.do?OC=test&target=law&type=JSON&query=%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4&display=3" | head -c 400
{"LawSearch": {
"law": [
{ "법령일련번호": "117412", "법령명한글": "개인정보 단체소송규칙", ... },
루트가 LawSearch → 배열이 law 이므로 records_path = $.LawSearch.law 입니다. (OpenDART는 $.list, 다른 API는 또 다릅니다 — API마다 직접 확인해야 합니다.)
위 %EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4 는 개인정보 입니다. 비-ASCII를 URL에 그대로 넣으면 클라이언트/프록시 구간에 따라 인코딩이 어긋날 수 있습니다.
2. 파이프라인 만들기
파이프라인 스튜디오(/pipeline-studio) → 새 파이프라인 → 이름 입력 → 생성.

생성하면 URL이 발급되지만 저장을 누르기 전에는 서버에 아무것도 없습니다. 이 상태에서 새로고침하면 작업이 사라집니다(#2467). 노드를 배치한 뒤 저장을 먼저 누르세요.
좌측 팔레트에서 두 노드를 캔버스로 끌어다 놓고 연결합니다.
- 소스 (REST/RSS API) —
rest_source - 싱크 (Intel Bronze + MCP) —
intel_bronze_sink

3. 소스 노드 설정 + 드라이런
소스 노드를 클릭하면 우측 설정 패널이 열립니다.
| 필드 | 값 |
|---|---|
| URL | https://www.law.go.kr/DRF/lawSearch.do?OC=test&target=law&type=JSON&query=%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4&display=20 |
| method / 응답 포맷 | GET / JSON |
| records_path | $.LawSearch.law |
설정 후 ▶ 드라이런을 누르면 실제로 API를 한 번 호출해 건수와 샘플을 보여줍니다. 적재는 일어나지 않으므로 부작용 없이 몇 번이든 확인할 수 있습니다.

건수가 잡히면 records_path가 맞은 것입니다. 0건이면 경로가 틀렸다는 뜻이니 1단계로 돌아가 응답 구조를 다시 보세요.
4. 싱크 노드 설정
싱크 노드에서 적재 위치와 MCP 도구 이름을 정합니다.
| 필드 | 값 | 설명 |
|---|---|---|
| source | law_go_kr | 출처 식별자 |
| domain | law | 적재 테이블이 iceberg.bronze.intel_law_raw 가 됩니다 |
| external_id path | $.법령일련번호 | 중복 판정 키. 한글 키도 그대로 동작합니다 |
| MCP 도구 | query_law + 설명 | 적재 후 자동 등록될 조회 도구 |
5. 배포 → 실행
저장 → 배포 → 실행 순으로 누릅니다.
아래는 analyst 역할(관리자 아님)로 로그인한 화면입니다 — 배포·실행 버튼이 모두 활성입니다.

배포 권한은 역할이 아니라 그래프 내용으로 결정됩니다(#2462). 이 튜토리얼처럼 코드 노드가 없는 그래프는 analyst도 직접 배포할 수 있습니다. 다만 인라인 Shell/Python 코드 노드를 넣으면 관리자 권한이 필요합니다 — 그 코드를 승인된 라이브러리 스텝으로 교체하면 다시 직접 배포할 수 있습니다(버튼에 마우스를 올리면 어느 쪽인지 툴팁으로 알려줍니다).
실행이 끝나면 실행 탭에서 해당 실행을 펼치면 적재 결과 패널이 표시됩니다.

재실행으로 전부 중복 스킵된 경우 "신규 없음 — 전부 중복 스킵" 으로 표시됩니다 — 실패가 아닙니다:

"샘플 N건" 을 펼치면 적재된 레코드의 payload 원문을 바로 확인할 수 있습니다:

SQL 로 직접 확인하려면:
SELECT count(*), max(fetched_at) FROM iceberg.bronze.intel_law_raw;
6. MCP 도구로 조회
적재와 동시에 워크스페이스 MCP 도구가 자동 등록됩니다. 별도 작업이 필요 없습니다.
TOKEN=... # 워크스페이스 멤버의 JWT 또는 M2M 토큰
# 도구 목록에 query_law 가 있는지
curl -sS -X POST https://gend.genon.ai/mcp \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
# 실제 조회
curl -sS -X POST https://gend.genon.ai/mcp \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
"params":{"name":"query_law","arguments":{"limit":3}}}'
응답은 external_id | payload | fetched_at 표이며 payload에 법령명한글·시행일자 등 원본 필드가 그대로 들어 있습니다.
이 시점부터 Claude Desktop·Cursor·사내 에이전트 등 MCP 클라이언트에서 이 도구를 바로 쓸 수 있습니다 — 연결 방법은 MCP 클라이언트 연동을 참조하세요.
Bronze는 전 워크스페이스가 공유하는 물리 테이블이고 행은 workspace_id로 구분됩니다. 자동 등록되는 MCP 도구에는 서버가 강제하는 ws-펜스가 걸려 자기 워크스페이스 행만 조회됩니다. 호출자가 이 펜스를 바꿀 수 없고, 펜스 없는 공유 테이블 도구는 dispatch에서 거부됩니다.
API 키가 필요한 경우
법제처 API는 공개 파라미터(OC)만 쓰므로 위 절차로 충분합니다. 헤더에 API 키를 넣어야 하는 API라면 두 가지가 추가됩니다.
- 워크스페이스 시크릿 등록 — 소스 노드 설정의 "워크스페이스 시크릿"에서 키를 등록하고 헤더에서
{{secret.이름}}으로 참조합니다. 키 평문은 노드나 Dagster가 수신하지 않고 egress-proxy가 서버사이드에서 주입합니다. - egress 호스트 등록 — 시크릿을 쓰는 요청은 대상 호스트가 워크스페이스 allowlist에 있어야 통과합니다. Pipeline Studio → 외부 egress 호스트 탭에서 등록하며, 이 탭은 워크스페이스 admin 전용입니다. 자세한 내용은 egress 호스트 가이드를 참조하세요.
공개 API(이 튜토리얼처럼)는 SSRF 필터만 통과하면 호출됩니다. allowlist는 시크릿이 임의 호스트로 새는 것을 막는 장치라, 시크릿을 안 쓰면 해당하지 않습니다.
증분 수집 / 페이지네이션
매번 전체를 받지 않고 신규분만 가져오려면 소스 노드의 고급 설정을 씁니다.
| 설정 | 용도 |
|---|---|
| 페이지네이션 | page / offset / cursor / link_header 전략 선택 → 필요한 칸만 표시 |
| 커서 추출 path | 응답에서 다음 커서를 뽑을 JSONPath |
| 커서 키 이름 | 워터마크 저장·비교 키 |
| 커서 비교 방식 | 자동(숫자/날짜 감지) / 숫자 / 날짜 / 문자 |
워터마크는 단조 전진만 하며(후퇴·중복 전진 거부), 업스트림이 429/5xx를 반환하면 노드가 실패하고 워터마크는 전진하지 않습니다 — 데이터 구멍을 방지하기 위해서입니다.
목록 + 상세 (detail fan-out)
법제처·공공데이터포털처럼 목록 API는 메타데이터만 주고, 본문은 건별 상세 API를 따로 호출해야 하는 구조라면 소스 노드 고급 설정의 "상세 fan-out" 을 씁니다. 목록의 각 레코드마다 상세 URL 을 호출해 응답을 payload 에 병합합니다.
{
"url_template": "https://www.law.go.kr/DRF/lawService.do?OC=test&target=law&MST={{record.법령일련번호}}&type=JSON",
"extract_path": "$.법령",
"max_calls": 50,
"delay_ms": 200
}
| 키 | 용도 |
|---|---|
url_template | 상세 URL — {{record.<필드>}} 로 목록 레코드의 필드를 참조 (값은 자동 percent-encode) |
extract_path | 상세 응답에서 병합할 부분의 JSONPath (생략 시 응답 전체) |
merge_key | payload 에 붙일 키 이름 (기본 detail) |
max_calls | 실행당 상세 호출 상한 (기본 50, 서버 상한 200) — 초과분은 메타만 적재 |
delay_ms | 호출 간 지연 (기본 200ms, 하한 50ms) — 외부 API rate limit 보호 |
동작 규칙: 상세 호출도 목록과 같은 헤더/시크릿/egress 경로를 그대로 씁니다. 레코드 단위 실패 처리 방식은 증분 여부에 따라 다릅니다 —
- 비증분(커서 미사용):
__detail_error__마커를 남기고 계속 진행합니다. 다음 실행이 전체를 재조회하므로 content_hash 가 달라 자동 재시도되며, 이때 같은external_id로 실패행·성공행이 모두 남습니다(Bronze 는 append-only 스냅샷 — 소비 측은fetched_at최신 행을 쓰세요). - 증분(
cursor_extract병용): 부분 실패/스킵이 하나라도 있으면 실행 전체가 실패하고 워터마크가 전진하지 않습니다 — 커서가 전진하면 실패 레코드가 다시는 목록에 나타나지 않아 영구 결손이 되기 때문입니다. 다음 실행이 같은 구간을 재시도합니다.
전면 실패는 항상 실행을 실패시킵니다. 상세 fan-out 단계에는 전체 10분의 실행 예산이 있고 초과분은 스킵됩니다. 드라이런은 첫 레코드 1건만 상세를 미리보기해 URL/경로 오류를 배포 전에 드러냅니다.
정기 실행은 파이프라인의 cron 스케줄로 설정합니다. 파이프라인 목록(또는 빌더 우상단)의 작업 메뉴 → 스케줄에서 6개 프리셋(5분/15분/매시/6시간/12시간/매일 02:00 UTC) 중 고르거나, 5필드 cron 을 직접 입력합니다(수동 = 스케줄 해제 포함 총 8개 옵션) — 비-admin(analyst)도 설정할 수 있습니다.

스케줄 다이얼로그의 "폴더 감시 (선택)" 를 펼치면 감시 경로·확장자 필터·"기존 파일 포함" 체크박스(기본 켜짐)를 설정할 수 있습니다:

소스 노드(s3_op)에 uri 가 설정된 그래프에는 감시를 걸 수 없습니다 — 저장 시 명확한 오류로 거부됩니다(파일은 감시 스캔이 공급하므로 uri 는 비워야 합니다):

폴더에 파일이 도착하면 다음 tick 에 파일당 실행이 하나씩 만들어지고, 실행 탭 카드에 파일명이 표시됩니다:

스케줄을 저장하면 크론 센서가 60초마다 만기를 평가해 자동 실행합니다. 아래는 실제 실행 탭입니다 — 수동 실행 1건에 이어 크론이 자동 트리거한 실행이 5분 간격으로 이어지며 성공하고 있습니다.

같은 데이터를 다시 가져와도 Bronze 적재가 content_hash 로 이미 적재된 레코드를 건너뜁니다. 실측: 수동 1회 + 크론 2회 실행 후에도 Bronze 는 10행/10 distinct — 중복 0. 신규 공포·개정분만 쌓입니다.
이전에 안내됐던 "인텔 파이프라인 스케줄 자동 실행 불가"(#2470) 제약은 해소됐습니다 (2026-07-21). 실행의 워크스페이스가 파이프라인 정의에서 파생되도록 수정돼, 위 화면처럼 크론 자동 실행이 정상 동작합니다.
POST /api/v1/ps/pipelines 생성 본문에 schedule_cron 을 넣어도 무시됩니다. 생성 후 PATCH /api/v1/ps/pipelines/{id} 로 별도 설정해야 합니다 ({"schedule_cron": "5m"}, null 이면 스케줄 해제). UI 의 스케줄 다이얼로그가 내부적으로 이 PATCH 를 호출합니다.
폴더 감시 (다중 파일 배치)
여기까지는 API 호출 1건 = 실행 1건이었습니다. S3 폴더에 여러 파일이 쌓이는 경우(예: 매일 새 리포트 PDF가 올라오는 inbox)라면 폴더 감시를 씁니다 — 파일이 늘어날 때마다 파일 하나당 실행 하나가 자동으로 생성됩니다.
s3_op 소스 그래프 — rest_source 는 해당 없음폴더 감시는 위에서 만든 것과 같은 rest_source(REST/RSS API) 그래프에는 설정할 수 없습니다. 그런 파이프라인은 앞의 증분 수집 / 페이지네이션 절의 커서/워터마크가 이미 "신규분만 가져오기"를 담당합니다. 폴더 감시는 소스 노드가 s3_op(S3 파일 pull)인 그래프 전용입니다.
소스 노드의 uri 는 비워 두세요 — 파일은 감시가 채웁니다. (uri 가 설정돼 있으면 저장 시 422 로 거부됩니다.)
설정 — 스케줄 다이얼로그 안에서
같은 작업 메뉴 → 스케줄 다이얼로그를 열면, cron 설정 아래에 "폴더 감시 (선택)" 섹션이 있습니다(기본은 접혀 있으며, 펼쳐서 입력합니다).
| 입력 | 값 예시 | 설명 |
|---|---|---|
| 폴더 감시 경로 | s3://bucket/inbox/ | s3:// 로 시작해야 합니다. 비워두면 감시하지 않습니다(기존 스케줄 동작에 영향 없음) |
| 확장자 필터 (콤마 구분) | .pdf,.docx | 비우면 모든 확장자 대상 |
| 기존 파일 포함 | 체크박스 (기본 켜짐) | 아래 "기존 파일 포함을 끄면" 참고 |
폴더 감시에는 별도의 스캔 주기가 없습니다 — 파이프라인의 cron 스케줄 자체가 스캔 주기입니다(스케줄이 없으면 감시도 동작하지 않습니다). uri_prefix 는 S3 prefix 매칭이라 하위 경로까지 전부 재귀로 포함됩니다 — inbox/2026/07/a.pdf 도 inbox/ 감시에 걸립니다.
폴더 감시가 지원하는 경로는 S3(prod=SeaweedFS)뿐입니다. SMB/SFTP/NFS 공유폴더 감시는 공용 수집기(관리형)의 컬렉터가 담당합니다. 필요하면 "SMB → S3 랜딩(관리형 수집) → PS 폴더 감시" 2단으로 조합하세요 — PS가 SMB를 직접 감시하는 기능은 이 스코프 밖입니다.
파일당 1개 실행 (fan-out)
크론이 만기될 때마다 폴더를 스캔해, 그때까지 처리하지 않은 파일마다 독립된 실행(run)이 하나씩 생성됩니다. 실행 탭의 run 카드에는 그 실행이 처리 중인 파일명이 표시되므로(전체 경로는 마우스를 올리면 확인), 폴더 전체가 아니라 파일 단위로 성공/실패를 모니터링할 수 있습니다 — 기존의 run → 노드 모니터링 화면을 그대로 재사용합니다.
tick당 처리 상한(cap) — 대량 백로그는 나눠 소화
한 번의 스캔(tick)에서 새로 실행으로 전환하는 파일 수에는 상한이 있습니다(기본 20개, 서버 설정 — 파이프라인별로 조정할 수 없습니다. 한 파이프라인의 대량 백로그가 Dagster 워커를 독점하는 것을 막기 위한 운영 안전장치입니다). 예를 들어 감시를 처음 켰을 때 폴더에 이미 파일 100개가 있다면, 한 번에 100개 실행이 동시에 뜨지 않고 tick마다 최대 20개씩 순차적으로 소화됩니다 — 남은 파일은 다음 tick에 이어서 처리됩니다.
중복 방지 — 2중 안전장치
같은 파일이 두 번 처리되지 않도록 두 겹으로 막습니다.
- 감시 대장(ledger) — 파이프라인마다 처리한(또는 처리 중인) 파일 키를 기록하며, 파이프라인·파일 키 조합에 유일성(UNIQUE) 제약이 걸려 있습니다. 센서 tick이 겹치거나 재시작돼도 같은 파일로 실행이 두 번 생기지 않습니다.
- 싱크 노드의 기존 중복 판정 — 설령 같은 내용이 다른 파일 키로 다시 들어오더라도, 그래프의 싱크 노드가 한 번 더 걸러냅니다.
intel_bronze_sink이면content_hash비교로 "신규 없음 — 전부 중복 스킵"으로 표시되고(위 5단계 참고), 벡터 싱크(builtin.vector)라면 동일document_id로 레코드가 갱신되는 재적재 멱등이 적용됩니다(Pipeline Studio QA 가이드 §4-3 참고).
감시 대장은 파일 키 단위로 "이미 처리했는가"만 기록합니다. 같은 S3 키에 새 내용을 덮어써도 다시 처리되지 않습니다. 재처리가 필요하면 새 파일 키(경로/이름)로 업로드하세요.
"기존 파일 포함"을 끄면 — 첫 스캔은 전부 skipped
기존 파일 포함 체크를 끄고 감시를 시작하면, 감시 대장이 비어 있는 첫 스캔에서 발견된 파일들은 실행을 만들지 않고 "skipped"로만 기록되고 지나갑니다. 그 이후 tick부터 새로 도착하는 파일만 처리됩니다 — "지금부터 쌓이는 것만 받고 싶을 때" 사용합니다.
관리형 승격 요청 — 전사 공용 데이터가 됐다면
셀프서비스로 수집하던 소스가 여러 워크스페이스/전사가 함께 쓰는 표준 데이터로 자리 잡으면, 관리자에게 공용 수집기 (관리형) 등록을 요청하세요. 관리형으로 승격되면:
- 수집 결과가 워크스페이스 격리된 Bronze 대신 전사 공용 테이블에 적재되고, 모든 사용자가 인텔리전스 메뉴에서 조회합니다.
- 스케줄·키 관리·장애 대응을 팀이 아니라 플랫폼 운영자가 담당합니다 (수집기 API 키 패널, 센서 모니터링 포함).
- 팀별 중복 수집(같은 API 를 워크스페이스마다 호출)이 사라집니다.
승격 요청 시 관리자에게 다음 정보를 전달하면 등록이 빠릅니다:
| 전달 정보 | 예시 |
|---|---|
| 소스 URL (목록/상세) | https://www.law.go.kr/DRF/lawSearch.do?... |
| 수집 주기 | 매일 02:00 UTC (또는 cron 0 2 * * *) |
| 파라미터 | records_path, 페이지네이션/커서 설정, 상세 fan-out 템플릿 등 현재 소스 노드 설정값 |
| 인증 방식 | 공개 / 헤더 API 키 (키 발급처 링크 포함) |
| 사용 팀·용도 | 어떤 워크스페이스들이 어떤 분석에 쓰는지 |
관리자는 사이드바 하단 ⚙ 관리 콘솔 → 데이터플레인 설정 → 공용 수집기 (관리형) (/admin/intel-sources) 에서 소스를 등록합니다. 단, 공용 수집기는 지원되는 collector 타입(dart/rss/ccxt 등 12종) 안에서 동작하므로, 임의 REST API 는 타입 추가(코드 작업)가 필요할 수 있습니다 — 이 경우 셀프서비스 파이프라인을 유지하면서 워크스페이스 멤버십으로 공유 범위를 넓히는 것이 대안입니다. 판단 기준은 데이터 수집, 어떤 기능으로? 의 "직접 조립(셀프서비스) vs 공용 수집기(관리형)" 절을 참조하세요.
잘 안 될 때
| 증상 | 원인 / 조치 |
|---|---|
| 드라이런 0건 | records_path 오류 — 1단계로 돌아가 응답 구조 재확인 |
| 등록 버튼을 눌러도 반응 없음 | 워크스페이스 미소속 — 상단바 확인 후 배정 요청·재로그인 |
| 배포 버튼 비활성 | 인라인 코드 노드 존재 → 승인된 라이브러리 스텝으로 교체하거나 관리자에게 요청 |
| 새로고침 후 "pipeline not found" | 저장 전 상태였음 — 노드 배치 후 저장 먼저 |
| 시크릿 참조 호출이 차단됨 | egress 호스트 allowlist에 대상 호스트 미등록 |
| 폴더 감시 저장 시 422 | 폴더 감시 경로가 s3:// 로 시작하지 않거나, 그래프에 s3_op 소스 노드가 없거나(rest_source 그래프에는 설정 불가), s3_op 노드의 uri 가 비어 있지 않음(감시는 파일을 스스로 채우므로 uri 를 비워야 함) |
관련 문서
- rest_source 노드 QA 가이드 — 필드별 상세 레퍼런스
- intel_bronze_sink QA 가이드 — 적재·MCP 등록 동작
- egress 호스트 QA 가이드 — 시크릿 사용 시 allowlist
- Pipeline Studio QA 가이드 — 스케줄 — cron 스케줄 다이얼로그 공통 동작(폴더 감시는 이 다이얼로그 안에 있음)
- MCP 클라이언트 연동 — Claude Desktop / Cursor / 서버 사이드 연동