diff --git a/mcp-inmemory-transport/README.md b/mcp-inmemory-transport/README.md
new file mode 100644
index 000000000..c1d1dec47
--- /dev/null
+++ b/mcp-inmemory-transport/README.md
@@ -0,0 +1,221 @@
+# MCP In-Memory Transport
+
+In-memory transport implementation for the Model Context Protocol (MCP) Java SDK.
+
+## Overview
+
+The `mcp-inmemory-transport` module provides an in-memory transport layer for the Model Context Protocol (MCP) Java SDK.
+This transport is particularly useful for testing, local development, and scenarios where you need to establish direct communication between MCP client and server components without network overhead.
+
+## Features
+
+- **In-Memory Communication**: Enables direct communication between MCP client and server without network calls
+- **Reactive Streams**: Built on Project Reactor for non-blocking, reactive communication
+- **Easy Integration**: Seamlessly integrates with the MCP Java SDK
+- **Testing-Friendly**: Ideal for unit and integration testing of MCP implementations
+
+## Getting Started
+
+### Prerequisites
+
+- Java 17 or higher
+- Maven or Gradle build system
+- MCP Java SDK core dependency
+
+### Installation
+
+#### Maven
+
+Add the following dependency to your `pom.xml`:
+
+```xml
+
+ io.modelcontextprotocol.sdk
+ mcp-inmemory-transport
+ 0.12.0-SNAPSHOT
+
+```
+
+#### Gradle
+
+Add the following to your `build.gradle`:
+
+```gradle
+implementation 'io.modelcontextprotocol.sdk:mcp-inmemory-transport:0.12.0-SNAPSHOT'
+```
+
+### Usage
+
+#### Basic Setup
+
+To use the in-memory transport, you'll need to create an `InMemoryTransport` instance and use it to create both client and server transports:
+
+```java
+// Create the shared in-memory transport
+InMemoryTransport transport = new InMemoryTransport();
+
+// Create server transport provider
+InMemoryServerTransportProvider serverProvider = new InMemoryServerTransportProvider(transport);
+
+// Create client transport
+InMemoryClientTransport clientTransport = new InMemoryClientTransport(transport);
+```
+
+#### Creating an MCP Server
+
+```java
+McpServer server = McpServer.sync(serverProvider)
+ .toolCall(McpSchema.Tool.builder()
+ .name("echo")
+ .description("Echoes the input")
+ .inputSchema(new McpSchema.JsonSchema(
+ "object",
+ Map.of("message", Map.of("type", "string")),
+ List.of("message"),
+ true,
+ null,
+ null))
+ .build(), (exchange, request) -> {
+ String message = (String) request.arguments().get("message");
+ return new McpSchema.CallToolResult("Echo: " + message, false);
+ })
+ .build();
+```
+
+#### Creating an MCP Client
+
+```java
+try (McpClient client = McpClient.sync(clientTransport).build()) {
+ // Initialize the client
+ client.initialize();
+
+ // Call a tool
+ McpSchema.CallToolRequest request = new McpSchema.CallToolRequest(
+ "echo",
+ Map.of("message", "Hello, MCP!")
+ );
+
+ McpSchema.CallToolResult result = client.callTool(request);
+ System.out.println("Result: " + result.content());
+}
+```
+
+### Complete Example
+
+Here's a complete example showing how to set up and use the in-memory transport:
+
+```java
+import io.modelcontextprotocol.client.McpClient;
+import io.modelcontextprotocol.server.McpServer;
+import io.modelcontextprotocol.spec.McpSchema;
+import io.modelcontextprotocol.transport.inmemory.InMemoryTransport;
+import io.modelcontextprotocol.transport.inmemory.InMemoryClientTransport;
+import io.modelcontextprotocol.transport.inmemory.InMemoryServerTransportProvider;
+
+import java.util.List;
+import java.util.Map;
+
+public class InMemoryTransportExample {
+ public static void main(String[] args) {
+ // Create the shared in-memory transport
+ InMemoryTransport transport = new InMemoryTransport();
+
+ // Create server transport provider
+ InMemoryServerTransportProvider serverProvider = new InMemoryServerTransportProvider(transport);
+
+ // Create client transport
+ InMemoryClientTransport clientTransport = new InMemoryClientTransport(transport);
+
+ // Set up the server
+ McpServer server = McpServer.sync(serverProvider)
+ .toolCall(McpSchema.Tool.builder()
+ .name("calculate")
+ .description("Performs a calculation")
+ .inputSchema(new McpSchema.JsonSchema(
+ "object",
+ Map.of(
+ "operation", Map.of("type", "string", "enum", List.of("add", "subtract")),
+ "a", Map.of("type", "number"),
+ "b", Map.of("type", "number")
+ ),
+ List.of("operation", "a", "b"),
+ true,
+ null,
+ null))
+ .build(), (exchange, request) -> {
+ String operation = (String) request.arguments().get("operation");
+ Number a = (Number) request.arguments().get("a");
+ Number b = (Number) request.arguments().get("b");
+
+ double result;
+ switch (operation) {
+ case "add":
+ result = a.doubleValue() + b.doubleValue();
+ break;
+ case "subtract":
+ result = a.doubleValue() - b.doubleValue();
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown operation: " + operation);
+ }
+
+ return new McpSchema.CallToolResult(String.valueOf(result), false);
+ })
+ .build();
+
+ // Use the client
+ try (McpClient client = McpClient.sync(clientTransport).build()) {
+ // Initialize the client
+ client.initialize();
+
+ // Call the calculate tool
+ McpSchema.CallToolRequest addRequest = new McpSchema.CallToolRequest(
+ "calculate",
+ Map.of("operation", "add", "a", 10, "b", 5)
+ );
+
+ McpSchema.CallToolResult addResult = client.callTool(addRequest);
+ System.out.println("10 + 5 = " + addResult.content().get(0));
+
+ McpSchema.CallToolRequest subtractRequest = new McpSchema.CallToolRequest(
+ "calculate",
+ Map.of("operation", "subtract", "a", 10, "b", 3)
+ );
+
+ McpSchema.CallToolResult subtractResult = client.callTool(subtractRequest);
+ System.out.println("10 - 3 = " + subtractResult.content().get(0));
+ }
+ }
+}
+```
+
+## Architecture
+
+The in-memory transport consists of three main components:
+
+1. **InMemoryTransport**: The core transport that manages the communication channels between client and server
+2. **InMemoryClientTransport**: Implements the client-side transport interface
+3. **InMemoryServerTransportProvider**: Provides the server-side transport implementation
+
+The transport uses Reactor's `Sinks.Many` to create multicast channels for message passing between client and server.
+
+## Testing
+
+The module includes comprehensive unit tests. To run them:
+
+```bash
+mvn test
+```
+
+## Contributing
+
+Contributions are welcome! Please see [CONTRIBUTING.md](../CONTRIBUTING.md) for details on how to contribute to this project.
+
+## License
+
+This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
+
+## Related Projects
+
+- [MCP Java SDK](https://github.com/modelcontextprotocol/java-sdk)
+- [Model Context Protocol Specification](https://github.com/modelcontextprotocol/specification)
\ No newline at end of file
diff --git a/mcp-inmemory-transport/pom.xml b/mcp-inmemory-transport/pom.xml
new file mode 100644
index 000000000..afbe5a2be
--- /dev/null
+++ b/mcp-inmemory-transport/pom.xml
@@ -0,0 +1,69 @@
+
+
+ 4.0.0
+
+
+ io.modelcontextprotocol.sdk
+ mcp-parent
+ 0.12.0-SNAPSHOT
+ ../pom.xml
+
+
+ mcp-inmemory-transport
+ Java SDK MCP In-Memory Transport
+ In-memory transport implementation for the Model Context Protocol (MCP)
+
+
+
+ io.modelcontextprotocol.sdk
+ mcp
+ ${project.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-logging
+ 3.4.0-SNAPSHOT
+ provided
+
+
+ io.projectreactor
+ reactor-core
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit.version}
+ test
+
+
+ org.assertj
+ assertj-core
+ ${assert4j.version}
+ test
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+
+
+
+ spring-snapshots
+ Spring Snapshots
+ https://repo.spring.io/snapshot
+
+ false
+
+
+
+
+
diff --git a/mcp-inmemory-transport/src/main/java/io/modelcontextprotocol/transport/inmemory/InMemoryClientTransport.java b/mcp-inmemory-transport/src/main/java/io/modelcontextprotocol/transport/inmemory/InMemoryClientTransport.java
new file mode 100644
index 000000000..690d05b0f
--- /dev/null
+++ b/mcp-inmemory-transport/src/main/java/io/modelcontextprotocol/transport/inmemory/InMemoryClientTransport.java
@@ -0,0 +1,61 @@
+package io.modelcontextprotocol.transport.inmemory;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import io.modelcontextprotocol.spec.McpClientTransport;
+import io.modelcontextprotocol.spec.McpSchema.JSONRPCMessage;
+import reactor.core.Disposable;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.Sinks;
+
+import java.util.function.Function;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.Optional.ofNullable;
+
+public class InMemoryClientTransport implements McpClientTransport {
+
+ private final InMemoryTransport transport;
+
+ private Disposable disposable;
+
+ public InMemoryClientTransport( InMemoryTransport transport ) {
+ this.transport = requireNonNull(transport, "transport cannot be null");
+ }
+
+ @Override
+ public Mono connect(Function, Mono> handler) {
+ disposable = transport.clientSink().asFlux()
+ .flatMap(message -> handler.apply(Mono.just(message)))
+ .subscribe( message -> sendMessage( message ).subscribe() );
+ return Mono.empty();
+ }
+
+ @Override
+ public Mono sendMessage(JSONRPCMessage message) {
+ var result = ofNullable(transport.serverSink())
+ .map( s -> s.tryEmitNext(message))
+ .orElse( Sinks.EmitResult.FAIL_TERMINATED );
+ return switch( result ) {
+ case OK -> Mono.empty();
+ case FAIL_TERMINATED,
+ FAIL_NON_SERIALIZED,
+ FAIL_OVERFLOW,
+ FAIL_CANCELLED,
+ FAIL_ZERO_SUBSCRIBER -> Mono.error( () -> new Sinks.EmissionException(result) );
+ };
+ }
+
+ @Override
+ public T unmarshalFrom(Object data, TypeReference typeRef) {
+ return transport.objectMapper().convertValue(data, typeRef);
+ }
+
+ @Override
+ public Mono closeGracefully() {
+ if( disposable!=null && !disposable.isDisposed() ) {
+ disposable.dispose();
+ }
+ return Mono.empty();
+ }
+
+}
diff --git a/mcp-inmemory-transport/src/main/java/io/modelcontextprotocol/transport/inmemory/InMemoryServerTransport.java b/mcp-inmemory-transport/src/main/java/io/modelcontextprotocol/transport/inmemory/InMemoryServerTransport.java
new file mode 100644
index 000000000..114fc8d40
--- /dev/null
+++ b/mcp-inmemory-transport/src/main/java/io/modelcontextprotocol/transport/inmemory/InMemoryServerTransport.java
@@ -0,0 +1,49 @@
+package io.modelcontextprotocol.transport.inmemory;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import io.modelcontextprotocol.spec.McpSchema;
+import io.modelcontextprotocol.spec.McpServerTransport;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.Sinks;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.Optional.ofNullable;
+
+public class InMemoryServerTransport implements McpServerTransport {
+
+ private final InMemoryTransport transport;
+
+ public InMemoryServerTransport( InMemoryTransport transport ) {
+ this.transport = requireNonNull(transport, "transport cannot be null");
+ }
+
+ public Sinks.Many serverSink() {
+ return transport.serverSink();
+ }
+
+ @Override
+ public Mono closeGracefully() {
+ return Mono.empty();
+ }
+
+ @Override
+ public Mono sendMessage(McpSchema.JSONRPCMessage message) {
+ var result = ofNullable( transport.clientSink())
+ .map( s -> s.tryEmitNext(message) )
+ .orElse( Sinks.EmitResult.FAIL_TERMINATED );
+ return switch( result ) {
+ case OK -> Mono.empty();
+ case FAIL_TERMINATED,
+ FAIL_NON_SERIALIZED,
+ FAIL_OVERFLOW,
+ FAIL_CANCELLED,
+ FAIL_ZERO_SUBSCRIBER -> Mono.error( () -> new Sinks.EmissionException(result) );
+ };
+ }
+
+ @Override
+ public T unmarshalFrom(Object data, TypeReference typeRef) {
+ return transport.objectMapper().convertValue(data, typeRef);
+ }
+
+}
diff --git a/mcp-inmemory-transport/src/main/java/io/modelcontextprotocol/transport/inmemory/InMemoryServerTransportProvider.java b/mcp-inmemory-transport/src/main/java/io/modelcontextprotocol/transport/inmemory/InMemoryServerTransportProvider.java
new file mode 100644
index 000000000..9bcea3cf9
--- /dev/null
+++ b/mcp-inmemory-transport/src/main/java/io/modelcontextprotocol/transport/inmemory/InMemoryServerTransportProvider.java
@@ -0,0 +1,41 @@
+package io.modelcontextprotocol.transport.inmemory;
+
+import io.modelcontextprotocol.spec.McpSchema;
+import io.modelcontextprotocol.spec.McpServerSession;
+import io.modelcontextprotocol.spec.McpServerTransportProvider;
+import reactor.core.Disposable;
+import reactor.core.publisher.Mono;
+
+public class InMemoryServerTransportProvider implements McpServerTransportProvider {
+
+ private final InMemoryServerTransport serverTransport;
+ private Disposable disposable;
+
+ public InMemoryServerTransportProvider( InMemoryTransport transport ) {
+ serverTransport = new InMemoryServerTransport(transport);
+ }
+
+ @Override
+ public void setSessionFactory(McpServerSession.Factory sessionFactory) {
+
+ var session = sessionFactory.create(serverTransport);
+ disposable = serverTransport.serverSink().asFlux().subscribe(message -> {
+ session.handle(message).subscribe();
+ });
+ }
+
+ @Override
+ public Mono closeGracefully() {
+ if( disposable!=null && !disposable.isDisposed() ) {
+ disposable.dispose();
+ }
+ return Mono.empty();
+ }
+
+ @Override
+ public Mono notifyClients(String method, Object params) {
+ // Not implemented for in-memory transport
+ return Mono.empty();
+ }
+
+}
diff --git a/mcp-inmemory-transport/src/main/java/io/modelcontextprotocol/transport/inmemory/InMemoryTransport.java b/mcp-inmemory-transport/src/main/java/io/modelcontextprotocol/transport/inmemory/InMemoryTransport.java
new file mode 100644
index 000000000..d80f07cae
--- /dev/null
+++ b/mcp-inmemory-transport/src/main/java/io/modelcontextprotocol/transport/inmemory/InMemoryTransport.java
@@ -0,0 +1,24 @@
+package io.modelcontextprotocol.transport.inmemory;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.spec.McpSchema;
+import reactor.core.publisher.Sinks;
+
+import static java.util.Objects.requireNonNull;
+
+public record InMemoryTransport(
+ Sinks.Many clientSink,
+ Sinks.Many serverSink,
+ ObjectMapper objectMapper
+){
+ public InMemoryTransport {
+ requireNonNull(clientSink,"clientSink cannot be null!");
+ requireNonNull(serverSink,"serverSink cannot be null!");
+ requireNonNull(objectMapper,"objectMapper cannot be null!");
+ }
+ public InMemoryTransport() {
+ this( Sinks.many().multicast().onBackpressureBuffer(),
+ Sinks.many().multicast().onBackpressureBuffer(),
+ new ObjectMapper() );
+ }
+}
diff --git a/mcp-inmemory-transport/src/test/java/io/modelcontextprotocol/transport/inmemory/InMemoryTransportTest.java b/mcp-inmemory-transport/src/test/java/io/modelcontextprotocol/transport/inmemory/InMemoryTransportTest.java
new file mode 100644
index 000000000..cb52536e0
--- /dev/null
+++ b/mcp-inmemory-transport/src/test/java/io/modelcontextprotocol/transport/inmemory/InMemoryTransportTest.java
@@ -0,0 +1,80 @@
+package io.modelcontextprotocol.transport.inmemory;
+
+import io.modelcontextprotocol.client.McpClient;
+import io.modelcontextprotocol.server.McpServer;
+import io.modelcontextprotocol.spec.McpSchema;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+class InMemoryTransportTest {
+
+ final InMemoryTransport transport = new InMemoryTransport();
+
+ @BeforeEach
+ public void createSyncMCPServer() {
+ var serverProvider = new InMemoryServerTransportProvider(transport);
+ McpServer.sync(serverProvider)
+ .toolCall(McpSchema.Tool.builder()
+ .name("test-tool")
+ .description("a test tool")
+ .inputSchema(new McpSchema.JsonSchema("object", Map.of(), List.of(), true, null, null))
+ .build(), (exchange, request) ->
+ new McpSchema.CallToolResult("test-result", false)
+ )
+ .build();
+
+ }
+ @Test
+ void shouldSendMessageFromSyncClientToServer() {
+
+ var clientTransport = new InMemoryClientTransport(transport);
+
+ try(var client = McpClient.sync(clientTransport).build()) {
+
+ client.initialize();
+ var toolList = client.listTools();
+
+ assertFalse( toolList.tools().isEmpty() );
+ assertEquals( 1, toolList.tools().size() );
+
+ var result = client.callTool(new McpSchema.CallToolRequest("test-tool", Map.of()));
+
+ assertThat(result.content().get(0)).isInstanceOf(McpSchema.TextContent.class);
+ McpSchema.TextContent textContent = (McpSchema.TextContent) result.content().get(0);
+ assertThat(textContent.text()).isEqualTo("test-result");
+ }
+ }
+
+ @Test
+ void shouldSendMessageFromAsyncClientToServer() {
+ var clientTransport = new InMemoryClientTransport(transport);
+
+ var client = McpClient.async(clientTransport).build();
+
+ client.initialize()
+ .flatMap( initResult -> client.listTools() )
+ .flatMap( toolList -> {
+ assertFalse(toolList.tools().isEmpty());
+ assertEquals(1, toolList.tools().size());
+
+ return client.callTool(new McpSchema.CallToolRequest("test-tool", Map.of()));
+ })
+ .doFinally(signalType -> {
+ client.closeGracefully().subscribe();
+ })
+ .subscribe( result -> {
+
+ assertThat(result.content().get(0)).isInstanceOf(McpSchema.TextContent.class);
+ McpSchema.TextContent textContent = (McpSchema.TextContent) result.content().get(0);
+ assertThat(textContent.text()).isEqualTo("test-result");
+ });
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index c0b1f7a44..3859fbf57 100644
--- a/pom.xml
+++ b/pom.xml
@@ -61,7 +61,7 @@
3.26.3
5.10.2
- 5.17.0
+ 5.2.0
1.20.4
1.17.5
1.21.0
@@ -103,6 +103,7 @@
mcp-bom
mcp
+ mcp-inmemory-transport
mcp-spring/mcp-spring-webflux
mcp-spring/mcp-spring-webmvc
mcp-test
@@ -182,7 +183,7 @@
maven-surefire-plugin
${maven-surefire-plugin.version}
- ${surefireArgLine} -javaagent:${org.mockito:mockito-core:jar}
+ ${surefireArgLine}
false
false