-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Add in-memory transport #25
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
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
acb89b4
Add in-memory transport
nick-merrill 85e9e19
handle ping requests
nick-merrill b544f93
info => debug
nick-merrill 4f1bca6
ruff
nick-merrill 488a424
ruff E501
nick-merrill 593efcb
fix type
nick-merrill 21d0ded
ruff
nick-merrill ff86ee7
self review
nick-merrill bef37ce
add options to client server connection
nick-merrill 0ae03fa
cleanup from review comments
nick-merrill 5dd79b2
move memory.py from server/ to shared/
nick-merrill a64d1ee
bump minor version and relock
nick-merrill File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
""" | ||
In-memory transports | ||
""" | ||
|
||
from contextlib import asynccontextmanager | ||
from datetime import timedelta | ||
from typing import AsyncGenerator | ||
|
||
import anyio | ||
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream | ||
|
||
from mcp_python.client.session import ClientSession | ||
from mcp_python.server import Server | ||
from mcp_python.types import JSONRPCMessage | ||
|
||
MessageStream = tuple[ | ||
MemoryObjectReceiveStream[JSONRPCMessage | Exception], | ||
MemoryObjectSendStream[JSONRPCMessage] | ||
] | ||
|
||
@asynccontextmanager | ||
async def create_client_server_memory_streams() -> AsyncGenerator[ | ||
tuple[MessageStream, MessageStream], | ||
None | ||
]: | ||
""" | ||
Creates a pair of bidirectional memory streams for client-server communication. | ||
|
||
Returns: | ||
A tuple of (client_streams, server_streams) where each is a tuple of | ||
(read_stream, write_stream) | ||
""" | ||
# Create streams for both directions | ||
server_to_client_send, server_to_client_receive = anyio.create_memory_object_stream[ | ||
JSONRPCMessage | Exception | ||
](1) | ||
client_to_server_send, client_to_server_receive = anyio.create_memory_object_stream[ | ||
JSONRPCMessage | Exception | ||
](1) | ||
|
||
client_streams = (server_to_client_receive, client_to_server_send) | ||
server_streams = (client_to_server_receive, server_to_client_send) | ||
|
||
async with ( | ||
server_to_client_receive, | ||
client_to_server_send, | ||
client_to_server_receive, | ||
server_to_client_send, | ||
): | ||
yield client_streams, server_streams | ||
|
||
|
||
@asynccontextmanager | ||
async def create_connected_server_and_client_session( | ||
server: Server, | ||
read_timeout_seconds: timedelta | None = None, | ||
raise_exceptions: bool = False, | ||
) -> AsyncGenerator[ClientSession, None]: | ||
"""Creates a ClientSession that is connected to a running MCP server.""" | ||
async with create_client_server_memory_streams() as ( | ||
client_streams, | ||
server_streams, | ||
): | ||
client_read, client_write = client_streams | ||
server_read, server_write = server_streams | ||
|
||
# Create a cancel scope for the server task | ||
async with anyio.create_task_group() as tg: | ||
tg.start_soon( | ||
lambda: server.run( | ||
server_read, | ||
server_write, | ||
server.create_initialization_options(), | ||
raise_exceptions=raise_exceptions, | ||
) | ||
) | ||
|
||
try: | ||
async with ClientSession( | ||
read_stream=client_read, | ||
write_stream=client_write, | ||
read_timeout_seconds=read_timeout_seconds, | ||
) as client_session: | ||
await client_session.initialize() | ||
yield client_session | ||
finally: | ||
tg.cancel_scope.cancel() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import pytest | ||
from pydantic import AnyUrl | ||
|
||
from mcp_python.server import Server | ||
from mcp_python.server.types import InitializationOptions | ||
from mcp_python.types import Resource, ServerCapabilities | ||
|
||
TEST_INITIALIZATION_OPTIONS = InitializationOptions( | ||
server_name="my_mcp_server", | ||
server_version="0.1.0", | ||
capabilities=ServerCapabilities(), | ||
) | ||
|
||
@pytest.fixture | ||
def mcp_server() -> Server: | ||
server = Server(name="test_server") | ||
|
||
@server.list_resources() | ||
async def handle_list_resources(): | ||
return [ | ||
Resource( | ||
uri=AnyUrl("memory://test"), | ||
name="Test Resource", | ||
description="A test resource" | ||
) | ||
] | ||
|
||
return server |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import pytest | ||
from typing_extensions import AsyncGenerator | ||
|
||
from mcp_python.client.session import ClientSession | ||
from mcp_python.server import Server | ||
from mcp_python.shared.memory import ( | ||
create_connected_server_and_client_session, | ||
) | ||
from mcp_python.types import ( | ||
EmptyResult, | ||
) | ||
|
||
|
||
@pytest.fixture | ||
async def client_connected_to_server( | ||
mcp_server: Server, | ||
) -> AsyncGenerator[ClientSession, None]: | ||
async with create_connected_server_and_client_session(mcp_server) as client_session: | ||
yield client_session | ||
|
||
|
||
@pytest.mark.anyio | ||
async def test_memory_server_and_client_connection( | ||
client_connected_to_server: ClientSession, | ||
): | ||
"""Shows how a client and server can communicate over memory streams.""" | ||
response = await client_connected_to_server.send_ping() | ||
assert isinstance(response, EmptyResult) |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.