본문으로 건너뛰기

외부 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마다 직접 확인해야 합니다.)

URL의 한글은 percent-encoding으로

%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. 소스 노드 설정 + 드라이런

소스 노드를 클릭하면 우측 설정 패널이 열립니다.

필드
URLhttps://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를 한 번 호출해 건수와 샘플을 보여줍니다. 적재는 일어나지 않으므로 부작용 없이 몇 번이든 확인할 수 있습니다.

소스 노드 설정과 드라이런 결과 — 13건 892ms, 개인정보 보호법 등 실제 법령 샘플

건수가 잡히면 records_path가 맞은 것입니다. 0건이면 경로가 틀렸다는 뜻이니 1단계로 돌아가 응답 구조를 다시 보세요.

4. 싱크 노드 설정

싱크 노드에서 적재 위치와 MCP 도구 이름을 정합니다.

필드설명
sourcelaw_go_kr출처 식별자
domainlaw적재 테이블이 iceberg.bronze.intel_law_raw 가 됩니다
external_id path$.법령일련번호중복 판정 키. 한글 키도 그대로 동작합니다
MCP 도구query_law + 설명적재 후 자동 등록될 조회 도구

5. 배포 → 실행

저장배포실행 순으로 누릅니다.

아래는 analyst 역할(관리자 아님)로 로그인한 화면입니다 — 배포·실행 버튼이 모두 활성입니다.

analyst 계정에서 배포·실행 버튼이 활성화된 파이프라인 빌더

배포 버튼이 비활성이라면

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

실행이 끝나면 실행 탭에서 해당 실행을 펼치면 적재 결과 패널이 표시됩니다.

적재 결과 패널 — Bronze 5행 적재 · 0건 스킵 → 테이블명 + MCP 도구 배지

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

전부 중복 스킵된 재실행 — 신규 없음 표시

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

샘플 3건 펼침 — 실제 적재 레코드 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라면 두 가지가 추가됩니다.

  1. 워크스페이스 시크릿 등록 — 소스 노드 설정의 "워크스페이스 시크릿"에서 키를 등록하고 헤더에서 {{secret.이름}} 으로 참조합니다. 키 평문은 노드나 Dagster가 수신하지 않고 egress-proxy가 서버사이드에서 주입합니다.
  2. egress 호스트 등록 — 시크릿을 쓰는 요청은 대상 호스트가 워크스페이스 allowlist에 있어야 통과합니다. Pipeline Studio → 외부 egress 호스트 탭에서 등록하며, 이 탭은 워크스페이스 admin 전용입니다. 자세한 내용은 egress 호스트 가이드를 참조하세요.
시크릿을 쓰지 않으면 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_keypayload 에 붙일 키 이름 (기본 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)도 설정할 수 있습니다.

작업 메뉴의 스케줄 설정 다이얼로그 — 프리셋 + 직접 cron 입력

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

스케줄 다이얼로그의 폴더 감시 입력 — 경로/확장자/기존 파일 포함

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

uri 가 설정된 그래프에 감시 저장 시 422 오류 토스트

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

실행 탭 — 파일당 run 1개씩, 카드에 파일명(report-2026-q1.txt 등) 표시

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

실행 탭 — 수동 실행에 이어 크론 자동 실행이 성공으로 기록됨

재실행·자동 반복은 중복을 만들지 않습니다

같은 데이터를 다시 가져와도 Bronze 적재가 content_hash 로 이미 적재된 레코드를 건너뜁니다. 실측: 수동 1회 + 크론 2회 실행 후에도 Bronze 는 10행/10 distinct — 중복 0. 신규 공포·개정분만 쌓입니다.

이전에 안내됐던 "인텔 파이프라인 스케줄 자동 실행 불가"(#2470) 제약은 해소됐습니다 (2026-07-21). 실행의 워크스페이스가 파이프라인 정의에서 파생되도록 수정돼, 위 화면처럼 크론 자동 실행이 정상 동작합니다.

API 자동화 시 — schedule_cron 은 PATCH 전용

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.pdfinbox/ 감시에 걸립니다.

S3 전용 — SMB/SFTP/NFS 공유폴더는 대상 아님

폴더 감시가 지원하는 경로는 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중 안전장치

같은 파일이 두 번 처리되지 않도록 두 겹으로 막습니다.

  1. 감시 대장(ledger) — 파이프라인마다 처리한(또는 처리 중인) 파일 키를 기록하며, 파이프라인·파일 키 조합에 유일성(UNIQUE) 제약이 걸려 있습니다. 센서 tick이 겹치거나 재시작돼도 같은 파일로 실행이 두 번 생기지 않습니다.
  2. 싱크 노드의 기존 중복 판정 — 설령 같은 내용이 다른 파일 키로 다시 들어오더라도, 그래프의 싱크 노드가 한 번 더 걸러냅니다. 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 를 비워야 함)

관련 문서