From d727b3039deb44e014739a54afa40f396f4e9657 Mon Sep 17 00:00:00 2001 From: DamyanBG Date: Thu, 22 May 2025 12:31:27 +0300 Subject: [PATCH 1/6] Add Bundler class for managing tools, resources, and prompts --- src/mcp/server/fastmcp/server.py | 8 + src/mcp/server/fastmcp/tools/tool_manager.py | 3 + src/mcp/server/fastmcp/utilities/bundler.py | 251 +++++++++++++++++++ 3 files changed, 262 insertions(+) create mode 100644 src/mcp/server/fastmcp/utilities/bundler.py diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index 3282baae6..f81f4cd19 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -38,6 +38,7 @@ from mcp.server.fastmcp.prompts import Prompt, PromptManager from mcp.server.fastmcp.resources import FunctionResource, Resource, ResourceManager from mcp.server.fastmcp.tools import Tool, ToolManager +from mcp.server.fastmcp.utilities.bundler import Bundler from mcp.server.fastmcp.utilities.logging import configure_logging, get_logger from mcp.server.fastmcp.utilities.types import Image from mcp.server.lowlevel.helper_types import ReadResourceContents @@ -586,6 +587,13 @@ def decorator( return func return decorator + + def include_bunder(self, bundler: Bundler) -> None: + """Add bundler of resources, tools and prompts to the server.""" + bundler_tools = bundler.get_tools() + for name, tool in bundler_tools.items(): + self.add_tool(tool.fn, name, tool.description, tool.annotations) + # TODO finish code for resources and prompts async def run_stdio_async(self) -> None: """Run the server using stdio transport.""" diff --git a/src/mcp/server/fastmcp/tools/tool_manager.py b/src/mcp/server/fastmcp/tools/tool_manager.py index 6ec4fd151..0ce11eb6b 100644 --- a/src/mcp/server/fastmcp/tools/tool_manager.py +++ b/src/mcp/server/fastmcp/tools/tool_manager.py @@ -37,6 +37,9 @@ def __init__( def get_tool(self, name: str) -> Tool | None: """Get tool by name.""" return self._tools.get(name) + + def get_all_tools(self) -> dict[str, Tool]: + return self._tools def list_tools(self) -> list[Tool]: """List all registered tools.""" diff --git a/src/mcp/server/fastmcp/utilities/bundler.py b/src/mcp/server/fastmcp/utilities/bundler.py new file mode 100644 index 000000000..3a1c6b30e --- /dev/null +++ b/src/mcp/server/fastmcp/utilities/bundler.py @@ -0,0 +1,251 @@ +import inspect +import re +from collections.abc import Callable + +from mcp.server.fastmcp.prompts import Prompt, PromptManager +from mcp.server.fastmcp.resources import FunctionResource, Resource, ResourceManager +from mcp.server.fastmcp.tools import Tool, ToolManager +from mcp.types import ( + AnyFunction, + ToolAnnotations, +) + + +class Bundler: + def __init__(self, tools: list[Tool] | None = None,): + self._tool_manager = ToolManager( + tools=tools, warn_on_duplicate_tools=True + ) + self._resource_manager = ResourceManager( + warn_on_duplicate_resources=True + ) + self._prompt_manager = PromptManager( + warn_on_duplicate_prompts=True + ) + + def add_tool( + self, + fn: AnyFunction, + name: str | None = None, + description: str | None = None, + annotations: ToolAnnotations | None = None, + ) -> None: + """Add a tool to the bundler. + + The tool function can optionally request a Context object by adding a parameter + with the Context type annotation. See the @tool decorator for examples. + + Args: + fn: The function to register as a tool + name: Optional name for the tool (defaults to function name) + description: Optional description of what the tool does + annotations: Optional ToolAnnotations providing additional tool information + """ + self._tool_manager.add_tool( + fn, name=name, description=description, annotations=annotations + ) + + def tool( + self, + name: str | None = None, + description: str | None = None, + annotations: ToolAnnotations | None = None, + ) -> Callable[[AnyFunction], AnyFunction]: + """Decorator to register a tool. + + Tools can optionally request a Context object by adding a parameter with the + Context type annotation. The context provides access to MCP capabilities like + logging, progress reporting, and resource access. + + Args: + name: Optional name for the tool (defaults to function name) + description: Optional description of what the tool does + annotations: Optional ToolAnnotations providing additional tool information + + Example: + @server.tool() + def my_tool(x: int) -> str: + return str(x) + + @server.tool() + def tool_with_context(x: int, ctx: Context) -> str: + ctx.info(f"Processing {x}") + return str(x) + + @server.tool() + async def async_tool(x: int, context: Context) -> str: + await context.report_progress(50, 100) + return str(x) + """ + # Check if user passed function directly instead of calling decorator + if callable(name): + raise TypeError( + "The @tool decorator was used incorrectly. " + "Did you forget to call it? Use @tool() instead of @tool" + ) + + def decorator(fn: AnyFunction) -> AnyFunction: + self.add_tool( + fn, name=name, description=description, annotations=annotations + ) + return fn + + return decorator + + def get_tools(self) -> dict[str, Tool]: + return self._tool_manager.get_all_tools() + + def add_resource(self, resource: Resource) -> None: + """Add a resource to the server. + + Args: + resource: A Resource instance to add + """ + self._resource_manager.add_resource(resource) + + def resource( + self, + uri: str, + *, + name: str | None = None, + description: str | None = None, + mime_type: str | None = None, + ) -> Callable[[AnyFunction], AnyFunction]: + """Decorator to register a function as a resource. + + The function will be called when the resource is read to generate its content. + The function can return: + - str for text content + - bytes for binary content + - other types will be converted to JSON + + If the URI contains parameters (e.g. "resource://{param}") or the function + has parameters, it will be registered as a template resource. + + Args: + uri: URI for the resource (e.g. "resource://my-resource" or "resource://{param}") + name: Optional name for the resource + description: Optional description of the resource + mime_type: Optional MIME type for the resource + + Example: + @server.resource("resource://my-resource") + def get_data() -> str: + return "Hello, world!" + + @server.resource("resource://my-resource") + async get_data() -> str: + data = await fetch_data() + return f"Hello, world! {data}" + + @server.resource("resource://{city}/weather") + def get_weather(city: str) -> str: + return f"Weather for {city}" + + @server.resource("resource://{city}/weather") + async def get_weather(city: str) -> str: + data = await fetch_weather(city) + return f"Weather for {city}: {data}" + """ + # Check if user passed function directly instead of calling decorator + if callable(uri): + raise TypeError( + "The @resource decorator was used incorrectly. " + "Did you forget to call it? Use @resource('uri') instead of @resource" + ) + + def decorator(fn: AnyFunction) -> AnyFunction: + # Check if this should be a template + has_uri_params = "{" in uri and "}" in uri + has_func_params = bool(inspect.signature(fn).parameters) + + if has_uri_params or has_func_params: + # Validate that URI params match function params + uri_params = set(re.findall(r"{(\w+)}", uri)) + func_params = set(inspect.signature(fn).parameters.keys()) + + if uri_params != func_params: + raise ValueError( + f"Mismatch between URI parameters {uri_params} " + f"and function parameters {func_params}" + ) + + # Register as template + self._resource_manager.add_template( + fn=fn, + uri_template=uri, + name=name, + description=description, + mime_type=mime_type, + ) + else: + # Register as regular resource + resource = FunctionResource.from_function( + fn=fn, + uri=uri, + name=name, + description=description, + mime_type=mime_type, + ) + self.add_resource(resource) + return fn + + return decorator + + def add_prompt(self, prompt: Prompt) -> None: + """Add a prompt to the server. + + Args: + prompt: A Prompt instance to add + """ + self._prompt_manager.add_prompt(prompt) + + def prompt( + self, name: str | None = None, description: str | None = None + ) -> Callable[[AnyFunction], AnyFunction]: + """Decorator to register a prompt. + + Args: + name: Optional name for the prompt (defaults to function name) + description: Optional description of what the prompt does + + Example: + @server.prompt() + def analyze_table(table_name: str) -> list[Message]: + schema = read_table_schema(table_name) + return [ + { + "role": "user", + "content": f"Analyze this schema:\n{schema}" + } + ] + + @server.prompt() + async def analyze_file(path: str) -> list[Message]: + content = await read_file(path) + return [ + { + "role": "user", + "content": { + "type": "resource", + "resource": { + "uri": f"file://{path}", + "text": content + } + } + } + ] + """ + # Check if user passed function directly instead of calling decorator + if callable(name): + raise TypeError( + "The @prompt decorator was used incorrectly. " + "Did you forget to call it? Use @prompt() instead of @prompt" + ) + + def decorator(func: AnyFunction) -> AnyFunction: + prompt = Prompt.from_function(func, name=name, description=description) + self.add_prompt(prompt) + return func + + return decorator From 0e2a34189cd6c7dc586c676bdf8f69cab33d8215 Mon Sep 17 00:00:00 2001 From: Mo Date: Fri, 23 May 2025 03:57:46 -0400 Subject: [PATCH 2/6] Fix `ClientSessionGroup` cleanup logic (#787) --- src/mcp/client/session_group.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/mcp/client/session_group.py b/src/mcp/client/session_group.py index c23f2523e..a430533b3 100644 --- a/src/mcp/client/session_group.py +++ b/src/mcp/client/session_group.py @@ -77,10 +77,10 @@ class ClientSessionGroup: the client and can be accessed via the session. Example Usage: - name_fn = lambda name, server_info: f"{(server_info.name)}-{name}" + name_fn = lambda name, server_info: f"{(server_info.name)}_{name}" async with ClientSessionGroup(component_name_hook=name_fn) as group: for server_params in server_params: - group.connect_to_server(server_param) + await group.connect_to_server(server_param) ... """ @@ -145,14 +145,15 @@ async def __aexit__( ) -> bool | None: """Closes session exit stacks and main exit stack upon completion.""" + # Only close the main exit stack if we created it + if self._owns_exit_stack: + await self._exit_stack.aclose() + # Concurrently close session stacks. async with anyio.create_task_group() as tg: for exit_stack in self._session_exit_stacks.values(): tg.start_soon(exit_stack.aclose) - # Only close the main exit stack if we created it - if self._owns_exit_stack: - await self._exit_stack.aclose() @property def sessions(self) -> list[mcp.ClientSession]: From 28ab221f4022ce2e55461de0c0c1119ae3b00eef Mon Sep 17 00:00:00 2001 From: DamyanBG Date: Thu, 22 May 2025 12:31:27 +0300 Subject: [PATCH 3/6] Add Bundler class for managing tools, resources, and prompts --- src/mcp/server/fastmcp/server.py | 8 + src/mcp/server/fastmcp/tools/tool_manager.py | 3 + src/mcp/server/fastmcp/utilities/bundler.py | 251 +++++++++++++++++++ 3 files changed, 262 insertions(+) create mode 100644 src/mcp/server/fastmcp/utilities/bundler.py diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index 3282baae6..f81f4cd19 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -38,6 +38,7 @@ from mcp.server.fastmcp.prompts import Prompt, PromptManager from mcp.server.fastmcp.resources import FunctionResource, Resource, ResourceManager from mcp.server.fastmcp.tools import Tool, ToolManager +from mcp.server.fastmcp.utilities.bundler import Bundler from mcp.server.fastmcp.utilities.logging import configure_logging, get_logger from mcp.server.fastmcp.utilities.types import Image from mcp.server.lowlevel.helper_types import ReadResourceContents @@ -586,6 +587,13 @@ def decorator( return func return decorator + + def include_bunder(self, bundler: Bundler) -> None: + """Add bundler of resources, tools and prompts to the server.""" + bundler_tools = bundler.get_tools() + for name, tool in bundler_tools.items(): + self.add_tool(tool.fn, name, tool.description, tool.annotations) + # TODO finish code for resources and prompts async def run_stdio_async(self) -> None: """Run the server using stdio transport.""" diff --git a/src/mcp/server/fastmcp/tools/tool_manager.py b/src/mcp/server/fastmcp/tools/tool_manager.py index 6ec4fd151..0ce11eb6b 100644 --- a/src/mcp/server/fastmcp/tools/tool_manager.py +++ b/src/mcp/server/fastmcp/tools/tool_manager.py @@ -37,6 +37,9 @@ def __init__( def get_tool(self, name: str) -> Tool | None: """Get tool by name.""" return self._tools.get(name) + + def get_all_tools(self) -> dict[str, Tool]: + return self._tools def list_tools(self) -> list[Tool]: """List all registered tools.""" diff --git a/src/mcp/server/fastmcp/utilities/bundler.py b/src/mcp/server/fastmcp/utilities/bundler.py new file mode 100644 index 000000000..3a1c6b30e --- /dev/null +++ b/src/mcp/server/fastmcp/utilities/bundler.py @@ -0,0 +1,251 @@ +import inspect +import re +from collections.abc import Callable + +from mcp.server.fastmcp.prompts import Prompt, PromptManager +from mcp.server.fastmcp.resources import FunctionResource, Resource, ResourceManager +from mcp.server.fastmcp.tools import Tool, ToolManager +from mcp.types import ( + AnyFunction, + ToolAnnotations, +) + + +class Bundler: + def __init__(self, tools: list[Tool] | None = None,): + self._tool_manager = ToolManager( + tools=tools, warn_on_duplicate_tools=True + ) + self._resource_manager = ResourceManager( + warn_on_duplicate_resources=True + ) + self._prompt_manager = PromptManager( + warn_on_duplicate_prompts=True + ) + + def add_tool( + self, + fn: AnyFunction, + name: str | None = None, + description: str | None = None, + annotations: ToolAnnotations | None = None, + ) -> None: + """Add a tool to the bundler. + + The tool function can optionally request a Context object by adding a parameter + with the Context type annotation. See the @tool decorator for examples. + + Args: + fn: The function to register as a tool + name: Optional name for the tool (defaults to function name) + description: Optional description of what the tool does + annotations: Optional ToolAnnotations providing additional tool information + """ + self._tool_manager.add_tool( + fn, name=name, description=description, annotations=annotations + ) + + def tool( + self, + name: str | None = None, + description: str | None = None, + annotations: ToolAnnotations | None = None, + ) -> Callable[[AnyFunction], AnyFunction]: + """Decorator to register a tool. + + Tools can optionally request a Context object by adding a parameter with the + Context type annotation. The context provides access to MCP capabilities like + logging, progress reporting, and resource access. + + Args: + name: Optional name for the tool (defaults to function name) + description: Optional description of what the tool does + annotations: Optional ToolAnnotations providing additional tool information + + Example: + @server.tool() + def my_tool(x: int) -> str: + return str(x) + + @server.tool() + def tool_with_context(x: int, ctx: Context) -> str: + ctx.info(f"Processing {x}") + return str(x) + + @server.tool() + async def async_tool(x: int, context: Context) -> str: + await context.report_progress(50, 100) + return str(x) + """ + # Check if user passed function directly instead of calling decorator + if callable(name): + raise TypeError( + "The @tool decorator was used incorrectly. " + "Did you forget to call it? Use @tool() instead of @tool" + ) + + def decorator(fn: AnyFunction) -> AnyFunction: + self.add_tool( + fn, name=name, description=description, annotations=annotations + ) + return fn + + return decorator + + def get_tools(self) -> dict[str, Tool]: + return self._tool_manager.get_all_tools() + + def add_resource(self, resource: Resource) -> None: + """Add a resource to the server. + + Args: + resource: A Resource instance to add + """ + self._resource_manager.add_resource(resource) + + def resource( + self, + uri: str, + *, + name: str | None = None, + description: str | None = None, + mime_type: str | None = None, + ) -> Callable[[AnyFunction], AnyFunction]: + """Decorator to register a function as a resource. + + The function will be called when the resource is read to generate its content. + The function can return: + - str for text content + - bytes for binary content + - other types will be converted to JSON + + If the URI contains parameters (e.g. "resource://{param}") or the function + has parameters, it will be registered as a template resource. + + Args: + uri: URI for the resource (e.g. "resource://my-resource" or "resource://{param}") + name: Optional name for the resource + description: Optional description of the resource + mime_type: Optional MIME type for the resource + + Example: + @server.resource("resource://my-resource") + def get_data() -> str: + return "Hello, world!" + + @server.resource("resource://my-resource") + async get_data() -> str: + data = await fetch_data() + return f"Hello, world! {data}" + + @server.resource("resource://{city}/weather") + def get_weather(city: str) -> str: + return f"Weather for {city}" + + @server.resource("resource://{city}/weather") + async def get_weather(city: str) -> str: + data = await fetch_weather(city) + return f"Weather for {city}: {data}" + """ + # Check if user passed function directly instead of calling decorator + if callable(uri): + raise TypeError( + "The @resource decorator was used incorrectly. " + "Did you forget to call it? Use @resource('uri') instead of @resource" + ) + + def decorator(fn: AnyFunction) -> AnyFunction: + # Check if this should be a template + has_uri_params = "{" in uri and "}" in uri + has_func_params = bool(inspect.signature(fn).parameters) + + if has_uri_params or has_func_params: + # Validate that URI params match function params + uri_params = set(re.findall(r"{(\w+)}", uri)) + func_params = set(inspect.signature(fn).parameters.keys()) + + if uri_params != func_params: + raise ValueError( + f"Mismatch between URI parameters {uri_params} " + f"and function parameters {func_params}" + ) + + # Register as template + self._resource_manager.add_template( + fn=fn, + uri_template=uri, + name=name, + description=description, + mime_type=mime_type, + ) + else: + # Register as regular resource + resource = FunctionResource.from_function( + fn=fn, + uri=uri, + name=name, + description=description, + mime_type=mime_type, + ) + self.add_resource(resource) + return fn + + return decorator + + def add_prompt(self, prompt: Prompt) -> None: + """Add a prompt to the server. + + Args: + prompt: A Prompt instance to add + """ + self._prompt_manager.add_prompt(prompt) + + def prompt( + self, name: str | None = None, description: str | None = None + ) -> Callable[[AnyFunction], AnyFunction]: + """Decorator to register a prompt. + + Args: + name: Optional name for the prompt (defaults to function name) + description: Optional description of what the prompt does + + Example: + @server.prompt() + def analyze_table(table_name: str) -> list[Message]: + schema = read_table_schema(table_name) + return [ + { + "role": "user", + "content": f"Analyze this schema:\n{schema}" + } + ] + + @server.prompt() + async def analyze_file(path: str) -> list[Message]: + content = await read_file(path) + return [ + { + "role": "user", + "content": { + "type": "resource", + "resource": { + "uri": f"file://{path}", + "text": content + } + } + } + ] + """ + # Check if user passed function directly instead of calling decorator + if callable(name): + raise TypeError( + "The @prompt decorator was used incorrectly. " + "Did you forget to call it? Use @prompt() instead of @prompt" + ) + + def decorator(func: AnyFunction) -> AnyFunction: + prompt = Prompt.from_function(func, name=name, description=description) + self.add_prompt(prompt) + return func + + return decorator From c149a480ca985196743d5a4f29286374f8b7cdba Mon Sep 17 00:00:00 2001 From: DamyanBG Date: Fri, 23 May 2025 11:52:53 +0300 Subject: [PATCH 4/6] fix function typo --- src/mcp/server/fastmcp/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index f81f4cd19..edd15365b 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -588,7 +588,7 @@ def decorator( return decorator - def include_bunder(self, bundler: Bundler) -> None: + def include_bundler(self, bundler: Bundler) -> None: """Add bundler of resources, tools and prompts to the server.""" bundler_tools = bundler.get_tools() for name, tool in bundler_tools.items(): From aaaa3bee88952be9d8cea1d19ba1381d94b2a6e0 Mon Sep 17 00:00:00 2001 From: DamyanBG Date: Fri, 23 May 2025 12:18:51 +0300 Subject: [PATCH 5/6] Add methods to retrieve all resources and prompts in Bundler and managers --- src/mcp/server/fastmcp/prompts/manager.py | 3 ++ .../fastmcp/resources/resource_manager.py | 3 ++ src/mcp/server/fastmcp/server.py | 23 ++++++++++--- src/mcp/server/fastmcp/utilities/bundler.py | 34 ++++++++++++------- 4 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/mcp/server/fastmcp/prompts/manager.py b/src/mcp/server/fastmcp/prompts/manager.py index 7ccbdef36..d0fb4bc08 100644 --- a/src/mcp/server/fastmcp/prompts/manager.py +++ b/src/mcp/server/fastmcp/prompts/manager.py @@ -18,6 +18,9 @@ def __init__(self, warn_on_duplicate_prompts: bool = True): def get_prompt(self, name: str) -> Prompt | None: """Get prompt by name.""" return self._prompts.get(name) + + def get_all_prompts(self) -> dict[str, Prompt]: + return self._prompts def list_prompts(self) -> list[Prompt]: """List all registered prompts.""" diff --git a/src/mcp/server/fastmcp/resources/resource_manager.py b/src/mcp/server/fastmcp/resources/resource_manager.py index d27e6ac12..a2243d416 100644 --- a/src/mcp/server/fastmcp/resources/resource_manager.py +++ b/src/mcp/server/fastmcp/resources/resource_manager.py @@ -20,6 +20,9 @@ def __init__(self, warn_on_duplicate_resources: bool = True): self._templates: dict[str, ResourceTemplate] = {} self.warn_on_duplicate_resources = warn_on_duplicate_resources + def get_all_resources(self) -> tuple[dict[str, Resource], dict[str, ResourceTemplate]]: + return self._resources, self._templates + def add_resource(self, resource: Resource) -> Resource: """Add a resource to the manager. diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index edd15365b..961147e9f 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -587,13 +587,28 @@ def decorator( return func return decorator - + def include_bundler(self, bundler: Bundler) -> None: """Add bundler of resources, tools and prompts to the server.""" bundler_tools = bundler.get_tools() - for name, tool in bundler_tools.items(): - self.add_tool(tool.fn, name, tool.description, tool.annotations) - # TODO finish code for resources and prompts + for tool_name, tool in bundler_tools.items(): + self.add_tool(tool.fn, tool_name, tool.description, tool.annotations) + + bundler_resources, bundler_templates = bundler.get_resources() + for resource in bundler_resources.values(): + self.add_resource(resource) + for template_name, template in bundler_templates.items(): + self._resource_manager.add_template( + template.fn, + template.uri_template, + template_name, + template.description, + template.mime_type, + ) + + bundler_prompts = bundler.get_prompts() + for prompt in bundler_prompts.values(): + self.add_prompt(prompt) async def run_stdio_async(self) -> None: """Run the server using stdio transport.""" diff --git a/src/mcp/server/fastmcp/utilities/bundler.py b/src/mcp/server/fastmcp/utilities/bundler.py index 3a1c6b30e..94ed6015f 100644 --- a/src/mcp/server/fastmcp/utilities/bundler.py +++ b/src/mcp/server/fastmcp/utilities/bundler.py @@ -3,7 +3,12 @@ from collections.abc import Callable from mcp.server.fastmcp.prompts import Prompt, PromptManager -from mcp.server.fastmcp.resources import FunctionResource, Resource, ResourceManager +from mcp.server.fastmcp.resources import ( + FunctionResource, + Resource, + ResourceManager, + ResourceTemplate, +) from mcp.server.fastmcp.tools import Tool, ToolManager from mcp.types import ( AnyFunction, @@ -12,16 +17,13 @@ class Bundler: - def __init__(self, tools: list[Tool] | None = None,): - self._tool_manager = ToolManager( - tools=tools, warn_on_duplicate_tools=True - ) - self._resource_manager = ResourceManager( - warn_on_duplicate_resources=True - ) - self._prompt_manager = PromptManager( - warn_on_duplicate_prompts=True - ) + def __init__( + self, + tools: list[Tool] | None = None, + ): + self._tool_manager = ToolManager(tools=tools, warn_on_duplicate_tools=True) + self._resource_manager = ResourceManager(warn_on_duplicate_resources=True) + self._prompt_manager = PromptManager(warn_on_duplicate_prompts=True) def add_tool( self, @@ -91,10 +93,10 @@ def decorator(fn: AnyFunction) -> AnyFunction: return fn return decorator - + def get_tools(self) -> dict[str, Tool]: return self._tool_manager.get_all_tools() - + def add_resource(self, resource: Resource) -> None: """Add a resource to the server. @@ -192,6 +194,9 @@ def decorator(fn: AnyFunction) -> AnyFunction: return decorator + def get_resources(self) -> tuple[dict[str, Resource], dict[str, ResourceTemplate]]: + return self._resource_manager.get_all_resources() + def add_prompt(self, prompt: Prompt) -> None: """Add a prompt to the server. @@ -249,3 +254,6 @@ def decorator(func: AnyFunction) -> AnyFunction: return func return decorator + + def get_prompts(self) -> dict[str, Prompt]: + return self._prompt_manager.get_all_prompts() From e1308804ea3a3c0737259fe77b3d7c004f2d633f Mon Sep 17 00:00:00 2001 From: DamyanBG Date: Fri, 23 May 2025 12:24:29 +0300 Subject: [PATCH 6/6] fux ruff error --- src/mcp/server/fastmcp/resources/resource_manager.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mcp/server/fastmcp/resources/resource_manager.py b/src/mcp/server/fastmcp/resources/resource_manager.py index a2243d416..1a4450dd1 100644 --- a/src/mcp/server/fastmcp/resources/resource_manager.py +++ b/src/mcp/server/fastmcp/resources/resource_manager.py @@ -20,7 +20,9 @@ def __init__(self, warn_on_duplicate_resources: bool = True): self._templates: dict[str, ResourceTemplate] = {} self.warn_on_duplicate_resources = warn_on_duplicate_resources - def get_all_resources(self) -> tuple[dict[str, Resource], dict[str, ResourceTemplate]]: + def get_all_resources( + self, + ) -> tuple[dict[str, Resource], dict[str, ResourceTemplate]]: return self._resources, self._templates def add_resource(self, resource: Resource) -> Resource: