mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-18 19:44:19 +02:00
* fix: enforce auth/visibility in ResourcesAsTools and PromptsAsTools for non-FastMCP providers
🤖 Co-authored-by: Claude <noreply@anthropic.com>
* fix: honor stdio auth bypass and correct transform ordering in provider wrappers
Co-authored-by: Claude <noreply@anthropic.com>
* fix: move context/dependencies imports into function to break circular import
* fix: route ResourcesAsTools/PromptsAsTools through ctx.fastmcp
Instead of manually reimplementing auth, visibility, and session
transforms in the transform layer, tool functions now call
ctx.fastmcp.read_resource() / ctx.fastmcp.render_prompt() which
routes through the server's full middleware chain. This matches
the pattern CodeMode uses with ctx.fastmcp.call_tool().
The isinstance(provider, FastMCP) branching is removed entirely.
* feat: add _scope parameter for provider-scoped listing
AggregateProvider can now filter which child providers to query when
listing components. ResourcesAsTools and PromptsAsTools use this to
scope listings to their configured provider while still routing
through ctx.fastmcp for full middleware coverage.
The scope matching walks wrapped providers, so a
WrappedProvider(Namespace, inner=MyProvider) matches if MyProvider
is in the scope list.
* test: add coverage for ResourcesAsTools scoped to a sub-server
* fix: delegate to super() when _scope is None, add AggregateProvider to scope matching
* simplify: remove _scope machinery, route everything through ctx.fastmcp
Reverts the _scope parameter from Provider/AggregateProvider/Server.
ResourcesAsTools and PromptsAsTools now simply route through
ctx.fastmcp for all operations. Apply to a FastMCP server instance
for proper auth/visibility/middleware coverage.
Tests rewritten to use FastMCP server directly instead of raw providers.
* warn when ResourcesAsTools/PromptsAsTools is applied to a non-FastMCP provider
* docs: explain that ResourcesAsTools/PromptsAsTools should wrap a FastMCP server
* raise TypeError instead of warning when applied to non-FastMCP provider
---------
Co-authored-by: Claude <noreply@anthropic.com>
130 lines
4.1 KiB
Text
130 lines
4.1 KiB
Text
---
|
|
title: Prompts as Tools
|
|
sidebarTitle: Prompts as Tools
|
|
description: Expose prompts to tool-only clients
|
|
icon: message-lines
|
|
tag: NEW
|
|
---
|
|
|
|
import { VersionBadge } from '/snippets/version-badge.mdx'
|
|
|
|
<VersionBadge version="3.0.0" />
|
|
|
|
Some MCP clients only support tools. They cannot list or get prompts directly because they lack prompt protocol support. The `PromptsAsTools` transform bridges this gap by generating tools that provide access to your server's prompts.
|
|
|
|
When you add `PromptsAsTools` to a server, it creates two tools that clients can call instead of using the prompt protocol:
|
|
|
|
- **`list_prompts`** returns JSON describing all available prompts and their arguments
|
|
- **`get_prompt`** renders a specific prompt with provided arguments
|
|
|
|
This means any client that can call tools can now access prompts, even if the client has no native prompt support.
|
|
|
|
## Basic Usage
|
|
|
|
Pass your FastMCP server to `PromptsAsTools` when adding the transform. The generated tools route through the server at runtime, which means all server middleware — auth, visibility, rate limiting — applies to prompt operations automatically, exactly as it would for direct `prompts/get` calls.
|
|
|
|
<Note>
|
|
`PromptsAsTools` (and `ResourcesAsTools`) should be applied to a FastMCP server instance, not a raw Provider. The generated tools call back into the server's middleware chain at runtime, so they need a server to route through. If you want to expose only a subset of prompts, create a dedicated FastMCP server for those prompts and apply the transform there.
|
|
</Note>
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.server.transforms import PromptsAsTools
|
|
|
|
mcp = FastMCP("My Server")
|
|
|
|
@mcp.prompt
|
|
def analyze_code(code: str, language: str = "python") -> str:
|
|
"""Analyze code for potential issues."""
|
|
return f"Analyze this {language} code:\n{code}"
|
|
|
|
@mcp.prompt
|
|
def explain_concept(concept: str) -> str:
|
|
"""Explain a programming concept."""
|
|
return f"Explain: {concept}"
|
|
|
|
# Add the transform - creates list_prompts and get_prompt tools
|
|
mcp.add_transform(PromptsAsTools(mcp))
|
|
```
|
|
|
|
Clients now see three items: whatever tools you defined directly, plus `list_prompts` and `get_prompt`.
|
|
|
|
## Listing Prompts
|
|
|
|
The `list_prompts` tool returns JSON with metadata for each prompt, including its arguments.
|
|
|
|
```python
|
|
result = await client.call_tool("list_prompts", {})
|
|
prompts = json.loads(result.data)
|
|
# [
|
|
# {
|
|
# "name": "analyze_code",
|
|
# "description": "Analyze code for potential issues.",
|
|
# "arguments": [
|
|
# {"name": "code", "description": null, "required": true},
|
|
# {"name": "language", "description": null, "required": false}
|
|
# ]
|
|
# },
|
|
# {
|
|
# "name": "explain_concept",
|
|
# "description": "Explain a programming concept.",
|
|
# "arguments": [
|
|
# {"name": "concept", "description": null, "required": true}
|
|
# ]
|
|
# }
|
|
#]
|
|
```
|
|
|
|
Each argument includes:
|
|
- `name`: The argument name
|
|
- `description`: Optional description from type hints or docstrings
|
|
- `required`: Whether the argument must be provided
|
|
|
|
## Getting Prompts
|
|
|
|
The `get_prompt` tool accepts a prompt name and optional arguments dict. It returns the rendered prompt as JSON with a messages array.
|
|
|
|
```python
|
|
# Prompt with required and optional arguments
|
|
result = await client.call_tool(
|
|
"get_prompt",
|
|
{
|
|
"name": "analyze_code",
|
|
"arguments": {
|
|
"code": "x = 1\nprint(x)",
|
|
"language": "python"
|
|
}
|
|
}
|
|
)
|
|
|
|
response = json.loads(result.data)
|
|
# {
|
|
# "messages": [
|
|
# {
|
|
# "role": "user",
|
|
# "content": "Analyze this python code:\nx = 1\nprint(x)"
|
|
# }
|
|
# ]
|
|
# }
|
|
```
|
|
|
|
If a prompt has no arguments, you can omit the `arguments` field or pass an empty dict:
|
|
|
|
```python
|
|
result = await client.call_tool(
|
|
"get_prompt",
|
|
{"name": "simple_prompt"}
|
|
)
|
|
```
|
|
|
|
## Message Format
|
|
|
|
Rendered prompts return a messages array following the standard MCP format. Each message includes:
|
|
- `role`: The message role ("user" or "assistant")
|
|
- `content`: The message text content
|
|
|
|
Multi-message prompts are supported - the array will contain all messages in order.
|
|
|
|
## Binary Content
|
|
|
|
Unlike resources, prompts always return text content. There is no binary encoding needed.
|