MCP Deep Dive Part 9: 도구가 몇 분이 걸릴 때 — MCP를 통한 스트리밍 및 장시간 실행 도구
요약
Model Context Protocol(MCP)을 사용하여 실행 시간이 긴 도구를 효율적으로 처리하는 방법을 다룹니다. 진행 알림, 비동기 작업 패턴, 취소 처리 및 연결 유지를 통해 에이전트의 반응성을 높이는 기술적 가이드를 제공합니다.
핵심 포인트
- 작업 시간에 따라 SYNC, STREAM, ASYNC JOB 패턴을 선택해야 함
- MCP의 progressToken을 활용해 진행 상황을 실시간으로 알림
- CancellationToken을 준수하여 불필요한 리소스 소모를 방지
- Keepalive 설정을 통해 네트워크 유휴 상태로 인한 연결 끊김 방지
대부분의 MCP 도구는 밀리초 단위로 반환되어 작업하기가 쉽습니다. 그러다가 PDF를 생성하거나, 다단계 분석을 수행하거나, 수십억 행 테이블에 쿼리를 실행하는 도구가 추가되면, 연결이 시간 초과될 때까지 에이전트 전체가 스피너에서 멈춥니다. _작업_의 길이가 조용히 _호출(call)_의 길이 자체가 되어버렸고, 그것이 버그입니다.
이는 **Model Context Protocol (MCP)**에 대한 15부작 심층 분석 중 제9부입니다. 보안 삼총사(Part 6-8)는 지났으며, 이제 우리는 반응성으로 전환합니다. 100ms의 KPI 읽기부터 몇 분이 걸리는 보고서 렌더링까지 범위가 다른 도구들에서도 에이전트가 민첩하게 작동하도록 유지하는 것입니다.
지속 시간에 따른 패턴 선택
< ~1s SYNC 결과를 직접 반환
~1-30s STREAM 진행 알림 + 스트리밍된 부분 결과 (SSE)
> ~30s ASYNC JOB 큐에 넣기(enqueue) -> jobId 반환 -> 상태 폴링 / 리소스 대기
...
1. 블로킹(Block) -> 진행 알림(progress notifications)
피드백이 전혀 없는 상태로 1분 동안 실행되는 도구는 클라이언트가 시간 초과됩니다. 대신 MCP 진행 알림을 방출하세요:
public async Task<Report> GenerateReport(
ReportArgs args, IProgress<ProgressNotification> progress, CancellationToken ct)
{
...
}
MCP는 일급(first-class)의 진행 채널을 가지고 있습니다. 요청에 포함된 progressToken과 되돌아오는 notifications/progress입니다. 이를 사용하면 장시간 도구가 시간 초과로 끝나는 스피너 대신
[McpServerTool(Name = "create_report"]
[Description("보고서 생성을 시작합니다. 즉시 jobId를 반환하며, report_status를 폴링하거나 report.ready를 기다립니다.")]
public async Task<ReportQueued> CreateReport(CreateReportArgs args, CancellationToken ct)
...
create_report는 p95 기준 ~90 ms 만에 반환되며, PuppeteerSharp 렌더링 (전체 작업량은 1.2M / 48시간)은 Service Bus에서 비동기적으로 실행됩니다.
4. 취소(Cancellation) — 아무도 원하지 않는 작업을 중단하기
MCP의 취소 알림은 CancellationToken에 매핑되므로, 처음부터 끝까지 이를 준수해야 합니다:
public async Task<Report> GenerateReport(ReportArgs args, CancellationToken ct)
{
for (var i = 0; i < args.Pages; i++)
...
취소 처리를 무시하는 장시간 도구는 용량 누수(capacity leak)를 유발합니다. 방치된 실행은 아무도 읽지 않는 아티팩트에서 계속 컴퓨팅 자원을 소모합니다.
5. 시간 초과 및 keepalive — 네트워크 생존하기
프록시와 로드 밸런서는 "유휴(idle)" 연결을 끊어버리고, SSE는 청크 사이사이에 유휴 상태로 보입니다. Ping을 보내세요:
builder.Services.AddMcpServer().WithHttpTransport(o =>
{
o.KeepAliveInterval = TimeSpan.FromSeconds(15); // 중간 장비가 연결을 끊지 않도록
...
Keepalive는 연결을 유지하고, 호출별 시간 초과(timeout)는 최악의 경우를 제한하며, 복원력 있는 재연결 클라이언트는 여전히 연결이 끊어져도 복구합니다.
6. 사용자에게 스트리밍하고 모델에 결과를 공급하기
두 가지 청중, 두 가지 채널:
progress.Report(...); // -> 사용자의 진행률 표시줄
stream.WriteToUser(chunk); // -> 사용자가 토큰을 받는 대로 확인
return ToolResults.Structured(finalKpis); // -> 모델이 깔끔한 결과를 받음
인간은 진행 상황을 보는 것을 원하고, 모델은 _답변_을 원합니다. 모든
작업의 길이와 호출의 길이를 분리합니다. 빠른 도구는 결과를 반환하고; 중간 정도의 도구는 진행 상황과 부분 결과를 스트리밍하며; 긴 도구는 작업을 예약(enqueue)하고 핸들(handle)을 돌려줍니다. 그런 다음 채널을 활성 상태로 유지하고(keepalive, reconnect), 깔끔하게 종료되도록 합니다(cancellation). 이렇게 하면 아래의 도구가 100밀리초가 걸리든 10분이 걸리든 에이전트는 즉각적인 느낌을 받게 됩니다.
원래 prepstack.co.in에 게시되었습니다. Part 10에서는 이 모든 것을 관찰 가능하게 만듭니다: 프로덕션 환경에서의 MCP 디버깅 및 관측 가능성(observability).
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기