diff --git a/docs/servers/context.mdx b/docs/servers/context.mdx index cb4fe52ad..c33f1812c 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 diff --git a/src/fastmcp/server/context.py b/src/fastmcp/server/context.py index a0fcfc1c2..b5dbc5533 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,36 @@ 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 + """ + 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 + """ + 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 + """ + return await self.fastmcp._get_prompt_mcp(name, arguments) + async def read_resource(self, uri: str | AnyUrl) -> list[ReadResourceContents]: """Read a resource by URI. @@ -224,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/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( 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" },