MCP Tasks — 장기 실행 작업의 비동기 처리

MCP Tasks — 장기 실행 작업의 비동기 처리

모든 도구 호출이 즉시 결과를 돌려주는 건 아니에요. CI 파이프라인, 배치 처리, 사람의 승인 같은 작업은 몇 초·몇 분·그 이상이 걸리기도 하죠. MCP Tasks는 서버가 결과를 기다리며 연결을 막는 대신 내구성 있는 핸들(durable handle) 을 돌려주고, 클라이언트가 진행 상황을 폴링하고 필요하면 입력을 제공하며 재연결 후 최종 결과를 가져올 수 있게 해줘요.

출처: 공식문서 — Tasks

왜 그냥 블로킹하지 않나요?

연결을 작업이 끝날 때까지 열어둘 수도 있지만, 블로킹으로는 해결되지 않는 문제가 있어요.

  • 장기 연결이 없다. 블로킹은 작업 기간 내내 연결을 점유하지만, 많은 클라이언트·전송 중개자는 몇 초를 넘으면 타임아웃을 걸어 실용적이지 않아요.
  • 크래시 복원력. task ID는 내구성 있는 핸들이라 클라이언트가 끊기거나 재시작해도 같은 ID로 폴링을 재개할 수 있어요.
  • 진행 상황 가시성. Task는 상태 메타데이터(working, input_required, completed, failed, cancelled)와 선택적 상태 메시지를 실어 진행 상황을 보여줘요.
  • 진행 중 상호작용. 작업에 입력이 필요하면(예: 사용자 확인을 위한 elicitation) input_required로 전환해 요청을 드러내요. 클라이언트는 tasks/update로 응답하며, 두 번째 연결이나 서버→클라이언트 비요청 메시지가 필요 없어요.
  • 서버 주도. 서버가 요청별로 task 생성 여부를 결정해요. 클라이언트는 확장 capability로 한 번 옵트인하고 어떤 결과 모양이 오든 처리하면 돼요. 도구별 준비 단계나 요청별 플래그가 필요 없어요.

Tasks는 어떻게 동작하나요

Tasks는 표준 요청 흐름을 확장해요. 서버가 요청이 장기 실행될 것이라 결정하면 최종 결과 대신 task 핸들을 돌려주고, 클라이언트가 완료를 폴링해요.

  1. Capability 협상. 클라이언트는 요청별 capabilities에 io.modelcontextprotocol/tasks를 포함하고, 서버는 server/discover capabilities에서 같은 확장을 광고해요.
  2. Task 생성. 지원 요청에 대한 응답으로 서버는 resultType: "task"로 식별되는 CreateTaskResult(taskId, 초기 상태, TTL, 권장 폴링 간격 포함)를 돌려줘요. task는 응답 전에 내구적으로 생성돼요.
  3. 폴링. 클라이언트는 taskId로 tasks/get을 호출해요. 응답은 현재 상태를, 종료 상태면 최종 결과나 오류를 실어요.
  4. 진행 중 입력. task가 input_required로 전환되면 tasks/get 응답에 elicitation 등 서버 요청이 담긴 inputRequests 맵이 포함되고, 클라이언트가 tasks/update로 이를 이행해요.
  5. 완료. 상태가 completed에 도달하면 result 필드에 원래 요청이 동기적으로 반환했을 값이 들어 있어요. failed라면 error 필드에 JSON-RPC 오류가 담겨요.
  6. 취소. 클라이언트는 언제든 tasks/cancel을 보낼 수 있어요. 취소는 협조적(cooperative)이라 서버는 의사를 인정하지만 작업을 멈출 의무는 없어요.
sequenceDiagram
    participant Client
    participant Server
    Client->>Server: tools/call (with tasks capability)
    Server-->>Client: CreateTaskResult (taskId, status: working)
    loop Poll until terminal
        Client->>Server: tasks/get (taskId)
        Server-->>Client: Task (status: working)
    end
    Note over Client,Server: Server needs user input
    Client->>Server: tasks/get (taskId)
    Server-->>Client: Task (status: input_required, inputRequests)
    Client->>Server: tasks/update (taskId, inputResponses)
    Server-->>Client: ack
    loop Poll until terminal
        Client->>Server: tasks/get (taskId)
        Server-->>Client: Task (status: working)
    end
    Client->>Server: tasks/get (taskId)
    Server-->>Client: Task (status: completed, result)

Tasks는 언제 쓰나요

장기 실행 작업(몇 분~몇 시간 걸리는 CI 파이프라인·배치 데이터 처리·모델 학습), 사람 개입 워크플로(승인 게이트·검토 단계처럼 사용자 확인을 위해 멈추는 작업), 외부 작업 시스템(이미 job ID를 쓰는 API를 감싸는 서버), 불안정한 연결(모바일·간헐적 네트워크), 배치 처리(일괄 import·대량 업데이트처럼 부분 진행이 의미 있는 작업)에 잘 맞아요.

Task 수명주기

상태 의미
working 작업이 진행 중.
input_required 계속 진행 전 서버가 클라이언트 입력을 요구. inputRequests 참조.
completed 작업 완료. result 필드에 최종 출력.
failed 실행 중 JSON-RPC 오류 발생. error 필드에 상세.
cancelled 작업이 취소됨(항상 존중되진 않음).

completed, failed, cancelled는 종료 상태라 한 번 도달하면 task 상태는 바뀌지 않아요. 서버는 notifications/tasks로 상태 업데이트를 푸시할 수도 있고, 각 알림은 전체 task 상태를 실어 추가 tasks/get 왕복을 없애줘요. 폴링이 기본이고, 서버가 알림을 지원하면 클라이언트는 폴링 대신 알림에 의존할 수 있어요.

구현 가이드

MCP 클라이언트: (1) 요청별 capabilities에 확장 선언, (2) tools/call 같은 지원 요청 시 표준 결과 또는 resultType: "task"CreateTaskResult를 받을 준비, (3) tasks/getpollIntervalMs에 맞춰 종료 상태까지 폴링, (4) input_requiredinputRequests를 읽어 tasks/update로 응답, (5) taskId를 내구적으로 저장해 크래시·재시작 후 폴링 재개.

MCP 서버: (1) server/discover capabilities에 확장 광고, (2) CreateTaskResult 반환 전 클라이언트가 확장을 선언했는지 확인(선언 안 한 클라이언트엔 절대 task 반환 금지), (3) 장기 요청 시 resultType: "task"Task 객체(고유 taskId·초기 상태·ttlMs·pollIntervalMs)로 응답하고 응답 전 내구 생성, (4) tasks/get으로 현재 상태 반환(종료 상태면 result/error 포함), (5) tasks/update로 대기 중 inputRequests에 키잉된 inputResponses 처리(빈 결과로 ack, 알 수 없거나 이미 충족된 키는 무시), (6) tasks/cancel은 빈 결과로 ack하되 가능하면 존중(취소는 협조적).

클라이언트 지원

MCP Tasks는 핵심 MCP 스펙의 확장이며 호스트 지원은 클라이언트마다 달라요. 클라이언트 매트릭스에서 확인할 수 있고, task 지원은 클라이언트와 서버 양쪽의 명시적 옵트인이 필요해요.

더 알아보기