# ADR-0007 MCP 배포 하나는 Tool Service 하나만 본다 - 상태: Superseded - 결정일: 2026-08-02 - 대체 결정: [ADR-0013](ADR-0013-portal-owns-route-and-endpoint-registry.md) - 관련: [ADR-0001](ADR-0001-stateless-execution-boundary.md), [ADR-0002](ADR-0002-tool-exposure-and-single-call.md), [ADR-0009](ADR-0009-container-handles-public-mcp-path.md), [계약 v0.2](../contracts/tool-service-mcp/protocol-v0.2-bundle-discovery.md) > 이 문서는 검토 이력을 보존한다. 현재 구현과 신규 연동에는 [ADR-0013](ADR-0013-portal-owns-route-and-endpoint-registry.md)을 적용한다. > route↔Tool Service 매핑의 원천이 Portal로 옮겨지면서, MCP 배포 하나가 N개 route를 보고 route 하나에 N개 Tool Service가 붙는다. > 아래 격리 논거는 폐기된 것이 아니라 ADR-0013이 무엇을 포기했는지 판단하는 근거로 남는다. 외부에서 여러 MCP를 하나의 host 아래 path로 묶는 방식은 [ADR-0009](ADR-0009-container-handles-public-mcp-path.md)이 소유한다. ## 배경 MCP 설정의 `mcp.bundles`는 여러 Tool Service를 하나의 카탈로그로 병합할 수 있다. 이 능력을 실제로 쓸지, 즉 MCP와 Tool Service를 M:N으로 묶을지는 결정되지 않은 상태였다. 고객 요구는 **Tool의 군집화**다. MCP 자체를 군집화해 달라는 요구가 아니다. 요구의 목적은 가용성이며, 중요한 Tool은 다운이 없어야 한다는 것이다. 분할 기준은 먼저 업무로 나누고, 그 안에서 중단 시 업무 영향도와 가용성 위험도로 다시 나누는 형태다. 예: 처리계-중요, 처리계-비중요, 정보계-중요, 정보계-비중요. 여기서 위험도는 보안·권한 정책이 아니라 **서비스 중단 위험**을 뜻한다. 업무 정책은 Tool Service가 관리하며 이 배포 등급의 범위가 아니다. Tool 목록이 확정되지 않아 Tool Service가 몇 개가 될지 모르며, 10~20개 이상이 될 수 있다. ## 결정 1. **MCP 배포 하나는 Tool Service를 정확히 하나 본다.** `mcp.bundles`는 항상 한 항목이다. 2. 배포 단위의 분할 축은 **업무 × 등급(tier)** 이다. 등급은 `critical`과 `standard`로 둔다. 3. 등급은 **배포 속성일 뿐 wire 계약에 나타나지 않는다.** Tool 이름·`namePrefix`·매니페스트에 등급을 넣지 않는다. 4. 다중 bundle 병합 코드는 **삭제하지 않고 유지**하되, 배포 설정에서 bundle 1개로 잠근다. ## 근거 ### 등급이 다른 Tool Service를 한 MCP가 보면 격리가 깨진다 `ToolBundleRegistryClient.fetchTools()`는 사용 가능한 성공본이 없는 bundle이 하나라도 있으면 카탈로그 전체 교체를 거부한다(계약 v0.2 §1, §7). 한 MCP가 중요·비중요 Tool Service를 함께 보면 **비중요 쪽 조회가 확정되지 않는 동안 중요 Tool의 카탈로그 갱신까지 멈춘다.** 직전 성공본으로 서빙은 계속되지만 변경 반영은 막힌다. 여기에 두 Tool Service 호출이 같은 프로세스의 HTTP connection pool과 스레드를 공유하므로, 비중요 쪽 지연이 중요 쪽 여유를 잠식한다. **병합은 가용성 요구와 정면으로 충돌한다.** 등급을 나눈 목적을 배포 구조가 되돌려 놓는다. ### 병합해서 얻는 것이 없다 MCP에는 업무 로직이 없다. 여러 Tool Service를 하나로 합치는 일이 MCP 안에서 일어나야 할 기술적 이유가 없다. Agent Builder는 MCP를 개별 등록하면서 하위 Tool 정보를 자기 DB에 저장하고, 사용자 요청을 판단한 뒤 **해당 Tool을 가진 MCP로 호출을 보낸다.** 여러 Tool 묶음을 아우르는 일은 Tool 선택을 이미 수행하는 Agent Builder 계층에서 끝난다. 런타임에 공유되는 공통 Tool Service도 없다. Tool 파트의 `tool-common`은 각 Tool Service 프로젝트가 함께 빌드하는 **빌드 타임 라이브러리**이지 별도로 뜨는 서비스가 아니다. 1:1을 깨야 할 사례가 남지 않는다. ### 1:1이라야 등급별로 다른 비용을 쓸 수 있다 한 MCP가 등급을 섞어 들고 있으면 그 배포 전체에 중요 등급 기준을 적용해야 한다. 나뉘어 있으면 `critical`에만 replica 여유와 PodDisruptionBudget을 주고 `standard`는 최소로 둘 수 있다. **분할의 실질 이득은 격리 자체보다 여기에 있다.** 다만 배포를 나누는 것만으로 가용성이 생기지는 않는다. 같은 노드 배치, 같은 namespace의 쿼터, 공통 Redis·클러스터 장애는 분할로 막히지 않는다. 등급 분리가 의미를 가지려면 replica 하한, PodDisruptionBudget, anti-affinity와 usable Tool snapshot 기반 readiness가 함께 가야 한다. Chart의 `tiers` 설정과 `HelmDeploymentContractTest`가 test·prod의 정적 values를 검사하며, 실제 렌더링 결과는 배포 파이프라인의 `helm lint`와 `helm template`이 확인한다. ## 전제 아래가 깨지면 이 결정을 재검토한다. 1. Agent Builder는 MCP를 개별 등록하고, 한 Agent가 여러 MCP의 Tool을 사용할 수 있다. 2. Tool 호출은 한 요청에 하나이며([ADR-0002](ADR-0002-tool-exposure-and-single-call.md)) 그 Tool을 가진 MCP로 직접 간다. 3. 런타임에 공유되는 공통 Tool Service가 없다. ## 영향 - **배포 수 = Tool Service 수**다. 10~20개를 전제로 Helm values는 토폴로지를 한 파일에 모으고 배포 시 `deploymentKey`로 하나를 고른다. 배포가 늘어도 파일 수는 변하지 않는다. - 계약 v0.2 §7의 병합 규칙 중 bundle 간 이름 충돌(6번)과 `maxToolsTotal`(3번 후단)은 운영에서 발동하지 않는다. 규칙 자체는 계약에 남는다. - **Tool 이름의 전역 유일성은 Tool Service 책임으로 남는다.** 서로 다른 MCP가 같은 `namePrefix`를 쓰는 것을 MCP는 막지 못한다. 등급으로 나뉜 두 배포가 같은 업무 prefix(`processing.`)를 공유하는 것은 의도된 구성이며, 그 안에서 Tool 이름이 겹치지 않아야 한다. - Tool을 다른 등급으로 옮기면 그 Tool을 제공하는 **MCP endpoint가 바뀐다.** Agent Builder가 Tool 정보를 DB에 보관하므로 반영에는 재등록 또는 다음 `tools/list` 주기가 필요하다. 등급은 자주 바꾸지 않는 값으로 다룬다. - [ADR-0002](ADR-0002-tool-exposure-and-single-call.md)의 Tool 노출 상한 50개는 한 Agent가 여러 MCP에서 가져온 Tool의 **합계**에 적용된다. MCP를 나눈다고 상한이 늘지 않는다. ## 채택하지 않은 대안 **M:N — 한 MCP가 여러 Tool Service를 본다.** 배포 수는 줄지만 위의 격리 문제가 그대로 남는다. 가용성이 분할의 목적이므로 목적과 수단이 어긋난다. **코드에서 bundle 1개를 강제한다.** `McpProperties`에 검증을 넣으면 다중 bundle 병합 코드가 도달 불가능해진다. 전제 3이 깨질 때 되돌리는 비용이 커지고, 이미 작성·테스트된 경로를 죽은 코드로 만든다. 1:1은 애플리케이션 불변식이 아니라 **배포 결정**이므로 배포 정의에서 잠그는 편이 맞다. 이 선택은 검증 위치를 옮긴 것이지 검증을 뺀 것이 아니다.