Agentic AI Engineering

Lesson 25: Orchestrate and Integrate Our Capstone Agents

Throughout this course, we have built two specialized agents: Nova for research and Brown for writing. We have run them separately, but they were always designed to work in sequence. Nova gathers the information, and Brown uses that information to write an article. Now it is time to integrate them into a unified system.

In this lesson, we will explore how to orchestrate both agents so that a single LLM can use their tools together. The beauty of the Model Context Protocol (MCP) lies in its straightforward integration. We will explore two approaches: a multi-server MCP client that connects to multiple independent servers, and a composed MCP server that unifies multiple agents into a single endpoint. By the end of this lesson, you will understand when to use each approach.

From Separate Agents to Unified System

Before we dive into the implementation, let's understand the orchestration model we are using. We are implementing a Central LLM Orchestration pattern. In this approach, a single, central LLM, like the one that may be in your IDE, has access to tools from multiple specialized agents. When you assign a task, the LLM dynamically determines which agent's tools to use, maintaining a single conversation context and orchestrating the workflow by selecting the appropriate tools as needed [6], [7].

This pattern is different from others. A Supervisor Agent would have one agent explicitly delegate sub-tasks to worker agents [9]. In a Peer-to-Peer or Network Architecture, agents communicate dynamically without a central controller, offering flexibility but increasing the complexity of coordination [9]. Our central LLM orchestration is simpler: the LLM acts as an intelligent tool selector rather than an explicit coordinator [10].

This pattern works well with MCP. Both Nova and Brown expose their capabilities as MCP tools with clear descriptions, allowing the central LLM to discover all available tools through a single, standardized protocol [11]. This unified interface eliminates the need for custom integration code. Because both agents speak MCP, we do not need to write adapters or APIs. The client connects to both servers and aggregates their tools.

The Rationale: Why Choose Central LLM Orchestration?

This orchestration pattern offers several advantages that make it ideal for integrating Nova and Brown. The most immediate benefit is simplicity and maintainability. All decision-making happens in one place: the central LLM. This means the workflow is transparent and easy to understand. There is no need for complex state management or inter-agent communication protocols, which reduces the cognitive overhead of debugging the system [12].

The pattern also enables natural task decomposition. The central LLM can break down complex requests on the fly without requiring predefined workflows. More importantly, it can adapt its strategy based on intermediate results. For example, if research reveals unexpected information, the LLM can adjust its approach accordingly by doing further research on that topic. This adaptive behavior is valuable for human-in-the-loop workflows, which are common in IDE environments. You can provide feedback at any point, and the LLM incorporates it naturally [5].

The central LLM maintains a single, unified context window , which means it can reference information from Nova's research when calling Brown's tools without requiring explicit data passing between agents [13]. This avoids the "siloed knowledge" problem, where critical information gets trapped in one agent's context and becomes unavailable to others.

Finally, this pattern is perfect for sequential, interdependent tasks. Our research workflow, followed by writing, is inherently sequential, and the writing task heavily depends on the research results. A central orchestrator can easily manage these dependencies because it sees the entire workflow and can make informed decisions about when to transition between phases [14], [15].

The Trade-offs: Understanding the Limitations

While central LLM orchestration is powerful, it is essential to understand its limitations. The first is tool overload. As the number of available tools increases, LLMs struggle to make reliable selections of tools. They may choose suboptimal tools or miss relevant ones entirely. Research shows that performance can degrade significantly when models must choose from a large, diverse set of tools [1], [2]. Our system, with 14 tools total (11 from Nova and 3 from Brown), is comfortably within the typical limit of 15-20 tools.

Another limitation is the pattern's sequential execution model. The LLM executes tools one at a time. If you needed to research 50 companies simultaneously, this approach would be inefficient. In such scenarios, a Supervisor-Worker pattern with parallel execution would be better [8].

The pattern also reduces opportunities for human-in-the-loop interaction compared to our earlier single-agent workflows. As we delegate more control to the central orchestrator, the system becomes more autonomous. The LLM now decides not just how to use individual tools, but when to transition between research and writing phases. This shift up the autonomy spectrum means humans have fewer natural intervention points.

The pattern also struggles with complex inter-agent dependencies. If agents need to negotiate with each other, engage in debate, or iteratively refine each other's work through back-and-forth exchanges, direct agent-to-agent communication would be more natural. Our pattern handles simple, linear dependencies well, but complex multi-way interactions would become awkward [7].

Despite these limitations, central LLM orchestration is the correct default choice for most agent integration scenarios. It is simple, maintainable, and uses the LLM's natural reasoning without adding unnecessary complexity. You should only consider more elaborate patterns when you hit clear scaling limits or have fundamentally different requirements.

We’ll dive more into multi-agent architectures in later lessons.

Integrating Nova and Brown

Now that we understand the central LLM orchestration pattern and its rationale, let's see how it works in practice. Throughout this course, we've built two specialized agents: Nova for research (which ingests article guidelines, performs web research, scrapes sources, and compiles comprehensive research files) and Brown for writing (which takes research and guidelines to generate, review, and edit articles with human-in-the-loop feedback).

These agents were designed from the start to work in sequence (Nova gathers the research, and Brown uses that research to write the article), but we've been running them separately. The beauty of MCP is that integration is now straightforward. Because both agents already expose their capabilities as MCP tools with clear descriptions, the central LLM can discover and use all available tools through the standardized protocol. We don't need custom integration code, adapters, or APIs. We simply connect to both servers and let the central LLM decide which tools to use and when.

We'll explore two different approaches for achieving this integration, each with distinct deployment patterns:

  1. Multi-Server MCP Client : A single client connects directly to multiple independent MCP servers simultaneously. The client aggregates all tools from both servers and presents them to the LLM.
  2. Composed MCP Server : We create a new server that internally connects to both Nova and Brown, exposing their combined capabilities as a single unified endpoint.

Both approaches implement the same central LLM orchestration pattern, which gives one LLM access to all tools from both agents. However, they differ in how the integration is configured and deployed. Let's examine each one.

Approach 1: Multi-Server MCP Client

MCP Client

reads configuration

connects to

connects to

mcp_servers_config_http.json

MCP Client

User

Nova MCP Server

Brown MCP Server

Image 1: Architecture diagram depicting the Multi-Server MCP Client approach.

To teach this, we prepared a modified version of our MCP client in lessons/agents_integration/mcp_client. This MCP client can be run by specifying the MCP server’s configuration file, and it would automatically connect to all the MCP servers configured.

Let's look at the configuration file for this first approach (from lessons/25_integrate_agents/mcp_servers_config_http.json).

{
  "mcpServers": {
    "nova-research-agent": {
      "url": "http://localhost:8001/mcp"
    },
    "brown-writing-workflow": {
      "url": "http://localhost:8002/mcp"
    }
  }
}

This configuration tells the MCP client how to connect to both servers via HTTP. Each server has a unique name and specifies the HTTP URL where the server is listening.

Now let's see how the MCP client code loads this configuration and connects to both servers (from lessons/agents_integration/mcp_client/src/client.py).

import argparse
import json
from pathlib import Path
from fastmcp import Client
 
# Parse command line arguments for config file
parser = argparse.ArgumentParser(description="Multi-Server MCP Client")
parser.add_argument("--config", "-c", type=str, default=None,
                    help="Path to MCP servers config file")
args = parser.parse_args()
 
# Load configuration from JSON file
config_path = Path(args.config) if args.config else Path("mcp_servers_config.json")
with open(config_path) as f:
    config = json.load(f)
 
server_names = list(config["mcpServers"].keys())
logging.info(f"Found {len(server_names)} MCP servers in configuration: {', '.join(server_names)}")
 
# Create a single client with a multi-server configuration
client = Client(config)
 
# Connect and fetch capabilities from all servers
async with client:
    tools = await client.list_tools()
    resources = await client.list_resources()
    prompts = await client.list_prompts()
    
    logging.info(
        f"Total capabilities: {len(tools)} tools, {len(resources)} resources, {len(prompts)} prompts"
    )
    
    # Main conversation loop
    while True:
        user_input = input("👤 You: ").strip()
        parsed_input = parse_user_input(user_input)
        
        # Handle the user message (commands, prompts, or normal messages)
        should_continue, thinking_enabled = await handle_user_message(
            parsed_input=parsed_input,
            tools=tools,
            resources=resources,
            prompts=prompts,
            conversation_history=conversation_history,
            mcp_client=client,
            thinking_enabled=thinking_enabled,
            server_names=server_names,
        )
        
        if not should_continue:
            break

The key insight here is that Client(config) accepts a multi-server configuration. When you call list_tools(), list_resources(), or list_prompts(), the client automatically aggregates capabilities from all connected servers.

The client supports several interactive commands:

  • /tools, /resources, /prompts - List available capabilities;
  • /prompt/<name>?arg=value - Load and execute an MCP prompt with optional arguments;
  • /resource/<uri> - Read a resource's content;
  • /model-thinking-switch - Toggle the LLM's thinking mode;
  • /quit - Exit the client.

When a prompt is loaded via /prompt/<name>, the client retrieves its content from the MCP server and sends it to the LLM, which then executes the workflow using the available tools.

How Capabilities Are Named

When you have multiple servers, how do you distinguish which tool belongs to which server? FastMCP handles this automatically by prefixing the capability names with the server name from your configuration.

For example, with our configuration having nova-research-agent and brown-writing-workflow as server names:

  • Nova's extract_guidelines_urls tool becomes nova-research-agent_extract_guidelines_urls ;
  • Brown's generate_article tool becomes brown-writing-workflow_generate_article ;
  • Nova's full_research_instructions_prompt prompt becomes nova-research-agent_full_research_instructions_prompt ;
  • Brown's generate_article_prompt prompt becomes brown-writing-workflow_generate_article_prompt .

This naming convention makes it easy to identify which capability comes from which server, and ensures there are no naming collisions if both servers happen to expose capabilities with the same base name.

Running from the Terminal

When you run the multi-server client from your terminal, you will see it connect to both servers and aggregate their tools.

$ cd agents_integration/mcp_client
$ uv run -m src.client --config ../../25_integrate_agents/mcp_servers_config_http.json

It outputs:

INFO:root:Loading MCP server configuration from: mcp_servers_config_http.json
INFO:root:Found 2 MCP servers in configuration: nova-research-agent, brown-writing-workflow
INFO:root:Connecting to MCP servers...
INFO:root:Fetching capabilities from all servers...
INFO:root:Total capabilities: 14 tools, 4 resources, 4 prompts

============================================================
All Servers
============================================================

  - 14 tools available
  - 4 resources available
  - 4 prompts available

Available Commands: /tools, /resources, /prompts, /prompt/<name>?arg=value, /resource/<uri>, /model-thinking-switch, /quit

When you type /prompts, you'll see all prompts from both servers:

👤 You: /prompts
============================================================
Prompts (4)
============================================================

1. nova-research-agent_full_research_instructions_prompt
   Complete Nova research agent workflow instructions.

2. brown-writing-workflow_generate_article_prompt
   Retrieve a prompt that will trigger the article generation workflow using Brown.

3. brown-writing-workflow_edit_article_prompt
   Retrieve a prompt that will trigger the article editing workflow using Brown.

4. brown-writing-workflow_edit_selected_text_prompt
   Retrieve a prompt that will trigger the selected text editing workflow using Brown.

This demonstrates that the client successfully connected to both servers and aggregated their capabilities. Notice that each prompt is prefixed with its server name, making it clear which agent provides each capability.

Running the Complete Workflow: Two Prompts in Sequence

To run both agents together—first Nova for research, then Brown for article generation—you need to execute their prompts in sequence.

Step 1: Run Nova's Research Workflow

First, trigger Nova's research prompt:

👤 You: /prompt/nova-research-agent_full_research_instructions_prompt

The LLM receives the prompt instructions and responds:

💬 LLM Response: Hello! I will help you execute the Nova research agent workflow. Here are the steps involved:

1.  **Setup:** Extract URLs and local file references from your article guidelines.
2.  **Process Resources:** Concurrently process local files, scrape other web URLs, process GitHub URLs, and transcribe YouTube URLs found in the guidelines.
3.  **Research Loop:** Conduct 3 rounds of web research using Perplexity to generate new queries and gather results.
4.  **Filter Results:** Evaluate and select high-quality sources from the Perplexity research.
5.  **Scrape Research Sources:** Identify and scrape the full content of the most valuable research sources.
6.  **Final Research File:** Compile all gathered and processed research into a comprehensive markdown file.

To begin, please provide the path to your research directory. Also, let me know if you need any modifications to this workflow, such as starting from a specific step or adding user feedback.

👤 You:

You then provide the research directory path and let Nova complete the research workflow.

Step 2: Run Brown's Article Generation Workflow

Once Nova finishes, you trigger Brown's article generation prompt with the same directory:

👤 You: /prompt/brown-writing-workflow_generate_article_prompt?dir_path=/path/to/your/research/directory

The LLM receives Brown's prompt and calls the article generation tool:

🔧 Function Call (Tool):
  Tool: brown-writing-workflow_generate_article
  Arguments: {
    "dir_path": "/path/to/your/research/directory"
  }

⚡ Executing tool 'brown-writing-workflow_generate_article' via MCP server...

Brown then generates the article using the research files Nova created.

The Limitation: No Single Unified Command

While this two-step approach works, it has a significant limitation: you must manually run two separate prompts in the correct order. There's no single command that orchestrates the entire research-to-article workflow.

You might think: "Why not just add a combined prompt to one of the servers?" But this creates a design problem:

  1. Adding to Nova? Nova is a research agent. It shouldn't need to know about Brown's article generation internals. This would create tight coupling between agents.
  2. Adding to Brown? Similarly, Brown is a writing workflow. It shouldn't need to know how Nova's research steps work.
  3. Writing a manual prompt? You could craft a long prompt that describes both workflows, but this duplicates the logic already encoded in each agent's prompts. It's error-prone and hard to maintain.

The clean solution is Approach 2 : create a composed MCP server that proxies both Nova and Brown. This composed server can own the combined workflow prompt without polluting either agent's codebase. The orchestration logic lives in the right place—the integration layer—not in the individual agents.

Approach 2: Composed MCP Server

The second approach is to create a new MCP server that combines the Nova and Brown servers. Instead of the client connecting to multiple servers, you create a single composed server that internally proxies requests to the underlying servers. This approach is useful when you want to package multiple agents as a single deployable unit, simplify the client-side configuration, or add a layer of coordination logic between agents.

Composed MCP Server
 
Delegates requests
 
Delegates requests
 
Reads mcp_servers_to_compose_http.json
 
Creates proxies (FastMCP.as_proxy())
 
Mounts proxies (mcp.mount()) without prefix
 
Adds orchestration prompt
 
Nova MCP Server
 
Brown MCP Server

Image 2: Architecture diagram illustrating the Composed MCP Server approach.

First, we define which servers to compose. The composed server will connect to Nova and Brown via HTTP (from lessons/25_integrate_agents/mcp_servers_config_http.json). This file is identical to the one used by the multi-server client, but its purpose is different: it tells the composed server which underlying servers to proxy to via HTTP.

{
  "mcpServers": {
    "nova-research-agent": {
      "url": "http://localhost:8001/mcp"
    },
    "brown-writing-workflow": {
      "url": "http://localhost:8002/mcp"
    }
  }
}

Now let's see how to create a composed server using FastMCP's composition features (code from lessons/agents_integration/mcp_server/src/main.py).

import json
import logging
from pathlib import Path
from fastmcp import Client, FastMCP
 
def load_server_config(config_path: Path | None = None) -> dict:
    """Load the MCP servers configuration from JSON file."""
    if config_path is None:
        config_path = Path(__file__).parent.parent / "mcp_servers_to_compose.json"
    with open(config_path) as f:
        return json.load(f)
 
def create_composed_server(config_path: Path | None = None) -> FastMCP:
    """Create a composed MCP server by mounting Nova and Brown servers."""
    # Create the main composed server
    mcp = FastMCP(
        name="Nova+Brown Composed Server",
        version="0.1.0",
    )
 
    # Load configuration
    config = load_server_config(config_path)
    servers_config = config.get("mcpServers", {})
 
    # Create proxies and mount each server
    for server_name, server_config in servers_config.items():
        # Wrap the server config in the structure expected by Client
        client_config = {"mcpServers": {server_name: server_config}}
 
        # Create a client for this server
        client = Client(client_config)
 
        # Create a proxy from the client
        proxy = FastMCP.as_proxy(client)
 
        # Mount the proxy WITHOUT a prefix - capabilities keep their original names
        mcp.mount(proxy)
 
    # Register the combined workflow prompt
    register_combined_prompt(mcp)
 
    return mcp
 
def register_combined_prompt(mcp: FastMCP) -> None:
    """Register the combined research and writing workflow prompt."""
 
    @mcp.prompt()
    def full_research_and_writing_workflow(dir_path: Path) -> str:
        """Complete workflow for research and article generation.
 
        This prompt combines the Nova research agent workflow with the Brown
        article generation workflow, providing end-to-end instructions for
        conducting comprehensive research and generating an article from that research.
        """
        return f"""
# Complete Research and Article Generation Workflow
 
This workflow combines two phases: research (Nova) and article generation (Brown).
 
## PHASE 1: Research (Nova)
 
Your job is to execute the workflow below...
[Full Nova workflow instructions]
 
## PHASE 2: Article Generation (Brown)
 
Once the research phase is complete, use Brown to generate an article
using the research from: {dir_path}
""".strip()
 
if __name__ == "__main__":
    composed_server = create_composed_server()
    composed_server.run()

Let's break down the key steps. We first create a FastMCP instance for our composed server. Then, for each server defined in our configuration, we create a Client that connects to it, use FastMCP.as_proxy(client) to create a proxy object, and finally use mcp.mount(proxy) to mount it without a prefix. When you mount a proxy without a prefix, all capabilities keep their original names. This means tools, resources, and prompts from both Nova and Brown are exposed exactly as they were defined in each agent [3], [4].

After mounting both servers, we add a new capability: the full_research_and_writing_workflow prompt. This is the key advantage of the composed server approach—we can add orchestration logic that coordinates both agents without modifying either agent's code. The @mcp.prompt() decorator registers a new MCP prompt that combines instructions for both Nova's research workflow and Brown's article generation. When a user triggers this prompt, the LLM receives comprehensive instructions to run both agents end-to-end in a single conversation.

Mounting creates a live, dynamic link between the parent server and the subserver. Instead of copying components, requests for tools are delegated to the mounted server at runtime. This means any changes to the subserver are immediately reflected in the parent [3].

Running the Client from the Terminal

To use the composed server, the client configuration is much simpler, pointing to a single endpoint (from lessons/25_integrate_agents/mcp_composed_server_config_http.json).

{
  "mcpServers": {
    "nova-brown-composed": {
      "url": "http://localhost:8003/mcp"
    }
  }
}

When you run the client with this configuration, it connects to the single composed server.

$ cd agents_integration/mcp_client
$ uv run -m src.client --config ../../25_integrate_agents/mcp_composed_server_config_http.json

It outputs:

INFO:root:Loading MCP server configuration from: mcp_composed_server_config_http.json
INFO:root:Found 1 MCP servers in configuration: nova-brown-composed
INFO:root:Connecting to MCP servers...
INFO:__main__:Starting composed MCP server...
...
INFO:__main__:Composed server created successfully!
INFO:__main__:Running composed server...
INFO:root:Fetching capabilities from all servers...
INFO:root:Total capabilities: 14 tools, 4 resources, 5 prompts


============================================================
All Servers
============================================================

  - 14 tools available
  - 4 resources available
  - 5 prompts available

Available Commands: /tools, /resources, /prompts, /quit

The output looks similar to the multi-server approach, but there's a key difference: the client is connecting to only one server (i.e., the composed server), which internally delegates to Nova and Brown. The composed server successfully exposes the same 14 tools, 4 resources, and 5 prompts. This demonstrates how composition provides the same capabilities with a simpler client-side configuration.

Notice that:

  • The composed server mounts both agents without prefixes , so capabilities keep their original names
  • There are now 5 prompts (not 4). The composed server has added a new full_research_and_writing_workflow prompt
  • The client sees a single unified interface with all capabilities from both agents plus the new orchestration prompt

This is the key advantage over the multi-server approach: with a composed server, you can add a single command that orchestrates both agents end-to-end.

Listing Prompts from the Composed Server

When you type /prompts, you'll see all 5 prompts available:

👤 You: /prompts
============================================================
Prompts (5)
============================================================

1. full_research_and_writing_workflow
   Complete workflow for research and article generation.

   This prompt combines the Nova research agent workflow with the Brown
   article generation workflow, providing end-to-end instructions for
   conducting comprehensive research and generating an article from that research.

2. full_research_instructions_prompt
   Complete Nova research agent workflow instructions.

3. generate_article_prompt
   Retrieve a prompt that will trigger the article generation workflow using Brown.

4. edit_article_prompt
   Retrieve a prompt that will trigger the article editing workflow using Brown.

5. edit_selected_text_prompt
   Retrieve a prompt that will trigger the selected text editing workflow using Brown.

Notice that full_research_and_writing_workflow is new —it's not from Nova or Brown, but added by the composed server itself. This prompt orchestrates both agents, instructing the LLM to:

  1. Run the complete Nova research workflow
  2. Then use Brown to generate an article from that research

This is the key advantage of the composed server approach: you can add coordination logic at the integration layer without modifying the individual agents.

Running the Complete Workflow with One Prompt

Now let's see the power of the composed server approach. Instead of running two separate prompts like in Approach 1, we can trigger the complete end-to-end workflow with a single command:

👤 You: /prompt/full_research_and_writing_workflow?dir_path=/absolute/path/to/research_folder

The LLM receives the combined prompt and immediately understands it needs to execute both phases:

🤔 LLM's Thoughts:
Okay, I'm looking at a two-phase operation here: Research (Nova) and Article Generation (Brown).
This is a well-defined process, and I need to execute it methodically. The first phase, Nova,
is the more involved one, and it's where my focus will be initially...

🔧 Function Call (Tool):
  Tool: extract_guidelines_urls
  Arguments: {
    "research_directory": "/absolute/path/to/research_folder"
  }

⚡ Executing tool 'extract_guidelines_urls' via MCP server...
✅ Tool execution successful!

🔧 Function Call (Tool):
  Tool: process_local_files
  Arguments: {
    "research_directory": "/absolute/path/to/research_folder"
  }

⚡ Executing tool 'process_local_files' via MCP server...
✅ Tool execution successful!

🔧 Function Call (Tool):
  Tool: scrape_and_clean_other_urls
  Arguments: {
    "research_directory": "/absolute/path/to/research_folder"
  }

⚡ Executing tool 'scrape_and_clean_other_urls' via MCP server...

The LLM continues executing the Nova research workflow tools, then seamlessly transitions to Brown's article generation. All with a single prompt command.

This addresses the limitation we identified in Approach 1, where you had to manually run two prompts in sequence. The composed server encapsulates the orchestration logic, providing a clean, single-command interface to the complete workflow.

Comparing the Approaches: When to Use Each

Both approaches achieve the same goal of integrating Nova and Brown, but they have different use cases and trade-offs.

AspectMulti-Server ClientComposed Server
How it worksThe client connects to multiple independent servers simultaneously.A single server internally proxies to multiple underlying servers.
Orchestration promptsNo built-in way to add combined prompts—must run agent prompts sequentially.Can add prompts that orchestrate multiple agents (e.g., full_research_and_writing_workflow).
Single command workflowRequires running multiple prompts in sequence (Nova, then Brown).Single full_research_and_writing_workflow prompt runs both agents end-to-end.
Client configurationClient needs to know about all servers.Single endpoint, simplifying client configuration.
FlexibilityEasy to add or remove servers dynamically by changing the client config.Requires code changes to modify the server-side composition.
Use when• Developing or testing agents • Quickly combining existing agents • Need flexibility to add/remove agents dynamically• Deploying agents as a unified system • Need coordination logic between agents • Want simpler client experience • Building a product that packages multiple agents

In general, the composed server pattern is better suited for production if you want to package the entire multi-agent system into a single, deployable unit with a clean, unified interface, which is ideal for integration into applications like Cursor or Claude Desktop.

Conclusion

In this lesson, we explored how to integrate the Nova research agent and Brown writing workflow using MCP. We learned:

  1. Multi-Server MCP Client : How to connect a single client to multiple MCP servers using FastMCP's multi-server configuration. Capabilities are prefixed with server names, and you run agent workflows by triggering their prompts sequentially.
  2. Composed MCP Server : How to use FastMCP's composition features (as_proxy() and mount()) to create a unified server that proxies multiple agents. Crucially, composed servers can add new orchestration prompts—like full_research_and_writing_workflow—that coordinate multiple agents without modifying their code.
  3. Use Cases : When to use a multi-server client vs a composed server. The multi-server approach is better for flexibility and independence, while the composed server is better for having a unified interface, orchestration capabilities, and single-command workflows.

The key insight is that MCP makes agent integration straightforward. Because both Nova and Brown are already MCP servers, we don't need to write custom integration code. We simply leverage MCP's standardized protocol and FastMCP's composition features. The composed server approach goes further by allowing us to add orchestration logic at the integration layer—providing a single-command interface to run both agents end-to-end—without polluting either agent's codebase.

In the next lesson, you'll see how to use both agents from Cursor to work on an article end-to-end. In Part 3 of this course, we'll explore production deployment, including how to deploy these composed servers remotely, add monitoring with Opik, and implement security measures.

References

  1. Berkeley Function Calling Leaderboard. (n.d.). Gorilla. gorilla.cs.berkeley.edu/leaderboard.html
  2. Evaluating Tool Calling Capabilities in Large Language Models: A Literature Review. (n.d.). Quotient AI. blog.quotientai.co/evaluating-tool-calling-capabilities-in-larg...
  3. Server Composition. (n.d.). FastMCP. gofastmcp.com/servers/composition
  4. The FastMCP Server. (n.d.). FastMCP. gofastmcp.com/python-sdk/fastmcp-server-server
  5. Multi-Agent Orchestration with Reinforcement Learning. (n.d.). arXiv. arxiv.org/abs/2505.19591
  6. Designing Multi-Agent Intelligence. (n.d.). Microsoft Developer Blog. developer.microsoft.com/blog/designing-multi-agent-intelligence
  7. LLM Multi-Agent Reinforcement Learning. (n.d.). Xue Guang. xue-guang.com/post/llm-marl
  8. Multi-Agent Research System. (n.d.). Anthropic. anthropic.com/engineering/multi-agent-research-system
  9. Multi-Agent LLM Systems for Enterprises. (n.d.). Fiddler AI. fiddler.ai/articles/multi-agent-llm-systems-for-enterpr...
  10. Multi-Agent. (n.d.). OpenAI Agents Python. openai.github.io/openai-agents-python/multi_agent
  11. Architecture. (n.d.). Model Context Protocol. modelcontextprotocol.io/docs/learn/architecture
  12. Architectures for Multi-Agent Systems. (n.d.). Galileo AI. galileo.ai/blog/architectures-for-multi-agent-systems
  13. A Practical Guide to Building Agents. (n.d.). OpenAI. cdn.openai.com/business-guides-and-resources/a-practical-gu...
  14. LLM Agent Orchestration with LangChain and Granite. (n.d.). IBM Think. ibm.com/think/tutorials/llm-agent-orchestration-with...
  15. AI Agent Design Patterns. (n.d.). Microsoft Learn. learn.microsoft.com/en-us/azure/architecture/ai-ml/guide/ai-agen...
  16. Model Context Protocol Specification. (2025, November 25). Model Context Protocol. modelcontextprotocol.io/specification/2025-11-25