Understanding MCP and FastMCP: Building Unified Tools for Enterprise APIs

Imagine trying to use ten different apps that each speak a different language. Frustrating, right? That’s exactly the problem developers face when connecting AI models to enterprise tools. Each internal API has its own quirks, authentication methods, and data formats.

What you’ll learn: This tutorial demystifies the Model Context Protocol (MCP), MCP Servers, FastMCP, Tool Schemas, Internal APIs, and Unified Access Wrappers. By the end, you’ll understand how these pieces fit together to create a clean, maintainable way to expose enterprise tools to AI systems.

What is the Model Context Protocol?

Think of a universal remote. You don’t care if the TV is Samsung or Sony—the remote speaks one language to control them all. That’s MCP—a standard way for AI models to talk to tools.

Model Context Protocol (MCP) is an open protocol that defines how AI models discover and invoke tools. Instead of writing custom integrations for every internal API, MCP gives you one consistent interface.

How it works: MCP defines three core operations:

  • ListTools — the AI asks “what can you do?”
  • CallTool — the AI says “run this specific tool with these arguments”
  • DescribeTool — the AI asks for details about a specific tool

Here’s what a basic MCP server looks like in Python:

# A simple MCP server that provides calculator tools
from mcp import Server, types

server = Server("calculator")

@server.list_tools
async def list_tools():
    return [
        types.Tool(
            name="add",
            description="Add two numbers together",
            input_schema={
                "type": "object",
                "properties": {
                    "a": {"type": "number"},
                    "b": {"type": "number"}
                },
                "required": ["a", "b"]
            }
        )
    ]

@server.call_tool
async def call_tool(name, arguments):
    if name == "add":
        return arguments["a"] + arguments["b"]

Gotcha: MCP doesn’t handle authentication—that’s your responsibility. Your server must validate who’s calling before executing tools.

MCP Servers: The Bridge Between AI and Your Tools

Think of a restaurant kitchen. The waiter (AI) takes orders from customers (users). The kitchen (MCP server) receives those orders and prepares the actual food (runs your business logic). The waiter doesn’t need to know how to cook—they just need a standard way to communicate orders.

MCP Servers are applications that implement the MCP protocol. They sit between AI models and your actual tools, translating MCP requests into real operations.

How it works under the hood:

  1. The AI model sends an MCP request (JSON over WebSocket or HTTP)
  2. The server parses the request, validates it
  3. It calls your actual business logic
  4. Returns the result back as an MCP response
# An MCP server wrapping a user database API
from mcp import Server, types
from myapp import db  # Your internal database library

server = Server("user-service")

@server.list_tools
async def list_tools():
    return [
        types.Tool(
            name="get_user_by_email",
            description="Find a user by their email address",
            input_schema={
                "type": "object",
                "properties": {
                    "email": {"type": "string", "format": "email"}
                },
                "required": ["email"]
            }
        )
    ]

@server.call_tool
async def call_tool(name, arguments):
    if name == "get_user_by_email":
        user = db.find_user(arguments["email"])
        if not user:
            raise types.Error("User not found")
        return {"id": user.id, "name": user.name, "email": user.email}

Edge case: What if your internal API is slow? MCP servers should implement timeouts—a 30-second default is common. Your AI model can’t wait forever.

FastMCP: Making Server Creation Painless

Think of a scaffolding kit. Building an MCP server from scratch is like welding your own scaffolding—doable but tedious. FastMCP is like buying pre-built scaffolding that snaps together in minutes.

FastMCP is a Python library that greatly simplifies creating MCP servers. It handles protocol details, authentication, and deployment so you focus on your business logic.

How it works: You decorate Python functions, and FastMCP automatically:

  1. Generates tool schemas from your function signatures
  2. Sets up the MCP protocol handlers
  3. Manages connections and error handling
# FastMCP makes this dramatically simpler
from fastmcp import FastMCP

app = FastMCP("user-service")

@app.tool()
async def get_user_by_email(email: str) -> dict:
    """Find a user by their email address.
    
    Args:
        email: The user's email address
    Returns:
        User details as a dictionary
    """
    user = await db.find_user(email)
    if not user:
        raise ValueError("User not found")
    return {"id": user.id, "name": user.name, "email": user.email}

@app.tool()
async def list_users(department: str = None) -> list:
    """List all users, optionally filtered by department.
    
    Args:
        department: Optional department filter
    Returns:
        List of user dictionaries
    """
    users = await db.get_all_users()
    if department:
        users = [u for u in users if u.department == department]
    return [{"id": u.id, "name": u.name} for u in users]

Non-obvious insight: FastMCP automatically validates inputs against your type hints. Pass a string where an integer is expected? It returns a clear error before your function even runs. This saves you from writing validation boilerplate.

Tool Schemas: Your API’s Menu

Think of a restaurant menu. It tells you the dish name, description, and ingredients. A tool schema does the same—it tells the AI what a tool is called, what it does, and what data it needs.

Tool Schemas are JSON objects that describe a tool to an MCP client (usually an AI model). They define the tool’s name, description, input parameters (with types), and expected output.

{
  "name": "get_user_by_email",
  "description": "Find a user by their email address",
  "input_schema": {
    "type": "object",
    "properties": {
      "email": {
        "type": "string",
        "format": "email",
        "description": "The user's email address"
      }
    },
    "required": ["email"]
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "id": {"type": "integer"},
      "name": {"type": "string"},
      "email": {"type": "string"}
    }
  }
}

How it works: When an AI lists tools, it receives schemas. It then uses these schemas to decide which tool to call and how to format arguments. A well-written schema means better AI decisions.

Gotcha: Schema descriptions matter more than you think. “Find user by email” vs. “Retrieves user profile from internal HR database using email as identifier” — the second gives the AI context to use the tool appropriately.

Internal APIs vs. Unified Access Wrappers

Think of speaking different languages. Your HR system speaks French (REST API), your accounting system speaks Spanish (GraphQL), and your CRM speaks German (SOAP). Each requires a different translator.

Internal APIs are the actual backend services your company uses. They’re often inconsistent—different authentication, different data formats, different endpoints.

Unified Access Wrappers are layers on top that provide a consistent interface. They translate between the MCP standard and your internal APIs, hiding complexity.

# Unified Access Wrapper using FastMCP
from fastmcp import FastMCP
import httpx  # For HTTP calls

app = FastMCP("enterprise-bridge")

@app.tool()
async def get_employee_info(employee_id: str) -> dict:
    """Get employee information from HR system.
    
    Args:
        employee_id: Employee's ID number
    Returns:
        Employee details with name, department, role
    """
    # Wrapper translates MCP call to internal HR API
    async with httpx.AsyncClient() as client:
        response = await client.get(
            f"http://internal-hr/api/v1/employees/{employee_id}",
            headers={"Authorization": "Bearer HR_API_KEY"}
        )
        response.raise_for_status()
        data = response.json()
    # Transform internal format to consistent output
    return {
        "name": f"{data['firstName']} {data['lastName']}",
        "department": data.get("departmentName", "Unknown"),
        "role": data.get("jobTitle", "Unknown")
    }

@app.tool()
async def create_expense_report(employee_id: str, amount: float, category: str) -> str:
    """Create a new expense report in accounting system.
    
    Args:
        employee_id: Employee's ID
        amount: Expense amount
        category: Expense category (travel, supplies, etc.)
    Returns:
        Confirmation message with report ID
    """
    # Wrapper translates to accounting system's SOAP-like API
    xml_payload = f"""<?xml version="1.0"?>
        <expense>
            <employeeId>{employee_id}</employeeId>
            <amount>{amount}</amount>
            <category>{category}</category>
        </expense>"""
    
    async with httpx.AsyncClient() as client:
        response = await client.post(
            "http://internal-accounting/expenses",
            content=xml_payload,
            headers={"Content-Type": "application/xml"}
        )
        response.raise_for_status()
    return f"Expense report created with ID: {response.text}"

Non-obvious insight: Wrappers can also handle retry logic, caching, and rate limiting. Your HR API might go down occasionally—the wrapper should retry three times before telling the AI it failed.

How Everything Fits Together

Here’s how these concepts relate in a typical enterprise setup:

Concept Role Analogy
Model Context Protocol Communication standard Universal remote protocol
MCP Server Program that implements the protocol Kitchen that receives orders
FastMCP Library to build servers faster Pre-built scaffolding kit
Tool Schemas Descriptions of available operations Restaurant menu
Internal APIs Actual backend services Kitchen appliances
Unified Access Wrappers Translation layer between MCP and APIs Power adapter

A typical data flow: AI ModelMCP ProtocolFastMCP ServerTool Schema LookupUnified Access WrapperInternal API

Key Takeaways

  • Model Context Protocol standardizes how AI discovers and calls tools—like a universal remote for your enterprise systems
  • MCP Servers translate protocol requests into actual operations, handling validation and error management
  • FastMCP eliminates boilerplate by generating tool schemas from typed function signatures
  • Tool Schemas are JSON menus that tell AI what each tool does and needs
  • Internal APIs are your actual backend services—inconsistent and messy
  • Unified Access Wrappers hide that messiness behind a clean MCP interface, handling authentication, retries, and data transformation

You now have a mental model for building enterprise AI tools that actually work. Your next step: pick one internal API, build a FastMCP wrapper for it, and watch your AI assistant discover capabilities it never had before.