# FastMCP Framework Comparison

## Overview

[FastMCP](https://github.com/PrefectHQ/fastmcp) (25K stars, Python) is the de facto MCP server framework for Python. It powers ~70% of MCP servers across all languages. Unlike our purpose-built server, FastMCP is a generic framework — developers build MCP servers using decorators and the framework handles protocol, transport, and lifecycle.

---

## How FastMCP works

1. **Decorator-based tool registration**: `@mcp.tool()` decorates Python functions to register them as MCP tools. The function signature (parameter names, types, docstring) is automatically converted to the tool schema. This is zero-schema-boilerplate — the framework infers everything from the function.

2. **Pydantic integration**: Input and output types are defined as Pydantic models. FastMCP auto-converts these to JSON Schema for the MCP protocol and validates inputs at runtime. This is the Python equivalent of our Zod schemas.

3. **Multi-transport out of the box**: stdio, SSE (deprecated), and Streamable HTTP are all built in. The server runs on any transport with a single configuration change.

4. **Async-first**: All tool handlers are async. The framework manages the event loop and concurrency.

5. **`context.report_progress()`**: Progress reporting is built into the tool handler context, not a separate notification system. This is more ergonomic than our `sendProgress` helper.

---

## How our system differs

| Aspect                | FastMCP                         | Coda MCP Server                           |
| --------------------- | ------------------------------- | ----------------------------------------- |
| **Purpose**           | Generic MCP framework           | Purpose-built proxy server                |
| **Language**          | Python                          | TypeScript                                |
| **Schema format**     | Pydantic models (auto-inferred) | Zod v4 (hand-authored with `.describe()`) |
| **Tool registration** | `@mcp.tool()` decorator         | Static `TOOL_DEFINITIONS` array           |
| **Transport**         | stdio + SSE + Streamable HTTP   | stdio only                                |
| **Execution**         | In-process functions            | HTTP proxy to Express API                 |
| **Concurrency**       | asyncio (built-in)              | Semaphore(5) from `@coda/async`           |
| **OTel**              | Native instrumentation          | Custom `TelemetryProvider`                |
| **Progress**          | `context.report_progress()`     | `sendProgress()` helper                   |
| **Users**             | ~70% of MCP servers             | Single internal server                    |

---

## What we are NOT adopting and why

1. **Decorator-based registration**: Our tools are proxied to an Express API, not executed in-process. Decorators make sense for in-process tool handlers where the function _is_ the tool. Our `ToolDefinition` data structures are already the "schema" — the handler is on the Express side.

2. **FastMCP as a dependency**: FastMCP is Python-only. Our server is TypeScript. The equivalent TypeScript framework is the MCP SDK itself, which we already use.

3. **Auto-inferred schemas**: Our tool descriptions carry business domain context (`.describe()` chains, `hint` fields, `examples` arrays) that cannot be auto-inferred from function signatures. Hand-authored schemas give us control over the AI's understanding of each parameter.

---

## What we are adopting

### 1. Native OTel instrumentation pattern

FastMCP has built-in OTel instrumentation that emits spans for every tool call with standardized attribute names. Our `telemetry.ts` follows the same pattern — per-tool spans with `mcp.tool.name` attributes. FastMCP validates that built-in OTel is the right approach (vs. relying on external instrumentation).

### 2. Progress as part of the handler context

FastMCP passes progress reporting via the handler context (`context.report_progress(current, total)`), making it ergonomic. Our pattern of threading `extra` through `handleToolCall` for progress is more manual but achieves the same result. If we refactor, the context-based approach is cleaner.

---

## Key insight: framework vs. purpose-built

FastMCP's adoption (~70% of MCP servers) is driven by developer experience — decorators, auto-inferred schemas, multi-transport, and zero boilerplate. This is the right approach for the general case. Our purpose-built proxy is the right approach for our specific case (existing Express API, existing auth, existing tools). We would use FastMCP if building a Python MCP server from scratch. We don't because we're adapting an existing system.

---

## References

- [PrefectHQ/fastmcp](https://github.com/PrefectHQ/fastmcp) (GitHub)
- FastMCP docs: "The fast, Pythonic way to build MCP servers"

---

_Comparison conducted 2026-05-10._
