Skip to content

feat: add MCP tools for SOP discovery and retrieval#29

Open
konippi wants to merge 4 commits intostrands-agents:mainfrom
konippi:add-mcp-tools-for-sop-discovery-and-retrieval
Open

feat: add MCP tools for SOP discovery and retrieval#29
konippi wants to merge 4 commits intostrands-agents:mainfrom
konippi:add-mcp-tools-for-sop-discovery-and-retrieval

Conversation

@konippi
Copy link

@konippi konippi commented Dec 12, 2025

Issue #, if available:
resolve #22

Description of changes:
This PR implements MCP tools that allow model-driven agents to discover and use SOPs autonomously, similar to Claude Skills. Agents can now query available SOPs and retrieve their content without requiring explicit user invocation.

How to verify

  • passed all tests
  • verified that it works as expected by executing npx @modelcontextprotocol/inspector hatch run strands-agents-sops mcp --sop-paths <SOP_PATH>.

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

@konippi konippi requested a review from Unshure January 28, 2026 15:00
return sops


def get_all_sops(sop_paths: str | None = None) -> list[dict[str, Any]]:
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 you name this load_sops, then call each of the load_external_sops and load_builtin_sops?

Suggested change
def get_all_sops(sop_paths: str | None = None) -> list[dict[str, Any]]:
def load_sops(external_sop_paths: str | None = None) -> list[dict[str, Any]]:

return external_sops


def load_builtin_sops() -> list[dict[str, str]]:
Copy link
Member

Choose a reason for hiding this comment

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

Seems like there is a lot of duplicated logic in this functions and the load_external_sops, and the https://github.com/strands-agents/agent-sop/blob/main/python/strands_agents_sops/__init__.py file. Can we better consolidate this logic?

Comment on lines +154 to +164
for sop in external_sops:
if sop["name"] not in seen_names:
seen_names.add(sop["name"])
all_sops.append(sop)

# Load built-in SOPs
builtin_sops = load_builtin_sops()
for sop in builtin_sops:
if sop["name"] not in seen_names:
seen_names.add(sop["name"])
all_sops.append(sop)
Copy link
Member

Choose a reason for hiding this comment

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

nit: Could you consolidate this logic into something like:

sops = load_external_sops(external_directories) + load_builtin_sops()
if sop["name"] not in seen_names:
    seen_names.add(sop["name"])
    all_sops.append(sop)

Comment on lines +93 to +94
logger.error(f"Error registering prompt for SOP '{sop['name']}': {e}")
continue
Copy link
Member

Choose a reason for hiding this comment

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

nit: Why continue and not throw here?

return sorted(result, key=lambda x: x["name"])

@self.mcp.tool()
def get_agent_sop(sop_name: str) -> dict:
Copy link
Member

Choose a reason for hiding this comment

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

nit: I think a simple name might be better as a parameter for an llm to generate

Suggested change
def get_agent_sop(sop_name: str) -> dict:
def get_agent_sop(name: str) -> dict:

Comment on lines +64 to +70
for sop in self.sops:
if sop["name"] == sop_name:
return {
"name": sop["name"],
"description": sop["description"],
"content": sop["content"],
}
Copy link
Member

Choose a reason for hiding this comment

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

Should sop just be a dict instead of a list so we can do O(1) lookup?

"description": sop["description"],
}
)
return sorted(result, key=lambda x: x["name"])
Copy link
Member

Choose a reason for hiding this comment

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

nit: Do we need to sort this? Can we remove this sorting?

logger.error(f"Error registering prompt for SOP '{sop['name']}': {e}")
continue

def _create_prompt_handler(self, sop_name: str, sop_content: str):
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 add a return type?

self.mcp = FastMCP("agent-sop-prompt-server")
self.sops: list[dict] = []

def setup(self) -> None:
Copy link
Member

Choose a reason for hiding this comment

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

do we need a separate setup function? Can we just do this as part of init?

Comment on lines +58 to +59
"strands_agents_sops/mcp/tools.py",
"strands_agents_sops/mcp/prompts.py",
Copy link
Member

Choose a reason for hiding this comment

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

These dont exist anymore

@konippi konippi force-pushed the add-mcp-tools-for-sop-discovery-and-retrieval branch from ec1bba7 to 32b6934 Compare February 17, 2026 14:07
@konippi konippi requested a review from Unshure February 17, 2026 14:07
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.

Allow Model-Driven Agents to reference SOPs as needed through MCP without user invocation

3 participants