An overview synthesis of Max Gfeller’s “Building Agentic Applications with CrewAI and MCP” Book from Manning
Image from Manning Site
Introduction
The shift from standard Large Language Model (LLM) text prediction to production-ready AI agents represents a core transition in software engineering. As Max Gfeller highlights in the introduction of Building Agentic Applications with CrewAI and MCP, raw foundation models possess a singular, fundamental limitation: they predict tokens based on patterns in training data and stop. They cannot natively orchestrate multi-source research pipelines, programmatically pivot mid-task when encountering execution errors, or execute stateful recursive reasoning without architectural wrappers.
Usual disclaimer: I have no ties in any ways neither with the author nor the edition company! I’m just a book eater 🧟♂️
The Core Architecture: From Token Predictors to Augmented LLMs
To successfully build agentic systems, developers must internalize a key distinction established by Anthropic: the line separating AI Agents from Agentic Workflows.
- AI Agents (Dynamic Reasoning Loops): Systems where the LLM dynamically evaluates its state, autonomously determines which pre-configured tool to invoke, assesses runtime output, and controls its own stop conditions.
- Agentic Workflows (Deterministic Directed Acyclic Graphs): Workflows modeled as graphs consisting of static code routes, programmatic gates, and LLM orchestration nodes. Here, flexibility is traded for predictable, industrial-grade execution.
The book introduces the Augmented LLM as the core building block of this architecture. Multi-agent systems are not magic; they are simply configurations of individual augmented LLMs passing state, instructions, and schemas to one another.
An Augmented LLM enhances text prediction by wrapping it with three core layers:
- Retrieval: Pulling external knowledge from vector stores, relational databases, or file systems.
- Tools: Functional interfaces enabling the model to execute real-world mutations — such as API calls, system execution, or cloud interactions.
- Memory: Stateful persistence tracking variables, execution entities, and long-term data schemas across discrete inference iterations.
Under the Hood: Tool Calling Mechanics
The foundational mechanic under pinning all agentic behavior is tool calling (or function calling). Modern frontier models are explicitly fine-tuned to detect tool schemas, halt standard token generation, and output valid structured data (typically JSON matching a precise schema) rather than natural language text.
The developer’s application acts as the orchestration layer: it intercepts the model’s structured request, handles the underlying computation or network request, and feeds the raw execution result back into the model’s next context window window. The model never directly touches external services — it remains an isolated text predictor requesting execution boundaries.
# Listing 1.1: Standard Low-Level Tool Definition & Interception Mechanics
from openai import OpenAI
import json
client = OpenAI()
# Strict functional declarations tell the LLM exactly how to format its intent
tools = [{
"type": "function",
"name": "get_report",
"description": "Get the financial report for a given year.",
"parameters": {
"type": "object",
"properties": {
"year": {"type": "number"},
},
"required": ["year"],
"additionalProperties": False
},
"strict": True
}]
input_messages = [{"role": "user", "content": "How much revenue did we make in 2025?"}]
response = client.responses.create(
model="gpt-5", # Structural conceptual target
input=input_messages,
tools=tools,
)
# Intercepting the model's instruction to drive local execution code
print([item.model_dump_json() for item in response.output])
# Example Output Intercepted:
# [{"type": "function_call", "name": "get_report", "arguments": "{\"year\":2025}"}]
Industrial Design Patterns and Their Engineering Realities
Moving past basic single-prompt scripts requires adopting standardized agentic design patterns. The book breaks down five core architectural configurations that balance execution cost, latency, and reliability:
| Design Pattern | Execution Model | Ideal Production Use Case | Primary Operational Failure Mode |
| ------------------------ | --------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| **Prompt Chaining** | Linear Sequence | Fixed data transformation pipelines (e.g., Extract → Sanitize → Report) | High fragility; errors compound mathematically across the pipe |
| **Routing** | Classification Switch | Triage workflows (e.g., Sorting customer issues into billing or technical handlers) | Classification drift; misrouted context breaks downstream assumption schemas |
| **Parallelization** | Concurrent Workers | High-throughput horizontal tasks (e.g., Scoping 5 competitors simultaneously) | Uncapped token exhaustion and rate-limiting blocks |
| **Orchestrator-Workers** | Dynamic Delegation | Open-ended complex discovery where subtask schemas cannot be predicted | Infinite reasoning loops; loss of deterministic control |
| **Evaluator-Optimizer** | Recursive Feedback | Refinement tasks requiring a strict quality bar (e.g., Code generation, legal drafting) | Semantic deadlocks where generator and evaluator fail to reach convergence |
The Core Paradox of Agent Deployment
This is the part I truly appreciated!
Engineering these patterns introduces unique production challenges. The most glaring is compounding error rates. If an individual LLM node has a 95% reliability rate, chaining 10 sequential agentic operations together reduces the aggregate pipeline success rate to roughly ≈60% (0.9510).
Furthermore, context window degradation is a constant challenge: as an agent executes multiple loops, the context window continuously accumulates verbose tool payloads and intermediate reasoning text. This high token volume degrades model performance, causing it to lose track of early system instructions or become distracted by irrelevant data.
The architectural antidote is clear: start with the absolute simplest pattern possible. Never use a multi-agent system where a single augmented LLM or static workflow will suffice.
Developing with CrewAI: Primitives, Scaffolding, and Strict Typing
CrewAI simplifies this engineering landscape by providing three core abstractions: Agents (who executes), Tasks (what gets executed), and Tools (how actions are taken).
The book outlines a foundational rule of thumb when working with the framework: The 80/20 Rule of Agentic Setup. Developers often waste development cycles tuning agent descriptions and backstories, when they should instead focus on task specifications. A poorly specified task guarantees a broken workflow regardless of agent descriptions, whereas highly explicit, strictly bounded task constraints can guide even simple models to flawless execution.
To ensure structural integrity and prevent conversational hallucinations, CrewAI tasks can enforce typed output interfaces using Pydantic models. This allows developers to treat agent outputs as standard JSON data objects within programmatic systems.
# Listing 1.2 & 2.7 Combined: Building Deterministic Structured Pipes in CrewAI
from typing import List
from pydantic import BaseModel, Field
from crewai import Agent, Crew, Task, Process
# Enforcing strict typing on the AI's final output artifact
class BlogArticle(BaseModel):
title: str = Field(..., description="SEO-optimized title")
summary: str = Field(..., description="High-level executive overview")
content: str = Field(..., description="The complete written body text")
tags: List[str] = Field(..., description="Metadata categorization tags")
# Establishing the persona boundary
blog_writer = Agent(
role="Blog post writer",
goal="Write highly optimized, technically accurate content",
backstory="You are an expert technical editor with 20 years of experience in data-driven publishing.",
verbose=True
)
# Structuring the task parameters with an output interface constraint
crewai_blog_task = Task(
description="Write an in-depth post detailing CrewAI framework capabilities in modern enterprise stacks.",
expected_output="A valid structured schema breaking down the core thesis.",
agent=blog_writer,
output_pydantic=BlogArticle # Hard contract boundary
)
# Initializing execution topology
crew = Crew(
agents=[blog_writer],
tasks=[crewai_blog_task],
process=Process.sequential,
verbose=True
)
# Launching execution runtime loop
result = crew.kickoff()
Clean Scaffolding: Declarative Architectures
While simple architectures can be built in single python scripts, production crews require clean isolation. CrewAI facilitates this via a declarative layout where agent backstories and task configurations are isolated inside clean YAML files, separated from runtime execution logic.
# Config: src/seo_crew/config/agents.yaml
keyword_researcher:
role: >
Keyword Discovery Specialist
goal: >
Identify high-value keyword targets from competitor data footprints.
backstory: >
You are a data analyst specializing in search engine algorithms and web scraping forensics.
topic_researcher:
role: >
Topic researcher
goal: >
Based on the keyword candidates, choose a topic for a single blog post that incorporates one or more of the keywords.
backstory: >
You are an experienced researcher who is very experienced in finding rabbit holes and digging deep into the internet to find the most relevant information - the kind of information you wouldn't find in a simple surface-level Google search.
By decoupling configuration from code, developers can modify backstories, adjust temperature thresholds, and fine-tune goals without changing application code or breaking integration paths.
Bridging Systems via the Model Context Protocol (MCP)
Agents are often isolated from the internal systems where real work happens. The Model Context Protocol (MCP), an open standard highlighted heavily by Gfeller, addresses this challenge. MCP serves as a unified context plane between AI systems and external data platforms, tools, and environments.
Rather than writing custom API wrapper logic for every unique tool deployment, an application can configure an MCP Server. This server exposes three key primitives to any compliant AI host or agentic framework:
- Resources: Read-only data properties providing raw contextual files or database dumps to the agent.
- Tools: Executable function boundaries that the agent can request the runtime system to run safely.
- Prompts: Pre-defined templates or user commands that pre-structure agent execution parameters.
Implementing local MCP Solutions
The author highlights how to deploy local open-source implementations over proprietary cloud architectures using libraries like fastmcp. The example below demonstrates a custom documentation management system that safely opens file operations to agentic manipulation:
# Listing 4.18 & 4.19: Comprehensive FastMCP 2.0 Server Architecture
import os
import json
import asyncio
from pathlib import Path
from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel, Field
# Initialize a named FastMCP node
mcp = FastMCP("docs-updater")
def get_docs_path() -> str:
return os.environ.get("DOCS_PATH", "./docs")
# Exposing an MCP Resource primitive to provide structural context to the agent
@mcp.resource("docs://registry")
def get_doc_pages() -> str:
"""Parse local docs.json metadata to discover registered files."""
docs_path = get_docs_path()
docs_json_path = Path(docs_path) / "docs.json"
if not docs_json_path.exists():
return "[]"
with open(docs_json_path, 'r') as f:
config = json.load(f)
pages = []
navigation = config.get("navigation", {})
# Handle both direct and multi-tier tab groupings within the schema
for tab in navigation.get("tabs", []):
for group in tab.get("groups", []):
pages.extend(group.get("pages", []))
for group in navigation.get("groups", []):
pages.extend(group.get("pages", []))
return json.dumps({"available_pages": pages})
# Defining typed structure for input execution parameters
class FileUpdatePayload(BaseModel):
file_path: str = Field(..., description="Relative path targeting file to modify")
new_content: str = Field(..., description="Precise contents to inject into file storage")
# Exposing an MCP Tool primitive enabling execution mutation powers
@mcp.tool()
def apply_docs_update(payload: FileUpdatePayload) -> str:
"""Safely updates a documentation page locally."""
base_dir = Path(get_docs_path()).resolve()
target_path = (base_dir / payload.file_path).resolve()
# Critical Security Barrier: Prevent Directory Traversal Attacks
if not str(target_path).startswith(str(base_dir)):
return "Security Error: Attempted execution outside document root sandbox boundaries."
try:
with open(target_path, 'w', encoding='utf-8') as f:
f.write(payload.new_content)
return f"Mutation successful on file: {payload.file_path}"
except Exception as e:
return f"Execution failure encountered: {str(e)}"
if __name__ == "__main__":
# Runs the local MCP loop via standard I/O communication pipelines
mcp.run()
Advanced Applications: Multimodality and Vector Spaces
In Chapter 5, the book moves beyond simple text pipelines into multimodal AI architectures. True multi-modality spans two distinct design spaces:
- Generative Vision: An agent leverages a vision-capable model to analyze an image, extract structured attributes, and describe visible details.
-
Cross-Modal Embedding Search: A single multi-modal embedding model (such as
gemini-embedding-2-preview) maps both textual strings and raw binary image data into a shared vector space. This allows agents to perform similarity searches across media types—using an image query to locate matching text documentation, or vice versa.
The book demonstrates a hypothethical and an automated e-commerce catalog pipeline. A raw image enters the pipeline and passes through a sequence of tasks:
-
Image Analyzer Agent: Uses a vision model to extract precise material details, dimensions, and colors into a
ProductFeaturesPydantic model. - Description Writer Agent: Receives the structural properties from the first task and drafts a persuasive, SEO-optimized marketing listing.
- Catalog Analyst Agent: Generates a joint multimodal vector embedding using both the raw image data and written description. It queries a local vector database to find similar inventory and generate optimal pricing recommendations.
# Listing 5.13: Multi-Modal Context Generation and Vector Retrieval Tool
import numpy as np
import chromadb
from crewai.tools import BaseTool
from google import genai
from google.genai import types
from pydantic import BaseModel, Field
class CatalogSimilarityInput(BaseModel):
image_path: str = Field(..., description="Local system path to product item image source")
product_description: str = Field(..., description="Text breakdown generated by preceding agent outputs")
class CatalogSimilarityTool(BaseTool):
name: str = "catalog_similarity_locator"
description: str = "Generates joint multi-modal embeddings to locate similar product coordinates inside vector store space."
args_schema: type[BaseModel] = CatalogSimilarityInput
def _run(self, image_path: str, product_description: str) -> str:
# Initialize standard client instances safely
ai_client = genai.Client()
db_client = chromadb.PersistentClient(path="data/catalog_index")
collection = db_client.get_or_create_collection("product_embeddings")
# Load raw binary asset payloads securely
with open(image_path, "rb") as img_file:
image_bytes = img_file.read()
# Drive execution to joint multi-modal embedding API models
response = ai_client.models.embed_content(
model="gemini-embedding-2-preview",
contents=[
types.Part.from_bytes(data=image_bytes, mime_type="image/jpeg"),
product_description
]
)
raw_vector = response.embeddings[0].values
# Enforce mathematical L2 Normalization bounds to assure cosine similarity accuracy
norm = np.linalg.norm(raw_vector)
normalized_vector = (raw_vector / norm).tolist() if norm > 0 else raw_vector
# Query the local vector store for nearest coordinates
query_results = collection.query(
query_embeddings=[normalized_vector],
n_results=3,
include=["metadatas", "documents"]
)
return str(query_results)
Putting the Knowledge in Practice
Based on the patterns established in Building Agentic Applications with CrewAI and MCP and the official crewai-in-action repository, the framework leans heavily on separating natural language prose from application logic.
The industry-standard setup utilizes YAML configuration files for declaring the agent’s identity narrative and tasks, alongside a structured Python class using decorators like @CrewBase, @agent, and @task to assemble the pipeline.
Below is the implementation of a simple MarketResearcher agent leveraging built-in capabilities inspired by the book. 👇
-
Define the Narrative Configurations (YAML): to achieve strict decoupling, we declare our agent’s properties and corresponding tasks in explicit configuration files, first in
**config/agents.yaml**.
market_researcher:
role: "Senior Market Research Analyst"
goal: "Gather and analyze comprehensive data regarding emerging technologies"
backstory: |
You are an expert market analyst with 15 years of industry experience.
You are thorough, objective, and specialize in synthesizing fragmented technical data
into concise, structured market intelligence reports. You stick strictly to facts
and avoid speculation.
- Then for the research in
**config/tasks.yaml**
research_task:
description: |
Conduct a deep-dive analysis on the following technical trend: {topic}.
Identify market size indicators, prominent competitors, and primary technical barriers.
Utilize any tools at your disposal to verify data.
expected_output: |
A Markdown formatted market research report organized into clear sections:
- Executive Summary
- Competitive Landscape
- Technical Barriers
- Strategic Outlook
agent: market_researcher
- Writing the Agent Application Code: this application file creates the structural scaffolding. It reads the configurations, assigns pre-built tooling (such as
SerperDevToolfor web operations), and instantiates the execution runtime via theCrewwrapper;
# research_crew.py
import os
from crewai import Agent, Crew, Process, Task
from crewai.project import CrewBase, agent, crew, task
from crewai_tools import SerperDevTool
# The CrewBase structure
@CrewBase
class MarketResearchCrew:
"""Market Research Crew handling structured intelligence collection"""
# declaring configuration files
agents_config = 'config/agents.yaml'
tasks_config = 'config/tasks.yaml'
@agent
def market_researcher(self) -> Agent:
"""Instantiates the Market Researcher with identity patterns"""
return Agent(
config=self.agents_config['market_researcher'],
tools=[SerperDevTool()], # external web search...
verbose=True
@task
def research_task(self) -> Task:
"""Instantiates the discrete tracking task contract"""
return Task(
config=self.tasks_config['research_task']
)
@crew
def crew(self) -> Crew:
"""Assembles the compiled execution container"""
return Crew(
agents=self.agents,
tasks=self.tasks,
process=Process.sequential,
verbose=True
)
- Run the Orchestration Loop: in this part, we execute the runtime configuration by using an entrypoint script that kicks off the compiled execution container, passing injection parameters (like
{topic}) to fill out the underlying task definitions.
# main.py
import os
from research_crew import MarketResearchCrew
def run_analysis():
# api key must bse set beforehand
inputs = {
'topic': 'Model Context Protocol (MCP) Adoption in Enterprise Agentic Design'
}
print(f"--- Launching Market Analysis for: {inputs['topic']} ---")
# kick off the agent loop
research_crew_instance = MarketResearchCrew()
result = research_crew_instance.crew().kickoff(inputs=inputs)
print("\n--- Final Analysis Output Generated ---")
print(result.raw)
if __name__ == "__main__":
run_analysis()
Conclusion: Navigating the Frontier of Agentic Systems
Max Gfeller’s “Building Agentic Applications with CrewAI and MCP” serves as a vital blueprint for shifting from fragile, single-prompt interactions to production-grade, autonomous software ecosystems. By mastering structured design patterns, anchoring agent behaviors with strict Pydantic contract boundaries, and leveraging the open standard of the Model Context Protocol, developers can build deterministic pipelines capable of real-world reasoning and execution. Although this book is currently an early-stage work in progress (MEAP), it represents an exceptionally practical and clear starting point for software engineers, developers, and technical professionals who are new to this rapidly evolving field. Rather than getting lost in theoretical noise, it provides the hands-on, architectural scaffolding needed to transform raw LLMs into secure, modular, and highly industrialized multi-agent applications.
>>> Thanks for reading <<<
Links
- The Book on the Publisher’s site: https://www.manning.com/books/building-agentic-applications-with-crewai-and-mcp
- The book’s GitHub repository: https://github.com/MaxGfeller/crewai-in-action



Top comments (0)