Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
sproutMultiModuleBuild {
moduleToBuild = 'mcp-parent'
nodeLabel = 'ephemeral'
tribes = ['global']
notifySlackGroupsOnFailure = ['@kevin']
jdk = 17
deployRCBranches = true
}
6 changes: 3 additions & 3 deletions mcp-bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.modelcontextprotocol.sdk</groupId>
<groupId>com.sproutsocial.io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-parent</artifactId>
<version>0.12.0-SNAPSHOT</version>
<version>0.12.0-sprout</version>
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: why not SNAPSHOT?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@david-huber actually i wasn't sure if we even can or should push SNAPSHOT into nexus. I am thinking this should just be 0.12.0 or 0.12.0-rc1 or something?

</parent>

<artifactId>mcp-bom</artifactId>
Expand Down Expand Up @@ -95,4 +95,4 @@
</plugins>
</build>

</project>
</project>
4 changes: 2 additions & 2 deletions mcp-spring/mcp-spring-webflux/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.modelcontextprotocol.sdk</groupId>
<groupId>com.sproutsocial.io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-parent</artifactId>
<version>0.12.0-SNAPSHOT</version>
<version>0.12.0-sprout</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>mcp-spring-webflux</artifactId>
Expand Down
4 changes: 2 additions & 2 deletions mcp-spring/mcp-spring-webmvc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.modelcontextprotocol.sdk</groupId>
<groupId>com.sproutsocial.io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-parent</artifactId>
<version>0.12.0-SNAPSHOT</version>
<version>0.12.0-sprout</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>mcp-spring-webmvc</artifactId>
Expand Down
6 changes: 3 additions & 3 deletions mcp-test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.modelcontextprotocol.sdk</groupId>
<groupId>com.sproutsocial.io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-parent</artifactId>
<version>0.12.0-SNAPSHOT</version>
<version>0.12.0-sprout</version>
</parent>
<artifactId>mcp-test</artifactId>
<packaging>jar</packaging>
Expand Down Expand Up @@ -101,4 +101,4 @@
</dependencies>


</project>
</project>
6 changes: 3 additions & 3 deletions mcp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.modelcontextprotocol.sdk</groupId>
<groupId>com.sproutsocial.io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-parent</artifactId>
<version>0.12.0-SNAPSHOT</version>
<version>0.12.0-sprout</version>
</parent>
<artifactId>mcp</artifactId>
<packaging>jar</packaging>
Expand Down Expand Up @@ -220,4 +220,4 @@
</dependencies>


</project>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.DefaultMcpTransportContext;
import io.modelcontextprotocol.server.McpTransportContext;
import io.modelcontextprotocol.server.McpTransportContextExtractor;
import io.modelcontextprotocol.spec.McpError;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpServerSession;
Expand Down Expand Up @@ -102,6 +105,8 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
/** Map of active client sessions, keyed by session ID */
private final Map<String, McpServerSession> sessions = new ConcurrentHashMap<>();

private McpTransportContextExtractor<HttpServletRequest> contextExtractor;

/** Flag indicating if the transport is in the process of shutting down */
private final AtomicBoolean isClosing = new AtomicBoolean(false);

Expand Down Expand Up @@ -144,7 +149,7 @@ public HttpServletSseServerTransportProvider(ObjectMapper objectMapper, String m
@Deprecated
public HttpServletSseServerTransportProvider(ObjectMapper objectMapper, String baseUrl, String messageEndpoint,
String sseEndpoint) {
this(objectMapper, baseUrl, messageEndpoint, sseEndpoint, null);
this(objectMapper, baseUrl, messageEndpoint, sseEndpoint, null, null);
}

/**
Expand All @@ -163,11 +168,33 @@ public HttpServletSseServerTransportProvider(ObjectMapper objectMapper, String b
@Deprecated
public HttpServletSseServerTransportProvider(ObjectMapper objectMapper, String baseUrl, String messageEndpoint,
String sseEndpoint, Duration keepAliveInterval) {
this(objectMapper, baseUrl, messageEndpoint, sseEndpoint, keepAliveInterval, null);
}

/**
* Creates a new HttpServletSseServerTransportProvider instance with a custom SSE
* endpoint.
* @param objectMapper The JSON object mapper to use for message
* serialization/deserialization
* @param baseUrl The base URL for the server transport
* @param messageEndpoint The endpoint path where clients will send their messages
* @param sseEndpoint The endpoint path where clients will establish SSE connections
* @param keepAliveInterval The interval for keep-alive pings, or null to disable
* keep-alive functionality
* @param contextExtractor The extractor for transport context from the request.
* @deprecated Use the builder {@link #builder()} instead for better configuration
* options.
*/
@Deprecated
public HttpServletSseServerTransportProvider(ObjectMapper objectMapper, String baseUrl, String messageEndpoint,
String sseEndpoint, Duration keepAliveInterval,
McpTransportContextExtractor<HttpServletRequest> contextExtractor) {

this.objectMapper = objectMapper;
this.baseUrl = baseUrl;
this.messageEndpoint = messageEndpoint;
this.sseEndpoint = sseEndpoint;
this.contextExtractor = contextExtractor;

if (keepAliveInterval != null) {

Expand Down Expand Up @@ -339,10 +366,13 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
body.append(line);
}

final McpTransportContext transportContext = contextExtractor.extract(request,
new DefaultMcpTransportContext());
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(objectMapper, body.toString());

// Process the message through the session's handle method
session.handle(message).block(); // Block for Servlet compatibility
// Block for Servlet compatibility
session.handle(message).contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext)).block();

response.setStatus(HttpServletResponse.SC_OK);
}
Expand Down Expand Up @@ -534,6 +564,8 @@ public static class Builder {

private String sseEndpoint = DEFAULT_SSE_ENDPOINT;

private McpTransportContextExtractor<HttpServletRequest> contextExtractor = (serverRequest, context) -> context;

private Duration keepAliveInterval;

/**
Expand Down Expand Up @@ -583,6 +615,19 @@ public Builder sseEndpoint(String sseEndpoint) {
return this;
}

/**
* Sets the context extractor for extracting transport context from the request.
* @param contextExtractor The context extractor to use. Must not be null.
* @return this builder instance
* @throws IllegalArgumentException if contextExtractor is null
*/
public HttpServletSseServerTransportProvider.Builder contextExtractor(
McpTransportContextExtractor<HttpServletRequest> contextExtractor) {
Assert.notNull(contextExtractor, "Context extractor must not be null");
this.contextExtractor = contextExtractor;
return this;
}

/**
* Sets the interval for keep-alive pings.
* <p>
Expand All @@ -609,7 +654,7 @@ public HttpServletSseServerTransportProvider build() {
throw new IllegalStateException("MessageEndpoint must be set");
}
return new HttpServletSseServerTransportProvider(objectMapper, baseUrl, messageEndpoint, sseEndpoint,
keepAliveInterval);
keepAliveInterval, contextExtractor);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,9 @@ public Mono<Void> sendNotification(String method, Object params) {
* @return a Mono that completes when the message is processed
*/
public Mono<Void> handle(McpSchema.JSONRPCMessage message) {
return Mono.defer(() -> {
return Mono.deferContextual(ctx -> {
McpTransportContext transportContext = ctx.getOrDefault(McpTransportContext.KEY, McpTransportContext.EMPTY);

// TODO handle errors for communication to without initialization happening
// first
if (message instanceof McpSchema.JSONRPCResponse response) {
Expand All @@ -214,7 +216,7 @@ public Mono<Void> handle(McpSchema.JSONRPCMessage message) {
}
else if (message instanceof McpSchema.JSONRPCRequest request) {
logger.debug("Received request: {}", request);
return handleIncomingRequest(request).onErrorResume(error -> {
return handleIncomingRequest(request, transportContext).onErrorResume(error -> {
var errorResponse = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), null,
new McpSchema.JSONRPCResponse.JSONRPCError(McpSchema.ErrorCodes.INTERNAL_ERROR,
error.getMessage(), null));
Expand All @@ -227,7 +229,7 @@ else if (message instanceof McpSchema.JSONRPCNotification notification) {
// happening first
logger.debug("Received notification: {}", notification);
// TODO: in case of error, should the POST request be signalled?
return handleIncomingNotification(notification)
return handleIncomingNotification(notification, transportContext)
.doOnError(error -> logger.error("Error handling notification: {}", error.getMessage()));
}
else {
Expand All @@ -240,9 +242,11 @@ else if (message instanceof McpSchema.JSONRPCNotification notification) {
/**
* Handles an incoming JSON-RPC request by routing it to the appropriate handler.
* @param request The incoming JSON-RPC request
* @param transportContext
* @return A Mono containing the JSON-RPC response
*/
private Mono<McpSchema.JSONRPCResponse> handleIncomingRequest(McpSchema.JSONRPCRequest request) {
private Mono<McpSchema.JSONRPCResponse> handleIncomingRequest(McpSchema.JSONRPCRequest request,
McpTransportContext transportContext) {
return Mono.defer(() -> {
Mono<?> resultMono;
if (McpSchema.METHOD_INITIALIZE.equals(request.method())) {
Expand All @@ -266,7 +270,11 @@ private Mono<McpSchema.JSONRPCResponse> handleIncomingRequest(McpSchema.JSONRPCR
error.message(), error.data())));
}

resultMono = this.exchangeSink.asMono().flatMap(exchange -> handler.handle(exchange, request.params()));
resultMono = this.exchangeSink.asMono().flatMap(exchange -> {
McpAsyncServerExchange newExchange = new McpAsyncServerExchange(exchange.sessionId(), this,
exchange.getClientCapabilities(), exchange.getClientInfo(), transportContext);
return handler.handle(newExchange, request.params());
});
}
return resultMono
.map(result -> new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), result, null))
Expand All @@ -280,24 +288,30 @@ private Mono<McpSchema.JSONRPCResponse> handleIncomingRequest(McpSchema.JSONRPCR
/**
* Handles an incoming JSON-RPC notification by routing it to the appropriate handler.
* @param notification The incoming JSON-RPC notification
* @param transportContext
* @return A Mono that completes when the notification is processed
*/
private Mono<Void> handleIncomingNotification(McpSchema.JSONRPCNotification notification) {
private Mono<Void> handleIncomingNotification(McpSchema.JSONRPCNotification notification,
McpTransportContext transportContext) {
return Mono.defer(() -> {
if (McpSchema.METHOD_NOTIFICATION_INITIALIZED.equals(notification.method())) {
this.state.lazySet(STATE_INITIALIZED);
// FIXME: The session ID passed here is not the same as the one in the
// legacy SSE transport.
exchangeSink.tryEmitValue(new McpAsyncServerExchange(this.id, this, clientCapabilities.get(),
clientInfo.get(), McpTransportContext.EMPTY));
clientInfo.get(), transportContext));
}

var handler = notificationHandlers.get(notification.method());
if (handler == null) {
logger.warn("No handler registered for notification method: {}", notification);
return Mono.empty();
}
return this.exchangeSink.asMono().flatMap(exchange -> handler.handle(exchange, notification.params()));
return this.exchangeSink.asMono().flatMap(exchange -> {
McpAsyncServerExchange newExchange = new McpAsyncServerExchange(exchange.sessionId(), this,
exchange.getClientCapabilities(), exchange.getClientInfo(), transportContext);
return handler.handle(newExchange, notification.params());
});
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,21 @@ class StdioMcpAsyncClientTests extends AbstractMcpAsyncClientTests {
@Override
protected McpClientTransport createMcpTransport() {
ServerParameters stdioParams;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
stdioParams = ServerParameters.builder("cmd.exe")
.args("/c", "npx.cmd", "-y", "@modelcontextprotocol/server-everything", "stdio")
.build();
}
String currentPath = System.getenv("PATH");
String nodePath = System.getProperty("user.dir") + "/node";
String newPath = nodePath + (currentPath != null ? System.getProperty("path.separator") + currentPath : "");

if (System.getProperty("os.name").toLowerCase().contains("win")) {
stdioParams = ServerParameters.builder("./node/npx.cmd")
.args("-y", "@modelcontextprotocol/server-everything", "stdio")
.addEnvVar("PATH", newPath)
.build();
}
else {
stdioParams = ServerParameters.builder("npx")
.args("-y", "@modelcontextprotocol/server-everything", "stdio")
.build();
stdioParams = ServerParameters.builder("./node/npx")
.args("-y", "@modelcontextprotocol/server-everything", "stdio")
.addEnvVar("PATH", newPath)
.build();
}
return new StdioClientTransport(stdioParams);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,21 @@ class StdioMcpSyncClientTests extends AbstractMcpSyncClientTests {
@Override
protected McpClientTransport createMcpTransport() {
ServerParameters stdioParams;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
stdioParams = ServerParameters.builder("cmd.exe")
.args("/c", "npx.cmd", "-y", "@modelcontextprotocol/server-everything", "stdio")
String currentPath = System.getenv("PATH");
String nodePath = System.getProperty("user.dir") + "/node";
String newPath = nodePath + (currentPath != null ? System.getProperty("path.separator") + currentPath : "");

if (System.getProperty("os.name").toLowerCase().contains("win")) {
stdioParams = ServerParameters.builder("./node/npx.cmd")
.args("-y", "@modelcontextprotocol/server-everything", "stdio")
.addEnvVar("PATH", newPath)
.build();
}
else {
stdioParams = ServerParameters.builder("npx")
.args("-y", "@modelcontextprotocol/server-everything", "stdio")
.build();
stdioParams = ServerParameters.builder("./node/npx")
.args("-y", "@modelcontextprotocol/server-everything", "stdio")
.addEnvVar("PATH", newPath)
.build();
}
return new StdioClientTransport(stdioParams);
}
Expand Down
Loading