-
Notifications
You must be signed in to change notification settings - Fork 111
feat: AgentCore tool search plugin for Strands Agents #494
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
Merged
jariy17
merged 8 commits into
aws:main
from
senthilkumarmohan:feat/agentcore-tool-search
May 29, 2026
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
17ae352
Adding AgentCoreToolSearch plugin
senthilkumarmohan 78c2e31
Merge branch 'aws:main' into main
senthilkumarmohan aceabba
refactor: address review feedback for AgentCoreToolSearchPlugin
senthilkumarmohan 41aa552
Merge branch 'aws:main' into feat/agentcore-tool-search
senthilkumarmohan 0774c7d
style: fix linting and formatting for AgentCoreToolSearchPlugin
senthilkumarmohan 8ce79b1
fix: add mcp-proxy-for-aws to dev dependencies for integration tests
senthilkumarmohan f816789
test: auto-provision IAM role and Lambda for integration tests
senthilkumarmohan 6d51568
ci: add mcp-proxy-for-aws as extra dep for gateway integration tests
senthilkumarmohan 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 @@ | ||
| """Gateway integrations.""" |
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 @@ | ||
| """Strands Agents integrations for AgentCore Gateway.""" |
5 changes: 5 additions & 0 deletions
5
src/bedrock_agentcore/gateway/integrations/strands/plugins/__init__.py
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,5 @@ | ||
| """Gateway Strands plugins.""" | ||
|
|
||
| from .agentcore_tool_search import AgentCoreToolSearchPlugin | ||
|
|
||
| __all__ = ["AgentCoreToolSearchPlugin"] | ||
138 changes: 138 additions & 0 deletions
138
..._agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/README.md
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,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]' | ||
| ``` | ||
|
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 | ||
|
|
||
|  | ||
|
|
||
| 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). | ||
6 changes: 6 additions & 0 deletions
6
src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/__init__.py
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,6 @@ | ||
| """AgentCore Tool Search plugin for Strands Agents.""" | ||
|
|
||
| from .intent_providers import IntentProvider, StrandsIntentProvider | ||
| from .plugin import AgentCoreToolSearchPlugin | ||
|
|
||
| __all__ = ["AgentCoreToolSearchPlugin", "IntentProvider", "StrandsIntentProvider"] |
Binary file added
BIN
+140 KB
...s/strands/plugins/agentcore_tool_search/images/agentcore_tool_search_plugin.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions
6
...e/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/__init__.py
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,6 @@ | ||
| """Intent provider interfaces and implementations.""" | ||
|
|
||
| from .intent_provider import IntentProvider | ||
| from .strands_intent_provider import StrandsIntentProvider | ||
|
|
||
| __all__ = ["StrandsIntentProvider", "IntentProvider"] |
26 changes: 26 additions & 0 deletions
26
...ay/integrations/strands/plugins/agentcore_tool_search/intent_providers/intent_provider.py
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,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. | ||
| """ | ||
| ... |
73 changes: 73 additions & 0 deletions
73
...rations/strands/plugins/agentcore_tool_search/intent_providers/strands_intent_provider.py
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,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." | ||
| ) | ||
|
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() | ||
|
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) | ||
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.
Uh oh!
There was an error while loading. Please reload this page.