-
Notifications
You must be signed in to change notification settings - Fork 742
Sampling #386
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Sampling #386
Changes from all commits
04612c3
b2ad462
c995ec5
2d4f3bd
f9161bf
6967174
6bbb18d
d5265a5
3b6be04
fec2a5a
fbda14a
f8c844a
efb8108
6d4d6c6
38f2b60
b6db823
1e0f411
551a075
874537e
9c908aa
345fecf
b034621
7d9120f
0d81b9a
effa673
c27b588
66550c1
aa23e59
fced30c
e4a256b
332d71d
b096b6e
ccdd310
5da0ec4
d6bc44c
fa96d30
11956c5
bd65320
cbbbd9c
2ba325f
7a8ece7
5b47509
6242777
a1f43e0
22242c9
ad3e04a
e1eee3a
cf6f205
e81f8b2
26eb541
3601bd0
8dcbf8b
d17b2c0
5134ee0
1901dba
2fa0347
4bd0a3d
d39b979
4f8bf92
882dc29
7176f5d
50470a8
dfdd558
fd2f1c1
553a567
98b1d7e
83ce967
39751c2
ec3b314
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,54 +1,14 @@ | ||||||
from mcp.server.fastmcp import FastMCP | ||||||
import datetime | ||||||
from mcp.types import ModelPreferences, ModelHint, SamplingMessage, TextContent | ||||||
import json | ||||||
|
||||||
# Store server start time | ||||||
SERVER_START_TIME = datetime.datetime.utcnow() | ||||||
|
||||||
mcp = FastMCP("Resource Demo MCP Server") | ||||||
|
||||||
# Define some static resources | ||||||
STATIC_RESOURCES = { | ||||||
"demo://docs/readme": { | ||||||
"name": "README", | ||||||
"description": "A sample README file.", | ||||||
"content_type": "text/markdown", | ||||||
"content": "# Demo Resource Server\n\nThis is a sample README resource provided by the demo MCP server.", | ||||||
}, | ||||||
"demo://data/users": { | ||||||
"name": "User Data", | ||||||
"description": "Sample user data in JSON format.", | ||||||
"content_type": "application/json", | ||||||
"content": json.dumps( | ||||||
[ | ||||||
{"id": 1, "name": "Alice"}, | ||||||
{"id": 2, "name": "Bob"}, | ||||||
{"id": 3, "name": "Charlie"}, | ||||||
], | ||||||
indent=2, | ||||||
), | ||||||
}, | ||||||
} | ||||||
|
||||||
|
||||||
@mcp.resource("demo://docs/readme") | ||||||
def get_readme(): | ||||||
"""Provide the README file content.""" | ||||||
meta = STATIC_RESOURCES["demo://docs/readme"] | ||||||
return meta["content"] | ||||||
|
||||||
|
||||||
@mcp.resource("demo://data/users") | ||||||
def get_users(): | ||||||
"""Provide user data.""" | ||||||
meta = STATIC_RESOURCES["demo://data/users"] | ||||||
return meta["content"] | ||||||
|
||||||
|
||||||
@mcp.resource("demo://{city}/weather") | ||||||
def get_weather(city: str) -> str: | ||||||
"""Provide a simple weather report for a given city.""" | ||||||
return f"It is sunny in {city} today!" | ||||||
return "# Demo Resource Server\n\nThis is a sample README resource provided by the demo MCP server." | ||||||
|
||||||
|
||||||
@mcp.prompt() | ||||||
|
@@ -60,6 +20,53 @@ def echo(message: str) -> str: | |||||
return f"Prompt: {message}" | ||||||
|
||||||
|
||||||
@mcp.resource("demo://data/friends") | ||||||
def get_users(): | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function name
Suggested change
Spotted by Diamond |
||||||
"""Provide my friend list.""" | ||||||
return ( | ||||||
json.dumps( | ||||||
[ | ||||||
{"id": 1, "friend": "Alice"}, | ||||||
] | ||||||
) | ||||||
) | ||||||
|
||||||
|
||||||
@mcp.prompt() | ||||||
def get_haiku_prompt(topic: str) -> str: | ||||||
"""Get a haiku prompt about a given topic.""" | ||||||
return f"I am fascinated about {topic}. Can you generate a haiku combining {topic} + my friend name?" | ||||||
|
||||||
|
||||||
@mcp.tool() | ||||||
async def get_haiku(topic: str) -> str: | ||||||
"""Get a haiku about a given topic.""" | ||||||
haiku = await mcp.get_context().session.create_message( | ||||||
messages=[ | ||||||
SamplingMessage( | ||||||
role="user", | ||||||
content=TextContent( | ||||||
type="text", text=f"Generate a haiku about {topic}." | ||||||
), | ||||||
) | ||||||
], | ||||||
system_prompt="You are a poet.", | ||||||
max_tokens=100, | ||||||
temperature=0.7, | ||||||
model_preferences=ModelPreferences( | ||||||
hints=[ModelHint(name="gpt-4o-mini")], | ||||||
costPriority=0.1, | ||||||
speedPriority=0.8, | ||||||
intelligencePriority=0.1, | ||||||
), | ||||||
) | ||||||
|
||||||
if isinstance(haiku.content, TextContent): | ||||||
return haiku.content.text | ||||||
else: | ||||||
return "Haiku generation failed, unexpected content type." | ||||||
|
||||||
roman-van-der-krogt marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
def main(): | ||||||
"""Main entry point for the MCP server.""" | ||||||
mcp.run() | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
# MCP Sampling Example | ||
|
||
This example demonstrates how to use **MCP sampling** in an agent application. | ||
It shows how to connect to an MCP server that exposes a tool that uses a sampling request to generate a response. | ||
|
||
--- | ||
|
||
## What is MCP sampling? | ||
Sampling in MCP allows servers to implement agentic behaviors, by enabling LLM calls to occur nested inside other MCP server features. | ||
Following the MCP recommendations, users are prompted to approve sampling requests, as well as the output produced by the LLM for the sampling request. | ||
More details can be found in the [MCP documentation](https://modelcontextprotocol.io/specification/2025-06-18/client/sampling). | ||
|
||
This example demonstrates sampling using [MCP agent servers](https://github.com/lastmile-ai/mcp-agent/blob/main/examples/mcp_agent_server/README.md). | ||
It is also possible to use sampling when explicitly creating an MCP client. The code for that would look like the following: | ||
|
||
```python | ||
settings = ... # MCP agent configuration | ||
registry = ServerRegistry(settings) | ||
|
||
@mcp.tool() | ||
async def my_tool(input: str, ctx: Context) -> str: | ||
async with gen_client("my_server", registry, upstream_session=ctx.session) as my_client: | ||
result = await my_client.call_tool("some_tool", {"input": input}) | ||
... # etc | ||
``` | ||
|
||
--- | ||
|
||
## Example Overview | ||
|
||
- **nested_server.py** implements a simple MCP server that uses sampling to generate a haiku about a given topic | ||
- **demo_server.py** implements a simple MCP server that implements an agent generating haikus using the tool exposed by `nested_server.py` | ||
- **main.py** shows how to: | ||
1. Connect an agent to the demo MCP server, and then | ||
2. Invoke the agent implemented by the demo MCP server, thereby triggering a sampling request. | ||
|
||
--- | ||
|
||
## Architecture | ||
|
||
```plaintext | ||
┌────────────────────┐ | ||
│ nested_server │──────┐ | ||
│ MCP Server │ │ | ||
└─────────┬──────────┘ │ | ||
│ │ | ||
▼ │ | ||
┌────────────────────┐ │ | ||
│ demo_server │ │ | ||
│ MCP Server │ │ | ||
└─────────┬──────────┘ │ | ||
│ sampling, via user approval | ||
▼ │ | ||
┌────────────────────┐ │ | ||
│ Agent (Python) │ │ | ||
│ + LLM (OpenAI) │◀─────┘ | ||
└─────────┬──────────┘ | ||
│ | ||
▼ | ||
[User/Developer] | ||
``` | ||
|
||
--- | ||
|
||
## 1. Setup | ||
|
||
Clone the repo and navigate to this example: | ||
|
||
```bash | ||
git clone https://github.com/lastmile-ai/mcp-agent.git | ||
cd mcp-agent/examples/mcp/mcp_sampling | ||
``` | ||
|
||
--- | ||
|
||
## 2. Run the Agent Example | ||
|
||
Run the agent script which should auto install all necessary dependencies: | ||
|
||
```bash | ||
uv run main.py | ||
``` | ||
|
||
You should see logs showing: | ||
|
||
- The agent connecting to the demo server, and calling the tool | ||
- A request to approve the sampling request; type `approve` to approve (anything else will deny the request) | ||
- A request to approve the result of the sampling request | ||
- The final result of the tool call | ||
|
||
--- | ||
|
||
## References | ||
|
||
- [Model Context Protocol (MCP) Introduction](https://modelcontextprotocol.io/introduction) | ||
- [MCP Agent Framework](https://github.com/lastmile-ai/mcp-agent) | ||
- [MCP Server Sampling](https://modelcontextprotocol.io/specification/2025-06-18/client/sampling) | ||
|
||
--- | ||
|
||
This example is a minimal, practical demonstration of how to use **MCP sampling** as first-class context for agent applications. |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,120 @@ | ||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||
A simple workflow server which generates haikus on request using a tool. | ||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
import asyncio | ||||||||||||||||||||||||||||||||||||||||
import logging | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
import yaml | ||||||||||||||||||||||||||||||||||||||||
from mcp.server.fastmcp import FastMCP | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
from mcp_agent.app import MCPApp | ||||||||||||||||||||||||||||||||||||||||
from mcp_agent.config import Settings, LoggerSettings, MCPSettings, MCPServerSettings, LogPathSettings | ||||||||||||||||||||||||||||||||||||||||
from mcp_agent.server.app_server import create_mcp_server_for_app | ||||||||||||||||||||||||||||||||||||||||
from mcp_agent.agents.agent import Agent | ||||||||||||||||||||||||||||||||||||||||
from mcp_agent.workflows.llm.augmented_llm_openai import OpenAIAugmentedLLM | ||||||||||||||||||||||||||||||||||||||||
from mcp_agent.executor.workflow import Workflow, WorkflowResult | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
# Initialize logging | ||||||||||||||||||||||||||||||||||||||||
logging.basicConfig(level=logging.INFO) | ||||||||||||||||||||||||||||||||||||||||
logger = logging.getLogger(__name__) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
# Note: This is purely optional: | ||||||||||||||||||||||||||||||||||||||||
# if not provided, a default FastMCP server will be created by MCPApp using create_mcp_server_for_app() | ||||||||||||||||||||||||||||||||||||||||
mcp = FastMCP(name="haiku_generation_server", description="Server to generate haikus") | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
# Create settings explicitly, as we want to use a different configuration from the main app | ||||||||||||||||||||||||||||||||||||||||
secrets_file = Settings.find_secrets() | ||||||||||||||||||||||||||||||||||||||||
if secrets_file and secrets_file.exists(): | ||||||||||||||||||||||||||||||||||||||||
with open(secrets_file, "r", encoding="utf-8") as f: | ||||||||||||||||||||||||||||||||||||||||
yaml_secrets = yaml.safe_load(f) or {} | ||||||||||||||||||||||||||||||||||||||||
openai_secret = yaml_secrets["openai"] | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
settings = Settings( | ||||||||||||||||||||||||||||||||||||||||
Comment on lines
+27
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid NameError/KeyError when secrets are absent.
-secrets_file = Settings.find_secrets()
-if secrets_file and secrets_file.exists():
- with open(secrets_file, "r", encoding="utf-8") as f:
- yaml_secrets = yaml.safe_load(f) or {}
- openai_secret = yaml_secrets["openai"]
+secrets_file = Settings.find_secrets()
+openai_secret = None
+if secrets_file and secrets_file.exists():
+ with open(secrets_file, "r", encoding="utf-8") as f:
+ yaml_secrets = yaml.safe_load(f) or {}
+ openai_secret = yaml_secrets.get("openai") 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||
execution_engine="asyncio", | ||||||||||||||||||||||||||||||||||||||||
logger=LoggerSettings( | ||||||||||||||||||||||||||||||||||||||||
type="file", | ||||||||||||||||||||||||||||||||||||||||
level="debug", | ||||||||||||||||||||||||||||||||||||||||
path_settings=LogPathSettings( | ||||||||||||||||||||||||||||||||||||||||
path_pattern="logs/demo_server-{unique_id}.jsonl", | ||||||||||||||||||||||||||||||||||||||||
unique_id="timestamp", | ||||||||||||||||||||||||||||||||||||||||
timestamp_format="%Y%m%d_%H%M%S"), | ||||||||||||||||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||||||||||||||||
mcp=MCPSettings( | ||||||||||||||||||||||||||||||||||||||||
servers={ | ||||||||||||||||||||||||||||||||||||||||
"haiku_server": MCPServerSettings( | ||||||||||||||||||||||||||||||||||||||||
command="uv", | ||||||||||||||||||||||||||||||||||||||||
args=["run", "nested_server.py"], | ||||||||||||||||||||||||||||||||||||||||
description="nested server providing a haiku generator" | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||||||||||||||||
openai=openai_secret | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
# Define the MCPApp instance | ||||||||||||||||||||||||||||||||||||||||
app = MCPApp( | ||||||||||||||||||||||||||||||||||||||||
name="haiku_server", | ||||||||||||||||||||||||||||||||||||||||
description="Haiku server", | ||||||||||||||||||||||||||||||||||||||||
mcp=mcp, | ||||||||||||||||||||||||||||||||||||||||
settings=settings | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
@app.workflow | ||||||||||||||||||||||||||||||||||||||||
class HaikuWorkflow(Workflow[str]): | ||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||
A workflow that generates haikus on request. | ||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
@app.workflow_run | ||||||||||||||||||||||||||||||||||||||||
async def run(self, input: str) -> WorkflowResult[str]: | ||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||
Run the haiku agent workflow. | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
Args: | ||||||||||||||||||||||||||||||||||||||||
input: The topic to create a haiku about | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
Returns: | ||||||||||||||||||||||||||||||||||||||||
WorkflowResult containing the processed data. | ||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
logger = app.logger | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
haiku_agent = Agent( | ||||||||||||||||||||||||||||||||||||||||
name="poet", | ||||||||||||||||||||||||||||||||||||||||
instruction="""You are an agent with access to a tool that helps you write haikus.""", | ||||||||||||||||||||||||||||||||||||||||
server_names=["haiku_server"], | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
async with haiku_agent: | ||||||||||||||||||||||||||||||||||||||||
llm = await haiku_agent.attach_llm(OpenAIAugmentedLLM) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
result = await llm.generate_str( | ||||||||||||||||||||||||||||||||||||||||
message=f"Write a haiku about {input} using the tool at your disposal", | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
logger.info(f"Input: {input}, Result: {result}") | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
return WorkflowResult(value=result) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
async def main(): | ||||||||||||||||||||||||||||||||||||||||
async with app.run() as agent_app: | ||||||||||||||||||||||||||||||||||||||||
# Log registered workflows and agent configurations | ||||||||||||||||||||||||||||||||||||||||
logger.info(f"Creating MCP server for {agent_app.name}") | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
logger.info("Registered workflows:") | ||||||||||||||||||||||||||||||||||||||||
for workflow_id in agent_app.workflows: | ||||||||||||||||||||||||||||||||||||||||
logger.info(f" - {workflow_id}") | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
# Create the MCP server that exposes both workflows and agent configurations | ||||||||||||||||||||||||||||||||||||||||
mcp_server = create_mcp_server_for_app(agent_app, **({})) | ||||||||||||||||||||||||||||||||||||||||
logger.info(f"MCP Server settings: {mcp_server.settings}") | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
# Run the server | ||||||||||||||||||||||||||||||||||||||||
await mcp_server.run_stdio_async() | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
if __name__ == "__main__": | ||||||||||||||||||||||||||||||||||||||||
asyncio.run(main()) |
Uh oh!
There was an error while loading. Please reload this page.