Use manifest-defined endpoints for tool routing
All checks were successful
Deploy Gateway / deploy (push) Successful in 2m35s

This commit is contained in:
2026-08-19 11:43:24 +09:00
parent 422dd8c6e7
commit 60788525d6
19 changed files with 318 additions and 107 deletions

View File

@@ -30,8 +30,8 @@ MCP는 Agent Builder가 `tools/call`에 명시한 단일 Tool을 실행한다. T
6. `McpMethodHandlerRegistry`가 method를 명시적 handler에 연결한다.
7. `tools/list``ToolRegistryService`의 in-memory snapshot에서 실행 metadata를 얻는다. 요청 경로는 Redis를 호출하지 않으므로 Redis 장애·지연이 응답에 영향을 주지 않으며, snapshot이 비어 있는 기동 직후에만 Tool catalog provider를 한 번 조회한다. 이후 `ToolsListHandler`가 MCP SDK의 `Tool``ListToolsResult`로 변환한다. local 기본 구성은 Tool Service 매니페스트를 먼저 조회하고, 최초 실패 시 bundle별 local manifest sample을 cold-start fallback으로 사용한다. 운영은 이 배포가 보는 Tool Service 매니페스트의 사용 가능한 성공본만 원천으로 사용한다.
8. `tools/call``ToolsCallHandler`가 표준 MCP의 `params.name`과 object인 `params.arguments`를 검증하고 추출한다.
9. `ToolExecutionService`가 표준 Tool name으로 metadata를 확정하고 argument schema를 검증한다. `ToolRoutingService`안전한 Tool name을 설정의 base endpoint 뒤에 붙여 `POST {baseEndpoint}/{toolName}` 요청과 metadata timeout을 만든다. Agent Builder가 보낸 `arguments` 객체는 JSON raw body로 전달하며 MCP가 Tool을 대체 선택하지 않는다.
10. `arguments`의 어떤 field도 outbound URL 선택에 사용하지 않는다. endpoint는 local catalog의 `_meta.endpoint` 또는 운영 배포 설정의 `baseEndpoint`에서 가져오므로 Agent Builder 입력이나 Tool Service 매니페스트로 outbound 대상이 바뀌지 않는다.
9. `ToolExecutionService`가 표준 Tool name으로 metadata를 확정하고 argument schema를 검증한다. `ToolRoutingService`snapshot에 저장된 정확한 Tool endpoint와 metadata timeout으로 HTTP 요청을 만든다. Agent Builder가 보낸 `arguments` 객체는 JSON raw body로 전달하며 MCP가 Tool을 대체 선택하지 않는다.
10. `arguments`의 어떤 field도 outbound URL 선택에 사용하지 않는다. Portal registry는 Tool Server의 `serviceDomain``manifestPath`만 제공하고, Tool별 실행 endpoint는 Tool Server manifest의 top-level `endpoint` 또는 `_meta.endpoint`에서 가져온다. manifest endpoint가 절대 HTTP(S) URL이면 Tool Server가 제공한 실행 주소 원천으로 허용하고, 상대 경로이면 Portal registry의 `serviceDomain` 뒤에 붙인다.
11. `HttpToolClient`가 JDK 공유 HTTP client의 connection pool을 사용해 correlation 헤더와 함께 POST를 실행한다. arguments는 JSON body로 전달하며 Tool read timeout은 metadata timeout과 요청 전체 deadline의 남은 시간 이하로 제한한다. Authorization 전달은 설정으로 통제한다.
12. Tool 응답은 요청 payload와 분리해 `response.data`만 사용한다. plain text는 그대로, JSON object/array는 compact JSON string으로 MCP SDK `CallToolResult`/`TextContent``result.content[0].text`에 넣고 outer JSON serializer가 escaping을 처리한다. 호출 소요 시간(ms)은 `result.content[0]._meta.searchTime`으로 반환하고, 정상 결과에도 `isError: false`를 명시한다. Tool 실행·timeout·권한 오류는 JSON-RPC error가 아니라 `isError: true` result로 변환한다. JSON-RPC envelope/params/method 및 서버 구성 오류는 최상위 JSON-RPC `error`로 반환한다.
13. local과 운영 모두 같은 `name` lookup, endpoint/timeout, inputSchema validation 경로를 사용한다.
@@ -198,9 +198,9 @@ Redis는 요청 경로의 의존성이 아닌 선택적인 warm-start cache다.
로컬 검증에서는 `mcp.portal.registry-url``file:./config/local-toolserver-info-sample-v1.json` 같은 Spring resource location으로 지정할 수 있다. 이 경우 MCP는 기동 preload와 주기 endpoint refresh에서 Portal HTTP API를 호출하지 않고 프로젝트 안의 registry JSON을 읽는다. 파일에서 확보한 endpoint 목록 이후의 Tool Server `tool-manifest` 주기 조회, route별 in-memory snapshot 갱신, Redis fallback 규칙은 Portal API를 사용할 때와 동일하다.
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 원천을 확보하지 못한 것으로 처리하고 다음 주기에서 재시도한다.
Portal Registry를 사용하는 구성에서는 포털을 route별 Tool Server 목록의 원천으로만 사용한다. MCP는 기동 preload 때 포털 registry API를 먼저 호출해 `serviceDomain``manifestPath` 확보한 뒤 Tool Server `tool-manifest`를 조회한다. 이후에는 `mcp.registry.refresh-interval-seconds` 주기로 저장된 Tool Server 목록에 대해 manifest만 다시 조회하고, `mcp.portal.refresh-interval-seconds` 주기로 포털 registry만 별도로 갱신한다. 포털 `registryRevision`은 포털 응답 JSON 변경 로그와 Tool Server 목록 변경 진단에 사용하며, Tool Server 내부 tool/schema/revision/endpoint 변경 감지는 MCP의 manifest 주기 조회 결과를 route별 in-memory snapshot에 다시 병합하면서 처리한다. 요청 경로의 `tools/list``tools/call`은 계속 in-memory snapshot만 읽는다. Portal API 조회가 실패하면 이미 확보한 in-memory Tool Server snapshot을 유지하며, cold start처럼 memory가 비어 있을 때만 `mcp.redis.portal-registry-key`의 Redis registry JSON을 fallback으로 읽는다. 이 Portal registry fallback은 route 목록과 Tool Server 목록 확보용이고, route별 Tool snapshot Redis key는 이미 알고 있는 route의 마지막 Tool 목록 fallback에만 사용한다. Redis fallback도 실패하면 Tool Server 원천을 확보하지 못한 것으로 처리하고 다음 주기에서 재시도한다.
노출 대상 Tool은 그 파일이 정의한다. 목록을 이 문서에 옮겨 적지 않는다. 파일의 공개 필드는 그대로 보존하고 `_meta` 실행 정보만 제거해 `tools/list`에 내보낸다. fallback도 원격 매니페스트와 같이 설정된 `base-endpoint`에 요청 name을 path segment로 붙여 `tools/call`을 POST한다.
노출 대상 Tool은 그 파일이 정의한다. 목록을 이 문서에 옮겨 적지 않는다. 파일의 공개 필드는 그대로 보존하고 `_meta``endpoint` 실행 정보만 제거해 `tools/list`에 내보낸다. fallback도 원격 매니페스트와 같이 top-level `endpoint` 또는 `_meta.endpoint`를 내부 실행 endpoint로 사용한다.
이 fixture는 연동 확인용이며 실제 고객·계약·수납·지급 데이터를 담지 않는다.

View File

@@ -45,7 +45,7 @@ ToolBundleRegistryClient.fetchTools()
-> ToolBundleDiscovery.discoverAll()
-> GET {manifestUrl}
-> bundleId / tools[] / Tool 필수 필드 검증
-> ToolMetadata 생성 (endpoint는 MCP 배포 설정의 baseEndpoint 사용)
-> ToolMetadata 생성 (endpoint는 Tool Service manifest의 top-level endpoint 또는 _meta.endpoint 사용)
-> enabled=false Tool 제외
-> immutable List<ToolMetadata>를 AtomicReference snapshot에 저장
```
@@ -213,7 +213,7 @@ Content-Type: application/json
3. 모든 Tool에 고유한 표준 `name`, 비어 있지 않은 `description`, object 형태의 `inputSchema`, `_meta.version`을 넣는다.
4. Tool을 숨기려면 `_meta.enabled: false`를 쓰거나 정상 전체 목록에서 제거한다. 둘의 변경 반영 시점은 다음 refresh다.
5. 실행 주소·credential·개인정보·업무 payload를 매니페스트에 넣지 않는다.
6. Tool 자체 실행 endpoint는 별도로 `POST {baseEndpoint}/{toolName}`을 구현한다. manifest endpoint는 실행 endpoint가 아니다.
6. Tool 자체 실행 endpoint는 manifest의 top-level `endpoint` 또는 `_meta.endpoint`에 선언한다. 상대 경로는 Portal registry의 `serviceDomain` 뒤에 붙고, 절대 HTTP(S) URL은 Tool Service가 제공한 실행 주소 원천으로 그대로 사용한다.
## 확인한 구현·테스트

View File

@@ -27,6 +27,7 @@
},
"_meta": {
"version": "1.2.0",
"endpoint": "/mcp/processing.contract.inquiry",
"timeoutMillis": 3000,
"enabled": true
}
@@ -61,6 +62,7 @@
},
"_meta": {
"version": "1.0.1",
"endpoint": "/mcp/processing.payment.history",
"timeoutMillis": 5000,
"enabled": true
}
@@ -86,6 +88,7 @@
},
"_meta": {
"version": "0.9.0",
"endpoint": "/mcp/processing.notice.send",
"timeoutMillis": 10000,
"enabled": false
}

View File

@@ -4,7 +4,7 @@
- 기준일: 2026-07-30
- 대체 대상: push 등록 방식(v0.1). 채택하지 않은 이유는 §2
- 조회 endpoint: `GET {manifestUrl}` — Tool Service가 제공
- 실행 endpoint: `POST {baseEndpoint}/{toolName}` — 현재 구현
- 실행 endpoint: Tool Service manifest의 top-level `endpoint` 또는 `_meta.endpoint` — 현재 구현
## 1. 계약 범위와 원칙
@@ -15,7 +15,7 @@ MCP Server는 자기 설정에 선언된 bundle의 매니페스트를 **주기
|---|---|
| MCP가 가져온다 | Tool Service는 매니페스트를 제공만 한다. MCP에 등록 요청을 보내지 않는다 |
| 조회 대상은 설정이 정한다 | 어떤 bundle이 이 MCP에 속하는지는 배포 시점 YAML로 확정된다 |
| **라우팅 주소는 설정이 소유한다** | 호출 대상 주소는 MCP 설정에서만 온다. 매니페스트가 바꿀 수 없다 |
| **Tool Server domain은 포털이, Tool endpoint는 Tool Service가 소유한다** | 포털은 Tool Server의 `serviceDomain``manifestPath`만 제공하고, 개별 Tool 실행 endpoint는 Tool Service manifest의 top-level `endpoint` 또는 `_meta.endpoint`에서 온다 |
| 매니페스트는 전체 상태 | 응답은 그 bundle의 Tool 전체 목록이다. 증분 없음 |
| 조회 성공이 생존 신호 | 별도 heartbeat·TTL 장치가 없다 |
| bundle 단위 조회 격리 | 한 bundle의 조회 실패가 다른 bundle의 조회를 중단시키지 않는다 |
@@ -170,7 +170,7 @@ MCP는 이 경우 직전 매니페스트를 그대로 유지한다. **선택 기
| `revision` | 아니오 | 매니페스트 버전. 변경 감지·로그·ETag에만 쓰인다 |
| `tools` | 예 | 이 bundle이 노출하는 Tool 전체. 빈 배열은 "노출할 Tool 없음"이다 |
`baseEndpoint`**매니페스트에 넣지 않는다.** 넣어도 MCP는 무시한다(§1 세 번째 원칙).
Tool 실행 endpoint는 각 Tool의 top-level `endpoint` 또는 `_meta.endpoint`에 넣는다. 상대 경로를 쓰면 포털 registry의 `serviceDomain` 뒤에 붙고, 절대 URL을 쓰면 Tool Service manifest가 제공한 실행 주소 원천으로 그대로 사용한다. HTTP(S)가 아닌 scheme은 거부한다.
### `tools[]` 필드
@@ -299,7 +299,7 @@ management port(운영 기본 9090)에서 MCP가 알고 있는 bundle의 조회
이미 구현되어 있는 계약이다. Tool Service는 아래를 받을 수 있어야 한다.
```text
POST {baseEndpoint}/{toolName}
POST {endpoint}
Content-Type: application/json
guid, x-request-id, mcp-session-id, employee-no, virtual-employee-no
Authorization: <설정에 따라 전달>