diff --git a/pyproject.toml b/pyproject.toml index 4fa93e4ec..8be922138 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,10 @@ dev = [ ] [tool.uv.workspace] -members = ["agents"] +members = [ + "agents", + "checking", +] [tool.uv.sources] agents = { workspace = true } diff --git a/src/agents/mcp/server.py b/src/agents/mcp/server.py index dfd331eaa..07fea5f9a 100644 --- a/src/agents/mcp/server.py +++ b/src/agents/mcp/server.py @@ -3,7 +3,7 @@ import abc import asyncio import inspect -from collections.abc import Awaitable +from collections.abc import AsyncIterator, Awaitable from contextlib import AbstractAsyncContextManager, AsyncExitStack from datetime import timedelta from pathlib import Path @@ -15,7 +15,15 @@ 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 ( + AnyUrl, + CallToolResult, + GetPromptResult, + InitializeResult, + ListPromptsResult, + ListResourcesResult, + ReadResourceResult, +) from typing_extensions import NotRequired, TypedDict from ..exceptions import UserError @@ -78,6 +86,21 @@ 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: AnyUrl) -> ReadResourceResult: + """Read the content of a resource by URI.""" + pass + + @abc.abstractmethod + async def subscribe_resource(self, uri: AnyUrl, **kwargs) -> AsyncIterator[SessionMessage]: + """Subscribe to resource updates.""" + pass + @abc.abstractmethod async def list_prompts( self, @@ -140,7 +163,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 @@ -326,6 +348,28 @@ 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: AnyUrl) -> 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: AnyUrl, **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): + yield msg + async def list_prompts( self, ) -> ListPromptsResult: