fastmcp 2.2.6__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/client/client.py +243 -41
- 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 +12 -1
- fastmcp/server/openapi.py +28 -5
- fastmcp/server/proxy.py +3 -2
- fastmcp/server/server.py +243 -18
- 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.6.dist-info → fastmcp-2.2.7.dist-info}/METADATA +3 -3
- {fastmcp-2.2.6.dist-info → fastmcp-2.2.7.dist-info}/RECORD +20 -19
- {fastmcp-2.2.6.dist-info → fastmcp-2.2.7.dist-info}/WHEEL +0 -0
- {fastmcp-2.2.6.dist-info → fastmcp-2.2.7.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.2.6.dist-info → fastmcp-2.2.7.dist-info}/licenses/LICENSE +0 -0
fastmcp/client/client.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
|
|
@@ -107,6 +107,7 @@ class Client:
|
|
|
107
107
|
self._session = None
|
|
108
108
|
|
|
109
109
|
# --- MCP Client Methods ---
|
|
110
|
+
|
|
110
111
|
async def ping(self) -> None:
|
|
111
112
|
"""Send a ping request."""
|
|
112
113
|
await self.session.send_ping()
|
|
@@ -128,23 +129,100 @@ class Client:
|
|
|
128
129
|
"""Send a roots/list_changed notification."""
|
|
129
130
|
await self.session.send_roots_list_changed()
|
|
130
131
|
|
|
131
|
-
|
|
132
|
-
|
|
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
|
+
"""
|
|
133
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()
|
|
134
157
|
return result.resources
|
|
135
158
|
|
|
136
|
-
async def
|
|
137
|
-
|
|
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
|
+
"""
|
|
138
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()
|
|
139
186
|
return result.resourceTemplates
|
|
140
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
|
+
|
|
141
208
|
async def read_resource(
|
|
142
209
|
self, uri: AnyUrl | str
|
|
143
210
|
) -> list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents]:
|
|
144
|
-
"""
|
|
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
|
+
"""
|
|
145
223
|
if isinstance(uri, str):
|
|
146
224
|
uri = AnyUrl(uri) # Ensure AnyUrl
|
|
147
|
-
result = await self.
|
|
225
|
+
result = await self.read_resource_mcp(uri)
|
|
148
226
|
return result.contents
|
|
149
227
|
|
|
150
228
|
# async def subscribe_resource(self, uri: AnyUrl | str) -> None:
|
|
@@ -159,66 +237,190 @@ class Client:
|
|
|
159
237
|
# uri = AnyUrl(uri)
|
|
160
238
|
# await self.session.unsubscribe_resource(uri)
|
|
161
239
|
|
|
162
|
-
|
|
163
|
-
|
|
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
|
+
"""
|
|
164
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()
|
|
165
265
|
return result.prompts
|
|
166
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
|
+
|
|
167
287
|
async def get_prompt(
|
|
168
288
|
self, name: str, arguments: dict[str, str] | None = None
|
|
169
289
|
) -> list[mcp.types.PromptMessage]:
|
|
170
|
-
"""
|
|
171
|
-
|
|
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)
|
|
172
303
|
return result.messages
|
|
173
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
|
+
|
|
174
328
|
async def complete(
|
|
175
329
|
self,
|
|
176
330
|
ref: mcp.types.ResourceReference | mcp.types.PromptReference,
|
|
177
331
|
argument: dict[str, str],
|
|
178
332
|
) -> mcp.types.Completion:
|
|
179
|
-
"""Send a completion request.
|
|
180
|
-
|
|
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)
|
|
181
346
|
return result.completion
|
|
182
347
|
|
|
183
|
-
|
|
184
|
-
|
|
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
|
+
"""
|
|
185
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()
|
|
186
373
|
return result.tools
|
|
187
374
|
|
|
188
|
-
|
|
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
|
+
|
|
189
399
|
async def call_tool(
|
|
190
400
|
self,
|
|
191
401
|
name: str,
|
|
192
402
|
arguments: dict[str, Any] | None = None,
|
|
193
|
-
_return_raw_result: Literal[False] = False,
|
|
194
403
|
) -> list[
|
|
195
404
|
mcp.types.TextContent | mcp.types.ImageContent | mcp.types.EmbeddedResource
|
|
196
|
-
]:
|
|
405
|
+
]:
|
|
406
|
+
"""Call a tool on the server.
|
|
197
407
|
|
|
198
|
-
|
|
199
|
-
async def call_tool(
|
|
200
|
-
self,
|
|
201
|
-
name: str,
|
|
202
|
-
arguments: dict[str, Any] | None = None,
|
|
203
|
-
_return_raw_result: Literal[True] = True,
|
|
204
|
-
) -> mcp.types.CallToolResult: ...
|
|
408
|
+
Unlike call_tool_mcp, this method raises a ClientError if the tool call results in an error.
|
|
205
409
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
"""
|
|
218
|
-
result = await self.
|
|
219
|
-
if
|
|
220
|
-
return result
|
|
221
|
-
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:
|
|
222
424
|
msg = cast(mcp.types.TextContent, result.content[0]).text
|
|
223
425
|
raise ClientError(msg)
|
|
224
426
|
return result.content
|
|
@@ -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
|
@@ -13,10 +13,12 @@ from mcp.types import (
|
|
|
13
13
|
SamplingMessage,
|
|
14
14
|
TextContent,
|
|
15
15
|
)
|
|
16
|
-
from pydantic import BaseModel
|
|
16
|
+
from pydantic import BaseModel, ConfigDict
|
|
17
17
|
from pydantic.networks import AnyUrl
|
|
18
|
+
from starlette.requests import Request
|
|
18
19
|
|
|
19
20
|
from fastmcp.server.server import FastMCP
|
|
21
|
+
from fastmcp.utilities.http import get_current_starlette_request
|
|
20
22
|
from fastmcp.utilities.logging import get_logger
|
|
21
23
|
|
|
22
24
|
logger = get_logger(__name__)
|
|
@@ -59,6 +61,8 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT]):
|
|
|
59
61
|
_request_context: RequestContext[ServerSessionT, LifespanContextT] | None
|
|
60
62
|
_fastmcp: FastMCP | None
|
|
61
63
|
|
|
64
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
65
|
+
|
|
62
66
|
def __init__(
|
|
63
67
|
self,
|
|
64
68
|
*,
|
|
@@ -222,3 +226,10 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT]):
|
|
|
222
226
|
)
|
|
223
227
|
|
|
224
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
|
fastmcp/server/openapi.py
CHANGED
|
@@ -5,12 +5,13 @@ from __future__ import annotations
|
|
|
5
5
|
import enum
|
|
6
6
|
import json
|
|
7
7
|
import re
|
|
8
|
+
from collections.abc import Callable
|
|
8
9
|
from dataclasses import dataclass
|
|
9
10
|
from re import Pattern
|
|
10
11
|
from typing import TYPE_CHECKING, Any, Literal
|
|
11
12
|
|
|
12
13
|
import httpx
|
|
13
|
-
from mcp.types import EmbeddedResource, ImageContent, TextContent
|
|
14
|
+
from mcp.types import EmbeddedResource, ImageContent, TextContent, ToolAnnotations
|
|
14
15
|
from pydantic.networks import AnyUrl
|
|
15
16
|
|
|
16
17
|
from fastmcp.resources import Resource, ResourceTemplate
|
|
@@ -126,6 +127,8 @@ class OpenAPITool(Tool):
|
|
|
126
127
|
is_async: bool = True,
|
|
127
128
|
tags: set[str] = set(),
|
|
128
129
|
timeout: float | None = None,
|
|
130
|
+
annotations: ToolAnnotations | None = None,
|
|
131
|
+
serializer: Callable[[Any], str] | None = None,
|
|
129
132
|
):
|
|
130
133
|
super().__init__(
|
|
131
134
|
name=name,
|
|
@@ -136,6 +139,8 @@ class OpenAPITool(Tool):
|
|
|
136
139
|
is_async=is_async,
|
|
137
140
|
context_kwarg="context", # Default context keyword argument
|
|
138
141
|
tags=tags,
|
|
142
|
+
annotations=annotations,
|
|
143
|
+
serializer=serializer,
|
|
139
144
|
)
|
|
140
145
|
self._client = client
|
|
141
146
|
self._route = route
|
|
@@ -534,10 +539,12 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
534
539
|
or f"Executes {route.method} {route.path}"
|
|
535
540
|
)
|
|
536
541
|
|
|
537
|
-
# Format enhanced description
|
|
542
|
+
# Format enhanced description with parameters and request body
|
|
538
543
|
enhanced_description = format_description_with_responses(
|
|
539
544
|
base_description=base_description,
|
|
540
545
|
responses=route.responses,
|
|
546
|
+
parameters=route.parameters,
|
|
547
|
+
request_body=route.request_body,
|
|
541
548
|
)
|
|
542
549
|
|
|
543
550
|
tool = OpenAPITool(
|
|
@@ -565,10 +572,12 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
565
572
|
route.description or route.summary or f"Represents {route.path}"
|
|
566
573
|
)
|
|
567
574
|
|
|
568
|
-
# Format enhanced description
|
|
575
|
+
# Format enhanced description with parameters and request body
|
|
569
576
|
enhanced_description = format_description_with_responses(
|
|
570
577
|
base_description=base_description,
|
|
571
578
|
responses=route.responses,
|
|
579
|
+
parameters=route.parameters,
|
|
580
|
+
request_body=route.request_body,
|
|
572
581
|
)
|
|
573
582
|
|
|
574
583
|
resource = OpenAPIResource(
|
|
@@ -600,16 +609,30 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
600
609
|
route.description or route.summary or f"Template for {route.path}"
|
|
601
610
|
)
|
|
602
611
|
|
|
603
|
-
# Format enhanced description
|
|
612
|
+
# Format enhanced description with parameters and request body
|
|
604
613
|
enhanced_description = format_description_with_responses(
|
|
605
614
|
base_description=base_description,
|
|
606
615
|
responses=route.responses,
|
|
616
|
+
parameters=route.parameters,
|
|
617
|
+
request_body=route.request_body,
|
|
607
618
|
)
|
|
608
619
|
|
|
609
620
|
template_params_schema = {
|
|
610
621
|
"type": "object",
|
|
611
622
|
"properties": {
|
|
612
|
-
p.name:
|
|
623
|
+
p.name: {
|
|
624
|
+
**(p.schema_.copy() if isinstance(p.schema_, dict) else {}),
|
|
625
|
+
**(
|
|
626
|
+
{"description": p.description}
|
|
627
|
+
if p.description
|
|
628
|
+
and not (
|
|
629
|
+
isinstance(p.schema_, dict) and "description" in p.schema_
|
|
630
|
+
)
|
|
631
|
+
else {}
|
|
632
|
+
),
|
|
633
|
+
}
|
|
634
|
+
for p in route.parameters
|
|
635
|
+
if p.location == "path"
|
|
613
636
|
},
|
|
614
637
|
"required": [
|
|
615
638
|
p.name for p in route.parameters if p.location == "path" and p.required
|
fastmcp/server/proxy.py
CHANGED
|
@@ -65,8 +65,9 @@ class ProxyTool(Tool):
|
|
|
65
65
|
# the client context manager will swallow any exceptions inside a TaskGroup
|
|
66
66
|
# so we return the raw result and raise an exception ourselves
|
|
67
67
|
async with self._client:
|
|
68
|
-
result = await self._client.
|
|
69
|
-
self.name,
|
|
68
|
+
result = await self._client.call_tool_mcp(
|
|
69
|
+
name=self.name,
|
|
70
|
+
arguments=arguments,
|
|
70
71
|
)
|
|
71
72
|
if result.isError:
|
|
72
73
|
raise ValueError(cast(mcp.types.TextContent, result.content[0]).text)
|