5cfb8a1이 .gitignore에 docs/를 넣고 68개 파일을 지웠다. 그런데
AgentBuilderContractExampleTest, ToolBundleContractExampleTest,
ArchitectureDocumentContractTest는 docs/ 아래 계약 예제와 architecture
문서를 입력으로 직접 읽는다. 그 결과 clean clone에서 테스트 10건이
입력을 찾지 못해 실패했다.
제외 범위를 원래 의도대로 좁힌다. 에이전트 산출물(AGENTS.md, .agents/,
.codex/, docs/superpowers/)은 계속 제외하고 저장소 문서는 추적한다.
문서는 삭제 직전 상태(3de052a)를 기준으로 복원하고, 그 위에 main 코드와
대조해 어긋난 부분을 고쳤다.
- ADR-0007을 Superseded로 바꾸고 ADR-0013을 새로 쓴다. route당 Tool
Service N개가 최종안이며, PortalToolRegistryClient가 이미 route별로
N개를 유지하고 있는데 ADR-0007은 "bundles는 항상 한 항목"을 Accepted
상태로 주장하고 있었다. ADR-0009 결정 4도 부분 대체한다.
- ADR-0008 파일 헤더가 Accepted였으나 ADR-0009가 이미 대체한 상태였다.
- 6078852의 endpoint 소유권 반전이 반영되지 않은 서술을 계약 v0.2,
bundle 설정 예제, Tool 적재 안내에서 고친다.
- Portal registry 계약 v0.1과 route key 규약을 새로 문서화한다. 둘 다
구현은 있는데 계약 문서가 없었다.
- MCP SDK 2.0.0 SBOM을 추가한다.
번호 주석: ADR-0011과 0012는 feature/mcp-integration이 Tool inputSchema
정책에 쓰고 있어 비워 둔다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
102 lines
7.1 KiB
Markdown
102 lines
7.1 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)
|
||
|
||
> 이 문서는 검토 이력을 보존한다. 현재 구현과 신규 연동에는 [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은 애플리케이션 불변식이 아니라 **배포 결정**이므로 배포 정의에서 잠그는 편이 맞다.
|
||
이 선택은 검증 위치를 옮긴 것이지 검증을 뺀 것이 아니다.
|