-
Notifications
You must be signed in to change notification settings - Fork 2.9k
feat: add MCP Resources support #2060
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
Changes from 1 commit
396abb6
a469a0e
037610b
151b7f3
e4aae92
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,15 +7,16 @@ | |
| from contextlib import AbstractAsyncContextManager, AsyncExitStack | ||
| from datetime import timedelta | ||
| from pathlib import Path | ||
| from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar | ||
| from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar, AsyncIterator | ||
|
|
||
|
|
||
| from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream | ||
| from mcp import ClientSession, StdioServerParameters, Tool as MCPTool, stdio_client | ||
| from mcp.client.session import MessageHandlerFnT | ||
| from mcp.client.sse import sse_client | ||
| from mcp.client.streamable_http import GetSessionIdCallback, streamablehttp_client | ||
| from mcp.shared.message import SessionMessage | ||
| from mcp.types import CallToolResult, GetPromptResult, InitializeResult, ListPromptsResult | ||
| from mcp.types import CallToolResult, GetPromptResult, InitializeResult, ListPromptsResult, ListResourcesResult, ReadResourceResult | ||
| from typing_extensions import NotRequired, TypedDict | ||
|
|
||
| from ..exceptions import UserError | ||
|
|
@@ -78,6 +79,19 @@ async def call_tool(self, tool_name: str, arguments: dict[str, Any] | None) -> C | |
| """Invoke a tool on the server.""" | ||
| pass | ||
|
|
||
| @abc.abstractmethod | ||
| async def list_resources(self)-> ListResourcesResult: | ||
| """List resources available on the server.""" | ||
| pass | ||
| @abc.abstractmethod | ||
| async def read_resource(self, uri:str)-> ReadResourceResult: | ||
| """Read the content of a resource by URI.""" | ||
| pass | ||
| @abc.abstractmethod | ||
| async def subscribe_resource(self, uri:str, **kwargs) -> AsyncIterator[SessionMessage]: | ||
| """Subscribe to resource updates.""" | ||
| pass | ||
|
Comment on lines
78
to
102
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Introducing Useful? React with 👍 / 👎. |
||
|
|
||
| @abc.abstractmethod | ||
| async def list_prompts( | ||
| self, | ||
|
|
@@ -91,7 +105,7 @@ async def get_prompt( | |
| ) -> GetPromptResult: | ||
| """Get a specific prompt from the server.""" | ||
| pass | ||
|
|
||
|
|
||
| class _MCPServerWithClientSession(MCPServer, abc.ABC): | ||
| """Base class for MCP servers that use a `ClientSession` to communicate with the server.""" | ||
|
|
@@ -140,7 +154,6 @@ def __init__( | |
| self.max_retry_attempts = max_retry_attempts | ||
| self.retry_backoff_seconds_base = retry_backoff_seconds_base | ||
| self.message_handler = message_handler | ||
|
|
||
| # The cache is always dirty at startup, so that we fetch tools at least once | ||
| self._cache_dirty = True | ||
| self._tools_list: list[MCPTool] | None = None | ||
|
|
@@ -288,6 +301,10 @@ async def connect(self): | |
| await self.cleanup() | ||
| raise | ||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
| async def list_tools( | ||
| self, | ||
| run_context: RunContextWrapper[Any] | None = None, | ||
|
|
@@ -326,6 +343,30 @@ async def call_tool(self, tool_name: str, arguments: dict[str, Any] | None) -> C | |
|
|
||
| return await self._run_with_retries(lambda: session.call_tool(tool_name, arguments)) | ||
|
|
||
| async def list_resources(self)-> ListResourcesResult: | ||
| if not self.session: | ||
| raise UserError("Server not initialized. Make sure you call `connect()` first.") | ||
| session = self.session | ||
| assert session is not None | ||
| return await session.list_resources() | ||
|
|
||
| async def read_resource(self,uri:str)-> ReadResourceResult: | ||
| if not self.session: | ||
| raise UserError("Server not initialized. Make sure you call `connect()` first.") | ||
| session = self.session | ||
| assert session is not None | ||
| return await session.read_resource(uri) | ||
|
|
||
| async def subscribe_resource(self, uri:str, **kwargs)-> AsyncIterator[SessionMessage]: | ||
| if not self.session: | ||
| raise UserError("Server not initialized. Make sure you call `connect()` first.") | ||
| session = self.session | ||
| assert session is not None | ||
| async for msg in session.subscribe_resource(uri, **kwargs): | ||
| yield msg | ||
|
|
||
|
|
||
|
|
||
| async def list_prompts( | ||
| self, | ||
| ) -> ListPromptsResult: | ||
|
|
@@ -593,6 +634,8 @@ class MCPServerStreamableHttpParams(TypedDict): | |
| """Custom HTTP client factory for configuring httpx.AsyncClient behavior.""" | ||
|
|
||
|
|
||
|
|
||
|
|
||
| class MCPServerStreamableHttp(_MCPServerWithClientSession): | ||
| """MCP server implementation that uses the Streamable HTTP transport. See the [spec] | ||
| (https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) | ||
|
|
@@ -688,4 +731,4 @@ def create_streams( | |
| @property | ||
| def name(self) -> str: | ||
| """A readable name for the server.""" | ||
| return self._name | ||
| return self._name | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
checkingAdding
"checking"totool.uv.workspace.membersintroduces a build-time failure because the repository contains nochecking/package. Runninguvormakecommands now causes uv to error with “No such workspace member ‘checking’”, preventing dependency installs or packaging. This looks unrelated to the MCP resources feature and should be reverted or the directory added.Useful? React with 👍 / 👎.