William Easton commited on
Commit
17763eb
·
unverified ·
1 Parent(s): ba340db

Improve stdio and mcp_config clean-up (#1444)

Browse files
docs/python-sdk/fastmcp-client-transports.mdx CHANGED
@@ -7,7 +7,7 @@ sidebarTitle: transports
7
 
8
  ## Functions
9
 
10
- ### `infer_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L903" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
11
 
12
  ```python
13
  infer_transport(transport: ClientTransport | FastMCP | FastMCP1Server | AnyUrl | Path | MCPConfig | dict[str, Any] | str) -> ClientTransport
@@ -167,55 +167,55 @@ connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[
167
  connect(self, **session_kwargs: Unpack[SessionKwargs]) -> ClientSession | None
168
  ```
169
 
170
- #### `disconnect` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L407" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
171
 
172
  ```python
173
  disconnect(self)
174
  ```
175
 
176
- #### `close` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L422" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
177
 
178
  ```python
179
  close(self)
180
  ```
181
 
182
- ### `PythonStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L431" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
183
 
184
 
185
  Transport for running Python scripts.
186
 
187
 
188
- ### `FastMCPStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L477" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
189
 
190
 
191
  Transport for running FastMCP servers using the FastMCP CLI.
192
 
193
 
194
- ### `NodeStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L504" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
195
 
196
 
197
  Transport for running Node.js scripts.
198
 
199
 
200
- ### `UvStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L546" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
201
 
202
 
203
  Transport for running commands via the uv tool.
204
 
205
 
206
- ### `UvxStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L603" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
207
 
208
 
209
  Transport for running commands via the uvx tool.
210
 
211
 
212
- ### `NpxStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L667" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
213
 
214
 
215
  Transport for running commands via the npx tool.
216
 
217
 
218
- ### `FastMCPTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L729" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
219
 
220
 
221
  In-memory transport for FastMCP servers.
@@ -228,13 +228,13 @@ tests or scenarios where client and server run in the same runtime.
228
 
229
  **Methods:**
230
 
231
- #### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L748" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
232
 
233
  ```python
234
  connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
235
  ```
236
 
237
- ### `MCPConfigTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L783" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
238
 
239
 
240
  Transport for connecting to one or more MCP servers defined in an MCPConfig.
@@ -287,8 +287,14 @@ async with client:
287
 
288
  **Methods:**
289
 
290
- #### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L855" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
291
 
292
  ```python
293
  connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
294
  ```
 
 
 
 
 
 
 
7
 
8
  ## Functions
9
 
10
+ ### `infer_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L950" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
11
 
12
  ```python
13
  infer_transport(transport: ClientTransport | FastMCP | FastMCP1Server | AnyUrl | Path | MCPConfig | dict[str, Any] | str) -> ClientTransport
 
167
  connect(self, **session_kwargs: Unpack[SessionKwargs]) -> ClientSession | None
168
  ```
169
 
170
+ #### `disconnect` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L390" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
171
 
172
  ```python
173
  disconnect(self)
174
  ```
175
 
176
+ #### `close` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L405" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
177
 
178
  ```python
179
  close(self)
180
  ```
181
 
182
+ ### `PythonStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L465" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
183
 
184
 
185
  Transport for running Python scripts.
186
 
187
 
188
+ ### `FastMCPStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L511" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
189
 
190
 
191
  Transport for running FastMCP servers using the FastMCP CLI.
192
 
193
 
194
+ ### `NodeStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L538" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
195
 
196
 
197
  Transport for running Node.js scripts.
198
 
199
 
200
+ ### `UvStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L580" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
201
 
202
 
203
  Transport for running commands via the uv tool.
204
 
205
 
206
+ ### `UvxStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L637" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
207
 
208
 
209
  Transport for running commands via the uvx tool.
210
 
211
 
212
+ ### `NpxStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L701" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
213
 
214
 
215
  Transport for running commands via the npx tool.
216
 
217
 
218
+ ### `FastMCPTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L763" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
219
 
220
 
221
  In-memory transport for FastMCP servers.
 
228
 
229
  **Methods:**
230
 
231
+ #### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L782" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
232
 
233
  ```python
234
  connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
235
  ```
236
 
237
+ ### `MCPConfigTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L817" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
238
 
239
 
240
  Transport for connecting to one or more MCP servers defined in an MCPConfig.
 
287
 
288
  **Methods:**
289
 
290
+ #### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L898" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
291
 
292
  ```python
293
  connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
294
  ```
295
+
296
+ #### `close` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L904" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
297
+
298
+ ```python
299
+ close(self)
300
+ ```
docs/python-sdk/fastmcp-mcp_config.mdx CHANGED
@@ -42,7 +42,7 @@ infer_transport_type_from_url(url: str | AnyUrl) -> Literal['http', 'sse']
42
  Infer the appropriate transport type from the given URL.
43
 
44
 
45
- ### `update_config_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L293" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
46
 
47
  ```python
48
  update_config_file(file_path: Path, server_name: str, server_config: CanonicalMCPServerTypes) -> None
@@ -57,7 +57,7 @@ worry about transforming server objects here.
57
 
58
  ## Classes
59
 
60
- ### `StdioMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L110" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
61
 
62
 
63
  MCP server configuration for stdio transport.
@@ -67,19 +67,19 @@ This is the canonical configuration format for MCP servers using stdio transport
67
 
68
  **Methods:**
69
 
70
- #### `to_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L140" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
71
 
72
  ```python
73
  to_transport(self) -> StdioTransport
74
  ```
75
 
76
- ### `TransformingStdioMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L151" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
77
 
78
 
79
  A Stdio server with tool transforms.
80
 
81
 
82
- ### `RemoteMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L155" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
83
 
84
 
85
  MCP server configuration for HTTP/SSE transport.
@@ -89,19 +89,19 @@ This is the canonical configuration format for MCP servers using remote transpor
89
 
90
  **Methods:**
91
 
92
- #### `to_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L191" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
93
 
94
  ```python
95
  to_transport(self) -> StreamableHttpTransport | SSETransport
96
  ```
97
 
98
- ### `TransformingRemoteMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L216" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
99
 
100
 
101
  A Remote server with tool transforms.
102
 
103
 
104
- ### `MCPConfig` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L227" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
105
 
106
 
107
  A configuration object for MCP Servers that conforms to the canonical MCP configuration format
@@ -113,7 +113,7 @@ For an MCPConfig that is strictly canonical, see the `CanonicalMCPConfig` class.
113
 
114
  **Methods:**
115
 
116
- #### `validate_mcp_servers` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L240" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
117
 
118
  ```python
119
  validate_mcp_servers(self, info: ValidationInfo) -> dict[str, Any]
@@ -122,7 +122,7 @@ validate_mcp_servers(self, info: ValidationInfo) -> dict[str, Any]
122
  Validate the MCP servers.
123
 
124
 
125
- #### `add_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L250" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
126
 
127
  ```python
128
  add_server(self, name: str, server: MCPServerTypes) -> None
@@ -131,7 +131,7 @@ add_server(self, name: str, server: MCPServerTypes) -> None
131
  Add or update a server in the configuration.
132
 
133
 
134
- #### `from_dict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L255" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
135
 
136
  ```python
137
  from_dict(cls, config: dict[str, Any]) -> Self
@@ -140,7 +140,7 @@ from_dict(cls, config: dict[str, Any]) -> Self
140
  Parse MCP configuration from dictionary format.
141
 
142
 
143
- #### `to_dict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L259" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
144
 
145
  ```python
146
  to_dict(self) -> dict[str, Any]
@@ -149,7 +149,7 @@ to_dict(self) -> dict[str, Any]
149
  Convert MCPConfig to dictionary format, preserving all fields.
150
 
151
 
152
- #### `write_to_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L263" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
153
 
154
  ```python
155
  write_to_file(self, file_path: Path) -> None
@@ -158,7 +158,7 @@ write_to_file(self, file_path: Path) -> None
158
  Write configuration to JSON file.
159
 
160
 
161
- #### `from_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L269" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
162
 
163
  ```python
164
  from_file(cls, file_path: Path) -> Self
@@ -167,7 +167,7 @@ from_file(cls, file_path: Path) -> Self
167
  Load configuration from JSON file.
168
 
169
 
170
- ### `CanonicalMCPConfig` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L278" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
171
 
172
 
173
  Canonical MCP configuration format.
@@ -178,7 +178,7 @@ The format is designed to be client-agnostic and extensible for future use cases
178
 
179
  **Methods:**
180
 
181
- #### `add_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L288" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
182
 
183
  ```python
184
  add_server(self, name: str, server: CanonicalMCPServerTypes) -> None
 
42
  Infer the appropriate transport type from the given URL.
43
 
44
 
45
+ ### `update_config_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L300" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
46
 
47
  ```python
48
  update_config_file(file_path: Path, server_name: str, server_config: CanonicalMCPServerTypes) -> None
 
57
 
58
  ## Classes
59
 
60
+ ### `StdioMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L117" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
61
 
62
 
63
  MCP server configuration for stdio transport.
 
67
 
68
  **Methods:**
69
 
70
+ #### `to_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L147" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
71
 
72
  ```python
73
  to_transport(self) -> StdioTransport
74
  ```
75
 
76
+ ### `TransformingStdioMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L158" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
77
 
78
 
79
  A Stdio server with tool transforms.
80
 
81
 
82
+ ### `RemoteMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L162" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
83
 
84
 
85
  MCP server configuration for HTTP/SSE transport.
 
89
 
90
  **Methods:**
91
 
92
+ #### `to_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L198" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
93
 
94
  ```python
95
  to_transport(self) -> StreamableHttpTransport | SSETransport
96
  ```
97
 
98
+ ### `TransformingRemoteMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L223" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
99
 
100
 
101
  A Remote server with tool transforms.
102
 
103
 
104
+ ### `MCPConfig` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L234" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
105
 
106
 
107
  A configuration object for MCP Servers that conforms to the canonical MCP configuration format
 
113
 
114
  **Methods:**
115
 
116
+ #### `validate_mcp_servers` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L247" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
117
 
118
  ```python
119
  validate_mcp_servers(self, info: ValidationInfo) -> dict[str, Any]
 
122
  Validate the MCP servers.
123
 
124
 
125
+ #### `add_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L257" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
126
 
127
  ```python
128
  add_server(self, name: str, server: MCPServerTypes) -> None
 
131
  Add or update a server in the configuration.
132
 
133
 
134
+ #### `from_dict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L262" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
135
 
136
  ```python
137
  from_dict(cls, config: dict[str, Any]) -> Self
 
140
  Parse MCP configuration from dictionary format.
141
 
142
 
143
+ #### `to_dict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L266" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
144
 
145
  ```python
146
  to_dict(self) -> dict[str, Any]
 
149
  Convert MCPConfig to dictionary format, preserving all fields.
150
 
151
 
152
+ #### `write_to_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L270" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
153
 
154
  ```python
155
  write_to_file(self, file_path: Path) -> None
 
158
  Write configuration to JSON file.
159
 
160
 
161
+ #### `from_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L276" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
162
 
163
  ```python
164
  from_file(cls, file_path: Path) -> Self
 
167
  Load configuration from JSON file.
168
 
169
 
170
+ ### `CanonicalMCPConfig` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L285" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
171
 
172
 
173
  Canonical MCP configuration format.
 
178
 
179
  **Methods:**
180
 
181
+ #### `add_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L295" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
182
 
183
  ```python
184
  add_server(self, name: str, server: CanonicalMCPServerTypes) -> None
docs/python-sdk/fastmcp-server-context.mdx CHANGED
@@ -7,7 +7,7 @@ sidebarTitle: context
7
 
8
  ## Functions
9
 
10
- ### `set_context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L63" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
11
 
12
  ```python
13
  set_context(context: Context) -> Generator[Context, None, None]
@@ -15,7 +15,7 @@ set_context(context: Context) -> Generator[Context, None, None]
15
 
16
  ## Classes
17
 
18
- ### `LogData` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L51" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
19
 
20
 
21
  Data object for passing log arguments to client-side handlers.
@@ -24,7 +24,7 @@ This provides an interface to match the Python standard library logging,
24
  for compatibility with structured logging.
25
 
26
 
27
- ### `Context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L72" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
28
 
29
 
30
  Context object providing access to MCP capabilities.
@@ -72,7 +72,16 @@ The context is optional - tools that don't need it can omit the parameter.
72
 
73
  **Methods:**
74
 
75
- #### `request_context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L145" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
 
 
 
 
 
 
 
 
 
76
 
77
  ```python
78
  request_context(self) -> RequestContext[ServerSession, Any, Request]
@@ -83,7 +92,7 @@ Access to the underlying request context.
83
  If called outside of a request context, this will raise a ValueError.
84
 
85
 
86
- #### `report_progress` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L155" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
87
 
88
  ```python
89
  report_progress(self, progress: float, total: float | None = None, message: str | None = None) -> None
@@ -96,7 +105,7 @@ Report progress for the current operation.
96
  - `total`: Optional total value e.g. 100
97
 
98
 
99
- #### `read_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L182" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
100
 
101
  ```python
102
  read_resource(self, uri: str | AnyUrl) -> list[ReadResourceContents]
@@ -111,7 +120,7 @@ Read a resource by URI.
111
  - The resource content as either text or bytes
112
 
113
 
114
- #### `log` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L195" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
115
 
116
  ```python
117
  log(self, message: str, level: LoggingLevel | None = None, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -127,7 +136,7 @@ Send a log message to the client.
127
  - `extra`: Optional mapping for additional arguments
128
 
129
 
130
- #### `client_id` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L222" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
131
 
132
  ```python
133
  client_id(self) -> str | None
@@ -136,7 +145,7 @@ client_id(self) -> str | None
136
  Get the client ID if available.
137
 
138
 
139
- #### `request_id` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L231" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
140
 
141
  ```python
142
  request_id(self) -> str
@@ -145,7 +154,7 @@ request_id(self) -> str
145
  Get the unique ID for this request.
146
 
147
 
148
- #### `session_id` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L236" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
149
 
150
  ```python
151
  session_id(self) -> str
@@ -162,7 +171,7 @@ the same client session.
162
  - for other transports.
163
 
164
 
165
- #### `session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L280" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
166
 
167
  ```python
168
  session(self) -> ServerSession
@@ -171,7 +180,7 @@ session(self) -> ServerSession
171
  Access to the underlying session for advanced usage.
172
 
173
 
174
- #### `debug` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L285" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
175
 
176
  ```python
177
  debug(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -180,7 +189,7 @@ debug(self, message: str, logger_name: str | None = None, extra: Mapping[str, An
180
  Send a debug log message.
181
 
182
 
183
- #### `info` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L296" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
184
 
185
  ```python
186
  info(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -189,7 +198,7 @@ info(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any
189
  Send an info log message.
190
 
191
 
192
- #### `warning` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L307" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
193
 
194
  ```python
195
  warning(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -198,7 +207,7 @@ warning(self, message: str, logger_name: str | None = None, extra: Mapping[str,
198
  Send a warning log message.
199
 
200
 
201
- #### `error` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L318" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
202
 
203
  ```python
204
  error(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -207,7 +216,7 @@ error(self, message: str, logger_name: str | None = None, extra: Mapping[str, An
207
  Send an error log message.
208
 
209
 
210
- #### `list_roots` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L329" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
211
 
212
  ```python
213
  list_roots(self) -> list[Root]
@@ -216,7 +225,7 @@ list_roots(self) -> list[Root]
216
  List the roots available to the server, as indicated by the client.
217
 
218
 
219
- #### `send_tool_list_changed` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L334" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
220
 
221
  ```python
222
  send_tool_list_changed(self) -> None
@@ -225,7 +234,7 @@ send_tool_list_changed(self) -> None
225
  Send a tool list changed notification to the client.
226
 
227
 
228
- #### `send_resource_list_changed` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L338" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
229
 
230
  ```python
231
  send_resource_list_changed(self) -> None
@@ -234,7 +243,7 @@ send_resource_list_changed(self) -> None
234
  Send a resource list changed notification to the client.
235
 
236
 
237
- #### `send_prompt_list_changed` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L342" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
238
 
239
  ```python
240
  send_prompt_list_changed(self) -> None
@@ -243,7 +252,7 @@ send_prompt_list_changed(self) -> None
243
  Send a prompt list changed notification to the client.
244
 
245
 
246
- #### `sample` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L346" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
247
 
248
  ```python
249
  sample(self, messages: str | list[str | SamplingMessage], system_prompt: str | None = None, include_context: IncludeContext | None = None, temperature: float | None = None, max_tokens: int | None = None, model_preferences: ModelPreferences | str | list[str] | None = None) -> ContentBlock
@@ -256,25 +265,25 @@ completion from the client. The client must be appropriately configured,
256
  or the request will error.
257
 
258
 
259
- #### `elicit` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L393" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
260
 
261
  ```python
262
  elicit(self, message: str, response_type: None) -> AcceptedElicitation[dict[str, Any]] | DeclinedElicitation | CancelledElicitation
263
  ```
264
 
265
- #### `elicit` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L405" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
266
 
267
  ```python
268
  elicit(self, message: str, response_type: type[T]) -> AcceptedElicitation[T] | DeclinedElicitation | CancelledElicitation
269
  ```
270
 
271
- #### `elicit` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L415" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
272
 
273
  ```python
274
  elicit(self, message: str, response_type: list[str]) -> AcceptedElicitation[str] | DeclinedElicitation | CancelledElicitation
275
  ```
276
 
277
- #### `elicit` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L424" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
278
 
279
  ```python
280
  elicit(self, message: str, response_type: type[T] | list[str] | None = None) -> AcceptedElicitation[T] | AcceptedElicitation[dict[str, Any]] | AcceptedElicitation[str] | DeclinedElicitation | CancelledElicitation
@@ -303,7 +312,7 @@ type or dataclass or BaseModel. If it is a primitive type, an
303
  object schema with a single "value" field will be generated.
304
 
305
 
306
- #### `get_http_request` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L517" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
307
 
308
  ```python
309
  get_http_request(self) -> Request
@@ -312,7 +321,7 @@ get_http_request(self) -> Request
312
  Get the active starlette request.
313
 
314
 
315
- #### `set_state` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L532" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
316
 
317
  ```python
318
  set_state(self, key: str, value: Any) -> None
@@ -321,7 +330,7 @@ set_state(self, key: str, value: Any) -> None
321
  Set a value in the context state.
322
 
323
 
324
- #### `get_state` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L536" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
325
 
326
  ```python
327
  get_state(self, key: str) -> Any
 
7
 
8
  ## Functions
9
 
10
+ ### `set_context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L64" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
11
 
12
  ```python
13
  set_context(context: Context) -> Generator[Context, None, None]
 
15
 
16
  ## Classes
17
 
18
+ ### `LogData` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L52" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
19
 
20
 
21
  Data object for passing log arguments to client-side handlers.
 
24
  for compatibility with structured logging.
25
 
26
 
27
+ ### `Context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L73" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
28
 
29
 
30
  Context object providing access to MCP capabilities.
 
72
 
73
  **Methods:**
74
 
75
+ #### `fastmcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L125" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
76
+
77
+ ```python
78
+ fastmcp(self) -> FastMCP
79
+ ```
80
+
81
+ Get the FastMCP instance.
82
+
83
+
84
+ #### `request_context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L154" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
85
 
86
  ```python
87
  request_context(self) -> RequestContext[ServerSession, Any, Request]
 
92
  If called outside of a request context, this will raise a ValueError.
93
 
94
 
95
+ #### `report_progress` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L164" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
96
 
97
  ```python
98
  report_progress(self, progress: float, total: float | None = None, message: str | None = None) -> None
 
105
  - `total`: Optional total value e.g. 100
106
 
107
 
108
+ #### `read_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L191" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
109
 
110
  ```python
111
  read_resource(self, uri: str | AnyUrl) -> list[ReadResourceContents]
 
120
  - The resource content as either text or bytes
121
 
122
 
123
+ #### `log` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L204" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
124
 
125
  ```python
126
  log(self, message: str, level: LoggingLevel | None = None, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
 
136
  - `extra`: Optional mapping for additional arguments
137
 
138
 
139
+ #### `client_id` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L231" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
140
 
141
  ```python
142
  client_id(self) -> str | None
 
145
  Get the client ID if available.
146
 
147
 
148
+ #### `request_id` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L240" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
149
 
150
  ```python
151
  request_id(self) -> str
 
154
  Get the unique ID for this request.
155
 
156
 
157
+ #### `session_id` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L245" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
158
 
159
  ```python
160
  session_id(self) -> str
 
171
  - for other transports.
172
 
173
 
174
+ #### `session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L289" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
175
 
176
  ```python
177
  session(self) -> ServerSession
 
180
  Access to the underlying session for advanced usage.
181
 
182
 
183
+ #### `debug` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L294" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
184
 
185
  ```python
186
  debug(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
 
189
  Send a debug log message.
190
 
191
 
192
+ #### `info` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L305" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
193
 
194
  ```python
195
  info(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
 
198
  Send an info log message.
199
 
200
 
201
+ #### `warning` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L316" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
202
 
203
  ```python
204
  warning(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
 
207
  Send a warning log message.
208
 
209
 
210
+ #### `error` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L327" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
211
 
212
  ```python
213
  error(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
 
216
  Send an error log message.
217
 
218
 
219
+ #### `list_roots` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L338" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
220
 
221
  ```python
222
  list_roots(self) -> list[Root]
 
225
  List the roots available to the server, as indicated by the client.
226
 
227
 
228
+ #### `send_tool_list_changed` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L343" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
229
 
230
  ```python
231
  send_tool_list_changed(self) -> None
 
234
  Send a tool list changed notification to the client.
235
 
236
 
237
+ #### `send_resource_list_changed` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L347" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
238
 
239
  ```python
240
  send_resource_list_changed(self) -> None
 
243
  Send a resource list changed notification to the client.
244
 
245
 
246
+ #### `send_prompt_list_changed` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L351" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
247
 
248
  ```python
249
  send_prompt_list_changed(self) -> None
 
252
  Send a prompt list changed notification to the client.
253
 
254
 
255
+ #### `sample` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L355" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
256
 
257
  ```python
258
  sample(self, messages: str | list[str | SamplingMessage], system_prompt: str | None = None, include_context: IncludeContext | None = None, temperature: float | None = None, max_tokens: int | None = None, model_preferences: ModelPreferences | str | list[str] | None = None) -> ContentBlock
 
265
  or the request will error.
266
 
267
 
268
+ #### `elicit` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L402" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
269
 
270
  ```python
271
  elicit(self, message: str, response_type: None) -> AcceptedElicitation[dict[str, Any]] | DeclinedElicitation | CancelledElicitation
272
  ```
273
 
274
+ #### `elicit` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L414" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
275
 
276
  ```python
277
  elicit(self, message: str, response_type: type[T]) -> AcceptedElicitation[T] | DeclinedElicitation | CancelledElicitation
278
  ```
279
 
280
+ #### `elicit` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L424" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
281
 
282
  ```python
283
  elicit(self, message: str, response_type: list[str]) -> AcceptedElicitation[str] | DeclinedElicitation | CancelledElicitation
284
  ```
285
 
286
+ #### `elicit` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L433" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
287
 
288
  ```python
289
  elicit(self, message: str, response_type: type[T] | list[str] | None = None) -> AcceptedElicitation[T] | AcceptedElicitation[dict[str, Any]] | AcceptedElicitation[str] | DeclinedElicitation | CancelledElicitation
 
312
  object schema with a single "value" field will be generated.
313
 
314
 
315
+ #### `get_http_request` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L526" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
316
 
317
  ```python
318
  get_http_request(self) -> Request
 
321
  Get the active starlette request.
322
 
323
 
324
+ #### `set_state` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L541" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
325
 
326
  ```python
327
  set_state(self, key: str, value: Any) -> None
 
330
  Set a value in the context state.
331
 
332
 
333
+ #### `get_state` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L545" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
334
 
335
  ```python
336
  get_state(self, key: str) -> Any
docs/python-sdk/fastmcp-utilities-mcp_config.mdx CHANGED
@@ -7,22 +7,22 @@ sidebarTitle: mcp_config
7
 
8
  ## Functions
9
 
10
- ### `composite_server_from_mcp_config` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L7" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
11
 
12
  ```python
13
- composite_server_from_mcp_config(config: MCPConfig, name_as_prefix: bool = True) -> FastMCP[None]
14
  ```
15
 
16
 
17
- A utility function to create a composite server from an MCPConfig.
18
 
19
 
20
- ### `mount_mcp_config_into_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L18" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
21
 
22
  ```python
23
- mount_mcp_config_into_server(config: MCPConfig, server: FastMCP[Any], name_as_prefix: bool = True) -> None
24
  ```
25
 
26
 
27
- A utility function to mount the servers from an MCPConfig into a FastMCP server.
28
 
 
7
 
8
  ## Functions
9
 
10
+ ### `mcp_config_to_servers_and_transports` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L11" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
11
 
12
  ```python
13
+ mcp_config_to_servers_and_transports(config: MCPConfig) -> list[tuple[str, FastMCP[Any], ClientTransport]]
14
  ```
15
 
16
 
17
+ A utility function to convert each entry of an MCP Config into a transport and server.
18
 
19
 
20
+ ### `mcp_server_type_to_servers_and_transports` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L21" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
21
 
22
  ```python
23
+ mcp_server_type_to_servers_and_transports(name: str, mcp_server: MCPServerTypes) -> tuple[str, FastMCP[Any], ClientTransport]
24
  ```
25
 
26
 
27
+ A utility function to convert each entry of an MCP Config into a transport and server.
28
 
pyproject.toml CHANGED
@@ -47,6 +47,7 @@ dev = [
47
  "ipython>=8.12.3",
48
  "pdbpp>=0.10.3",
49
  "pre-commit",
 
50
  "pyinstrument>=5.0.2",
51
  "pyperclip>=1.9.0",
52
  "pyright>=1.1.389",
 
47
  "ipython>=8.12.3",
48
  "pdbpp>=0.10.3",
49
  "pre-commit",
50
+ "psutil",
51
  "pyinstrument>=5.0.2",
52
  "pyperclip>=1.9.0",
53
  "pyright>=1.1.389",
src/fastmcp/client/transports.py CHANGED
@@ -359,42 +359,22 @@ class StdioTransport(ClientTransport):
359
  if self._connect_task is not None:
360
  return
361
 
362
- async def _connect_task():
363
- from mcp.client.stdio import stdio_client
364
-
365
- try:
366
- async with contextlib.AsyncExitStack() as stack:
367
- try:
368
- server_params = StdioServerParameters(
369
- command=self.command,
370
- args=self.args,
371
- env=self.env,
372
- cwd=self.cwd,
373
- )
374
- transport = await stack.enter_async_context(
375
- stdio_client(server_params)
376
- )
377
- read_stream, write_stream = transport
378
- self._session = await stack.enter_async_context(
379
- ClientSession(read_stream, write_stream, **session_kwargs)
380
- )
381
-
382
- logger.debug("Stdio transport connected")
383
- self._ready_event.set()
384
-
385
- # Wait until disconnect is requested (stop_event is set)
386
- await self._stop_event.wait()
387
- finally:
388
- # Clean up client on exit
389
- self._session = None
390
- logger.debug("Stdio transport disconnected")
391
- except Exception:
392
- # Ensure ready event is set even if connection fails
393
- self._ready_event.set()
394
- raise
395
 
396
  # start the connection task
397
- self._connect_task = asyncio.create_task(_connect_task())
 
 
 
 
 
 
 
 
 
 
 
 
398
  # wait for the client to be ready before returning
399
  await self._ready_event.wait()
400
 
@@ -404,6 +384,9 @@ class StdioTransport(ClientTransport):
404
  if exception is not None:
405
  raise exception
406
 
 
 
 
407
  async def disconnect(self):
408
  if self._connect_task is None:
409
  return
@@ -422,12 +405,63 @@ class StdioTransport(ClientTransport):
422
  async def close(self):
423
  await self.disconnect()
424
 
 
 
 
 
 
425
  def __repr__(self) -> str:
426
  return (
427
  f"<{self.__class__.__name__}(command='{self.command}', args={self.args})>"
428
  )
429
 
430
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
431
  class PythonStdioTransport(StdioTransport):
432
  """Transport for running Python scripts."""
433
 
@@ -829,12 +863,14 @@ class MCPConfigTransport(ClientTransport):
829
  """
830
 
831
  def __init__(self, config: MCPConfig | dict, name_as_prefix: bool = True):
832
- from fastmcp.utilities.mcp_config import composite_server_from_mcp_config
833
 
834
  if isinstance(config, dict):
835
  config = MCPConfig.from_dict(config)
836
  self.config = config
837
 
 
 
838
  # if there are no servers, raise an error
839
  if len(self.config.mcpServers) == 0:
840
  raise ValueError("No MCP servers defined in the config")
@@ -842,14 +878,21 @@ class MCPConfigTransport(ClientTransport):
842
  # if there's exactly one server, create a client for that server
843
  elif len(self.config.mcpServers) == 1:
844
  self.transport = list(self.config.mcpServers.values())[0].to_transport()
 
845
 
846
  # otherwise create a composite client
847
  else:
848
- self.transport = FastMCPTransport(
849
- mcp=composite_server_from_mcp_config(
850
- self.config, name_as_prefix=name_as_prefix
 
 
 
 
 
851
  )
852
- )
 
853
 
854
  @contextlib.asynccontextmanager
855
  async def connect_session(
@@ -858,6 +901,10 @@ class MCPConfigTransport(ClientTransport):
858
  async with self.transport.connect_session(**session_kwargs) as session:
859
  yield session
860
 
 
 
 
 
861
  def __repr__(self) -> str:
862
  return f"<MCPConfigTransport(config='{self.config}')>"
863
 
 
359
  if self._connect_task is not None:
360
  return
361
 
362
+ session_future: asyncio.Future[ClientSession] = asyncio.Future()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363
 
364
  # start the connection task
365
+ self._connect_task = asyncio.create_task(
366
+ _stdio_transport_connect_task(
367
+ command=self.command,
368
+ args=self.args,
369
+ env=self.env,
370
+ cwd=self.cwd,
371
+ session_kwargs=session_kwargs,
372
+ ready_event=self._ready_event,
373
+ stop_event=self._stop_event,
374
+ session_future=session_future,
375
+ )
376
+ )
377
+
378
  # wait for the client to be ready before returning
379
  await self._ready_event.wait()
380
 
 
384
  if exception is not None:
385
  raise exception
386
 
387
+ self._session = await session_future
388
+ return self._session
389
+
390
  async def disconnect(self):
391
  if self._connect_task is None:
392
  return
 
405
  async def close(self):
406
  await self.disconnect()
407
 
408
+ def __del__(self):
409
+ """Ensure that we send a disconnection signal to the transport task if we are being garbage collected."""
410
+ if not self._stop_event.is_set():
411
+ self._stop_event.set()
412
+
413
  def __repr__(self) -> str:
414
  return (
415
  f"<{self.__class__.__name__}(command='{self.command}', args={self.args})>"
416
  )
417
 
418
 
419
+ async def _stdio_transport_connect_task(
420
+ command: str,
421
+ args: list[str],
422
+ env: dict[str, str] | None,
423
+ cwd: str | None,
424
+ session_kwargs: SessionKwargs,
425
+ ready_event: anyio.Event,
426
+ stop_event: anyio.Event,
427
+ session_future: asyncio.Future[ClientSession],
428
+ ):
429
+ """A standalone connection task for a stdio transport. It is not a part of the StdioTransport class
430
+ to ensure that the connection task does not hold a reference to the Transport object."""
431
+
432
+ from mcp.client.stdio import stdio_client
433
+
434
+ try:
435
+ async with contextlib.AsyncExitStack() as stack:
436
+ try:
437
+ server_params = StdioServerParameters(
438
+ command=command,
439
+ args=args,
440
+ env=env,
441
+ cwd=cwd,
442
+ )
443
+ transport = await stack.enter_async_context(stdio_client(server_params))
444
+ read_stream, write_stream = transport
445
+ session_future.set_result(
446
+ await stack.enter_async_context(
447
+ ClientSession(read_stream, write_stream, **session_kwargs)
448
+ )
449
+ )
450
+
451
+ logger.debug("Stdio transport connected")
452
+ ready_event.set()
453
+
454
+ # Wait until disconnect is requested (stop_event is set)
455
+ await stop_event.wait()
456
+ finally:
457
+ # Clean up client on exit
458
+ logger.debug("Stdio transport disconnected")
459
+ except Exception:
460
+ # Ensure ready event is set even if connection fails
461
+ ready_event.set()
462
+ raise
463
+
464
+
465
  class PythonStdioTransport(StdioTransport):
466
  """Transport for running Python scripts."""
467
 
 
863
  """
864
 
865
  def __init__(self, config: MCPConfig | dict, name_as_prefix: bool = True):
866
+ from fastmcp.utilities.mcp_config import mcp_config_to_servers_and_transports
867
 
868
  if isinstance(config, dict):
869
  config = MCPConfig.from_dict(config)
870
  self.config = config
871
 
872
+ self._underlying_transports: list[ClientTransport] = []
873
+
874
  # if there are no servers, raise an error
875
  if len(self.config.mcpServers) == 0:
876
  raise ValueError("No MCP servers defined in the config")
 
878
  # if there's exactly one server, create a client for that server
879
  elif len(self.config.mcpServers) == 1:
880
  self.transport = list(self.config.mcpServers.values())[0].to_transport()
881
+ self._underlying_transports.append(self.transport)
882
 
883
  # otherwise create a composite client
884
  else:
885
+ self._composite_server = FastMCP[Any]()
886
+
887
+ for name, server, transport in mcp_config_to_servers_and_transports(
888
+ self.config
889
+ ):
890
+ self._underlying_transports.append(transport)
891
+ self._composite_server.mount(
892
+ server, prefix=name if name_as_prefix else None
893
  )
894
+
895
+ self.transport = FastMCPTransport(mcp=self._composite_server)
896
 
897
  @contextlib.asynccontextmanager
898
  async def connect_session(
 
901
  async with self.transport.connect_session(**session_kwargs) as session:
902
  yield session
903
 
904
+ async def close(self):
905
+ for transport in self._underlying_transports:
906
+ await transport.close()
907
+
908
  def __repr__(self) -> str:
909
  return f"<MCPConfigTransport(config='{self.config}')>"
910
 
src/fastmcp/mcp_config.py CHANGED
@@ -47,11 +47,11 @@ from fastmcp.utilities.types import FastMCPBaseModel
47
  if TYPE_CHECKING:
48
  from fastmcp.client.transports import (
49
  ClientTransport,
50
- FastMCPTransport,
51
  SSETransport,
52
  StdioTransport,
53
  StreamableHttpTransport,
54
  )
 
55
 
56
 
57
  def infer_transport_type_from_url(
@@ -90,10 +90,11 @@ class _TransformingMCPServerMixin(FastMCPBaseModel):
90
  description="The tags to exclude in the proxy.",
91
  )
92
 
93
- def to_transport(self) -> FastMCPTransport:
94
- """Get the transport for the server."""
95
- from fastmcp.client.transports import FastMCPTransport
96
- from fastmcp.server.server import FastMCP
 
97
 
98
  transport: ClientTransport = super().to_transport() # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue, reportUnknownVariableType]
99
 
@@ -104,7 +105,13 @@ class _TransformingMCPServerMixin(FastMCPBaseModel):
104
  exclude_tags=self.exclude_tags,
105
  )
106
 
107
- return FastMCPTransport(wrapped_mcp_server)
 
 
 
 
 
 
108
 
109
 
110
  class StdioMCPServer(BaseModel):
 
47
  if TYPE_CHECKING:
48
  from fastmcp.client.transports import (
49
  ClientTransport,
 
50
  SSETransport,
51
  StdioTransport,
52
  StreamableHttpTransport,
53
  )
54
+ from fastmcp.server.server import FastMCP
55
 
56
 
57
  def infer_transport_type_from_url(
 
90
  description="The tags to exclude in the proxy.",
91
  )
92
 
93
+ def _to_server_and_underlying_transport(
94
+ self,
95
+ ) -> tuple[FastMCP[Any], ClientTransport]:
96
+ """Turn the Transforming MCPServer into a FastMCP Server and also return the underlying transport."""
97
+ from fastmcp import FastMCP
98
 
99
  transport: ClientTransport = super().to_transport() # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue, reportUnknownVariableType]
100
 
 
105
  exclude_tags=self.exclude_tags,
106
  )
107
 
108
+ return wrapped_mcp_server, transport
109
+
110
+ def to_transport(self) -> ClientTransport:
111
+ """Get the transport for the transforming MCP server."""
112
+ from fastmcp.client.transports import FastMCPTransport
113
+
114
+ return FastMCPTransport(mcp=self._to_server_and_underlying_transport()[0])
115
 
116
 
117
  class StdioMCPServer(BaseModel):
src/fastmcp/server/context.py CHANGED
@@ -3,6 +3,7 @@ from __future__ import annotations
3
  import asyncio
4
  import copy
5
  import warnings
 
6
  from collections.abc import Generator, Mapping
7
  from contextlib import contextmanager
8
  from contextvars import ContextVar, Token
@@ -115,11 +116,19 @@ class Context:
115
  """
116
 
117
  def __init__(self, fastmcp: FastMCP):
118
- self.fastmcp = fastmcp
119
  self._tokens: list[Token] = []
120
  self._notification_queue: set[str] = set() # Dedupe notifications
121
  self._state: dict[str, Any] = {}
122
 
 
 
 
 
 
 
 
 
123
  async def __aenter__(self) -> Context:
124
  """Enter the context manager and set this context as the current context."""
125
  parent_context = _current_context.get(None)
 
3
  import asyncio
4
  import copy
5
  import warnings
6
+ import weakref
7
  from collections.abc import Generator, Mapping
8
  from contextlib import contextmanager
9
  from contextvars import ContextVar, Token
 
116
  """
117
 
118
  def __init__(self, fastmcp: FastMCP):
119
+ self._fastmcp: weakref.ref[FastMCP] = weakref.ref(fastmcp)
120
  self._tokens: list[Token] = []
121
  self._notification_queue: set[str] = set() # Dedupe notifications
122
  self._state: dict[str, Any] = {}
123
 
124
+ @property
125
+ def fastmcp(self) -> FastMCP:
126
+ """Get the FastMCP instance."""
127
+ fastmcp = self._fastmcp()
128
+ if fastmcp is None:
129
+ raise RuntimeError("FastMCP instance is no longer available")
130
+ return fastmcp
131
+
132
  async def __aenter__(self) -> Context:
133
  """Enter the context manager and set this context as the current context."""
134
  parent_context = _current_context.get(None)
src/fastmcp/utilities/mcp_config.py CHANGED
@@ -1,28 +1,40 @@
1
  from typing import Any
2
 
3
- from fastmcp.mcp_config import MCPConfig
 
 
 
 
4
  from fastmcp.server.server import FastMCP
5
 
6
 
7
- def composite_server_from_mcp_config(
8
- config: MCPConfig, name_as_prefix: bool = True
9
- ) -> FastMCP[None]:
10
- """A utility function to create a composite server from an MCPConfig."""
11
- composite_server = FastMCP[None]()
 
 
 
12
 
13
- mount_mcp_config_into_server(config, composite_server, name_as_prefix)
14
 
15
- return composite_server
 
 
 
 
 
 
 
 
16
 
 
 
17
 
18
- def mount_mcp_config_into_server(
19
- config: MCPConfig,
20
- server: FastMCP[Any],
21
- name_as_prefix: bool = True,
22
- ) -> None:
23
- """A utility function to mount the servers from an MCPConfig into a FastMCP server."""
24
- for name, mcp_server in config.mcpServers.items():
25
- server.mount(
26
- prefix=name if name_as_prefix else None,
27
- server=FastMCP.as_proxy(backend=mcp_server.to_transport()),
28
- )
 
1
  from typing import Any
2
 
3
+ from fastmcp.client.transports import ClientTransport
4
+ from fastmcp.mcp_config import (
5
+ MCPConfig,
6
+ MCPServerTypes,
7
+ )
8
  from fastmcp.server.server import FastMCP
9
 
10
 
11
+ def mcp_config_to_servers_and_transports(
12
+ config: MCPConfig,
13
+ ) -> list[tuple[str, FastMCP[Any], ClientTransport]]:
14
+ """A utility function to convert each entry of an MCP Config into a transport and server."""
15
+ return [
16
+ mcp_server_type_to_servers_and_transports(name, mcp_server)
17
+ for name, mcp_server in config.mcpServers.items()
18
+ ]
19
 
 
20
 
21
+ def mcp_server_type_to_servers_and_transports(
22
+ name: str,
23
+ mcp_server: MCPServerTypes,
24
+ ) -> tuple[str, FastMCP[Any], ClientTransport]:
25
+ """A utility function to convert each entry of an MCP Config into a transport and server."""
26
+ from fastmcp.mcp_config import (
27
+ TransformingRemoteMCPServer,
28
+ TransformingStdioMCPServer,
29
+ )
30
 
31
+ server: FastMCP[Any]
32
+ transport: ClientTransport
33
 
34
+ if isinstance(mcp_server, TransformingRemoteMCPServer | TransformingStdioMCPServer):
35
+ server, transport = mcp_server._to_server_and_underlying_transport()
36
+ else:
37
+ transport = mcp_server.to_transport()
38
+ server = FastMCP.as_proxy(backend=transport)
39
+
40
+ return name, server, transport
 
 
 
 
tests/client/test_stdio.py CHANGED
@@ -1,11 +1,29 @@
 
 
1
  import inspect
 
 
2
 
 
3
  import pytest
4
 
5
  from fastmcp import Client
6
  from fastmcp.client.transports import PythonStdioTransport, StdioTransport
7
 
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  class TestKeepAlive:
10
  # https://github.com/jlowin/fastmcp/issues/581
11
 
@@ -56,6 +74,77 @@ class TestKeepAlive:
56
 
57
  assert pid1 == pid2
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  async def test_keep_alive_false_starts_new_session_across_multiple_calls(
60
  self, stdio_script
61
  ):
 
1
+ import asyncio
2
+ import gc
3
  import inspect
4
+ import os
5
+ import weakref
6
 
7
+ import psutil
8
  import pytest
9
 
10
  from fastmcp import Client
11
  from fastmcp.client.transports import PythonStdioTransport, StdioTransport
12
 
13
 
14
+ def running_under_debugger():
15
+ return os.environ.get("DEBUGPY_RUNNING") == "true"
16
+
17
+
18
+ def gc_collect_harder():
19
+ gc.collect()
20
+ gc.collect()
21
+ gc.collect()
22
+ gc.collect()
23
+ gc.collect()
24
+ gc.collect()
25
+
26
+
27
  class TestKeepAlive:
28
  # https://github.com/jlowin/fastmcp/issues/581
29
 
 
74
 
75
  assert pid1 == pid2
76
 
77
+ @pytest.mark.skipif(
78
+ running_under_debugger(), reason="Debugger holds a reference to the transport"
79
+ )
80
+ async def test_keep_alive_true_exit_scope_kills_transport(self, stdio_script):
81
+ transport_weak_ref: weakref.ref[PythonStdioTransport] | None = None
82
+
83
+ async def test_server():
84
+ transport = PythonStdioTransport(script_path=stdio_script, keep_alive=True)
85
+ nonlocal transport_weak_ref
86
+ transport_weak_ref = weakref.ref(transport)
87
+ async with transport.connect_session():
88
+ pass
89
+
90
+ await test_server()
91
+
92
+ gc_collect_harder()
93
+
94
+ # This test will fail while debugging because the debugger holds a reference to the underlying transport
95
+ assert transport_weak_ref
96
+ transport = transport_weak_ref()
97
+ assert transport is None
98
+
99
+ @pytest.mark.skipif(
100
+ running_under_debugger(), reason="Debugger holds a reference to the transport"
101
+ )
102
+ async def test_keep_alive_true_exit_scope_kills_client(self, stdio_script):
103
+ pid: int | None = None
104
+
105
+ async def test_server():
106
+ transport = PythonStdioTransport(script_path=stdio_script, keep_alive=True)
107
+ client = Client(transport=transport)
108
+
109
+ assert client.transport.keep_alive is True
110
+
111
+ async with client:
112
+ result1 = await client.call_tool("pid")
113
+ nonlocal pid
114
+ pid = result1.data
115
+
116
+ await test_server()
117
+
118
+ gc_collect_harder()
119
+
120
+ # This test may fail/hang while debugging because the debugger holds a reference to the underlying transport
121
+
122
+ with pytest.raises(psutil.NoSuchProcess):
123
+ while True:
124
+ psutil.Process(pid)
125
+ await asyncio.sleep(0.1)
126
+
127
+ async def test_keep_alive_false_exit_scope_kills_server(self, stdio_script):
128
+ pid: int | None = None
129
+
130
+ async def test_server():
131
+ transport = PythonStdioTransport(script_path=stdio_script, keep_alive=False)
132
+ client = Client(transport=transport)
133
+ assert client.transport.keep_alive is False
134
+ async with client:
135
+ result1 = await client.call_tool("pid")
136
+ nonlocal pid
137
+ pid = result1.data
138
+
139
+ del client
140
+
141
+ await test_server()
142
+
143
+ with pytest.raises(psutil.NoSuchProcess):
144
+ while True:
145
+ psutil.Process(pid)
146
+ await asyncio.sleep(0.1)
147
+
148
  async def test_keep_alive_false_starts_new_session_across_multiple_calls(
149
  self, stdio_script
150
  ):
tests/test_mcp_config.py CHANGED
@@ -1,10 +1,14 @@
 
 
1
  import inspect
2
  import logging
 
3
  import tempfile
4
  from collections.abc import AsyncGenerator
5
  from pathlib import Path
6
  from typing import Any
7
 
 
8
  import pytest
9
 
10
  from fastmcp.client.auth.bearer import BearerAuth
@@ -29,6 +33,19 @@ from fastmcp.mcp_config import (
29
  from fastmcp.tools.tool import Tool as FastMCPTool
30
 
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  def test_parse_single_stdio_config():
33
  config = {
34
  "mcpServers": {
@@ -225,6 +242,122 @@ async def test_multi_client(tmp_path: Path):
225
  assert result_2.data == 3
226
 
227
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  async def test_remote_config_default_no_auth():
229
  config = {
230
  "mcpServers": {
 
1
+ import asyncio
2
+ import gc
3
  import inspect
4
  import logging
5
+ import os
6
  import tempfile
7
  from collections.abc import AsyncGenerator
8
  from pathlib import Path
9
  from typing import Any
10
 
11
+ import psutil
12
  import pytest
13
 
14
  from fastmcp.client.auth.bearer import BearerAuth
 
33
  from fastmcp.tools.tool import Tool as FastMCPTool
34
 
35
 
36
+ def running_under_debugger():
37
+ return os.environ.get("DEBUGPY_RUNNING") == "true"
38
+
39
+
40
+ def gc_collect_harder():
41
+ gc.collect()
42
+ gc.collect()
43
+ gc.collect()
44
+ gc.collect()
45
+ gc.collect()
46
+ gc.collect()
47
+
48
+
49
  def test_parse_single_stdio_config():
50
  config = {
51
  "mcpServers": {
 
242
  assert result_2.data == 3
243
 
244
 
245
+ @pytest.mark.skipif(
246
+ running_under_debugger(), reason="Debugger holds a reference to the transport"
247
+ )
248
+ async def test_multi_client_lifespan(tmp_path: Path):
249
+ pid_1: int | None = None
250
+ pid_2: int | None = None
251
+
252
+ async def test_server():
253
+ server_script = inspect.cleandoc("""
254
+ from fastmcp import FastMCP
255
+ import os
256
+
257
+ mcp = FastMCP()
258
+
259
+ @mcp.tool
260
+ def pid() -> int:
261
+ return os.getpid()
262
+
263
+ if __name__ == '__main__':
264
+ mcp.run()
265
+ """)
266
+
267
+ script_path = tmp_path / "test.py"
268
+ script_path.write_text(server_script)
269
+
270
+ config = {
271
+ "mcpServers": {
272
+ "test_1": {
273
+ "command": "python",
274
+ "args": [str(script_path)],
275
+ },
276
+ "test_2": {
277
+ "command": "python",
278
+ "args": [str(script_path)],
279
+ },
280
+ }
281
+ }
282
+ transport = MCPConfigTransport(config)
283
+ client = Client(transport)
284
+
285
+ async with client:
286
+ nonlocal pid_1
287
+ pid_1 = (await client.call_tool("test_1_pid")).data
288
+
289
+ nonlocal pid_2
290
+ pid_2 = (await client.call_tool("test_2_pid")).data
291
+
292
+ await test_server()
293
+
294
+ gc_collect_harder()
295
+
296
+ # This test will fail while debugging because the debugger holds a reference to the underlying transport
297
+
298
+ with pytest.raises(psutil.NoSuchProcess):
299
+ while True:
300
+ psutil.Process(pid_1)
301
+ await asyncio.sleep(0.1)
302
+
303
+ with pytest.raises(psutil.NoSuchProcess):
304
+ while True:
305
+ psutil.Process(pid_2)
306
+ await asyncio.sleep(0.1)
307
+
308
+
309
+ async def test_multi_client_force_close(tmp_path: Path):
310
+ server_script = inspect.cleandoc("""
311
+ from fastmcp import FastMCP
312
+ import os
313
+
314
+ mcp = FastMCP()
315
+
316
+ @mcp.tool
317
+ def pid() -> int:
318
+ return os.getpid()
319
+
320
+ if __name__ == '__main__':
321
+ mcp.run()
322
+ """)
323
+
324
+ script_path = tmp_path / "test.py"
325
+ script_path.write_text(server_script)
326
+
327
+ config = {
328
+ "mcpServers": {
329
+ "test_1": {
330
+ "command": "python",
331
+ "args": [str(script_path)],
332
+ },
333
+ "test_2": {
334
+ "command": "python",
335
+ "args": [str(script_path)],
336
+ },
337
+ }
338
+ }
339
+ transport = MCPConfigTransport(config)
340
+ client = Client(transport)
341
+
342
+ async with client:
343
+ pid_1 = (await client.call_tool("test_1_pid")).data
344
+ pid_2 = (await client.call_tool("test_2_pid")).data
345
+
346
+ await client.close()
347
+
348
+ gc_collect_harder()
349
+
350
+ with pytest.raises(psutil.NoSuchProcess):
351
+ process = psutil.Process(pid_1)
352
+
353
+ assert not process
354
+
355
+ with pytest.raises(psutil.NoSuchProcess):
356
+ process = psutil.Process(pid_2)
357
+
358
+ assert not process
359
+
360
+
361
  async def test_remote_config_default_no_auth():
362
  config = {
363
  "mcpServers": {
uv.lock CHANGED
The diff for this file is too large to render. See raw diff