콘텐츠로 이동

MCP 런타임 생명주기

이 문서는 코딩 에이전트 런타임에서 MCP 서버가 발견, 연결, 도구로 노출, 갱신 및 종료되는 방식을 설명합니다.

  1. SDK 시작discoverAndLoadMCPTools()를 호출합니다(MCP가 비활성화되지 않은 경우).
  2. 발견 (loadAllMCPConfigs)은 기능 소스에서 MCP 서버 설정을 확인하고, 비활성화된/프로젝트/Exa 항목을 필터링하며, 소스 메타데이터를 보존합니다.
  3. 매니저 연결 단계 (MCPManager.connectServers)는 서버별 연결 + tools/list를 병렬로 시작합니다.
  4. 빠른 시작 게이트는 최대 250ms 대기 후 다음을 반환할 수 있습니다:
    • 완전히 로드된 MCPTool,
    • 서버별 실패,
    • 또는 아직 대기 중인 서버에 대한 캐시된 DeferredMCPTool.
  5. SDK 연결은 MCP 도구를 세션의 런타임 도구 레지스트리에 병합합니다.
  6. 라이브 세션/mcp 플로우(disconnectAll + 재발견 + session.refreshMCPTools)를 통해 MCP 도구를 갱신할 수 있습니다.
  7. 종료는 호출자가 disconnectServer/disconnectAll을 호출할 때 발생하며, 매니저는 연결 해제된 서버의 MCP 도구 등록도 제거합니다.

src/sdk.tscreateAgentSession()enableMCP이 true(기본값)일 때 MCP 시작을 수행합니다:

  • discoverAndLoadMCPTools(cwd, { ... })를 호출하고,
  • authStorage, 캐시 저장소, mcp.enableProjectConfig 설정을 전달하며,
  • 항상 filterExa: true로 설정하고,
  • 서버별 로드/연결 오류를 로깅하며,
  • 반환된 매니저를 toolSession.mcpManager와 세션 결과에 저장합니다.

enableMCP이 false이면 MCP 발견이 완전히 건너뛰어집니다.

loadAllMCPConfigs() (src/mcp/config.ts)은 기능 발견을 통해 정규 MCP 서버 항목을 로드한 후 레거시 MCPServerConfig로 변환합니다.

필터링 동작:

  • enableProjectConfig: false는 프로젝트 수준 항목(_source.level === "project")을 제거합니다.
  • enabled: false인 서버는 연결 시도 전에 건너뛰어집니다.
  • Exa 서버는 기본적으로 필터링되며, API 키는 네이티브 Exa 도구 통합을 위해 추출됩니다.

결과에는 configssources(나중에 프로바이더 레이블링에 사용되는 메타데이터) 모두 포함됩니다.

discoverAndLoadMCPTools()는 두 가지 실패 유형을 구분합니다:

  • 발견 하드 실패 (manager.discoverAndConnect의 예외, 일반적으로 설정 발견에서 발생): 빈 도구 세트와 하나의 합성 오류 { path: ".mcp.json", error }를 반환합니다.
  • 서버별 런타임/연결 실패: 매니저가 errors 맵과 함께 부분 성공을 반환하며, 다른 서버는 계속 진행됩니다.

따라서 개별 MCP 서버가 실패해도 전체 에이전트 세션이 실패하지 않습니다.

MCPManager는 별도의 레지스트리로 런타임 생명주기를 추적합니다:

  • #connections: Map<string, MCPServerConnection> — 완전히 연결된 서버.
  • #pendingConnections: Map<string, Promise<MCPServerConnection>> — 핸드셰이크 진행 중.
  • #pendingToolLoads: Map<string, Promise<{ connection, serverTools }>> — 연결되었지만 도구가 아직 로딩 중.
  • #tools: CustomTool[] — 호출자에게 노출되는 현재 MCP 도구 뷰.
  • #sources: Map<string, SourceMeta> — 연결 완료 전의 프로바이더/소스 메타데이터.

getConnectionStatus(name)는 이러한 맵에서 상태를 도출합니다:

  • #connections에 있으면 connected,
  • 대기 중인 연결 또는 대기 중인 도구 로드가 있으면 connecting,
  • 그 외에는 disconnected.

connectServers()에서 발견된 각 서버에 대해:

  1. 소스 메타데이터 저장/업데이트,
  2. 이미 연결됨/대기 중이면 건너뛰기,
  3. 전송 필드 검증 (validateServerConfig),
  4. 인증/셸 치환 해석 (#resolveAuthConfig),
  5. connectToServer(name, resolvedConfig) 호출,
  6. listTools(connection) 호출,
  7. 도구 정의 캐시 (MCPToolCache.set) 최선 노력.

connectToServer() 동작 (src/mcp/client.ts):

  • stdio 또는 HTTP/SSE 전송을 생성하고,
  • MCP initialize + notifications/initialized를 수행하며,
  • 타임아웃 사용 (config.timeout 또는 기본 30초),
  • 초기화 실패 시 전송을 닫습니다.

connectServers()는 다음 사이의 경쟁을 대기합니다:

  • 모든 연결/도구 로드 작업 완료, 그리고
  • STARTUP_TIMEOUT_MS = 250.

250ms 이후:

  • 완료된 작업은 라이브 MCPTool이 되고,
  • 거부된 작업은 서버별 오류를 생성하며,
  • 아직 대기 중인 작업은:
    • 캐시된 도구 정의가 있으면 (MCPToolCache.get) DeferredMCPTool을 생성하고,
    • 그렇지 않으면 대기 중인 작업이 완료될 때까지 블로킹합니다.

이는 하이브리드 시작 모델입니다: 캐시가 있을 때 빠른 반환, 캐시가 없을 때 정확성을 위한 대기.

대기 중인 각 toolsPromise에는 최종적으로 다음을 수행하는 백그라운드 연속 작업도 있습니다:

  • #replaceServerTools를 통해 매니저 상태에서 해당 서버의 도구 슬라이스를 교체하고,
  • 캐시를 기록하며,
  • 시작 이후에만 지연 실패를 로깅합니다 (allowBackgroundLogging).

도구 노출 및 라이브 세션 가용성

섹션 제목: “도구 노출 및 라이브 세션 가용성”

discoverAndLoadMCPTools()는 매니저 도구를 LoadedCustomTool[]로 변환하고 경로를 장식합니다(알려진 경우 mcp:<server> via <providerName>).

createAgentSession()은 이 도구들을 customTools에 추가하며, 이들은 래핑되어 mcp_<server>_<tool>과 같은 이름으로 런타임 도구 레지스트리에 추가됩니다.

  • MCPTool은 이미 연결된 MCPServerConnection을 통해 도구를 호출합니다.
  • DeferredMCPTool은 호출 전에 waitForConnection(server)를 대기합니다; 이를 통해 연결이 준비되기 전에 캐시된 도구가 존재할 수 있습니다.

둘 다 구조화된 도구 출력을 반환하고 전송/도구 오류를 MCP error: ... 도구 콘텐츠로 변환합니다(중단은 중단으로 유지).

갱신/리로드 경로 (시작 vs 라이브 리로드)

섹션 제목: “갱신/리로드 경로 (시작 vs 라이브 리로드)”
  • sdk.ts에서의 일회성 발견/로드,
  • 도구가 초기 세션 도구 레지스트리에 등록됩니다.

/mcp reload 경로 (src/modes/controllers/mcp-command-controller.ts)는 다음을 수행합니다:

  1. mcpManager.disconnectAll(),
  2. mcpManager.discoverAndConnect(),
  3. session.refreshMCPTools(mcpManager.getTools()).

session.refreshMCPTools() (src/session/agent-session.ts)는 모든 mcp_ 도구를 제거하고, 최신 MCP 도구를 재래핑하며, 도구 세트를 재활성화하여 세션을 재시작하지 않고도 MCP 변경사항이 적용되도록 합니다.

지연 연결을 위한 후속 경로도 있습니다: 특정 서버를 대기한 후 상태가 connected가 되면, session.refreshMCPTools(...)를 재실행하여 새로 사용 가능한 도구가 세션 내에서 재바인딩됩니다.

상태 확인, 재연결 및 부분 실패 동작

섹션 제목: “상태 확인, 재연결 및 부분 실패 동작”

현재 런타임 동작은 의도적으로 최소화되어 있습니다:

  • 매니저/클라이언트에 자율 상태 모니터가 없습니다.
  • 전송이 끊어졌을 때 자동 재연결 루프가 없습니다.
  • 매니저는 전송의 onClose/onError를 구독하지 않으며, 상태는 레지스트리 기반입니다.
  • 재연결은 명시적입니다: 리로드 플로우 또는 직접 connectServers() 호출.

운영적으로:

  • 하나의 서버 실패가 정상 서버의 도구를 제거하지 않으며,
  • 연결/목록 실패는 서버별로 격리되고,
  • 도구 캐시와 백그라운드 업데이트는 최선 노력입니다(경고/오류 로깅, 하드 중지 없음).

disconnectServer(name):

  • 대기 중인 항목/소스 메타데이터를 제거하고,
  • 연결된 경우 전송을 닫으며,
  • 매니저 상태에서 해당 서버의 mcp_ 도구를 제거합니다.

disconnectAll():

  • Promise.allSettled로 모든 활성 전송을 닫고,
  • 대기 맵, 소스, 연결 및 매니저 도구 목록을 초기화합니다.

현재 연결에서 명시적 종료는 MCP 명령 플로우(리로드/제거/비활성화)에서 사용됩니다. 시작 경로 자체에는 별도의 자동 매니저 해제 훅이 없으며, 결정론적 MCP 종료가 필요한 경우 호출자가 매니저 연결 해제 메서드를 호출할 책임이 있습니다.

시나리오동작하드 실패 vs 최선 노력
발견 예외 발생 (기능/설정 로드 경로)로더가 빈 도구 + 합성 .mcp.json 오류 반환최선 노력 세션 시작
잘못된 서버 설정검증 오류 항목으로 서버 건너뛰기서버별 최선 노력
연결 타임아웃/초기화 실패서버 오류 기록; 다른 서버 계속서버별 최선 노력
시작 시 tools/list가 대기 중이고 캐시 적중지연 도구 즉시 반환최선 노력 빠른 시작
시작 시 tools/list가 대기 중이고 캐시 없음시작이 대기 완료까지 대기정확성을 위한 하드 대기
지연된 백그라운드 도구 로드 실패시작 게이트 이후 로깅최선 노력 로깅
런타임 전송 끊김자동 재연결 없음; 재연결/리로드까지 이후 호출 실패수동 조치를 통한 최선 노력 복구

src/mcp/index.ts는 외부 호출자를 위해 로더/매니저/클라이언트 API를 재내보냅니다. src/sdk.ts는 동일한 로더 결과 형태를 반환하는 편의 래퍼로 discoverMCPServers()를 노출합니다.