MCP Java SDK 도입으로 들어온 json-schema-validator는 schema의 $ref가 문서
밖을 가리키면 그 주소로 직접 조회하고, pattern 검증을 백트래킹 기반
java.util.regex로 처리한다. inputSchema는 Tool Service 매니페스트에서 오므로
매니페스트가 서버의 outbound 대상과 CPU 소비를 정할 수 있었다. AGENTS.md의
"outbound 주소는 설정에서만 온다"는 불변식이 이 경로에서 뚫려 있었다.
DefaultJsonSchemaValidator는 SchemaRegistry를 생성자 안에서 만들고 private
final로 들고 있어 정책 주입 지점이 없다. 따라서 SDK 밖에서만 막을 수 있다.
검사는 ToolMetadata의 표준 생성자에 둔다. Portal 매니페스트, local 파일,
Redis snapshot 역직렬화가 모두 이 생성자를 지나므로 우회 경로가 생기지 않는다.
위반은 기존 매니페스트 형식 오류와 같게 다뤄 bundle 단위 실패 격리와
"Redis 실패는 언제나 cache miss" 동작을 그대로 유지한다.
정규식 규칙은 JDK 21.0.11 실측으로 정했다. 통념과 달리 (a+)+는 빠르게 끝나고,
중첩이 아닌 a*a*a*a*a*b와 바깥 반복이 유한한 (.*,){11}P가 폭증했다. 겹치는
문자 집합 판정은 결정 불가능하므로 모양 검사만으로는 부족하고, pattern 필드에
maxLength 동반 선언을 요구해 입력 길이를 묶는 것이 실질적인 상한이 된다.
patternProperties는 key에 길이를 선언할 자리가 없어 사용을 금지한다.
format은 단언되지 않아 format:regex 경로가 실행되지 않는다는 사실도 계약
테스트로 고정했다. SDK 업그레이드로 단언이 켜지면 테스트가 실패한다.
Tool Service는 pattern을 쓰는 필드에 maxLength(<=256)를 선언해야 하므로
매니페스트 수용 조건이 바뀐다. Tool Service 파트와 합의가 필요하다.
근거: ADR-0011, ADR-0012
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9.2 KiB
MCP Java SDK 선택적 도입 설계
- 상태: 적용 완료
- 적용 버전:
io.modelcontextprotocol.sdk:mcp-json-jackson2:2.0.0 - 대상 런타임: Java 21, Spring Boot 3.5.11
- 적용 원칙: 외부 계약과 AX HUB 고유 실행 경계는 유지하고, 표준 프로토콜 모델과 JSON Schema 검증만 SDK에 위임한다.
1. 도입 결론
이 프로젝트는 MCP Java SDK의 서버 Starter나 HTTP transport를 사용하지 않는다. 현재 /mcp endpoint는
Agent Builder와 합의한 동기 JSON, Mcp-Session-Id, protocol version HTTP 400, trace 계약을 이미 구현하고
있으므로 SDK transport를 함께 활성화하면 같은 endpoint에 두 프로토콜 처리 경로가 생길 수 있기 때문이다.
대신 실제 사용 모듈인 mcp-json-jackson2에 직접 의존한다. 이 모듈이 mcp-core를 전이 제공하므로 aggregate artifact를 별도로 선언하지 않는다. 적용 범위는 다음과 같다.
| 적용 영역 | SDK 타입/기능 | 기존 코드에서의 사용 위치 |
|---|---|---|
| MCP method 이름 | McpSchema.METHOD_* |
handler와 protocol validator |
| JSON-RPC 버전과 표준 오류 번호 | McpSchema.JSONRPC_VERSION, McpSchema.ErrorCodes |
request parser, response, error enum |
| initialize 결과 | McpSchema.InitializeResult, Implementation, ServerCapabilities |
InitializeHandler |
| tools/list 결과 | McpSchema.ListToolsResult, Tool |
ToolsListHandler |
| tools/call 결과 | McpSchema.CallToolResult, TextContent |
ToolsCallHandler |
| Tool arguments schema 검증 | DefaultJsonSchemaValidator |
ToolArgumentValidator |
이 서버는 Spring AI API를 사용하지 않으므로 Spring AI BOM을 두지 않는다. MCP SDK 버전은 2.0.0으로 직접
고정한다. Starter를 추가하지 않았기 때문에 Spring AI MCP auto-configuration, 별도 /mcp mapping,
SSE/Streamable transport bean은 생성되지 않는다.
2. 전체 요청 경계
Agent Builder
-> 기존 McpExchangeFilter
- 호출자 header 추출 (guid, request-id, session, 사원 식별자)
- guid, x-request-id, MCP Session correlation
- protocol version HTTP header 검증
- HTTP/Tool 경계 trace log
-> 기존 McpController
-> 기존 JsonRpcRequestParser / McpMethodHandlerRegistry
-> Handler
- initialize: SDK InitializeResult 생성
- tools/list: 기존 Registry metadata -> SDK Tool/ListToolsResult
- tools/call: 기존 실행 결과 -> SDK CallToolResult/TextContent
-> 기존 JsonRpcResponse envelope
-> application/json 응답
Tool 실행 경로는 다음과 같다.
tools/call
-> 기존 ToolsCallHandler params 검증
-> 기존 ToolRegistryService
-> 기존 basic contract + SDK JSON Schema 검증
-> SDK JSON Schema 2020-12 검증
-> 기존 ToolRoutingService / ToolClient
-> 기존 timeout·correlation 처리 (MCP는 인증·인가하지 않음)
SDK 모델은 handler의 표준 MCP payload를 만드는 데만 사용한다. SDK server가 요청을 dispatch하거나 Tool Service를 호출하지 않는다.
3. SDK와 기존 소스의 책임 경계
SDK에 위임한 책임
- 표준 MCP method 문자열과 JSON-RPC 상수
- initialize, Tool definition, list result, call result의 표준 필드 구조
- text content의
type: "text"표현 - JSON Schema 2020-12 기반 arguments 검증과 schema 컴파일 재사용
minLength,pattern,enum,additionalProperties, 중첩 객체 등 기존 기본 validator보다 넓은 schema keyword
기존 구현이 계속 소유하는 책임
/mcpHTTP mapping과 항상application/json을 반환하는 transport 정책Accept: application/json, text/event-stream수용Mcp-Session-Id생성과 stateless correlationMCP-Protocol-Version누락·미지원 시 HTTP 400 처리- JSON-RPC parse/invalid request/invalid params의 현재 오류 envelope와 메시지
- Tool Service 매니페스트 pull, Redis/memory fallback, local fixture
- Tool endpoint/timeout/version 진단 정보와 dynamic metadata
- 호출자 header 추출·검증과 Tool Service bypass 정책 (인증·인가는 하지 않음, ADR-0006)
guid·x-request-id전파, 요청 context 정리, HTTP/Tool 경계 로그- Tool 선택 금지, exactly-one Tool 실행, outbound routing과 업무 오류 변환
4. 기존 계약 보존 방법
| 위험 | 회피 방식 |
|---|---|
SDK Starter가 기존 /mcp와 충돌 |
Starter를 사용하지 않고 core 모델과 Jackson 2 validator만 의존 |
SDK가 Tool 검증 실패를 isError=true로 바꿈 |
SDK server handler를 사용하지 않고 검증 실패를 기존 -32602 Invalid params로 변환 |
| 기존 required/type 오류 문구 변경 | 공개된 기본 검증을 SDK보다 먼저 실행해 기존 메시지를 그대로 유지 |
Mcp-Session-Id/protocol header 동작 변경 |
기존 filter, controller, validator를 유지 |
tools/call 결과 필드 위치 변경 |
직렬화 계약 테스트로 content[0]._meta.searchTime과 isError를 고정 |
| local tools/list 공개 필드 누락 | SDK Tool 변환 전 _meta만 제거하고 title/outputSchema/annotations를 직렬화 비교 |
| Registry의 기존 Tool에 inputSchema 누락 | SDK 필수 조건을 만족하는 빈 object schema로 정규화 |
| dynamic Registry를 annotation Tool로 고정 | @McpTool을 사용하지 않고 요청마다 기존 Registry service를 조회 |
| correlation·Trace 기능이 SDK 내부로 사라짐 | transport와 실행 orchestration을 기존 코드에 유지 |
| SDK 업그레이드로 wire payload 변경 | SDK 버전을 명시하고 initialize/list/call golden serialization 테스트를 통과한 경우에만 변경 |
5. JSON Schema 검증 정책
SDK 검증은 ToolExecutionService가 Registry 기반 argument validation을 수행하는 위치에 연결했다.
검증 순서는 다음과 같다.
- 기존 object/required/basic type 검증으로 공개된 오류 문구를 보존한다.
- SDK JSON Schema validator로 나머지 2020-12 keyword를 검증한다.
- 실패하면 Tool Service를 호출하지 않고 기존
JsonRpcException(INVALID_PARAMS)으로 종료한다. SDK 원문 오류는 입력값을 포함할 수 있으므로 외부에는arguments do not match inputSchema만 반환한다.
SDK validator는 Spring singleton으로 한 번 생성되며 동일 schema의 컴파일 결과를 재사용한다.
Registry가 제공하는 schema 자체의 허용 dialect와 $ref 해석 범위는
ADR-0011로 확정했다. ToolSchemaReferencePolicy가
ToolMetadata 생성 시점에 문서 밖 $ref·$dynamicRef와 2020-12가 아닌 $schema를 거부하므로, 검증기가 schema에
적힌 주소로 조회를 시도할 수 있는 경로가 남지 않는다. DefaultJsonSchemaValidator는 SchemaRegistry를 내부에서
생성해 정책 주입 지점을 열어 두지 않으므로, 이 통제는 SDK 밖에서만 걸 수 있다. format 키워드의 검증 강도는 아직
협의 항목이다.
pattern 정규식은 joni·graal-js를 해석하지 않아 java.util.regex로 검증된다. 백트래킹 폭증을 막기 위해
ToolSchemaPatternPolicy가 반복 구조·수량자 개수·정규식 길이를 제한하고 maxLength 동반 선언을 요구한다
(ADR-0012). 측정 근거와 남는 위험은 그 ADR에 있다.
format은 단언하지 않는다. 2020-12에서 format-assertion은 opt-in이고 SDK 검증기가 이를 켜지 않으므로,
format: "date-time"이나 format: "ipv4"에 임의 문자열을 넣어도 통과한다. 덕분에 입력 값을 정규식으로
컴파일하는 format: "regex" 경로도 실행되지 않는다. 이 동작은 ToolArgumentValidatorTest가 고정하므로,
SDK 업그레이드로 단언이 켜지면 테스트가 실패해 알 수 있다.
6. 의도적으로 도입하지 않은 SDK 기능
spring-ai-starter-mcp-server-webmvc- SDK sync/async/stateless server와 transport provider
- SSE 또는 Streamable HTTP 응답
- SDK authorization/security 구현
- annotation 기반 정적
@McpTool등록 - SDK가 소유하는 Tool lifecycle, list-changed notification
- Resources, Prompts, Sampling, Elicitation
이 기능들은 현재 요구사항을 해결하지 않거나 기존 계약과 중복된다. 실제 필요가 생기면 별도 ADR과 Agent Builder contract test를 먼저 추가한다.
7. 변경 및 검증 기준
SDK 버전을 올릴 때는 다음을 모두 확인한다.
- Spring Boot/Java/MCP Java SDK 조합의 dependency resolution
- SDK
McpSchema필드와 Jackson 2 직렬화 변경 여부 - initialize, tools/list, tools/call 성공·실패 JSON의 기존 예제 일치
- JSON-RPC 오류 code/message/data 및 HTTP status
Accept에text/event-stream이 있어도 JSON 응답을 유지하는지Mcp-Session-Id, protocol version,guid,x-request-id전파- local profile, Registry 장애 fallback, Redis 비필수 동작
.\gradlew.bat clean test
참고: