DEV Community

Cover image for FastMCP - A Smarter Way to Build LLM Toolchains
Developer Service
Developer Service

Posted on • Originally published at developer-service.blog

FastMCP - A Smarter Way to Build LLM Toolchains

Working with LLMs often starts with excitement and quickly turns into frustration.

You wire together a few tools, throw in some prompts, maybe add LangChain or OpenAI function calling, and it kind of works. Until it doesn’t.

You tweak a prompt and suddenly the agent can’t find the right tool. You add a new function and now you’re debugging JSON schema errors from a black box.

You try to test locally and realize… you can’t. Not easily, not meaningfully.

The problems pile up fast:

  • Prompt chains are fragile and impossible to validate.
  • OpenAI tool calling is great, until you need flexibility.
  • LangChain does everything, but it’s heavy, slow, and hard to reason about.
  • Local-first development? Forget it.

What should be a clean loop, define tools → expose them → test the agent → ship, turns into duct tape and guesswork.

“I just want to define some tools, wire up an agent, and test the damn thing.”

FastMCP was built to solve exactly this. No orchestration glue. No hidden layers. Just Pythonic, testable, LLM-safe tools that behave the way you expect, because you wrote them that way.


What is FastMCP?

FastMCP Homepage

FastMCP is a Python-first framework for building LLM-safe APIs, a structured way to expose tools, prompts, and resources to language models.

It’s the reference implementation of the Model-Context Protocol (MCP), a protocol designed to make LLM-agent interactions more predictable, inspectable, and testable.

Created by Jonathan Lowin, CEO at prefect.io, FastMCP is what happens when you take OpenAI’s tool calling seriously, and reimagine it for real-world developers.

At the heart of FastMCP is a simple but powerful idea:

Everything is a tool.

Whether it’s a function, a prompt, a resource, or an agent call, it’s treated as a typed, callable object with clear inputs and outputs.

That means:

  • It’s introspectable by LLMs (via JSON schemas or natural-language docs).
  • It’s testable by you (in memory, without a server).
  • It’s just Python (no decorators that swallow errors, no mysterious “chains”).

FastMCP doesn’t replace your model or your frontend, it replaces the glue layer between LLMs and the real world.


How FastMCP Adds Value

FastMCP isn’t trying to be everything. It’s trying to be the thing that actually works, a developer-first, LLM-native interface layer that doesn’t fight you.

Here’s what makes it stand out:

In-Memory Testing

You don’t need a running server or an OpenAI key to test your tools.

With Client(mcp), you can call any tool (or entire toolchain) directly in Python, just like a unit test.

No more “vibe testing” agents in production. You can validate logic locally, fast.

client = Client("server.py") ... result = await client.call_tool("greet", {"name": name}) 
Enter fullscreen mode Exit fullscreen mode

Typed Tool Interfaces

Every tool is defined with standard Python type hints.

FastMCP converts these into structured schemas that LLMs can understand, and that developers can trust.

You’ll never wonder what a tool expects or returns. It’s all explicit and introspectable.

@mcp.tool def greet(name: str) -> str:     ... 
Enter fullscreen mode Exit fullscreen mode

No Boilerplate

FastMCP includes everything you need to expose a tool: type handling, JSON schemas, OpenAI tool formatting, and even a local FastAPI server (if you want one).

There’s no wiring, no adapters, no glue code. Just tools.

Composable and Modular

Tools can call other tools. Resources can be tagged and routed.

You can filter, wrap, log, override, or proxy any part of your system using middleware.

It’s like functional programming for LLM APIs—clean, composable, and testable.

LLM-Ready by Design

Whether you’re using OpenAI, Claude, or a local model, FastMCP outputs structured tool definitions, JSON-callable messages, and clear tool schemas.

LLMs don’t just “see” your tools, they understand how to use them.

In short, FastMCP gives you the benefits of a framework, structure, clarity, and speed, without the weight. It’s a developer-first toolkit for anyone building LLM-powered systems in production.


Quick Code Examples

Here are some code examples to give you an idea on how quick and simple is to create AI powered tools with FastMCP.

Before creating any of this examples, first install the FastMCP package:

pip install fastmcp 
Enter fullscreen mode Exit fullscreen mode

Example 1: Hello, FastMCP

In this first example, let's build a simple MCP server that exposes a tool that greats the user.

In a server.py file:

from fastmcp import FastMCP # Create a new MCP server mcp = FastMCP("My MCP Server") # Create a new tool @mcp.tool def greet(name: str) -> str: return f"Hello, {name}!" # Run the server if __name__ == "__main__": mcp.run() 
Enter fullscreen mode Exit fullscreen mode

This code sets up a simple FastMCP server with one tool, greet, which returns a personalized greeting. The tool is typed, LLM-safe, and exposed via a local server with minimal setup using mcp.run().

Now, we build the client in a client.py file:

import asyncio from fastmcp import Client # Create a new client client = Client("server.py") # Call the tool async def call_tool(name: str): async with client: result = await client.call_tool("greet", {"name": name}) print(result) # Run the client asynchronously asyncio.run(call_tool("Ford")) 
Enter fullscreen mode Exit fullscreen mode

This code creates an asynchronous FastMCP client that connects to a local MCP server (server.py) and calls the greet tool with the input "Ford". It uses asyncio to run the request, and prints the result returned by the tool, demonstrating how to interact with MCP tools programmatically from a client.

Let's run the example:

$ python client.py CallToolResult(content=[TextContent(type='text', text='Hello, Ford!', annotations=None, meta=None)], structured_content={'result': 'Hello, Ford!'}, data='Hello, Ford!', is_error=False) 
Enter fullscreen mode Exit fullscreen mode

As you can see, we were able to test the greet tool effortlessly by running the test client script.

The FastMCP client automatically locates and connects to the specified server module (server.py), spins up the server if it's not already running, and seamlessly executes the registered tool with the given parameters.

This streamlined testing flow, doesn't requiring extra setup, mocks, or external API calls, which makes it incredibly easy to validate tool behaviour locally, debug workflows, and iterate quickly during development.

Example 2: Toolchain Composition

Let’s say you’re building a set of tools for an LLM to:

  • look up a product by ID,
  • calculate its discounted price,
  • and format a final message.

FastMCP lets you model this with clear, testable tools. File server_composition.py:

from fastmcp import FastMCP # Create a new MCP server mcp = FastMCP("PricingTools") # Step 1: Fake product lookup @mcp.tool() def get_product(product_id: int) -> dict: return {"id": product_id, "name": "Headphones", "price": 120.0} # Step 2: Discount calculator @mcp.tool() def apply_discount(price: float, percent: float = 10.0) -> float: return round(price * (1 - percent / 100), 2) # Step 3: Formatter @mcp.tool() def format_message(name: str, final_price: float) -> str: return f"The product '{name}' is available for ${final_price}." # Run the server if __name__ == "__main__": mcp.run() 
Enter fullscreen mode Exit fullscreen mode

Now, use FastMCP’s in-memory client to simulate an agent calling these tools. File client_composition.py:

import asyncio from fastmcp import Client # Create a new client client = Client("server_composition.py") # Call the tool async def call_tools(): async with client: # Get the product  product = await client.call_tool("get_product", {"product_id": 42}) product = product.data # Apply the discount  discounted = await client.call_tool("apply_discount", {"price": product["price"], "percent": 15}) discounted = discounted.data # Format the message  message = await client.call_tool("format_message", {"name": product["name"], "final_price": discounted}) message = message.data print(message) # Run the client asynchronously asyncio.run(call_tools()) 
Enter fullscreen mode Exit fullscreen mode

Running the client:

$ python client_composition.py The product 'Headphones' is available for $102.0. 
Enter fullscreen mode Exit fullscreen mode

Why is this composition capability of build->test important:

  • You can run this exact code in tests, in notebooks, or inside a prompt-engineered LLM agent.
  • You didn’t have to spin up a server, define API schemas, or write glue code.
  • The LLM sees these tools as structured metadata it can reason about.

You can find all the source code at: https://github.com/nunombispo/FastMCP-Intro-Article


Beyond Tools: What Else Can You Build?

FastMCP starts with tools, but doesn’t stop there.

The same clean, type-safe design extends to other core building blocks of LLM apps:

Resources

Need your agent to fetch an image, a document, or a video?

FastMCP supports typed resource URIs like image://thumbnail_123 or json://config/global.

Behind the scenes, these map to Python objects or fetch logic you define. LLMs get structured metadata, and you get full control over loading and formatting.

Prompts (LLM-as-a-function)

You can define prompts as first-class tools, with input parameters, output types, and natural fallback logic.

FastMCP turns these into callable functions that use LLMs under the hood.

@mcp.prompt() def generate_title(topic: str) -> str: """Write a clear and compelling article title.""" 
Enter fullscreen mode Exit fullscreen mode

It’s a prompt you can version, test, and treat like any other tool.

Middleware

FastMCP includes a clean middleware layer that lets you:

  • Intercept tool calls
  • Log usage or modify inputs/outputs
  • Add observability, auth, rate limiting, or tracing

You can even tag tools, rewrite them, or short-circuit calls dynamically—without touching the tool logic itself.

Native Support for Images, JSON, and Files

Need to send an image or structured JSON to a model?

FastMCP handles serialization for common data types out of the box. Just return the object, the framework takes care of the rest.

Together, these features let you build not just tools, but full agent ecosystems, structured, debuggable, and ready to evolve.


Final Thoughts

The hype around LLMs has produced a sea of tooling, some bloated, some brittle, but mostly opaque.

FastMCP is different. It’s small, fast, Pythonic, and built to help you ship real systems without sacrificing testability, clarity, or control.

If you’re tired of duct-taping prompts to APIs, or fighting to debug tool calls that only fail in prod, then this is the framework that gets out of your way and lets you build.

Build better LLM workflows. Ship faster. Sleep easier.


Follow me on Twitter: https://twitter.com/DevAsService

Follow me on Instagram: https://www.instagram.com/devasservice/

Follow me on TikTok: https://www.tiktok.com/@devasservice

Follow me on YouTube: https://www.youtube.com/@DevAsService

Top comments (0)