Skip to content

MCP Servers

MCPServer

Bases: ABC

Base class for Model Context Protocol servers.

Source code in src/agents/mcp/server.py
class MCPServer(abc.ABC):
    """Base class for Model Context Protocol servers."""

    def __init__(
        self,
        use_structured_content: bool = False,
        require_approval: RequireApprovalSetting = None,
        failure_error_function: ToolErrorFunction | None | _UnsetType = _UNSET,
        tool_meta_resolver: MCPToolMetaResolver | None = None,
        custom_data_extractor: MCPToolCustomDataExtractor | None = None,
    ):
        """
        Args:
            use_structured_content: Whether to use `tool_result.structured_content` when calling an
                MCP tool. Defaults to False for backwards compatibility - most MCP servers still
                include the structured content in the `tool_result.content`, and using it by
                default will cause duplicate content. You can set this to True if you know the
                server will not duplicate the structured content in the `tool_result.content`.
            require_approval: Approval policy for tools on this server. Accepts "always"/"never",
                a dict of tool names to those values, a boolean, an object with always/never
                tool lists (mirroring TS requireApproval), or a sync/async callable that receives
                `(run_context, agent, tool)` and returns whether the tool call needs approval.
                Normalized into a needs_approval policy.
            failure_error_function: Optional function used to convert MCP tool failures into
                a model-visible error message. If explicitly set to None, tool errors will be
                raised instead of converted. If left unset, the agent-level configuration (or
                SDK default) will be used.
            tool_meta_resolver: Optional callable that produces MCP request metadata (`_meta`) for
                tool calls. It is invoked by the Agents SDK before calling `call_tool`.
            custom_data_extractor: Optional callable that produces SDK-only custom data for
                emitted MCP tool output items.
        """
        self.use_structured_content = use_structured_content
        self._needs_approval_policy = self._normalize_needs_approval(
            require_approval=require_approval
        )
        self._failure_error_function = failure_error_function
        self.tool_meta_resolver = tool_meta_resolver
        self.custom_data_extractor = custom_data_extractor

    @abc.abstractmethod
    async def connect(self):
        """Connect to the server. For example, this might mean spawning a subprocess or
        opening a network connection. The server is expected to remain connected until
        `cleanup()` is called.
        """
        pass

    @property
    @abc.abstractmethod
    def name(self) -> str:
        """A readable name for the server."""
        pass

    @property
    def _error_name(self) -> str:
        """Return a diagnostic server name with URL credentials removed."""
        return get_mcp_server_log_name(self.name)

    @abc.abstractmethod
    async def cleanup(self):
        """Cleanup the server. For example, this might mean closing a subprocess or
        closing a network connection.
        """
        pass

    @abc.abstractmethod
    async def list_tools(
        self,
        run_context: RunContextWrapper[Any] | None = None,
        agent: AgentBase | None = None,
    ) -> list[MCPTool]:
        """List the tools available on the server."""
        pass

    @abc.abstractmethod
    async def call_tool(
        self,
        tool_name: str,
        arguments: dict[str, Any] | None,
        meta: dict[str, Any] | None = None,
    ) -> CallToolResult:
        """Invoke a tool on the server."""
        pass

    @property
    def cached_tools(self) -> list[MCPTool] | None:
        """Return the most recently fetched tools list, if available.

        Implementations may return `None` when tools have not been fetched yet or caching is
        disabled.
        """

        return None

    @abc.abstractmethod
    async def list_prompts(
        self,
    ) -> ListPromptsResult:
        """List the prompts available on the server."""
        pass

    @abc.abstractmethod
    async def get_prompt(
        self, name: str, arguments: dict[str, Any] | None = None
    ) -> GetPromptResult:
        """Get a specific prompt from the server."""
        pass

    async def list_resources(self, cursor: str | None = None) -> ListResourcesResult:
        """List the resources available on the server.

        Args:
            cursor: An opaque pagination cursor returned in a previous
                :class:`~mcp.types.ListResourcesResult` as ``next_cursor`` under
                MCP v2 or ``nextCursor`` under MCP v1.  Pass it here to fetch the
                next page of results.  ``None`` fetches the first page.

        Returns a :class:`~mcp.types.ListResourcesResult`.  When the result contains
        a ``next_cursor`` field under MCP v2 or ``nextCursor`` under MCP v1, call
        this method again with that cursor to retrieve the next page.  Subclasses
        that do not support resources may leave this unimplemented; it will raise
        :exc:`NotImplementedError` at call time.
        """
        raise NotImplementedError(
            f"MCP server '{self._error_name}' does not support list_resources. "
            "Override this method in your server implementation."
        )

    async def list_resource_templates(
        self, cursor: str | None = None
    ) -> ListResourceTemplatesResult:
        """List the resource templates available on the server.

        Args:
            cursor: An opaque pagination cursor returned in a previous
                :class:`~mcp.types.ListResourceTemplatesResult` as ``next_cursor``
                under MCP v2 or ``nextCursor`` under MCP v1.  Pass it here to fetch
                the next page of results.  ``None`` fetches the first page.

        Returns a :class:`~mcp.types.ListResourceTemplatesResult`.  When the result
        contains a ``next_cursor`` field under MCP v2 or ``nextCursor`` under MCP
        v1, call this method again with that cursor to retrieve the next page.
        Subclasses that do not support resource templates may leave this
        unimplemented; it will raise :exc:`NotImplementedError` at call time.
        """
        raise NotImplementedError(
            f"MCP server '{self._error_name}' does not support list_resource_templates. "
            "Override this method in your server implementation."
        )

    async def read_resource(self, uri: str) -> ReadResourceResult:
        """Read the contents of a specific resource by URI.

        Args:
            uri: The URI of the resource to read. See :class:`~pydantic.networks.AnyUrl`
                for the supported URI formats.

        Returns a :class:`~mcp.types.ReadResourceResult`.  Subclasses that do not
        support resources may leave this unimplemented; it will raise
        :exc:`NotImplementedError` at call time.
        """
        raise NotImplementedError(
            f"MCP server '{self._error_name}' does not support read_resource. "
            "Override this method in your server implementation."
        )

    @staticmethod
    def _normalize_needs_approval(
        *,
        require_approval: RequireApprovalSetting,
    ) -> (
        bool
        | dict[str, bool]
        | Callable[[RunContextWrapper[Any], AgentBase, MCPTool], MaybeAwaitable[bool]]
    ):
        """Normalize approval inputs to booleans or a name->bool map."""

        if require_approval is None:
            return False

        def _to_bool(value: object, *, location: str) -> bool:
            if value == "always":
                return True
            if value == "never":
                return False
            raise UserError(
                f"Invalid require_approval value at {location}: "
                f"expected 'always' or 'never', got {value!r}."
            )

        def _validate_tool_names(value: object, *, location: str) -> list[str]:
            if not isinstance(value, list):
                raise UserError(
                    f"Invalid require_approval tool_names at {location}: "
                    f"expected a list of strings, got {type(value).__name__}."
                )

            tool_names: list[str] = []
            for index, tool_name in enumerate(value):
                if not isinstance(tool_name, str):
                    raise UserError(
                        f"Invalid require_approval tool name at {location}[{index}]: "
                        f"expected a string, got {type(tool_name).__name__}."
                    )
                tool_names.append(tool_name)
            return tool_names

        def _get_tool_names_entry(value: object, *, policy: str) -> list[str]:
            if not isinstance(value, dict):
                raise UserError(
                    f"Invalid require_approval.{policy}: "
                    f"expected an object with tool_names, got {type(value).__name__}."
                )
            return _validate_tool_names(
                value.get("tool_names", []),
                location=f"require_approval.{policy}.tool_names",
            )

        def _is_tool_list_schema(value: object) -> bool:
            if not isinstance(value, dict):
                return False
            for key in ("always", "never"):
                if key not in value:
                    continue
                entry = value.get(key)
                if isinstance(entry, dict) and "tool_names" in entry:
                    return True
            return False

        if isinstance(require_approval, dict) and _is_tool_list_schema(require_approval):
            always_entry: RequireApprovalToolList | Any = require_approval.get("always", {})
            never_entry: RequireApprovalToolList | Any = require_approval.get("never", {})
            invalid_keys = sorted(set(require_approval) - {"always", "never"})
            if invalid_keys:
                raise UserError(
                    "Invalid require_approval tool list policy: "
                    f"unexpected keys {invalid_keys!r}; expected only 'always' and 'never'."
                )
            always_names = _get_tool_names_entry(always_entry, policy="always")
            never_names = _get_tool_names_entry(never_entry, policy="never")
            overlapping_names = sorted(set(always_names) & set(never_names))
            if overlapping_names:
                raise UserError(
                    "Invalid require_approval tool list policy: "
                    f"tool names cannot appear in both always and never: {overlapping_names!r}."
                )
            tool_list_mapping: dict[str, bool] = {}
            for name in always_names:
                tool_list_mapping[name] = True
            for name in never_names:
                tool_list_mapping[name] = False
            return tool_list_mapping

        if isinstance(require_approval, dict):
            tool_mapping: dict[str, bool] = {}
            for name, value in require_approval.items():
                if isinstance(value, bool):
                    tool_mapping[str(name)] = value
                else:
                    tool_mapping[str(name)] = _to_bool(
                        value, location=f"require_approval[{name!r}]"
                    )
            return tool_mapping

        if callable(require_approval):
            return require_approval

        if isinstance(require_approval, bool):
            return require_approval

        return _to_bool(require_approval, location="require_approval")

    def _get_needs_approval_for_tool(
        self,
        tool: MCPTool,
        agent: AgentBase | None,
    ) -> bool | Callable[[RunContextWrapper[Any], dict[str, Any], str], Awaitable[bool]]:
        """Return a FunctionTool.needs_approval value for a given MCP tool.

        Legacy callers may omit ``agent`` when using ``MCPUtil.to_function_tool()`` directly.
        When approval is configured with a callable policy and no agent is available, this method
        returns ``True`` to preserve the historical fail-closed behavior.
        """

        policy = self._needs_approval_policy

        if callable(policy):
            if agent is None:
                return True

            async def _needs_approval(
                run_context: RunContextWrapper[Any], _args: dict[str, Any], _call_id: str
            ) -> bool:
                result = policy(run_context, agent, tool)
                if inspect.isawaitable(result):
                    result = await result
                return bool(result)

            return _needs_approval

        if isinstance(policy, dict):
            return bool(policy.get(tool.name, False))

        return bool(policy)

    def _get_failure_error_function(
        self, agent_failure_error_function: ToolErrorFunction | None
    ) -> ToolErrorFunction | None:
        """Return the effective error handler for MCP tool failures."""
        if self._failure_error_function is _UNSET:
            return agent_failure_error_function
        return cast(ToolErrorFunction | None, self._failure_error_function)

name abstractmethod property

name: str

A readable name for the server.

cached_tools property

cached_tools: list[Tool] | None

Return the most recently fetched tools list, if available.

Implementations may return None when tools have not been fetched yet or caching is disabled.

__init__

__init__(
    use_structured_content: bool = False,
    require_approval: RequireApprovalSetting = None,
    failure_error_function: ToolErrorFunction
    | None
    | _UnsetType = _UNSET,
    tool_meta_resolver: MCPToolMetaResolver | None = None,
    custom_data_extractor: MCPToolCustomDataExtractor
    | None = None,
)

Parameters:

Name Type Description Default
use_structured_content bool

Whether to use tool_result.structured_content when calling an MCP tool. Defaults to False for backwards compatibility - most MCP servers still include the structured content in the tool_result.content, and using it by default will cause duplicate content. You can set this to True if you know the server will not duplicate the structured content in the tool_result.content.

False
require_approval RequireApprovalSetting

Approval policy for tools on this server. Accepts "always"/"never", a dict of tool names to those values, a boolean, an object with always/never tool lists (mirroring TS requireApproval), or a sync/async callable that receives (run_context, agent, tool) and returns whether the tool call needs approval. Normalized into a needs_approval policy.

None
failure_error_function ToolErrorFunction | None | _UnsetType

Optional function used to convert MCP tool failures into a model-visible error message. If explicitly set to None, tool errors will be raised instead of converted. If left unset, the agent-level configuration (or SDK default) will be used.

_UNSET
tool_meta_resolver MCPToolMetaResolver | None

Optional callable that produces MCP request metadata (_meta) for tool calls. It is invoked by the Agents SDK before calling call_tool.

None
custom_data_extractor MCPToolCustomDataExtractor | None

Optional callable that produces SDK-only custom data for emitted MCP tool output items.

None
Source code in src/agents/mcp/server.py
def __init__(
    self,
    use_structured_content: bool = False,
    require_approval: RequireApprovalSetting = None,
    failure_error_function: ToolErrorFunction | None | _UnsetType = _UNSET,
    tool_meta_resolver: MCPToolMetaResolver | None = None,
    custom_data_extractor: MCPToolCustomDataExtractor | None = None,
):
    """
    Args:
        use_structured_content: Whether to use `tool_result.structured_content` when calling an
            MCP tool. Defaults to False for backwards compatibility - most MCP servers still
            include the structured content in the `tool_result.content`, and using it by
            default will cause duplicate content. You can set this to True if you know the
            server will not duplicate the structured content in the `tool_result.content`.
        require_approval: Approval policy for tools on this server. Accepts "always"/"never",
            a dict of tool names to those values, a boolean, an object with always/never
            tool lists (mirroring TS requireApproval), or a sync/async callable that receives
            `(run_context, agent, tool)` and returns whether the tool call needs approval.
            Normalized into a needs_approval policy.
        failure_error_function: Optional function used to convert MCP tool failures into
            a model-visible error message. If explicitly set to None, tool errors will be
            raised instead of converted. If left unset, the agent-level configuration (or
            SDK default) will be used.
        tool_meta_resolver: Optional callable that produces MCP request metadata (`_meta`) for
            tool calls. It is invoked by the Agents SDK before calling `call_tool`.
        custom_data_extractor: Optional callable that produces SDK-only custom data for
            emitted MCP tool output items.
    """
    self.use_structured_content = use_structured_content
    self._needs_approval_policy = self._normalize_needs_approval(
        require_approval=require_approval
    )
    self._failure_error_function = failure_error_function
    self.tool_meta_resolver = tool_meta_resolver
    self.custom_data_extractor = custom_data_extractor

connect abstractmethod async

connect()

Connect to the server. For example, this might mean spawning a subprocess or opening a network connection. The server is expected to remain connected until cleanup() is called.

Source code in src/agents/mcp/server.py
@abc.abstractmethod
async def connect(self):
    """Connect to the server. For example, this might mean spawning a subprocess or
    opening a network connection. The server is expected to remain connected until
    `cleanup()` is called.
    """
    pass

cleanup abstractmethod async

cleanup()

Cleanup the server. For example, this might mean closing a subprocess or closing a network connection.

Source code in src/agents/mcp/server.py
@abc.abstractmethod
async def cleanup(self):
    """Cleanup the server. For example, this might mean closing a subprocess or
    closing a network connection.
    """
    pass

list_tools abstractmethod async

list_tools(
    run_context: RunContextWrapper[Any] | None = None,
    agent: AgentBase | None = None,
) -> list[Tool]

List the tools available on the server.

Source code in src/agents/mcp/server.py
@abc.abstractmethod
async def list_tools(
    self,
    run_context: RunContextWrapper[Any] | None = None,
    agent: AgentBase | None = None,
) -> list[MCPTool]:
    """List the tools available on the server."""
    pass

call_tool abstractmethod async

call_tool(
    tool_name: str,
    arguments: dict[str, Any] | None,
    meta: dict[str, Any] | None = None,
) -> CallToolResult

Invoke a tool on the server.

Source code in src/agents/mcp/server.py
@abc.abstractmethod
async def call_tool(
    self,
    tool_name: str,
    arguments: dict[str, Any] | None,
    meta: dict[str, Any] | None = None,
) -> CallToolResult:
    """Invoke a tool on the server."""
    pass

list_prompts abstractmethod async

list_prompts() -> ListPromptsResult

List the prompts available on the server.

Source code in src/agents/mcp/server.py
@abc.abstractmethod
async def list_prompts(
    self,
) -> ListPromptsResult:
    """List the prompts available on the server."""
    pass

get_prompt abstractmethod async

get_prompt(
    name: str, arguments: dict[str, Any] | None = None
) -> GetPromptResult

Get a specific prompt from the server.

Source code in src/agents/mcp/server.py
@abc.abstractmethod
async def get_prompt(
    self, name: str, arguments: dict[str, Any] | None = None
) -> GetPromptResult:
    """Get a specific prompt from the server."""
    pass

list_resources async

list_resources(
    cursor: str | None = None,
) -> ListResourcesResult

List the resources available on the server.

Parameters:

Name Type Description Default
cursor str | None

An opaque pagination cursor returned in a previous :class:~mcp.types.ListResourcesResult as next_cursor under MCP v2 or nextCursor under MCP v1. Pass it here to fetch the next page of results. None fetches the first page.

None

Returns a :class:~mcp.types.ListResourcesResult. When the result contains a next_cursor field under MCP v2 or nextCursor under MCP v1, call this method again with that cursor to retrieve the next page. Subclasses that do not support resources may leave this unimplemented; it will raise :exc:NotImplementedError at call time.

Source code in src/agents/mcp/server.py
async def list_resources(self, cursor: str | None = None) -> ListResourcesResult:
    """List the resources available on the server.

    Args:
        cursor: An opaque pagination cursor returned in a previous
            :class:`~mcp.types.ListResourcesResult` as ``next_cursor`` under
            MCP v2 or ``nextCursor`` under MCP v1.  Pass it here to fetch the
            next page of results.  ``None`` fetches the first page.

    Returns a :class:`~mcp.types.ListResourcesResult`.  When the result contains
    a ``next_cursor`` field under MCP v2 or ``nextCursor`` under MCP v1, call
    this method again with that cursor to retrieve the next page.  Subclasses
    that do not support resources may leave this unimplemented; it will raise
    :exc:`NotImplementedError` at call time.
    """
    raise NotImplementedError(
        f"MCP server '{self._error_name}' does not support list_resources. "
        "Override this method in your server implementation."
    )

list_resource_templates async

list_resource_templates(
    cursor: str | None = None,
) -> ListResourceTemplatesResult

List the resource templates available on the server.

Parameters:

Name Type Description Default
cursor str | None

An opaque pagination cursor returned in a previous :class:~mcp.types.ListResourceTemplatesResult as next_cursor under MCP v2 or nextCursor under MCP v1. Pass it here to fetch the next page of results. None fetches the first page.

None

Returns a :class:~mcp.types.ListResourceTemplatesResult. When the result contains a next_cursor field under MCP v2 or nextCursor under MCP v1, call this method again with that cursor to retrieve the next page. Subclasses that do not support resource templates may leave this unimplemented; it will raise :exc:NotImplementedError at call time.

Source code in src/agents/mcp/server.py
async def list_resource_templates(
    self, cursor: str | None = None
) -> ListResourceTemplatesResult:
    """List the resource templates available on the server.

    Args:
        cursor: An opaque pagination cursor returned in a previous
            :class:`~mcp.types.ListResourceTemplatesResult` as ``next_cursor``
            under MCP v2 or ``nextCursor`` under MCP v1.  Pass it here to fetch
            the next page of results.  ``None`` fetches the first page.

    Returns a :class:`~mcp.types.ListResourceTemplatesResult`.  When the result
    contains a ``next_cursor`` field under MCP v2 or ``nextCursor`` under MCP
    v1, call this method again with that cursor to retrieve the next page.
    Subclasses that do not support resource templates may leave this
    unimplemented; it will raise :exc:`NotImplementedError` at call time.
    """
    raise NotImplementedError(
        f"MCP server '{self._error_name}' does not support list_resource_templates. "
        "Override this method in your server implementation."
    )

read_resource async

read_resource(uri: str) -> ReadResourceResult

Read the contents of a specific resource by URI.

Parameters:

Name Type Description Default
uri str

The URI of the resource to read. See :class:~pydantic.networks.AnyUrl for the supported URI formats.

required

Returns a :class:~mcp.types.ReadResourceResult. Subclasses that do not support resources may leave this unimplemented; it will raise :exc:NotImplementedError at call time.

Source code in src/agents/mcp/server.py
async def read_resource(self, uri: str) -> ReadResourceResult:
    """Read the contents of a specific resource by URI.

    Args:
        uri: The URI of the resource to read. See :class:`~pydantic.networks.AnyUrl`
            for the supported URI formats.

    Returns a :class:`~mcp.types.ReadResourceResult`.  Subclasses that do not
    support resources may leave this unimplemented; it will raise
    :exc:`NotImplementedError` at call time.
    """
    raise NotImplementedError(
        f"MCP server '{self._error_name}' does not support read_resource. "
        "Override this method in your server implementation."
    )

MCPServerStdioParams

Bases: TypedDict

Mirrors mcp.client.stdio.StdioServerParameters, but lets you pass params without another import.

Source code in src/agents/mcp/server.py
class MCPServerStdioParams(TypedDict):
    """Mirrors `mcp.client.stdio.StdioServerParameters`, but lets you pass params without another
    import.
    """

    command: str
    """The executable to run to start the server. For example, `python` or `node`."""

    args: NotRequired[list[str]]
    """Command line args to pass to the `command` executable. For example, `['foo.py']` or
    `['server.js', '--port', '8080']`."""

    env: NotRequired[dict[str, str]]
    """The environment variables to set for the server."""

    cwd: NotRequired[str | Path]
    """The working directory to use when spawning the process."""

    encoding: NotRequired[str]
    """The text encoding used when sending/receiving messages to the server. Defaults to `utf-8`."""

    encoding_error_handler: NotRequired[Literal["strict", "ignore", "replace"]]
    """The text encoding error handler. Defaults to `strict`.

    See https://docs.python.org/3/library/codecs.html#codec-base-classes for
    explanations of possible values.
    """

command instance-attribute

command: str

The executable to run to start the server. For example, python or node.

args instance-attribute

args: NotRequired[list[str]]

Command line args to pass to the command executable. For example, ['foo.py'] or ['server.js', '--port', '8080'].

env instance-attribute

env: NotRequired[dict[str, str]]

The environment variables to set for the server.

cwd instance-attribute

cwd: NotRequired[str | Path]

The working directory to use when spawning the process.

encoding instance-attribute

encoding: NotRequired[str]

The text encoding used when sending/receiving messages to the server. Defaults to utf-8.

encoding_error_handler instance-attribute

encoding_error_handler: NotRequired[
    Literal["strict", "ignore", "replace"]
]

The text encoding error handler. Defaults to strict.

See https://docs.python.org/3/library/codecs.html#codec-base-classes for explanations of possible values.

MCPServerStdio

Bases: _MCPServerWithClientSession

MCP server implementation that uses the stdio transport. See the [spec] (https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#stdio) for details.

Source code in src/agents/mcp/server.py
class MCPServerStdio(_MCPServerWithClientSession):
    """MCP server implementation that uses the stdio transport. See the [spec]
    (https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#stdio) for
    details.
    """

    def __init__(
        self,
        params: MCPServerStdioParams,
        cache_tools_list: bool = False,
        name: str | None = None,
        client_session_timeout_seconds: float | None = 5,
        tool_filter: ToolFilter = None,
        use_structured_content: bool = False,
        max_retry_attempts: int = 0,
        retry_backoff_seconds_base: float = 1.0,
        message_handler: MessageHandlerFnT | None = None,
        require_approval: RequireApprovalSetting = None,
        failure_error_function: ToolErrorFunction | None | _UnsetType = _UNSET,
        tool_meta_resolver: MCPToolMetaResolver | None = None,
        custom_data_extractor: MCPToolCustomDataExtractor | None = None,
    ):
        """Create a new MCP server based on the stdio transport.

        Args:
            params: The params that configure the server. This includes the command to run to
                start the server, the args to pass to the command, the environment variables to
                set for the server, the working directory to use when spawning the process, and
                the text encoding used when sending/receiving messages to the server.
            cache_tools_list: Whether to cache the tools list. If `True`, the tools list will be
                cached and only fetched from the server once. If `False`, the tools list will be
                fetched from the server on each call to `list_tools()`. The cache can be
                invalidated by calling `invalidate_tools_cache()`. You should set this to `True`
                if you know the server will not change its tools list, because it can drastically
                improve latency (by avoiding a round-trip to the server every time).
            name: A readable name for the server. If not provided, we'll create one from the
                command.
            client_session_timeout_seconds: The MCP ClientSession read timeout. Positive finite
                values representable by `datetime.timedelta` and at least one microsecond set a
                timeout; `None` and `0` disable it. Other values are rejected during server
                construction.
            tool_filter: The tool filter to use for filtering tools.
            use_structured_content: Whether to use `tool_result.structured_content` when calling an
                MCP tool. Defaults to False for backwards compatibility - most MCP servers still
                include the structured content in the `tool_result.content`, and using it by
                default will cause duplicate content. You can set this to True if you know the
                server will not duplicate the structured content in the `tool_result.content`.
            max_retry_attempts: Number of times to retry failed list_tools/call_tool calls.
                Defaults to no retries.
            retry_backoff_seconds_base: The base delay, in seconds, for exponential
                backoff between retries.
            message_handler: Optional handler invoked for session messages as delivered by the
                ClientSession.
            require_approval: Approval policy for tools on this server. Accepts "always"/"never",
                a dict of tool names to those values, or an object with always/never tool lists.
            failure_error_function: Optional function used to convert MCP tool failures into
                a model-visible error message. If explicitly set to None, tool errors will be
                raised instead of converted. If left unset, the agent-level configuration (or
                SDK default) will be used.
            tool_meta_resolver: Optional callable that produces MCP request metadata (`_meta`) for
                tool calls. It is invoked by the Agents SDK before calling `call_tool`.
            custom_data_extractor: Optional callable that produces SDK-only custom data for
                emitted MCP tool output items.
        """
        super().__init__(
            cache_tools_list=cache_tools_list,
            client_session_timeout_seconds=client_session_timeout_seconds,
            tool_filter=tool_filter,
            use_structured_content=use_structured_content,
            max_retry_attempts=max_retry_attempts,
            retry_backoff_seconds_base=retry_backoff_seconds_base,
            message_handler=message_handler,
            require_approval=require_approval,
            failure_error_function=failure_error_function,
            tool_meta_resolver=tool_meta_resolver,
            custom_data_extractor=custom_data_extractor,
        )

        self.params = StdioServerParameters(
            command=params["command"],
            args=params.get("args", []),
            env=params.get("env"),
            cwd=params.get("cwd"),
            encoding=params.get("encoding", "utf-8"),
            encoding_error_handler=params.get("encoding_error_handler", "strict"),
        )

        self._name = name or f"stdio: {self.params.command}"

    def create_streams(
        self,
    ) -> AbstractAsyncContextManager[MCPStreamTransport]:
        """Create the streams for the server."""
        return stdio_client(self.params)

    @property
    def name(self) -> str:
        """A readable name for the server."""
        return self._name

name property

name: str

A readable name for the server.

__init__

__init__(
    params: MCPServerStdioParams,
    cache_tools_list: bool = False,
    name: str | None = None,
    client_session_timeout_seconds: float | None = 5,
    tool_filter: ToolFilter = None,
    use_structured_content: bool = False,
    max_retry_attempts: int = 0,
    retry_backoff_seconds_base: float = 1.0,
    message_handler: MessageHandlerFnT | None = None,
    require_approval: RequireApprovalSetting = None,
    failure_error_function: ToolErrorFunction
    | None
    | _UnsetType = _UNSET,
    tool_meta_resolver: MCPToolMetaResolver | None = None,
    custom_data_extractor: MCPToolCustomDataExtractor
    | None = None,
)

Create a new MCP server based on the stdio transport.

Parameters:

Name Type Description Default
params MCPServerStdioParams

The params that configure the server. This includes the command to run to start the server, the args to pass to the command, the environment variables to set for the server, the working directory to use when spawning the process, and the text encoding used when sending/receiving messages to the server.

required
cache_tools_list bool

Whether to cache the tools list. If True, the tools list will be cached and only fetched from the server once. If False, the tools list will be fetched from the server on each call to list_tools(). The cache can be invalidated by calling invalidate_tools_cache(). You should set this to True if you know the server will not change its tools list, because it can drastically improve latency (by avoiding a round-trip to the server every time).

False
name str | None

A readable name for the server. If not provided, we'll create one from the command.

None
client_session_timeout_seconds float | None

The MCP ClientSession read timeout. Positive finite values representable by datetime.timedelta and at least one microsecond set a timeout; None and 0 disable it. Other values are rejected during server construction.

5
tool_filter ToolFilter

The tool filter to use for filtering tools.

None
use_structured_content bool

Whether to use tool_result.structured_content when calling an MCP tool. Defaults to False for backwards compatibility - most MCP servers still include the structured content in the tool_result.content, and using it by default will cause duplicate content. You can set this to True if you know the server will not duplicate the structured content in the tool_result.content.

False
max_retry_attempts int

Number of times to retry failed list_tools/call_tool calls. Defaults to no retries.

0
retry_backoff_seconds_base float

The base delay, in seconds, for exponential backoff between retries.

1.0
message_handler MessageHandlerFnT | None

Optional handler invoked for session messages as delivered by the ClientSession.

None
require_approval RequireApprovalSetting

Approval policy for tools on this server. Accepts "always"/"never", a dict of tool names to those values, or an object with always/never tool lists.

None
failure_error_function ToolErrorFunction | None | _UnsetType

Optional function used to convert MCP tool failures into a model-visible error message. If explicitly set to None, tool errors will be raised instead of converted. If left unset, the agent-level configuration (or SDK default) will be used.

_UNSET
tool_meta_resolver MCPToolMetaResolver | None

Optional callable that produces MCP request metadata (_meta) for tool calls. It is invoked by the Agents SDK before calling call_tool.

None
custom_data_extractor MCPToolCustomDataExtractor | None

Optional callable that produces SDK-only custom data for emitted MCP tool output items.

None
Source code in src/agents/mcp/server.py
def __init__(
    self,
    params: MCPServerStdioParams,
    cache_tools_list: bool = False,
    name: str | None = None,
    client_session_timeout_seconds: float | None = 5,
    tool_filter: ToolFilter = None,
    use_structured_content: bool = False,
    max_retry_attempts: int = 0,
    retry_backoff_seconds_base: float = 1.0,
    message_handler: MessageHandlerFnT | None = None,
    require_approval: RequireApprovalSetting = None,
    failure_error_function: ToolErrorFunction | None | _UnsetType = _UNSET,
    tool_meta_resolver: MCPToolMetaResolver | None = None,
    custom_data_extractor: MCPToolCustomDataExtractor | None = None,
):
    """Create a new MCP server based on the stdio transport.

    Args:
        params: The params that configure the server. This includes the command to run to
            start the server, the args to pass to the command, the environment variables to
            set for the server, the working directory to use when spawning the process, and
            the text encoding used when sending/receiving messages to the server.
        cache_tools_list: Whether to cache the tools list. If `True`, the tools list will be
            cached and only fetched from the server once. If `False`, the tools list will be
            fetched from the server on each call to `list_tools()`. The cache can be
            invalidated by calling `invalidate_tools_cache()`. You should set this to `True`
            if you know the server will not change its tools list, because it can drastically
            improve latency (by avoiding a round-trip to the server every time).
        name: A readable name for the server. If not provided, we'll create one from the
            command.
        client_session_timeout_seconds: The MCP ClientSession read timeout. Positive finite
            values representable by `datetime.timedelta` and at least one microsecond set a
            timeout; `None` and `0` disable it. Other values are rejected during server
            construction.
        tool_filter: The tool filter to use for filtering tools.
        use_structured_content: Whether to use `tool_result.structured_content` when calling an
            MCP tool. Defaults to False for backwards compatibility - most MCP servers still
            include the structured content in the `tool_result.content`, and using it by
            default will cause duplicate content. You can set this to True if you know the
            server will not duplicate the structured content in the `tool_result.content`.
        max_retry_attempts: Number of times to retry failed list_tools/call_tool calls.
            Defaults to no retries.
        retry_backoff_seconds_base: The base delay, in seconds, for exponential
            backoff between retries.
        message_handler: Optional handler invoked for session messages as delivered by the
            ClientSession.
        require_approval: Approval policy for tools on this server. Accepts "always"/"never",
            a dict of tool names to those values, or an object with always/never tool lists.
        failure_error_function: Optional function used to convert MCP tool failures into
            a model-visible error message. If explicitly set to None, tool errors will be
            raised instead of converted. If left unset, the agent-level configuration (or
            SDK default) will be used.
        tool_meta_resolver: Optional callable that produces MCP request metadata (`_meta`) for
            tool calls. It is invoked by the Agents SDK before calling `call_tool`.
        custom_data_extractor: Optional callable that produces SDK-only custom data for
            emitted MCP tool output items.
    """
    super().__init__(
        cache_tools_list=cache_tools_list,
        client_session_timeout_seconds=client_session_timeout_seconds,
        tool_filter=tool_filter,
        use_structured_content=use_structured_content,
        max_retry_attempts=max_retry_attempts,
        retry_backoff_seconds_base=retry_backoff_seconds_base,
        message_handler=message_handler,
        require_approval=require_approval,
        failure_error_function=failure_error_function,
        tool_meta_resolver=tool_meta_resolver,
        custom_data_extractor=custom_data_extractor,
    )

    self.params = StdioServerParameters(
        command=params["command"],
        args=params.get("args", []),
        env=params.get("env"),
        cwd=params.get("cwd"),
        encoding=params.get("encoding", "utf-8"),
        encoding_error_handler=params.get("encoding_error_handler", "strict"),
    )

    self._name = name or f"stdio: {self.params.command}"

create_streams

create_streams() -> AbstractAsyncContextManager[
    MCPStreamTransport
]

Create the streams for the server.

Source code in src/agents/mcp/server.py
def create_streams(
    self,
) -> AbstractAsyncContextManager[MCPStreamTransport]:
    """Create the streams for the server."""
    return stdio_client(self.params)

connect async

connect()

Connect to the server.

Source code in src/agents/mcp/server.py
async def connect(self):
    """Connect to the server."""
    read_timeout = _client_session_read_timeout(self.client_session_timeout_seconds)
    connection_succeeded = False
    connection_error: UserError | None = None
    connection_cause: Exception | None = None
    connection_exception: BaseException | None = None
    cleanup_failure: BaseException | None = None
    try:
        if MCP_V2:
            session = await self.exit_stack.enter_async_context(
                self._client_session_context(read_timeout)
            )
            self.server_initialize_result = getattr(session, "initialize_result", None)
        else:
            v1_read_timeout = cast(timedelta | None, read_timeout)
            transport = await self.exit_stack.enter_async_context(self.create_streams())
            read, write, *rest = transport
            self._get_session_id = rest[0] if rest and callable(rest[0]) else None
            session = await self.exit_stack.enter_async_context(
                cast(Any, ClientSession)(
                    read,
                    write,
                    v1_read_timeout,
                    message_handler=self.message_handler,
                )
            )
            self.server_initialize_result = await session.initialize()
        self.session = session
        connection_succeeded = True
    except BaseException as e:
        if not isinstance(e, Exception):
            connection_exception = e
        else:
            http_errors = self._extract_http_errors_from_exception(e)
            if not http_errors:
                connection_exception = e
            else:
                unsafe_http_error = _first_unretainable_transport_error(http_errors)
                http_error = unsafe_http_error or http_errors[0]
                connection_cause = _safe_transport_cause(http_error)
                maps_safe_error = (
                    is_http_status_error(http_error)
                    or is_http_connect_error(http_error)
                    or is_http_timeout_error(http_error)
                )
                if connection_cause is not None and not maps_safe_error:
                    connection_exception = e
                    connection_cause = None
                else:
                    connection_error = self._user_error_for_http_error(http_error)
                http_errors.clear()
                del http_error
                del unsafe_http_error

    # Run cleanup after leaving the connection exception handler so a cleanup UserError does
    # not retain the pending connection failure as its implicit context.
    if not connection_succeeded:
        try:
            await self.cleanup()
        except UserError as e:
            cleanup_failure = e
        except Exception as cleanup_error:
            # Suppress RuntimeError about cancel scopes during cleanup - this is a known
            # issue with the MCP library's async generator cleanup and shouldn't mask the
            # original error.
            if isinstance(cleanup_error, RuntimeError) and "cancel scope" in str(cleanup_error):
                logger.debug(
                    "%s",
                    get_mcp_server_log_message(
                        "Ignoring cancel scope error during cleanup of MCP server", self
                    ),
                    stacklevel=2,
                )
            else:
                # Log other cleanup errors but don't raise - original error is more important.
                logger.warning(
                    "%s",
                    get_mcp_server_log_message("Error during cleanup of MCP server", self),
                    stacklevel=2,
                )
        except BaseException as e:
            cleanup_failure = e

    if cleanup_failure is not None:
        connection_exception = None
        connection_error = None
        connection_cause = None
        if isinstance(cleanup_failure, UserError):
            self._raise_mapped_transport_error(cleanup_failure, None)
        raise cleanup_failure

    if connection_exception is not None:
        raise connection_exception

    if connection_error is not None:
        self._raise_mapped_transport_error(connection_error, connection_cause)

cleanup async

cleanup()

Cleanup the server.

Source code in src/agents/mcp/server.py
async def cleanup(self):
    """Cleanup the server."""
    async with self._cleanup_lock:
        # Only raise HTTP errors if we're cleaning up after a failed connection.
        # During normal teardown (via __aexit__), log but don't raise to avoid
        # masking the original exception.
        is_failed_connection_cleanup = self.session is None
        cleanup_error: UserError | None = None

        try:
            await self.exit_stack.aclose()
        except asyncio.CancelledError as e:
            log_tool_action_debug(
                logger,
                get_mcp_server_log_message("Cleanup cancelled for MCP server", self),
                e,
            )
            raise
        except (  # type: ignore[misc]
            BaseExceptionGroup,
            *HTTP_STATUS_ERROR_TYPES,
            *HTTP_REQUEST_ERROR_TYPES,
        ) as e:
            selected_http_error = self._select_cleanup_transport_error(e)
            if selected_http_error is not None:
                if is_failed_connection_cleanup:
                    cleanup_error = self._user_error_for_http_error(
                        selected_http_error,
                        include_http_reason_phrase=False,
                    )
                    del selected_http_error
                else:
                    _log_cleanup_transport_warning(
                        get_mcp_server_log_message(
                            _get_cleanup_transport_error_message(selected_http_error), self
                        )
                    )
            elif is_http_request_error(e):
                _log_cleanup_transport_warning(
                    get_mcp_server_log_message(_get_cleanup_transport_error_message(e), self)
                )
            elif isinstance(e, BaseExceptionGroup):
                http_errors = self._extract_http_errors_from_exception(e)
                if http_errors:
                    safe_error_group = _credential_safe_exception_group(e)
                    log_tool_action_error(
                        logger,
                        get_mcp_server_log_message("Error cleaning up MCP server", self),
                        safe_error_group,
                    )
                else:
                    # No HTTP error found, suppress RuntimeError about cancel scopes.
                    has_cancel_scope_error = any(
                        isinstance(exc, RuntimeError) and "cancel scope" in str(exc)
                        for exc in e.exceptions
                    )
                    if has_cancel_scope_error:
                        log_tool_action_debug(
                            logger,
                            get_mcp_server_log_message(
                                "Ignoring cancel scope error during cleanup of MCP server", self
                            ),
                            e,
                        )
                    else:
                        log_tool_action_error(
                            logger,
                            get_mcp_server_log_message("Error cleaning up MCP server", self),
                            e,
                        )
            else:
                log_tool_action_error(
                    logger,
                    get_mcp_server_log_message("Error cleaning up MCP server", self),
                    e,
                )
        except Exception as e:
            # Suppress RuntimeError about cancel scopes - this is a known issue with the MCP
            # library when background tasks fail during async generator cleanup
            if isinstance(e, RuntimeError) and "cancel scope" in str(e):
                log_tool_action_debug(
                    logger,
                    get_mcp_server_log_message(
                        "Ignoring cancel scope error during cleanup of MCP server", self
                    ),
                    e,
                )
            else:
                log_tool_action_error(
                    logger,
                    get_mcp_server_log_message("Error cleaning up MCP server", self),
                    e,
                )
        finally:
            self.session = None
            self._get_session_id = None
            self._v2_session_id = None

        if cleanup_error is not None:
            self._raise_mapped_transport_error(cleanup_error, None)

list_tools async

list_tools(
    run_context: RunContextWrapper[Any] | None = None,
    agent: AgentBase | None = None,
) -> list[Tool]

List the tools available on the server.

Source code in src/agents/mcp/server.py
async def list_tools(
    self,
    run_context: RunContextWrapper[Any] | None = None,
    agent: AgentBase | None = None,
) -> list[MCPTool]:
    """List the tools available on the server."""
    if not self.session:
        raise UserError("Server not initialized. Make sure you call `connect()` first.")
    session = self.session
    assert session is not None

    transport_error: UserError | None = None
    transport_cause: Exception | None = None
    try:
        tools: list[MCPTool]
        # Return from cache if caching is enabled, we have tools, and the cache is not dirty
        if self.cache_tools_list and not self._cache_dirty and self._tools_list:
            tools = self._tools_list
        else:
            tools = []
            cursor: str | None = None
            seen_cursors: set[str | None] = set()

            async def fetch_pages() -> bool:
                nonlocal cursor
                while True:
                    result = await self._list_tools_page(session, cursor)
                    tools.extend(result.tools)
                    seen_cursors.add(cursor)
                    next_cursor = result_next_cursor(result)
                    if next_cursor is None:
                        return True
                    if next_cursor in seen_cursors:
                        return False
                    cursor = next_cursor

            pagination_complete = False
            pagination_failure: BaseException | None = None
            try:
                pagination_complete = await self._run_with_retries(fetch_pages)
            except BaseException as error:
                if cursor is None:
                    raise
                if isinstance(error, BaseExceptionGroup):
                    pagination_failure = _credential_safe_exception_group(error)
                elif isinstance(error, Exception):
                    pagination_failure = self._user_error_for_request_operation(
                        "list tools", error
                    )
                else:
                    pagination_failure = _credential_safe_exception_leaf(error)

            if pagination_failure is not None or not pagination_complete:
                cursor = None
                seen_cursors.clear()
                tools.clear()
                del fetch_pages
                if pagination_failure is not None:
                    raise pagination_failure from None
                raise UserError(
                    f"MCP server '{self._error_name}' returned a repeated cursor while "
                    "listing tools."
                ) from None

            cursor = None
            seen_cursors.clear()
            del fetch_pages
            self._tools_list = tools
            self._cache_dirty = False

        # Filter tools based on tool_filter
        filtered_tools = tools
        if self.tool_filter is not None:
            filtered_tools = await self._apply_tool_filter(filtered_tools, run_context, agent)
        return filtered_tools
    except HTTP_STATUS_ERROR_TYPES as e:
        status_code = http_status_code(e)
        transport_error = UserError(
            f"Failed to list tools from MCP server '{self._error_name}': "
            f"HTTP error {status_code}"
        )
        transport_cause = _safe_transport_cause(e)
    except HTTP_REQUEST_ERROR_TYPES as e:
        transport_cause = _safe_transport_cause(e)
        if transport_cause is not None and not is_http_connect_error(e):
            raise
        if is_http_connect_error(e):
            transport_error = UserError(
                f"Failed to list tools from MCP server '{self._error_name}': Connection lost. "
                f"The server may have disconnected."
            )
        elif is_http_timeout_error(e):
            transport_error = UserError(
                f"Failed to list tools from MCP server '{self._error_name}': "
                "Connection timeout."
            )
        else:
            transport_error = UserError(
                f"Failed to list tools from MCP server '{self._error_name}': Request failed."
            )

    assert transport_error is not None
    self._raise_mapped_transport_error(transport_error, transport_cause)

call_tool async

call_tool(
    tool_name: str,
    arguments: dict[str, Any] | None,
    meta: dict[str, Any] | None = None,
) -> CallToolResult

Invoke a tool on the server.

Source code in src/agents/mcp/server.py
async def call_tool(
    self,
    tool_name: str,
    arguments: dict[str, Any] | None,
    meta: dict[str, Any] | None = None,
) -> CallToolResult:
    """Invoke a tool on the server."""
    if not self.session:
        raise UserError("Server not initialized. Make sure you call `connect()` first.")
    session = self.session
    assert session is not None

    transport_error: UserError | None = None
    transport_cause: Exception | None = None
    try:
        self._validate_required_parameters(tool_name=tool_name, arguments=arguments)
        if meta is None:
            return await self._run_with_retries(
                lambda: self._maybe_serialize_request(
                    lambda: session.call_tool(tool_name, arguments)
                )
            )
        return await self._run_with_retries(
            lambda: self._maybe_serialize_request(
                lambda: cast(Any, session).call_tool(tool_name, arguments, meta=meta)
            )
        )
    except HTTP_STATUS_ERROR_TYPES as e:
        status_code = http_status_code(e)
        transport_error = UserError(
            f"Failed to call tool '{tool_name}' on MCP server '{self._error_name}': "
            f"HTTP error {status_code}"
        )
        transport_cause = _safe_transport_cause(e)
    except HTTP_REQUEST_ERROR_TYPES as e:
        transport_cause = _safe_transport_cause(e)
        if transport_cause is not None and not is_http_connect_error(e):
            raise
        if is_http_connect_error(e):
            transport_error = UserError(
                f"Failed to call tool '{tool_name}' on MCP server '{self._error_name}': "
                "Connection lost. The server may have disconnected."
            )
        elif is_http_timeout_error(e):
            transport_error = UserError(
                f"Failed to call tool '{tool_name}' on MCP server '{self._error_name}': "
                "Connection timeout."
            )
        else:
            transport_error = UserError(
                f"Failed to call tool '{tool_name}' on MCP server '{self._error_name}': "
                "Request failed."
            )

    assert transport_error is not None
    self._raise_mapped_transport_error(transport_error, transport_cause)

list_prompts async

list_prompts() -> ListPromptsResult

List the prompts available on the server.

Source code in src/agents/mcp/server.py
async def list_prompts(
    self,
) -> ListPromptsResult:
    """List the prompts available on the server."""
    if not self.session:
        raise UserError("Server not initialized. Make sure you call `connect()` first.")
    session = self.session
    assert session is not None
    result = await self._list_prompts_page(session)
    if result_next_cursor(result) is None:
        return result

    prompts = list(result.prompts)
    cursor: str | None = result_next_cursor(result)
    seen_cursors: set[str | None] = {None}
    pagination_failure: BaseException | None = None
    repeated_cursor = False
    page: ListPromptsResult | None = None
    next_cursor: str | None = None
    while cursor is not None:
        try:
            page = await self._list_prompts_page(session, cursor)
        except BaseException as error:
            if isinstance(error, BaseExceptionGroup):
                pagination_failure = _credential_safe_exception_group(error)
            elif isinstance(error, Exception):
                pagination_failure = self._user_error_for_request_operation(
                    "list prompts", error
                )
            else:
                pagination_failure = _credential_safe_exception_leaf(error)
            break
        prompts.extend(page.prompts)
        seen_cursors.add(cursor)
        next_cursor = result_next_cursor(page)
        if next_cursor is not None and next_cursor in seen_cursors:
            repeated_cursor = True
            break
        cursor = next_cursor

    if pagination_failure is not None or repeated_cursor:
        cursor = None
        seen_cursors.clear()
        prompts.clear()
        page = None
        next_cursor = None
        del result
        if pagination_failure is not None:
            raise pagination_failure from None
        raise UserError(
            f"MCP server '{self._error_name}' returned a repeated cursor while listing prompts."
        ) from None

    return cast(ListPromptsResult, clear_result_next_cursor(result, prompts=prompts))

get_prompt async

get_prompt(
    name: str, arguments: dict[str, Any] | None = None
) -> GetPromptResult

Get a specific prompt from the server.

Source code in src/agents/mcp/server.py
async def get_prompt(
    self, name: str, arguments: dict[str, Any] | None = None
) -> GetPromptResult:
    """Get a specific prompt from the server."""
    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 self._run_request_with_transport_error_redaction(
        "get prompt",
        lambda: self._maybe_serialize_request(lambda: session.get_prompt(name, arguments)),
    )

list_resources async

list_resources(
    cursor: str | None = None,
) -> ListResourcesResult

List the resources available on the server.

Source code in src/agents/mcp/server.py
async def list_resources(self, cursor: str | None = None) -> ListResourcesResult:
    """List the resources available on the server."""
    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 self._run_request_with_transport_error_redaction(
        "list resources",
        lambda: self._maybe_serialize_request(
            lambda: (
                session.list_resources()
                if cursor is None
                else session.list_resources(params=PaginatedRequestParams(cursor=cursor))
            )
            if MCP_V2
            else cast(Any, session).list_resources(cursor)
        ),
    )

list_resource_templates async

list_resource_templates(
    cursor: str | None = None,
) -> ListResourceTemplatesResult

List the resource templates available on the server.

Source code in src/agents/mcp/server.py
async def list_resource_templates(
    self, cursor: str | None = None
) -> ListResourceTemplatesResult:
    """List the resource templates available on the server."""
    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 self._run_request_with_transport_error_redaction(
        "list resource templates",
        lambda: self._maybe_serialize_request(
            lambda: (
                session.list_resource_templates()
                if cursor is None
                else session.list_resource_templates(
                    params=PaginatedRequestParams(cursor=cursor)
                )
            )
            if MCP_V2
            else cast(Any, session).list_resource_templates(cursor)
        ),
    )

read_resource async

read_resource(uri: str) -> ReadResourceResult

Read the contents of a specific resource by URI.

Parameters:

Name Type Description Default
uri str

The URI of the resource to read. See :class:~pydantic.networks.AnyUrl for the supported URI formats.

required
Source code in src/agents/mcp/server.py
async def read_resource(self, uri: str) -> ReadResourceResult:
    """Read the contents of a specific resource by URI.

    Args:
        uri: The URI of the resource to read. See :class:`~pydantic.networks.AnyUrl`
            for the supported URI formats.
    """
    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 self._run_request_with_transport_error_redaction(
        "read resource",
        lambda: self._maybe_serialize_request(
            lambda: cast(Any, session).read_resource(resource_uri(uri))
        ),
    )

invalidate_tools_cache

invalidate_tools_cache()

Invalidate the tools cache.

Source code in src/agents/mcp/server.py
def invalidate_tools_cache(self):
    """Invalidate the tools cache."""
    self._cache_dirty = True

MCPServerSseParams

Bases: TypedDict

Mirrors the params in mcp.client.sse.sse_client.

Source code in src/agents/mcp/server.py
class MCPServerSseParams(TypedDict):
    """Mirrors the params in `mcp.client.sse.sse_client`."""

    url: str
    """The URL of the server."""

    headers: NotRequired[dict[str, str]]
    """The headers to send to the server."""

    timeout: NotRequired[float]
    """The timeout for the HTTP request. Defaults to 5 seconds."""

    sse_read_timeout: NotRequired[float]
    """The timeout for the SSE connection, in seconds. Defaults to 5 minutes."""

    auth: NotRequired[Any]
    """Optional authentication handler for the installed MCP SDK's HTTP stack.

    Use ``httpx.Auth`` with MCP v1 or ``httpx2.Auth`` with MCP v2.
    """

    httpx_client_factory: NotRequired[HttpClientFactory]
    """Custom HTTP client factory for the installed MCP SDK's HTTP stack.

    Return ``httpx.AsyncClient`` with MCP v1 or ``httpx2.AsyncClient`` with MCP v2.
    """

url instance-attribute

url: str

The URL of the server.

headers instance-attribute

headers: NotRequired[dict[str, str]]

The headers to send to the server.

timeout instance-attribute

timeout: NotRequired[float]

The timeout for the HTTP request. Defaults to 5 seconds.

sse_read_timeout instance-attribute

sse_read_timeout: NotRequired[float]

The timeout for the SSE connection, in seconds. Defaults to 5 minutes.

auth instance-attribute

auth: NotRequired[Any]

Optional authentication handler for the installed MCP SDK's HTTP stack.

Use httpx.Auth with MCP v1 or httpx2.Auth with MCP v2.

httpx_client_factory instance-attribute

httpx_client_factory: NotRequired[HttpClientFactory]

Custom HTTP client factory for the installed MCP SDK's HTTP stack.

Return httpx.AsyncClient with MCP v1 or httpx2.AsyncClient with MCP v2.

MCPServerSse

Bases: _MCPServerWithClientSession

MCP server implementation that uses the HTTP with SSE transport. See the [spec] (https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#http-with-sse) for details.

Source code in src/agents/mcp/server.py
class MCPServerSse(_MCPServerWithClientSession):
    """MCP server implementation that uses the HTTP with SSE transport. See the [spec]
    (https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#http-with-sse)
    for details.
    """

    def __init__(
        self,
        params: MCPServerSseParams,
        cache_tools_list: bool = False,
        name: str | None = None,
        client_session_timeout_seconds: float | None = 5,
        tool_filter: ToolFilter = None,
        use_structured_content: bool = False,
        max_retry_attempts: int = 0,
        retry_backoff_seconds_base: float = 1.0,
        message_handler: MessageHandlerFnT | None = None,
        require_approval: RequireApprovalSetting = None,
        failure_error_function: ToolErrorFunction | None | _UnsetType = _UNSET,
        tool_meta_resolver: MCPToolMetaResolver | None = None,
        custom_data_extractor: MCPToolCustomDataExtractor | None = None,
    ):
        """Create a new MCP server based on the HTTP with SSE transport.

        Args:
            params: The params that configure the server. This includes the URL of the server,
                the headers to send to the server, the timeout for the HTTP request, and the
                timeout for the SSE connection.

            cache_tools_list: Whether to cache the tools list. If `True`, the tools list will be
                cached and only fetched from the server once. If `False`, the tools list will be
                fetched from the server on each call to `list_tools()`. The cache can be
                invalidated by calling `invalidate_tools_cache()`. You should set this to `True`
                if you know the server will not change its tools list, because it can drastically
                improve latency (by avoiding a round-trip to the server every time).

            name: A readable name for the server. If not provided, we'll create one from the
                URL.

            client_session_timeout_seconds: The MCP ClientSession read timeout. Positive finite
                values representable by `datetime.timedelta` and at least one microsecond set a
                timeout; `None` and `0` disable it. Other values are rejected during server
                construction.
            tool_filter: The tool filter to use for filtering tools.
            use_structured_content: Whether to use `tool_result.structured_content` when calling an
                MCP tool. Defaults to False for backwards compatibility - most MCP servers still
                include the structured content in the `tool_result.content`, and using it by
                default will cause duplicate content. You can set this to True if you know the
                server will not duplicate the structured content in the `tool_result.content`.
            max_retry_attempts: Number of times to retry failed list_tools/call_tool calls.
                Defaults to no retries.
            retry_backoff_seconds_base: The base delay, in seconds, for exponential
                backoff between retries.
            message_handler: Optional handler invoked for session messages as delivered by the
                ClientSession.
            require_approval: Approval policy for tools on this server. Accepts "always"/"never",
                a dict of tool names to those values, or an object with always/never tool lists.
            failure_error_function: Optional function used to convert MCP tool failures into
                a model-visible error message. If explicitly set to None, tool errors will be
                raised instead of converted. If left unset, the agent-level configuration (or
                SDK default) will be used.
            tool_meta_resolver: Optional callable that produces MCP request metadata (`_meta`) for
                tool calls. It is invoked by the Agents SDK before calling `call_tool`.
            custom_data_extractor: Optional callable that produces SDK-only custom data for
                emitted MCP tool output items.
        """
        super().__init__(
            cache_tools_list=cache_tools_list,
            client_session_timeout_seconds=client_session_timeout_seconds,
            tool_filter=tool_filter,
            use_structured_content=use_structured_content,
            max_retry_attempts=max_retry_attempts,
            retry_backoff_seconds_base=retry_backoff_seconds_base,
            message_handler=message_handler,
            require_approval=require_approval,
            failure_error_function=failure_error_function,
            tool_meta_resolver=tool_meta_resolver,
            custom_data_extractor=custom_data_extractor,
        )

        self.params = params
        self._name = name or f"sse: {self.params['url']}"

    def create_streams(
        self,
    ) -> AbstractAsyncContextManager[MCPStreamTransport]:
        """Create the streams for the server."""
        kwargs: dict[str, Any] = {
            "url": self.params["url"],
            "headers": self.params.get("headers", None),
            "timeout": self.params.get("timeout", 5),
            "sse_read_timeout": self.params.get("sse_read_timeout", 60 * 5),
        }
        if MCP_V2:
            _validate_v2_http_auth(self.params.get("auth"))
            factory = (
                self.params.get("httpx_client_factory") or _create_default_streamable_http_client
            )
            kwargs["httpx_client_factory"] = _validated_v2_http_client_factory(factory)
            if "auth" in self.params:
                kwargs["auth"] = self.params["auth"]
            return sse_client(**kwargs)

        if "auth" in self.params:
            kwargs["auth"] = self.params["auth"]
        kwargs["httpx_client_factory"] = (
            self.params.get("httpx_client_factory") or _create_default_streamable_http_client
        )
        return sse_client(**kwargs)

    @property
    def name(self) -> str:
        """A readable name for the server."""
        return self._name

name property

name: str

A readable name for the server.

__init__

__init__(
    params: MCPServerSseParams,
    cache_tools_list: bool = False,
    name: str | None = None,
    client_session_timeout_seconds: float | None = 5,
    tool_filter: ToolFilter = None,
    use_structured_content: bool = False,
    max_retry_attempts: int = 0,
    retry_backoff_seconds_base: float = 1.0,
    message_handler: MessageHandlerFnT | None = None,
    require_approval: RequireApprovalSetting = None,
    failure_error_function: ToolErrorFunction
    | None
    | _UnsetType = _UNSET,
    tool_meta_resolver: MCPToolMetaResolver | None = None,
    custom_data_extractor: MCPToolCustomDataExtractor
    | None = None,
)

Create a new MCP server based on the HTTP with SSE transport.

Parameters:

Name Type Description Default
params MCPServerSseParams

The params that configure the server. This includes the URL of the server, the headers to send to the server, the timeout for the HTTP request, and the timeout for the SSE connection.

required
cache_tools_list bool

Whether to cache the tools list. If True, the tools list will be cached and only fetched from the server once. If False, the tools list will be fetched from the server on each call to list_tools(). The cache can be invalidated by calling invalidate_tools_cache(). You should set this to True if you know the server will not change its tools list, because it can drastically improve latency (by avoiding a round-trip to the server every time).

False
name str | None

A readable name for the server. If not provided, we'll create one from the URL.

None
client_session_timeout_seconds float | None

The MCP ClientSession read timeout. Positive finite values representable by datetime.timedelta and at least one microsecond set a timeout; None and 0 disable it. Other values are rejected during server construction.

5
tool_filter ToolFilter

The tool filter to use for filtering tools.

None
use_structured_content bool

Whether to use tool_result.structured_content when calling an MCP tool. Defaults to False for backwards compatibility - most MCP servers still include the structured content in the tool_result.content, and using it by default will cause duplicate content. You can set this to True if you know the server will not duplicate the structured content in the tool_result.content.

False
max_retry_attempts int

Number of times to retry failed list_tools/call_tool calls. Defaults to no retries.

0
retry_backoff_seconds_base float

The base delay, in seconds, for exponential backoff between retries.

1.0
message_handler MessageHandlerFnT | None

Optional handler invoked for session messages as delivered by the ClientSession.

None
require_approval RequireApprovalSetting

Approval policy for tools on this server. Accepts "always"/"never", a dict of tool names to those values, or an object with always/never tool lists.

None
failure_error_function ToolErrorFunction | None | _UnsetType

Optional function used to convert MCP tool failures into a model-visible error message. If explicitly set to None, tool errors will be raised instead of converted. If left unset, the agent-level configuration (or SDK default) will be used.

_UNSET
tool_meta_resolver MCPToolMetaResolver | None

Optional callable that produces MCP request metadata (_meta) for tool calls. It is invoked by the Agents SDK before calling call_tool.

None
custom_data_extractor MCPToolCustomDataExtractor | None

Optional callable that produces SDK-only custom data for emitted MCP tool output items.

None
Source code in src/agents/mcp/server.py
def __init__(
    self,
    params: MCPServerSseParams,
    cache_tools_list: bool = False,
    name: str | None = None,
    client_session_timeout_seconds: float | None = 5,
    tool_filter: ToolFilter = None,
    use_structured_content: bool = False,
    max_retry_attempts: int = 0,
    retry_backoff_seconds_base: float = 1.0,
    message_handler: MessageHandlerFnT | None = None,
    require_approval: RequireApprovalSetting = None,
    failure_error_function: ToolErrorFunction | None | _UnsetType = _UNSET,
    tool_meta_resolver: MCPToolMetaResolver | None = None,
    custom_data_extractor: MCPToolCustomDataExtractor | None = None,
):
    """Create a new MCP server based on the HTTP with SSE transport.

    Args:
        params: The params that configure the server. This includes the URL of the server,
            the headers to send to the server, the timeout for the HTTP request, and the
            timeout for the SSE connection.

        cache_tools_list: Whether to cache the tools list. If `True`, the tools list will be
            cached and only fetched from the server once. If `False`, the tools list will be
            fetched from the server on each call to `list_tools()`. The cache can be
            invalidated by calling `invalidate_tools_cache()`. You should set this to `True`
            if you know the server will not change its tools list, because it can drastically
            improve latency (by avoiding a round-trip to the server every time).

        name: A readable name for the server. If not provided, we'll create one from the
            URL.

        client_session_timeout_seconds: The MCP ClientSession read timeout. Positive finite
            values representable by `datetime.timedelta` and at least one microsecond set a
            timeout; `None` and `0` disable it. Other values are rejected during server
            construction.
        tool_filter: The tool filter to use for filtering tools.
        use_structured_content: Whether to use `tool_result.structured_content` when calling an
            MCP tool. Defaults to False for backwards compatibility - most MCP servers still
            include the structured content in the `tool_result.content`, and using it by
            default will cause duplicate content. You can set this to True if you know the
            server will not duplicate the structured content in the `tool_result.content`.
        max_retry_attempts: Number of times to retry failed list_tools/call_tool calls.
            Defaults to no retries.
        retry_backoff_seconds_base: The base delay, in seconds, for exponential
            backoff between retries.
        message_handler: Optional handler invoked for session messages as delivered by the
            ClientSession.
        require_approval: Approval policy for tools on this server. Accepts "always"/"never",
            a dict of tool names to those values, or an object with always/never tool lists.
        failure_error_function: Optional function used to convert MCP tool failures into
            a model-visible error message. If explicitly set to None, tool errors will be
            raised instead of converted. If left unset, the agent-level configuration (or
            SDK default) will be used.
        tool_meta_resolver: Optional callable that produces MCP request metadata (`_meta`) for
            tool calls. It is invoked by the Agents SDK before calling `call_tool`.
        custom_data_extractor: Optional callable that produces SDK-only custom data for
            emitted MCP tool output items.
    """
    super().__init__(
        cache_tools_list=cache_tools_list,
        client_session_timeout_seconds=client_session_timeout_seconds,
        tool_filter=tool_filter,
        use_structured_content=use_structured_content,
        max_retry_attempts=max_retry_attempts,
        retry_backoff_seconds_base=retry_backoff_seconds_base,
        message_handler=message_handler,
        require_approval=require_approval,
        failure_error_function=failure_error_function,
        tool_meta_resolver=tool_meta_resolver,
        custom_data_extractor=custom_data_extractor,
    )

    self.params = params
    self._name = name or f"sse: {self.params['url']}"

create_streams

create_streams() -> AbstractAsyncContextManager[
    MCPStreamTransport
]

Create the streams for the server.

Source code in src/agents/mcp/server.py
def create_streams(
    self,
) -> AbstractAsyncContextManager[MCPStreamTransport]:
    """Create the streams for the server."""
    kwargs: dict[str, Any] = {
        "url": self.params["url"],
        "headers": self.params.get("headers", None),
        "timeout": self.params.get("timeout", 5),
        "sse_read_timeout": self.params.get("sse_read_timeout", 60 * 5),
    }
    if MCP_V2:
        _validate_v2_http_auth(self.params.get("auth"))
        factory = (
            self.params.get("httpx_client_factory") or _create_default_streamable_http_client
        )
        kwargs["httpx_client_factory"] = _validated_v2_http_client_factory(factory)
        if "auth" in self.params:
            kwargs["auth"] = self.params["auth"]
        return sse_client(**kwargs)

    if "auth" in self.params:
        kwargs["auth"] = self.params["auth"]
    kwargs["httpx_client_factory"] = (
        self.params.get("httpx_client_factory") or _create_default_streamable_http_client
    )
    return sse_client(**kwargs)

connect async

connect()

Connect to the server.

Source code in src/agents/mcp/server.py
async def connect(self):
    """Connect to the server."""
    read_timeout = _client_session_read_timeout(self.client_session_timeout_seconds)
    connection_succeeded = False
    connection_error: UserError | None = None
    connection_cause: Exception | None = None
    connection_exception: BaseException | None = None
    cleanup_failure: BaseException | None = None
    try:
        if MCP_V2:
            session = await self.exit_stack.enter_async_context(
                self._client_session_context(read_timeout)
            )
            self.server_initialize_result = getattr(session, "initialize_result", None)
        else:
            v1_read_timeout = cast(timedelta | None, read_timeout)
            transport = await self.exit_stack.enter_async_context(self.create_streams())
            read, write, *rest = transport
            self._get_session_id = rest[0] if rest and callable(rest[0]) else None
            session = await self.exit_stack.enter_async_context(
                cast(Any, ClientSession)(
                    read,
                    write,
                    v1_read_timeout,
                    message_handler=self.message_handler,
                )
            )
            self.server_initialize_result = await session.initialize()
        self.session = session
        connection_succeeded = True
    except BaseException as e:
        if not isinstance(e, Exception):
            connection_exception = e
        else:
            http_errors = self._extract_http_errors_from_exception(e)
            if not http_errors:
                connection_exception = e
            else:
                unsafe_http_error = _first_unretainable_transport_error(http_errors)
                http_error = unsafe_http_error or http_errors[0]
                connection_cause = _safe_transport_cause(http_error)
                maps_safe_error = (
                    is_http_status_error(http_error)
                    or is_http_connect_error(http_error)
                    or is_http_timeout_error(http_error)
                )
                if connection_cause is not None and not maps_safe_error:
                    connection_exception = e
                    connection_cause = None
                else:
                    connection_error = self._user_error_for_http_error(http_error)
                http_errors.clear()
                del http_error
                del unsafe_http_error

    # Run cleanup after leaving the connection exception handler so a cleanup UserError does
    # not retain the pending connection failure as its implicit context.
    if not connection_succeeded:
        try:
            await self.cleanup()
        except UserError as e:
            cleanup_failure = e
        except Exception as cleanup_error:
            # Suppress RuntimeError about cancel scopes during cleanup - this is a known
            # issue with the MCP library's async generator cleanup and shouldn't mask the
            # original error.
            if isinstance(cleanup_error, RuntimeError) and "cancel scope" in str(cleanup_error):
                logger.debug(
                    "%s",
                    get_mcp_server_log_message(
                        "Ignoring cancel scope error during cleanup of MCP server", self
                    ),
                    stacklevel=2,
                )
            else:
                # Log other cleanup errors but don't raise - original error is more important.
                logger.warning(
                    "%s",
                    get_mcp_server_log_message("Error during cleanup of MCP server", self),
                    stacklevel=2,
                )
        except BaseException as e:
            cleanup_failure = e

    if cleanup_failure is not None:
        connection_exception = None
        connection_error = None
        connection_cause = None
        if isinstance(cleanup_failure, UserError):
            self._raise_mapped_transport_error(cleanup_failure, None)
        raise cleanup_failure

    if connection_exception is not None:
        raise connection_exception

    if connection_error is not None:
        self._raise_mapped_transport_error(connection_error, connection_cause)

cleanup async

cleanup()

Cleanup the server.

Source code in src/agents/mcp/server.py
async def cleanup(self):
    """Cleanup the server."""
    async with self._cleanup_lock:
        # Only raise HTTP errors if we're cleaning up after a failed connection.
        # During normal teardown (via __aexit__), log but don't raise to avoid
        # masking the original exception.
        is_failed_connection_cleanup = self.session is None
        cleanup_error: UserError | None = None

        try:
            await self.exit_stack.aclose()
        except asyncio.CancelledError as e:
            log_tool_action_debug(
                logger,
                get_mcp_server_log_message("Cleanup cancelled for MCP server", self),
                e,
            )
            raise
        except (  # type: ignore[misc]
            BaseExceptionGroup,
            *HTTP_STATUS_ERROR_TYPES,
            *HTTP_REQUEST_ERROR_TYPES,
        ) as e:
            selected_http_error = self._select_cleanup_transport_error(e)
            if selected_http_error is not None:
                if is_failed_connection_cleanup:
                    cleanup_error = self._user_error_for_http_error(
                        selected_http_error,
                        include_http_reason_phrase=False,
                    )
                    del selected_http_error
                else:
                    _log_cleanup_transport_warning(
                        get_mcp_server_log_message(
                            _get_cleanup_transport_error_message(selected_http_error), self
                        )
                    )
            elif is_http_request_error(e):
                _log_cleanup_transport_warning(
                    get_mcp_server_log_message(_get_cleanup_transport_error_message(e), self)
                )
            elif isinstance(e, BaseExceptionGroup):
                http_errors = self._extract_http_errors_from_exception(e)
                if http_errors:
                    safe_error_group = _credential_safe_exception_group(e)
                    log_tool_action_error(
                        logger,
                        get_mcp_server_log_message("Error cleaning up MCP server", self),
                        safe_error_group,
                    )
                else:
                    # No HTTP error found, suppress RuntimeError about cancel scopes.
                    has_cancel_scope_error = any(
                        isinstance(exc, RuntimeError) and "cancel scope" in str(exc)
                        for exc in e.exceptions
                    )
                    if has_cancel_scope_error:
                        log_tool_action_debug(
                            logger,
                            get_mcp_server_log_message(
                                "Ignoring cancel scope error during cleanup of MCP server", self
                            ),
                            e,
                        )
                    else:
                        log_tool_action_error(
                            logger,
                            get_mcp_server_log_message("Error cleaning up MCP server", self),
                            e,
                        )
            else:
                log_tool_action_error(
                    logger,
                    get_mcp_server_log_message("Error cleaning up MCP server", self),
                    e,
                )
        except Exception as e:
            # Suppress RuntimeError about cancel scopes - this is a known issue with the MCP
            # library when background tasks fail during async generator cleanup
            if isinstance(e, RuntimeError) and "cancel scope" in str(e):
                log_tool_action_debug(
                    logger,
                    get_mcp_server_log_message(
                        "Ignoring cancel scope error during cleanup of MCP server", self
                    ),
                    e,
                )
            else:
                log_tool_action_error(
                    logger,
                    get_mcp_server_log_message("Error cleaning up MCP server", self),
                    e,
                )
        finally:
            self.session = None
            self._get_session_id = None
            self._v2_session_id = None

        if cleanup_error is not None:
            self._raise_mapped_transport_error(cleanup_error, None)

list_tools async

list_tools(
    run_context: RunContextWrapper[Any] | None = None,
    agent: AgentBase | None = None,
) -> list[Tool]

List the tools available on the server.

Source code in src/agents/mcp/server.py
async def list_tools(
    self,
    run_context: RunContextWrapper[Any] | None = None,
    agent: AgentBase | None = None,
) -> list[MCPTool]:
    """List the tools available on the server."""
    if not self.session:
        raise UserError("Server not initialized. Make sure you call `connect()` first.")
    session = self.session
    assert session is not None

    transport_error: UserError | None = None
    transport_cause: Exception | None = None
    try:
        tools: list[MCPTool]
        # Return from cache if caching is enabled, we have tools, and the cache is not dirty
        if self.cache_tools_list and not self._cache_dirty and self._tools_list:
            tools = self._tools_list
        else:
            tools = []
            cursor: str | None = None
            seen_cursors: set[str | None] = set()

            async def fetch_pages() -> bool:
                nonlocal cursor
                while True:
                    result = await self._list_tools_page(session, cursor)
                    tools.extend(result.tools)
                    seen_cursors.add(cursor)
                    next_cursor = result_next_cursor(result)
                    if next_cursor is None:
                        return True
                    if next_cursor in seen_cursors:
                        return False
                    cursor = next_cursor

            pagination_complete = False
            pagination_failure: BaseException | None = None
            try:
                pagination_complete = await self._run_with_retries(fetch_pages)
            except BaseException as error:
                if cursor is None:
                    raise
                if isinstance(error, BaseExceptionGroup):
                    pagination_failure = _credential_safe_exception_group(error)
                elif isinstance(error, Exception):
                    pagination_failure = self._user_error_for_request_operation(
                        "list tools", error
                    )
                else:
                    pagination_failure = _credential_safe_exception_leaf(error)

            if pagination_failure is not None or not pagination_complete:
                cursor = None
                seen_cursors.clear()
                tools.clear()
                del fetch_pages
                if pagination_failure is not None:
                    raise pagination_failure from None
                raise UserError(
                    f"MCP server '{self._error_name}' returned a repeated cursor while "
                    "listing tools."
                ) from None

            cursor = None
            seen_cursors.clear()
            del fetch_pages
            self._tools_list = tools
            self._cache_dirty = False

        # Filter tools based on tool_filter
        filtered_tools = tools
        if self.tool_filter is not None:
            filtered_tools = await self._apply_tool_filter(filtered_tools, run_context, agent)
        return filtered_tools
    except HTTP_STATUS_ERROR_TYPES as e:
        status_code = http_status_code(e)
        transport_error = UserError(
            f"Failed to list tools from MCP server '{self._error_name}': "
            f"HTTP error {status_code}"
        )
        transport_cause = _safe_transport_cause(e)
    except HTTP_REQUEST_ERROR_TYPES as e:
        transport_cause = _safe_transport_cause(e)
        if transport_cause is not None and not is_http_connect_error(e):
            raise
        if is_http_connect_error(e):
            transport_error = UserError(
                f"Failed to list tools from MCP server '{self._error_name}': Connection lost. "
                f"The server may have disconnected."
            )
        elif is_http_timeout_error(e):
            transport_error = UserError(
                f"Failed to list tools from MCP server '{self._error_name}': "
                "Connection timeout."
            )
        else:
            transport_error = UserError(
                f"Failed to list tools from MCP server '{self._error_name}': Request failed."
            )

    assert transport_error is not None
    self._raise_mapped_transport_error(transport_error, transport_cause)

call_tool async

call_tool(
    tool_name: str,
    arguments: dict[str, Any] | None,
    meta: dict[str, Any] | None = None,
) -> CallToolResult

Invoke a tool on the server.

Source code in src/agents/mcp/server.py
async def call_tool(
    self,
    tool_name: str,
    arguments: dict[str, Any] | None,
    meta: dict[str, Any] | None = None,
) -> CallToolResult:
    """Invoke a tool on the server."""
    if not self.session:
        raise UserError("Server not initialized. Make sure you call `connect()` first.")
    session = self.session
    assert session is not None

    transport_error: UserError | None = None
    transport_cause: Exception | None = None
    try:
        self._validate_required_parameters(tool_name=tool_name, arguments=arguments)
        if meta is None:
            return await self._run_with_retries(
                lambda: self._maybe_serialize_request(
                    lambda: session.call_tool(tool_name, arguments)
                )
            )
        return await self._run_with_retries(
            lambda: self._maybe_serialize_request(
                lambda: cast(Any, session).call_tool(tool_name, arguments, meta=meta)
            )
        )
    except HTTP_STATUS_ERROR_TYPES as e:
        status_code = http_status_code(e)
        transport_error = UserError(
            f"Failed to call tool '{tool_name}' on MCP server '{self._error_name}': "
            f"HTTP error {status_code}"
        )
        transport_cause = _safe_transport_cause(e)
    except HTTP_REQUEST_ERROR_TYPES as e:
        transport_cause = _safe_transport_cause(e)
        if transport_cause is not None and not is_http_connect_error(e):
            raise
        if is_http_connect_error(e):
            transport_error = UserError(
                f"Failed to call tool '{tool_name}' on MCP server '{self._error_name}': "
                "Connection lost. The server may have disconnected."
            )
        elif is_http_timeout_error(e):
            transport_error = UserError(
                f"Failed to call tool '{tool_name}' on MCP server '{self._error_name}': "
                "Connection timeout."
            )
        else:
            transport_error = UserError(
                f"Failed to call tool '{tool_name}' on MCP server '{self._error_name}': "
                "Request failed."
            )

    assert transport_error is not None
    self._raise_mapped_transport_error(transport_error, transport_cause)

list_prompts async

list_prompts() -> ListPromptsResult

List the prompts available on the server.

Source code in src/agents/mcp/server.py
async def list_prompts(
    self,
) -> ListPromptsResult:
    """List the prompts available on the server."""
    if not self.session:
        raise UserError("Server not initialized. Make sure you call `connect()` first.")
    session = self.session
    assert session is not None
    result = await self._list_prompts_page(session)
    if result_next_cursor(result) is None:
        return result

    prompts = list(result.prompts)
    cursor: str | None = result_next_cursor(result)
    seen_cursors: set[str | None] = {None}
    pagination_failure: BaseException | None = None
    repeated_cursor = False
    page: ListPromptsResult | None = None
    next_cursor: str | None = None
    while cursor is not None:
        try:
            page = await self._list_prompts_page(session, cursor)
        except BaseException as error:
            if isinstance(error, BaseExceptionGroup):
                pagination_failure = _credential_safe_exception_group(error)
            elif isinstance(error, Exception):
                pagination_failure = self._user_error_for_request_operation(
                    "list prompts", error
                )
            else:
                pagination_failure = _credential_safe_exception_leaf(error)
            break
        prompts.extend(page.prompts)
        seen_cursors.add(cursor)
        next_cursor = result_next_cursor(page)
        if next_cursor is not None and next_cursor in seen_cursors:
            repeated_cursor = True
            break
        cursor = next_cursor

    if pagination_failure is not None or repeated_cursor:
        cursor = None
        seen_cursors.clear()
        prompts.clear()
        page = None
        next_cursor = None
        del result
        if pagination_failure is not None:
            raise pagination_failure from None
        raise UserError(
            f"MCP server '{self._error_name}' returned a repeated cursor while listing prompts."
        ) from None

    return cast(ListPromptsResult, clear_result_next_cursor(result, prompts=prompts))

get_prompt async

get_prompt(
    name: str, arguments: dict[str, Any] | None = None
) -> GetPromptResult

Get a specific prompt from the server.

Source code in src/agents/mcp/server.py
async def get_prompt(
    self, name: str, arguments: dict[str, Any] | None = None
) -> GetPromptResult:
    """Get a specific prompt from the server."""
    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 self._run_request_with_transport_error_redaction(
        "get prompt",
        lambda: self._maybe_serialize_request(lambda: session.get_prompt(name, arguments)),
    )

list_resources async

list_resources(
    cursor: str | None = None,
) -> ListResourcesResult

List the resources available on the server.

Source code in src/agents/mcp/server.py
async def list_resources(self, cursor: str | None = None) -> ListResourcesResult:
    """List the resources available on the server."""
    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 self._run_request_with_transport_error_redaction(
        "list resources",
        lambda: self._maybe_serialize_request(
            lambda: (
                session.list_resources()
                if cursor is None
                else session.list_resources(params=PaginatedRequestParams(cursor=cursor))
            )
            if MCP_V2
            else cast(Any, session).list_resources(cursor)
        ),
    )

list_resource_templates async

list_resource_templates(
    cursor: str | None = None,
) -> ListResourceTemplatesResult

List the resource templates available on the server.

Source code in src/agents/mcp/server.py
async def list_resource_templates(
    self, cursor: str | None = None
) -> ListResourceTemplatesResult:
    """List the resource templates available on the server."""
    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 self._run_request_with_transport_error_redaction(
        "list resource templates",
        lambda: self._maybe_serialize_request(
            lambda: (
                session.list_resource_templates()
                if cursor is None
                else session.list_resource_templates(
                    params=PaginatedRequestParams(cursor=cursor)
                )
            )
            if MCP_V2
            else cast(Any, session).list_resource_templates(cursor)
        ),
    )

read_resource async

read_resource(uri: str) -> ReadResourceResult

Read the contents of a specific resource by URI.

Parameters:

Name Type Description Default
uri str

The URI of the resource to read. See :class:~pydantic.networks.AnyUrl for the supported URI formats.

required
Source code in src/agents/mcp/server.py
async def read_resource(self, uri: str) -> ReadResourceResult:
    """Read the contents of a specific resource by URI.

    Args:
        uri: The URI of the resource to read. See :class:`~pydantic.networks.AnyUrl`
            for the supported URI formats.
    """
    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 self._run_request_with_transport_error_redaction(
        "read resource",
        lambda: self._maybe_serialize_request(
            lambda: cast(Any, session).read_resource(resource_uri(uri))
        ),
    )

invalidate_tools_cache

invalidate_tools_cache()

Invalidate the tools cache.

Source code in src/agents/mcp/server.py
def invalidate_tools_cache(self):
    """Invalidate the tools cache."""
    self._cache_dirty = True

MCPServerStreamableHttpParams

Bases: TypedDict

Mirrors the params in mcp.client.streamable_http.streamablehttp_client.

Source code in src/agents/mcp/server.py
class MCPServerStreamableHttpParams(TypedDict):
    """Mirrors the params in `mcp.client.streamable_http.streamablehttp_client`."""

    url: str
    """The URL of the server."""

    headers: NotRequired[dict[str, str]]
    """The headers to send to the server."""

    timeout: NotRequired[timedelta | float]
    """The timeout for the HTTP request. Defaults to 5 seconds."""

    sse_read_timeout: NotRequired[timedelta | float]
    """The timeout for the SSE connection, in seconds. Defaults to 5 minutes."""

    terminate_on_close: NotRequired[bool]
    """Terminate on close"""

    httpx_client_factory: NotRequired[HttpClientFactory]
    """Custom HTTP client factory for the installed MCP SDK's HTTP stack.

    Return ``httpx.AsyncClient`` with MCP v1 or ``httpx2.AsyncClient`` with MCP v2.
    """

    auth: NotRequired[Any]
    """Optional authentication handler for the installed MCP SDK's HTTP stack.

    Use ``httpx.Auth`` with MCP v1 or ``httpx2.Auth`` with MCP v2.
    """

    ignore_initialized_notification_failure: NotRequired[bool]
    """Whether to ignore failures when sending the best-effort
    ``notifications/initialized`` POST.

    Defaults to ``False``. When set to ``True``, initialized-notification failures are
    logged and ignored so subsequent requests on the same transport can continue. This
    option requires MCP Python SDK v1; MCP v2 rejects it before connecting because its
    public transport API does not expose these failures.
    """

url instance-attribute

url: str

The URL of the server.

headers instance-attribute

headers: NotRequired[dict[str, str]]

The headers to send to the server.

timeout instance-attribute

timeout: NotRequired[timedelta | float]

The timeout for the HTTP request. Defaults to 5 seconds.

sse_read_timeout instance-attribute

sse_read_timeout: NotRequired[timedelta | float]

The timeout for the SSE connection, in seconds. Defaults to 5 minutes.

terminate_on_close instance-attribute

terminate_on_close: NotRequired[bool]

Terminate on close

httpx_client_factory instance-attribute

httpx_client_factory: NotRequired[HttpClientFactory]

Custom HTTP client factory for the installed MCP SDK's HTTP stack.

Return httpx.AsyncClient with MCP v1 or httpx2.AsyncClient with MCP v2.

auth instance-attribute

auth: NotRequired[Any]

Optional authentication handler for the installed MCP SDK's HTTP stack.

Use httpx.Auth with MCP v1 or httpx2.Auth with MCP v2.

ignore_initialized_notification_failure instance-attribute

ignore_initialized_notification_failure: NotRequired[bool]

Whether to ignore failures when sending the best-effort notifications/initialized POST.

Defaults to False. When set to True, initialized-notification failures are logged and ignored so subsequent requests on the same transport can continue. This option requires MCP Python SDK v1; MCP v2 rejects it before connecting because its public transport API does not expose these failures.

MCPServerStreamableHttp

Bases: _MCPServerWithClientSession

MCP server implementation that uses the Streamable HTTP transport. See the [spec] (https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) for details.

Source code in src/agents/mcp/server.py
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
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)
    for details.
    """

    def __init__(
        self,
        params: MCPServerStreamableHttpParams,
        cache_tools_list: bool = False,
        name: str | None = None,
        client_session_timeout_seconds: float | None = 5,
        tool_filter: ToolFilter = None,
        use_structured_content: bool = False,
        max_retry_attempts: int = 0,
        retry_backoff_seconds_base: float = 1.0,
        message_handler: MessageHandlerFnT | None = None,
        require_approval: RequireApprovalSetting = None,
        failure_error_function: ToolErrorFunction | None | _UnsetType = _UNSET,
        tool_meta_resolver: MCPToolMetaResolver | None = None,
        custom_data_extractor: MCPToolCustomDataExtractor | None = None,
    ):
        """Create a new MCP server based on the Streamable HTTP transport.

        Args:
            params: The params that configure the server. This includes the URL of the server,
                the headers to send to the server, the timeout for the HTTP request, the
                timeout for the Streamable HTTP connection, whether we need to
                terminate on close, and an optional custom HTTP client factory.

            cache_tools_list: Whether to cache the tools list. If `True`, the tools list will be
                cached and only fetched from the server once. If `False`, the tools list will be
                fetched from the server on each call to `list_tools()`. The cache can be
                invalidated by calling `invalidate_tools_cache()`. You should set this to `True`
                if you know the server will not change its tools list, because it can drastically
                improve latency (by avoiding a round-trip to the server every time).

            name: A readable name for the server. If not provided, we'll create one from the
                URL.

            client_session_timeout_seconds: The MCP ClientSession read timeout. Positive finite
                values representable by `datetime.timedelta` and at least one microsecond set a
                timeout; `None` and `0` disable it. Other values are rejected during server
                construction.
            tool_filter: The tool filter to use for filtering tools.
            use_structured_content: Whether to use `tool_result.structured_content` when calling an
                MCP tool. Defaults to False for backwards compatibility - most MCP servers still
                include the structured content in the `tool_result.content`, and using it by
                default will cause duplicate content. You can set this to True if you know the
                server will not duplicate the structured content in the `tool_result.content`.
            max_retry_attempts: Number of times to retry failed list_tools/call_tool calls.
                Defaults to no retries.
            retry_backoff_seconds_base: The base delay, in seconds, for exponential
                backoff between retries.
            message_handler: Optional handler invoked for session messages as delivered by the
                ClientSession.
            require_approval: Approval policy for tools on this server. Accepts "always"/"never",
                a dict of tool names to those values, or an object with always/never tool lists.
            failure_error_function: Optional function used to convert MCP tool failures into
                a model-visible error message. If explicitly set to None, tool errors will be
                raised instead of converted. If left unset, the agent-level configuration (or
                SDK default) will be used.
            tool_meta_resolver: Optional callable that produces MCP request metadata (`_meta`) for
                tool calls. It is invoked by the Agents SDK before calling `call_tool`.
            custom_data_extractor: Optional callable that produces SDK-only custom data for
                emitted MCP tool output items.
        """
        super().__init__(
            cache_tools_list=cache_tools_list,
            client_session_timeout_seconds=client_session_timeout_seconds,
            tool_filter=tool_filter,
            use_structured_content=use_structured_content,
            max_retry_attempts=max_retry_attempts,
            retry_backoff_seconds_base=retry_backoff_seconds_base,
            message_handler=message_handler,
            require_approval=require_approval,
            failure_error_function=failure_error_function,
            tool_meta_resolver=tool_meta_resolver,
            custom_data_extractor=custom_data_extractor,
        )

        self.params = params
        self._name = name or f"streamable_http: {self.params['url']}"
        self._serialize_session_requests = True

    def create_streams(
        self,
    ) -> AbstractAsyncContextManager[MCPStreamTransport]:
        """Create the streams for the server."""
        kwargs: dict[str, Any] = {
            "url": self.params["url"],
            "headers": self.params.get("headers", None),
            "timeout": self.params.get("timeout", 5),
            "sse_read_timeout": self.params.get("sse_read_timeout", 60 * 5),
            "terminate_on_close": self.params.get("terminate_on_close", True),
        }
        httpx_client_factory = self.params.get("httpx_client_factory")
        if MCP_V2:
            if self.params.get("ignore_initialized_notification_failure", False):
                raise UserError(
                    "ignore_initialized_notification_failure is not supported with MCP Python "
                    "SDK v2 because its public transport API does not expose initialized-"
                    "notification failures. Leave it disabled or pin mcp<2."
                )
            _validate_v2_http_auth(self.params.get("auth"))
            on_session_id: Callable[[str], None] | None = None
            if self.session is None:
                self._v2_session_id = None

                def capture_session_id(session_id: str) -> None:
                    self._v2_session_id = session_id

                on_session_id = capture_session_id
                self._get_session_id = lambda: self._v2_session_id
            return _streamablehttp_client_v2(
                **kwargs,
                httpx_client_factory=httpx_client_factory or _create_default_streamable_http_client,
                auth=self.params.get("auth"),
                on_session_id=on_session_id,
            )

        if self.params.get("ignore_initialized_notification_failure", False):
            return _streamablehttp_client_with_transport(
                **kwargs,
                httpx_client_factory=httpx_client_factory or _create_default_streamable_http_client,
                auth=self.params.get("auth"),
                transport_factory=_InitializedNotificationTolerantStreamableHTTPTransport,
            )
        kwargs["httpx_client_factory"] = (
            httpx_client_factory or _create_default_streamable_http_client
        )
        if "auth" in self.params:
            kwargs["auth"] = self.params["auth"]
        return cast(
            AbstractAsyncContextManager[MCPStreamTransport],
            _require_streamablehttp_client_v1()(**kwargs),
        )

    @asynccontextmanager
    async def _isolated_client_session(self):
        read_timeout = _client_session_read_timeout(self.client_session_timeout_seconds)
        async with self._client_session_context(read_timeout) as session:
            yield session

    async def _call_tool_with_session(
        self,
        session: ClientSession,
        tool_name: str,
        arguments: dict[str, Any] | None,
        meta: dict[str, Any] | None = None,
    ) -> CallToolResult:
        if meta is None:
            return await session.call_tool(tool_name, arguments)
        return cast(
            CallToolResult,
            await cast(Any, session).call_tool(tool_name, arguments, meta=meta),
        )

    def _should_retry_in_isolated_session(self, exc: BaseException) -> bool:
        if isinstance(exc, asyncio.CancelledError | ClosedResourceError):
            return True
        if is_http_connect_error(exc) or is_http_timeout_error(exc):
            return True
        if is_http_status_error(exc):
            return http_status_code(exc) >= 500
        if isinstance(exc, MCPError):
            return is_mcp_timeout_error(exc) or is_mcp_connection_closed_error(exc)
        if isinstance(exc, BaseExceptionGroup):
            return bool(exc.exceptions) and all(
                self._should_retry_in_isolated_session(inner) for inner in exc.exceptions
            )
        return False

    async def _call_tool_with_shared_session(
        self,
        tool_name: str,
        arguments: dict[str, Any] | None,
        meta: dict[str, Any] | None = None,
        *,
        allow_isolated_retry: bool,
    ) -> CallToolResult:
        session = self.session
        assert session is not None
        try:
            return await self._maybe_serialize_request(
                lambda: self._call_tool_with_session(session, tool_name, arguments, meta)
            )
        except BaseException as exc:
            if allow_isolated_retry and self._should_retry_in_isolated_session(exc):
                raise _SharedSessionRequestNeedsIsolation from exc
            raise

    async def _call_tool_with_isolated_retry(
        self,
        tool_name: str,
        arguments: dict[str, Any] | None,
        meta: dict[str, Any] | None = None,
        *,
        allow_isolated_retry: bool,
    ) -> tuple[CallToolResult, bool]:
        request_task = asyncio.create_task(
            self._call_tool_with_shared_session(
                tool_name,
                arguments,
                meta,
                allow_isolated_retry=allow_isolated_retry,
            )
        )
        try:
            return await asyncio.shield(request_task), False
        except _SharedSessionRequestNeedsIsolation:
            exit_stack = AsyncExitStack()
            try:
                session = await exit_stack.enter_async_context(self._isolated_client_session())
            except asyncio.CancelledError:
                await exit_stack.aclose()
                raise
            except BaseException as exc:
                await exit_stack.aclose()
                raise _IsolatedSessionRetryFailed() from exc
            try:
                try:
                    result = await self._call_tool_with_session(session, tool_name, arguments, meta)
                    return result, True
                except asyncio.CancelledError:
                    raise
                except BaseException as exc:
                    raise _IsolatedSessionRetryFailed() from exc
            finally:
                await exit_stack.aclose()
        except asyncio.CancelledError:
            if not request_task.done():
                request_task.cancel()
            try:
                await request_task
            except BaseException:
                pass
            raise

    async def call_tool(
        self,
        tool_name: str,
        arguments: dict[str, Any] | None,
        meta: dict[str, Any] | None = None,
    ) -> CallToolResult:
        if not self.session:
            raise UserError("Server not initialized. Make sure you call `connect()` first.")

        transport_error: UserError | None = None
        transport_cause: Exception | None = None
        try:
            self._validate_required_parameters(tool_name=tool_name, arguments=arguments)
            retries_used = 0
            # `retries_used` measures the retry budget, not elapsed backoffs: it is
            # deliberately not advanced while `max_retry_attempts` is -1, and a single
            # isolated-session retry charges it twice. Count backoffs separately so the
            # delay follows the configured schedule in both cases.
            backoffs_taken = 0
            first_attempt = True
            while True:
                if not first_attempt and self.max_retry_attempts != -1:
                    retries_used += 1
                allow_isolated_retry = (
                    self.max_retry_attempts == -1 or retries_used < self.max_retry_attempts
                )
                try:
                    result, used_isolated_retry = await self._call_tool_with_isolated_retry(
                        tool_name,
                        arguments,
                        meta,
                        allow_isolated_retry=allow_isolated_retry,
                    )
                    if used_isolated_retry and self.max_retry_attempts != -1:
                        retries_used += 1
                    return result
                except _IsolatedSessionRetryFailed as exc:
                    retries_used += 1
                    if self.max_retry_attempts != -1 and retries_used >= self.max_retry_attempts:
                        if exc.__cause__ is not None:
                            raise exc.__cause__ from exc
                        raise
                    backoff = self.retry_backoff_seconds_base * (2**backoffs_taken)
                    backoffs_taken += 1
                    await asyncio.sleep(backoff)
                except Exception:
                    if self.max_retry_attempts != -1 and retries_used >= self.max_retry_attempts:
                        raise
                    backoff = self.retry_backoff_seconds_base * (2**backoffs_taken)
                    backoffs_taken += 1
                    await asyncio.sleep(backoff)
                first_attempt = False
        except HTTP_STATUS_ERROR_TYPES as e:
            status_code = http_status_code(e)
            transport_error = UserError(
                f"Failed to call tool '{tool_name}' on MCP server '{self._error_name}': "
                f"HTTP error {status_code}"
            )
            transport_cause = _safe_transport_cause(e)
        except HTTP_REQUEST_ERROR_TYPES as e:
            transport_cause = _safe_transport_cause(e)
            if transport_cause is not None and not is_http_connect_error(e):
                raise
            if is_http_connect_error(e):
                transport_error = UserError(
                    f"Failed to call tool '{tool_name}' on MCP server '{self._error_name}': "
                    "Connection lost. The server may have disconnected."
                )
            elif is_http_timeout_error(e):
                transport_error = UserError(
                    f"Failed to call tool '{tool_name}' on MCP server '{self._error_name}': "
                    "Connection timeout."
                )
            else:
                transport_error = UserError(
                    f"Failed to call tool '{tool_name}' on MCP server '{self._error_name}': "
                    "Request failed."
                )
        except BaseExceptionGroup as e:
            http_errors = self._extract_http_errors_from_exception(e)
            if not http_errors:
                raise

            unsafe_http_error = _first_unretainable_transport_error(http_errors)
            http_error = unsafe_http_error or http_errors[0]
            transport_cause = _safe_transport_cause(http_error)
            if is_http_status_error(http_error):
                status_code = http_status_code(http_error)
                transport_error = UserError(
                    f"Failed to call tool '{tool_name}' on MCP server '{self._error_name}': "
                    f"HTTP error {status_code}"
                )
            elif is_http_connect_error(http_error):
                transport_error = UserError(
                    f"Failed to call tool '{tool_name}' on MCP server '{self._error_name}': "
                    "Connection lost. The server may have disconnected."
                )
            elif is_http_timeout_error(http_error):
                transport_error = UserError(
                    f"Failed to call tool '{tool_name}' on MCP server '{self._error_name}': "
                    "Connection timeout."
                )
            elif is_http_request_error(http_error):
                if transport_cause is not None:
                    raise
                transport_error = UserError(
                    f"Failed to call tool '{tool_name}' on MCP server '{self._error_name}': "
                    "Request failed."
                )
            else:
                raise
            if transport_cause is None:
                http_errors.clear()
                del http_error
                del unsafe_http_error

        assert transport_error is not None
        self._raise_mapped_transport_error(transport_error, transport_cause)

    @property
    def name(self) -> str:
        """A readable name for the server."""
        return self._name

    @property
    def session_id(self) -> str | None:
        """The legacy MCP session ID assigned by the server, if one is available.

        MCP 2026-07-28 does not use protocol sessions, so this property returns None for a
        modern connection. It also returns None before connection or when a legacy server does
        not issue a session ID. A legacy session ID is stable for this instance's connection and
        can be passed through the Mcp-Session-Id request header when reconnecting to a server that
        supports legacy session resumption.

        Example::

            async with MCPServerStreamableHttp(params={"url": url}) as server:
                session_id = server.session_id

            # In a new worker / process:
            async with MCPServerStreamableHttp(
                params={"url": url, "headers": {"Mcp-Session-Id": session_id}}
            ) as server:
                # Resumes the same server-side session.
                ...
        """
        if self._get_session_id is None:
            return None
        return self._get_session_id()

name property

name: str

A readable name for the server.

session_id property

session_id: str | None

The legacy MCP session ID assigned by the server, if one is available.

MCP 2026-07-28 does not use protocol sessions, so this property returns None for a modern connection. It also returns None before connection or when a legacy server does not issue a session ID. A legacy session ID is stable for this instance's connection and can be passed through the Mcp-Session-Id request header when reconnecting to a server that supports legacy session resumption.

Example::

async with MCPServerStreamableHttp(params={"url": url}) as server:
    session_id = server.session_id

# In a new worker / process:
async with MCPServerStreamableHttp(
    params={"url": url, "headers": {"Mcp-Session-Id": session_id}}
) as server:
    # Resumes the same server-side session.
    ...

__init__

__init__(
    params: MCPServerStreamableHttpParams,
    cache_tools_list: bool = False,
    name: str | None = None,
    client_session_timeout_seconds: float | None = 5,
    tool_filter: ToolFilter = None,
    use_structured_content: bool = False,
    max_retry_attempts: int = 0,
    retry_backoff_seconds_base: float = 1.0,
    message_handler: MessageHandlerFnT | None = None,
    require_approval: RequireApprovalSetting = None,
    failure_error_function: ToolErrorFunction
    | None
    | _UnsetType = _UNSET,
    tool_meta_resolver: MCPToolMetaResolver | None = None,
    custom_data_extractor: MCPToolCustomDataExtractor
    | None = None,
)

Create a new MCP server based on the Streamable HTTP transport.

Parameters:

Name Type Description Default
params MCPServerStreamableHttpParams

The params that configure the server. This includes the URL of the server, the headers to send to the server, the timeout for the HTTP request, the timeout for the Streamable HTTP connection, whether we need to terminate on close, and an optional custom HTTP client factory.

required
cache_tools_list bool

Whether to cache the tools list. If True, the tools list will be cached and only fetched from the server once. If False, the tools list will be fetched from the server on each call to list_tools(). The cache can be invalidated by calling invalidate_tools_cache(). You should set this to True if you know the server will not change its tools list, because it can drastically improve latency (by avoiding a round-trip to the server every time).

False
name str | None

A readable name for the server. If not provided, we'll create one from the URL.

None
client_session_timeout_seconds float | None

The MCP ClientSession read timeout. Positive finite values representable by datetime.timedelta and at least one microsecond set a timeout; None and 0 disable it. Other values are rejected during server construction.

5
tool_filter ToolFilter

The tool filter to use for filtering tools.

None
use_structured_content bool

Whether to use tool_result.structured_content when calling an MCP tool. Defaults to False for backwards compatibility - most MCP servers still include the structured content in the tool_result.content, and using it by default will cause duplicate content. You can set this to True if you know the server will not duplicate the structured content in the tool_result.content.

False
max_retry_attempts int

Number of times to retry failed list_tools/call_tool calls. Defaults to no retries.

0
retry_backoff_seconds_base float

The base delay, in seconds, for exponential backoff between retries.

1.0
message_handler MessageHandlerFnT | None

Optional handler invoked for session messages as delivered by the ClientSession.

None
require_approval RequireApprovalSetting

Approval policy for tools on this server. Accepts "always"/"never", a dict of tool names to those values, or an object with always/never tool lists.

None
failure_error_function ToolErrorFunction | None | _UnsetType

Optional function used to convert MCP tool failures into a model-visible error message. If explicitly set to None, tool errors will be raised instead of converted. If left unset, the agent-level configuration (or SDK default) will be used.

_UNSET
tool_meta_resolver MCPToolMetaResolver | None

Optional callable that produces MCP request metadata (_meta) for tool calls. It is invoked by the Agents SDK before calling call_tool.

None
custom_data_extractor MCPToolCustomDataExtractor | None

Optional callable that produces SDK-only custom data for emitted MCP tool output items.

None
Source code in src/agents/mcp/server.py
def __init__(
    self,
    params: MCPServerStreamableHttpParams,
    cache_tools_list: bool = False,
    name: str | None = None,
    client_session_timeout_seconds: float | None = 5,
    tool_filter: ToolFilter = None,
    use_structured_content: bool = False,
    max_retry_attempts: int = 0,
    retry_backoff_seconds_base: float = 1.0,
    message_handler: MessageHandlerFnT | None = None,
    require_approval: RequireApprovalSetting = None,
    failure_error_function: ToolErrorFunction | None | _UnsetType = _UNSET,
    tool_meta_resolver: MCPToolMetaResolver | None = None,
    custom_data_extractor: MCPToolCustomDataExtractor | None = None,
):
    """Create a new MCP server based on the Streamable HTTP transport.

    Args:
        params: The params that configure the server. This includes the URL of the server,
            the headers to send to the server, the timeout for the HTTP request, the
            timeout for the Streamable HTTP connection, whether we need to
            terminate on close, and an optional custom HTTP client factory.

        cache_tools_list: Whether to cache the tools list. If `True`, the tools list will be
            cached and only fetched from the server once. If `False`, the tools list will be
            fetched from the server on each call to `list_tools()`. The cache can be
            invalidated by calling `invalidate_tools_cache()`. You should set this to `True`
            if you know the server will not change its tools list, because it can drastically
            improve latency (by avoiding a round-trip to the server every time).

        name: A readable name for the server. If not provided, we'll create one from the
            URL.

        client_session_timeout_seconds: The MCP ClientSession read timeout. Positive finite
            values representable by `datetime.timedelta` and at least one microsecond set a
            timeout; `None` and `0` disable it. Other values are rejected during server
            construction.
        tool_filter: The tool filter to use for filtering tools.
        use_structured_content: Whether to use `tool_result.structured_content` when calling an
            MCP tool. Defaults to False for backwards compatibility - most MCP servers still
            include the structured content in the `tool_result.content`, and using it by
            default will cause duplicate content. You can set this to True if you know the
            server will not duplicate the structured content in the `tool_result.content`.
        max_retry_attempts: Number of times to retry failed list_tools/call_tool calls.
            Defaults to no retries.
        retry_backoff_seconds_base: The base delay, in seconds, for exponential
            backoff between retries.
        message_handler: Optional handler invoked for session messages as delivered by the
            ClientSession.
        require_approval: Approval policy for tools on this server. Accepts "always"/"never",
            a dict of tool names to those values, or an object with always/never tool lists.
        failure_error_function: Optional function used to convert MCP tool failures into
            a model-visible error message. If explicitly set to None, tool errors will be
            raised instead of converted. If left unset, the agent-level configuration (or
            SDK default) will be used.
        tool_meta_resolver: Optional callable that produces MCP request metadata (`_meta`) for
            tool calls. It is invoked by the Agents SDK before calling `call_tool`.
        custom_data_extractor: Optional callable that produces SDK-only custom data for
            emitted MCP tool output items.
    """
    super().__init__(
        cache_tools_list=cache_tools_list,
        client_session_timeout_seconds=client_session_timeout_seconds,
        tool_filter=tool_filter,
        use_structured_content=use_structured_content,
        max_retry_attempts=max_retry_attempts,
        retry_backoff_seconds_base=retry_backoff_seconds_base,
        message_handler=message_handler,
        require_approval=require_approval,
        failure_error_function=failure_error_function,
        tool_meta_resolver=tool_meta_resolver,
        custom_data_extractor=custom_data_extractor,
    )

    self.params = params
    self._name = name or f"streamable_http: {self.params['url']}"
    self._serialize_session_requests = True

create_streams

create_streams() -> AbstractAsyncContextManager[
    MCPStreamTransport
]

Create the streams for the server.

Source code in src/agents/mcp/server.py
def create_streams(
    self,
) -> AbstractAsyncContextManager[MCPStreamTransport]:
    """Create the streams for the server."""
    kwargs: dict[str, Any] = {
        "url": self.params["url"],
        "headers": self.params.get("headers", None),
        "timeout": self.params.get("timeout", 5),
        "sse_read_timeout": self.params.get("sse_read_timeout", 60 * 5),
        "terminate_on_close": self.params.get("terminate_on_close", True),
    }
    httpx_client_factory = self.params.get("httpx_client_factory")
    if MCP_V2:
        if self.params.get("ignore_initialized_notification_failure", False):
            raise UserError(
                "ignore_initialized_notification_failure is not supported with MCP Python "
                "SDK v2 because its public transport API does not expose initialized-"
                "notification failures. Leave it disabled or pin mcp<2."
            )
        _validate_v2_http_auth(self.params.get("auth"))
        on_session_id: Callable[[str], None] | None = None
        if self.session is None:
            self._v2_session_id = None

            def capture_session_id(session_id: str) -> None:
                self._v2_session_id = session_id

            on_session_id = capture_session_id
            self._get_session_id = lambda: self._v2_session_id
        return _streamablehttp_client_v2(
            **kwargs,
            httpx_client_factory=httpx_client_factory or _create_default_streamable_http_client,
            auth=self.params.get("auth"),
            on_session_id=on_session_id,
        )

    if self.params.get("ignore_initialized_notification_failure", False):
        return _streamablehttp_client_with_transport(
            **kwargs,
            httpx_client_factory=httpx_client_factory or _create_default_streamable_http_client,
            auth=self.params.get("auth"),
            transport_factory=_InitializedNotificationTolerantStreamableHTTPTransport,
        )
    kwargs["httpx_client_factory"] = (
        httpx_client_factory or _create_default_streamable_http_client
    )
    if "auth" in self.params:
        kwargs["auth"] = self.params["auth"]
    return cast(
        AbstractAsyncContextManager[MCPStreamTransport],
        _require_streamablehttp_client_v1()(**kwargs),
    )

connect async

connect()

Connect to the server.

Source code in src/agents/mcp/server.py
async def connect(self):
    """Connect to the server."""
    read_timeout = _client_session_read_timeout(self.client_session_timeout_seconds)
    connection_succeeded = False
    connection_error: UserError | None = None
    connection_cause: Exception | None = None
    connection_exception: BaseException | None = None
    cleanup_failure: BaseException | None = None
    try:
        if MCP_V2:
            session = await self.exit_stack.enter_async_context(
                self._client_session_context(read_timeout)
            )
            self.server_initialize_result = getattr(session, "initialize_result", None)
        else:
            v1_read_timeout = cast(timedelta | None, read_timeout)
            transport = await self.exit_stack.enter_async_context(self.create_streams())
            read, write, *rest = transport
            self._get_session_id = rest[0] if rest and callable(rest[0]) else None
            session = await self.exit_stack.enter_async_context(
                cast(Any, ClientSession)(
                    read,
                    write,
                    v1_read_timeout,
                    message_handler=self.message_handler,
                )
            )
            self.server_initialize_result = await session.initialize()
        self.session = session
        connection_succeeded = True
    except BaseException as e:
        if not isinstance(e, Exception):
            connection_exception = e
        else:
            http_errors = self._extract_http_errors_from_exception(e)
            if not http_errors:
                connection_exception = e
            else:
                unsafe_http_error = _first_unretainable_transport_error(http_errors)
                http_error = unsafe_http_error or http_errors[0]
                connection_cause = _safe_transport_cause(http_error)
                maps_safe_error = (
                    is_http_status_error(http_error)
                    or is_http_connect_error(http_error)
                    or is_http_timeout_error(http_error)
                )
                if connection_cause is not None and not maps_safe_error:
                    connection_exception = e
                    connection_cause = None
                else:
                    connection_error = self._user_error_for_http_error(http_error)
                http_errors.clear()
                del http_error
                del unsafe_http_error

    # Run cleanup after leaving the connection exception handler so a cleanup UserError does
    # not retain the pending connection failure as its implicit context.
    if not connection_succeeded:
        try:
            await self.cleanup()
        except UserError as e:
            cleanup_failure = e
        except Exception as cleanup_error:
            # Suppress RuntimeError about cancel scopes during cleanup - this is a known
            # issue with the MCP library's async generator cleanup and shouldn't mask the
            # original error.
            if isinstance(cleanup_error, RuntimeError) and "cancel scope" in str(cleanup_error):
                logger.debug(
                    "%s",
                    get_mcp_server_log_message(
                        "Ignoring cancel scope error during cleanup of MCP server", self
                    ),
                    stacklevel=2,
                )
            else:
                # Log other cleanup errors but don't raise - original error is more important.
                logger.warning(
                    "%s",
                    get_mcp_server_log_message("Error during cleanup of MCP server", self),
                    stacklevel=2,
                )
        except BaseException as e:
            cleanup_failure = e

    if cleanup_failure is not None:
        connection_exception = None
        connection_error = None
        connection_cause = None
        if isinstance(cleanup_failure, UserError):
            self._raise_mapped_transport_error(cleanup_failure, None)
        raise cleanup_failure

    if connection_exception is not None:
        raise connection_exception

    if connection_error is not None:
        self._raise_mapped_transport_error(connection_error, connection_cause)

cleanup async

cleanup()

Cleanup the server.

Source code in src/agents/mcp/server.py
async def cleanup(self):
    """Cleanup the server."""
    async with self._cleanup_lock:
        # Only raise HTTP errors if we're cleaning up after a failed connection.
        # During normal teardown (via __aexit__), log but don't raise to avoid
        # masking the original exception.
        is_failed_connection_cleanup = self.session is None
        cleanup_error: UserError | None = None

        try:
            await self.exit_stack.aclose()
        except asyncio.CancelledError as e:
            log_tool_action_debug(
                logger,
                get_mcp_server_log_message("Cleanup cancelled for MCP server", self),
                e,
            )
            raise
        except (  # type: ignore[misc]
            BaseExceptionGroup,
            *HTTP_STATUS_ERROR_TYPES,
            *HTTP_REQUEST_ERROR_TYPES,
        ) as e:
            selected_http_error = self._select_cleanup_transport_error(e)
            if selected_http_error is not None:
                if is_failed_connection_cleanup:
                    cleanup_error = self._user_error_for_http_error(
                        selected_http_error,
                        include_http_reason_phrase=False,
                    )
                    del selected_http_error
                else:
                    _log_cleanup_transport_warning(
                        get_mcp_server_log_message(
                            _get_cleanup_transport_error_message(selected_http_error), self
                        )
                    )
            elif is_http_request_error(e):
                _log_cleanup_transport_warning(
                    get_mcp_server_log_message(_get_cleanup_transport_error_message(e), self)
                )
            elif isinstance(e, BaseExceptionGroup):
                http_errors = self._extract_http_errors_from_exception(e)
                if http_errors:
                    safe_error_group = _credential_safe_exception_group(e)
                    log_tool_action_error(
                        logger,
                        get_mcp_server_log_message("Error cleaning up MCP server", self),
                        safe_error_group,
                    )
                else:
                    # No HTTP error found, suppress RuntimeError about cancel scopes.
                    has_cancel_scope_error = any(
                        isinstance(exc, RuntimeError) and "cancel scope" in str(exc)
                        for exc in e.exceptions
                    )
                    if has_cancel_scope_error:
                        log_tool_action_debug(
                            logger,
                            get_mcp_server_log_message(
                                "Ignoring cancel scope error during cleanup of MCP server", self
                            ),
                            e,
                        )
                    else:
                        log_tool_action_error(
                            logger,
                            get_mcp_server_log_message("Error cleaning up MCP server", self),
                            e,
                        )
            else:
                log_tool_action_error(
                    logger,
                    get_mcp_server_log_message("Error cleaning up MCP server", self),
                    e,
                )
        except Exception as e:
            # Suppress RuntimeError about cancel scopes - this is a known issue with the MCP
            # library when background tasks fail during async generator cleanup
            if isinstance(e, RuntimeError) and "cancel scope" in str(e):
                log_tool_action_debug(
                    logger,
                    get_mcp_server_log_message(
                        "Ignoring cancel scope error during cleanup of MCP server", self
                    ),
                    e,
                )
            else:
                log_tool_action_error(
                    logger,
                    get_mcp_server_log_message("Error cleaning up MCP server", self),
                    e,
                )
        finally:
            self.session = None
            self._get_session_id = None
            self._v2_session_id = None

        if cleanup_error is not None:
            self._raise_mapped_transport_error(cleanup_error, None)

list_tools async

list_tools(
    run_context: RunContextWrapper[Any] | None = None,
    agent: AgentBase | None = None,
) -> list[Tool]

List the tools available on the server.

Source code in src/agents/mcp/server.py
async def list_tools(
    self,
    run_context: RunContextWrapper[Any] | None = None,
    agent: AgentBase | None = None,
) -> list[MCPTool]:
    """List the tools available on the server."""
    if not self.session:
        raise UserError("Server not initialized. Make sure you call `connect()` first.")
    session = self.session
    assert session is not None

    transport_error: UserError | None = None
    transport_cause: Exception | None = None
    try:
        tools: list[MCPTool]
        # Return from cache if caching is enabled, we have tools, and the cache is not dirty
        if self.cache_tools_list and not self._cache_dirty and self._tools_list:
            tools = self._tools_list
        else:
            tools = []
            cursor: str | None = None
            seen_cursors: set[str | None] = set()

            async def fetch_pages() -> bool:
                nonlocal cursor
                while True:
                    result = await self._list_tools_page(session, cursor)
                    tools.extend(result.tools)
                    seen_cursors.add(cursor)
                    next_cursor = result_next_cursor(result)
                    if next_cursor is None:
                        return True
                    if next_cursor in seen_cursors:
                        return False
                    cursor = next_cursor

            pagination_complete = False
            pagination_failure: BaseException | None = None
            try:
                pagination_complete = await self._run_with_retries(fetch_pages)
            except BaseException as error:
                if cursor is None:
                    raise
                if isinstance(error, BaseExceptionGroup):
                    pagination_failure = _credential_safe_exception_group(error)
                elif isinstance(error, Exception):
                    pagination_failure = self._user_error_for_request_operation(
                        "list tools", error
                    )
                else:
                    pagination_failure = _credential_safe_exception_leaf(error)

            if pagination_failure is not None or not pagination_complete:
                cursor = None
                seen_cursors.clear()
                tools.clear()
                del fetch_pages
                if pagination_failure is not None:
                    raise pagination_failure from None
                raise UserError(
                    f"MCP server '{self._error_name}' returned a repeated cursor while "
                    "listing tools."
                ) from None

            cursor = None
            seen_cursors.clear()
            del fetch_pages
            self._tools_list = tools
            self._cache_dirty = False

        # Filter tools based on tool_filter
        filtered_tools = tools
        if self.tool_filter is not None:
            filtered_tools = await self._apply_tool_filter(filtered_tools, run_context, agent)
        return filtered_tools
    except HTTP_STATUS_ERROR_TYPES as e:
        status_code = http_status_code(e)
        transport_error = UserError(
            f"Failed to list tools from MCP server '{self._error_name}': "
            f"HTTP error {status_code}"
        )
        transport_cause = _safe_transport_cause(e)
    except HTTP_REQUEST_ERROR_TYPES as e:
        transport_cause = _safe_transport_cause(e)
        if transport_cause is not None and not is_http_connect_error(e):
            raise
        if is_http_connect_error(e):
            transport_error = UserError(
                f"Failed to list tools from MCP server '{self._error_name}': Connection lost. "
                f"The server may have disconnected."
            )
        elif is_http_timeout_error(e):
            transport_error = UserError(
                f"Failed to list tools from MCP server '{self._error_name}': "
                "Connection timeout."
            )
        else:
            transport_error = UserError(
                f"Failed to list tools from MCP server '{self._error_name}': Request failed."
            )

    assert transport_error is not None
    self._raise_mapped_transport_error(transport_error, transport_cause)

list_prompts async

list_prompts() -> ListPromptsResult

List the prompts available on the server.

Source code in src/agents/mcp/server.py
async def list_prompts(
    self,
) -> ListPromptsResult:
    """List the prompts available on the server."""
    if not self.session:
        raise UserError("Server not initialized. Make sure you call `connect()` first.")
    session = self.session
    assert session is not None
    result = await self._list_prompts_page(session)
    if result_next_cursor(result) is None:
        return result

    prompts = list(result.prompts)
    cursor: str | None = result_next_cursor(result)
    seen_cursors: set[str | None] = {None}
    pagination_failure: BaseException | None = None
    repeated_cursor = False
    page: ListPromptsResult | None = None
    next_cursor: str | None = None
    while cursor is not None:
        try:
            page = await self._list_prompts_page(session, cursor)
        except BaseException as error:
            if isinstance(error, BaseExceptionGroup):
                pagination_failure = _credential_safe_exception_group(error)
            elif isinstance(error, Exception):
                pagination_failure = self._user_error_for_request_operation(
                    "list prompts", error
                )
            else:
                pagination_failure = _credential_safe_exception_leaf(error)
            break
        prompts.extend(page.prompts)
        seen_cursors.add(cursor)
        next_cursor = result_next_cursor(page)
        if next_cursor is not None and next_cursor in seen_cursors:
            repeated_cursor = True
            break
        cursor = next_cursor

    if pagination_failure is not None or repeated_cursor:
        cursor = None
        seen_cursors.clear()
        prompts.clear()
        page = None
        next_cursor = None
        del result
        if pagination_failure is not None:
            raise pagination_failure from None
        raise UserError(
            f"MCP server '{self._error_name}' returned a repeated cursor while listing prompts."
        ) from None

    return cast(ListPromptsResult, clear_result_next_cursor(result, prompts=prompts))

get_prompt async

get_prompt(
    name: str, arguments: dict[str, Any] | None = None
) -> GetPromptResult

Get a specific prompt from the server.

Source code in src/agents/mcp/server.py
async def get_prompt(
    self, name: str, arguments: dict[str, Any] | None = None
) -> GetPromptResult:
    """Get a specific prompt from the server."""
    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 self._run_request_with_transport_error_redaction(
        "get prompt",
        lambda: self._maybe_serialize_request(lambda: session.get_prompt(name, arguments)),
    )

list_resources async

list_resources(
    cursor: str | None = None,
) -> ListResourcesResult

List the resources available on the server.

Source code in src/agents/mcp/server.py
async def list_resources(self, cursor: str | None = None) -> ListResourcesResult:
    """List the resources available on the server."""
    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 self._run_request_with_transport_error_redaction(
        "list resources",
        lambda: self._maybe_serialize_request(
            lambda: (
                session.list_resources()
                if cursor is None
                else session.list_resources(params=PaginatedRequestParams(cursor=cursor))
            )
            if MCP_V2
            else cast(Any, session).list_resources(cursor)
        ),
    )

list_resource_templates async

list_resource_templates(
    cursor: str | None = None,
) -> ListResourceTemplatesResult

List the resource templates available on the server.

Source code in src/agents/mcp/server.py
async def list_resource_templates(
    self, cursor: str | None = None
) -> ListResourceTemplatesResult:
    """List the resource templates available on the server."""
    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 self._run_request_with_transport_error_redaction(
        "list resource templates",
        lambda: self._maybe_serialize_request(
            lambda: (
                session.list_resource_templates()
                if cursor is None
                else session.list_resource_templates(
                    params=PaginatedRequestParams(cursor=cursor)
                )
            )
            if MCP_V2
            else cast(Any, session).list_resource_templates(cursor)
        ),
    )

read_resource async

read_resource(uri: str) -> ReadResourceResult

Read the contents of a specific resource by URI.

Parameters:

Name Type Description Default
uri str

The URI of the resource to read. See :class:~pydantic.networks.AnyUrl for the supported URI formats.

required
Source code in src/agents/mcp/server.py
async def read_resource(self, uri: str) -> ReadResourceResult:
    """Read the contents of a specific resource by URI.

    Args:
        uri: The URI of the resource to read. See :class:`~pydantic.networks.AnyUrl`
            for the supported URI formats.
    """
    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 self._run_request_with_transport_error_redaction(
        "read resource",
        lambda: self._maybe_serialize_request(
            lambda: cast(Any, session).read_resource(resource_uri(uri))
        ),
    )

invalidate_tools_cache

invalidate_tools_cache()

Invalidate the tools cache.

Source code in src/agents/mcp/server.py
def invalidate_tools_cache(self):
    """Invalidate the tools cache."""
    self._cache_dirty = True