연결 커넥터가 없을 때 AI 에이전트를 위한 맞춤형 통합 구축 방법 (TypeScript)
요약
본 글은 기존 커넥터가 없는 사내 도구나 틈새 API와 AI 에이전트를 연결하는 실용적인 방법을 안내합니다. 단순히 일회성 코드를 작성하는 대신, 재사용 가능하고 타입 안전하며 인증 및 격리가 완벽하게 처리된 플러그인 스캐폴딩을 구축하는 과정을 다룹니다.
핵심 포인트
- 맞춤형 API 통합은 초기 80%보다 나머지 20%의 유지보수 작업이 중요합니다.
- OAuth 토큰 순환, 웹훅 서명 검증, 속도 제한 준수 등 프로덕션 레벨 관리가 핵심입니다.
- 오픈 소스 통합 프레임워크를 사용하면 공급업체 종속성을 피하고 재사용 가능한 구조를 얻을 수 있습니다.
에이전트가 아직 아무도 만들지 않은 앱과 통신해야 할 때
시간이 지나면, 에이전트는 여러분의 스택에 있는 어떤 것도 커넥터를 구축하지 못한 앱과 통신해야 할 필요가 생깁니다. 어쩌면 그것은 틈새 CRM(CRM), 내부 도구, 또는 아직 통합 플랫폼이 접근하지 못한 새로운 API일 수 있습니다. 이 지점에서 실제로 가능한 경로는 두 가지뿐입니다: 일회성 TypeScript 클라이언트를 직접 작성하거나, 오픈 소스 통합 프레임워크가 이미 제공하는 플러그인 스캐폴딩을 사용하여 다른 모든 플러그인이 사용하는 것과 동일한 구조를 얻는 것입니다.
이 게시물은 두 번째 경로를 처음부터 끝까지 안내합니다: 플러그인을 스캐폴딩하고, 에이전트가 오용할 수 없도록 모든 엔드포인트를 타이핑하며, 단 하나의 하드코딩된 비밀 없이 인증을 연결하고, 그 결과를 다시 기여하여 다음 사람이 동일한 API가 필요할 때 작업을 반복하지 않도록 합니다. 여기서는 SDK 디자인 이론을 원리부터 가르치려고 하지 않습니다. 이에 관한 책이 온통 있습니다. 여기서의 목표는 실용적인 형태입니다: 빠른 스크립트가 보통 건너뛰는, 프로덕션 통합에 필요한 것과 그 이유입니다.
직접 구축하는 것의 실제 비용
무엇이든 스캐폴딩하기 전에, 간단한 테스트를 해볼 가치가 있습니다: 이 통합은 제품이 한 번 필요로 하는 일회성 호출인가요, 아니면 제품이 서비스하는 모든 테넌트(tenant)에 걸쳐 타이핑되고 인증되며 유지 관리되어야 할 무언가인가요? 그 답변이 앞서 구조화하는 데 얼마나 많은 가치를 두어야 하는지를 바꿉니다.
모든 맞춤형 API 통합의 처음 80%는 보통 쉬운 부분입니다. 제공업체의 문서를 읽고, 소수의 엔드포인트를 매핑하며, 작동하는 호출을 얻는 것입니다. 나머지 20%가 진정한 엔지니어링 시간이 투입되는 곳입니다. 만료되기 전에 OAuth 토큰을 순환시키고(rotating), 들어오는 모든 이벤트의 웹훅 서명을 검증하고(verifying webhook signatures), 요청을 조용히 누락시키지 않으면서 속도 제한(rate limits)을 준수하며, 한 테넌트의 토큰이 다른 테넌트 호출로 절대 유출되지 않도록 다중 테넌트 자격 증명 격리(multi tenant credential isolation)를 구현하는 것입니다. 이 중 어느 것도 첫 번째 개념 증명(proof of concept)에서는 나타나지 않으며, 모든 것이 프로덕션에서 무언가 고장 날 때 주간에 나타납니다.
이것이 바로 실제 빌드 대 구매 통합 결정이며, 이것은 HTTP 호출을 작성할 수 있는지 여부보다는(물론 할 수 있습니다), 그 20%를 영구적으로 소유하고 싶은지 여부에 관한 문제입니다. 폐쇄형 플랫폼은 이러한 유지보수 작업을 당신의 부담에서 덜어주지만, 동시에 다음에 필요한 API가 아직 구축되지 않았다면 다른 사람의 로드맵을 기다려야 한다는 의미도 내포합니다.
오픈 소스 통합은 작업 자체를 완전히 제거하지 않으면서 그 계산 방식을 바꿉니다. 기존 플러그인 카탈로그(여기에서 살펴보기)를 탐색할 수 있으므로 공급업체에게 갇힐 염려가 없으며, 필요한 앱이 이미 다루어지지 않았다면 다른 통합들이 사용하는 것과 동일한 구조로 직접 스캐폴딩(scaffold) 할 수도 있습니다. 또한 빈 파일에서 시작하는 것도 아닙니다. 아래의 스캐폴딩, 타이핑 규칙(typing conventions), 인증 패턴(auth patterns)이 실제로 그 구조가 어떻게 생겼는지 보여줍니다.
한 번의 명령어로 플러그인 스캐폴딩하기
수동으로 구축된 통합은 보통 소수의 fetch 호출을 가진 단일 파일로 시작합니다. 이것은 두 번째 엔드포인트, 첫 번째 웹훅, 또는 두 번째 테넌트가 격리된 자격 증명을 필요로 하는 첫 순간까지는 작동하지만, 그 시점부터 당신은 모든 통합이 결국 수렴하는 동일한 TypeScript SDK 구조를 재구축하게 됩니다:
packages/<plugin-name>/
├── index.ts # 진입점(entry point), 인증 설정(auth setup)
├── client.ts # 인증 헤더가 포함된 HTTP 클라이언트
...
제너레이터는 수동으로 작업하는 대신 단 하나의 명령어로 동일한 레이아웃을 생성합니다:
pnpm run generate:plugin MyService
이것을 실행하면 단순히 빈 폴더를 만드는 것 이상의 작업을 수행합니다. 프레임워크의 핵심 상수(core constants)에 플러그인을 등록하여 즉시 가져와 사용할 수 있게 하고, API 키 또는 OAuth 2 흐름을 위한 기본 인증 패턴을 연결하며, 번들러 및 패키지 설정을 구성하여 작업 공간(workspace)이 이를 단순히 파일들의 느슨한 폴더가 아닌 실제 패키지로 인식하도록 합니다.
이 구조 자체는 어떤 API를 래핑하든 상관없이 실제로 작동하는 작업을 수행합니다. 이렇게 구축된 모든 플러그인은 모양이 같기 때문에, 이미 한 통합 작업에 참여했던 개발자는 다음 통합에서 웹훅 핸들러나 Zod 스키마가 어디에 있는지 알고 있습니다. 또한 이는 AI 에이전트 도구의 경우 특히 중요합니다. 예측 가능한 스캐폴드(scaffold)는 코딩 에이전트가 검사하고 올바르게 채울 수 있는 요소입니다. 왜냐하면 이미 client.ts나 endpoints/가 무엇을 포함해야 하는지 알기 때문에, 처음부터 임시 파일의 규칙을 역설계할 필요가 없기 때문입니다. Corsair의 플러그인 제너레이터는 정확히 이 레이아웃을 따르며, 비록 자체 스택에서 수동으로 스캐폴드를 구축하게 되더라도 참고할 가치가 있습니다.
모든 엔드포인트에 타이핑 적용하여 에이전트가 오용하지 못하게 하기
통합 기능을 호출하는 에이전트는 여전히 단지 함수를 호출하는 것일 뿐입니다. 이는 곧 함수 시그니처(function signature)가 올바른 호출과 잘못된 호출 사이에 존재하는 유일한 요소라는 의미입니다. 이 지점에서 API 클라이언트 설계 결정은 인간 개발자에게보다 에이전트에게 더 중요합니다. 왜냐하면 에이전트는 필드가 조용히 누락되었다는 것을 알아차리는 본능이 없기 때문입니다. 엔드포인트를 도메인 이름 공간화되고(domain namespaced), 타입 안전한(type safe) 엔드포인트로 그룹화하는 것은 발견 가능성 문제와 타이핑 문제를 한 번에 해결합니다:
const sdk = new MyServiceClient({ apiKey });
await sdk.customers.list({ limit: 20 });
...
sdk.를 타이핑하면 IDE가 알고 있는 모든 리소스를 표시하며, 각 메서드는 일반적인 옵션 꾸러미 대신 자체 요청 및 응답 계약을 가집니다. 요청 타입(CreateInvoiceRequest, ListCustomersRequest)과 일반적인 응답 래퍼(APIResponse<T>, PaginatedResponse<T>)는 반환되는 형태를 예측 가능하게 유지합니다. 만약 제공업체가 이미 OpenAPI 스펙을 게시했다면, openapi-typescript나 Fern 같은 도구를 사용한 OpenAPI 코드 생성을 통해 모든 것을 수동으로 타이핑하는 대신 이러한 인터페이스들을 직접 생성할 수 있으며, 이는 아무것도 손으로 작성하기 전에 할 가치가 있습니다.
하지만 정적 타입은 런타임에 사라지며, 제3자 API는 경고 없이 응답 형태를 변경할 수 있습니다. 따라서 컴파일러만 신뢰하는 것보다는 경계에서 Zod 유효성 검사를 사용하는 것이 경우입니다: 스키마가 페이로드가 잘못되었거나 벗어났을 때 에이전트의 추론 단계에 도달하기 전에 이를 포착하고, TypeScript 타입을 직접 추론하므로 인터페이스와 검증기가 절대 어긋나지 않습니다. 트레이드오프는 실제적입니다: Zod는 약간의 런타임 비용을 추가하며 너무 엄격하게 작성된 스키마는 제공업체가 나중에 추가하는 무해한 새 필드를 거부할 수 있으므로, 스키마는 일반적으로 명시적으로 필요하지 않은 필드에 대해서는 관대하게 유지하는 것이 가장 좋습니다.
오류와 재시도 역시 응답과 동일한 처리를 받을 자격이 있습니다. HTTP 상태 코드를 표준화된 SDKError로 매핑하고, RATE_LIMIT_ERROR나 AUTHENTICATION_ERROR 같은 명시적인 코드를 담는 중앙 집중식 요청 실행기(request runner)는 에이전트가 실제로 분기할 수 있는 무언가를 제공합니다. 동일한 실행기는 오류 처리 재시도 로직이 속해야 할 곳이기도 합니다: 타임아웃, 503, 429와 같은 일시적인 실패는 백오프(backoff)를 사용하여 재시도되는 반면, 401 또는 400은 무작정 재시도되기보다는 즉시 노출됩니다.
단일 하드코딩된 시크릿 없이 인증 연결하기
Secrets는 환경 변수(environment variables)에 저장되어 시작 시점에 주입되어야 하며, 소스 파일에 절대 직접 입력해서는 안 됩니다. 클라이언트 생성자(client constructor)가 이를 강제하기에 적절한 위치입니다. 즉, 클라이언트를 생성하는 순간 필요한 키들이 존재하는지 검증하고, 네트워크 호출이 나가기 전에 명확하게 실패해야 합니다. 그렇지 않으면 세 요청 후에 혼란스럽게 실패할 수 있습니다.
OAuth 토큰 새로고침(token refresh) 과정에서 대부분의 맞춤형 통합(custom integrations)이 조용히 깨집니다. 이 과정에서는 세 가지 일이 함께 일어나야 합니다.
사전 만료 확인(Preemptive expiry checking)은 토큰이 도착할 때 만료 타임스탬프를 저장하고, 요청이 실패하기를 기다리는 대신 현재 시간이 그 만료 시점으로부터 몇 분 안에 들어오면 선제적으로 새로고침합니다.
401 에러에 대한 반응적 재시도(Reactive retry on 401)는 클럭 드리프트(clock drifted)나 토큰이 조기에 취소된 경우에도 요청이 여전히 권한 없음(unauthorized)으로 돌아오는 상황을 포착하여, 새로고침하고 원래의 요청을 한 번 다시 시도합니다.
동시성(concurrency) 문제가 발생할 때는 새로고침 자체를 감싸는 단일 비행 잠금(single flight lock)이 중요합니다. 여러 요청이 같은 순간에 만료된 토큰을 발견하더라도, 실제로 토큰 엔드포인트(token endpoint)를 호출하는 것은 오직 하나여야 합니다. 나머지는 동일한 비행 중인 Promise가 완료되기를 기다리면 됩니다. 왜냐하면 리프레시 토큰(refresh tokens)을 순환시키는 제공업체(provider)에 대해 여러 개의 동시 새로고침 호출을 보내면, 그 요청들 중 일부는 거부되거나 세션 자체가 취소될 수 있기 때문입니다.
if (Date.now() >= expiresAt - BUFFER_MS) {
await (refreshPromise ??= refreshToken());
}
실제 멀티 테넌트(multi tenant) 제품에서는 이 모든 것이 에이전트에게 전혀 보여서는 안 됩니다. 멀티 테넌트 자격 증명(Multi tenant credentials)은 호출이 이루어지는 순간, 테넌트별로 내부적으로 해결됩니다. 이는 또한 Corsair의 호스팅 및 셀프 호스트 설정이 사용하는 패턴이므로, 에이전트는 근본적인 토큰을 절대 보지 못하고 메서드 이름과 결과만 보게 됩니다.
로드맵을 기다리기보다 직접 배포하기(Shipping It Back Instead of Waiting on a Roadmap)
오픈 소스에 기여하는 과정은 다른 곳에서 기여하는 방식과 동일합니다: 작업을 시작하기 전에 먼저 권리를 주장하세요. 대부분의 오픈 소스 통합 프레임워크는 두 명의 기여자가 주말 동안 같은 Notion 플러그인을 독립적으로 구축하는 것을 방지하기 위해 추적 보드나 공개 이슈 목록을 운영합니다. 대상 API 문서, 다룰 예정인 엔드포인트, 필요한 인증 유형을 포함하여 이슈를 열면 보통 그것만으로도 그 작업이 당신의 이름으로 확정됩니다.
기존 프레임워크에 대한 풀 리퀘스트(pull request)는 누군가 로직을 검토하기 전에 특정하고 지루한 기준을 통과해야 합니다: 타입 체크, 린트 패스(lint pass), 그리고 디렉터리 계약이 준수되었는지 확인하는 플러그인 유효성 검사 스크립트입니다. 이 모든 것은 당신의 아이디어가 좋은지 여부가 아니라, 당신의 플러그인을 열어보는 다음 사람이 기대하는 것을 찾을 수 있는지에 관한 것입니다.
실제 검토는 얇은 구현(thin implementations)이 포착되는 곳입니다. 검토자들은 OAuth 지원으로 위장된 단순한 헤더 매핑 해킹 같은 인증 방식을 거부하고, 타입 지정된 엔드포인트를 조용히 비타입화하는 광범위한 as any 캐스트도 거부합니다. 이 둘은 스캐폴드(scaffold)의 목적을 무력화시킵니다.
이러한 기준을 통과하는 대가로 얻는 것은 폐쇄형 플랫폼이 제공할 수 없는 것입니다: 당신은 기능 요청을 제출하고 기다리는 것이 아니라, 통합 자체를 직접 배포하며, 같은 API가 필요한 다음 개발자는 그것을 무료로 얻게 됩니다. Corsair는 정확히 그 모델을 기반으로 구축되었습니다: 모든 플러그인은 타입 지정되고 사전 검증되어 배포되며, 셀프 호스팅은 무료이고, 자격 증명(credentials)은 절대 당신의 데이터베이스를 벗어나지 않습니다. 필요한 통합이 아직 다뤄지지 않았다면, 공급업체의 로드맵을 기다리기보다 스캐폴드를 사용하여 기여할 수 있습니다. 현재 플러그인 카탈로그를 확인하고 corsair.dev에서 직접 사용해 보세요.
자주 묻는 질문
단일 API 호출을 위해 맞춤형 통합(custom integration)을 구축할 가치가 있을까요, 아니면 그냥 인라인으로 작성하는 것이 나을까요? 인증에 정적 API 키 외의 추가적인 절차가 없는 진정한 일회성 호출이라면, 타입이 지정된 함수만으로 충분합니다. 여기서 설명한 골격(scaffolding)과 구조는 통합 기능에 인증(authenticated)이 필요하거나, 다중 테넌트(multi-tenant)를 지원해야 하거나, 첫 번째 릴리스 이후에도 유지보수되어야 할 때 빛을 발합니다.
Zod가 단순히 TypeScript 인터페이스를 작성하는 것보다 실제적인 이점은 무엇인가요? TypeScript 타입은 런타임에 사라지기 때문에 예상하지 못한 페이로드(payload)로부터 보호해 주지 못합니다. Zod는 응답이 도착할 때 검증하고 동일한 스키마에서 TypeScript 타입을 추론하므로, 제공업체의 API가 조용히 변경되어도 파이프라인 하단부의 데이터 손상을 일으키기 전에 포착할 수 있습니다.
여러 개의 리프레시 토큰 요청이 동시에 발생하는 것을 어떻게 막을 수 있나요? 리프레시 호출을 단일 공유 Promise로 감싸세요. 만료된 토큰을 감지하는 첫 번째 요청이 리프레시를 시작하고, 다른 모든 동시 요청은 토큰 엔드포인트를 다시 호출하는 대신 이 동일한 Promise를 기다립니다. 이는 또한 재사용 시 리프레시 토큰을 회전시키고 무효화하는 제공업체를 건드리는 것도 방지합니다.
오픈 소스 유지보자들이 새로운 플러그인 PR에서 실제로 거부하는 것은 무엇인가요? 가장 흔한 두 가지 거절 사유는 실제 OAuth 처리가 아닌 단순히 헤더 매핑 단축키에 불과한 인증 방식, 그리고 나머지 프레임워크가 의존하는 타입 지정(typing)을 우회하는 as any와 같은 광범위한 타입 단언(type assertion)입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기