diff --git a/src/main/java/io/shinhanlife/dap/biz/mcp/config/McpProperties.java b/src/main/java/io/shinhanlife/dap/biz/mcp/config/McpProperties.java index ee8512a..f25df00 100644 --- a/src/main/java/io/shinhanlife/dap/biz/mcp/config/McpProperties.java +++ b/src/main/java/io/shinhanlife/dap/biz/mcp/config/McpProperties.java @@ -187,14 +187,9 @@ public record McpProperties( } /** - * MCP 경계 로그와 선택적인 payload logging 정책 설정입니다. - * payload logging은 Agent·Tool 요청/응답 본문을 남기므로 로컬 검증처럼 명시적으로 켠 환경에서만 사용합니다. + * MCP 경계 로그 활성화와 수신 요청 최대 크기 정책 설정입니다. */ - public record Trace( - boolean enabled, - @Min(1) int maxBodyBytes, - boolean payloadLoggingEnabled, - @Min(1) int maxPayloadBytes) { + public record Trace(boolean enabled, @Min(1) int maxBodyBytes) { } /** diff --git a/src/main/java/io/shinhanlife/dap/biz/mcp/observability/TraceLogger.java b/src/main/java/io/shinhanlife/dap/biz/mcp/observability/TraceLogger.java index c0dcf68..9ebca71 100644 --- a/src/main/java/io/shinhanlife/dap/biz/mcp/observability/TraceLogger.java +++ b/src/main/java/io/shinhanlife/dap/biz/mcp/observability/TraceLogger.java @@ -1,11 +1,5 @@ package io.shinhanlife.dap.biz.mcp.observability; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.NullNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.node.TextNode; import io.shinhanlife.dap.biz.mcp.config.McpProperties; import io.shinhanlife.dap.biz.mcp.context.McpRequestContext; import io.shinhanlife.dap.biz.mcp.context.McpRequestContextHolder; @@ -15,30 +9,26 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; /** - * MCP HTTP 입출구와 Tool HTTP 호출 경계의 이벤트를 로그로 남기는 관측 컴포넌트입니다. - * 일반 trace는 key=value 형식으로 유지하고, payload 로그는 명시적으로 켜진 검증 환경에서만 JSON 한 줄로 남깁니다. - * 요청 본문과 Tool 응답에는 업무 데이터가 포함될 수 있으므로 기본값은 비활성화이며, 주요 의존성은 trace 설정을 가진 {@link McpProperties}와 JSON 직렬화를 담당하는 {@link ObjectMapper}입니다. + * MCP HTTP 입출구와 Tool HTTP 호출 경계의 이벤트를 한 줄 key=value 로그로 남깁니다. + * 현재 요청의 guid와 requestId는 {@link McpRequestContextHolder}에서 읽어 로그 메시지에 직접 포함하므로 MDC를 사용하지 않습니다. + * payload 본문은 이 공통 logger가 보관하지 않고, 임시 검증이 필요한 경계 클래스에서 삭제하기 쉬운 별도 {@code TEMP_*} 로그로만 남깁니다. */ @Component public class TraceLogger { private static final Logger log = LoggerFactory.getLogger(TraceLogger.class); - private final McpProperties properties; - private final ObjectMapper objectMapper; /** - * trace 설정과 payload JSON 직렬화기를 주입받습니다. - * payload logging 활성 여부와 최대 기록 크기는 {@code mcp.trace.*} 설정으로 판단합니다. + * trace 로그 활성화 여부를 판단할 설정 객체를 주입받습니다. */ - public TraceLogger(McpProperties properties, ObjectMapper objectMapper) { + public TraceLogger(McpProperties properties) { this.properties = properties; - this.objectMapper = objectMapper; } /** * 정상적인 처리 단계를 key=value 형식의 구조화 로그로 남깁니다. - * payload는 포함하지 않으며, trace 로그 설정이 켜진 경우에만 기록합니다. + * trace 로그 설정이 켜진 경우에만 기록하며, 요청·응답 본문은 포함하지 않습니다. */ public void event(String event, Object... keyValues) { if (properties.trace().enabled()) { @@ -52,32 +42,9 @@ public class TraceLogger { } } - /** - * Agent와 MCP, MCP와 Tool Server 사이의 요청·응답 payload를 JSON 한 줄 로그로 남깁니다. - * 업무 payload 노출 위험 때문에 {@code mcp.trace.payload-logging-enabled=true}인 경우에만 동작하고, 설정된 byte 상한을 넘으면 본문 대신 축약 메시지를 남깁니다. - */ - public void payload(String event, Object payload, Object... keyValues) { - if (!properties.trace().enabled() || !properties.trace().payloadLoggingEnabled()) { - return; - } - McpRequestContext context = McpRequestContextHolder.get().orElse(null); - ObjectNode envelope = objectMapper.createObjectNode(); - envelope.put("event", safe(event)); - envelope.put("guid", context == null ? "" : context.guid()); - envelope.put("requestId", context == null ? "" : context.requestId()); - envelope.put("routeKey", context == null ? "" : context.routeKey()); - addFields(envelope, keyValues); - envelope.set("payload", boundedPayload(payload)); - try { - log.info(objectMapper.writeValueAsString(envelope)); - } catch (JsonProcessingException exception) { - log.warn("event={} payloadLoggingFailed=true reason={}", safe(event), exception.getClass().getSimpleName()); - } - } - /** * 예외가 발생한 처리 단계를 오류 로그로 남깁니다. - * 오류 로그에도 payload는 포함하지 않고, 예외 종류와 메시지만 correlation 값과 함께 남깁니다. + * 오류 로그에는 payload를 포함하지 않고 예외 종류와 메시지만 correlation 값과 함께 남깁니다. */ public void error(String event, Throwable error, Object... keyValues) { McpRequestContext context = McpRequestContextHolder.get().orElse(null); @@ -104,59 +71,6 @@ public class TraceLogger { return joiner.toString(); } - /** - * payload 로그 envelope에 추가 필드를 안전한 JSON 값으로 넣습니다. - * 필드명은 로그 검색에 쓰이는 메타데이터만 받는 전제라 개행과 공백을 정리합니다. - */ - private void addFields(ObjectNode envelope, Object... keyValues) { - for (int index = 0; index + 1 < keyValues.length; index += 2) { - String key = safe(keyValues[index]); - Object value = keyValues[index + 1]; - if (!key.isBlank()) { - envelope.set(key, objectMapper.valueToTree(value)); - } - } - } - - /** - * payload를 JSON 노드로 바꾸고 설정된 최대 byte 수를 넘으면 축약 문자열로 대체합니다. - * 문자열 payload가 JSON이면 JSON으로 파싱하고, 그렇지 않으면 문자열 그대로 기록합니다. - */ - private JsonNode boundedPayload(Object payload) { - JsonNode node = payloadNode(payload); - try { - String serialized = objectMapper.writeValueAsString(node); - if (serialized.getBytes(java.nio.charset.StandardCharsets.UTF_8).length - > properties.trace().maxPayloadBytes()) { - return TextNode.valueOf(""); - } - return node; - } catch (JsonProcessingException exception) { - return TextNode.valueOf(""); - } - } - - /** - * 다양한 payload 입력을 JSON 노드로 정규화합니다. - * 이미 JsonNode면 그대로 쓰고, 문자열은 JSON 파싱을 먼저 시도한 뒤 실패하면 text node로 보관합니다. - */ - private JsonNode payloadNode(Object payload) { - if (payload == null) { - return NullNode.getInstance(); - } - if (payload instanceof JsonNode jsonNode) { - return jsonNode; - } - if (payload instanceof String text) { - try { - return objectMapper.readTree(text); - } catch (Exception ignored) { - return TextNode.valueOf(text); - } - } - return objectMapper.valueToTree(payload); - } - /** * 줄바꿈과 공백을 치환해 로그 이벤트가 여러 줄로 갈라지지 않게 합니다. */ diff --git a/src/main/java/io/shinhanlife/dap/biz/mcp/toolclient/HttpToolClient.java b/src/main/java/io/shinhanlife/dap/biz/mcp/toolclient/HttpToolClient.java index 8d68cc2..99509ff 100644 --- a/src/main/java/io/shinhanlife/dap/biz/mcp/toolclient/HttpToolClient.java +++ b/src/main/java/io/shinhanlife/dap/biz/mcp/toolclient/HttpToolClient.java @@ -5,13 +5,14 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.TextNode; import io.shinhanlife.dap.biz.mcp.config.McpProperties; import io.shinhanlife.dap.biz.mcp.context.McpRequestContext; -import io.shinhanlife.dap.biz.mcp.observability.TraceLogger; import io.shinhanlife.dap.biz.mcp.toolclient.ToolClient.ToolClientException; import io.shinhanlife.dap.biz.mcp.toolclient.ToolClient.ToolRequest; import io.shinhanlife.dap.biz.mcp.toolclient.ToolClient.ToolResponse; import java.net.SocketTimeoutException; import java.net.http.HttpClient; import java.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; @@ -28,10 +29,11 @@ import org.springframework.web.client.RestClientException; @Component public class HttpToolClient implements ToolClient { + private static final Logger log = LoggerFactory.getLogger(HttpToolClient.class); + private final ObjectMapper objectMapper; private final McpProperties properties; private final HttpClient toolHttpClient; - private final TraceLogger traceLogger; /** * JSON 변환, Tool 설정, 공유 connection pool을 가진 HTTP client를 주입받습니다. @@ -39,12 +41,10 @@ public class HttpToolClient implements ToolClient { public HttpToolClient( ObjectMapper objectMapper, McpProperties properties, - @Qualifier("toolHttpClient") HttpClient toolHttpClient, - TraceLogger traceLogger) { + @Qualifier("toolHttpClient") HttpClient toolHttpClient) { this.objectMapper = objectMapper; this.properties = properties; this.toolHttpClient = toolHttpClient; - this.traceLogger = traceLogger; } /** @@ -53,17 +53,12 @@ public class HttpToolClient implements ToolClient { @Override public ToolResponse execute(ToolRequest request, McpRequestContext context) { try { - traceLogger.payload( - "tool_http_request_body", - request.arguments(), - "direction", - "mcp_to_tool_server", - "toolName", + log.info( + "TEMP_TOOL_HTTP_REQUEST_BODY direction=mcp_to_tool_server toolName={} version={} endpoint={} body={}", request.toolName(), - "version", request.version(), - "endpoint", - request.endpoint()); + request.endpoint(), + request.arguments()); RestClient.RequestBodySpec spec = requestSpec(request, context); var entity = spec.retrieve() @@ -73,18 +68,12 @@ public class HttpToolClient implements ToolClient { throw statusException(response.getStatusCode().value(), request.toolName()); }) .toEntity(String.class); - JsonNode responseBody = parseResponse(entity.getBody(), entity.getHeaders().getContentType()); - traceLogger.payload( - "tool_http_response_body", - responseBody, - "direction", - "tool_server_to_mcp", - "toolName", + JsonNode responseBody = parseResponse(entity.getBody(), entity.getHeaders().getContentType()); log.info( + "TEMP_TOOL_HTTP_RESPONSE_BODY direction=tool_server_to_mcp toolName={} version={} statusCode={} body={}", request.toolName(), - "version", request.version(), - "statusCode", - entity.getStatusCode().value()); + entity.getStatusCode().value(), + responseBody); return new ToolResponse(entity.getStatusCode().value(), responseBody); } catch (ToolClientException exception) { throw exception; diff --git a/src/main/java/io/shinhanlife/dap/biz/mcp/transport/http/McpController.java b/src/main/java/io/shinhanlife/dap/biz/mcp/transport/http/McpController.java index 7a715ed..020438e 100644 --- a/src/main/java/io/shinhanlife/dap/biz/mcp/transport/http/McpController.java +++ b/src/main/java/io/shinhanlife/dap/biz/mcp/transport/http/McpController.java @@ -1,6 +1,7 @@ package io.shinhanlife.dap.biz.mcp.transport.http; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import io.shinhanlife.dap.biz.mcp.context.McpRequestContext; import io.shinhanlife.dap.biz.mcp.context.McpRequestContextHolder; import io.shinhanlife.dap.biz.mcp.jsonrpc.JsonRpcErrorCode; @@ -9,8 +10,9 @@ import io.shinhanlife.dap.biz.mcp.jsonrpc.JsonRpcRequest; import io.shinhanlife.dap.biz.mcp.jsonrpc.JsonRpcRequestParser; import io.shinhanlife.dap.biz.mcp.jsonrpc.JsonRpcResponse; import io.shinhanlife.dap.biz.mcp.method.McpMethodHandlerRegistry; -import io.shinhanlife.dap.biz.mcp.observability.TraceLogger; import java.util.UUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -24,20 +26,22 @@ import org.springframework.web.bind.annotation.RestController; @RestController public class McpController { + private static final Logger log = LoggerFactory.getLogger(McpController.class); + public static final String MCP_SESSION_ID_HEADER = "Mcp-Session-Id"; private final JsonRpcRequestParser requestParser; private final McpMethodHandlerRegistry handlerRegistry; - private final TraceLogger traceLogger; + private final ObjectMapper objectMapper; /** * JSON-RPC 변환과 method dispatch 협력 객체를 주입받습니다. Controller는 실행 규칙을 직접 구현하지 않고 각 책임 객체를 올바른 순서로 연결합니다. */ public McpController( - JsonRpcRequestParser requestParser, McpMethodHandlerRegistry handlerRegistry, TraceLogger traceLogger) { + JsonRpcRequestParser requestParser, McpMethodHandlerRegistry handlerRegistry, ObjectMapper objectMapper) { this.requestParser = requestParser; this.handlerRegistry = handlerRegistry; - this.traceLogger = traceLogger; + this.objectMapper = objectMapper; } /** @@ -55,15 +59,11 @@ public class McpController { try { JsonRpcResponse response = handlerRegistry.resolve(request.method()).handle(request, context); if (request.notification()) { - traceLogger.payload( - "mcp_http_response_body", - null, - "direction", - "mcp_to_agent", - "mcpMethod", + log.info( + "TEMP_MCP_HTTP_RESPONSE_BODY direction=mcp_to_agent mcpMethod={} httpStatus={} body={}", request.method(), - "httpStatus", - 202); + 202, + ""); return ResponseEntity.accepted().build(); } ResponseEntity.BodyBuilder responseBuilder = @@ -71,15 +71,11 @@ public class McpController { if ("initialize".equals(request.method())) { responseBuilder.header(MCP_SESSION_ID_HEADER, UUID.randomUUID().toString()); } - traceLogger.payload( - "mcp_http_response_body", - response, - "direction", - "mcp_to_agent", - "mcpMethod", + log.info( + "TEMP_MCP_HTTP_RESPONSE_BODY direction=mcp_to_agent mcpMethod={} httpStatus={} body={}", request.method(), - "httpStatus", - 200); + 200, + toJson(response)); return responseBuilder.body(response); } catch (JsonRpcException exception) { if (exception.requestId() != null) { @@ -92,4 +88,16 @@ public class McpController { JsonRpcErrorCode.INTERNAL_ERROR, "Unexpected server error", request.id(), exception); } } + + /** + * 임시 응답 본문 로그를 위해 객체를 JSON 문자열로 직렬화합니다. + * 직렬화 실패 시에도 실제 응답 흐름은 바꾸지 않고 문자열 변환 결과만 로그에 남깁니다. + */ + private String toJson(Object value) { + try { + return objectMapper.writeValueAsString(value); + } catch (Exception exception) { + return String.valueOf(value); + } + } } diff --git a/src/main/java/io/shinhanlife/dap/biz/mcp/transport/http/McpExchangeFilter.java b/src/main/java/io/shinhanlife/dap/biz/mcp/transport/http/McpExchangeFilter.java index 93c5e17..2dae8b1 100644 --- a/src/main/java/io/shinhanlife/dap/biz/mcp/transport/http/McpExchangeFilter.java +++ b/src/main/java/io/shinhanlife/dap/biz/mcp/transport/http/McpExchangeFilter.java @@ -16,6 +16,8 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.MediaType; @@ -30,6 +32,8 @@ import org.springframework.web.filter.OncePerRequestFilter; @Order(Ordered.HIGHEST_PRECEDENCE + 10) public class McpExchangeFilter extends OncePerRequestFilter { + private static final Logger log = LoggerFactory.getLogger(McpExchangeFilter.class); + private final McpRequestContextFactory headerExtractor; private final TraceLogger traceLogger; private final ObjectMapper objectMapper; @@ -103,18 +107,12 @@ public class McpExchangeFilter extends OncePerRequestFilter { "path", request.getRequestURI(), "mcpMethod", - mcpMethod); - traceLogger.payload( - "mcp_http_request_body", - cachedRequest.bodyText(), - "direction", - "agent_to_mcp", - "httpMethod", + mcpMethod); log.info( + "TEMP_MCP_HTTP_REQUEST_BODY direction=agent_to_mcp httpMethod={} path={} mcpMethod={} body={}", request.getMethod(), - "path", request.getRequestURI(), - "mcpMethod", - mcpMethod); + mcpMethod, + cachedRequest.bodyText()); try { protocolVersionValidator.validatePostInitializeRequest(request, mcpMethod); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 05f5cff..12cf8a0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -93,11 +93,7 @@ mcp: # nothing a Tool Service returns can change where MCP sends the call. bundles: [] trace: - enabled: true - # Request/response body logging is for temporary verification only. Keep disabled by default. - payload-logging-enabled: ${MCP_TRACE_PAYLOAD_LOGGING_ENABLED:true} - max-payload-bytes: ${MCP_TRACE_MAX_PAYLOAD_BYTES:65536} - # Rejects oversized MCP request bodies before controller processing. + enabled: true # Rejects oversized MCP request bodies before controller processing. max-body-bytes: 1048576 logging: diff --git a/src/test/java/io/shinhanlife/dap/biz/mcp/TestFixtures.java b/src/test/java/io/shinhanlife/dap/biz/mcp/TestFixtures.java index 702657f..c587abf 100644 --- a/src/test/java/io/shinhanlife/dap/biz/mcp/TestFixtures.java +++ b/src/test/java/io/shinhanlife/dap/biz/mcp/TestFixtures.java @@ -29,7 +29,7 @@ public final class TestFixtures { "file:./config/local-core-tools-manifest-sample-v1.json", 30, 5), new McpProperties.ToolClient(1_000, 5_000, 300_000, forwardAuthorization, "tool-server-key"), new McpProperties.Redis(redisEnabled, "test:mcp:tools", "test:mcp:portal-registry"), - new McpProperties.Trace(true, 1_048_576, false, 65_536), + new McpProperties.Trace(true, 1_048_576), new McpProperties.Protocol(List.of("2025-11-25"), "2025-11-25"), new McpProperties.Discovery(!bundles.isEmpty(), 1_000, 3_000, 100, 200, 1_048_576, 30_000), new McpProperties.Portal(false, "", "", 300), diff --git a/src/test/java/io/shinhanlife/dap/biz/mcp/observability/TraceLoggerTest.java b/src/test/java/io/shinhanlife/dap/biz/mcp/observability/TraceLoggerTest.java index e5fbd4a..9e7d760 100644 --- a/src/test/java/io/shinhanlife/dap/biz/mcp/observability/TraceLoggerTest.java +++ b/src/test/java/io/shinhanlife/dap/biz/mcp/observability/TraceLoggerTest.java @@ -26,7 +26,7 @@ class TraceLoggerTest { logger.addAppender(appender); McpRequestContextHolder.set(context()); - new TraceLogger(properties(false, false), io.shinhanlife.dap.biz.mcp.TestFixtures.OBJECT_MAPPER) + new TraceLogger(properties(false, false)) .event("mcp_http_response_completed", "httpStatus", 200); assertThat(appender.list) diff --git a/src/test/java/io/shinhanlife/dap/biz/mcp/toolclient/HttpToolClientTest.java b/src/test/java/io/shinhanlife/dap/biz/mcp/toolclient/HttpToolClientTest.java index 8f0f7a5..5721a91 100644 --- a/src/test/java/io/shinhanlife/dap/biz/mcp/toolclient/HttpToolClientTest.java +++ b/src/test/java/io/shinhanlife/dap/biz/mcp/toolclient/HttpToolClientTest.java @@ -41,7 +41,7 @@ class HttpToolClientTest { .setHeader("Content-Type", "application/json") .setBody("{\"customerName\":\"홍길동\"}")); HttpToolClient client = - new HttpToolClient(OBJECT_MAPPER, properties(false, false), HttpClient.newHttpClient(), new io.shinhanlife.dap.biz.mcp.observability.TraceLogger(properties(false, false), OBJECT_MAPPER)); + new HttpToolClient(OBJECT_MAPPER, properties(false, false), HttpClient.newHttpClient()); ToolRequest request = new ToolRequest( "customer.search", @@ -75,7 +75,7 @@ class HttpToolClientTest { server.enqueue(new MockResponse().setHeader("Content-Type", "application/json") .setBody("{\"quote\":\"시작이 반이다.\"}")); HttpToolClient client = - new HttpToolClient(OBJECT_MAPPER, properties(false, false), HttpClient.newHttpClient(), new io.shinhanlife.dap.biz.mcp.observability.TraceLogger(properties(false, false), OBJECT_MAPPER)); + new HttpToolClient(OBJECT_MAPPER, properties(false, false), HttpClient.newHttpClient()); ToolRequest request = new ToolRequest( "smp_quote_daily", "1.0.0", server.url("/mcp/smp_quote_daily").toString(), OBJECT_MAPPER.readTree("{\"category\":\"속담\"}"), 3_000); @@ -97,7 +97,7 @@ class HttpToolClientTest { void preservesPlainTextToolResponseAsTextNode() throws Exception { server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("123")); HttpToolClient client = - new HttpToolClient(OBJECT_MAPPER, properties(false, false), HttpClient.newHttpClient(), new io.shinhanlife.dap.biz.mcp.observability.TraceLogger(properties(false, false), OBJECT_MAPPER)); + new HttpToolClient(OBJECT_MAPPER, properties(false, false), HttpClient.newHttpClient()); ToolRequest request = new ToolRequest( "processing", @@ -116,7 +116,7 @@ class HttpToolClientTest { void preservesHttpStatusOnToolError() throws Exception { server.enqueue(new MockResponse().setResponseCode(410).setBody("gone")); HttpToolClient client = - new HttpToolClient(OBJECT_MAPPER, properties(false, false), HttpClient.newHttpClient(), new io.shinhanlife.dap.biz.mcp.observability.TraceLogger(properties(false, false), OBJECT_MAPPER)); + new HttpToolClient(OBJECT_MAPPER, properties(false, false), HttpClient.newHttpClient()); ToolRequest request = new ToolRequest( "customer.search", diff --git a/src/test/java/io/shinhanlife/dap/biz/mcp/transport/http/McpControllerTest.java b/src/test/java/io/shinhanlife/dap/biz/mcp/transport/http/McpControllerTest.java index a190d26..a0857da 100644 --- a/src/test/java/io/shinhanlife/dap/biz/mcp/transport/http/McpControllerTest.java +++ b/src/test/java/io/shinhanlife/dap/biz/mcp/transport/http/McpControllerTest.java @@ -41,7 +41,7 @@ class McpControllerTest { McpRequestContextHolder.set(context()); var response = - new McpController(parser, registry, org.mockito.Mockito.mock(io.shinhanlife.dap.biz.mcp.observability.TraceLogger.class)).handleMcpRequest(JsonNodeFactory.instance.objectNode()); + new McpController(parser, registry, io.shinhanlife.dap.biz.mcp.TestFixtures.OBJECT_MAPPER).handleMcpRequest(JsonNodeFactory.instance.objectNode()); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED); assertThat(response.getBody()).isNull(); @@ -64,7 +64,7 @@ class McpControllerTest { McpRequestContextHolder.set(context()); var response = - new McpController(parser, registry, org.mockito.Mockito.mock(io.shinhanlife.dap.biz.mcp.observability.TraceLogger.class)).handleMcpRequest(JsonNodeFactory.instance.objectNode()); + new McpController(parser, registry, io.shinhanlife.dap.biz.mcp.TestFixtures.OBJECT_MAPPER).handleMcpRequest(JsonNodeFactory.instance.objectNode()); String sessionId = response.getHeaders().getFirst(McpController.MCP_SESSION_ID_HEADER); assertThat(sessionId).isNotBlank(); @@ -89,7 +89,7 @@ class McpControllerTest { McpRequestContextHolder.set(context()); var response = - new McpController(parser, registry, org.mockito.Mockito.mock(io.shinhanlife.dap.biz.mcp.observability.TraceLogger.class)).handleMcpRequest(JsonNodeFactory.instance.objectNode()); + new McpController(parser, registry, io.shinhanlife.dap.biz.mcp.TestFixtures.OBJECT_MAPPER).handleMcpRequest(JsonNodeFactory.instance.objectNode()); PostMapping mapping = McpController.class diff --git a/src/test/java/io/shinhanlife/dap/biz/mcp/transport/http/McpExchangeFilterTest.java b/src/test/java/io/shinhanlife/dap/biz/mcp/transport/http/McpExchangeFilterTest.java index 3dcdd4b..243665f 100644 --- a/src/test/java/io/shinhanlife/dap/biz/mcp/transport/http/McpExchangeFilterTest.java +++ b/src/test/java/io/shinhanlife/dap/biz/mcp/transport/http/McpExchangeFilterTest.java @@ -219,7 +219,7 @@ class McpExchangeFilterTest { base.registry(), base.toolClient(), base.redis(), - new McpProperties.Trace(true, 8, false, 65_536), + new McpProperties.Trace(true, 8), base.protocol(), base.discovery(), base.portal(), @@ -357,7 +357,7 @@ class McpExchangeFilterTest { private McpExchangeFilter filter(McpProperties properties) { return new McpExchangeFilter( new McpRequestContextFactory(properties), - new TraceLogger(properties, io.shinhanlife.dap.biz.mcp.TestFixtures.OBJECT_MAPPER), + new TraceLogger(properties), OBJECT_MAPPER, properties, new McpProtocolVersionValidator(properties));