Skip to content

feat: Add MCP client tool #49

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

mkmeral
Copy link

@mkmeral mkmeral commented May 26, 2025

Description

This PR introduces a new MCP (Model Context Protocol) client tool for Strands Agents that provides a high-level interface for connecting to any MCP server with simplified configuration and enhanced functionality.

Key Features:

  • Multiple Transport Support: Connect to MCP servers via stdio (for local processes) or SSE (Server-Sent Events) for web-based servers
  • Simplified Configuration: Direct parameter passing without complex nested configurations
  • Connection Management: Thread-safe connection storage with support for multiple simultaneous connections
  • Tool Discovery: List available tools from connected MCP servers
  • Direct Tool Invocation: Call MCP tools directly or load them into the agent's tool registry
  • Environment Variable Support: Pass environment variables to stdio-based MCP servers
  • Comprehensive Error Handling: Detailed error messages and connection status tracking

Usage Examples:

# Connect to a custom MCP server
agent.tool.mcp_client(
    action="connect",
    connection_id="my_server",
    transport="stdio",
    command="python",
    args=["server.py"]
)

# List available tools
agent.tool.mcp_client(
    action="list_tools",
    connection_id="my_server"
)

# Call a tool directly
agent.tool.mcp_client(
    action="call_tool",
    connection_id="my_server",
    tool_name="calculate",
    x=10,
    y=20
)

# Load tools into agent's registry for direct access
agent.tool.mcp_client(
    action="load_tools",
    connection_id="my_server"
)

Related Issues

N/A

Documentation PR

N/A - Documentation is included in the README.md update

Type of Change

  • Bug fix
  • New Tool
  • Breaking change
  • Other (please describe):

Testing

The implementation includes comprehensive unit tests covering:

  • Connection management (stdio and SSE transports)
  • Tool listing and discovery
  • Direct tool invocation
  • Tool loading into agent registry
  • Error handling scenarios
  • Thread safety
  • Full workflow integration tests

Testing commands run:

  • hatch fmt --linter
  • hatch fmt --formatter
  • hatch test --all

Manual Testing

**user:** use perplexity mcp from ~/.aws/amazonq/mcp.json and tell me what techno events are in amsterdam this weekend

**strands:** connected to Perplexity MCP server using `mcp_client(action="connect")` with stdio transport, reading config from ~/.aws/amazonq/mcp.json

**strands:** called `mcp_client(action="call_tool", tool_name="perplexity_ask")` to search for Amsterdam techno events, found 4 events including Amsterdam Sessions at Machinegebouw

**user:** load mcp's tools

**strands:** executed `mcp_client(action="load_tools")` to register MCP tools directly into agent, successfully loaded mcp_perplexity_perplexity_ask tool

**user:** using this tool, tell me what events there are this weekend at radion, lofi and thuishaven

**strands:** directly called loaded `mcp_perplexity_perplexity_ask()` tool without going through mcp_client, found Vault Sessions x BASSIANI event at Radion with full lineup details

Key achievements:

  • Connected to MCP server from JSON config file
  • Called MCP tools through the client interface
  • Loaded MCP tools into agent's native tool registry
  • Used loaded MCP tools directly like built-in tools
  • Seamless integration of external MCP services into Strands workflow

Checklist

  • I have read the CONTRIBUTING document

  • I have added tests that prove my fix is effective or my feature works

  • I have updated the documentation accordingly

  • I have added an appropriate example to the documentation to outline the feature

  • My changes generate no new warnings

  • Any dependent changes have been merged and published

  • By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@mkmeral mkmeral requested a review from a team as a code owner May 26, 2025 14:21
@mkmeral
Copy link
Author

mkmeral commented May 27, 2025

Related strands-agents/agent-builder#4

Murat Kaan Meral added 2 commits May 31, 2025 00:23
- Add streamable_http transport option alongside stdio and sse
- Support headers, timeout, sse_read_timeout, terminate_on_close, and auth parameters
- Add comprehensive tests for streamable HTTP functionality
- Update README with streamable HTTP example
elif isinstance(result, list):
# If it's already a list of content items, use it directly
content = result
elif isinstance(result, dict):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make sure we support other types here like images?

Copy link
Member

@cagataycali cagataycali left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Approving and leaving a room to team to take a look!

@dbschmigelski dbschmigelski self-requested a review June 11, 2025 16:04
@Unshure Unshure assigned dbschmigelski and unassigned awsarron Jun 17, 2025
@@ -52,6 +52,7 @@ Strands Agents Tools provides a powerful set of tools for your agents to use. It
- ⏱️ **Task Scheduling** - Schedule and manage cron jobs
- 🧠 **Advanced Reasoning** - Tools for complex thinking and reasoning capabilities
- 🐝 **Swarm Intelligence** - Coordinate multiple AI agents for parallel problem solving with shared memory
- 🔌 **MCP Client** - Connect to any Model Context Protocol server and access remote tools
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can we edit this to callout that we are allowing an agent to autonomously perform this connection and dynamic loading. This was brought up on a client call and, as expected, this was called out as being potentially dangerous.

This tool is useful, I want to prevent customers from accidentally doing something like the following instead of using the MCPClient approach in the SDK

agent = Agent(tools=[mcp_client, use_browser])
agent.tool.mcp_client("connect")
# Agent uses the browser, then connects and downloads tools from an attackers mcp_server.

This be be documentation, but I also have concerns with this tool being called mcp_client. I would bet that we will get customers asking us "should I use mcp_client tool or MCPClient" because of this.

# Suppress warnings from MCP SDK about unknown notification types
# This is particularly useful for test servers that send non-standard notifications
# Users can override this by setting their own logging configuration
logging.getLogger("mcp.shared.session").setLevel(logging.ERROR)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I am using strands with mcp pinned at a particular version, how would I get a deprecation warning with this set to Error? Are we doing this elsewhere?

This forced suppression seems strange to me. Couldn't we just remove this and then if the user thinks it is noisy because they are using standard notifications override it themselves?

connection_id: str,
tool_name: str,
tool_spec: ToolSpec,
mcp_client: Any, # Required: direct MCP client reference
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why Any?

tool_name: str,
tool_spec: ToolSpec,
mcp_client: Any, # Required: direct MCP client reference
name_prefix: bool = True,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: name prefix as this param doesn't seem clear to me

super().__init__()
self.connection_id = connection_id
self.original_tool_name = tool_name
self.mcp_client = mcp_client # Direct client reference
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "# Direct client reference" does this add anything?

return {"toolUseId": tool_use_id, "status": "error", "content": [{"text": result["error"]}]}

# Convert MCP result to Strands format
content = []
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have https://github.com/strands-agents/sdk-python/blob/main/src/strands/tools/mcp/mcp_client.py#L262, is it possible to expose that so we can reuse it here rather than reimplement?

_connections[connection_id].last_error = last_error


class MCPToolWrapper(AgentTool):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused by the pattern here, we have MCPToolWrapper but we also have mcp_client. And both can be loaded by an Agent class as MCPToolWrapper implements AgentTool and mcp_client is annotated with @tool

When should I use one and not the other? Should there just be one?

Edit: it looks like this may be a naming related confusion where this is an MCPTool that was generated by the MCPClient

Still, curious why we opted for the class based approach here but the annotation one below.

try:
# Use the stored client
result = config.mcp_client.call_tool_sync(
tool_use_id=f"mcp_{connection_id}_{tool_name}_{uuid.uuid4().hex[:8]}", name=tool_name, arguments=tool_args
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a length limitation here, my fear is that our uniqueness strategy of using uuid is no impacted by using the [:8] this may impact customer traceability

agent: Optional[Any] = None, # Agent instance passed by SDK
) -> Dict[str, Any]:
"""
MCP client tool for connecting to any MCP server with simplified configuration.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above I think we need some serious editing here and callouts of the risks involved with using this

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add an integration test as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants