fastmcp 2.2.1__py3-none-any.whl → 2.2.3__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/cli/cli.py +1 -1
- fastmcp/client/client.py +14 -21
- fastmcp/client/transports.py +2 -1
- fastmcp/contrib/README.md +19 -0
- fastmcp/contrib/bulk_tool_caller/README.md +35 -0
- fastmcp/contrib/bulk_tool_caller/__init__.py +3 -0
- fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py +135 -0
- fastmcp/contrib/bulk_tool_caller/example.py +17 -0
- fastmcp/contrib/mcp_mixin/README.md +39 -0
- fastmcp/contrib/mcp_mixin/__init__.py +8 -0
- fastmcp/contrib/mcp_mixin/example.py +52 -0
- fastmcp/contrib/mcp_mixin/mcp_mixin.py +208 -0
- fastmcp/resources/template.py +5 -2
- fastmcp/server/proxy.py +34 -4
- fastmcp/tools/tool.py +6 -1
- {fastmcp-2.2.1.dist-info → fastmcp-2.2.3.dist-info}/METADATA +19 -19
- {fastmcp-2.2.1.dist-info → fastmcp-2.2.3.dist-info}/RECORD +20 -11
- {fastmcp-2.2.1.dist-info → fastmcp-2.2.3.dist-info}/WHEEL +0 -0
- {fastmcp-2.2.1.dist-info → fastmcp-2.2.3.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.2.1.dist-info → fastmcp-2.2.3.dist-info}/licenses/LICENSE +0 -0
fastmcp/cli/cli.py
CHANGED
|
@@ -186,7 +186,7 @@ def version(ctx: Context):
|
|
|
186
186
|
"MCP version": importlib.metadata.version("mcp"),
|
|
187
187
|
"Python version": platform.python_version(),
|
|
188
188
|
"Platform": platform.platform(),
|
|
189
|
-
"FastMCP root path":
|
|
189
|
+
"FastMCP root path": Path(fastmcp.__file__).resolve().parents[1],
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
g = Table.grid(padding=(0, 1))
|
fastmcp/client/client.py
CHANGED
|
@@ -45,7 +45,8 @@ class Client:
|
|
|
45
45
|
):
|
|
46
46
|
self.transport = infer_transport(transport)
|
|
47
47
|
self._session: ClientSession | None = None
|
|
48
|
-
self.
|
|
48
|
+
self._session_cm: AbstractAsyncContextManager[ClientSession] | None = None
|
|
49
|
+
self._nesting_counter: int = 0
|
|
49
50
|
|
|
50
51
|
self._session_kwargs: SessionKwargs = {
|
|
51
52
|
"sampling_callback": None,
|
|
@@ -85,29 +86,21 @@ class Client:
|
|
|
85
86
|
return self._session is not None
|
|
86
87
|
|
|
87
88
|
async def __aenter__(self):
|
|
88
|
-
if self.
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
self._session = await self._session_cms[-1].__aenter__()
|
|
96
|
-
return self
|
|
97
|
-
except Exception as e:
|
|
98
|
-
# Ensure cleanup if __aenter__ fails partially
|
|
99
|
-
self._session = None
|
|
100
|
-
if self._session_cms:
|
|
101
|
-
self._session_cms.pop()
|
|
102
|
-
raise ConnectionError(
|
|
103
|
-
f"Failed to connect using {self.transport}: {e}"
|
|
104
|
-
) from e
|
|
89
|
+
if self._nesting_counter == 0:
|
|
90
|
+
# create new session
|
|
91
|
+
self._session_cm = self.transport.connect_session(**self._session_kwargs)
|
|
92
|
+
self._session = await self._session_cm.__aenter__()
|
|
93
|
+
|
|
94
|
+
self._nesting_counter += 1
|
|
95
|
+
return self
|
|
105
96
|
|
|
106
97
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
107
|
-
|
|
108
|
-
|
|
98
|
+
self._nesting_counter -= 1
|
|
99
|
+
|
|
100
|
+
if self._nesting_counter == 0 and self._session_cm is not None:
|
|
101
|
+
await self._session_cm.__aexit__(exc_type, exc_val, exc_tb)
|
|
102
|
+
self._session_cm = None
|
|
109
103
|
self._session = None
|
|
110
|
-
self._session_cms.pop()
|
|
111
104
|
|
|
112
105
|
# --- MCP Client Methods ---
|
|
113
106
|
async def ping(self) -> None:
|
fastmcp/client/transports.py
CHANGED
|
@@ -3,6 +3,7 @@ import contextlib
|
|
|
3
3
|
import datetime
|
|
4
4
|
import os
|
|
5
5
|
import shutil
|
|
6
|
+
import sys
|
|
6
7
|
from collections.abc import AsyncIterator
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import (
|
|
@@ -185,7 +186,7 @@ class PythonStdioTransport(StdioTransport):
|
|
|
185
186
|
args: list[str] | None = None,
|
|
186
187
|
env: dict[str, str] | None = None,
|
|
187
188
|
cwd: str | None = None,
|
|
188
|
-
python_cmd: str =
|
|
189
|
+
python_cmd: str = sys.executable,
|
|
189
190
|
):
|
|
190
191
|
"""
|
|
191
192
|
Initialize a Python transport.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# FastMCP Contrib Modules
|
|
2
|
+
|
|
3
|
+
This directory holds community-contributed modules for FastMCP. These modules extend FastMCP's functionality but are not officially maintained by the core team.
|
|
4
|
+
|
|
5
|
+
**Guarantees:**
|
|
6
|
+
* Modules in `contrib` may have different testing requirements or stability guarantees compared to the core library.
|
|
7
|
+
* Changes to the core FastMCP library might break modules in `contrib` without explicit warnings in the main changelog.
|
|
8
|
+
|
|
9
|
+
Use these modules at your own discretion. Contributions are welcome, but please include tests and documentation.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
To use a contrib module, import it from the `fastmcp.contrib` package.
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from fastmcp.contrib import my_module
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Note that the contrib modules may have different dependencies than the core library, which can be noted in their respective README's or even separate requirements / dependency files.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Bulk Tool Caller
|
|
2
|
+
|
|
3
|
+
This module provides the `BulkToolCaller` class, which extends the `MCPMixin` to offer tools for performing multiple tool calls in a single request to a FastMCP server. This can be useful for optimizing interactions with the server by reducing the overhead of individual tool calls.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
To use the `BulkToolCaller`, see the example [example.py](./example.py) file. The `BulkToolCaller` can be instantiated and then registered with a FastMCP server URL. It provides methods to call multiple tools in bulk, either different tools or the same tool with different arguments.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Provided Tools
|
|
11
|
+
|
|
12
|
+
The `BulkToolCaller` provides the following tools:
|
|
13
|
+
|
|
14
|
+
### `call_tools_bulk`
|
|
15
|
+
|
|
16
|
+
Calls multiple different tools registered on the MCP server in a single request.
|
|
17
|
+
|
|
18
|
+
- **Arguments:**
|
|
19
|
+
- `tool_calls` (list of `CallToolRequest`): A list of objects, where each object specifies the `tool` name and `arguments` for an individual tool call.
|
|
20
|
+
- `continue_on_error` (bool, optional): If `True`, continue executing subsequent tool calls even if a previous one resulted in an error. Defaults to `True`.
|
|
21
|
+
|
|
22
|
+
- **Returns:**
|
|
23
|
+
A list of `CallToolRequestResult` objects, each containing the result (`isError`, `content`) and the original `tool` name and `arguments` for each call.
|
|
24
|
+
|
|
25
|
+
### `call_tool_bulk`
|
|
26
|
+
|
|
27
|
+
Calls a single tool registered on the MCP server multiple times with different arguments in a single request.
|
|
28
|
+
|
|
29
|
+
- **Arguments:**
|
|
30
|
+
- `tool` (str): The name of the tool to call.
|
|
31
|
+
- `tool_arguments` (list of dict): A list of dictionaries, where each dictionary contains the arguments for an individual run of the tool.
|
|
32
|
+
- `continue_on_error` (bool, optional): If `True`, continue executing subsequent tool calls even if a previous one resulted in an error. Defaults to `True`.
|
|
33
|
+
|
|
34
|
+
- **Returns:**
|
|
35
|
+
A list of `CallToolRequestResult` objects, each containing the result (`isError`, `content`) and the original `tool` name and `arguments` for each call.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from mcp.types import CallToolResult
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
from fastmcp import FastMCP
|
|
7
|
+
from fastmcp.client import Client
|
|
8
|
+
from fastmcp.client.transports import FastMCPTransport
|
|
9
|
+
from fastmcp.contrib.mcp_mixin.mcp_mixin import (
|
|
10
|
+
_DEFAULT_SEPARATOR_TOOL,
|
|
11
|
+
MCPMixin,
|
|
12
|
+
mcp_tool,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CallToolRequest(BaseModel):
|
|
17
|
+
"""A class to represent a request to call a tool with specific arguments."""
|
|
18
|
+
|
|
19
|
+
tool: str = Field(description="The name of the tool to call.")
|
|
20
|
+
arguments: dict[str, Any] = Field(
|
|
21
|
+
description="A dictionary containing the arguments for the tool call."
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CallToolRequestResult(CallToolResult):
|
|
26
|
+
"""
|
|
27
|
+
A class to represent the result of a bulk tool call.
|
|
28
|
+
It extends CallToolResult to include information about the requested tool call.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
tool: str = Field(description="The name of the tool that was called.")
|
|
32
|
+
arguments: dict[str, Any] = Field(
|
|
33
|
+
description="The arguments used for the tool call."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def from_call_tool_result(
|
|
38
|
+
cls, result: CallToolResult, tool: str, arguments: dict[str, Any]
|
|
39
|
+
) -> "CallToolRequestResult":
|
|
40
|
+
"""
|
|
41
|
+
Create a CallToolRequestResult from a CallToolResult.
|
|
42
|
+
"""
|
|
43
|
+
return cls(
|
|
44
|
+
tool=tool,
|
|
45
|
+
arguments=arguments,
|
|
46
|
+
isError=result.isError,
|
|
47
|
+
content=result.content,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class BulkToolCaller(MCPMixin):
|
|
52
|
+
"""
|
|
53
|
+
A class to provide a "bulk tool call" tool for a FastMCP server
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def register_tools(
|
|
57
|
+
self,
|
|
58
|
+
mcp_server: "FastMCP",
|
|
59
|
+
prefix: str | None = None,
|
|
60
|
+
separator: str = _DEFAULT_SEPARATOR_TOOL,
|
|
61
|
+
) -> None:
|
|
62
|
+
"""
|
|
63
|
+
Register the tools provided by this class with the given MCP server.
|
|
64
|
+
"""
|
|
65
|
+
self.connection = FastMCPTransport(mcp_server)
|
|
66
|
+
|
|
67
|
+
super().register_tools(mcp_server=mcp_server)
|
|
68
|
+
|
|
69
|
+
@mcp_tool()
|
|
70
|
+
async def call_tools_bulk(
|
|
71
|
+
self, tool_calls: list[CallToolRequest], continue_on_error: bool = True
|
|
72
|
+
) -> list[CallToolRequestResult]:
|
|
73
|
+
"""
|
|
74
|
+
Call multiple tools registered on this MCP server in a single request. Each call can
|
|
75
|
+
be for a different tool and can include different arguments. Useful for speeding up
|
|
76
|
+
what would otherwise take several individual tool calls.
|
|
77
|
+
"""
|
|
78
|
+
results = []
|
|
79
|
+
|
|
80
|
+
for tool_call in tool_calls:
|
|
81
|
+
result = await self._call_tool(tool_call.tool, tool_call.arguments)
|
|
82
|
+
|
|
83
|
+
results.append(result)
|
|
84
|
+
|
|
85
|
+
if result.isError and not continue_on_error:
|
|
86
|
+
return results
|
|
87
|
+
|
|
88
|
+
return results
|
|
89
|
+
|
|
90
|
+
@mcp_tool()
|
|
91
|
+
async def call_tool_bulk(
|
|
92
|
+
self,
|
|
93
|
+
tool: str,
|
|
94
|
+
tool_arguments: list[dict[str, str | int | float | bool | None]],
|
|
95
|
+
continue_on_error: bool = True,
|
|
96
|
+
) -> list[CallToolRequestResult]:
|
|
97
|
+
"""
|
|
98
|
+
Call a single tool registered on this MCP server multiple times with a single request.
|
|
99
|
+
Each call can include different arguments. Useful for speeding up what would otherwise
|
|
100
|
+
take several individual tool calls.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
tool: The name of the tool to call.
|
|
104
|
+
tool_arguments: A list of dictionaries, where each dictionary contains the arguments for an individual run of the tool.
|
|
105
|
+
"""
|
|
106
|
+
results = []
|
|
107
|
+
|
|
108
|
+
for tool_call_arguments in tool_arguments:
|
|
109
|
+
result = await self._call_tool(tool, tool_call_arguments)
|
|
110
|
+
|
|
111
|
+
results.append(result)
|
|
112
|
+
|
|
113
|
+
if result.isError and not continue_on_error:
|
|
114
|
+
return results
|
|
115
|
+
|
|
116
|
+
return results
|
|
117
|
+
|
|
118
|
+
async def _call_tool(
|
|
119
|
+
self, tool: str, arguments: dict[str, Any]
|
|
120
|
+
) -> CallToolRequestResult:
|
|
121
|
+
"""
|
|
122
|
+
Helper method to call a tool with the provided arguments.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
async with Client(self.connection) as client:
|
|
126
|
+
result = await client.call_tool(
|
|
127
|
+
name=tool, arguments=arguments, _return_raw_result=True
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
return CallToolRequestResult(
|
|
131
|
+
tool=tool,
|
|
132
|
+
arguments=arguments,
|
|
133
|
+
isError=result.isError,
|
|
134
|
+
content=result.content,
|
|
135
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Sample code for FastMCP using MCPMixin."""
|
|
2
|
+
|
|
3
|
+
from fastmcp import FastMCP
|
|
4
|
+
from fastmcp.contrib.bulk_tool_caller import BulkToolCaller
|
|
5
|
+
|
|
6
|
+
mcp = FastMCP()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@mcp.tool()
|
|
10
|
+
def echo_tool(text: str) -> str:
|
|
11
|
+
"""Echo the input text"""
|
|
12
|
+
return text
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
bulk_tool_caller = BulkToolCaller()
|
|
16
|
+
|
|
17
|
+
bulk_tool_caller.register_tools(mcp)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# MCP Mixin
|
|
2
|
+
|
|
3
|
+
This module provides the `MCPMixin` base class and associated decorators (`@mcp_tool`, `@mcp_resource`, `@mcp_prompt`).
|
|
4
|
+
|
|
5
|
+
It allows developers to easily define classes whose methods can be registered as tools, resources, or prompts with a `FastMCP` server instance using the `register_all()`, `register_tools()`, `register_resources()`, or `register_prompts()` methods provided by the mixin.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
Inherit from `MCPMixin` and use the decorators on the methods you want to register.
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from fastmcp import FastMCP
|
|
13
|
+
from fastmcp.contrib.mcp_mixin import MCPMixin, mcp_tool, mcp_resource
|
|
14
|
+
|
|
15
|
+
class MyComponent(MCPMixin):
|
|
16
|
+
@mcp_tool(name="my_tool", description="Does something cool.")
|
|
17
|
+
def tool_method(self):
|
|
18
|
+
return "Tool executed!"
|
|
19
|
+
|
|
20
|
+
@mcp_resource(uri="component://data")
|
|
21
|
+
def resource_method(self):
|
|
22
|
+
return {"data": "some data"}
|
|
23
|
+
|
|
24
|
+
mcp_server = FastMCP()
|
|
25
|
+
component = MyComponent()
|
|
26
|
+
|
|
27
|
+
# Register all decorated methods with a prefix
|
|
28
|
+
# Useful if you will have multiple instantiated objects of the same class
|
|
29
|
+
# and want to avoid name collisions.
|
|
30
|
+
component.register_all(mcp_server, prefix="my_comp")
|
|
31
|
+
|
|
32
|
+
# Register without a prefix
|
|
33
|
+
# component.register_all(mcp_server)
|
|
34
|
+
|
|
35
|
+
# Now 'my_comp_my_tool' tool and 'my_comp+component://data' resource are registered (if prefix used)
|
|
36
|
+
# Or 'my_tool' and 'component://data' are registered (if no prefix used)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The `prefix` argument in registration methods is optional. If omitted, methods are registered with their original decorated names/URIs. Individual separators (`tools_separator`, `resources_separator`, `prompts_separator`) can also be provided to `register_all` to change the separator for specific types.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Sample code for FastMCP using MCPMixin."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from fastmcp import FastMCP
|
|
6
|
+
from fastmcp.contrib.mcp_mixin import (
|
|
7
|
+
MCPMixin,
|
|
8
|
+
mcp_prompt,
|
|
9
|
+
mcp_resource,
|
|
10
|
+
mcp_tool,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
mcp = FastMCP()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Sample(MCPMixin):
|
|
17
|
+
def __init__(self, name):
|
|
18
|
+
self.name = name
|
|
19
|
+
|
|
20
|
+
@mcp_tool()
|
|
21
|
+
def first_tool(self):
|
|
22
|
+
"""First tool description."""
|
|
23
|
+
return f"Executed tool {self.name}."
|
|
24
|
+
|
|
25
|
+
@mcp_resource(uri="test://test")
|
|
26
|
+
def first_resource(self):
|
|
27
|
+
"""First resource description."""
|
|
28
|
+
return f"Executed resource {self.name}."
|
|
29
|
+
|
|
30
|
+
@mcp_prompt()
|
|
31
|
+
def first_prompt(self):
|
|
32
|
+
"""First prompt description."""
|
|
33
|
+
return f"here's a prompt! {self.name}."
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
first_sample = Sample("First")
|
|
37
|
+
second_sample = Sample("Second")
|
|
38
|
+
|
|
39
|
+
first_sample.register_all(mcp_server=mcp, prefix="first")
|
|
40
|
+
second_sample.register_all(mcp_server=mcp, prefix="second")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def list_components():
|
|
44
|
+
print("MCP Server running with registered components...")
|
|
45
|
+
print("Tools:", list(await mcp.get_tools()))
|
|
46
|
+
print("Resources:", list(await mcp.get_resources()))
|
|
47
|
+
print("Prompts:", list(await mcp.get_prompts()))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
asyncio.run(list_components())
|
|
52
|
+
mcp.run()
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Provides a base mixin class and decorators for easy registration of class methods with FastMCP."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from fastmcp.server import FastMCP
|
|
8
|
+
|
|
9
|
+
_MCP_REGISTRATION_TOOL_ATTR = "_mcp_tool_registration"
|
|
10
|
+
_MCP_REGISTRATION_RESOURCE_ATTR = "_mcp_resource_registration"
|
|
11
|
+
_MCP_REGISTRATION_PROMPT_ATTR = "_mcp_prompt_registration"
|
|
12
|
+
|
|
13
|
+
_DEFAULT_SEPARATOR_TOOL = "_"
|
|
14
|
+
_DEFAULT_SEPARATOR_RESOURCE = "+"
|
|
15
|
+
_DEFAULT_SEPARATOR_PROMPT = "_"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def mcp_tool(
|
|
19
|
+
name: str | None = None,
|
|
20
|
+
description: str | None = None,
|
|
21
|
+
tags: set[str] | None = None,
|
|
22
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
23
|
+
"""Decorator to mark a method as an MCP tool for later registration."""
|
|
24
|
+
|
|
25
|
+
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
26
|
+
call_args = {
|
|
27
|
+
"name": name or func.__name__,
|
|
28
|
+
"description": description,
|
|
29
|
+
"tags": tags,
|
|
30
|
+
}
|
|
31
|
+
call_args = {k: v for k, v in call_args.items() if v is not None}
|
|
32
|
+
setattr(func, _MCP_REGISTRATION_TOOL_ATTR, call_args)
|
|
33
|
+
return func
|
|
34
|
+
|
|
35
|
+
return decorator
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def mcp_resource(
|
|
39
|
+
uri: str,
|
|
40
|
+
*,
|
|
41
|
+
name: str | None = None,
|
|
42
|
+
description: str | None = None,
|
|
43
|
+
mime_type: str | None = None,
|
|
44
|
+
tags: set[str] | None = None,
|
|
45
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
46
|
+
"""Decorator to mark a method as an MCP resource for later registration."""
|
|
47
|
+
|
|
48
|
+
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
49
|
+
call_args = {
|
|
50
|
+
"uri": uri,
|
|
51
|
+
"name": name or func.__name__,
|
|
52
|
+
"description": description,
|
|
53
|
+
"mime_type": mime_type,
|
|
54
|
+
"tags": tags,
|
|
55
|
+
}
|
|
56
|
+
call_args = {k: v for k, v in call_args.items() if v is not None}
|
|
57
|
+
|
|
58
|
+
setattr(func, _MCP_REGISTRATION_RESOURCE_ATTR, call_args)
|
|
59
|
+
|
|
60
|
+
return func
|
|
61
|
+
|
|
62
|
+
return decorator
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def mcp_prompt(
|
|
66
|
+
name: str | None = None,
|
|
67
|
+
description: str | None = None,
|
|
68
|
+
tags: set[str] | None = None,
|
|
69
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
70
|
+
"""Decorator to mark a method as an MCP prompt for later registration."""
|
|
71
|
+
|
|
72
|
+
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
73
|
+
call_args = {
|
|
74
|
+
"name": name or func.__name__,
|
|
75
|
+
"description": description,
|
|
76
|
+
"tags": tags,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
call_args = {k: v for k, v in call_args.items() if v is not None}
|
|
80
|
+
|
|
81
|
+
setattr(func, _MCP_REGISTRATION_PROMPT_ATTR, call_args)
|
|
82
|
+
return func
|
|
83
|
+
|
|
84
|
+
return decorator
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class MCPMixin:
|
|
88
|
+
"""Base mixin class for objects that can register tools, resources, and prompts
|
|
89
|
+
with a FastMCP server instance using decorators.
|
|
90
|
+
|
|
91
|
+
This mixin provides methods like `register_all`, `register_tools`, etc.,
|
|
92
|
+
which iterate over the methods of the inheriting class, find methods
|
|
93
|
+
decorated with `@mcp_tool`, `@mcp_resource`, or `@mcp_prompt`, and
|
|
94
|
+
register them with the provided FastMCP server instance.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def _get_methods_to_register(self, registration_type: str):
|
|
98
|
+
"""Retrieves all methods marked for a specific registration type."""
|
|
99
|
+
return [
|
|
100
|
+
(
|
|
101
|
+
getattr(self, method_name),
|
|
102
|
+
getattr(getattr(self, method_name), registration_type).copy(),
|
|
103
|
+
)
|
|
104
|
+
for method_name in dir(self)
|
|
105
|
+
if callable(getattr(self, method_name))
|
|
106
|
+
and hasattr(getattr(self, method_name), registration_type)
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
def register_tools(
|
|
110
|
+
self,
|
|
111
|
+
mcp_server: "FastMCP",
|
|
112
|
+
prefix: str | None = None,
|
|
113
|
+
separator: str = _DEFAULT_SEPARATOR_TOOL,
|
|
114
|
+
) -> None:
|
|
115
|
+
"""Registers all methods marked with @mcp_tool with the FastMCP server.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
mcp_server: The FastMCP server instance to register tools with.
|
|
119
|
+
prefix: Optional prefix to prepend to tool names. If provided, the
|
|
120
|
+
final name will be f"{prefix}{separator}{original_name}".
|
|
121
|
+
separator: The separator string used between prefix and original name.
|
|
122
|
+
Defaults to '_'.
|
|
123
|
+
"""
|
|
124
|
+
for method, registration_info in self._get_methods_to_register(
|
|
125
|
+
_MCP_REGISTRATION_TOOL_ATTR
|
|
126
|
+
):
|
|
127
|
+
if prefix:
|
|
128
|
+
registration_info["name"] = (
|
|
129
|
+
f"{prefix}{separator}{registration_info['name']}"
|
|
130
|
+
)
|
|
131
|
+
mcp_server.add_tool(fn=method, **registration_info)
|
|
132
|
+
|
|
133
|
+
def register_resources(
|
|
134
|
+
self,
|
|
135
|
+
mcp_server: "FastMCP",
|
|
136
|
+
prefix: str | None = None,
|
|
137
|
+
separator: str = _DEFAULT_SEPARATOR_RESOURCE,
|
|
138
|
+
) -> None:
|
|
139
|
+
"""Registers all methods marked with @mcp_resource with the FastMCP server.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
mcp_server: The FastMCP server instance to register resources with.
|
|
143
|
+
prefix: Optional prefix to prepend to resource names and URIs. If provided,
|
|
144
|
+
the final name will be f"{prefix}{separator}{original_name}" and the
|
|
145
|
+
final URI will be f"{prefix}{separator}{original_uri}".
|
|
146
|
+
separator: The separator string used between prefix and original name/URI.
|
|
147
|
+
Defaults to '+'.
|
|
148
|
+
"""
|
|
149
|
+
for method, registration_info in self._get_methods_to_register(
|
|
150
|
+
_MCP_REGISTRATION_RESOURCE_ATTR
|
|
151
|
+
):
|
|
152
|
+
if prefix:
|
|
153
|
+
registration_info["name"] = (
|
|
154
|
+
f"{prefix}{separator}{registration_info['name']}"
|
|
155
|
+
)
|
|
156
|
+
registration_info["uri"] = (
|
|
157
|
+
f"{prefix}{separator}{registration_info['uri']}"
|
|
158
|
+
)
|
|
159
|
+
mcp_server.add_resource_fn(fn=method, **registration_info)
|
|
160
|
+
|
|
161
|
+
def register_prompts(
|
|
162
|
+
self,
|
|
163
|
+
mcp_server: "FastMCP",
|
|
164
|
+
prefix: str | None = None,
|
|
165
|
+
separator: str = _DEFAULT_SEPARATOR_PROMPT,
|
|
166
|
+
) -> None:
|
|
167
|
+
"""Registers all methods marked with @mcp_prompt with the FastMCP server.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
mcp_server: The FastMCP server instance to register prompts with.
|
|
171
|
+
prefix: Optional prefix to prepend to prompt names. If provided, the
|
|
172
|
+
final name will be f"{prefix}{separator}{original_name}".
|
|
173
|
+
separator: The separator string used between prefix and original name.
|
|
174
|
+
Defaults to '_'.
|
|
175
|
+
"""
|
|
176
|
+
for method, registration_info in self._get_methods_to_register(
|
|
177
|
+
_MCP_REGISTRATION_PROMPT_ATTR
|
|
178
|
+
):
|
|
179
|
+
if prefix:
|
|
180
|
+
registration_info["name"] = (
|
|
181
|
+
f"{prefix}{separator}{registration_info['name']}"
|
|
182
|
+
)
|
|
183
|
+
mcp_server.add_prompt(fn=method, **registration_info)
|
|
184
|
+
|
|
185
|
+
def register_all(
|
|
186
|
+
self,
|
|
187
|
+
mcp_server: "FastMCP",
|
|
188
|
+
prefix: str | None = None,
|
|
189
|
+
tool_separator: str = _DEFAULT_SEPARATOR_TOOL,
|
|
190
|
+
resource_separator: str = _DEFAULT_SEPARATOR_RESOURCE,
|
|
191
|
+
prompt_separator: str = _DEFAULT_SEPARATOR_PROMPT,
|
|
192
|
+
) -> None:
|
|
193
|
+
"""Registers all marked tools, resources, and prompts with the server.
|
|
194
|
+
|
|
195
|
+
This method calls `register_tools`, `register_resources`, and `register_prompts`
|
|
196
|
+
internally, passing the provided prefix and separators.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
mcp_server: The FastMCP server instance to register with.
|
|
200
|
+
prefix: Optional prefix applied to all registered items unless overridden
|
|
201
|
+
by a specific separator argument.
|
|
202
|
+
tool_separator: Separator for tool names (defaults to '_').
|
|
203
|
+
resource_separator: Separator for resource names/URIs (defaults to '+').
|
|
204
|
+
prompt_separator: Separator for prompt names (defaults to '_').
|
|
205
|
+
"""
|
|
206
|
+
self.register_tools(mcp_server, prefix=prefix, separator=tool_separator)
|
|
207
|
+
self.register_resources(mcp_server, prefix=prefix, separator=resource_separator)
|
|
208
|
+
self.register_prompts(mcp_server, prefix=prefix, separator=prompt_separator)
|
fastmcp/resources/template.py
CHANGED
|
@@ -24,13 +24,16 @@ from fastmcp.utilities.types import _convert_set_defaults
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def build_regex(template: str) -> re.Pattern:
|
|
27
|
-
# Escape all non-brace characters, then restore {var} placeholders
|
|
28
27
|
parts = re.split(r"(\{[^}]+\})", template)
|
|
29
28
|
pattern = ""
|
|
30
29
|
for part in parts:
|
|
31
30
|
if part.startswith("{") and part.endswith("}"):
|
|
32
31
|
name = part[1:-1]
|
|
33
|
-
|
|
32
|
+
if name.endswith("*"):
|
|
33
|
+
name = name[:-1]
|
|
34
|
+
pattern += f"(?P<{name}>.+)"
|
|
35
|
+
else:
|
|
36
|
+
pattern += f"(?P<{name}>[^/]+)"
|
|
34
37
|
else:
|
|
35
38
|
pattern += re.escape(part)
|
|
36
39
|
return re.compile(f"^{pattern}$")
|
fastmcp/server/proxy.py
CHANGED
|
@@ -3,7 +3,9 @@ from urllib.parse import quote
|
|
|
3
3
|
|
|
4
4
|
import mcp.types
|
|
5
5
|
from mcp.server.lowlevel.helper_types import ReadResourceContents
|
|
6
|
+
from mcp.shared.exceptions import McpError
|
|
6
7
|
from mcp.types import (
|
|
8
|
+
METHOD_NOT_FOUND,
|
|
7
9
|
BlobResourceContents,
|
|
8
10
|
EmbeddedResource,
|
|
9
11
|
GetPromptResult,
|
|
@@ -173,7 +175,14 @@ class FastMCPProxy(FastMCP):
|
|
|
173
175
|
tools = await super().get_tools()
|
|
174
176
|
|
|
175
177
|
async with self.client:
|
|
176
|
-
|
|
178
|
+
try:
|
|
179
|
+
client_tools = await self.client.list_tools()
|
|
180
|
+
except McpError as e:
|
|
181
|
+
if e.error.code == METHOD_NOT_FOUND:
|
|
182
|
+
client_tools = []
|
|
183
|
+
else:
|
|
184
|
+
raise e
|
|
185
|
+
for tool in client_tools:
|
|
177
186
|
tool_proxy = await ProxyTool.from_client(self.client, tool)
|
|
178
187
|
tools[tool_proxy.name] = tool_proxy
|
|
179
188
|
|
|
@@ -183,7 +192,14 @@ class FastMCPProxy(FastMCP):
|
|
|
183
192
|
resources = await super().get_resources()
|
|
184
193
|
|
|
185
194
|
async with self.client:
|
|
186
|
-
|
|
195
|
+
try:
|
|
196
|
+
client_resources = await self.client.list_resources()
|
|
197
|
+
except McpError as e:
|
|
198
|
+
if e.error.code == METHOD_NOT_FOUND:
|
|
199
|
+
client_resources = []
|
|
200
|
+
else:
|
|
201
|
+
raise e
|
|
202
|
+
for resource in client_resources:
|
|
187
203
|
resource_proxy = await ProxyResource.from_client(self.client, resource)
|
|
188
204
|
resources[str(resource_proxy.uri)] = resource_proxy
|
|
189
205
|
|
|
@@ -193,7 +209,14 @@ class FastMCPProxy(FastMCP):
|
|
|
193
209
|
templates = await super().get_resource_templates()
|
|
194
210
|
|
|
195
211
|
async with self.client:
|
|
196
|
-
|
|
212
|
+
try:
|
|
213
|
+
client_templates = await self.client.list_resource_templates()
|
|
214
|
+
except McpError as e:
|
|
215
|
+
if e.error.code == METHOD_NOT_FOUND:
|
|
216
|
+
client_templates = []
|
|
217
|
+
else:
|
|
218
|
+
raise e
|
|
219
|
+
for template in client_templates:
|
|
197
220
|
template_proxy = await ProxyTemplate.from_client(self.client, template)
|
|
198
221
|
templates[template_proxy.uri_template] = template_proxy
|
|
199
222
|
|
|
@@ -203,7 +226,14 @@ class FastMCPProxy(FastMCP):
|
|
|
203
226
|
prompts = await super().get_prompts()
|
|
204
227
|
|
|
205
228
|
async with self.client:
|
|
206
|
-
|
|
229
|
+
try:
|
|
230
|
+
client_prompts = await self.client.list_prompts()
|
|
231
|
+
except McpError as e:
|
|
232
|
+
if e.error.code == METHOD_NOT_FOUND:
|
|
233
|
+
client_prompts = []
|
|
234
|
+
else:
|
|
235
|
+
raise e
|
|
236
|
+
for prompt in client_prompts:
|
|
207
237
|
prompt_proxy = await ProxyPrompt.from_client(self.client, prompt)
|
|
208
238
|
prompts[prompt_proxy.name] = prompt_proxy
|
|
209
239
|
return prompts
|
fastmcp/tools/tool.py
CHANGED
|
@@ -76,7 +76,12 @@ class Tool(BaseModel):
|
|
|
76
76
|
fn_callable,
|
|
77
77
|
skip_names=[context_kwarg] if context_kwarg is not None else [],
|
|
78
78
|
)
|
|
79
|
-
|
|
79
|
+
try:
|
|
80
|
+
parameters = func_arg_metadata.arg_model.model_json_schema()
|
|
81
|
+
except Exception as e:
|
|
82
|
+
raise TypeError(
|
|
83
|
+
f'Unable to parse parameters for function "{fn.__name__}": {e}'
|
|
84
|
+
) from e
|
|
80
85
|
|
|
81
86
|
return cls(
|
|
82
87
|
fn=fn_callable,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastmcp
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.3
|
|
4
4
|
Summary: The fast, Pythonic way to build MCP servers.
|
|
5
5
|
Project-URL: Homepage, https://gofastmcp.com
|
|
6
6
|
Project-URL: Repository, https://github.com/jlowin/fastmcp
|
|
@@ -361,32 +361,32 @@ The `Context` object provides:
|
|
|
361
361
|
|
|
362
362
|
### Images
|
|
363
363
|
|
|
364
|
-
Easily handle image
|
|
364
|
+
Easily handle image outputs using the `fastmcp.Image` helper class.
|
|
365
|
+
|
|
366
|
+
<Tip>
|
|
367
|
+
The below code requires the `pillow` library to be installed.
|
|
368
|
+
</Tip>
|
|
365
369
|
|
|
366
370
|
```python
|
|
367
|
-
from fastmcp import FastMCP, Image
|
|
368
|
-
from
|
|
369
|
-
|
|
371
|
+
from mcp.server.fastmcp import FastMCP, Image
|
|
372
|
+
from io import BytesIO
|
|
373
|
+
try:
|
|
374
|
+
from PIL import Image as PILImage
|
|
375
|
+
except ImportError:
|
|
376
|
+
raise ImportError("Please install the `pillow` library to run this example.")
|
|
370
377
|
|
|
371
|
-
mcp = FastMCP("
|
|
378
|
+
mcp = FastMCP("My App")
|
|
372
379
|
|
|
373
380
|
@mcp.tool()
|
|
374
|
-
def create_thumbnail(
|
|
375
|
-
"""
|
|
376
|
-
img = PILImage.open(
|
|
377
|
-
img.thumbnail((100, 100))
|
|
378
|
-
buffer =
|
|
381
|
+
def create_thumbnail(image_path: str) -> Image:
|
|
382
|
+
"""Create a thumbnail from an image"""
|
|
383
|
+
img = PILImage.open(image_path)
|
|
384
|
+
img.thumbnail((100, 100))
|
|
385
|
+
buffer = BytesIO()
|
|
379
386
|
img.save(buffer, format="PNG")
|
|
380
|
-
# Return a new Image object with the thumbnail data
|
|
381
387
|
return Image(data=buffer.getvalue(), format="png")
|
|
382
|
-
|
|
383
|
-
@mcp.tool()
|
|
384
|
-
def load_image_from_disk(path: str) -> Image:
|
|
385
|
-
"""Loads an image from the specified path."""
|
|
386
|
-
# Handles reading file and detecting format based on extension
|
|
387
|
-
return Image(path=path)
|
|
388
388
|
```
|
|
389
|
-
|
|
389
|
+
Return the `Image` helper class from your tool to send an image to the client. The `Image` helper class handles the conversion to/from the base64-encoded format required by the MCP protocol. It works with either a path to an image file, or a bytes object.
|
|
390
390
|
|
|
391
391
|
|
|
392
392
|
### MCP Clients
|
|
@@ -4,28 +4,37 @@ fastmcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
4
4
|
fastmcp/settings.py,sha256=VCjc-3011pKRYjt2h9rZ68XhVEekbpyLyVUREVBTSrg,1955
|
|
5
5
|
fastmcp/cli/__init__.py,sha256=Ii284TNoG5lxTP40ETMGhHEq3lQZWxu9m9JuU57kUpQ,87
|
|
6
6
|
fastmcp/cli/claude.py,sha256=IAlcZ4qZKBBj09jZUMEx7EANZE_IR3vcu7zOBJmMOuU,4567
|
|
7
|
-
fastmcp/cli/cli.py,sha256=
|
|
7
|
+
fastmcp/cli/cli.py,sha256=ESyqSl7rxQKoJokkQbWMupyVPlpC_KLs1pZ5-69aDTM,14850
|
|
8
8
|
fastmcp/client/__init__.py,sha256=BXO9NUhntZ5GnUACfaRCzDJ5IzxqFJs8qKG-CRMSco4,490
|
|
9
9
|
fastmcp/client/base.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
10
|
-
fastmcp/client/client.py,sha256=
|
|
10
|
+
fastmcp/client/client.py,sha256=XXpN28epV9N5w-keQSDPBCdLFtZ5EPK2msxuQ6PxTmo,7732
|
|
11
11
|
fastmcp/client/roots.py,sha256=IxI_bHwHTmg6c2H-s1av1ZgrRnNDieHtYwdGFbzXT5c,2471
|
|
12
12
|
fastmcp/client/sampling.py,sha256=WdRhIZbWv54rXYI8lWHv0thXmGCloZYPFpwJK9El_sQ,1613
|
|
13
|
-
fastmcp/client/transports.py,sha256=
|
|
13
|
+
fastmcp/client/transports.py,sha256=7dJUQemdxj6UHNThizPzSJbHTGiJHlM77vLf4X9g11M,15491
|
|
14
|
+
fastmcp/contrib/README.md,sha256=rKknYSI1T192UvSszqwwDlQ2eYQpxywrNTLoj177SYU,878
|
|
15
|
+
fastmcp/contrib/bulk_tool_caller/README.md,sha256=5aUUY1TSFKtz1pvTLSDqkUCkGkuqMfMZNsLeaNqEgAc,1960
|
|
16
|
+
fastmcp/contrib/bulk_tool_caller/__init__.py,sha256=xvGSSaUXTQrc31erBoi1Gh7BikgOliETDiYVTP3rLxY,75
|
|
17
|
+
fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py,sha256=BlZnZRY_l4X5VnrIeMWzmQM2lRsQPMcnIafKIl4GXxE,4216
|
|
18
|
+
fastmcp/contrib/bulk_tool_caller/example.py,sha256=3RdsU2KrRwYZHEdVAmHOGJsO3ZJBxSaqz8BTznkPg7Y,321
|
|
19
|
+
fastmcp/contrib/mcp_mixin/README.md,sha256=9DDTJXWkA3yv1fp5V58gofmARPQ2xWDhblYGvUhKpDQ,1689
|
|
20
|
+
fastmcp/contrib/mcp_mixin/__init__.py,sha256=aw9IQ1ssNjCgws4ZNt8bkdpossAAGVAwwjBpMp9O5ZQ,153
|
|
21
|
+
fastmcp/contrib/mcp_mixin/example.py,sha256=GnunkXmtG5hLLTUsM8aW5ZURU52Z8vI4tNLl-fK7Dg0,1228
|
|
22
|
+
fastmcp/contrib/mcp_mixin/mcp_mixin.py,sha256=cfIRbnSxsVzglTD-auyTE0izVQeHP7Oz18qzYoBZJgg,7899
|
|
14
23
|
fastmcp/prompts/__init__.py,sha256=LtPAv2JKIu54AwUd3iwv-HUd4DPcwgEqy6itEd3BH_E,194
|
|
15
24
|
fastmcp/prompts/prompt.py,sha256=mQ6iRnt7J8oKBUhlgPDYXnIzwwDNWCk4heTqCmv1sco,6622
|
|
16
25
|
fastmcp/prompts/prompt_manager.py,sha256=tMob9a-igjuzf6oTPLPGidFpJdg5JaPJVlYgyNkiCbE,2901
|
|
17
26
|
fastmcp/resources/__init__.py,sha256=t0x1j8lc74rjUKtXe9H5Gs4fpQt82K4NgBK6Y7A0xTg,467
|
|
18
27
|
fastmcp/resources/resource.py,sha256=5FN2a7dpNwf7FSEYTNvQvkTxtodu1OPxSlJL-U-8yrM,2413
|
|
19
28
|
fastmcp/resources/resource_manager.py,sha256=_0itubfjYvfkA_wXKa4DQN5YpE7ejXhsE1hdt7m8XwU,9072
|
|
20
|
-
fastmcp/resources/template.py,sha256=
|
|
29
|
+
fastmcp/resources/template.py,sha256=PlC-fSGbQWJcFgM-fFgW-Xq8XwN3xsI68ivYcrk690E,5825
|
|
21
30
|
fastmcp/resources/types.py,sha256=tigil7z-SUJMakGXzDLIGSqTepPrAsRpwqwtBA4yoUY,6168
|
|
22
31
|
fastmcp/server/__init__.py,sha256=pdkghG11VLMZiluQ-4_rl2JK1LMWmV003m9dDRUN8W4,92
|
|
23
32
|
fastmcp/server/context.py,sha256=s1885AZRipKB3VltfaO3VEtMxGefKs8fdZByj-4tbNI,7120
|
|
24
33
|
fastmcp/server/openapi.py,sha256=DVdUfs-rbBF_CIlxrI6HJ5aYbzuyDqGLAhT1TeyxwFc,22424
|
|
25
|
-
fastmcp/server/proxy.py,sha256=
|
|
34
|
+
fastmcp/server/proxy.py,sha256=JHbxnOKbxyD5Jg2M_zSlNGKVBSZ5NUlVhQoKf442wxo,9619
|
|
26
35
|
fastmcp/server/server.py,sha256=PFhnwa24diSKCz8KO39q43yuSHSbqYrzgnSspc-SPfg,31721
|
|
27
36
|
fastmcp/tools/__init__.py,sha256=ocw-SFTtN6vQ8fgnlF8iNAOflRmh79xS1xdO0Bc3QPE,96
|
|
28
|
-
fastmcp/tools/tool.py,sha256=
|
|
37
|
+
fastmcp/tools/tool.py,sha256=hAdeQaJ-1AgPyVZPvCQKYFkK0opccJWa39xWGFAWlzA,5975
|
|
29
38
|
fastmcp/tools/tool_manager.py,sha256=hClv7fwj0cQSSwW0i-Swt7xiVqR4T9LVmr1Tp704nW4,3283
|
|
30
39
|
fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
|
|
31
40
|
fastmcp/utilities/decorators.py,sha256=AjhjsetQZF4YOPV5MTZmIxO21iFp_4fDIS3O2_KNCEg,2990
|
|
@@ -33,8 +42,8 @@ fastmcp/utilities/func_metadata.py,sha256=uh-u3gAjLD4kCcGf0ZkZZwBTTl-84JuANZTnDq
|
|
|
33
42
|
fastmcp/utilities/logging.py,sha256=zav8pnFxG_fvGJHUV2XpobmT9WVrmv1mlQBSCz-CPx4,1159
|
|
34
43
|
fastmcp/utilities/openapi.py,sha256=PrH3usbTblaVC6jIH1UGiPEfgB2sSCLj33zA5dH7o_s,45193
|
|
35
44
|
fastmcp/utilities/types.py,sha256=m2rPYMzO-ZFvvZ46N-1-Xqyw693K7yq9Z2xR4pVELyk,2091
|
|
36
|
-
fastmcp-2.2.
|
|
37
|
-
fastmcp-2.2.
|
|
38
|
-
fastmcp-2.2.
|
|
39
|
-
fastmcp-2.2.
|
|
40
|
-
fastmcp-2.2.
|
|
45
|
+
fastmcp-2.2.3.dist-info/METADATA,sha256=dC6bbGM3xckA7dpYhpD3zQUyxSpTqLV0DppozXl5pjU,27771
|
|
46
|
+
fastmcp-2.2.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
47
|
+
fastmcp-2.2.3.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
|
|
48
|
+
fastmcp-2.2.3.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
49
|
+
fastmcp-2.2.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|