From 63add1897016be3f97ac57abbd21f2b78541206e Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 22:34:45 +0000 Subject: [PATCH 1/3] Add list_resources, list_prompts, and get_prompt methods to Context - Add Context.list_resources() to list all available resources - Add Context.list_prompts() to list all available prompts - Add Context.get_prompt() to get a specific prompt with arguments - Update ToolInjectionMiddleware to use new Context methods instead of creating temporary Client instances - Remove unused Client and FastMCPTransport imports from tool_injection.py This improves API consistency by allowing middleware/tools to use Context methods directly without needing to create temporary Client instances. Fixes #2245 Co-authored-by: William Easton --- src/fastmcp/server/context.py | 39 +++++++++++++++++++ .../server/middleware/tool_injection.py | 14 ++----- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/fastmcp/server/context.py b/src/fastmcp/server/context.py index a0fcfc1c2..1c565db55 100644 --- a/src/fastmcp/server/context.py +++ b/src/fastmcp/server/context.py @@ -22,6 +22,7 @@ from mcp.types import ( AudioContent, ClientCapabilities, CreateMessageResult, + GetPromptResult, ImageContent, IncludeContext, ModelHint, @@ -32,6 +33,8 @@ from mcp.types import ( TextContent, ) from mcp.types import CreateMessageRequestParams as SamplingParams +from mcp.types import Prompt as MCPPrompt +from mcp.types import Resource as MCPResource from pydantic.networks import AnyUrl from starlette.requests import Request from typing_extensions import TypeVar @@ -215,6 +218,42 @@ class Context: related_request_id=self.request_id, ) + async def list_resources(self) -> list[MCPResource]: + """List all available resources from the server. + + Returns: + List of Resource objects available on the server + """ + if self.fastmcp is None: + raise ValueError("Context is not available outside of a request") + return await self.fastmcp._list_resources_mcp() + + async def list_prompts(self) -> list[MCPPrompt]: + """List all available prompts from the server. + + Returns: + List of Prompt objects available on the server + """ + if self.fastmcp is None: + raise ValueError("Context is not available outside of a request") + return await self.fastmcp._list_prompts_mcp() + + async def get_prompt( + self, name: str, arguments: dict[str, Any] | None = None + ) -> GetPromptResult: + """Get a prompt by name with optional arguments. + + Args: + name: The name of the prompt to get + arguments: Optional arguments to pass to the prompt + + Returns: + The prompt result + """ + if self.fastmcp is None: + raise ValueError("Context is not available outside of a request") + return await self.fastmcp._get_prompt_mcp(name, arguments) + async def read_resource(self, uri: str | AnyUrl) -> list[ReadResourceContents]: """Read a resource by URI. diff --git a/src/fastmcp/server/middleware/tool_injection.py b/src/fastmcp/server/middleware/tool_injection.py index 8df5d18a4..7914c5eca 100644 --- a/src/fastmcp/server/middleware/tool_injection.py +++ b/src/fastmcp/server/middleware/tool_injection.py @@ -10,8 +10,6 @@ from mcp.types import Prompt from pydantic import AnyUrl from typing_extensions import override -from fastmcp.client.client import Client -from fastmcp.client.transports import FastMCPTransport from fastmcp.server.context import Context from fastmcp.server.middleware.middleware import CallNext, Middleware, MiddlewareContext from fastmcp.tools.tool import Tool, ToolResult @@ -55,9 +53,7 @@ class ToolInjectionMiddleware(Middleware): async def list_prompts(context: Context) -> list[Prompt]: """List prompts available on the server.""" - - async with Client[FastMCPTransport](context.fastmcp) as client: - return await client.list_prompts() + return await context.list_prompts() list_prompts_tool = Tool.from_function( @@ -73,9 +69,7 @@ async def get_prompt( ] = None, ) -> mcp.types.GetPromptResult: """Render a prompt available on the server.""" - - async with Client[FastMCPTransport](context.fastmcp) as client: - return await client.get_prompt(name=name, arguments=arguments) + return await context.get_prompt(name=name, arguments=arguments) get_prompt_tool = Tool.from_function( @@ -93,9 +87,7 @@ class PromptToolMiddleware(ToolInjectionMiddleware): async def list_resources(context: Context) -> list[mcp.types.Resource]: """List resources available on the server.""" - - async with Client[FastMCPTransport](context.fastmcp) as client: - return await client.list_resources() + return await context.list_resources() list_resources_tool = Tool.from_function( From 7de74c05cbb6d892fa5579f1087e4e7fa162dc99 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Fri, 24 Oct 2025 19:16:25 -0400 Subject: [PATCH 2/3] Update docs --- docs/servers/context.mdx | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/docs/servers/context.mdx b/docs/servers/context.mdx index 720272fb7..f9c32dacb 100644 --- a/docs/servers/context.mdx +++ b/docs/servers/context.mdx @@ -14,7 +14,8 @@ The `Context` object provides a clean interface to access MCP features within yo - **Logging**: Send debug, info, warning, and error messages back to the client - **Progress Reporting**: Update the client on the progress of long-running operations -- **Resource Access**: Read data from resources registered with the server +- **Resource Access**: List and read data from resources registered with the server +- **Prompt Access**: List and retrieve prompts registered with the server - **LLM Sampling**: Request the client's LLM to generate text based on provided messages - **User Elicitation**: Request structured input from users during tool execution - **State Management**: Store and share data between middleware and the handler within a single request @@ -177,16 +178,40 @@ See [Progress Reporting](/servers/progress) for detailed patterns and examples. ### Resource Access -Read data from resources registered with your FastMCP server, allowing access to files, configuration, or dynamic content. +List and read data from resources registered with your FastMCP server, allowing access to files, configuration, or dynamic content. ```python +# List available resources +resources = await ctx.list_resources() + +# Read a specific resource content_list = await ctx.read_resource("resource://config") content = content_list[0].content ``` -**Method signature:** +**Method signatures:** +- **`ctx.list_resources() -> list[MCPResource]`**: Returns list of all available resources - **`ctx.read_resource(uri: str | AnyUrl) -> list[ReadResourceContents]`**: Returns a list of resource content parts +### Prompt Access + + + +List and retrieve prompts registered with your FastMCP server, allowing tools and middleware to discover and use available prompts programmatically. + +```python +# List available prompts +prompts = await ctx.list_prompts() + +# Get a specific prompt with arguments +result = await ctx.get_prompt("analyze_data", {"dataset": "users"}) +messages = result.messages +``` + +**Method signatures:** +- **`ctx.list_prompts() -> list[MCPPrompt]`**: Returns list of all available prompts +- **`ctx.get_prompt(name: str, arguments: dict[str, Any] | None = None) -> GetPromptResult`**: Get a specific prompt with optional arguments + ### State Management From 42d851964f6888f865376d75f3016b6197e3722e Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 23:24:46 +0000 Subject: [PATCH 3/3] Remove unnecessary None checks for ctx.fastmcp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fastmcp property raises RuntimeError if None, so it can never return None. Removed unnecessary checks from list_resources(), list_prompts(), get_prompt(), and read_resource(). 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: William Easton --- src/fastmcp/server/context.py | 8 -------- uv.lock | 6 +++--- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/fastmcp/server/context.py b/src/fastmcp/server/context.py index 1c565db55..b5dbc5533 100644 --- a/src/fastmcp/server/context.py +++ b/src/fastmcp/server/context.py @@ -224,8 +224,6 @@ class Context: Returns: List of Resource objects available on the server """ - if self.fastmcp is None: - raise ValueError("Context is not available outside of a request") return await self.fastmcp._list_resources_mcp() async def list_prompts(self) -> list[MCPPrompt]: @@ -234,8 +232,6 @@ class Context: Returns: List of Prompt objects available on the server """ - if self.fastmcp is None: - raise ValueError("Context is not available outside of a request") return await self.fastmcp._list_prompts_mcp() async def get_prompt( @@ -250,8 +246,6 @@ class Context: Returns: The prompt result """ - if self.fastmcp is None: - raise ValueError("Context is not available outside of a request") return await self.fastmcp._get_prompt_mcp(name, arguments) async def read_resource(self, uri: str | AnyUrl) -> list[ReadResourceContents]: @@ -263,8 +257,6 @@ class Context: Returns: The resource content as either text or bytes """ - if self.fastmcp is None: - raise ValueError("Context is not available outside of a request") return await self.fastmcp._read_resource_mcp(uri) async def log( diff --git a/uv.lock b/uv.lock index 3c06bb91d..01c073600 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.11'", @@ -566,7 +566,6 @@ dependencies = [ { name = "py-key-value-aio", extra = ["disk", "keyring", "memory"] }, { name = "pydantic", extra = ["email"] }, { name = "pyperclip" }, - { name = "pytest-asyncio" }, { name = "python-dotenv" }, { name = "rich" }, { name = "websockets" }, @@ -591,6 +590,7 @@ dev = [ { name = "pyinstrument" }, { name = "pyperclip" }, { name = "pytest" }, + { name = "pytest-asyncio" }, { name = "pytest-cov" }, { name = "pytest-env" }, { name = "pytest-flakefinder" }, @@ -617,7 +617,6 @@ requires-dist = [ { name = "py-key-value-aio", extras = ["disk", "keyring", "memory"], specifier = ">=0.2.6,<0.3.0" }, { name = "pydantic", extras = ["email"], specifier = ">=2.11.7" }, { name = "pyperclip", specifier = ">=1.9.0" }, - { name = "pytest-asyncio", specifier = ">=1.2.0" }, { name = "python-dotenv", specifier = ">=1.1.0" }, { name = "rich", specifier = ">=13.9.4" }, { name = "websockets", specifier = ">=15.0.1" }, @@ -637,6 +636,7 @@ dev = [ { name = "pyinstrument", specifier = ">=5.0.2" }, { name = "pyperclip", specifier = ">=1.9.0" }, { name = "pytest", specifier = ">=8.3.3" }, + { name = "pytest-asyncio", specifier = ">=1.2.0" }, { name = "pytest-cov", specifier = ">=6.1.1" }, { name = "pytest-env", specifier = ">=1.1.5" }, { name = "pytest-flakefinder" },