내부망 운영은 route↔Tool Service 매핑을 Portal이 소유하고, MCP 배포 하나가 N개 route를 서비스하며, route 하나에 N개 Tool Service가 붙을 수 있다. 이 판단을 ADR-0010으로 남기고 ADR-0007 전체와 ADR-0009 결정 4를 대체한다. 계약 - Portal-MCP registry 조회 계약 v0.1과 예제 JSON을 docs/contracts/portal-mcp/에 신설한다. 지금까지 이 경로에는 정본이 없었다. - 예제를 PortalToolRegistryClient의 실제 파싱 경로에 태우는 계약 테스트를 추가해 문서와 구현이 따로 표류하지 않게 한다. 실패 격리 - fetchAllTools()의 실패 전파를 route 단위로 격리한다. 계약 v0.2의 "aggregate는 전부 아니면 전무"는 카탈로그 하나를 전제한 규칙인데, route가 N개가 되면서 전 route로 확대돼 있었다. Tool Service 하나의 장애가 cold start에서 Pod 전체를 내리고 steady state에서 모든 route의 갱신을 멈추던 동작을 없앤다. - 제거 판단의 원천을 ToolRegistryClient.knownRoutes()로 분리한다. 조회 결과를 기준으로 지우면 이번 주기에 실패한 route의 정상 snapshot까지 사라져 "어떤 실패도 목록을 비우지 않는다" 불변식이 깨진다. - readiness는 최소 1개 route로 UP을 유지한다. 모든 route를 요구하면 정상 route까지 트래픽에서 빠져 위 격리를 되돌리기 때문이다. 대신 routesWithoutSnapshot을 health detail로 노출해 관제가 부분 상태를 감지하게 한다. 설정과 기동 - warm start가 route별 Redis key를 읽도록 확장하고, 읽을 key를 알기 위해 기동 preload 순서를 registry 조회 → warm start → manifest 조회로 바꾼다. - 어떤 코드도 읽지 않던 mcp.portal.route-key를 제거한다. route key는 요청 URI에서만 결정되며, 설정으로 보정하면 잘못된 단일 진입점 호출이 조용히 성공한다. 정리 - ToolRegistryService.java의 이중 인코딩으로 깨져 있던 한글 Javadoc 33줄을 코드 동작에 맞춰 다시 쓰고, replaceSnapshot 위에 겹쳐 있던 고아 Javadoc 블록을 지운다. - Helm chart는 mcp.bundles 구성에서 계속 유효하므로 삭제하지 않고, 내부망 운영 대상이 아니라는 사실을 deploy/README.md와 values.yaml에 명시한다. 검증: 이 환경은 loopback이 막혀 gradlew check를 실행하지 못했다. CodeStyleContract가 보는 항목(줄바꿈, 탭, 행말 공백, 파일 끝 개행, 미사용 import, import 순서)은 변경된 Java 13개 파일에 대해 따로 재현해 확인했다. 컴파일과 테스트 실행은 미확인이다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Portal-MCP 계약 문서
이 디렉터리는 Portal과 MCP Server 사이의 Tool Server endpoint 목록 조회 계약을 관리한다.
Portal ──[portal-mcp 계약]──▶ MCP Server ──[tool-service-mcp 계약]──▶ Tool Service
(endpoint 목록) (Tool 목록과 실행)
| 문서 | 상태 | 용도 |
|---|---|---|
| protocol-v0.1-registry.md | MCP 측 구현 완료, Portal 측 미합의 | Portal registry 조회 요청·응답과 실패 처리 계약 |
이 계약이 존재하는 이유
mcp.bundles로 배포 YAML에 Tool Service를 직접 선언하는 구성에서는 이 계약이 필요 없다.
Portal이 route별 Tool Server 목록을 관리하는 구성(mcp.portal.enabled=true)에서만 사용하며,
이때 Portal은 endpoint 목록의 원천이 된다.
내부망 운영은 이 구성을 채택했다(ADR-0010). 따라서 이 계약은 선택 사항이 아니라 운영 경로의 정본이다. 배포 하나가 N개 route를 서비스하고 route 하나에 N개 Tool Service가 붙을 수 있다.
현재 원칙
- Portal은 어디에 Tool Server가 있는가만 답한다. 어떤 Tool이 있는가는 여전히 Tool Service 매니페스트가 답한다.
- MCP는 Portal registry와 Tool Service 매니페스트를 서로 다른 주기로 조회한다.
- Portal 조회 실패는 목록을 비우지 않는다. in-memory endpoint snapshot을 유지하고, cold start일 때만 Redis fallback을 읽는다.
- 요청 경로(
tools/list,tools/call)는 Portal을 호출하지 않는다. in-memory snapshot만 읽는다. - 이 구성에서 outbound 주소의 원천은 배포 YAML이 아니라 Portal이다. 따라서 Portal은 신뢰 경계 안에 있어야 하며, MCP→Portal 구간은 network 수준에서 제한한다. 근거와 요구사항은 v0.1 계약 §8에 있다.
예제와 검증
examples/registry-v0.1의 응답 JSON을 PortalRegistryContractExampleTest가 직접 읽어
PortalToolRegistryClient의 실제 파싱 경로에 태운다. 예제와 구현은 같은 변경에서 함께 고친다.
현재 producer는 외부망 검증용 PoC다
운영 Portal은 아직 이 API를 제공하지 않는다. 현재 응답을 만드는 것은 외부망 통합 검증용 PoC Portal이며, 이 계약 문서가 PoC와 운영 Portal이 공유해야 할 유일한 정본이다. PoC 구현이 저장소를 떠나도 이 문서와 예제는 남는다.
운영 적용 전에 Portal 개발 파트와 다음 항목을 확정한다.
- Portal registry API의 인증 방식과 MCP→Portal NetworkPolicy
- Tool Service 매니페스트 조회용 credential 전달 경로 (현재 registry 응답에 없다, §9)
registryRevision채번 주체와 단조 증가 보장 범위- route key 명명 규칙과 route 삭제 시 rolling 절차
상세 필드와 실패 처리는 v0.1 계약을 따른다.