Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix: Add an exception handling function to WebFluxSseClientTransport #223

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

YunKuiLu
Copy link

@YunKuiLu YunKuiLu commented May 7, 2025

  • sseErrorHandler is used to handle errors when processing SSE events and situations where an unrecognized event type is received.
  • Add unit tests to verify.

Motivation and Context

Users can use custom handlers when they encounter the following exceptions:
#93
spring-projects/spring-ai#2936

All other clients are compatible with this, but the WebFlux client throws an exception, which is necessary to fix.

Although the MCP protocol does not allow any message types other than JSON-PRC, users should have a way to handle it instead of having the client stop. Therefore, I did not attempt to ignore it. Instead, I simply made it clearly visible in the log without interrupting the connection.

How Has This Been Tested?

Breaking Changes

I've provided a default error handler that prints the exception. User can also override it with the set method.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Since I added a new field to catch exceptions, I need Member to confirm that the design conforms to the future path of Mcp Java Sdk. Thus avoiding the introduction of an AD hoc solution.

*/
private BiFunction<Throwable, ServerSentEvent<String>, Mono<? extends JSONRPCMessage>> sseErrorHandler = (error,
event) -> {
logger.warn("Failed to handle SSE event {}", event, error);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The log will look like this:
image

@@ -215,12 +226,26 @@ else if (MESSAGE_EVENT_TYPE.equals(event.event())) {
else {
s.error(new McpError("Received unrecognized SSE event type: " + event.event()));
}
}).transform(handler)).subscribe();
}).onErrorResume(e -> sseErrorHandler.apply(e, event)).transform(handler)).subscribe();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The connection will not be closed by an McpError exception:
image

@YunKuiLu
Copy link
Author

YunKuiLu commented May 7, 2025

@tzolov @chemicL Hope to get your CR. Thanks!

- `sseErrorHandler` is used to handle errors when processing SSE events and situations where an unrecognized event type is received.
- Add unit tests to verify.

Signed-off-by: YunKui Lu <[email protected]>
@ai-srcflow
Copy link

i met the same matter how to solve it?

2025-05-09 16:07:23 INFO o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-9004"]
2025-05-09 16:07:23 INFO o.s.b.w.e.tomcat.TomcatWebServer - Tomcat started on port 9004 (http) with context path '/'
2025-05-09 16:07:23 INFO n.q.q.a.AiAssistantApplication - Started AiAssistantApplication in 3.248 seconds (process running for 3.764)
2025-05-09 16:07:38 ERROR reactor.core.publisher.Operators - Operator called default onErrorDropped
reactor.core.Exceptions$ErrorCallbackNotImplemented: io.modelcontextprotocol.spec.McpError: Received unrecognized SSE event type: null
Caused by: io.modelcontextprotocol.spec.McpError: Received unrecognized SSE event type: null
at io.modelcontextprotocol.client.transport.WebFluxSseClientTransport.lambda$connect$0(WebFluxSseClientTransport.java:216)
at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onNext(FluxHandleFuseable.java:179)
at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2571)
at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.request(FluxHandleFuseable.java:260)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.request(FluxPeekFuseable.java:144)
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2367)
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:2241)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onSubscribe(FluxPeekFuseable.java:178)
at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onSubscribe(FluxHandleFuseable.java:164)
at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:55)
at reactor.core.publisher.Mono.subscribe(Mono.java:4576)
at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.onNext(FluxConcatMapNoPrefetch.java:207)
at reactor.core.publisher.SerializedSubscriber.onNext(SerializedSubscriber.java:99)
at reactor.core.publisher.FluxRetryWhen$RetryWhenMainSubscriber.onNext(FluxRetryWhen.java:178)
at reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner.onNext(MonoFlatMapMany.java:251)
at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)
at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:539)
at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.innerNext(FluxConcatMapNoPrefetch.java:259)
at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:865)
at reactor.core.publisher.FluxConcatMap$WeakScalarSubscription.request(FluxConcatMap.java:480)
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2367)
at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.onNext(FluxConcatMapNoPrefetch.java:202)
at reactor.core.publisher.FluxBufferPredicate$BufferPredicateSubscriber.onNextNewBuffer(FluxBufferPredicate.java:317)
at reactor.core.publisher.FluxBufferPredicate$BufferPredicateSubscriber.tryOnNext(FluxBufferPredicate.java:227)
at reactor.core.publisher.FluxBufferPredicate$BufferPredicateSubscriber.onNext(FluxBufferPredicate.java:200)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableConditionalSubscriber.onNext(FluxPeekFuseable.java:503)
at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:299)
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
at reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onNext(FluxDoFinally.java:113)
at reactor.core.publisher.FluxConcatArray$ConcatArraySubscriber.onNext(FluxConcatArray.java:180)
at reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber.drainAsync(FluxFlattenIterable.java:453)
at reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber.drain(FluxFlattenIterable.java:724)
at reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber.onNext(FluxFlattenIterable.java:256)
at reactor.core.publisher.FluxPublish$PublishSubscriber.drain(FluxPublish.java:571)
at reactor.core.publisher.FluxPublish$PublishSubscriber.onNext(FluxPublish.java:310)
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:299)
at reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber.drainAsync(FluxFlattenIterable.java:453)
at reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber.drain(FluxFlattenIterable.java:724)
at reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber.onNext(FluxFlattenIterable.java:256)
at reactor.adapter.JdkFlowAdapter$SubscriberToRS.onNext(JdkFlowAdapter.java:150)
at java.net.http/jdk.internal.net.http.ResponseSubscribers$PublishingBodySubscriber.onNext(ResponseSubscribers.java:1006)
at java.net.http/jdk.internal.net.http.ResponseSubscribers$PublishingBodySubscriber.onNext(ResponseSubscribers.java:846)
at java.net.http/jdk.internal.net.http.Http1Response$Http1BodySubscriber.onNext(Http1Response.java:382)
at java.net.http/jdk.internal.net.http.Http1Response$Http1BodySubscriber.onNext(Http1Response.java:297)
at java.net.http/jdk.internal.net.http.ResponseContent$ChunkedBodyParser.accept(ResponseContent.java:229)
at java.net.http/jdk.internal.net.http.ResponseContent$ChunkedBodyParser.accept(ResponseContent.java:129)
at java.net.http/jdk.internal.net.http.Http1Response$BodyReader.handle(Http1Response.java:790)
at java.net.http/jdk.internal.net.http.Http1Response$BodyReader.handle(Http1Response.java:720)
at java.net.http/jdk.internal.net.http.Http1Response$Receiver.accept(Http1Response.java:612)
at java.net.http/jdk.internal.net.http.Http1Response$BodyReader.tryAsyncReceive(Http1Response.java:750)
at java.net.http/jdk.internal.net.http.Http1AsyncReceiver.flush(Http1AsyncReceiver.java:233)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$LockingRestartableTask.run(SequentialScheduler.java:205)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:149)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:230)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:840)

@YunKuiLu
Copy link
Author

YunKuiLu commented May 9, 2025

i met the same matter how to solve it?

2025-05-09 16:07:23 INFO o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-9004"] 2025-05-09 16:07:23 INFO o.s.b.w.e.tomcat.TomcatWebServer - Tomcat started on port 9004 (http) with context path '/' 2025-05-09 16:07:23 INFO n.q.q.a.AiAssistantApplication - Started AiAssistantApplication in 3.248 seconds (process running for 3.764) 2025-05-09 16:07:38 ERROR reactor.core.publisher.Operators - Operator called default onErrorDropped reactor.core.Exceptions$ErrorCallbackNotImplemented: io.modelcontextprotocol.spec.McpError: Received unrecognized SSE event type: null Caused by: io.modelcontextprotocol.spec.McpError: Received unrecognized SSE event type: null at io.modelcontextprotocol.client.transport.WebFluxSseClientTransport.lambda$connect$0(WebFluxSseClientTransport.java:216) at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onNext(FluxHandleFuseable.java:179) at ...

@ai-srcflow

This issue arises because the server did not strictly follow the MCP protocol when sending messages.
Other implemented clients will ignore this situation, but WebFlux will throw an exception.
Since this PR has not been processed yet, the WebFlux client does not have a good solution for this situation.

If you are using spring-ai, you need to remove spring-ai-starter-mcp-client-webflux from your dependencies and replace it with spring-ai-starter-mcp-client to call the MCP service:

<!-- <dependency> -->
<!--     <groupId>org.springframework.ai</groupId> -->
<!--     <artifactId>spring-ai-starter-mcp-client-webflux</artifactId> -->
<!-- </dependency> -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants