-
Notifications
You must be signed in to change notification settings - Fork 768
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
Closed
Closed
Sampling #386
Changes from 55 commits
Commits
Show all changes
69 commits
Select commit
Hold shift + click to select a range
04612c3
Add resource management to MCPAggregator and Agent classes
StreetLamb b2ad462
Update MCPAggregator to fetch resources using URI
StreetLamb c995ec5
Add utility modules for content and resource handling
StreetLamb 2d4f3bd
Add resource URI parameter to LLM generation methods in AugmentedLLM …
StreetLamb f9161bf
Fix type conversion for resource URI in MCPAggregator
StreetLamb 6967174
Add basic example to showcase using mcp resources
StreetLamb 6bbb18d
Refactor resource URI handling in AugmentedLLM and OpenAIAugmentedLLM…
StreetLamb d5265a5
Enhance MCP Primitives example to demonstrate resource usage and serv…
StreetLamb 3b6be04
Add OpenAIConverter class for converting MCP message types to OpenAI …
StreetLamb fec2a5a
Add AnthropicConverter for converting MCP message types to Anthropic …
StreetLamb fbda14a
Add PromptMessageMultipart class for handling multiple content parts …
StreetLamb f8c844a
Add resource_uris parameter to generate_str and related methods in An…
StreetLamb efb8108
Add resource_uris parameter to generate methods in BedrockAugmentedLL…
StreetLamb 6d4d6c6
Add resource handling in AzureAugmentedLLM and implement AzureConvert…
StreetLamb 38f2b60
Add resource handling and GoogleConverter for multipart message conve…
StreetLamb b6db823
Add resource_uris parameter to generate_structured method in OllamaAu…
StreetLamb 1e0f411
Merge branch 'main' of https://github.com/lastmile-ai/mcp-agent into …
StreetLamb 551a075
Refactor resource handling in LLM classes to use attached resources i…
StreetLamb 874537e
Add prompt attachment functionality to LLM classes and update message…
StreetLamb 9c908aa
Add demo server implementation with resource and prompt handling
StreetLamb 345fecf
Update README to include prompts in MCP primitives example
StreetLamb b034621
Refactor settings in main.py
StreetLamb 7d9120f
Refactor LLM message handling to integrate PromptMessage support and …
StreetLamb 0d81b9a
Remove unused settings and health status resources from demo server; …
StreetLamb effa673
Refactor and add comments in example
StreetLamb c27b588
Refactor assertion in TestAnthropicAugmentedLLM to improve readability
StreetLamb 66550c1
Add create_prompt method to generate prompt messages from names and r…
StreetLamb aa23e59
Update README and main.py to reflect changes in resource and prompt r…
StreetLamb fced30c
Enhance MCPAggregator to return resources alongside tools and prompts…
StreetLamb e4a256b
Add comprehensive tests for MIME utilities and multipart converters
StreetLamb 332d71d
Refactor resource URI handling to use AnyUrl for improved type safety…
StreetLamb b096b6e
Fix exception class docstring and update file tags in OpenAIConverter…
StreetLamb ccdd310
Add comprehensive tests for Azure, Bedrock, and Google multipart conv…
StreetLamb 5da0ec4
Minor code formatting
StreetLamb d6bc44c
Add tests for generating responses with various input types in Augmen…
StreetLamb fa96d30
Refactor message conversion methods to use unified mixed message hand…
StreetLamb 11956c5
Refactor message tracing logic in AugmentedLLM to simplify attribute …
StreetLamb bd65320
Minor formatting
StreetLamb cbbbd9c
Refactor AzureConverter tests to assert list content structure for te…
StreetLamb 2ba325f
Fix potential issues raised by coderabbitai
StreetLamb 7a8ece7
Refactor URI handling to use str() for better compatibility and clari…
StreetLamb 5b47509
Refactor URI handling in Azure and Google converters to use str() for…
StreetLamb 6242777
Remove unnecessary import of AnyUrl in test_create_fallback_text_with…
StreetLamb a1f43e0
Add async get_poem tool to retrieve poems based on a topic; fix loggi…
StreetLamb 22242c9
Store active LLM instance in context for MCP sampling callbacks; upda…
StreetLamb ad3e04a
Implement SamplingHandler for human-in-the-loop sampling requests; re…
StreetLamb e1eee3a
Refactor human approval workflow in SamplingHandler to include reject…
StreetLamb cf6f205
Update requirements and lock files to include fastmcp dependency and …
StreetLamb e81f8b2
Refactor get_poem tool to get_haiku; update sampling logic for haiku …
StreetLamb 26eb541
Refactor demo server to streamline user data retrieval; update main a…
StreetLamb 3601bd0
Merge branch 'main' of https://github.com/lastmile-ai/mcp-agent into …
StreetLamb 8dcbf8b
Remove external FastMCP dependency, update example to use native Fast…
StreetLamb d17b2c0
Merge branch 'main' into feat/sampling
roman-van-der-krogt 5134ee0
sampling updates
roman-van-der-krogt 1901dba
remove temp files that shouldn't have been checked in
roman-van-der-krogt 2fa0347
fix linting issues
roman-van-der-krogt 4bd0a3d
test fixes & review feedback
roman-van-der-krogt d39b979
fix linting issue after test updates
roman-van-der-krogt 4f8bf92
Merge branch 'main' into rvdk/sampling
roman-van-der-krogt 882dc29
review comments
roman-van-der-krogt 7176f5d
Merge branch 'main' into rvdk/sampling
roman-van-der-krogt 50470a8
update code comment
roman-van-der-krogt dfdd558
fix lint errors
roman-van-der-krogt fd2f1c1
add proxy for temporal based flows
roman-van-der-krogt 553a567
Merge branch 'main' into rvdk/sampling
roman-van-der-krogt 98b1d7e
be consistent with notification handling
roman-van-der-krogt 83ce967
Merge branch 'main' into rvdk/sampling
roman-van-der-krogt 39751c2
cleanups
roman-van-der-krogt ec3b314
more cleanups
roman-van-der-krogt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
""" | ||
Workflow MCP Server Example | ||
This example demonstrates three approaches to creating agents and workflows: | ||
1. Traditional workflow-based approach with manual agent creation | ||
2. Programmatic agent configuration using AgentConfig | ||
3. Declarative agent configuration using FastMCPApp decorators | ||
""" | ||
|
||
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 | ||
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"] | ||
|
||
roman-van-der-krogt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
settings = Settings( | ||
execution_engine="asyncio", | ||
logger=LoggerSettings(type="console", level="debug"), | ||
mcp=MCPSettings( | ||
servers={ | ||
"haiku_server": MCPServerSettings( | ||
command="uv", | ||
args=["run", "nested_server.py"], | ||
description="nested server providing a haiku generator" | ||
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. 🛠️ Refactor suggestion Make nested_server path robust. Use a path relative to this file to avoid CWD-related failures. +from pathlib import Path
@@
- args=["run", "nested_server.py"],
+ args=["run", str(Path(__file__).parent / "nested_server.py")], Also applies to: 8-8 🤖 Prompt for AI Agents
|
||
) | ||
} | ||
), | ||
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 | ||
context = app.context | ||
|
||
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 | ||
fast_mcp_settings = ( | ||
{"host": "localhost", "port": 8001, "debug": True, "log_level": "DEBUG"} | ||
) | ||
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()) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function name
get_users()
doesn't match its purpose or registration. It's registered as a resource fordemo://data/friends
and returns friend data, but the function name suggests it retrieves user data. Consider renaming this function toget_friends()
to maintain semantic consistency between the function name, its registration path, and the data it returns.Spotted by Diamond

Is this helpful? React 👍 or 👎 to let us know.