Files
dap-was-dapms/docs/contracts/tool-service-mcp/TEMP-tool-list-loading-guide.md
janghw 87be952fd0
All checks were successful
Deploy Gateway / deploy (push) Successful in 2m36s
Implement project updates and refactor related functionality
2026-09-17 20:36:08 +09:00

10 KiB

임시 안내: Tool 목록 최초 적재와 tools/list 노출 흐름

상태: 임시 학습 문서 · 기준: 현재 MCP 서버 구현 · 대상: Tool Service 개발 파트

이 문서는 현재 동작을 이해하기 위한 안내다. 외부 wire 계약의 정본은 Tool Service-MCP Bundle 조회 계약 v0.2다.

먼저 구분할 것

Tool Service가 MCP 표준 tools/list를 직접 구현하는 구조가 아니다. Tool Service는 아래의 내부 매니페스트 endpoint를 제공하고, MCP Server가 이를 읽어 Agent Builder용 표준 tools/list 응답으로 변환한다.

Tool Service -- GET /tool-manifest --> MCP Server -- JSON-RPC tools/list --> Agent Builder

현재 운영 배포는 MCP 하나가 Tool Service Bundle 하나를 본다. 구현은 호환 목적으로 여러 Bundle의 병합도 지원하지만, Tool 개발 파트는 자기 Bundle 하나의 매니페스트만 제공하면 된다.

1. 최초 적재는 구현되어 있는가?

구현되어 있다. Spring 애플리케이션이 준비되면 ToolRegistryPreloader.preload()가 실행된다.

ApplicationReadyEvent
  -> 선택 Redis snapshot warm start (있으면 memory에 임시 적재)
  -> Tool Service manifest 즉시 조회
  -> 검증 성공한 전체 Tool 목록으로 memory snapshot 교체
  -> 선택 Redis cache 저장
  -> readiness 판단 가능

Redis는 선택 cache일 뿐이다. Redis가 없거나 실패해도 Tool Service 매니페스트 조회가 성공하면 정상 기동한다. 반대로 최초 조회와 선택 cache 모두 실패하면 애플리케이션 프로세스는 살아 있어도 usable Tool 목록이 없으므로 readiness는 DOWN이다. 다음 주기 조회에서 자동 재시도한다.

tools/list 요청이 기동 preload보다 먼저 들어와 memory snapshot이 비어 있으면, 요청 경로도 원천을 한 번 직접 조회해 cold start 공백을 메운다.

2. Tool Service에서 memory까지의 처리 순서

ToolBundleRegistryClient.fetchTools()
  -> ToolBundleDiscovery.discoverAll()
  -> GET {manifestUrl}
  -> bundleId / tools[] / Tool 필수 필드 검증
  -> ToolMetadata 생성 (endpoint는 Tool Service manifest의 top-level endpoint 또는 _meta.endpoint 사용)
  -> enabled=false Tool 제외
  -> immutable List<ToolMetadata>를 AtomicReference snapshot에 저장

실제 memory 저장소는 ToolRegistryServiceAtomicReference<List<ToolMetadata>>다.

  • 매니페스트 조회·검증에 성공했을 때만 새 immutable 목록으로 통째로 교체한다.
  • HTTP 오류, timeout, JSON 오류, 필수 필드 누락, 이름 규칙 위반은 기존 snapshot을 비우지 않는다.
  • Tool 하나만 걸러서 부분 반영하지 않는다. 매니페스트 하나가 잘못되면 해당 Bundle 전체를 거부한다.
  • 현재 운영은 Bundle 하나지만, 구현상 여러 Bundle이면 모두 사용 가능한 성공본이 있을 때만 하나의 snapshot을 교체한다.
  • 주기 refresh가 겹치면 single-flight로 하나의 원천 조회를 공유한다.

3. Agent Builder의 tools/list 요청은 어떻게 처리되는가?

Agent Builder는 공개 POST https://{mcpHost}{publicPath}로 JSON-RPC 요청을 보낸다. OpenShift Route는 해당 path의 MCP Service만 선택하고, 컨테이너가 같은 POST {publicPath}를 직접 처리한다.

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list",
  "params": {}
}

처리 경로는 다음과 같다.

McpController
  -> McpMethodHandlerRegistry
  -> ToolsListHandler
  -> ToolRegistryService.listTools()
  -> in-memory snapshot 읽기
  -> MCP SDK ListToolsResult 변환
  -> JSON-RPC result.tools 반환

memory snapshot이 이미 있으면 tools/list는 Tool Service나 Redis를 호출하지 않는다. 따라서 Tool Service가 잠시 느리거나 Redis가 장애여도 이미 적재한 목록은 바로 반환한다.

ToolsListHandler는 매니페스트 Tool 정의의 공개 필드만 MCP Tool로 만든다. _meta 안의 version, timeoutMillis, enabled와 MCP 내부의 endpoint는 절대 Agent Builder에 노출하지 않는다.

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "processing.contract.inquiry",
        "title": "계약 조회",
        "description": "계약번호로 계약의 기본 정보를 조회합니다.",
        "inputSchema": {
          "type": "object",
          "properties": {
            "contractNo": { "type": "string" }
          },
          "required": ["contractNo"]
        },
        "annotations": {
          "readOnlyHint": true
        }
      }
    ]
  }
}

정확한 Agent Builder 응답 fixture는 tools-list-response.json을 따른다.

4. Tool Service가 구현할 매니페스트 endpoint

Tool Service는 MCP 배포 설정에 등록된 manifestUrl에 대해 다음을 반환한다.

GET /tool-manifest
Accept: application/json

200 OK
Content-Type: application/json

이 요청은 사용자 Tool 실행이 아니라 MCP의 배경 metadata 갱신이다. 따라서 guid, 사원 식별자, Mcp-Session-Id 같은 요청 상관·사용자 header를 기대하면 안 된다.

현재 구현은 conditional GET을 보내지 않으므로 Tool Service는 우선 항상 200 OK와 전체 JSON을 반환하면 된다. 304 Not Modified와 ETag는 계약상 선택 사항이지만 현재 MCP 구현 범위가 아니다.

응답 규칙

{
  "bundleId": "insurance-processing",
  "revision": "2026-08-03T01",
  "tools": [
    {
      "name": "processing.contract.inquiry",
      "title": "계약 조회",
      "description": "계약번호로 계약의 기본 정보를 조회합니다.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "contractNo": {
            "type": "string",
            "description": "조회할 계약번호입니다.",
            "minLength": 1
          }
        },
        "required": ["contractNo"],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "_meta": {
        "version": "1.0.0",
        "timeoutMillis": 3000,
        "enabled": true
      }
    }
  ]
}
항목 Tool Service 규칙 MCP 처리
bundleId 필수. MCP 배포 설정의 Bundle id와 정확히 일치 다르면 Bundle 전체 거부
revision 선택. 변경 식별·운영 진단용 현재 호출 대상이나 공개 응답에는 사용하지 않음
tools 필수. 이 Bundle의 전체 상태를 배열로 반환 정상 빈 배열은 “노출 Tool 없음”으로 채택
name 필수. [A-Za-z0-9_./-]{1,64} 및 설정 namePrefix로 시작 위반 시 Bundle 전체 거부
description 필수. Agent Builder가 Tool 선택에 사용할 설명 그대로 tools/list에 공개
inputSchema 필수 JSON Schema object 그대로 공개하고 tools/call 전에 검증
title, annotations 선택 공개 정보 있으면 tools/list에 공개
_meta.version 필수 내부 metadata로만 사용, 공개하지 않음
_meta.timeoutMillis 선택 설정 상한 이하로 제한, 공개하지 않음
_meta.enabled 선택, 기본 true false면 memory snapshot과 tools/list에서 제외
outputSchema 현재 운영에서는 생략 structuredContent 미지원 상태라 선언하지 않음

baseEndpoint, Tool 실행 URL, credential은 매니페스트에 넣지 않는다. MCP가 실제 호출할 주소는 배포 설정의 baseEndpoint에서만 결정한다. 매니페스트 안의 endpoint 성격 필드는 있어도 읽지 않는다.

5. Tool Service가 알아야 할 실패 동작

Tool Service 매니페스트 결과 MCP 동작
200 + 전체 검증 통과 새 목록을 memory에 교체하고 다음 tools/list부터 노출
200 + JSON/필수 필드/이름 오류 직전 성공 목록 유지. 첫 기동이면 목록을 만들지 못함
timeout, 연결 실패, 4xx/5xx 직전 성공 목록 유지. 첫 기동이면 readiness DOWN
정상 tools: [] 빈 목록을 정상 전체 상태로 채택
Tool 하나만 제거한 정상 전체 manifest 다음 갱신에 그 Tool도 목록에서 제거

따라서 Tool Service는 manifest 응답을 부분 목록이나 증분 변경으로 보내면 안 된다. 한 번의 200 응답은 그 시점에 노출할 Tool의 완전한 목록이어야 한다.

Tool Service 구현 체크리스트

  1. GET /tool-manifest를 MCP Server namespace에서만 접근 가능하게 제공한다.
  2. bundleId가 배포 설정의 Bundle id와 정확히 일치하는지 배포 전에 함께 확인한다.
  3. 모든 Tool에 고유한 표준 name, 비어 있지 않은 description, object 형태의 inputSchema, _meta.version을 넣는다.
  4. Tool을 숨기려면 _meta.enabled: false를 쓰거나 정상 전체 목록에서 제거한다. 둘의 변경 반영 시점은 다음 refresh다.
  5. 실행 주소·credential·개인정보·업무 payload를 매니페스트에 넣지 않는다.
  6. Tool 자체 실행 endpoint는 manifest의 top-level endpoint 또는 _meta.endpoint에 선언한다. 상대 경로는 Portal registry의 serviceDomain 뒤에 붙고, 절대 HTTP(S) URL은 Tool Service가 제공한 실행 주소 원천으로 그대로 사용한다.

확인한 구현·테스트

  • 최초 preload: ToolRegistryPreloader
  • in-memory snapshot·실패 fallback: ToolRegistryService
  • HTTP 매니페스트 조회·필드 검증: ToolBundleDiscovery
  • tools/list 공개 필드 변환·_meta 제거: ToolsListHandler
  • 회귀 테스트: ToolRegistryServiceTest, ToolBundleDiscoveryTest, ToolsListHandlerTest

자세한 field 정의와 실행 계약은 v0.2 계약, 실제 manifest 전체 예시는 manifest-response.json을 참고한다.