Back to Articles

Strands Agents: Building AI Agents with Decorators and Model Context Protocol

[ View on GitHub ]

Strands Agents: Building AI Agents with Decorators and Model Context Protocol

Hook

While most AI agent frameworks require hundreds of lines of configuration code, Strands Agents can create a fully functional agent with tool calling in under 20 lines—and that includes the tool definitions.

Context

The explosion of LLM function calling capabilities created a developer problem: every team was writing the same orchestration code. You'd define tools, write JSON schemas for them, build a loop to handle the LLM's tool call requests, execute the functions, serialize results, and feed them back. Multiply this by supporting multiple LLM providers (OpenAI's format differs from Anthropic's, which differs from Bedrock's), and you'd spend more time on plumbing than building actual agent logic.

Strands Agents emerged to solve this boilerplate problem with a model-driven approach. Instead of manually wiring together agent loops and tool schemas, you decorate Python functions and let the SDK handle provider differences, schema generation, and execution flow. The timing proved fortuitous—Anthropic's introduction of the Model Context Protocol (MCP) in late 2024 created a standard for tool discovery and execution. Strands Agents integrated MCP natively, giving developers immediate access to thousands of pre-built tools from the MCP ecosystem. This combination of minimal boilerplate and ecosystem leverage is the SDK's core value proposition.

Technical Insight

The architecture centers on a decorator pattern that transforms ordinary Python functions into LLM-callable tools. The SDK introspects your function signatures and docstrings to automatically generate the JSON schemas that LLMs need for function calling. Here's the simplest possible agent:

from strands import Agent

@Agent.tool
def get_weather(location: str) -> str:
    """Get current weather for a location.
    
    Args:
        location: City name or zip code
    """
    # Your implementation here
    return f"Sunny in {location}, 72°F"

agent = Agent(
    name="weather-assistant",
    instructions="Help users check weather conditions"
)

result = agent.run("What's the weather in Seattle?")

That's it. No manual schema definition, no tool registration ceremony, no provider-specific formatting. The SDK parses the docstring and type hints to build the tool schema, handles the LLM's tool call request, executes get_weather, and returns the result to the model. The agent loop continues until the LLM decides it has enough information to answer.

The provider abstraction is where Strands Agents really shines. The SDK supports 13+ LLM providers through a unified interface—Bedrock, OpenAI, Anthropic, Azure OpenAI, Google Vertex AI, Ollama, and more. Switching providers requires changing a single parameter:

# Using OpenAI
agent = Agent(
    name="assistant",
    model="gpt-4o",
    provider="openai"
)

# Switch to local Ollama
agent = Agent(
    name="assistant",
    model="llama3.2",
    provider="ollama"
)

# Or Anthropic Claude
agent = Agent(
    name="assistant",
    model="claude-3-5-sonnet-20241022",
    provider="anthropic"
)

Under the hood, Strands Agents leverages LiteLLM for provider normalization, which handles the differences in API formats, authentication, and tool calling conventions. This means you write tools once and run them against any supported model.

The Model Context Protocol integration elevates the SDK beyond basic function calling. MCP defines a standard for tools (called "resources" and "tools" in MCP terminology) to be discovered and invoked across different systems. Strands Agents can connect to MCP servers and automatically import their tools:

from strands import Agent, MCPClient

# Connect to an MCP server (e.g., filesystem, GitHub, database)
mcp_client = MCPClient("path/to/mcp/server")

agent = Agent(
    name="data-analyst",
    instructions="Analyze data from multiple sources",
    mcp_clients=[mcp_client]
)

# Agent now has access to all tools from the MCP server
result = agent.run("Read the sales.csv file and summarize trends")

This matters because the MCP ecosystem already includes servers for common integrations: filesystems, databases, Slack, GitHub, Google Drive, and dozens more. Instead of writing custom integration code, you configure MCP servers and let the agent discover available operations.

For development workflows, Strands Agents supports hot-reloading tools from directories. Drop Python files with decorated functions into a watched folder, and the agent automatically picks them up without restarting:

agent = Agent(
    name="assistant",
    tool_dirs=["./tools"],  # Watch this directory
    hot_reload=True
)

This is particularly useful for teams where domain experts write tools independently. A financial analyst can add a portfolio calculation function, a data scientist can add a model inference tool, and the agent discovers both automatically.

The SDK also handles streaming responses with a clean async interface. This matters for user experience—users see the agent's reasoning and tool calls in real time rather than waiting for a final response:

import asyncio
from strands import Agent

agent = Agent(name="assistant")

async def stream_response():
    async for chunk in agent.stream("Calculate the compound interest..."):
        if chunk.type == "tool_call":
            print(f"Calling tool: {chunk.tool_name}")
        elif chunk.type == "content":
            print(chunk.text, end="", flush=True)

asyncio.run(stream_response())

The experimental bidirectional streaming feature takes this further, enabling real-time conversational experiences where the agent can interrupt itself or respond to mid-generation user input. This is bleeding-edge territory—OpenAI's Realtime API and similar technologies are still being standardized—but Strands Agents is positioning for this future.

One architectural decision worth noting: tools execute in the same process as the agent. This simplifies deployment (no microservices or RPC layer needed) but means long-running or blocking tools will pause the agent. For I/O-bound operations, this is fine—most tools are API calls or database queries. For CPU-intensive work, you'll want to wrap tools in async functions or push work to background queues.

Gotcha

The AWS-first defaults create immediate friction if you're not in the AWS ecosystem. The quickstart assumes you have AWS credentials configured and want to use Bedrock in us-west-2. For developers on OpenAI, Anthropic, or local models, this means digging through documentation to find the provider configuration syntax. It's not hard—just a provider parameter—but it's a papercut that will frustrate the first 10 minutes of evaluation.

The experimental bidirectional streaming feature is genuinely experimental. The API surface is marked unstable, and the underlying technologies (like OpenAI's Realtime API) are themselves evolving. If you build on this feature now, expect breaking changes. For production systems, stick with standard streaming until this stabilizes.

More fundamentally, Strands Agents optimizes for simplicity over control. The agent loop is opaque—you define tools and instructions, and the SDK handles the rest. This is perfect for straightforward tool-calling agents but limiting for complex orchestration. If you need multi-step reasoning with intermediate validation, conditional tool execution based on business rules, or custom prompt engineering for each loop iteration, you'll fight the abstraction. LangChain's more verbose API gives you knobs to turn; Strands Agents assumes the default loop works for your use case. The multi-agent coordination features help, but you're still working within the SDK's mental model.

Finally, the relatively early adoption (5,820 stars at time of writing) means fewer battle-tested edge cases. The core loop is solid, but you'll encounter undocumented quirks—how does hot-reloading behave when a tool signature changes mid-conversation? What happens when an MCP server goes offline during a tool call? These scenarios have answers, but you'll be discovering them rather than finding Stack Overflow threads.

Verdict

Use Strands Agents if you're building straightforward tool-calling agents and want to minimize boilerplate, especially if you need multi-provider support or want to leverage the MCP ecosystem for integrations. The decorator pattern and hot-reloading make it excellent for teams where non-engineers contribute tools, and the provider abstraction is invaluable if you're comparing models or want portability. It's ideal for internal tools, productivity agents, and prototypes where development speed matters more than fine-grained control. Skip it if you're building production systems that require deep customization of the agent loop—complex multi-step reasoning, sophisticated error handling, or business logic embedded in orchestration. Also skip if you need the stability of a more mature framework or if you're committed to non-AWS infrastructure and don't want to fight AWS-first assumptions. For those cases, LangChain's complexity buys you control, or consider building directly on provider SDKs if your architecture is simple enough.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/ai-agents/strands-agents-sdk-python.svg)](https://starlog.is/api/badge-click/ai-agents/strands-agents-sdk-python)