Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
5 changes: 4 additions & 1 deletion python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,13 @@ include = [
"strands_agents_sops/__init__.py",
"strands_agents_sops/__main__.py",
"strands_agents_sops/cursor.py",
"strands_agents_sops/mcp.py",
"strands_agents_sops/skills.py",
"strands_agents_sops/rules.py",
"strands_agents_sops/utils.py",
"strands_agents_sops/mcp/__init__.py",
"strands_agents_sops/mcp/server.py",
"strands_agents_sops/mcp/tools.py",
"strands_agents_sops/mcp/prompts.py",
Copy link
Copy Markdown
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

"strands_agents_sops/sops/*.md",
"strands_agents_sops/rules/*.md"
]
Expand Down
5 changes: 3 additions & 2 deletions python/strands_agents_sops/__main__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import argparse

from .cursor import generate_cursor_commands
from .mcp import run_mcp_server
from .mcp.server import AgentSOPMCPServer
from .rules import output_rules
from .skills import generate_anthropic_skills

Expand Down Expand Up @@ -85,7 +85,8 @@ def main():
else:
# Default to MCP server
sop_paths = getattr(args, "sop_paths", None)
run_mcp_server(sop_paths=sop_paths)
mcp_server = AgentSOPMCPServer(sop_paths=sop_paths)
mcp_server.run()


if __name__ == "__main__":
Expand Down
67 changes: 0 additions & 67 deletions python/strands_agents_sops/mcp.py

This file was deleted.

5 changes: 5 additions & 0 deletions python/strands_agents_sops/mcp/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""MCP server implementation for Agent SOPs."""

from .server import AgentSOPMCPServer

__all__ = ["AgentSOPMCPServer"]
48 changes: 48 additions & 0 deletions python/strands_agents_sops/mcp/prompts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""MCP prompts for SOP execution."""

import logging

from mcp.server.fastmcp import FastMCP

logger = logging.getLogger(__name__)


def register_sop_prompts(mcp: FastMCP, sops: list[dict]) -> None:
"""Register SOP prompts with MCP server.

Args:
mcp: FastMCP server instance
sops: List of SOP dictionaries with name, description, and content
"""
for sop in sops:
try:
handler = create_prompt_handler(sop["name"], sop["content"])
mcp.prompt(name=sop["name"], description=sop["description"])(handler)
except Exception as e:
logger.error(f"Error registering prompt for SOP '{sop['name']}': {e}")
continue


def create_prompt_handler(sop_name: str, sop_content: str):
"""Create a prompt handler for a specific SOP.

Args:
sop_name: Name of the SOP
sop_content: Content of the SOP

Returns:
Prompt handler function
"""

def get_prompt(user_input: str = "") -> str:
return f"""Run this SOP:
<agent-sop name="{sop_name}">
<content>
{sop_content}
</content>
<user-input>
{user_input}
</user-input>
</agent-sop>"""

return get_prompt
35 changes: 35 additions & 0 deletions python/strands_agents_sops/mcp/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""MCP server orchestrator for Agent SOPs."""

import logging

from mcp.server.fastmcp import FastMCP

logger = logging.getLogger(__name__)


class AgentSOPMCPServer:
"""MCP server for serving Agent SOPs as prompts and tools."""

def __init__(self, sop_paths: str | None = None):
"""Initialize the MCP server.

Args:
sop_paths: Optional colon-separated string of external SOP directory paths
"""
self.sop_paths = sop_paths
self.mcp = FastMCP("agent-sop-prompt-server")

def setup(self) -> None:
Copy link
Copy Markdown
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?

"""Setup the MCP server by loading SOPs and registering prompts/tools."""
from ..utils import get_all_sops
from .prompts import register_sop_prompts
from .tools import register_sop_tools

sops = get_all_sops(self.sop_paths)
register_sop_prompts(self.mcp, sops)
register_sop_tools(self.mcp, sops)

def run(self) -> None:
"""Start the MCP server."""
self.setup()
self.mcp.run()
55 changes: 55 additions & 0 deletions python/strands_agents_sops/mcp/tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""MCP tools for SOP management."""

from mcp.server.fastmcp import FastMCP

from ..utils import create_sop_metadata


def register_sop_tools(mcp: FastMCP, sops: list[dict]) -> None:
"""Register SOP management tools with MCP server.

Args:
mcp: FastMCP server instance
sops: List of SOP dictionaries with name, description, and content
"""

@mcp.tool()
def list_agent_sops() -> list[dict]:
"""List all available agent SOPs with metadata.

Returns:
List of SOP metadata dictionaries containing name, description, and parameters
"""
result = []
for sop in sops:
metadata = create_sop_metadata(name=sop["name"], content=sop["content"])
result.append(
{
"name": metadata["name"],
"description": metadata["description"],
"parameters": metadata["parameters"],
}
)
return sorted(result, key=lambda x: x["name"])

@mcp.tool()
def get_agent_sop(sop_name: str) -> dict:
"""Get the full content and metadata of a specific SOP.

Args:
sop_name: Name of the SOP to retrieve

Returns:
Complete SOP metadata including content, parameters, examples, troubleshooting

Raises:
ValueError: If SOP name is not found
"""
for sop in sops:
if sop["name"] == sop_name:
return create_sop_metadata(name=sop["name"], content=sop["content"])

available_sops = [sop["name"] for sop in sops]
raise ValueError(
f"SOP '{sop_name}' not found. Available SOPs: {available_sops}"
)
Loading