Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d1d2ecc

Browse files
thoothein
andauthored
Support mcp prompts (openai#1010)
### Summary Add MCP prompt support to enable user-controlled agent instruction generation. This enhancement allows MCP servers to provide prompts that can be used to dynamically generate agent instructions, enabling more flexible and user-controlled agent behavior. **Key changes:** - Added abstract `list_prompts()` and `get_prompt()` methods to base `MCPServer` class - Implemented prompt support in `MCPServerStdio` and `MCPServerStreamableHttp` classes - Added comprehensive test suite for prompt functionality - Created example MCP prompt server with working demonstration - Updated documentation with prompt usage examples **Note:** This PR implements MCP prompt support only. Resource support is not included in this implementation. ### Test plan - **Unit tests**: Added 11 comprehensive tests in `tests/mcp/test_prompt_server.py` covering: - Prompt listing and retrieval - Argument formatting and validation - Agent integration with prompt-generated instructions - Streaming and non-streaming scenarios - Error handling for missing prompts - **Integration tests**: Updated existing MCP test suite to handle new abstract methods - **Example verification**: Created working example with MCP prompt server and client - **All tests pass**: 450/450 tests passing after adding optional dependencies ### Issue number Partially addresses openai#544 (prompts only, resources not implemented) ### Checks - [x] I've added new tests (if relevant) - [x] I've added/updated the relevant documentation - [x] I've run `make lint` and `make format` - [x] I've made sure tests pass --------- Co-authored-by: thein <[email protected]>
1 parent f09874c commit d1d2ecc

File tree

7 files changed

+557
-6
lines changed

7 files changed

+557
-6
lines changed

docs/mcp.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ The [Model context protocol](https://modelcontextprotocol.io/introduction) (aka
44

55
> MCP is an open protocol that standardizes how applications provide context to LLMs. Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect your devices to various peripherals and accessories, MCP provides a standardized way to connect AI models to different data sources and tools.
66
7-
The Agents SDK has support for MCP. This enables you to use a wide range of MCP servers to provide tools to your Agents.
7+
The Agents SDK has support for MCP. This enables you to use a wide range of MCP servers to provide tools and prompts to your Agents.
88

99
## MCP servers
1010

@@ -135,6 +135,38 @@ The `ToolFilterContext` provides access to:
135135
- `agent`: The agent requesting the tools
136136
- `server_name`: The name of the MCP server
137137

138+
## Prompts
139+
140+
MCP servers can also provide prompts that can be used to dynamically generate agent instructions. This allows you to create reusable instruction templates that can be customized with parameters.
141+
142+
### Using prompts
143+
144+
MCP servers that support prompts provide two key methods:
145+
146+
- `list_prompts()`: Lists all available prompts on the server
147+
- `get_prompt(name, arguments)`: Gets a specific prompt with optional parameters
148+
149+
```python
150+
# List available prompts
151+
prompts_result = await server.list_prompts()
152+
for prompt in prompts_result.prompts:
153+
print(f"Prompt: {prompt.name} - {prompt.description}")
154+
155+
# Get a specific prompt with parameters
156+
prompt_result = await server.get_prompt(
157+
"generate_code_review_instructions",
158+
{"focus": "security vulnerabilities", "language": "python"}
159+
)
160+
instructions = prompt_result.messages[0].content.text
161+
162+
# Use the prompt-generated instructions with an Agent
163+
agent = Agent(
164+
name="Code Reviewer",
165+
instructions=instructions, # Instructions from MCP prompt
166+
mcp_servers=[server]
167+
)
168+
```
169+
138170
## Caching
139171

140172
Every time an Agent runs, it calls `list_tools()` on the MCP server. This can be a latency hit, especially if the server is a remote server. To automatically cache the list of tools, you can pass `cache_tools_list=True` to [`MCPServerStdio`][agents.mcp.server.MCPServerStdio], [`MCPServerSse`][agents.mcp.server.MCPServerSse], and [`MCPServerStreamableHttp`][agents.mcp.server.MCPServerStreamableHttp]. You should only do this if you're certain the tool list will not change.

examples/mcp/prompt_server/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# MCP Prompt Server Example
2+
3+
This example uses a local MCP prompt server in [server.py](server.py).
4+
5+
Run the example via:
6+
7+
```
8+
uv run python examples/mcp/prompt_server/main.py
9+
```
10+
11+
## Details
12+
13+
The example uses the `MCPServerStreamableHttp` class from `agents.mcp`. The server runs in a sub-process at `http://localhost:8000/mcp` and provides user-controlled prompts that generate agent instructions.
14+
15+
The server exposes prompts like `generate_code_review_instructions` that take parameters such as focus area and programming language. The agent calls these prompts to dynamically generate its system instructions based on user-provided parameters.
16+
17+
## Workflow
18+
19+
The example demonstrates two key functions:
20+
21+
1. **`show_available_prompts`** - Lists all available prompts on the MCP server, showing users what prompts they can select from. This demonstrates the discovery aspect of MCP prompts.
22+
23+
2. **`demo_code_review`** - Shows the complete user-controlled prompt workflow:
24+
- Calls `generate_code_review_instructions` with specific parameters (focus: "security vulnerabilities", language: "python")
25+
- Uses the generated instructions to create an Agent with specialized code review capabilities
26+
- Runs the agent against vulnerable sample code (command injection via `os.system`)
27+
- The agent analyzes the code and provides security-focused feedback using available tools
28+
29+
This pattern allows users to dynamically configure agent behavior through MCP prompts rather than hardcoded instructions.

examples/mcp/prompt_server/main.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import asyncio
2+
import os
3+
import shutil
4+
import subprocess
5+
import time
6+
from typing import Any
7+
8+
from agents import Agent, Runner, gen_trace_id, trace
9+
from agents.mcp import MCPServer, MCPServerStreamableHttp
10+
from agents.model_settings import ModelSettings
11+
12+
13+
async def get_instructions_from_prompt(mcp_server: MCPServer, prompt_name: str, **kwargs) -> str:
14+
"""Get agent instructions by calling MCP prompt endpoint (user-controlled)"""
15+
print(f"Getting instructions from prompt: {prompt_name}")
16+
17+
try:
18+
prompt_result = await mcp_server.get_prompt(prompt_name, kwargs)
19+
content = prompt_result.messages[0].content
20+
if hasattr(content, 'text'):
21+
instructions = content.text
22+
else:
23+
instructions = str(content)
24+
print("Generated instructions")
25+
return instructions
26+
except Exception as e:
27+
print(f"Failed to get instructions: {e}")
28+
return f"You are a helpful assistant. Error: {e}"
29+
30+
31+
async def demo_code_review(mcp_server: MCPServer):
32+
"""Demo: Code review with user-selected prompt"""
33+
print("=== CODE REVIEW DEMO ===")
34+
35+
# User explicitly selects prompt and parameters
36+
instructions = await get_instructions_from_prompt(
37+
mcp_server,
38+
"generate_code_review_instructions",
39+
focus="security vulnerabilities",
40+
language="python",
41+
)
42+
43+
agent = Agent(
44+
name="Code Reviewer Agent",
45+
instructions=instructions, # Instructions from MCP prompt
46+
model_settings=ModelSettings(tool_choice="auto"),
47+
)
48+
49+
message = """Please review this code:
50+
51+
def process_user_input(user_input):
52+
command = f"echo {user_input}"
53+
os.system(command)
54+
return "Command executed"
55+
56+
"""
57+
58+
print(f"Running: {message[:60]}...")
59+
result = await Runner.run(starting_agent=agent, input=message)
60+
print(result.final_output)
61+
print("\n" + "=" * 50 + "\n")
62+
63+
64+
async def show_available_prompts(mcp_server: MCPServer):
65+
"""Show available prompts for user selection"""
66+
print("=== AVAILABLE PROMPTS ===")
67+
68+
prompts_result = await mcp_server.list_prompts()
69+
print("User can select from these prompts:")
70+
for i, prompt in enumerate(prompts_result.prompts, 1):
71+
print(f" {i}. {prompt.name} - {prompt.description}")
72+
print()
73+
74+
75+
async def main():
76+
async with MCPServerStreamableHttp(
77+
name="Simple Prompt Server",
78+
params={"url": "http://localhost:8000/mcp"},
79+
) as server:
80+
trace_id = gen_trace_id()
81+
with trace(workflow_name="Simple Prompt Demo", trace_id=trace_id):
82+
print(f"Trace: https://platform.openai.com/traces/trace?trace_id={trace_id}\n")
83+
84+
await show_available_prompts(server)
85+
await demo_code_review(server)
86+
87+
88+
if __name__ == "__main__":
89+
if not shutil.which("uv"):
90+
raise RuntimeError("uv is not installed")
91+
92+
process: subprocess.Popen[Any] | None = None
93+
try:
94+
this_dir = os.path.dirname(os.path.abspath(__file__))
95+
server_file = os.path.join(this_dir, "server.py")
96+
97+
print("Starting Simple Prompt Server...")
98+
process = subprocess.Popen(["uv", "run", server_file])
99+
time.sleep(3)
100+
print("Server started\n")
101+
except Exception as e:
102+
print(f"Error starting server: {e}")
103+
exit(1)
104+
105+
try:
106+
asyncio.run(main())
107+
finally:
108+
if process:
109+
process.terminate()
110+
print("Server terminated.")

examples/mcp/prompt_server/server.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from mcp.server.fastmcp import FastMCP
2+
3+
# Create server
4+
mcp = FastMCP("Prompt Server")
5+
6+
7+
# Instruction-generating prompts (user-controlled)
8+
@mcp.prompt()
9+
def generate_code_review_instructions(
10+
focus: str = "general code quality", language: str = "python"
11+
) -> str:
12+
"""Generate agent instructions for code review tasks"""
13+
print(f"[debug-server] generate_code_review_instructions({focus}, {language})")
14+
15+
return f"""You are a senior {language} code review specialist. Your role is to provide comprehensive code analysis with focus on {focus}.
16+
17+
INSTRUCTIONS:
18+
- Analyze code for quality, security, performance, and best practices
19+
- Provide specific, actionable feedback with examples
20+
- Identify potential bugs, vulnerabilities, and optimization opportunities
21+
- Suggest improvements with code examples when applicable
22+
- Be constructive and educational in your feedback
23+
- Focus particularly on {focus} aspects
24+
25+
RESPONSE FORMAT:
26+
1. Overall Assessment
27+
2. Specific Issues Found
28+
3. Security Considerations
29+
4. Performance Notes
30+
5. Recommended Improvements
31+
6. Best Practices Suggestions
32+
33+
Use the available tools to check current time if you need timestamps for your analysis."""
34+
35+
36+
if __name__ == "__main__":
37+
mcp.run(transport="streamable-http")

src/agents/mcp/server.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from mcp.client.sse import sse_client
1414
from mcp.client.streamable_http import GetSessionIdCallback, streamablehttp_client
1515
from mcp.shared.message import SessionMessage
16-
from mcp.types import CallToolResult, InitializeResult
16+
from mcp.types import CallToolResult, GetPromptResult, InitializeResult, ListPromptsResult
1717
from typing_extensions import NotRequired, TypedDict
1818

1919
from ..exceptions import UserError
@@ -63,6 +63,20 @@ async def call_tool(self, tool_name: str, arguments: dict[str, Any] | None) -> C
6363
"""Invoke a tool on the server."""
6464
pass
6565

66+
@abc.abstractmethod
67+
async def list_prompts(
68+
self,
69+
) -> ListPromptsResult:
70+
"""List the prompts available on the server."""
71+
pass
72+
73+
@abc.abstractmethod
74+
async def get_prompt(
75+
self, name: str, arguments: dict[str, Any] | None = None
76+
) -> GetPromptResult:
77+
"""Get a specific prompt from the server."""
78+
pass
79+
6680

6781
class _MCPServerWithClientSession(MCPServer, abc.ABC):
6882
"""Base class for MCP servers that use a `ClientSession` to communicate with the server."""
@@ -118,9 +132,7 @@ async def _apply_tool_filter(
118132
return await self._apply_dynamic_tool_filter(tools, run_context, agent)
119133

120134
def _apply_static_tool_filter(
121-
self,
122-
tools: list[MCPTool],
123-
static_filter: ToolFilterStatic
135+
self, tools: list[MCPTool], static_filter: ToolFilterStatic
124136
) -> list[MCPTool]:
125137
"""Apply static tool filtering based on allowlist and blocklist."""
126138
filtered_tools = tools
@@ -261,6 +273,24 @@ async def call_tool(self, tool_name: str, arguments: dict[str, Any] | None) -> C
261273

262274
return await self.session.call_tool(tool_name, arguments)
263275

276+
async def list_prompts(
277+
self,
278+
) -> ListPromptsResult:
279+
"""List the prompts available on the server."""
280+
if not self.session:
281+
raise UserError("Server not initialized. Make sure you call `connect()` first.")
282+
283+
return await self.session.list_prompts()
284+
285+
async def get_prompt(
286+
self, name: str, arguments: dict[str, Any] | None = None
287+
) -> GetPromptResult:
288+
"""Get a specific prompt from the server."""
289+
if not self.session:
290+
raise UserError("Server not initialized. Make sure you call `connect()` first.")
291+
292+
return await self.session.get_prompt(name, arguments)
293+
264294
async def cleanup(self):
265295
"""Cleanup the server."""
266296
async with self._cleanup_lock:

tests/mcp/helpers.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import Any
55

66
from mcp import Tool as MCPTool
7-
from mcp.types import CallToolResult, TextContent
7+
from mcp.types import CallToolResult, GetPromptResult, ListPromptsResult, PromptMessage, TextContent
88

99
from agents.mcp import MCPServer
1010
from agents.mcp.server import _MCPServerWithClientSession
@@ -94,6 +94,18 @@ async def call_tool(self, tool_name: str, arguments: dict[str, Any] | None) -> C
9494
content=[TextContent(text=self.tool_results[-1], type="text")],
9595
)
9696

97+
async def list_prompts(self, run_context=None, agent=None) -> ListPromptsResult:
98+
"""Return empty list of prompts for fake server"""
99+
return ListPromptsResult(prompts=[])
100+
101+
async def get_prompt(
102+
self, name: str, arguments: dict[str, Any] | None = None
103+
) -> GetPromptResult:
104+
"""Return a simple prompt result for fake server"""
105+
content = f"Fake prompt content for {name}"
106+
message = PromptMessage(role="user", content=TextContent(type="text", text=content))
107+
return GetPromptResult(description=f"Fake prompt: {name}", messages=[message])
108+
97109
@property
98110
def name(self) -> str:
99111
return self._server_name

0 commit comments

Comments
 (0)