Files
dap-was-dapms/docs/decisions/ADR-0007-one-mcp-per-tool-service.md
koseokmin 5c069a0ee7 Portal registry ADR의 번호를 0013으로 옮긴다
main이 6078852의 endpoint 소유권 반전을 ADR-0010으로 기록하면서 이
브랜치의 ADR-0010과 번호가 겹쳤다. 두 문서는 다른 결정이므로 나중에
문서를 합칠 때 한쪽을 옮겨야 한다.

이 브랜치가 0012까지 쓰고 있어 0011·0012를 건드리지 않는 첫 번호인
0013을 쓴다. 파일명과 제목, 대체 관계를 가리키는 ADR-0007·ADR-0009,
결정 목록, architecture 문서, Portal 계약 문서, Helm 설명, 그리고
application.yml 주석의 참조를 함께 바꾼다. 결정 내용은 바뀌지 않는다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 21:23:29 +09:00

7.2 KiB
Raw Blame History

ADR-0007 MCP 배포 하나는 Tool Service 하나만 본다

이 문서는 당시 검토 이력을 보존한다. 내부망 운영은 endpoint 목록과 route 매핑의 원천을 Portal로 옮겼으므로 현재 구현과 신규 연동에는 ADR-0013을 적용한다. 아래 격리 논거는 폐기된 것이 아니라 ADR-0013이 무엇을 포기했는지 판단하는 근거로 남는다.

외부에서 여러 MCP를 하나의 host 아래 path로 묶는 방식은 ADR-0009이 소유한다. OpenShift Route가 원래 path를 유지한 채 각각의 독립 배포로 연결하므로 이 ADR의 1:1 결정은 그대로 유지된다.

배경

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) 이다. 등급은 criticalstandard로 둔다.
  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 linthelm template이 확인한다.

전제

아래가 깨지면 이 결정을 재검토한다.

  1. Agent Builder는 MCP를 개별 등록하고, 한 Agent가 여러 MCP의 Tool을 사용할 수 있다.
  2. Tool 호출은 한 요청에 하나이며(ADR-0002) 그 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의 Tool 노출 상한 50개는 한 Agent가 여러 MCP에서 가져온 Tool의 합계에 적용된다. MCP를 나눈다고 상한이 늘지 않는다.

채택하지 않은 대안

M:N — 한 MCP가 여러 Tool Service를 본다. 배포 수는 줄지만 위의 격리 문제가 그대로 남는다. 가용성이 분할의 목적이므로 목적과 수단이 어긋난다.

코드에서 bundle 1개를 강제한다. McpProperties에 검증을 넣으면 다중 bundle 병합 코드가 도달 불가능해진다. 전제 3이 깨질 때 되돌리는 비용이 커지고, 이미 작성·테스트된 경로를 죽은 코드로 만든다. 1:1은 애플리케이션 불변식이 아니라 배포 결정이므로 배포 정의에서 잠그는 편이 맞다. 이 선택은 검증 위치를 옮긴 것이지 검증을 뺀 것이 아니다.