
Claude Code SDK로 사내 에이전트를 2주 만에 구축 — TypeScript 구현과 운영 지견
요약
Claude Agent SDK를 사용하여 TypeScript 기반의 사내 에이전트를 2주 만에 구축한 실전 경험을 공유합니다. MCP를 통한 데이터 연결, 리소스 관리, 루프 제한 설정 등 운영 단계에서 겪은 시행착오와 최적화 방법을 다룹니다.
핵심 포인트
- Claude Agent SDK를 활용한 TypeScript 기반 에이전트 구현
- MCP(Model Context Protocol)를 통한 Notion, GitLab, Wiki 연동
- await using 구문을 활용한 에이전트 세션 리소스 관리
- Rate Limit 방지를 위한 maxSteps 설정 최적화
- Zod를 이용한 도구 입력 스키마의 타입 안전성 확보
앞으로 당분간, Production에서 실제로 구동 중인 AI 시스템의 구현 기록을 Qiita에 남겨보려 한다. 설계 이야기만, 혹은 라이브러리의 API 해설만 하는 글은 이미 좋은 기사가 대량으로 존재하므로, 필자는 "2주 차에 무엇이 망가졌는가", "처음에 빠진 함정은 무엇인가"와 같은 운영 단계의 입도로 쓰고 싶다.
첫 번째는, 사내용 작은 에이전트를 TypeScript로 2주 만에 구축한 이야기. 한 가지 미리 말해두자면, "Claude Code SDK"는 2025년 9월에 "Claude Agent SDK"로 리네임되었다. npm 패키지는 @anthropic-ai/claude-agent-sdk이다. 제목은 검색 유입을 위해 구명을 남겨두었으나, 본문의 코드는 모두 현행 SDK를 전제로 한다.

사내 Slack에서 호출할 수 있는 "문서 횡단 검색 + 간이 리포트 생성" 에이전트. Notion, 사내 GitLab, 사내 Wiki를 MCP를 통해 연결하여, 질문에 근거 링크와 함께 답변한다. 지난달, 팀의 개발 리드(LLM 프로덕트를 3개 정도 다뤄본 사람이다)가 회의실 화이트보드에 "2주 만에 Production에 투입할 수 없다면 일단 보류"라고 적었던, 그 그림이 시작점이었다.
"2주"라는 것은 배포하여 사내 30명이 사용하기 시작할 때까지를 포함한 기한이다. 3할 정도 빼고 읽어도 딱 적당한 숫자다.
Node.js 20.x / TypeScript 5.4
@anthropic-ai/claude-agent-sdk (2026-06 시점의 최신)
MCP 서버: 3개 (Notion / GitLab / 사내 Wiki)
모델: Claude Sonnet 4.6 (기본) / Opus 4.7 (무거운 요약 시에만 사용)
V2 Preview는 TypeScript 5.2 이후의 await using 구문을 지원한다. 에이전트 세션은 내부에서 MCP 커넥션, tool registry, 대화 이력을 모두 보유하므로, 명시적으로 닫지 않으면 프로세스가 남는 타입의 "잊기 쉬운 리소스"다. 처음에는 수동으로 session.close()를 호출했으나, 2일 차 테스트에서 예외가 발생한 순간 닫기 누락이 발생했기에 즉시 await using으로 전환했다.
import { Agent } from "@anthropic-ai/claude-agent-sdk";
import { tools } from "./tools";
import { connectInternalMcp } from "./mcp";
...
단 15줄뿐이지만, 이것만으로 "도구 호출 → 결과 → 다음 판단 → ..."을 SDK가 돌려준다. maxSteps: 8은 첫날의 아찔했던 경험에서 나온 설정이다. 어떤 직원이 "지난달까지의 장애 이력을 전부 요약해줘"라고 물었을 때, 루프가 30 step 가까이 계속 돌아가면서 Anthropic 측의 레이트 리밋(Rate Limit)에 걸렸다. 8 step이면 사내 질문의 95%는 충분했다.
SDK는 tool의 입력 스키마를 Zod로 작성할 수 있다. JSON Schema를 수기로 작성하는 시대로 돌아갈 이유는 이제 없다. 핸들러는 z.infer를 통해 타입이 그대로 내려온다.
import { z } from "zod";
import { defineTool } from "@anthropic-ai/claude-agent-sdk";
import { searchInternal } from "./search";
...
세세한 이야기지만, 최상위(top-level)의 description은 LLM을 위한 "이 tool을 선택할지 여부에 대한 판단 재료"이고, z.string().describe(...) 쪽은 "인수를 채우는 방법에 대한 설명"이라는 역할 분담이 있다. 이를 섞으면 tool 선택 실수가 눈에 띄게 늘어난다. 사내 리뷰에서 지적받고 나서야 겨우 깨달은 사실이다.
2025-03의 MCP spec에서 도입된 Streamable HTTP는 이제 "새로운 트랜스포트"가 아니다. 2026년 현재는 표준이라고 생각해도 좋다. stdio를 경유하는 로컬 MCP를 선택하는 이유는, 프로세스 경계를 정말로 나누고 싶을 때뿐이 되었다.
import { connectMcp } from "@anthropic-ai/claude-agent-sdk";
export async function connectInternalMcp() {
return connectMcp({
...
저지연 (Low-latency) 용도로는 in-process tool 방식도 있다. MCP 서버를 별도 프로세스로 띄우지 않고, TypeScript 코드 내에서 직접 tool을 정의하는 형식이다. 사내 검색의 일부는 원래 자체 제작 라이브러리로 완결되므로, 굳이 HTTP를 거칠 필요 없이 in-process 방식으로 처리했다. p95가 180ms에서 60ms로 단축되었다. "3배 빠르다"는 점보다, "3배 빠르면 사용자가 '생각해서 답했다'가 아니라 '즉시 답했다'라고 느끼게 된다"는 점이 더 크다.
session.run()
은 async iterable을 반환한다. 타입은 discriminated union이며, 최소 4종류의 이벤트를 의식해야 한다. 타입 시스템이 잘 작동하므로 switch 문으로 모든 케이스를 다루면 놓치는 부분은 없다.
for await (const event of session.run(question)) {
switch (event.type) {
case "text_delta":
...
처음에는 text_delta만 잡으면 동작할 것이라고 생각했다. 하지만 실제로는 에러 핸들링 시 tool_result.isError와 stop.reason을 모두 확인하지 않으면 "침묵하며 종료되는 에이전트"가 발생한다. 이를 깨달은 것은 프로덕션 (Production) 투입 3일째, CTO로부터 "방금 질문했는데 3분을 기다려도 아무 응답이 없다"라는 Slack DM을 받았을 때다. max_steps에 걸려 멈춰 있었지만, 사용자에게는 아무것도 표시되지 않고 있었다.
30명 / 14일간 / 약 1,200 query. 데이터만 나열한다.
1 query당 평균 토큰: input 18k / output 1.4k
p50 레이턴시 (Latency): 2.1s, p95: 6.8s
tool 호출 평균: 2.3회 / query
"답이 되지 않았다" 비율: 약 9% (사내 설문조사)
9%를 "1할"이라고 들으면 작게 느껴지지만, 100건 중 9건은 누군가가 "다시 직접 검색했다"는 것을 의미한다. 이 수치는 연재의 다음 회차 주제가 될 예정이다.
운영 측면의 이야기를 하나 하자면, 2026-06-15부터 Claude Agent SDK와 Claude Code GitHub Actions의 사용량은 인터랙티브한 Claude Code와는 별도로 측정되기 시작했다. 에이전트 업무는 토큰 단가 기반으로 전환되었으므로, 사내 에이전트를 운영하는 팀은 Anthropic Console의 usage를 한 번 확인해 두는 것이 좋다. 필자의 실측(앞서 언급한 1,200 query) 기준으로 보면, 예상보다 월 비용이 1.4배 정도 더 발생했다. 미리 산출해 두었다면 설명 비용이 들지 않았을 텐데, 나중에 깨달으면 번거로워진다.
- sub-agent 패턴: "검색하는 자식"과 "요약하는 자식"을 나누어, 검색 측은 저렴한 모델을, 요약 측은 Opus를 분담하게 함
- Memory tool로 "과거에 팀이 무엇을 물었는지"를 갖게 하여, 용어의 사내 방언을 학습시킴
- hooks를 통해 사내 PII(개인정보) 필터를 삽입 (출력 전 정규표현식으로 전화번호·개인명을 마스킹)
Claude Agent SDK는 TypeScript의 타입 시스템, Zod, async iterable을 완전히 전제로 설계되어 있어, "2주 만에 프로덕션"이 가능할 정도로 직관적이었다. 빠졌던 함정은 "tool description의 구분", "stop.reason의 간과", "과금 분리" 세 가지였다. 다음 회차에서는 동일한 사내 에이전트를 n8n과 Notion 측으로 확장한 멀티 벤더 게이트웨이 (Multi-vendor Gateway) 패턴의 구현에 대해 쓸 예정이다.
필자는 5년 이상 AI 에이전트 개발을 담당하고 있다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기