Files
dap-was-dapms/docs/contracts/portal-mcp/protocol-v0.1-registry.md
koseokmin cb29b192b4 docs를 저장소로 되돌리고 계약 예제를 복원한다
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>
2026-08-22 23:27:56 +09:00

6.2 KiB

Portal-MCP Tool Server Registry 계약 v0.1

  • 상태: Implemented (MCP 서버 측 구현 완료, 포털 측 합의 대기)
  • 기준일: 2026-08-22
  • 조회 위치: mcp.portal.registry-url — HTTP(S) Portal API 또는 file:/classpath: 로컬 리소스
  • 활성 조건: mcp.portal.enabled=true
  • 구현: PortalToolRegistryClient

1. 계약 범위와 원칙

포털은 route별로 어떤 Tool Server가 있는가만 알려 준다. Tool 목록과 Tool 실행 주소는 포털이 아니라 각 Tool Server의 매니페스트에서 온다(Tool Service-MCP Bundle 조회 계약 v0.2, ADR-0010).

원칙 내용
MCP가 가져온다 포털은 registry를 제공만 한다. MCP에 push하지 않는다
포털은 route와 Tool Server만 소유 serviceDomainmanifestPath까지다. Tool 목록·실행 endpoint는 매니페스트가 정한다
registry는 전체 상태 응답은 그 시점 route 전체다. 증분 없음
route 단위 격리 한 route의 manifest 조회 실패가 다른 route의 snapshot을 지우지 않는다
요청 경로는 조회하지 않는다 tools/list·tools/call과 route key 검증은 in-memory snapshot만 본다

2. 응답 형태

두 가지를 모두 받는다. routes가 배열이면 집계형으로, 아니면 단일 route로 해석한다.

집계형 — 한 번의 호출로 모든 route를 받는다. 운영에서 사용한다.

{
  "registryRevision": "portal-registry-2026-08-22-01",
  "routes": [
    {
      "routeKey": "cus",
      "toolServices": [
        {
          "serviceKey": "was-cus",
          "serviceDomain": "https://tool-cus.devjun.net",
          "manifestPath": "/tool-manifest",
          "namePrefix": "",
          "status": "ACTIVE"
        }
      ]
    }
  ]
}

단일 route형routes 없이 최상위에 routeKeytoolServices를 둔다.

{
  "routeKey": "cus",
  "toolServices": [ { "serviceKey": "was-cus", "serviceDomain": "https://tool-cus.devjun.net", "manifestPath": "/tool-manifest", "status": "ACTIVE" } ]
}

3. 필드

필드 위치 필수 MCP 처리
routes[] 최상위 선택 배열이면 집계형. 없으면 최상위를 단일 route로 읽는다
routeKey route 필수 정규화 후 route 식별자. 요청 URL /mcp/{routeKey}와 대조한다
toolServices[] route 필수 이 route가 보는 Tool Server 목록
serviceKey service 필수 bundle id로 사용. 매니페스트의 bundleId와 일치해야 한다
serviceDomain service 필수 Tool Server 주소. 후행 /는 제거한다. 매니페스트의 상대 endpoint를 절대 URL로 바꾸는 기준이 된다
manifestPath service 필수 매니페스트 경로. serviceDomain + manifestPath가 조회 주소다
status service 선택 기본 ACTIVE. 대소문자 무시하고 ACTIVE가 아니면 그 서비스를 건너뛴다
namePrefix service 선택 기본 "". Tool 이름 접두사 검증에 사용한다
registryRevision 최상위 선택 변경 진단·로그용. 호출 대상 결정에는 쓰지 않는다

MCP가 읽지 않는 필드가 있다. 현재 구현은 executeBasePath, displayName, toolEndpoints를 무시한다. 응답에 있어도 오류가 아니지만 동작에 영향을 주지 않으므로, 포털이 이를 근거로 실행 주소를 통제할 수 있다고 가정하면 안 된다.

4. 실패 동작

상황 MCP 동작
registry 조회 실패 이미 확보한 in-memory endpoint 목록 유지. memory가 비어 있으면 mcp.redis.portal-registry-key의 Redis fallback을 읽는다
Redis fallback도 실패 원천 미확보로 처리하고 다음 주기에 재시도
route에 ACTIVE 서비스가 하나도 없음 Portal registry has no active Tool Service로 그 route 조회 실패
한 Tool Server의 매니페스트에 사용 가능한 성공본이 없음 그 route 전체를 실패 처리. 부분 목록을 채택하지 않는다
route 간 Tool name 중복 또는 maxToolsTotal 초과 같은 이유로 실패 처리
registry에서 사라진 route 다음 갱신에 in-memory snapshot에서도 제거

route 단위 격리와 catalog 교체 규칙은 Tool Service 계약 v0.2 §7과 같은 원칙을 따른다. 조회는 route마다 독립이지만, 한 route 안에서는 전부 아니면 전무다.

5. 갱신 주기

  • mcp.portal.refresh-interval-seconds (기본 300초): 포털 registry만 다시 읽는다.
  • mcp.registry.refresh-interval-seconds: 이미 확보한 Tool Server 목록의 매니페스트만 다시 읽는다.

기동 preload는 포털 registry를 먼저 호출한 뒤 매니페스트를 조회한다. 두 주기는 독립이다.

6. 로컬 검증

registry-urlfile: 또는 classpath: 리소스를 지정하면 포털 서버 없이 같은 계약으로 읽는다. 이후 매니페스트 조회·route별 snapshot 갱신·Redis fallback 규칙은 HTTP API를 쓸 때와 동일하다.

mcp:
  portal:
    enabled: true
    registry-url: file:./config/local-toolserver-info-sample-v1.json
    refresh-interval-seconds: 15

7. 저장소의 샘플 파일

두 샘플이 있고, 현재 서로 다르다. 이 계약을 정본으로 삼고 맞춰야 한다.

파일 용도 이 계약과의 차이
config/local-toolserver-info-sample-v1.json 로컬 검증용 MCP가 읽지 않는 displayName을 포함
deploy/portal-registry.json 배포 참고용 MCP가 읽지 않는 executeBasePath를 포함하고 registryRevision이 없다

8. 열린 항목

  • 포털 API의 인증 방식은 이 계약이 정하지 않는다. MCP는 인증·인가를 하지 않으므로(ADR-0006) 네트워크 경계에서 통제한다.
  • registryRevision의 형식을 문자열로 고정할지 정하지 않았다. 현재 구현은 값을 로그·진단에만 쓰므로 형식에 의존하지 않는다.
  • 즉시 refresh 알림: 주기 반영으로 부족하다는 운영 근거가 생길 때 검토한다.