Skip to content
OR
ES
Agentic Automation · · 8 min read

No More Brittle Glue Code: Hands-On AI Agent Integration with MCP, FastMCP, and uv

Connecting an AI agent to databases and APIs should not be maintenance hell. Learn how to build custom MCP servers with FastMCP, consume them in LangChain, and spin up community tools on the fly with uv.

Author: Ones Romero | Santiago, Chile
#MCP #FastMCP #LangChain #AI Agents #Python #uv #Architecture

Spinning up an AI agent in a local notebook today takes about ten lines of code. Anyone can pull that off with a half-hour tutorial. The real headache—the one that surfaces the moment the project steps outside the sandbox and hits production—is wiring that agent to the real world.

Writing bespoke integrations for every database, every cloud storage bucket, every third-party API, or every internal service your agent needs quickly becomes unsustainable as systems scale. If an external API changes a parameter, your custom connector breaks. If you add five new tools, you end up with a tangled ball of coupled dependencies that is impossible to audit, maintain, or debug.

In this guide, we address this root issue using the Model Context Protocol (MCP). We will examine its three-tier topology, build a custom Python MCP server using FastMCP, consume it from a LangChain agent over standard OS pipes (stdio), and spin up community tools on the fly using uv without polluting local virtual environments.

💻 Code and practice notebooks on GitHub:
All interactive practice code for this lesson is available in the OnesRomero/Langchain-practices repository:


The Core Challenge: Point-to-Point Integration Chaos

Designing agents without a clean context abstraction layer inevitably creates tight architectural coupling. Every distinct data source demands its own authentication logic, serialization routines, error handlers, and bespoke prompt engineering.

graph TD
    subgraph Chaos["1. Traditional Scenario: Point-to-Point Chaos"]
        A1[AI Agent] -. Ad-hoc connector .-> B1[(Databases / PostgreSQL)]
        A1 -. Manual wrapper .-> C1[External Weather / Search API]
        A1 -. Custom script .-> D1[GitHub Docs / Local Logs]
        A1 -. Local dependency .-> E1[OS System Tools]
    end

    subgraph Bus["2. MCP Architecture: Universal Bus"]
        A2[AI Agent / Host] <-->|MCP Client| M[MCP Protocol]
        M <--> S1[MCP Server: Databases]
        M <--> S2[MCP Server: Web Search / APIs]
        M <--> S3[MCP Server: Documentation]
        M <--> S4[MCP Server: System Utilities]
    end

This is where MCP comes into play. Instead of stringing fragile point-to-point wires between model calls and tools, the protocol operates as a universal bus.

Think of the leap from proprietary cables for every single peripheral to the USB standard: one universal interface that makes hardware plug-and-play. With MCP, every data source (local files, cloud storage, or external APIs) exposes a standardized contract. The agent host no longer cares how a service works under the hood; it simply queries via the MCP protocol and gains immediate access to registered capabilities.

While originally introduced by Anthropic, MCP was designed from day one as an open-source, model-agnostic standard. Whether running Claude 3.5 Sonnet, GPT-4o, Llama 3, or a local model via Ollama, the protocol behaves identically. You implement an MCP server once and make it instantly available to any agent in your ecosystem.

DimensionTraditional Point-to-Point ApproachStandardized MCP Architecture
CouplingDirect tie between agent prompts and third-party SDKsDecoupled via structured client-server contracts
MaintenanceCascading failures when external APIs changeServer absorbs implementation changes; interface remains stable
ReusabilityDuplicated connectors across distinct agent codebasesA single MCP server serves multiple independent agents
Security & AuditingCredentials scattered across application logicCentralized boundary isolation at the server process level

MCP Architecture: Topology, Primitives, and Transports

Before diving into code, let us break down the foundational concepts behind the protocol.

1. Three-Tier Topology

flowchart LR
    Host["1. Host (Brain)<br/>Orchestrator / LangChain / LangGraph"] -->|Context / Tool Request| Client["2. MCP Client (Bridge)<br/>MultiServerMCPClient"]
    Client -->|stdio / SSE Transport| Server["3. MCP Server (Provider)<br/>FastMCP / Tools / Resources"]
    Server -->|Execution / Data Payload| Client
    Client -->|Structured Output| Host
  1. Host (The Brain): The primary application runtime or agent orchestrator (e.g., LangChain or LangGraph) responsible for reasoning, planning, and determining which actions to trigger.
  2. MCP Client (The Technical Bridge): Resides inside the host environment (using adapters such as langchain-mcp-adapters). It manages transport connections, handles protocol handshakes, and routes messages.
  3. MCP Server (The Provider): The process that exposes executable tools, documentation resources, or prompt templates. It executes the actual database query or API call and returns structured JSON-RPC payloads back to the client.

2. The Three Core Primitives (Scopes)

An MCP server is more than a simple function registry; it coordinates three distinct primitives:

  • Tools: Executable actions that let the model interact with the outside world or execute stateful operations. They require detailed docstrings and strict type hints so the model knows when and how to call them.
  • Resources: Read-only data payloads identified by standardized URIs (e.g., local file paths or URIs like github://repo/README.md). They inject clean reference material or logs directly into context without requiring custom API endpoints.
  • Prompts: Pre-packaged templates containing business rules and guardrails (e.g., constraining an agent’s domain to specific frameworks). This prevents client applications from having to reinvent system prompts from scratch.

3. Transport Layers: stdio vs. SSE

Messages between the MCP client and server move over two primary transport mechanisms:

  1. Standard Input / Output (stdio): Designed for local execution or single-container environments. The client launches the server as an OS subprocess and communicates over standard input/output pipes. It is lightning-fast, zero-overhead, requires no open network ports, and provides strict local isolation.
  2. Streamable HTTP with Server-Sent Events (SSE): Built for remote and distributed execution. When the MCP server lives in the cloud or is hosted as a third-party microservice, the client opens an HTTP connection and receives streaming responses.

Hands-On: Building the MCP Server with FastMCP

Let us look at our custom Python MCP server using FastMCP. This server exposes a web search tool using Tavily, a read-only documentation resource pointing to GitHub Markdown, and system prompt templates.

This code mirrors resources/2.1_mcp_server.py from our practice repository:

"""
resources/2.1_mcp_server.py: MCP server implemented with FastMCP.
Exposes Tavily search tools, GitHub documentation resources, and prompts.
"""
from dotenv import load_dotenv
load_dotenv()

from mcp.server.fastmcp import FastMCP
from tavily import TavilyClient
from typing import Dict, Any
from requests import get

# 1. Instantiate the MCP server
mcp = FastMCP("mcp_server")

# Initialize Tavily client for web search
tavily_client = TavilyClient()

# 2. Executable Tool: search the web with Tavily
@mcp.tool()
def search_web(query: str) -> Dict[str, Any]:
    """Busca información en la web"""
    results = tavily_client.search(query)
    return results

# 3. Read-Only Resource: direct access to repository documentation
@mcp.resource("github://langchain-ai/langchain-mcp-adapters/main/README.md")
def github_file():
    """
    Recurso para acceder al archivo README.md de langchain-ai/langchain-mcp-adapters
    """
    url = "https://raw.githubusercontent.com/langchain-ai/langchain-mcp-adapters/main/README.md"
    try:
        resp = get(url)
        return resp.text
    except Exception as e:
        return f"Error: {str(e)}"

# 4. Guarded Prompt Template: packages business rules inside the server contract
@mcp.prompt()
def prompt():
    """Analiza datos de un archivo del repositorio langchain-ai con información exhaustiva"""
    return """
    Eres un asistente útil que responde a las preguntas del usuario sobre LangChain, LangGraph y LangSmith.

    Puedes usar las siguientes herramientas/recursos para responder a las preguntas del usuario:
    - search_web: Busca información en la web
    - github_file: Accede a los archivos del repositorio langchain-ai

    Si el usuario hace una pregunta que no esté relacionada con LangChain, LangGraph o LangSmith, debes decir "Lo siento, solo puedo responder preguntas sobre LangChain, LangGraph y LangSmith."

    Puedes realizar múltiples llamadas a herramientas y recursos para responder la pregunta del usuario.

    También puedes hacer preguntas de aclaración al usuario para comprender mejor su pregunta.
    """

# 5. Run the server using standard input/output
if __name__ == "__main__":
    mcp.run(transport="stdio")

Notice how FastMCP handles the JSON-RPC complexity under the hood. The decorators @mcp.tool(), @mcp.resource(), and @mcp.prompt() generate the required schemas automatically for the client.


Consuming the Server with a LangChain Agent

Now we will build the client agent that consumes our custom MCP server inside a Jupyter notebook or interactive script (available in 2.1_mcp.ipynb).

We will use MultiServerMCPClient from langchain-mcp-adapters and spawn the server as a local subprocess over stdio.

💡 Pro-Tip for Windows and Jupyter Notebook Users

When executing asynchronous subprocesses in Windows or inside Jupyter notebooks (ipykernel), you will frequently encounter fileno() errors or event loop incompatibilities. To prevent this, add this configuration block at the top of your notebook:

import sys
import asyncio

# Workaround for subprocesses and fileno() error on Windows / Jupyter
if sys.platform == "win32":
    # 1. Use ProactorEventLoop for full subprocess support
    if not isinstance(asyncio.get_event_loop_policy(), asyncio.WindowsProactorEventLoopPolicy):
        asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
    
    # 2. Redirect stderr to avoid fileno() error when launching MCP servers
    if "ipykernel" in sys.modules:
        sys.stderr = sys.__stderr__

The Client Agent Implementation

"""
Consuming the local MCP server from a LangChain agent over stdio.
"""
from dotenv import load_dotenv
load_dotenv()

from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
from langchain.messages import HumanMessage
from pprint import pprint

# 1. Configure the MCP client to connect to our local server script over stdio
client = MultiServerMCPClient({
    "local_server": {
        "transport": "stdio",
        "command": "python",
        "args": ["resources/2.1_mcp_server.py"],
    }
})

# 2. Dynamically discover tools, resources, and system prompt from server
tools = await client.get_tools()
resources = await client.get_resources("local_server")
prompt_data = await client.get_prompt("local_server", "prompt")
system_prompt = prompt_data[0].content

print(f"✅ Discovered tools: {[t.name for t in tools]}")

# 3. Instantiate agent with dynamic tools and server prompt
agent = create_agent(
    model="gpt-4o",
    tools=tools,
    system_prompt=system_prompt
)

# 4. Run with traceable Thread ID for state isolation
config = {"configurable": {"thread_id": "1"}}
query = "Cuéntame sobre la librería langchain-mcp-adapters"

response = await agent.ainvoke(
    {"messages": [HumanMessage(content=query)]},
    config=config
)

# 5. Inspect structured output
pprint(response)

Execution Flow Step-by-Step

When you execute this script, the interaction follows a clean, predictable workflow:

  1. Subprocess Initialization: MultiServerMCPClient launches python resources/2.1_mcp_server.py as an isolated OS process via stdio pipes.
  2. Dynamic Discovery: The client negotiates with the server, discovering the search_web tool, the github_file resource, and the system prompt.
  3. User Request: The agent receives the user message via HumanMessage. The model evaluates the system prompt and determines whether to invoke registered tools.
  4. Isolated Invocation: The client routes the call over stdio to the child process. The server executes the lookup in Tavily and returns structured results.
  5. Synthesis: The model processes the tool output and returns the final response while adhering to system prompt directives.

Running Community MCP Servers on the Fly with uv

Beyond writing custom servers, the MCP open-source catalog offers dozens of pre-built tools (such as database query engines, file viewers, and system utilities).

The traditional pain point of testing third-party tools is package management: creating virtualenvs, wrestling with pip version conflicts, and polluting development environments. We can bypass all of that using uv, the high-performance Python package manager.

Instead of running pip install, we configure the MCP client to execute uv directly. Here is how to consume the community package mcp-server-time without installing it into your local Python environment:

"""
Consuming community MCP servers on the fly using uv.
"""
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
from langchain.messages import HumanMessage
from pprint import pprint

# Delegate isolated execution to uv running the mcp_server_time module
client_time = MultiServerMCPClient({
    "time": {
        "transport": "stdio",
        "command": "uv",
        "args": [
            "run",
            "python",
            "-m",
            "mcp_server_time",
            "--local-timezone=America/Santiago",
        ],
    }
})

# The client automatically discovers available functions (e.g. get_current_time)
tools_time = await client_time.get_tools()

agent_clock = create_agent(
    model="gpt-4o",
    tools=tools_time,
)

query = HumanMessage(content="¿Qué hora es?")

result = await agent_clock.ainvoke(
    {"messages": [query]}
)

pprint(result)

Console Output

Running the script produces immediate, autonomous execution:

👤 User: ¿Qué hora es?
🤖 [Tool Call] Agent invokes: get_current_time with {}
⚙️ [Tool Response] Payload: 2026-09-22T14:47:35-03:00 (America/Santiago)
🤖 AI: It is currently 2:47 PM in Santiago, Chile, on Tuesday, September 22, 2026.

No timezone conversion math, no calendar libraries, no dependency clutter. The client instructs uv to spawn mcp-server-time over stdio, and the agent automatically detects and executes the tool.


Production Best Practices

  1. Principle of Least Privilege: An MCP server executes real code on underlying infrastructure. Never hand administrative or bulk-deletion capabilities to an autonomous agent if it only requires read-only query access.
  2. Prefer stdio for Containerized Deployments: When running agents on AWS ECS, Kubernetes, or Docker, execute MCP servers as child processes inside the same container via stdio. This eliminates network hops, lowers cloud costs, and prevents open-port security vulnerabilities.
  3. Enforce Strict Schema Typing: Language models build function arguments strictly from Python type hints (pydantic models or standard typing annotations). Always annotate tool parameters explicitly.

What Comes Next in Agent Architecture

MCP provides a clean, standardized blueprint for connecting agents to tools and data. Once operational connectivity is solved, the next critical architectural hurdle is memory and persistence.

In our next deep dive, we will explore how to architect immutable context and state persistence that evolves across multi-turn sessions, allowing agents to retain user preferences and learn continuously in production environments.

Are you architecting multi-agent systems and looking to decouple your integration layer with MCP?

  • Join the discussion: Let’s connect on LinkedIn to discuss agentic patterns and production architectures.
  • Enterprise consulting & architecture: Reach out through the contact section to evaluate your use case.
  • Follow the series: Subscribe to the newsletter below for the next deep dive into agent state persistence and long-term memory.
OR

Ones Romero

Intelligent Automation Engineer · AWS Solutions Architect · UiPath MVP 2026

Designing AWS serverless architectures, autonomous AI agents, and enterprise RPA fleets across banking, government, and manufacturing.

Engineering Newsletter

Engineering in Production: Cloud, RPA & Artificial Intelligence

Hands-on engineering lessons, AWS serverless architectures, and autonomous AI agents directly in your inbox. Zero spam, 1-click unsubscribe.

  • No spam or sales pitches
  • 1 monthly digest
  • 1-click unsubscribe