Skip to content

MCP Servers

MCPServer

Bases: ABC

Base class for Model Context Protocol servers.

Source code in src/cai/sdk/agents/mcp/server.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class MCPServer(abc.ABC):
    """Base class for Model Context Protocol servers."""

    @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

    @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) -> 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) -> CallToolResult:
        """Invoke a tool on the server."""
        pass

name abstractmethod property

name: str

A readable name for the server.

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/cai/sdk/agents/mcp/server.py
24
25
26
27
28
29
30
@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/cai/sdk/agents/mcp/server.py
38
39
40
41
42
43
@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() -> list[Tool]

List the tools available on the server.

Source code in src/cai/sdk/agents/mcp/server.py
45
46
47
48
@abc.abstractmethod
async def list_tools(self) -> list[MCPTool]:
    """List the tools available on the server."""
    pass

call_tool abstractmethod async

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

Invoke a tool on the server.

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

MCPServerStdioParams

Bases: TypedDict

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

Source code in src/cai/sdk/agents/mcp/server.py
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
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/cai/sdk/agents/mcp/server.py
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
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,
    ):
        """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.
        """
        super().__init__(cache_tools_list)

        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[
        tuple[
            MemoryObjectReceiveStream[JSONRPCMessage | Exception],
            MemoryObjectSendStream[JSONRPCMessage],
        ]
    ]:
        """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,
)

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
Source code in src/cai/sdk/agents/mcp/server.py
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
def __init__(
    self,
    params: MCPServerStdioParams,
    cache_tools_list: bool = False,
    name: str | 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.
    """
    super().__init__(cache_tools_list)

    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[
    tuple[
        MemoryObjectReceiveStream[
            JSONRPCMessage | Exception
        ],
        MemoryObjectSendStream[JSONRPCMessage],
    ]
]

Create the streams for the server.

Source code in src/cai/sdk/agents/mcp/server.py
232
233
234
235
236
237
238
239
240
241
def create_streams(
    self,
) -> AbstractAsyncContextManager[
    tuple[
        MemoryObjectReceiveStream[JSONRPCMessage | Exception],
        MemoryObjectSendStream[JSONRPCMessage],
    ]
]:
    """Create the streams for the server."""
    return stdio_client(self.params)

connect async

connect()

Connect to the server.

Source code in src/cai/sdk/agents/mcp/server.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
async def connect(self):
    """Connect to the server."""
    try:
        transport = await self.exit_stack.enter_async_context(self.create_streams())
        read, write = transport
        session = await self.exit_stack.enter_async_context(ClientSession(read, write))
        await session.initialize()
        self.session = session
    except Exception as e:
        # Only log connection errors at debug level
        error_str = str(e).lower()
        error_type = type(e).__name__
        if ("connection" in error_str or 
            "refused" in error_str or 
            "taskgroup" in error_str or
            error_type == "ExceptionGroup"):
            logger.debug(f"Expected connection error during MCP server init: {e}")
        else:
            logger.error(f"Error initializing MCP server: {e}")
        await self.cleanup()
        raise

cleanup async

cleanup()

Cleanup the server.

Source code in src/cai/sdk/agents/mcp/server.py
146
147
148
149
150
151
152
153
154
155
156
157
158
159
async def cleanup(self):
    """Cleanup the server."""
    async with self._cleanup_lock:
        try:
            # Suppress async generator warnings during cleanup
            with warnings.catch_warnings():
                warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*asynchronous generator.*")
                warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*was never awaited.*")
                await self.exit_stack.aclose()
                self.session = None
        except Exception as e:
            # Only log errors that aren't expected during cleanup
            if "ClosedResourceError" not in str(e) and "async generator" not in str(e).lower():
                logger.debug(f"Expected cleanup error (can be ignored): {e}")

list_tools async

list_tools() -> list[Tool]

List the tools available on the server.

Source code in src/cai/sdk/agents/mcp/server.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
async def list_tools(self) -> list[MCPTool]:
    """List the tools available on the server."""
    if not self.session:
        raise UserError("Server not initialized. Make sure you call `connect()` first.")

    # 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:
        return self._tools_list

    # Reset the cache dirty to False
    self._cache_dirty = False

    # Fetch the tools from the server
    self._tools_list = (await self.session.list_tools()).tools
    return self._tools_list

call_tool async

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

Invoke a tool on the server.

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

    return await self.session.call_tool(tool_name, arguments)

invalidate_tools_cache

invalidate_tools_cache()

Invalidate the tools cache.

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

MCPServerSseParams

Bases: TypedDict

Mirrors the params inmcp.client.sse.sse_client.

Source code in src/cai/sdk/agents/mcp/server.py
249
250
251
252
253
254
255
256
257
258
259
260
261
262
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."""

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.

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/cai/sdk/agents/mcp/server.py
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
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,
    ):
        """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.
        """
        super().__init__(cache_tools_list)

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

    def create_streams(
        self,
    ) -> AbstractAsyncContextManager[
        tuple[
            MemoryObjectReceiveStream[JSONRPCMessage | Exception],
            MemoryObjectSendStream[JSONRPCMessage],
        ]
    ]:
        """Create the streams for the server."""
        return sse_client(
            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),
        )

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

    async def cleanup(self):
        """Cleanup the SSE server with special handling for async generators."""
        import warnings
        import asyncio

        async with self._cleanup_lock:
            try:
                # For SSE servers, we need to handle cleanup more carefully
                with warnings.catch_warnings():
                    warnings.filterwarnings("ignore", category=RuntimeWarning)
                    warnings.filterwarnings("ignore", message=".*asynchronous generator.*")
                    warnings.filterwarnings("ignore", message=".*didn't stop after athrow.*")
                    warnings.filterwarnings("ignore", message=".*cancel scope.*")

                    # Try to close gracefully with a short timeout
                    try:
                        await asyncio.wait_for(self.exit_stack.aclose(), timeout=0.5)
                    except asyncio.TimeoutError:
                        # Expected for SSE connections
                        pass
                    except Exception:
                        # Ignore other cleanup errors for SSE
                        pass

                    self.session = None
            except Exception:
                # Silently ignore all cleanup errors for SSE
                pass

    async def cleanup(self):
        """Cleanup the SSE server with special handling for async generators."""
        async with self._cleanup_lock:
            try:
                # For SSE connections, we need to handle cleanup differently
                # to avoid async generator warnings
                with warnings.catch_warnings():
                    warnings.filterwarnings("ignore", category=RuntimeWarning)
                    warnings.filterwarnings("ignore", message=".*asynchronous generator.*")
                    warnings.filterwarnings("ignore", message=".*was never awaited.*")

                    # Try a quick cleanup with a short timeout
                    try:
                        await asyncio.wait_for(self.exit_stack.aclose(), timeout=0.5)
                    except asyncio.TimeoutError:
                        # Expected for SSE connections
                        pass
                    except Exception:
                        # Ignore any other errors during SSE cleanup
                        pass
                    finally:
                        self.session = None
            except Exception:
                # Silently ignore all errors for SSE cleanup
                pass

name property

name: str

A readable name for the server.

__init__

__init__(
    params: MCPServerSseParams,
    cache_tools_list: bool = False,
    name: str | 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
Source code in src/cai/sdk/agents/mcp/server.py
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
def __init__(
    self,
    params: MCPServerSseParams,
    cache_tools_list: bool = False,
    name: str | 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.
    """
    super().__init__(cache_tools_list)

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

create_streams

create_streams() -> AbstractAsyncContextManager[
    tuple[
        MemoryObjectReceiveStream[
            JSONRPCMessage | Exception
        ],
        MemoryObjectSendStream[JSONRPCMessage],
    ]
]

Create the streams for the server.

Source code in src/cai/sdk/agents/mcp/server.py
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
def create_streams(
    self,
) -> AbstractAsyncContextManager[
    tuple[
        MemoryObjectReceiveStream[JSONRPCMessage | Exception],
        MemoryObjectSendStream[JSONRPCMessage],
    ]
]:
    """Create the streams for the server."""
    return sse_client(
        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),
    )

cleanup async

cleanup()

Cleanup the SSE server with special handling for async generators.

Source code in src/cai/sdk/agents/mcp/server.py
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
async def cleanup(self):
    """Cleanup the SSE server with special handling for async generators."""
    async with self._cleanup_lock:
        try:
            # For SSE connections, we need to handle cleanup differently
            # to avoid async generator warnings
            with warnings.catch_warnings():
                warnings.filterwarnings("ignore", category=RuntimeWarning)
                warnings.filterwarnings("ignore", message=".*asynchronous generator.*")
                warnings.filterwarnings("ignore", message=".*was never awaited.*")

                # Try a quick cleanup with a short timeout
                try:
                    await asyncio.wait_for(self.exit_stack.aclose(), timeout=0.5)
                except asyncio.TimeoutError:
                    # Expected for SSE connections
                    pass
                except Exception:
                    # Ignore any other errors during SSE cleanup
                    pass
                finally:
                    self.session = None
        except Exception:
            # Silently ignore all errors for SSE cleanup
            pass

connect async

connect()

Connect to the server.

Source code in src/cai/sdk/agents/mcp/server.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
async def connect(self):
    """Connect to the server."""
    try:
        transport = await self.exit_stack.enter_async_context(self.create_streams())
        read, write = transport
        session = await self.exit_stack.enter_async_context(ClientSession(read, write))
        await session.initialize()
        self.session = session
    except Exception as e:
        # Only log connection errors at debug level
        error_str = str(e).lower()
        error_type = type(e).__name__
        if ("connection" in error_str or 
            "refused" in error_str or 
            "taskgroup" in error_str or
            error_type == "ExceptionGroup"):
            logger.debug(f"Expected connection error during MCP server init: {e}")
        else:
            logger.error(f"Error initializing MCP server: {e}")
        await self.cleanup()
        raise

list_tools async

list_tools() -> list[Tool]

List the tools available on the server.

Source code in src/cai/sdk/agents/mcp/server.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
async def list_tools(self) -> list[MCPTool]:
    """List the tools available on the server."""
    if not self.session:
        raise UserError("Server not initialized. Make sure you call `connect()` first.")

    # 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:
        return self._tools_list

    # Reset the cache dirty to False
    self._cache_dirty = False

    # Fetch the tools from the server
    self._tools_list = (await self.session.list_tools()).tools
    return self._tools_list

call_tool async

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

Invoke a tool on the server.

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

    return await self.session.call_tool(tool_name, arguments)

invalidate_tools_cache

invalidate_tools_cache()

Invalidate the tools cache.

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