AI Coding AgentsLLM

FastMCP: Write Your Own MCP Server in Python (Linux, Windows, Mac)

Before you start

  • 21 min read

Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

What you get in one minute#

FastMCP is the Python framework for writing your own MCP server: you decorate ordinary functions with @mcp.tool, and it builds the tool definitions, checks the arguments and speaks the protocol, so Claude Code, Open WebUI and other AI clients can call your code.

FastMCP official logo

Logo: the official FastMCP project (github.com/PrefectHQ/fastmcp)

MCP (Model Context Protocol) is the standard way an AI client asks outside code to do something: read a file, query a database, call your API. You write the Python once and any MCP client can use it. This guide builds a small server, runs it over stdio and HTTP, connects it to Claude Code and Open WebUI, and protects it.

One thing to know first: the project moved. It used to live at github.com/jlowin/fastmcp; the maintained home is now github.com/PrefectHQ/fastmcp (the Prefect team), and the old address redirects there.

Current versionFastMCP 4.1.6 on PyPI (uploaded November 2026). FastMCP 4.0 became stable in August 2026
Time neededAbout 30 minutes (our estimate)
Size on diskNot published by the project; we did not measure it
PortsNone for stdio. HTTP uses 8000 and the path /mcp by default
LicenceApache 2.0
NeedsPython 3.10 or newer, and the uv tool (Step 1)
Official docsgofastmcp.com and github.com/PrefectHQ/fastmcp

At a glance:

  • What it is: a Python library plus a fastmcp command, not a program you run.
  • Two ways to connect: stdio (the AI client starts your script on the same computer) and HTTP (your script listens on a URL).
  • Authentication: off by default. Only HTTP can be protected; stdio relies on being local.
  • Works with: Claude Code, Open WebUI, Claude Desktop, Cursor, Gemini CLI and other MCP clients.
  • Status: alive and open source. Version 4.1.6 appeared in early November 2026, and the 3.x line still gets fixes.
  • Cost: free. Prefect also sells hosting (Horizon); you do not need it.

How it fits together#

Your Python file is the only thing you write. FastMCP turns it into an MCP server, and the transport you pick decides who can reach it. Dashed parts are optional.

Flow diagram: server.py with decorated functions goes into FastMCP, which serves either stdio, where Claude Code starts the script on the same computer, or HTTP at /mcp, which is protected by a bearer token or OAuth and can sit behind Caddy with HTTPS so that Claude Code or Open WebUI on other devices can connect

Drawn from the FastMCP running-server, HTTP deployment and authentication docs, November 2026.

Before you start#

You need Python 3.10 or newer (per PyPI) and uv, the package manager FastMCP's docs recommend; Step 1 installs it. Check Python first:

Run in terminalbash
python3 --version
Run in terminalpowershell
python --version

Checklist:

  • [ ] Python 3.10 or newer
  • [ ] A terminal and an empty folder for the project
  • [ ] For Step 5: Claude Code installed (claude --version prints a version)
  • [ ] For Step 8 with Open WebUI: an admin account (see our Open WebUI MCP guide)
  • [ ] For HTTPS: a domain pointing at your server and ports 80 and 443 free, or a home-only name

Tested on: not run by DIY Lab Hub. Every command and setting comes from the official FastMCP, uv, Claude Code, Open WebUI and Caddy docs, read in November 2026.

Step 1: Install uv#

uv creates the project, installs FastMCP and runs your server. It is a separate tool from FastMCP.

Linux#

Run in terminalbash
curl -LsSf https://astral.sh/uv/install.sh | sh

Windows (PowerShell)#

Run in terminalpowershell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Mac (Terminal)#

Same command as Linux, or use Homebrew:

Run in terminalbash
brew install uv

Check it worked. Open a new terminal and run uv --version. It prints a version number. If the command is not found, open another new terminal window first.

Step 2: Create the project and install FastMCP#

Pin the exact version: the docs say minor releases can contain breaking changes.

Linux#

Run in terminalbash
uv init fastmcp-demo
cd fastmcp-demo
uv add "fastmcp==4.1.6"

Windows (PowerShell)#

Run in terminalpowershell
uv init fastmcp-demo
cd fastmcp-demo
uv add "fastmcp==4.1.6"

Mac (Terminal)#

Same commands as Linux:

Run in terminalbash
uv init fastmcp-demo
cd fastmcp-demo
uv add "fastmcp==4.1.6"

Check it worked. Run uv run fastmcp version. The docs show output of this shape (their example, with an older number): lines for "FastMCP version", "MCP version", "Python version" and "Platform". Your FastMCP line should say 4.1.6.

Tip: If you use plain pip install fastmcp instead of uv, know this: the docs warn that one pip upgrade from FastMCP 3.2 or older to 3.3 or newer can leave import fastmcp broken. The fix is pip install --force-reinstall fastmcp. A fresh uv project avoids it.

Step 3: Write the server#

A server is a file with a FastMCP object and decorated functions. Type hints become the argument rules, and the docstring becomes the description the AI reads. This file has each kind of capability: two tools (actions), one resource (read-only data) and one prompt (a reusable question).

Linux#

Run in terminalbash
cat > server.py <<'EOF'
from fastmcp import FastMCP

mcp = FastMCP("Demo Server")


@mcp.tool
def add(a: int, b: int) -> int:
    """Add two whole numbers."""
    return a + b


@mcp.tool
def word_count(text: str) -> int:
    """Count the words in a piece of text."""
    return len(text.split())


@mcp.resource("resource://about")
def about() -> str:
    """What this server is."""
    return "A small demo server built with FastMCP."


@mcp.prompt
def summarize(text: str) -> str:
    """Ask the model for a three-bullet summary."""
    return f"Summarize this in three bullets: {text}"


if __name__ == "__main__":
    mcp.run()
EOF

Windows (PowerShell)#

Codepowershell
Set-Content -Path server.py -Encoding utf8 -Value @'
from fastmcp import FastMCP

mcp = FastMCP("Demo Server")


@mcp.tool
def add(a: int, b: int) -> int:
    """Add two whole numbers."""
    return a + b


@mcp.tool
def word_count(text: str) -> int:
    """Count the words in a piece of text."""
    return len(text.split())


@mcp.resource("resource://about")
def about() -> str:
    """What this server is."""
    return "A small demo server built with FastMCP."


@mcp.prompt
def summarize(text: str) -> str:
    """Ask the model for a three-bullet summary."""
    return f"Summarize this in three bullets: {text}"


if __name__ == "__main__":
    mcp.run()
'@

Mac (Terminal)#

Same as Linux (the cat > server.py <<'EOF' block).

Check it worked. uv run python -c "import server" prints nothing. A syntax error shows here instead of inside an AI client.

Tip: Give every tool a docstring and real type hints: the AI sees only the name, the docstring and the argument types. The docs also say functions with *args or **kwargs cannot be tools, because the protocol needs a complete argument list.

Step 4: Test it without any AI client#

Before you involve Claude, call the server with FastMCP's own command-line client; list and call accept the file itself.

Linux#

Run in terminalbash
uv run fastmcp list server.py
uv run fastmcp call server.py add a=2 b=3
uv run fastmcp call server.py word_count text="one two three"

Windows (PowerShell)#

Run in terminalpowershell
uv run fastmcp list server.py
uv run fastmcp call server.py add a=2 b=3
uv run fastmcp call server.py word_count text="one two three"

Mac (Terminal)#

Same commands as Linux.

Check it worked. list prints your tools as function signatures (add and word_count, with argument names and types), as the docs describe it. The call lines return 5 and 3: the docs say the CLI converts your text values to the types the tool expects.

Step 5: Connect it to Claude Code over stdio#

With stdio, Claude Code starts your script itself and talks to it through standard input and output. There is no port and no password. Right for tools only you use, on this computer.

Register it with claude mcp add. Everything after the -- is the command Claude Code runs. Use absolute paths, because Claude Code starts it from whatever folder you are in. The commands assume the project is in your home folder.

Linux#

Run in terminalbash
claude mcp add --transport stdio fastmcp-demo -- uv run --project "$HOME/fastmcp-demo" fastmcp run "$HOME/fastmcp-demo/server.py"

Windows (PowerShell)#

Run in terminalpowershell
claude mcp add --transport stdio fastmcp-demo -- uv run --project "$HOME\fastmcp-demo" fastmcp run "$HOME\fastmcp-demo\server.py"

Mac (Terminal)#

Same command as Linux.

Check it worked. Run claude mcp get fastmcp-demo; Claude Code's docs say it shows whether the server connected. Then start claude, type /mcp and look for fastmcp-demo. Ask: "Use the add tool to add 2 and 3." Claude asks permission, calls your function and answers 5.

Tip: FastMCP also has fastmcp install claude-code server.py. Its docs say it looks for the Claude Code program at the old default location, ~/.claude/local/claude, so the manual claude mcp add above is the safer route.

Stdio or HTTP: which one do you need?#

stdioHTTP (Streamable)
Who starts the serverThe AI client, per sessionYou, and it keeps running
Who can connectOnly that client on this computerAnyone who can reach the URL
PortNone8000 by default, path /mcp
AuthenticationNot used; inherits local securityOff by default; turn it on (Step 7)
Good forYour own tools in Claude Code or Claude DesktopA shared server, Open WebUI in a container, another computer
HTTPSNot neededNeeded once it leaves your computer

SSE, a third option, is legacy: the docs keep it only for old clients, and Claude Code marks it deprecated.

Step 6: Run the same server over HTTP#

You do not need to change the file: fastmcp run picks the transport. It binds to 127.0.0.1 by default, so only this computer can connect.

Linux#

Run in terminalbash
uv run fastmcp run server.py --transport http --port 8000

Windows (PowerShell)#

Run in terminalpowershell
uv run fastmcp run server.py --transport http --port 8000

Mac (Terminal)#

Same command as Linux.

Leave that terminal open. In a second terminal in the same project folder, check it. The server has no authentication yet, so pass --auth none: the docs say the CLI's default for HTTP is oauth.

Run in terminalbash
uv run fastmcp list http://127.0.0.1:8000/mcp --auth none

Check it worked. The same two tools appear. Stop the server with Ctrl-C in its terminal.

Tip: fastmcp run ignores the if __name__ == "__main__": block at the bottom of your file, so setup code placed there never runs. The docs' fix: put it in a function and point at it, for example fastmcp run server.py:create_server.

Step 7: Protect it with a token#

FastMCP's docs say a server starts with auth=None: anyone who can reach the HTTP port can use every tool. They call authentication "highly recommended" for remote servers, and some AI clients refuse an unprotected one.

For learning the pattern, the simplest protection is a static token. The docs are plain that StaticTokenVerifier keeps tokens as plain text and is for development and testing, never production. It suits a test on your own computer. For a server on the internet use an option from the next section.

First change server.py. Replace the line mcp = FastMCP("Demo Server") with this block. When MCP_TOKEN is not set (for example the stdio server from Step 5), the server starts as before.

Run in terminalpython
import os

from fastmcp.server.auth.providers.jwt import StaticTokenVerifier

auth = None
token = os.environ.get("MCP_TOKEN")
if token:
    auth = StaticTokenVerifier(
        tokens={token: {"client_id": "me", "scopes": ["read:data"]}},
        required_scopes=["read:data"],
    )

mcp = FastMCP("Demo Server", auth=auth)

Then create a random token, keep it out of git, start the server with it, and test from a second terminal (set MCP_TOKEN there the same way).

Linux#

Run in terminalbash
umask 077
openssl rand -hex 32 > token.txt
echo "token.txt" >> .gitignore
export MCP_TOKEN="$(cat token.txt)"
uv run fastmcp run server.py --transport http --port 8000

In the second terminal (run export MCP_TOKEN="$(cat token.txt)" there first):

Run in terminalbash
curl -i http://127.0.0.1:8000/mcp
uv run fastmcp list http://127.0.0.1:8000/mcp --auth "$MCP_TOKEN"

Windows (PowerShell)#

Run in terminalpowershell
function New-HexSecret { $b = New-Object byte[] 32; [Security.Cryptography.RandomNumberGenerator]::Fill($b); -join ($b | ForEach-Object { $_.ToString("x2") }) }
New-HexSecret | Set-Content -Encoding ascii token.txt
Add-Content .gitignore "token.txt"
$env:MCP_TOKEN = (Get-Content token.txt)
uv run fastmcp run server.py --transport http --port 8000

In the second terminal (run $env:MCP_TOKEN = (Get-Content token.txt) there first):

Run in terminalpowershell
curl.exe -i http://127.0.0.1:8000/mcp
uv run fastmcp list http://127.0.0.1:8000/mcp --auth $env:MCP_TOKEN

Mac (Terminal)#

Same commands as Linux (if openssl is missing, brew install openssl).

Check it worked. The request without a token must be refused; we expect an HTTP 401 status, but the docs do not print the output. The list with the token shows your tools. If the first request shows tools instead of an error, the token was not set in the server's terminal.

Tip: The docs say custom routes, such as a /health endpoint added with @mcp.custom_route, are never protected by the server's authentication. Use them only for harmless status checks.

Authentication for remote servers#

When the server leaves your computer, pick the row that matches what you already have. These are the classes in FastMCP's authentication docs.

You haveUseNotes from the docs
A login service that issues JWT tokensJWTVerifier with jwks_uri, issuer, audienceYour server only checks tokens; keys can rotate
Services you control, one shared secretJWTVerifier with algorithm="HS256", secret in public_keyA random secret of 32+ characters, never in git or logs
An identity provider with Dynamic Client Registration (WorkOS AuthKit, Descope)RemoteAuthProvider, e.g. AuthKitProviderClients register themselves
GitHub, Google, Azure, which need a fixed app registrationOAuthProxy and providers such as GitHubProviderRegister an app once; give client ID and secret
Only a quick testStaticTokenVerifier, DebugTokenVerifierDevelopment only

Clients send Authorization: Bearer <token>; FastMCP's own client takes Client(url, auth="<token>") without the word Bearer. If a gateway in front enforces authentication instead, the docs say you must stop anyone reaching the server directly.

Step 8: Connect Claude Code and Open WebUI over HTTP#

With the server from Step 7 running, add it to Claude Code as an HTTP server. The header carries the token.

Linux#

Run in terminalbash
claude mcp add --transport http fastmcp-demo-http http://127.0.0.1:8000/mcp --header "Authorization: Bearer $MCP_TOKEN"

Windows (PowerShell)#

Run in terminalpowershell
claude mcp add --transport http fastmcp-demo-http http://127.0.0.1:8000/mcp --header "Authorization: Bearer $env:MCP_TOKEN"

Mac (Terminal)#

Same command as Linux.

Check it worked. claude mcp get fastmcp-demo-http reports the connection, and /mcp lists the tools. Claude Code saves the header in its own settings, so treat that file like a password. Remove the test servers with claude mcp remove fastmcp-demo and claude mcp remove fastmcp-demo-http.

Open WebUI. It supports MCP servers that speak Streamable HTTP, which is what FastMCP's HTTP mode is. Our Open WebUI MCP guide has the full setup; in short, from Open WebUI's docs:

  1. Sign in as an admin (only admins can add MCP servers), open Settings, Admin, Integrations, and under External Tool Servers choose Add Connection.
  2. Set Type to MCP (Streamable HTTP), not OpenAPI.
  3. Enter the URL, for example http://host.docker.internal:8000/mcp when Open WebUI runs in a container and your server runs on the same machine (the docs say localhost inside a container means the container).
  4. For Auth choose Bearer and paste the token. The docs warn that Bearer with an empty key sends an empty header, and many servers reject it.
  5. Use a model that is good at tool calling; the docs say a weak model may ignore tools even when the connection works.

On Linux, a container cannot reach a server that listens only on 127.0.0.1. Start it with --host 0.0.0.0, keep the token on, and firewall the port. This is general Docker behaviour, not from the FastMCP or Open WebUI docs.

Put it behind HTTPS (with or without Traefik)#

Skip this while only you use the server on the same computer. Use it when other devices connect: the server speaks plain HTTP, so a token alone travels in clear text. Keep FastMCP on 127.0.0.1 and let a proxy on the same machine do HTTPS.

With Traefik#

FastMCP is a Python program, not a container, so there are no labels. If you already run Traefik, add a file-provider service that points to http://<this-computer>:8000 as in the Traefik file provider docs, and check that streamed answers arrive (we did not test Traefik with FastMCP). Otherwise use Caddy.

Without Traefik: Caddy#

Caddy gets and renews its own certificates. Its docs describe automatic Let's Encrypt certificates for a public domain (ports 80 and 443 must reach it) and tls internal for a local certificate at home. Caddy passes the original Host header through and flushes event-stream responses at once, which MCP streaming needs. Create a Caddyfile. For a public domain with a DNS record pointing at the server:

File contenttext
mcp.example.com {
    reverse_proxy localhost:8000
}

For a home-only name:

File contenttext
mcp.home.arpa {
    tls internal
    reverse_proxy localhost:8000
}

Our Caddy guide has the full walk-through, including trusting Caddy's local root certificate.

Linux#

Caddy's install page gives the official Debian and Ubuntu repository (other distributions are listed there):

Run in terminalbash
sudo apt install --yes debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
sudo chmod o+r /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy

The package runs Caddy as a service that reads /etc/caddy/Caddyfile. Put your Caddyfile there, then sudo systemctl reload caddy.

Windows (PowerShell)#

Download the Windows build from the Caddy releases (open "Assets"), then in the folder with your Caddyfile:

Run in terminalpowershell
caddy run --config Caddyfile

Mac (Terminal)#

Homebrew is a community-maintained route on Caddy's install page:

Run in terminalbash
brew install caddy
caddy run --config Caddyfile

For tls internal, every device must trust Caddy's local root certificate: caddy trust does it on the Caddy machine. Claude Code reads the operating system's certificate store (native installer), and its docs name NODE_EXTRA_CA_CERTS=/path/to/ca-cert.pem for extra roots. Optional hardening in FastMCP: pass host_origin_protection=True and allowed_hosts=["mcp.example.com"] to mcp.run(...). It is off by default, and if you turn it on without your public name in allowed_hosts, every request fails (see Common mistakes).

Check it worked. curl -I https://mcp.example.com/mcp returns an HTTP status line over HTTPS. The exact code is not documented; a certificate or connection error is the sign of a problem. Then add the server to Claude Code again with the https address.

Tip: With nginx instead, the docs call buffering "the most common issue" for FastMCP: set proxy_buffering off, proxy_http_version 1.1, an empty Connection header and a long proxy_read_timeout (the docs use 300 seconds), or progress updates and results never arrive. Caddy needs none of that.

Security and testing#

  • Bind to localhost. fastmcp run defaults to 127.0.0.1. Use --host 0.0.0.0 only with a token on and the port blocked from the internet.
  • Secrets in the environment, not in the file. Read them with os.environ, keep token.txt in .gitignore, and on Linux and Mac keep it private (umask 077, as in Step 7).
  • Tools run as you. Whatever a tool can do, the AI can do. Keep tools narrow, mark read-only ones with annotations={"readOnlyHint": True} (a documented option), and be careful with tools that run commands or touch files.
  • Test as a program. The testing docs use pytest with FastMCP's client (async with Client(transport=mcp) as client, then client.call_tool(...)), plus pytest-asyncio.
  • Status check. Add @mcp.custom_route("/health", methods=["GET"]) and curl it.

Common mistakes#

Each row comes from the FastMCP docs or a real issue (state as read in November 2026). The last row is about AI assistants.

MistakeWhy it happensDo this instead
print() in a stdio server, then nothing showsWith stdio, standard output is the protocol channel (maintainer's answer in issue #3278, closed February 2026)Log with await ctx.info(...), Python's logging, or print(..., file=sys.stderr)
A tool written with yield instead of returnSame issue #3278: an async generator does not run as the author expectedUse return
Setup code in the if __name__ == "__main__": blockfastmcp run and fastmcp dev inspector skip that block (docs warning)Put setup in a factory function: fastmcp run server.py:create_server
HTTP on 0.0.0.0 without authenticationThe default is auth=None, so anyone who can reach the port can call every toolAdd a verifier (Step 7), use HTTPS, firewall the port
StaticTokenVerifier for a real serviceEasy to copy; the docs say it keeps plain-text tokens and must not be used in productionJWTVerifier or an OAuth provider (table above)
Host check turned on behind a proxy, then 421 Misdirected Request on every callIssue #4468 (closed July 2026): a proxy that rewrites Host hides the public name; uvicorn's --proxy-headers does not helpPut your public name in allowed_hosts, or keep a proxy that passes the original Host (Caddy does)
OpenAPI instead of MCP in Open WebUI, or Bearer with an empty keyOpen WebUI's docs: MCP settings in an OpenAPI connection break the page; an empty Bearer key sends an empty headerType MCP (Streamable HTTP); None, or Bearer with the key filled in
Trusting an AI assistant's FastMCP exampleAssistants copy old tutorials: from mcp.server.fastmcp import FastMCP (the FastMCP 1.0 copy in the SDK), host and port inside FastMCP(...), ctx.sample() or ctx.list_roots() (removed in FastMCP 4), transport="sse" (legacy). Our assessment from the upgrade guide, not a measured testImport from fastmcp import FastMCP, pass host and port to mcp.run(transport="http", ...), check each call at gofastmcp.com, and give the assistant gofastmcp.com/llms.txt

Tip: After every change to a tool, run uv run fastmcp list server.py and one fastmcp call. It catches a broken signature before an AI client reports a vague "failed to connect".

Tip: When Claude Code shows "Failed to connect", read the detail line: claude mcp get <name> adds the HTTP status or error text. A wrong path in the stdio command and a missing token look different there.

Troubleshooting#

ProblemSolution
claude mcp add saves the server but it does not connectRun the command after the -- by hand in a terminal and fix the error; use absolute paths
Open WebUI says it failed to connectCheck the type (MCP, not OpenAPI), the URL (host.docker.internal from a container), and Auth (None, or Bearer with a key)
401 from your own serverToken missing or wrong; the client needs the same value the server was started with
Streaming results never arrive behind nginxproxy_buffering off and the other settings in the HTTPS section
A tool is missing from the listMissing decorator, unsaved file, or the function sits in the __main__ block
import fastmcp fails after a pip upgradepip install --force-reinstall fastmcp; if that fails, pip uninstall -y fastmcp fastmcp-slim, then install again

Exact error texts#

✘ Failed to connect is the status Claude Code shows for a server it could not reach; the detail line after it names the HTTP status or error code (Claude Code docs).

421 Misdirected Request on every call while /health works is the symptom reported in issue #4468.

Known bugs (open on the day we read them)#

  • #2837 (January 2026): a stdio server started by hand with python server.py needs several Ctrl-C presses and prints tracebacks; a commenter says the bare MCP SDK does the same. Claude Code starts and stops stdio servers for you, so it matters only when you test by hand.
  • #4192 (May 2026): on Windows, one reporter's HTTP server hung after about 12 sequential client sessions. Another tester could not reproduce it in runs of 30 and 110 sessions, so it may depend on the machine. If it freezes, restart it; there is no confirmed fix.

Updates and backups#

Update. Read the changelog first; releases come every few days, and 4.2.0 alphas already exist (pip and uv skip pre-releases unless you ask). Then change the pin:

Run in terminalbash
uv add "fastmcp==<NEW_VERSION>"
uv run fastmcp list server.py
Run in terminalpowershell
uv add "fastmcp==<NEW_VERSION>"
uv run fastmcp list server.py

From 3.x, FastMCP's upgrade guide says most servers run unchanged and lists the breaks.

Back up. FastMCP holds no data of its own. Keep the project folder (server.py, pyproject.toml, uv.lock) in git. The token is a secret, so keep a copy in your password manager instead. A dated archive without the virtual environment, on Linux and Mac:

Run in terminalbash
mkdir -p ~/backups
tar --exclude=.venv --exclude=token.txt -czf ~/backups/fastmcp-demo-$(date +%F).tar.gz -C ~ fastmcp-demo

On Windows PowerShell:

Codepowershell
New-Item -ItemType Directory -Force $HOME\backups | Out-Null
Get-ChildItem "$HOME\fastmcp-demo" -Exclude .venv,token.txt | Compress-Archive -DestinationPath "$HOME\backups\fastmcp-demo-$(Get-Date -Format yyyy-MM-dd).zip" -Force

Schedule it with cron at 03:00 on Linux and Mac, or Windows Task Scheduler.

Restore test. Extract the archive into a new folder, then run uv sync and uv run fastmcp list server.py there. If the tools list, the backup works.

Keep it running. The docs give a systemd service for Linux only (uvicorn on the ASGI app from mcp.http_app(), Restart=always). For Windows and Mac they give no recipe; use your system's own service tool.

Performance and resources#

  • No published figures. The FastMCP docs give no memory or speed numbers, and we measured none.
  • Several workers. The docs say you must turn on stateless mode (fastmcp run ... --stateless, or FASTMCP_STATELESS_HTTP=true) so any worker can answer any request.
  • Long tools. Proxies close idle connections; for tools that run for minutes the docs describe SSE polling with an event store.
  • Watch it. Use the /health route plus top, Activity Monitor or Task Manager.

FAQ#

Is FastMCP the same as the official MCP Python SDK? They are related. FastMCP 1.0 was folded into the official SDK in 2024; the standalone fastmcp package is the actively maintained, much larger framework, and FastMCP 4 is built on SDK version 2.

Do I need a GPU or Docker? No. It is an ordinary Python program, and the docs name no ready-made Docker image.

Can one server work with Claude Code and Open WebUI at once? Yes, in HTTP mode: both are clients of the same URL. stdio serves one client per started process.

Is the project still maintained? Yes. The repository is not archived, the newest release is from November 2026, and fixes also go to the 3.x line.

Sources#

Checked against the official docs on 2026-11-05.