|
| 1 | +# Workflow MCP Server Example |
| 2 | + |
| 3 | +This example demonstrates three approaches to creating agents and workflows: |
| 4 | + |
| 5 | +1. Traditional workflow-based approach with manual agent creation |
| 6 | +2. Programmatic agent configuration using AgentConfig |
| 7 | +3. Declarative agent configuration using FastMCPApp decorators |
| 8 | + |
| 9 | +All three approaches can use `app_server.py` to expose the agents and workflows as an MCP server. |
| 10 | + |
| 11 | +## Concepts Demonstrated |
| 12 | + |
| 13 | +- Using the `Workflow` base class to create custom workflows |
| 14 | +- Registering workflows with an `MCPApp` |
| 15 | +- Creating and registering agent configurations with both programmatic and declarative approaches |
| 16 | +- Exposing workflows and agents as MCP tools using `app_server.py` |
| 17 | +- Connecting to a workflow server using `gen_client` |
| 18 | +- Lazy instantiation of agents from configurations when their tools are called |
| 19 | + |
| 20 | +## Components in this Example |
| 21 | + |
| 22 | +1. **DataProcessorWorkflow**: A traditional workflow that processes data in three steps: |
| 23 | + |
| 24 | + - Finding and retrieving content from a source (file or URL) |
| 25 | + - Analyzing the content |
| 26 | + - Formatting the results |
| 27 | + |
| 28 | +2. **SummarizationWorkflow**: A traditional workflow that summarizes text content: |
| 29 | + |
| 30 | + - Generates a concise summary |
| 31 | + - Extracts key points |
| 32 | + - Returns structured data |
| 33 | + |
| 34 | +3. **Research Team**: A parallel workflow created using the agent configuration system: |
| 35 | + |
| 36 | + - Uses a fan-in/fan-out pattern with multiple specialized agents |
| 37 | + - Demonstrates declarative workflow pattern configuration |
| 38 | + |
| 39 | +4. **Specialist Router**: A router workflow created using FastMCPApp decorators: |
| 40 | + - Routes requests to specialized agents based on content |
| 41 | + - Shows how to use the decorator syntax for workflow creation |
| 42 | + |
| 43 | +## How to Run |
| 44 | + |
| 45 | +1. Copy the example secrets file: |
| 46 | + |
| 47 | + ``` |
| 48 | + cp mcp_agent.secrets.yaml.example mcp_agent.secrets.yaml |
| 49 | + ``` |
| 50 | + |
| 51 | +2. Edit `mcp_agent.secrets.yaml` to add your API keys. |
| 52 | + |
| 53 | +3. Run the client, which will automatically start the server: |
| 54 | + ``` |
| 55 | + uv run client.py |
| 56 | + ``` |
| 57 | + |
| 58 | +## Code Structure |
| 59 | + |
| 60 | +- `basic_agent_server.py`: Defines the BasicAgentWorkflow and creates an MCP server |
| 61 | +- `client.py`: Connects to the server and runs the workflow |
| 62 | +- `mcp_agent.config.yaml`: Configuration for MCP servers |
| 63 | +- `mcp_agent.secrets.yaml`: Secret API keys (not included in repository) |
| 64 | + |
| 65 | +## Understanding the Code |
| 66 | + |
| 67 | +### Approach 1: Traditional Workflow Definition |
| 68 | + |
| 69 | +Workflows are defined by subclassing the `Workflow` base class and implementing: |
| 70 | + |
| 71 | +- The `run` method containing the main workflow logic |
| 72 | +- Optional:`initialize` and `cleanup` methods for setup and teardown |
| 73 | +- Optional: a custom `create` class method for specialized instantiation |
| 74 | + |
| 75 | +Workflows are registered with the MCPApp using the `@app.workflow` decorator: |
| 76 | + |
| 77 | +Example: |
| 78 | + |
| 79 | +```python |
| 80 | +app = MCPApp(name="workflow_mcp_server") |
| 81 | + |
| 82 | +@app.workflow |
| 83 | +class DataProcessorWorkflow(Workflow[str]): |
| 84 | + @classmethod |
| 85 | + async def create(cls, executor: Executor, name: str | None = None, **kwargs: Any) -> "DataProcessorWorkflow": |
| 86 | + # Custom instantiation logic |
| 87 | + workflow = cls(executor=executor, name=name, **kwargs) |
| 88 | + await workflow.initialize() |
| 89 | + return workflow |
| 90 | + |
| 91 | + async def initialize(self): |
| 92 | + # Set up resources like agents and LLMs |
| 93 | + |
| 94 | + async def run(self, source: str, analysis_prompt: Optional[str] = None, output_format: Optional[str] = None) -> WorkflowResult[str]: |
| 95 | + # Workflow implementation... |
| 96 | + |
| 97 | + async def cleanup(self): |
| 98 | + # Clean up resources |
| 99 | +``` |
| 100 | + |
| 101 | +### Approach 2: Programmatic Agent Configuration |
| 102 | + |
| 103 | +Agent configurations can be created programmatically using Pydantic models: |
| 104 | + |
| 105 | +```python |
| 106 | +# Create a basic agent configuration |
| 107 | +research_agent_config = AgentConfig( |
| 108 | + name="researcher", |
| 109 | + instruction="You are a helpful research assistant that finds information and presents it clearly.", |
| 110 | + server_names=["fetch", "filesystem"], |
| 111 | + llm_config=AugmentedLLMConfig( |
| 112 | + factory=OpenAIAugmentedLLM, |
| 113 | + ) |
| 114 | +) |
| 115 | + |
| 116 | +# Create a parallel workflow configuration |
| 117 | +research_team_config = AgentConfig( |
| 118 | + name="research_team", |
| 119 | + instruction="You are a research team that produces high-quality, accurate content.", |
| 120 | + parallel_config=ParallelWorkflowConfig( |
| 121 | + fan_in_agent="editor", |
| 122 | + fan_out_agents=["summarizer", "fact_checker"], |
| 123 | + ) |
| 124 | +) |
| 125 | + |
| 126 | +# Register the configurations with the app |
| 127 | +app.register_agent_config(research_agent_config) |
| 128 | +app.register_agent_config(research_team_config) |
| 129 | +``` |
| 130 | + |
| 131 | +### Approach 3: Declarative Agent Configuration with FastMCPApp |
| 132 | + |
| 133 | +FastMCPApp provides decorators for creating agent configurations in a more declarative style: |
| 134 | + |
| 135 | +```python |
| 136 | +fast_app = FastMCPApp(name="fast_workflow_mcp_server") |
| 137 | + |
| 138 | +# Basic agent with OpenAI LLM |
| 139 | +@fast_app.agent("assistant", "You are a helpful assistant that answers questions concisely.", |
| 140 | + server_names=["calculator"]) |
| 141 | +def assistant_config(config): |
| 142 | + config.llm_config = AugmentedLLMConfig( |
| 143 | + factory=OpenAIAugmentedLLM, |
| 144 | + ) |
| 145 | + return config |
| 146 | + |
| 147 | +# Router workflow with specialist agents |
| 148 | +@fast_app.router("specialist_router", "You route requests to the appropriate specialist.", |
| 149 | + agent_names=["mathematician", "programmer", "writer"]) |
| 150 | +def router_config(config): |
| 151 | + config.llm_config = AugmentedLLMConfig( |
| 152 | + factory=OpenAIAugmentedLLM |
| 153 | + ) |
| 154 | + config.router_config.top_k = 1 |
| 155 | + return config |
| 156 | +``` |
| 157 | + |
| 158 | +### Exposing Workflows and Agents as Tools |
| 159 | + |
| 160 | +The MCP server automatically exposes both workflows and agent configurations as tools: |
| 161 | + |
| 162 | +**Workflow tools**: |
| 163 | + |
| 164 | +- Running a workflow: `workflows/{workflow_id}/run` |
| 165 | +- Checking status: `workflows/{workflow_id}/get_status` |
| 166 | +- Controlling workflow execution: `workflows/resume`, `workflows/cancel` |
| 167 | + |
| 168 | +**Agent tools**: |
| 169 | + |
| 170 | +- Running an agent: `agents/{agent_name}/generate` |
| 171 | +- Getting string response: `agents/{agent_name}/generate_str` |
| 172 | +- Getting structured response: `agents/{agent_name}/generate_structured` |
| 173 | + |
| 174 | +Agent configurations are lazily instantiated when their tools are called. If the agent is already active, the existing instance is reused. |
| 175 | + |
| 176 | +### Connecting to the Workflow Server |
| 177 | + |
| 178 | +The client connects to the workflow server using the `gen_client` function: |
| 179 | + |
| 180 | +```python |
| 181 | +async with gen_client("workflow_server", context.server_registry) as server: |
| 182 | + # Connect and use the server |
| 183 | +``` |
| 184 | + |
| 185 | +You can then call both workflow and agent tools through this client connection. |
0 commit comments