ADR-0007은 MCP 배포 하나가 Tool Service 하나만 보게 했으나 구현은 이미 Portal이 route와 endpoint를 소유하는 구조다. 결정 문서가 없어 ADR-0007이 Accepted로 남은 채 코드와 정반대되는 내용을 현재 설계 근거처럼 제시하고 있었다. ADR-0013이 그 경로를 확정하고 ADR-0007 전체와 ADR-0009 결정 4를 대체한다. ADR-0010은 Tool 실행 주소의 소유자가 설정이 아니라 Tool Service 매니페스트라는 6078852의 결정을 사후 기록한다. 두 ADR이 정본으로 인용하는 Portal-MCP 계약 v0.1도 함께 넣는다. ADR-0013은 main 현재 코드에 맞춰 세 곳을 고쳤다. route 간 갱신 격리 부재는 6653030이 해소해 격리 표로 옮겼고, 제거된 mcp.portal.route-key 항목은 뺐다. 남은 위험 둘(Portal 모드 warm start 미동작, readiness의 route별 상태 미노출)은 코드에서 유효함을 확인해 남긴다. ADR-0010이 기록하는 대로 endpoint 검증에는 도메인 허용목록이 없고 NetworkPolicy도 Ingress만 선언한다. 매니페스트 원천의 신뢰성이 곧 outbound 대상의 신뢰성이다. McpProperties와 application.yml의 주석이 이 결정과 반대로 남아 있다는 사실도 ADR에 적었다. 코드는 바꾸지 않는다. 192개 테스트 통과. 문서 상대링크 깨짐 없음. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
102 lines
7.2 KiB
Markdown
102 lines
7.2 KiB
Markdown
# 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)
|
||
|
||
> 이 문서는 당시 검토 이력을 보존한다. 내부망 운영은 endpoint 목록과 route 매핑의 원천을 Portal로 옮겼으므로
|
||
> 현재 구현과 신규 연동에는 [ADR-0013](ADR-0013-portal-owns-route-and-endpoint-registry.md)을 적용한다.
|
||
> 아래 격리 논거는 폐기된 것이 아니라 ADR-0013이 무엇을 포기했는지 판단하는 근거로 남는다.
|
||
|
||
외부에서 여러 MCP를 하나의 host 아래 path로 묶는 방식은 [ADR-0009](ADR-0009-container-handles-public-mcp-path.md)이
|
||
소유한다. 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)** 이다. 등급은 `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은 애플리케이션 불변식이 아니라 **배포 결정**이므로 배포 정의에서 잠그는 편이 맞다.
|
||
이 선택은 검증 위치를 옮긴 것이지 검증을 뺀 것이 아니다.
|