본문 바로가기
AI/Tool, 모델 소개

MCP 프로토콜의 대대적인 업그레이드: Spring AI, Alibaba와 Higress, 업계 최초로 스트리밍 가능한 HTTP 구현 솔루션 출시

by 주호파파 2025. 5. 5.
728x90
반응형

 

기사 요약

MCP는 공식적으로 원래 HTTP+SSE 전송 메커니즘에 비해 크게 개선된 새로운 Streamable HTTP 전송 계층을 도입했습니다.

이 문서에서는 다음을 수행합니다.

  1. 이 프로토콜의 설계 철학, 기술적 세부 사항 및 실제 적용을 자세히 설명합니다.
  2. Spring AI Alibaba 오픈 소스 프레임워크에서 제공하는 Streamable HTTP Java 구현에 대한 자세한 설명과 Spring AI, Alibaba 및 Higress를 사용하는 Streamable HTTP에 대한 예제 설명을 제공합니다.<

관련 프로젝트 링크는 다음과 같습니다.

● 완전한 실행 가능한 예: https://github.com/springaialibaba/spring-ai-alibaba-examples

● Spring AI 알리바바 공식 블로그 기사: https://java2ai.com/

● Spring AI Alibaba 오픈 소스 프로젝트 주소: https://github.com/alibaba/spring-ai-alibaba

● Higress 공식 사이트 : https://higress.ai/

HTTP+SSE 원칙 및 결함

원래 MCP 구현에서 클라이언트와 서버 간의 통신은 두 개의 기본 채널을 통해 발생합니다.

• HTTP 요청/응답: 클라이언트는 표준 HTTP 요청을 통해 서버에 메시지를 보냅니다.

● SSE(Server-Sent Events): 서버는 전용 /sse 엔드포인트를 통해 클라이언트에 메시지를 푸시합니다.

주요 문제

이 디자인은 단순하고 직관적이지만 다음과 같은 몇 가지 중요한 문제가 있습니다.

1. 재연결/복구가 지원되지 않음:

SSE 연결이 끊어지면 모든 세션 상태가 손실되므로 클라이언트가 연결을 다시 설정하고 전체 세션을 초기화해야 합니다. 예를 들어, 불안정한 WiFi로 인해 실행 중인 대규모 문서 분석 작업이 완전히 중단되어 사용자가 전체 프로세스를 다시 시작해야 할 수 있습니다.

2. 서버는 긴 연결을 유지해야 합니다.

서버는 각 클라이언트에 대해 수명이 긴 SSE 연결을 유지해야 하며, 이로 인해 많은 수의 동시 사용자로 인해 리소스 사용량이 크게 증가합니다. 서버를 다시 시작하거나 확장해야 하는 경우 모든 연결이 중단되어 사용자 경험과 시스템 안정성에 부정적인 영향을 미칩니다.

3. 서버 메시지는 SSE를 통해서만 전송할 수 있습니다.

간단한 요청-응답 상호 작용의 경우에도 서버는 SSE 채널을 통해 정보를 반환해야 하므로 불필요한 복잡성과 오버헤드가 발생합니다. 이 접근 방식은 수명이 긴 SSE 연결을 유지해야 하기 때문에 특정 환경(예: 클라우드 기능)에는 적합하지 않습니다.

4. 인프라 호환성 제한 사항:

CDN, 로드 밸런서 및 API 게이트웨이와 같은 많은 기존 웹 인프라는 수명이 긴 SSE 연결을 올바르게 처리하지 못할 수 있습니다. 회사 방화벽은 시간 초과된 연결을 강제로 닫아 서비스를 신뢰할 수 없게 만들 수 있습니다.

Streamable HTTP 원칙 및 개선 사항

Streamable의 주요 개선 사항:

원래 HTTP+SSE 메커니즘과 비교하여 Streamable HTTP는 몇 가지 주요 개선 사항을 도입했습니다.

  1. 통합 엔드포인트: 전용 /sse 엔드포인트를 제거하여 단일 엔드포인트(현재 공식 SDK에서 /mcp로 구현됨)를 통한 모든 통신을 허용합니다.
  2. 온디맨드 스트리밍: 서버는 표준 HTTP 응답을 반환하거나 SSE 스트림으로 업그레이드하도록 유연하게 선택할 수 있습니다.
  3. 세션 식별: 상태 관리 및 복구를 지원하는 세션 ID 메커니즘을 소개합니다.
  4. 유연한 초기화: 클라이언트는 빈 GET 요청을 통해 SSE 스트림을 능동적으로 초기화할 수 있습니다.

Streamable 작동 방식

Streamable HTTP의 워크플로우는 다음과 같습니다.

1. 세션 초기화(선택 사항, 상태 저장 구현 시나리오에 적합):

  • 클라이언트는 초기화 요청을 /mcp 엔드포인트로 보냅니다.
  • 서버는 세션 ID를 생성하여 클라이언트에 반환하도록 선택할 수 있습니다.
  • 세션 ID는 후속 요청에서 세션을 식별하는 데 사용됩니다.

2. 서버와 클라이언트 통신:

  • 모든 메시지는 HTTP POST 요청을 통해 /mcp 엔드포인트로 전송됩니다.
  • 세션 ID가 있는 경우 요청에 포함됩니다.

3. 서버 응답 방법:

  • 일반 응답: 간단한 상호 작용에 적합한 표준 HTTP 응답을 직접 반환합니다.
  • 스트리밍 응답: 연결을 SSE로 업그레이드하고 닫기 전에 일련의 이벤트를 보냅니다.
  • 긴 연결: 이벤트를 지속적으로 전송하기 위해 SSE 연결을 유지합니다.

4. SSE 스트림의 적극적인 구축:

  • 클라이언트는 /mcp 엔드포인트에 GET 요청을 보내 SSE 스트림을 적극적으로 설정할 수 있습니다.
  • 서버는 이 스트림을 통해 알림 또는 요청을 푸시할 수 있습니다.

5. 연결 복구:

  • 연결이 중단되면 클라이언트는 이전 세션 ID를 사용하여 다시 연결할 수 있습니다.
  • 서버는 세션 상태를 복구하여 이전 상호 작용을 계속할 수 있습니다.

스트리밍 가능한 요청 예제

상태 비저장 서버 모드

시나리오: 수학적 계산, 텍스트 처리 등과 같은 간단한 도구 API 서비스

이행:

Client                                            Server
   |                                                |
   |-- POST /message (Calculation Request) -------->|
   |                                                |-- Perform Calculation
   |<------- HTTP 200 (Calculation Result)   -------|
   |                                                |

장점: 배포가 매우 간단하고 상태 관리가 필요하지 않으며 서버리스 아키텍처 및 마이크로서비스에 적합합니다.

스트리밍 프로그레스 피드백 모드

시나리오: 대용량 파일 처리, 복잡한 AI 생성 등과 같은 장기 실행 작업

이행:

Client                                            Server
   |                                               |
   |-- POST /message (Processing Request) -------->|
   |                                               |-- Start Processing Task
   |<------- HTTP 200 (SSE Starts) ----------------|
   |                                               |
   |<------- SSE: Progress 10% --------------------|
   |<------- SSE: Progress 30% --------------------|
   |<------- SSE: Progress 70% --------------------|
   |<------- SSE: Completion + Result -------------|
   |                                               |

장점: 영구적인 연결 상태를 유지할 필요 없이 실시간 피드백을 제공합니다.

복잡한 AI 대화 모드

시나리오: 컨텍스트 유지 관리가 필요한 다중 턴 대화 AI 도우미.

이행:

Client                                         Server
   |                                             |
   |-- POST /message (Initialization) ---------->|
   |<-- HTTP 200 (Session ID: abc123) -----------|
   |                                             |
   |-- GET /message (Session ID: abc123) ------->|
   |<------- SSE Stream Established -------------|
   |                                             |
   |-- POST /message (Question 1, abc123) ------>|
   |<------- SSE: Thinking... -------------------|
   |<------- SSE: Answer 1 ----------------------|
   |                                             |
   |-- POST /message (Question 2, abc123)  ----->|
   |<------- SSE: Thinking... -------------------|
   |<------- SSE: Answer 2 ----------------------|

장점: 세션 컨텍스트를 유지하고, 복잡한 상호 작용을 지원하며, 수평적 확장을 허용합니다.

연결 끊김 복구 모드

시나리오: 불안정한 네트워크 환경에서 사용되는 AI 애플리케이션.

이행:

Client                                          Server
   |                                             |
   |-- POST /message (Initialization) ---------->|
   |<-- HTTP 200 (Session ID: xyz789) -----------|
   |                                             |
   |-- GET /message (Session ID: xyz789) ------->|
   |<------- SSE Stream Established -------------|
   |                                             |
   |-- POST /message (Long Task, xyz789) ------->|
   |<------- SSE: Progress 30% ------------------|
   |                                             |
   |     [Network Disruption]                    |
   |                                             |
   |-- GET /message (Session ID: xyz789) ------->|
   |<------- SSE Stream Re-established ----------|
   |<------- SSE: Progress 60% ------------------|
   |<------- SSE: Completion --------------------|

장점: 취약한 네트워크 환경에서 안정성을 높여 사용자 경험을 개선합니다.

Spring AI Alibaba 커뮤니티에서 스트리밍 가능한 HTTP 구현

엔터프라이즈 비즈니스 구현의 관점에서 Streamable HTTP가 필요한 이유는 무엇입니까?

이전 섹션에서는 HTTP+SSE 및 Streamable 모드의 장점과 단점을 이론적으로 설명했습니다. 실제 응용 프로그램에서, HTTP+SSE 모델의 단편화된 요청 및 응답 패턴은 아키텍처 구현 및 확장성에서 매우 까다로운 문제를 야기합니다: 세션 ID를 유지하고 동일한 세션 ID를 가진 요청이 동일한 서버 시스템으로 전송되도록 해야 하는 상태 비저장 통신의 경우에도 클라이언트와 서버 간의 고정 세션 연결을 유지해야 합니다. 이는 클라이언트와 서버 구현 모두에 큰 부담이 됩니다.

Streamable 모드의 경우 목표가 단순히 상태 비저장 통신을 유지하는 것이라면 고정 세션을 관리할 필요가 전혀 없습니다. MCP 서비스의 90% 이상이 상태 비저장일 수 있다는 점을 고려할 때 이는 전체 아키텍처의 확장성을 크게 향상시킵니다.

물론 상태 저장 통신을 구현해야 하는 경우 Streamable HTTP 모드는 여전히 세션 ID를 유지해야 합니다.

스트리밍 가능한 HTTP Java 구현 체계

현재 MCP나 Spring AI의 공식 문서 모두 Streamable 모드를 제공하지 않았습니다. 우리는 상태 비저장 모드만 지원하고 공식 Typescript 서버 구현 및 Higress 커뮤니티 서버 구현에 연결할 수 있는 Stream HTTP 클라이언트 구현만 제공했습니다.

전체 실행 가능한 예제는 다음에서 찾을 수 있습니다 https://github.com/springaialibaba/spring-ai-alibaba-examples

Streamable HTTP 솔루션에 대한 MCP Java SDK 구현의 지속적인 개발로 인해 이 예제 리포지토리에는 다음 두 리포지토리의 사용자 지정된 소스 코드가 포함되어 있습니다.

  1. io.modelcontextprotocol 패키지에 있는 MCP Java SDK.
  2. Spring AI, org.springframework.ai.mcp.client.autoconfigure 패키지에 있습니다.

이 예제에서는 MCP Streamable HTTP 프로토콜 구현을 지원하는 Higress 게이트웨이를 통합합니다. 이 구현에는 GET 요청을 지원하지 않거나 세션 ID 관리를 지원하지 않는 등 여전히 많은 제한 사항이 있습니다.

새로운 StreamHttpClientTransport

GET 요청 (빈 요청 본문), SSE 연결을 적극적으로 설정합니다.

클라이언트는 후속 요청-응답 채널 역할을 하는 /mcp 엔드포인트에 GET 요청을 보내 SSE 연결을 적극적으로 설정할 수 있습니다.

return Mono.defer(() -> Mono.fromFuture(() -> {
        final HttpRequest.Builder builder = requestBuilder.copy().GET().uri(uri);
        final String lastId = lastEventId.get();
        if (lastId != null) {
            builder.header("Last-Event-ID", lastId);
        }
        return httpClient.sendAsync(builder.build(), HttpResponse.BodyHandlers.ofInputStream());
    }).flatMap(response -> {
        if (response.statusCode() == 405 || response.statusCode() == 404) {
            // .....
        }
        return handleStreamingResponse(response, handler);
    })
    .retryWhen(Retry.backoff(3, Duration.ofSeconds(3)).filter(err -> err instanceof IllegalStateException))
    .doOnSuccess(v -> state.set(TransportState.CONNECTED))
    .doOnTerminate(() -> state.set(TransportState.CLOSED))
    .onErrorResume(e -> {
        System.out.println("Ignore GET connection error.");
        LOGGER.error("Streamable transport connection error", e);
        state.set(TransportState.CONNECTED);
        return Mono.just("Streamable transport connection error").then();
    }));

POST 요청, 서버는 일반 응답으로 응답하거나 SSE 응답으로 업그레이드할 수 있습니다.

동등한 HTTP 요청의 예로, listTool 및 callTool은 유사한 요청입니다.

curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Accept: text/event-stream"     -d '{
  "jsonrpc" : "2.0",   
  "method" : "initialize",   
  "id" : "9afdedcc-0",
  "params" : {             
    "protocolVersion" : "2024-11-05",   
    "capabilities" : { 
      "roots" : {        
        "listChanged" : true
      }  
    },  
    "clientInfo" : {
      "name" : "Java SDK MCP Client",                                                 
      "version" : "1.0.0"
    }
  }
}' -i http://localhost:3000/mcp 

현재 클라이언트 구현과 함께 공식 TypeScript SDK 에서 제공하는 Streamable Server로 시작하고 테스트할 수 있습니다.

자바 코드 구현

// Send POST request to /mcp, including
public Mono<Void> sendMessage(final McpSchema.JSONRPCMessage message,
            final Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler) {
    // ... 
    return sentPost(message, handler).onErrorResume(e -> {
        LOGGER.error("Streamable transport sendMessage error", e);
        return Mono.error(e);
    });
}

// Actually send the POST request and process the response
private Mono<Void> sentPost(final Object msg,
        final Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler) {
    return serializeJson(msg).flatMap(json -> {
        final HttpRequest request = requestBuilder.copy()
            .POST(HttpRequest.BodyPublishers.ofString(json))
            .uri(uri)
            .build();
        return Mono.fromFuture(httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()))
            .flatMap(response -> {
                // If the response is 202 Accepted, there's no body to process
                if (response.statusCode() == 202) {
                    return Mono.empty();
                }

                if (response.statusCode() == 405 || response.statusCode() == 404) {
                 // ...
                }

                if (response.statusCode() >= 400) {
                 // ...
                }

                return handleStreamingResponse(response, handler);
            });
    });
}

// Handle different types of responses that the server might return
private Mono<Void> handleStreamingResponse(final HttpResponse<InputStream> response,
            final Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler) {
    final String contentType = response.headers().firstValue("Content-Type").orElse("");
    if (contentType.contains("application/json-seq")) {
        return handleJsonStream(response, handler);
    }
    else if (contentType.contains("text/event-stream")) {
        return handleSseStream(response, handler);
    }
    else if (contentType.contains("application/json")) {
        return handleSingleJson(response, handler);
    }
    else {
        return Mono.error(new UnsupportedOperationException("Unsupported Content-Type: " + contentType));
    }
}

Spring AI 프레임워크에 통합

@AutoConfiguration
@ConditionalOnClass({ McpSchema.class, McpSyncClient.class })
@EnableConfigurationProperties({ McpStreamableClientProperties.class, McpClientCommonProperties.class })
@ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
        matchIfMissing = true)
public class StreamableHttpClientTransportAutoConfiguration {
    @Bean
    public List<NamedClientMcpTransport> mcpHttpClientTransports(McpStreamableClientProperties streamableProperties,
            ObjectProvider<ObjectMapper> objectMapperProvider) {

        ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new);

        List<NamedClientMcpTransport> sseTransports = new ArrayList<>();

        for (Map.Entry<String, McpStreamableClientProperties.StreamableParameters> serverParameters : streamableProperties.getConnections().entrySet()) {

            var transport = StreamableHttpClientTransport.builder(serverParameters.getValue().url()).withObjectMapper(objectMapper).build();
            sseTransports.add(new NamedClientMcpTransport(serverParameters.getKey(), transport));
        }

        return sseTransports;
    }

}

Spring AI Alibaba + Higress 스트리밍 가능한 HTTP 예제 완료

다음을 구성하여 Streamable HTTP Transport를 활성화할 수 있습니다. 구성은 Higress에서 제공하는 MCP 서버 주소를 표시합니다(제한된 Streamable HTTP 서버 구현 지원).

spring:
  ai:
    mcp:
      client:
        toolcallback:
          enabled: true
        streamable:
          connections:
            server1:
              url: http://env-cvpjbjem1hkjat42sk4g-ap-southeast-1.alicloudapi.com/mcp-quark
@SpringBootApplication(exclude = {
        org.springframework.ai.mcp.client.autoconfigure.SseHttpClientTransportAutoConfiguration.class,
})
@ComponentScan(basePackages = "org.springframework.ai.mcp.client")
public class Application {
    @Bean
    public CommandLineRunner predefinedQuestions(ChatClient.Builder chatClientBuilder, ToolCallbackProvider tools,
            ConfigurableApplicationContext context) {
        return args -> {
            var chatClient = chatClientBuilder
                    .defaultTools(tools)
                    .build();

            System.out.println("\n>>> QUESTION: " + "Alibaba Xixi Park");
            System.out.println("\n>>> ASSISTANT: " + chatClient.prompt("Alibaba Xixi Park").call().content());

            System.out.println("\n>>> QUESTION: " + "Gold price trend");
            System.out.println("\n>>> ASSISTANT: " + chatClient.prompt("Gold price trend").call().content());

        };
    }
}

예제를 실행한 후 MCP 서버에 대한 성공적인 연결과 도구 목록 실행이 표시됩니다. Higress 예제에는 두 가지 기본 제공 도구가 있습니다.

{
    "jsonrpc": "2.0",
    "id": "32124bd9-1",
    "result": {
        "nextCursor": "",
        "tools": [{
            "description": "Performs a web search using the Quark Search API, ideal for general queries, news, articles, and online content.\nUse this for broad information gathering, recent events, or when you need diverse web sources.\nBecause Quark search performs poorly for English searches, please use Chinese for the query parameters.",
            "inputSchema": {
                "additionalProperties": false,
                "properties": {
                    "contentMode": {
                        "default": "summary",
                        "description": "Return the level of content detail, choose to use summary or full text",
                        "enum": ["full", "summary"],
                        "type": "string"
                    },
                    "number": {
                        "default": 5,
                        "description": "Number of results",
                        "type": "integer"
                    },
                    "query": {
                        "description": "Search query, please use Chinese",
                        "examples": ["Gold price trend"],
                        "type": "string"
                    }
                },
                "required": ["query"],
                "type": "object"
            },
            "name": "web_search"
        }]
    }
}

이 예제에서는 채팅 세션을 시작하고, 모델은 에이전트가 web_search 도구를 호출하고 결과를 반환하도록 안내합니다.

Current Implementation Limitations and Improvement Plans

현재 구현은 공식 Java SDK를 기반으로 하며 McpClientTransport에 대한 Streamable HTTP 모드를 추가합니다. 그러나 이 수정은 워크플로가 HTTP+SSE의 여러 측면과 일치하지 않고 원래 Java SDK의 많은 프로세스가 HTTP+SSE 설계에 강력하게 조정되어 현재 SDK 구현에 몇 가지 구조적 변경이 필요하기 때문에 Streamable HTTP를 완전히 지원하지 않습니다.

예를 들어, 현재 구현이 제한되는 몇 가지 사항은 다음과 같습니다.

  1. 초기화는 Streamable HTTP에서 필수가 아닙니다. 상태 관리를 구현할 때만 필요합니다. 또한 초기화되면 모든 후속 요청에는 mcp-session-id가 포함되어야 합니다. 현재 Java SDK 설계는 초기화 상태 검사를 적용하고 초기화 후 mcp-session-id를 관리하지 않습니다.
  2. /mcp GET 요청은 클라이언트가 SSE 요청을 적극적으로 시작할 때 사용할 프로토콜로 제한됩니다. 그러나 현재 구현은 GET 요청을 시작하고 연결할 때마다 SSE 세션을 설정하며, 후속 POST 요청은 pendingResponses 속성 작업에서 볼 수 있듯이 여기에 반환된 응답에 의존합니다.

현재 Spring AI Alibaba 커뮤니티의 여러 핵심 기여자가 버그 수정 및 Streamable HTTP 솔루션 구현을 포함하여 공식 MCP SDK 개발에 적극적으로 참여하고 있습니다. 우리는 이미 공식 커뮤니티에 관련 풀 리퀘스트(PR)를 제출했습니다. 다음은 커뮤니티의 Streamable 솔루션에 대한 PR입니다.

  1. https://github.com/modelcontextprotocol/java-sdk/pull/144
  2. https://github.com/modelcontextprotocol/java-sdk/pull/163

Official Implementations and Reference Materials

1. Spring AI 알리바바 공식 웹사이트: https://java2ai.com/

2. Spring AI Alibaba 오픈 소스 프로젝트 소스 저장소: https://github.com/alibaba/spring-ai-alibaba

3. mcp-streamable-http: https://www.claudemcp.com/blog/mcp-streamable-http

4. MCP 자바 sdk: https://github.com/modelcontextprotocol/java-sdk

5. 스트리밍 가능한 HTTP: https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http

 

 

728x90
반응형