Files
dap-was-dapms/docs/decisions/ADR-0009-container-handles-public-mcp-path.md
koseokmin 1e6fa8f22f Portal을 endpoint 원천으로 확정하고 route 단위 실패 격리를 적용한다
내부망 운영은 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>
2026-08-17 18:10:34 +09:00

38 lines
2.3 KiB
Markdown

# ADR-0009 컨테이너가 공개 MCP path를 직접 처리한다
- 상태: Accepted
- 결정일: 2026-08-05
- 대체: [ADR-0008](ADR-0008-shared-host-path-routing.md)
- 부분 대체됨: 결정 4는 [ADR-0010](ADR-0010-portal-owns-route-and-endpoint-registry.md)이 대체한다
- 관련: [ADR-0007](ADR-0007-one-mcp-per-tool-service.md)
## 배경
Agent Builder는 `/mcp/core`, `/mcp/process`, `/mcp/information`처럼 sub path까지 포함한 URL을 각각의 MCP로 등록한다. 각 URL은 독립 MCP 컨테이너와 Tool Service에 연결된다. 공개 path를 OpenShift Route가 내부 `/mcp`로 바꾸면 Agent Builder가 등록한 경로와 컨테이너가 처리한 경로가 달라져 운영 추적과 설정 검증이 어려워진다.
## 결정
1. `deployments.<key>.publicPath`는 Agent Builder 등록 URL과 컨테이너 endpoint가 함께 사용하는 단일 정본이다.
2. OpenShift Route는 host와 path로 독립 MCP Service만 선택하고 path를 rewrite하지 않는다.
3. Helm ConfigMap이 `publicPath``mcp.endpoint-path`로 주입하고, Controller와 Filter가 그 경로만 처리한다.
4. MCP 컨테이너 하나는 endpoint와 Tool Service를 각각 하나만 가진다. 하나의 Java 프로세스에서 path별 Registry를 선택하지 않는다.
5. 요청 body·header·Tool name은 컨테이너 선택에 관여하지 않는다.
```text
https://{host}/mcp/core -> core MCP /mcp/core -> core Tool Service
https://{host}/mcp/process -> process MCP /mcp/process -> process Tool Service
https://{host}/mcp/information -> information MCP /mcp/information -> information Tool Service
```
## 영향
- 같은 host에서 여러 Service를 사용하므로 OpenShift Route의 path 기반 Service 선택은 유지한다.
- rewrite annotation은 사용하지 않는다. access log와 애플리케이션 log가 같은 path를 본다.
- `publicPath` 변경은 Route와 애플리케이션 endpoint를 함께 바꾸며 Agent Builder 등록 정보도 갱신해야 한다.
- Registry snapshot, Redis namespace, readiness, connection pool과 장애 범위는 배포별로 계속 분리된다.
- JSON-RPC payload와 Tool Service wire 계약은 바뀌지 않는다.
## 대체한 결정
ADR-0008의 공유 host와 독립 Deployment 원칙은 유지한다. 공개 path를 내부 `/mcp`로 rewrite한다는 부분만 이 ADR이 대체한다.