fastmcp 2.2.5__py3-none-any.whl → 2.2.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastmcp/__init__.py +1 -0
- fastmcp/client/base.py +0 -1
- fastmcp/client/client.py +255 -49
- fastmcp/client/logging.py +13 -0
- fastmcp/client/sampling.py +2 -0
- fastmcp/client/transports.py +37 -4
- fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py +1 -3
- fastmcp/prompts/prompt.py +8 -4
- fastmcp/resources/template.py +5 -2
- fastmcp/resources/types.py +4 -7
- fastmcp/server/context.py +27 -14
- fastmcp/server/openapi.py +94 -32
- fastmcp/server/proxy.py +4 -3
- fastmcp/server/server.py +261 -30
- fastmcp/settings.py +7 -0
- fastmcp/tools/tool.py +24 -22
- fastmcp/tools/tool_manager.py +16 -3
- fastmcp/utilities/http.py +44 -0
- fastmcp/utilities/openapi.py +147 -36
- fastmcp/utilities/types.py +29 -1
- {fastmcp-2.2.5.dist-info → fastmcp-2.2.7.dist-info}/METADATA +4 -4
- {fastmcp-2.2.5.dist-info → fastmcp-2.2.7.dist-info}/RECORD +25 -23
- {fastmcp-2.2.5.dist-info → fastmcp-2.2.7.dist-info}/WHEEL +0 -0
- {fastmcp-2.2.5.dist-info → fastmcp-2.2.7.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.2.5.dist-info → fastmcp-2.2.7.dist-info}/licenses/LICENSE +0 -0
fastmcp/__init__.py
CHANGED
fastmcp/client/base.py
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
fastmcp/client/client.py
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
from contextlib import AbstractAsyncContextManager
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Any,
|
|
4
|
+
from typing import Any, cast
|
|
5
5
|
|
|
6
6
|
import mcp.types
|
|
7
7
|
from mcp import ClientSession
|
|
8
|
-
from mcp.client.session import (
|
|
9
|
-
LoggingFnT,
|
|
10
|
-
MessageHandlerFnT,
|
|
11
|
-
)
|
|
12
8
|
from pydantic import AnyUrl
|
|
13
9
|
|
|
10
|
+
from fastmcp.client.logging import LogHandler, MessageHandler
|
|
14
11
|
from fastmcp.client.roots import (
|
|
15
12
|
RootsHandler,
|
|
16
13
|
RootsList,
|
|
@@ -22,7 +19,14 @@ from fastmcp.server import FastMCP
|
|
|
22
19
|
|
|
23
20
|
from .transports import ClientTransport, SessionKwargs, infer_transport
|
|
24
21
|
|
|
25
|
-
__all__ = [
|
|
22
|
+
__all__ = [
|
|
23
|
+
"Client",
|
|
24
|
+
"RootsHandler",
|
|
25
|
+
"RootsList",
|
|
26
|
+
"LogHandler",
|
|
27
|
+
"MessageHandler",
|
|
28
|
+
"SamplingHandler",
|
|
29
|
+
]
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
class Client:
|
|
@@ -35,12 +39,12 @@ class Client:
|
|
|
35
39
|
|
|
36
40
|
def __init__(
|
|
37
41
|
self,
|
|
38
|
-
transport: ClientTransport | FastMCP | AnyUrl | Path | str,
|
|
42
|
+
transport: ClientTransport | FastMCP | AnyUrl | Path | dict[str, Any] | str,
|
|
39
43
|
# Common args
|
|
40
44
|
roots: RootsList | RootsHandler | None = None,
|
|
41
45
|
sampling_handler: SamplingHandler | None = None,
|
|
42
|
-
log_handler:
|
|
43
|
-
message_handler:
|
|
46
|
+
log_handler: LogHandler | None = None,
|
|
47
|
+
message_handler: MessageHandler | None = None,
|
|
44
48
|
read_timeout_seconds: datetime.timedelta | None = None,
|
|
45
49
|
):
|
|
46
50
|
self.transport = infer_transport(transport)
|
|
@@ -103,6 +107,7 @@ class Client:
|
|
|
103
107
|
self._session = None
|
|
104
108
|
|
|
105
109
|
# --- MCP Client Methods ---
|
|
110
|
+
|
|
106
111
|
async def ping(self) -> None:
|
|
107
112
|
"""Send a ping request."""
|
|
108
113
|
await self.session.send_ping()
|
|
@@ -124,23 +129,100 @@ class Client:
|
|
|
124
129
|
"""Send a roots/list_changed notification."""
|
|
125
130
|
await self.session.send_roots_list_changed()
|
|
126
131
|
|
|
127
|
-
|
|
128
|
-
|
|
132
|
+
# --- Resources ---
|
|
133
|
+
|
|
134
|
+
async def list_resources_mcp(self) -> mcp.types.ListResourcesResult:
|
|
135
|
+
"""Send a resources/list request and return the complete MCP protocol result.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
mcp.types.ListResourcesResult: The complete response object from the protocol,
|
|
139
|
+
containing the list of resources and any additional metadata.
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
RuntimeError: If called while the client is not connected.
|
|
143
|
+
"""
|
|
129
144
|
result = await self.session.list_resources()
|
|
145
|
+
return result
|
|
146
|
+
|
|
147
|
+
async def list_resources(self) -> list[mcp.types.Resource]:
|
|
148
|
+
"""Retrieve a list of resources available on the server.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
list[mcp.types.Resource]: A list of Resource objects.
|
|
152
|
+
|
|
153
|
+
Raises:
|
|
154
|
+
RuntimeError: If called while the client is not connected.
|
|
155
|
+
"""
|
|
156
|
+
result = await self.list_resources_mcp()
|
|
130
157
|
return result.resources
|
|
131
158
|
|
|
132
|
-
async def
|
|
133
|
-
|
|
159
|
+
async def list_resource_templates_mcp(
|
|
160
|
+
self,
|
|
161
|
+
) -> mcp.types.ListResourceTemplatesResult:
|
|
162
|
+
"""Send a resources/listResourceTemplates request and return the complete MCP protocol result.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
mcp.types.ListResourceTemplatesResult: The complete response object from the protocol,
|
|
166
|
+
containing the list of resource templates and any additional metadata.
|
|
167
|
+
|
|
168
|
+
Raises:
|
|
169
|
+
RuntimeError: If called while the client is not connected.
|
|
170
|
+
"""
|
|
134
171
|
result = await self.session.list_resource_templates()
|
|
172
|
+
return result
|
|
173
|
+
|
|
174
|
+
async def list_resource_templates(
|
|
175
|
+
self,
|
|
176
|
+
) -> list[mcp.types.ResourceTemplate]:
|
|
177
|
+
"""Retrieve a list of resource templates available on the server.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
list[mcp.types.ResourceTemplate]: A list of ResourceTemplate objects.
|
|
181
|
+
|
|
182
|
+
Raises:
|
|
183
|
+
RuntimeError: If called while the client is not connected.
|
|
184
|
+
"""
|
|
185
|
+
result = await self.list_resource_templates_mcp()
|
|
135
186
|
return result.resourceTemplates
|
|
136
187
|
|
|
188
|
+
async def read_resource_mcp(
|
|
189
|
+
self, uri: AnyUrl | str
|
|
190
|
+
) -> mcp.types.ReadResourceResult:
|
|
191
|
+
"""Send a resources/read request and return the complete MCP protocol result.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
uri (AnyUrl | str): The URI of the resource to read. Can be a string or an AnyUrl object.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
mcp.types.ReadResourceResult: The complete response object from the protocol,
|
|
198
|
+
containing the resource contents and any additional metadata.
|
|
199
|
+
|
|
200
|
+
Raises:
|
|
201
|
+
RuntimeError: If called while the client is not connected.
|
|
202
|
+
"""
|
|
203
|
+
if isinstance(uri, str):
|
|
204
|
+
uri = AnyUrl(uri) # Ensure AnyUrl
|
|
205
|
+
result = await self.session.read_resource(uri)
|
|
206
|
+
return result
|
|
207
|
+
|
|
137
208
|
async def read_resource(
|
|
138
209
|
self, uri: AnyUrl | str
|
|
139
210
|
) -> list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents]:
|
|
140
|
-
"""
|
|
211
|
+
"""Read the contents of a resource or resolved template.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
uri (AnyUrl | str): The URI of the resource to read. Can be a string or an AnyUrl object.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents]: A list of content
|
|
218
|
+
objects, typically containing either text or binary data.
|
|
219
|
+
|
|
220
|
+
Raises:
|
|
221
|
+
RuntimeError: If called while the client is not connected.
|
|
222
|
+
"""
|
|
141
223
|
if isinstance(uri, str):
|
|
142
224
|
uri = AnyUrl(uri) # Ensure AnyUrl
|
|
143
|
-
result = await self.
|
|
225
|
+
result = await self.read_resource_mcp(uri)
|
|
144
226
|
return result.contents
|
|
145
227
|
|
|
146
228
|
# async def subscribe_resource(self, uri: AnyUrl | str) -> None:
|
|
@@ -155,66 +237,190 @@ class Client:
|
|
|
155
237
|
# uri = AnyUrl(uri)
|
|
156
238
|
# await self.session.unsubscribe_resource(uri)
|
|
157
239
|
|
|
158
|
-
|
|
159
|
-
|
|
240
|
+
# --- Prompts ---
|
|
241
|
+
|
|
242
|
+
async def list_prompts_mcp(self) -> mcp.types.ListPromptsResult:
|
|
243
|
+
"""Send a prompts/list request and return the complete MCP protocol result.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
mcp.types.ListPromptsResult: The complete response object from the protocol,
|
|
247
|
+
containing the list of prompts and any additional metadata.
|
|
248
|
+
|
|
249
|
+
Raises:
|
|
250
|
+
RuntimeError: If called while the client is not connected.
|
|
251
|
+
"""
|
|
160
252
|
result = await self.session.list_prompts()
|
|
253
|
+
return result
|
|
254
|
+
|
|
255
|
+
async def list_prompts(self) -> list[mcp.types.Prompt]:
|
|
256
|
+
"""Retrieve a list of prompts available on the server.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
list[mcp.types.Prompt]: A list of Prompt objects.
|
|
260
|
+
|
|
261
|
+
Raises:
|
|
262
|
+
RuntimeError: If called while the client is not connected.
|
|
263
|
+
"""
|
|
264
|
+
result = await self.list_prompts_mcp()
|
|
161
265
|
return result.prompts
|
|
162
266
|
|
|
267
|
+
# --- Prompt ---
|
|
268
|
+
async def get_prompt_mcp(
|
|
269
|
+
self, name: str, arguments: dict[str, str] | None = None
|
|
270
|
+
) -> mcp.types.GetPromptResult:
|
|
271
|
+
"""Send a prompts/get request and return the complete MCP protocol result.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
name (str): The name of the prompt to retrieve.
|
|
275
|
+
arguments (dict[str, str] | None, optional): Arguments to pass to the prompt. Defaults to None.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
mcp.types.GetPromptResult: The complete response object from the protocol,
|
|
279
|
+
containing the prompt messages and any additional metadata.
|
|
280
|
+
|
|
281
|
+
Raises:
|
|
282
|
+
RuntimeError: If called while the client is not connected.
|
|
283
|
+
"""
|
|
284
|
+
result = await self.session.get_prompt(name=name, arguments=arguments)
|
|
285
|
+
return result
|
|
286
|
+
|
|
163
287
|
async def get_prompt(
|
|
164
288
|
self, name: str, arguments: dict[str, str] | None = None
|
|
165
289
|
) -> list[mcp.types.PromptMessage]:
|
|
166
|
-
"""
|
|
167
|
-
|
|
290
|
+
"""Retrieve a rendered prompt message list from the server.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
name (str): The name of the prompt to retrieve.
|
|
294
|
+
arguments (dict[str, str] | None, optional): Arguments to pass to the prompt. Defaults to None.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
list[mcp.types.PromptMessage]: A list of prompt messages.
|
|
298
|
+
|
|
299
|
+
Raises:
|
|
300
|
+
RuntimeError: If called while the client is not connected.
|
|
301
|
+
"""
|
|
302
|
+
result = await self.get_prompt_mcp(name=name, arguments=arguments)
|
|
168
303
|
return result.messages
|
|
169
304
|
|
|
305
|
+
# --- Completion ---
|
|
306
|
+
|
|
307
|
+
async def complete_mcp(
|
|
308
|
+
self,
|
|
309
|
+
ref: mcp.types.ResourceReference | mcp.types.PromptReference,
|
|
310
|
+
argument: dict[str, str],
|
|
311
|
+
) -> mcp.types.CompleteResult:
|
|
312
|
+
"""Send a completion request and return the complete MCP protocol result.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
ref (mcp.types.ResourceReference | mcp.types.PromptReference): The reference to complete.
|
|
316
|
+
argument (dict[str, str]): Arguments to pass to the completion request.
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
mcp.types.CompleteResult: The complete response object from the protocol,
|
|
320
|
+
containing the completion and any additional metadata.
|
|
321
|
+
|
|
322
|
+
Raises:
|
|
323
|
+
RuntimeError: If called while the client is not connected.
|
|
324
|
+
"""
|
|
325
|
+
result = await self.session.complete(ref=ref, argument=argument)
|
|
326
|
+
return result
|
|
327
|
+
|
|
170
328
|
async def complete(
|
|
171
329
|
self,
|
|
172
330
|
ref: mcp.types.ResourceReference | mcp.types.PromptReference,
|
|
173
331
|
argument: dict[str, str],
|
|
174
332
|
) -> mcp.types.Completion:
|
|
175
|
-
"""Send a completion request.
|
|
176
|
-
|
|
333
|
+
"""Send a completion request to the server.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
ref (mcp.types.ResourceReference | mcp.types.PromptReference): The reference to complete.
|
|
337
|
+
argument (dict[str, str]): Arguments to pass to the completion request.
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
mcp.types.Completion: The completion object.
|
|
341
|
+
|
|
342
|
+
Raises:
|
|
343
|
+
RuntimeError: If called while the client is not connected.
|
|
344
|
+
"""
|
|
345
|
+
result = await self.complete_mcp(ref=ref, argument=argument)
|
|
177
346
|
return result.completion
|
|
178
347
|
|
|
179
|
-
|
|
180
|
-
|
|
348
|
+
# --- Tools ---
|
|
349
|
+
|
|
350
|
+
async def list_tools_mcp(self) -> mcp.types.ListToolsResult:
|
|
351
|
+
"""Send a tools/list request and return the complete MCP protocol result.
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
mcp.types.ListToolsResult: The complete response object from the protocol,
|
|
355
|
+
containing the list of tools and any additional metadata.
|
|
356
|
+
|
|
357
|
+
Raises:
|
|
358
|
+
RuntimeError: If called while the client is not connected.
|
|
359
|
+
"""
|
|
181
360
|
result = await self.session.list_tools()
|
|
361
|
+
return result
|
|
362
|
+
|
|
363
|
+
async def list_tools(self) -> list[mcp.types.Tool]:
|
|
364
|
+
"""Retrieve a list of tools available on the server.
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
list[mcp.types.Tool]: A list of Tool objects.
|
|
368
|
+
|
|
369
|
+
Raises:
|
|
370
|
+
RuntimeError: If called while the client is not connected.
|
|
371
|
+
"""
|
|
372
|
+
result = await self.list_tools_mcp()
|
|
182
373
|
return result.tools
|
|
183
374
|
|
|
184
|
-
|
|
375
|
+
# --- Call Tool ---
|
|
376
|
+
|
|
377
|
+
async def call_tool_mcp(
|
|
378
|
+
self, name: str, arguments: dict[str, Any]
|
|
379
|
+
) -> mcp.types.CallToolResult:
|
|
380
|
+
"""Send a tools/call request and return the complete MCP protocol result.
|
|
381
|
+
|
|
382
|
+
This method returns the raw CallToolResult object, which includes an isError flag
|
|
383
|
+
and other metadata. It does not raise an exception if the tool call results in an error.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
name (str): The name of the tool to call.
|
|
387
|
+
arguments (dict[str, Any]): Arguments to pass to the tool.
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
mcp.types.CallToolResult: The complete response object from the protocol,
|
|
391
|
+
containing the tool result and any additional metadata.
|
|
392
|
+
|
|
393
|
+
Raises:
|
|
394
|
+
RuntimeError: If called while the client is not connected.
|
|
395
|
+
"""
|
|
396
|
+
result = await self.session.call_tool(name=name, arguments=arguments)
|
|
397
|
+
return result
|
|
398
|
+
|
|
185
399
|
async def call_tool(
|
|
186
400
|
self,
|
|
187
401
|
name: str,
|
|
188
402
|
arguments: dict[str, Any] | None = None,
|
|
189
|
-
_return_raw_result: Literal[False] = False,
|
|
190
403
|
) -> list[
|
|
191
404
|
mcp.types.TextContent | mcp.types.ImageContent | mcp.types.EmbeddedResource
|
|
192
|
-
]:
|
|
405
|
+
]:
|
|
406
|
+
"""Call a tool on the server.
|
|
193
407
|
|
|
194
|
-
|
|
195
|
-
async def call_tool(
|
|
196
|
-
self,
|
|
197
|
-
name: str,
|
|
198
|
-
arguments: dict[str, Any] | None = None,
|
|
199
|
-
_return_raw_result: Literal[True] = True,
|
|
200
|
-
) -> mcp.types.CallToolResult: ...
|
|
408
|
+
Unlike call_tool_mcp, this method raises a ClientError if the tool call results in an error.
|
|
201
409
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
"""
|
|
214
|
-
result = await self.
|
|
215
|
-
if
|
|
216
|
-
return result
|
|
217
|
-
elif result.isError:
|
|
410
|
+
Args:
|
|
411
|
+
name (str): The name of the tool to call.
|
|
412
|
+
arguments (dict[str, Any] | None, optional): Arguments to pass to the tool. Defaults to None.
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
list[mcp.types.TextContent | mcp.types.ImageContent | mcp.types.EmbeddedResource]:
|
|
416
|
+
The content returned by the tool.
|
|
417
|
+
|
|
418
|
+
Raises:
|
|
419
|
+
ClientError: If the tool call results in an error.
|
|
420
|
+
RuntimeError: If called while the client is not connected.
|
|
421
|
+
"""
|
|
422
|
+
result = await self.call_tool_mcp(name=name, arguments=arguments or {})
|
|
423
|
+
if result.isError:
|
|
218
424
|
msg = cast(mcp.types.TextContent, result.content[0]).text
|
|
219
425
|
raise ClientError(msg)
|
|
220
426
|
return result.content
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from typing import TypeAlias
|
|
2
|
+
|
|
3
|
+
from mcp.client.session import (
|
|
4
|
+
LoggingFnT,
|
|
5
|
+
MessageHandlerFnT,
|
|
6
|
+
)
|
|
7
|
+
from mcp.types import LoggingMessageNotificationParams
|
|
8
|
+
|
|
9
|
+
LogMessage: TypeAlias = LoggingMessageNotificationParams
|
|
10
|
+
LogHandler: TypeAlias = LoggingFnT
|
|
11
|
+
MessageHandler: TypeAlias = MessageHandlerFnT
|
|
12
|
+
|
|
13
|
+
__all__ = ["LogMessage", "LogHandler", "MessageHandler"]
|
fastmcp/client/sampling.py
CHANGED
|
@@ -9,6 +9,8 @@ from mcp.shared.context import LifespanContextT, RequestContext
|
|
|
9
9
|
from mcp.types import CreateMessageRequestParams as SamplingParams
|
|
10
10
|
from mcp.types import SamplingMessage
|
|
11
11
|
|
|
12
|
+
__all__ = ["SamplingMessage", "SamplingParams", "MessageResult", "SamplingHandler"]
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
class MessageResult(CreateMessageResult):
|
|
14
16
|
role: mcp.types.Role = "assistant"
|
fastmcp/client/transports.py
CHANGED
|
@@ -6,9 +6,7 @@ import shutil
|
|
|
6
6
|
import sys
|
|
7
7
|
from collections.abc import AsyncIterator
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import
|
|
10
|
-
TypedDict,
|
|
11
|
-
)
|
|
9
|
+
from typing import Any, TypedDict
|
|
12
10
|
|
|
13
11
|
from exceptiongroup import BaseExceptionGroup, catch
|
|
14
12
|
from mcp import ClientSession, McpError, StdioServerParameters
|
|
@@ -416,7 +414,7 @@ class FastMCPTransport(ClientTransport):
|
|
|
416
414
|
|
|
417
415
|
|
|
418
416
|
def infer_transport(
|
|
419
|
-
transport: ClientTransport | FastMCPServer | AnyUrl | Path | str,
|
|
417
|
+
transport: ClientTransport | FastMCPServer | AnyUrl | Path | dict[str, Any] | str,
|
|
420
418
|
) -> ClientTransport:
|
|
421
419
|
"""
|
|
422
420
|
Infer the appropriate transport type from the given transport argument.
|
|
@@ -450,6 +448,41 @@ def infer_transport(
|
|
|
450
448
|
elif isinstance(transport, AnyUrl | str) and str(transport).startswith("ws"):
|
|
451
449
|
return WSTransport(url=transport)
|
|
452
450
|
|
|
451
|
+
## if the transport is a config dict
|
|
452
|
+
elif isinstance(transport, dict):
|
|
453
|
+
if "mcpServers" not in transport:
|
|
454
|
+
raise ValueError("Invalid transport dictionary: missing 'mcpServers' key")
|
|
455
|
+
else:
|
|
456
|
+
server = transport["mcpServers"]
|
|
457
|
+
if len(list(server.keys())) > 1:
|
|
458
|
+
raise ValueError(
|
|
459
|
+
"Invalid transport dictionary: multiple servers found - only one expected"
|
|
460
|
+
)
|
|
461
|
+
server_name = list(server.keys())[0]
|
|
462
|
+
# Stdio transport
|
|
463
|
+
if "command" in server[server_name] and "args" in server[server_name]:
|
|
464
|
+
return StdioTransport(
|
|
465
|
+
command=server[server_name]["command"],
|
|
466
|
+
args=server[server_name]["args"],
|
|
467
|
+
env=server[server_name].get("env", None),
|
|
468
|
+
cwd=server[server_name].get("cwd", None),
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
# HTTP transport
|
|
472
|
+
elif "url" in server:
|
|
473
|
+
return SSETransport(
|
|
474
|
+
url=server["url"],
|
|
475
|
+
headers=server.get("headers", None),
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
# WebSocket transport
|
|
479
|
+
elif "ws_url" in server:
|
|
480
|
+
return WSTransport(
|
|
481
|
+
url=server["ws_url"],
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
raise ValueError("Cannot determine transport type from dictionary")
|
|
485
|
+
|
|
453
486
|
# the transport is an unknown type
|
|
454
487
|
else:
|
|
455
488
|
raise ValueError(f"Could not infer a valid transport from: {transport}")
|
|
@@ -123,9 +123,7 @@ class BulkToolCaller(MCPMixin):
|
|
|
123
123
|
"""
|
|
124
124
|
|
|
125
125
|
async with Client(self.connection) as client:
|
|
126
|
-
result = await client.
|
|
127
|
-
name=tool, arguments=arguments, _return_raw_result=True
|
|
128
|
-
)
|
|
126
|
+
result = await client.call_tool_mcp(name=tool, arguments=arguments)
|
|
129
127
|
|
|
130
128
|
return CallToolRequestResult(
|
|
131
129
|
tool=tool,
|
fastmcp/prompts/prompt.py
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
from __future__ import annotations as _annotations
|
|
4
4
|
|
|
5
5
|
import inspect
|
|
6
|
-
import json
|
|
7
6
|
from collections.abc import Awaitable, Callable, Sequence
|
|
8
7
|
from typing import TYPE_CHECKING, Annotated, Any, Literal
|
|
9
8
|
|
|
@@ -13,7 +12,10 @@ from mcp.types import Prompt as MCPPrompt
|
|
|
13
12
|
from mcp.types import PromptArgument as MCPPromptArgument
|
|
14
13
|
from pydantic import BaseModel, BeforeValidator, Field, TypeAdapter, validate_call
|
|
15
14
|
|
|
16
|
-
from fastmcp.utilities.types import
|
|
15
|
+
from fastmcp.utilities.types import (
|
|
16
|
+
_convert_set_defaults,
|
|
17
|
+
is_class_member_of_type,
|
|
18
|
+
)
|
|
17
19
|
|
|
18
20
|
if TYPE_CHECKING:
|
|
19
21
|
from mcp.server.session import ServerSessionT
|
|
@@ -115,7 +117,7 @@ class Prompt(BaseModel):
|
|
|
115
117
|
else:
|
|
116
118
|
sig = inspect.signature(fn)
|
|
117
119
|
for param_name, param in sig.parameters.items():
|
|
118
|
-
if param.annotation
|
|
120
|
+
if is_class_member_of_type(param.annotation, Context):
|
|
119
121
|
context_kwarg = param_name
|
|
120
122
|
break
|
|
121
123
|
|
|
@@ -192,7 +194,9 @@ class Prompt(BaseModel):
|
|
|
192
194
|
content = TextContent(type="text", text=msg)
|
|
193
195
|
messages.append(Message(role="user", content=content))
|
|
194
196
|
else:
|
|
195
|
-
content =
|
|
197
|
+
content = pydantic_core.to_json(
|
|
198
|
+
msg, fallback=str, indent=2
|
|
199
|
+
).decode()
|
|
196
200
|
messages.append(Message(role="user", content=content))
|
|
197
201
|
except Exception:
|
|
198
202
|
raise ValueError(
|
fastmcp/resources/template.py
CHANGED
|
@@ -20,7 +20,10 @@ from pydantic import (
|
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
from fastmcp.resources.types import FunctionResource, Resource
|
|
23
|
-
from fastmcp.utilities.types import
|
|
23
|
+
from fastmcp.utilities.types import (
|
|
24
|
+
_convert_set_defaults,
|
|
25
|
+
is_class_member_of_type,
|
|
26
|
+
)
|
|
24
27
|
|
|
25
28
|
if TYPE_CHECKING:
|
|
26
29
|
from mcp.server.session import ServerSessionT
|
|
@@ -113,7 +116,7 @@ class ResourceTemplate(BaseModel):
|
|
|
113
116
|
else:
|
|
114
117
|
sig = inspect.signature(fn)
|
|
115
118
|
for param_name, param in sig.parameters.items():
|
|
116
|
-
if param.annotation
|
|
119
|
+
if is_class_member_of_type(param.annotation, Context):
|
|
117
120
|
context_kwarg = param_name
|
|
118
121
|
break
|
|
119
122
|
|
fastmcp/resources/types.py
CHANGED
|
@@ -97,15 +97,12 @@ class FunctionResource(Resource):
|
|
|
97
97
|
|
|
98
98
|
if isinstance(result, Resource):
|
|
99
99
|
return await result.read(context=context)
|
|
100
|
-
|
|
100
|
+
elif isinstance(result, bytes):
|
|
101
101
|
return result
|
|
102
|
-
|
|
102
|
+
elif isinstance(result, str):
|
|
103
103
|
return result
|
|
104
|
-
|
|
105
|
-
return
|
|
106
|
-
except (TypeError, pydantic_core.PydanticSerializationError):
|
|
107
|
-
# If JSON serialization fails, try str()
|
|
108
|
-
return str(result)
|
|
104
|
+
else:
|
|
105
|
+
return pydantic_core.to_json(result, fallback=str, indent=2).decode()
|
|
109
106
|
except Exception as e:
|
|
110
107
|
raise ValueError(f"Error reading resource {self.uri}: {e}")
|
|
111
108
|
|
fastmcp/server/context.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations as _annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any, Generic
|
|
3
|
+
from typing import Any, Generic
|
|
4
4
|
|
|
5
|
+
from mcp import LoggingLevel
|
|
5
6
|
from mcp.server.lowlevel.helper_types import ReadResourceContents
|
|
6
7
|
from mcp.server.session import ServerSessionT
|
|
7
8
|
from mcp.shared.context import LifespanContextT, RequestContext
|
|
@@ -12,10 +13,12 @@ from mcp.types import (
|
|
|
12
13
|
SamplingMessage,
|
|
13
14
|
TextContent,
|
|
14
15
|
)
|
|
15
|
-
from pydantic import BaseModel
|
|
16
|
+
from pydantic import BaseModel, ConfigDict
|
|
16
17
|
from pydantic.networks import AnyUrl
|
|
18
|
+
from starlette.requests import Request
|
|
17
19
|
|
|
18
20
|
from fastmcp.server.server import FastMCP
|
|
21
|
+
from fastmcp.utilities.http import get_current_starlette_request
|
|
19
22
|
from fastmcp.utilities.logging import get_logger
|
|
20
23
|
|
|
21
24
|
logger = get_logger(__name__)
|
|
@@ -58,6 +61,8 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT]):
|
|
|
58
61
|
_request_context: RequestContext[ServerSessionT, LifespanContextT] | None
|
|
59
62
|
_fastmcp: FastMCP | None
|
|
60
63
|
|
|
64
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
65
|
+
|
|
61
66
|
def __init__(
|
|
62
67
|
self,
|
|
63
68
|
*,
|
|
@@ -122,19 +127,20 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT]):
|
|
|
122
127
|
|
|
123
128
|
async def log(
|
|
124
129
|
self,
|
|
125
|
-
level: Literal["debug", "info", "warning", "error"],
|
|
126
130
|
message: str,
|
|
127
|
-
|
|
131
|
+
level: LoggingLevel | None = None,
|
|
128
132
|
logger_name: str | None = None,
|
|
129
133
|
) -> None:
|
|
130
134
|
"""Send a log message to the client.
|
|
131
135
|
|
|
132
136
|
Args:
|
|
133
|
-
level: Log level (debug, info, warning, error)
|
|
134
137
|
message: Log message
|
|
138
|
+
level: Optional log level. One of "debug", "info", "notice", "warning", "error", "critical",
|
|
139
|
+
"alert", or "emergency". Default is "info".
|
|
135
140
|
logger_name: Optional logger name
|
|
136
|
-
**extra: Additional structured data to include
|
|
137
141
|
"""
|
|
142
|
+
if level is None:
|
|
143
|
+
level = "info"
|
|
138
144
|
await self.request_context.session.send_log_message(
|
|
139
145
|
level=level, data=message, logger=logger_name
|
|
140
146
|
)
|
|
@@ -159,21 +165,21 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT]):
|
|
|
159
165
|
return self.request_context.session
|
|
160
166
|
|
|
161
167
|
# Convenience methods for common log levels
|
|
162
|
-
async def debug(self, message: str,
|
|
168
|
+
async def debug(self, message: str, logger_name: str | None = None) -> None:
|
|
163
169
|
"""Send a debug log message."""
|
|
164
|
-
await self.log("debug", message,
|
|
170
|
+
await self.log(level="debug", message=message, logger_name=logger_name)
|
|
165
171
|
|
|
166
|
-
async def info(self, message: str,
|
|
172
|
+
async def info(self, message: str, logger_name: str | None = None) -> None:
|
|
167
173
|
"""Send an info log message."""
|
|
168
|
-
await self.log("info", message,
|
|
174
|
+
await self.log(level="info", message=message, logger_name=logger_name)
|
|
169
175
|
|
|
170
|
-
async def warning(self, message: str,
|
|
176
|
+
async def warning(self, message: str, logger_name: str | None = None) -> None:
|
|
171
177
|
"""Send a warning log message."""
|
|
172
|
-
await self.log("warning", message,
|
|
178
|
+
await self.log(level="warning", message=message, logger_name=logger_name)
|
|
173
179
|
|
|
174
|
-
async def error(self, message: str,
|
|
180
|
+
async def error(self, message: str, logger_name: str | None = None) -> None:
|
|
175
181
|
"""Send an error log message."""
|
|
176
|
-
await self.log("error", message,
|
|
182
|
+
await self.log(level="error", message=message, logger_name=logger_name)
|
|
177
183
|
|
|
178
184
|
async def list_roots(self) -> list[Root]:
|
|
179
185
|
"""List the roots available to the server, as indicated by the client."""
|
|
@@ -220,3 +226,10 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT]):
|
|
|
220
226
|
)
|
|
221
227
|
|
|
222
228
|
return result.content
|
|
229
|
+
|
|
230
|
+
def get_http_request(self) -> Request:
|
|
231
|
+
"""Get the active starlette request."""
|
|
232
|
+
request = get_current_starlette_request()
|
|
233
|
+
if request is None:
|
|
234
|
+
raise ValueError("Request is not available outside a Starlette request")
|
|
235
|
+
return request
|