Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@ dev = [
]

[tool.uv.workspace]
members = ["agents"]
members = [
"agents",
"checking",
]
Comment on lines 79 to +83

Choose a reason for hiding this comment

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

P1 Badge Remove nonexistent workspace member checking

Adding "checking" to tool.uv.workspace.members introduces a build-time failure because the repository contains no checking/ package. Running uv or make commands 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 👍 / 👎.


[tool.uv.sources]
agents = { workspace = true }
Expand Down
53 changes: 48 additions & 5 deletions src/agents/mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Choose a reason for hiding this comment

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

P1 Badge New abstract resource methods break existing MCPServer subclasses

Introducing list_resources, read_resource, and subscribe_resource as abstract methods on MCPServer means every existing subclass must implement them. Classes like tests/mcp/test_prompt_server.FakeMCPPromptServer (and any user-defined servers) still lack these methods, so instantiating them now raises TypeError: Can't instantiate abstract class … and the current test suite will fail. Consider providing default implementations or updating all subclasses.

Useful? React with 👍 / 👎.


@abc.abstractmethod
async def list_prompts(
self,
Expand All @@ -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."""
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -288,6 +301,10 @@ async def connect(self):
await self.cleanup()
raise





async def list_tools(
self,
run_context: RunContextWrapper[Any] | None = None,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -688,4 +731,4 @@ def create_streams(
@property
def name(self) -> str:
"""A readable name for the server."""
return self._name
return self._name
Loading