Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/integration-testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ jobs:
- group: gateway
path: tests_integ/gateway
timeout: 15
extra-deps: ""
extra-deps: "mcp-proxy-for-aws"
ignore: ""
- group: identity
path: tests_integ/identity/test_identity_client.py
Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,15 @@ dev = [
"strands-agents-evals>=0.1.0",
"a2a-sdk[http-server]>=0.3",
"ag-ui-protocol>=0.1.10",
"mcp-proxy-for-aws>=0.1.0",
]

[project.optional-dependencies]
a2a = ["a2a-sdk[http-server]>=0.3"]
ag-ui = ["ag-ui-protocol>=0.1.10"]
strands-agents = [
"strands-agents>=1.20.0"
"strands-agents>=1.20.0",
"mcp>=1.23.0,<2.0.0",
]
strands-agents-evals = [
"strands-agents-evals>=0.1.0"
Expand Down
1 change: 1 addition & 0 deletions src/bedrock_agentcore/gateway/integrations/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Gateway integrations."""
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Strands Agents integrations for AgentCore Gateway."""
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Gateway Strands plugins."""

Comment thread
senthilkumarmohan marked this conversation as resolved.
from .agentcore_tool_search import AgentCoreToolSearchPlugin

__all__ = ["AgentCoreToolSearchPlugin"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Strands AgentCore Tool Search Plugin

A semantic tool discovery plugin for [Strands Agents](https://github.com/strands-agents/sdk-python) that uses the [Amazon Bedrock AgentCore Gateway](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-using-mcp-semantic-search.html) `x_amz_bedrock_agentcore_search` tool. This enables agents to dynamically load only the relevant tools for each invocation by deriving user intent from conversation history, even when hundreds of tools are registered on the gateway.

## Features

- **Semantic tool discovery** — uses AgentCore Gateway's built-in search to find relevant tools
- **Intent-based loading** — derives user intent via LLM before searching
- **No list_tools call** — tools are built directly from search results
- **Pluggable intent provider** — swap the default intent provider with your own
- **Agent model reuse** — by default, the intent classifier uses the same model as the parent agent

## Installation

```bash
pip install 'bedrock-agentcore[strands-agents]'
```
Comment thread
senthilkumarmohan marked this conversation as resolved.

## Usage

```python
from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client
from strands import Agent
from strands.tools.mcp import MCPClient
from bedrock_agentcore.gateway.integrations.strands.plugins import AgentCoreToolSearchPlugin

mcp_client = MCPClient(lambda: aws_iam_streamablehttp_client(
endpoint="https://<gateway-id>.gateway.bedrock-agentcore.<region>.amazonaws.com/mcp",
aws_region="us-east-1",
aws_service="bedrock-agentcore",
))

mcp_client.start()

agent = Agent(plugins=[AgentCoreToolSearchPlugin(mcp_client=mcp_client)])

agent("Find me afternoon flights to New York")
```

Or using a context manager:

```python
with mcp_client:
agent = Agent(plugins=[AgentCoreToolSearchPlugin(mcp_client=mcp_client)])
agent("Find me afternoon flights to New York")
```

## How It Works

![Tool Search Flow](images/agentcore_tool_search_plugin.png)

On each agent invocation:

1. **User query** — The user sends a query to Strands agent.
2. **Hook** — The agent triggers the `AgentCoreToolSearchPlugin` before model invocation
3. **Derive intent** — The `IntentProvider` sends the last N messages from conversation history to the configured LLM to produce a concise intent string
4. **Search gateway** — The intent is passed to AgentCore Gateway's `x_amz_bedrock_agentcore_search` tool to obtain most relevant tools.
5. **Invoke LLM** — The agent invokes the LLM with the user query along with the matched tools from registered MCP targets (Lambda, API Gateway, MCP Server)

Previously loaded tools are cleared before each search, so the agent always has the most relevant tools available.

## Intent Provider

An `IntentProvider` is responsible for analyzing conversation messages and producing a concise intent string that drives tool search. The plugin calls `derive_intent(messages, model)` before each invocation to determine what tools to load.

### StrandsIntentProvider

`StrandsIntentProvider` uses a Strands Agent to classify the last few conversation messages into a concise intent string. By default it uses the parent agent's model.

**Basic usage (uses the agent's model automatically):**

```python
from bedrock_agentcore.gateway.integrations.strands.plugins import AgentCoreToolSearchPlugin

agent = Agent(plugins=[
AgentCoreToolSearchPlugin(mcp_client=mcp_client)
])
```

**With a custom model for intent classification:**

```python
from strands.models.bedrock import BedrockModel
from bedrock_agentcore.gateway.integrations.strands.plugins import AgentCoreToolSearchPlugin
from bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers import StrandsIntentProvider

intent_model = BedrockModel(model_id="us.anthropic.claude-haiku-4-5-20251001-v1:0")
agent = Agent(plugins=[
AgentCoreToolSearchPlugin(
mcp_client=mcp_client,
intent_provider=StrandsIntentProvider(model=intent_model),
)
])
```

**With a custom system prompt:**

```python
from bedrock_agentcore.gateway.integrations.strands.plugins import AgentCoreToolSearchPlugin
from bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers import StrandsIntentProvider

agent = Agent(plugins=[
AgentCoreToolSearchPlugin(
mcp_client=mcp_client,
intent_provider=StrandsIntentProvider(
system_prompt="Classify the user's intent in one sentence. Focus on the action, not details."
),
)
])
```

### Custom Intent Provider

You can provide your own intent derivation strategy by subclassing `IntentProvider`:

```python
from bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers import IntentProvider

class MyIntentProvider(IntentProvider):
def derive_intent(self, messages: list[dict], model=None) -> str:
# custom logic to derive intent
return "intent string"

agent = Agent(plugins=[
AgentCoreToolSearchPlugin(
mcp_client=mcp_client,
intent_provider=MyIntentProvider(),
)
])
```

## Prerequisites

- An AgentCore Gateway with **[semantic search](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-using-mcp-semantic-search.html) enabled**
- Tools registered on the gateway with descriptions
- AWS credentials with access to the gateway

For more details, see the [AgentCore Gateway Documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-building.html).
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""AgentCore Tool Search plugin for Strands Agents."""

from .intent_providers import IntentProvider, StrandsIntentProvider
from .plugin import AgentCoreToolSearchPlugin

__all__ = ["AgentCoreToolSearchPlugin", "IntentProvider", "StrandsIntentProvider"]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Intent provider interfaces and implementations."""

from .intent_provider import IntentProvider
from .strands_intent_provider import StrandsIntentProvider

__all__ = ["StrandsIntentProvider", "IntentProvider"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Intent provider abstract interface."""

from abc import ABC, abstractmethod


class IntentProvider(ABC):
"""Abstract interface for deriving user intent from conversation messages.

Subclasses must implement the `derive_intent` method to analyze conversation
messages and return a concise intent string.
"""

@abstractmethod
def derive_intent(self, messages: list[dict], model=None) -> str:
"""Analyze conversation messages and return a concise intent string.

Args:
messages: List of conversation message dicts in Strands format.
model: Optional model instance from the parent agent. Implementations
can use this for LLM-based intent derivation.

Returns:
A plain text string describing the user's intent.
Returns empty string if intent cannot be determined.
"""
...
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Strands Agent-based intent provider implementation."""

import logging

from strands import Agent

from .intent_provider import IntentProvider

logger = logging.getLogger(__name__)

INTENT_SYSTEM_PROMPT = (
"You are an intent classifier. Given the recent conversation messages, "
"produce a concise one-sentence description of what the user is trying to accomplish. "
"Focus on the type of task, not the specific details. "
"Reply with ONLY the intent description, nothing else."
)
Comment thread
senthilkumarmohan marked this conversation as resolved.


class StrandsIntentProvider(IntentProvider):
"""LLM-based intent provider that uses a Strands Agent to classify the last N messages."""

def __init__(self, message_window: int = 5, model=None, system_prompt: str = INTENT_SYSTEM_PROMPT):
"""Initialize StrandsIntentProvider.

Args:
message_window: Number of recent messages to consider.
model: Optional explicit model for intent classification.
system_prompt: System prompt for the intent classifier. Defaults to INTENT_SYSTEM_PROMPT.
"""
self._message_window = message_window
self._explicit_model = model
self._system_prompt = system_prompt

def derive_intent(self, messages: list[dict], model=None) -> str:
"""Derive intent using an LLM. Falls back to agent's model if no explicit model set."""
try:
recent_messages = messages[-self._message_window :] if messages else []
if not recent_messages:
return ""

kwargs = {"system_prompt": self._system_prompt, "tools": []}
# Priority: explicit model > agent's model > Strands default
resolved_model = self._explicit_model or model
if resolved_model:
kwargs["model"] = resolved_model

intent_agent = Agent(**kwargs)
response = intent_agent(self._format_messages_for_prompt(recent_messages))
return str(response).strip()
Comment thread
senthilkumarmohan marked this conversation as resolved.
except Exception as e:
logger.error("Failed to derive intent: %s", e)
return ""

def _format_messages_for_prompt(self, messages: list[dict]) -> str:
"""Format user messages into a text prompt for the intent LLM.

Only includes user-role messages to avoid leaking PII or sensitive data
from tool results or assistant responses.
"""
parts = []
for msg in messages:
role = msg.get("role", "")
if role != "user":
continue
content = msg.get("content", [])
text = ""
if isinstance(content, list):
text = " ".join(
block.get("text", "") for block in content if isinstance(block, dict) and "text" in block
)
if text.strip():
parts.append(text.strip())
return "\n".join(parts)
Loading
Loading