Update project functionality and configuration

This commit is contained in:
2026-08-14 17:59:07 +09:00
parent a4eb5a580f
commit 189277a78c
113 changed files with 4838 additions and 348 deletions

View File

@@ -66,7 +66,7 @@ MCP는 Agent Builder가 `tools/call`에 명시한 단일 Tool을 실행한다. T
| `TraceLogger` | `observability` | context의 guid/requestId를 직접 포함하는 최소 key=value 경계 로그. 사원 식별자는 기록하지 않음 |
| `McpExceptionHandler` | `transport/http` | JSON parse, JSON-RPC, 예상 밖 오류의 표준 response 변환 |
Jackson 3 databind 모델은 `tools.jackson.databind.*`을 사용한다. 이 조합의 annotation API는 `com.fasterxml.jackson.annotation.*` namespace로 제공되므로, 이를 `tools.jackson.annotation.*`으로 바꾸지 않는다. Registry 응답의 unknown field 무시는 회귀 테스트로 검증한다.
Spring Boot 3.5가 관리하는 Jackson 2 databind 모델과 annotation API는 `com.fasterxml.jackson.*` namespace를 사용한다. Registry 응답의 unknown field 무시는 회귀 테스트로 검증한다.
MCP Java SDK는 protocol 상수, 표준 result 모델과 JSON Schema validator에만 사용한다. SDK/Spring AI MCP
Starter와 transport는 활성화하지 않으며 기존 `/mcp`, 보안, correlation, Registry, Tool 실행 경계를 유지한다.
@@ -148,7 +148,7 @@ Tool 호출 직전마다 `remainingMillis()`로 남은 예산을 계산해 read
## Protocol version 협상과 검증
- 서버는 `mcp.protocol.supported-versions``mcp.protocol.preferred-version`으로 지원 버전을 명시적으로 관리한다. preferred version은 반드시 supported versions에 포함되어야 한다.
- `initialize` 응답은 요청의 JSON-RPC `id`를 그대로 반환하며, preferred version과 `serverInfo(name/title/version)`, `capabilities.tools.listChanged=false`를 제공한다.
- `initialize` 응답은 요청의 JSON-RPC `id`를 그대로 반환하며, preferred version과 `serverInfo(name/title/version)`, `capabilities.tools.listChanged=true`를 제공한다.
- 이 서버는 stateless이므로 협상 결과를 session에 저장하지 않는다. `initialize` 이후 Agent Builder는 모든 MCP HTTP 요청에 `MCP-Protocol-Version: <initialize 응답 protocolVersion>`을 포함해야 하며, 서버는 매 요청을 독립적으로 검증한다.
- header가 누락되거나 지원하지 않는 값이면 JSON-RPC error가 아닌 HTTP `400 Bad Request`를 반환한다. 오류 body는 `error`, `message`, `supportedVersions`, `guid`를 포함해 호출자가 올바른 header를 진단할 수 있게 한다.
@@ -156,7 +156,7 @@ Tool 호출 직전마다 `remainingMillis()`로 남은 예산을 계산해 read
- `initialize`의 JSON-RPC result는 server protocol/capability 정보를 제공하고, HTTP response header `Mcp-Session-Id`에는 새 UUID를 제공한다.
- Agent Builder는 이 값을 `notifications/initialized`, `tools/list`, `tools/call``Mcp-Session-Id` header에 보낸다. 각 HTTP 요청은 별도 `x-request-id`를 유지한다.
- Agent Builder는 MCP 2025-06-18 lifecycle에 따라 `notifications/initialized`를 보낸다. 서버는 이를 저장하거나 이후 요청의 readiness gate로 사용하지 않는다.
- Agent Builder는 MCP 2025-11-25 lifecycle에 따라 `notifications/initialized`를 보낸다. 서버는 이를 저장하거나 이후 요청의 readiness gate로 사용하지 않는다.
- `InitializedNotificationHandler`는 id 없는 notification을 HTTP 202으로 수용한다. 이는 Tool 실행 준비 상태를 메모리에 세우는 동작이 아니므로 replica 간 affinity가 필요 없다.
## Tool metadata 갱신 장애 시나리오
@@ -185,8 +185,8 @@ rolling update 중 새 Pod이 빈 catalog로 기존 정상 Pod을 대체하지
각 bundle은 이번 성공본 또는 직전 성공본이 있어야 aggregate를 확정할 수 있다. 조회 실패는 Tool 삭제로 해석하지 않으며, 성공한 매니페스트에서 빠진 경우에만 삭제를 반영한다. 이름 충돌이나 총량 상한 초과도 전체 갱신 실패로 처리한다. 동시에 여러 refresh가 들어오면 single-flight로 하나의 원천 조회 결과를 공유한다.
캐시에서 Tool을 찾지 못하면 stale snapshot 가능성을 고려해 원천을 한 번 더 조회한 뒤 `-32001`을 결정한다.
Redis는 요청 경로의 의존성이 아닌 선택적인 warm-start cache다. key 형식, TTL, 공유 범위, 고가용성·보안 정책은
캐시에서 Tool을 찾지 못하면 stale snapshot 가능성을 고려해 원천을 한 번 더 조회한 뒤 `-32001`을 결정한다. 반대로 캐시에는 있던 Tool이 실행 시점에 upstream 404 또는 410을 반환하면 삭제된 Tool을 아직 들고 있는 stale snapshot 신호로 보고, 현재 요청은 Tool 실행 실패로 유지한 채 해당 route의 manifest refresh를 best-effort로 즉시 시도한다. 같은 route에서 삭제된 Tool 호출이 몰릴 때 Tool Server manifest 호출이 폭증하지 않도록 짧은 cooldown을 적용한다.
Redis는 요청 경로의 의존성이 아닌 선택적인 warm-start cache다. Tool snapshot은 route별 key(`key-prefix:identity:v2:route:{routeToken}`)로 분리해 서로 다른 route의 Tool 목록이 섞이지 않게 하며, Portal registry fallback key와도 분리한다. key 형식, TTL, 공유 범위, 고가용성·보안 정책은
아직 확정하지 않았으며 [extension-points.md](extension-points.md#운영-적용-전-필수-보완)에서 합의한다. 현재 구현값은
운영 계약이나 장기 설계 결정이 아니다.
@@ -194,6 +194,10 @@ Redis는 요청 경로의 의존성이 아닌 선택적인 warm-start cache다.
로컬 Agent Builder 연동 검증도 실제 Tool Service와 같은 매니페스트 조회 흐름을 먼저 사용한다. `application-local.yml`의 bundle URL을 조회하고, **처음 조회가 실패했을 때만** `fallback-manifest-file`의 manifest sample을 snapshot으로 채택한다. 기본 sample은 프로젝트 루트의 `config/local-core-tools-manifest-sample-v1.json`이다. 원격 조회가 이후 성공하면 즉시 원격 목록으로 교체하며, 이미 확보한 원격 성공본은 local sample로 덮어쓰지 않는다.
## Portal Registry and Tool manifest refresh
Portal Registry를 사용하는 구성에서는 포털을 route별 Tool Server endpoint 목록의 원천으로만 사용한다. MCP는 기동 preload 때 포털 registry API를 먼저 호출해 endpoint 목록을 확보한 뒤 Tool Server `tool-manifest`를 조회한다. 이후에는 `mcp.registry.refresh-interval-seconds` 주기로 저장된 endpoint 목록에 대해 manifest만 다시 조회하고, `mcp.portal.refresh-interval-seconds` 주기로 포털 registry만 별도로 갱신한다. 포털 `registryRevision`은 포털 응답 JSON 변경 로그와 endpoint 목록 변경 진단에 사용하며, Tool Server 내부 tool/schema/revision 변경 감지는 MCP의 manifest 주기 조회 결과를 route별 in-memory snapshot에 다시 병합하면서 처리한다. 요청 경로의 `tools/list``tools/call`은 계속 in-memory snapshot만 읽는다. Portal API 조회가 실패하면 이미 확보한 in-memory endpoint snapshot을 유지하며, cold start처럼 memory가 비어 있을 때만 `mcp.redis.portal-registry-key`의 Redis registry JSON을 fallback으로 읽는다. 이 Portal registry fallback은 route 목록과 endpoint 목록 확보용이고, route별 Tool snapshot Redis key는 이미 알고 있는 route의 마지막 Tool 목록 fallback에만 사용한다. Redis fallback도 실패하면 endpoint 원천을 확보하지 못한 것으로 처리하고 다음 주기에서 재시도한다.
노출 대상 Tool은 그 파일이 정의한다. 목록을 이 문서에 옮겨 적지 않는다. 파일의 공개 필드는 그대로 보존하고 `_meta` 실행 정보만 제거해 `tools/list`에 내보낸다. fallback도 원격 매니페스트와 같이 설정된 `base-endpoint`에 요청 name을 path segment로 붙여 `tools/call`을 POST한다.
이 fixture는 연동 확인용이며 실제 고객·계약·수납·지급 데이터를 담지 않는다.
@@ -214,3 +218,6 @@ Redis는 요청 경로의 의존성이 아닌 선택적인 warm-start cache다.
- correlation 변경: header extractor → filter/context 정리 → response/downstream header
- 공개 path/배포 변경: topology의 path 유일성 → Route host/path/Service → ConfigMap endpoint → Controller·Filter → Agent Builder 등록 URL
- 최종 확인: `.\gradlew.bat clean check`, `bootJar`, 실행 JAR의 initialize → notification → tools/list 흐름
## Tool list change notification
`initialize``capabilities.tools.listChanged=true`를 선언한다. 배경 Registry refresh가 기존 route snapshot과 다른 Tool 목록을 성공적으로 확보하면 `ToolListChangedEvent`가 표준 `notifications/tools/list_changed` JSON-RPC notification envelope를 만든다. 현재 HTTP 단발 응답 transport는 notification을 직접 push하지 않으며, SSE/Streamable HTTP 전송 계층이 추가되면 이 이벤트를 route별 Agent 연결에 전달하고 Agent Builder가 `tools/list`를 다시 호출한다.