Skip to content
Merged
129 changes: 129 additions & 0 deletions docs/clients/generate-cli.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
title: Generate CLI
sidebarTitle: Generate CLI
description: Turn any MCP server into a standalone, typed command-line tool.
icon: wand-magic-sparkles
---

import { VersionBadge } from '/snippets/version-badge.mdx'

<VersionBadge version="3.0.0" />

`fastmcp list` and `fastmcp call` let you poke at a server interactively, but they're developer tools — you always have to spell out the server spec, the tool name, and the arguments. `fastmcp generate-cli` takes the next step: it connects to a server, reads its schemas, and writes a standalone Python script where every tool is a proper subcommand with typed flags, help text, and tab completion. The result is a CLI that feels like it was hand-written for that specific server.

The key insight is that MCP tool schemas already contain everything a CLI framework needs: parameter names, types, descriptions, required/optional status, and defaults. `generate-cli` maps that schema into [cyclopts](https://cyclopts.readthedocs.io/) commands, so JSON Schema types become Python type annotations, descriptions become `--help` text, and required parameters become mandatory flags.

## Generating a Script

Point the command at any server spec — URLs, Python files, discovered server names, MCPConfig JSON — and it writes a CLI script:

```bash
fastmcp generate-cli weather
fastmcp generate-cli http://localhost:8000/mcp
fastmcp generate-cli server.py my_weather_cli.py
```

The second positional argument sets the output path. When omitted, it defaults to `cli.py`. If the file already exists, the command refuses to overwrite unless you pass `-f`:

```bash
fastmcp generate-cli weather -f
fastmcp generate-cli weather my_cli.py -f
```

Name-based resolution works here too, so if you have a server configured in Claude Desktop, Cursor, or any other supported editor, you can reference it by name. Run [`fastmcp discover`](/clients/cli#discovering-configured-servers) to see what's available.

```bash
fastmcp generate-cli claude-code:my-server output.py
```

The `--timeout` and `--auth` flags work the same way they do in `fastmcp list` and `fastmcp call`.

## What You Get

The generated script is a regular Python file — executable, editable, and yours. Here's what it looks like in practice:

```
$ python cli.py --help
Usage: weather-cli COMMAND

CLI for weather MCP server

Commands:
call-tool Call a tool on the server
list-tools List available tools.
list-resources List available resources.
read-resource Read a resource by URI.
list-prompts List available prompts.
get-prompt Get a prompt by name. Pass arguments as key=value pairs.
```

The `call-tool` subcommand is where the generated code lives. Each tool on the server becomes its own command:

```
$ python cli.py call-tool --help
Usage: weather-cli call-tool COMMAND

Call a tool on the server

Commands:
get_forecast Get the weather forecast for a city.
search_city Search for a city by name.
```

And each tool has typed parameters with help text pulled directly from the server's schema:

```
$ python cli.py call-tool get_forecast --help
Usage: weather-cli call-tool get_forecast [OPTIONS]

Get the weather forecast for a city.

Options:
--city [str] City name (required)
--days [int] Number of forecast days (default: 3)
```

Tool names are preserved exactly as the server defines them — underscores stay as underscores, so `call-tool get_forecast` matches what the server expects.

## How It Works

The generated script is a client, not a server. It doesn't bundle or embed the MCP server — it connects to it on every invocation. For URL-based servers, the server needs to be running. For stdio-based servers, the command specified in `CLIENT_SPEC` must be available on the system's `PATH`.

At the top of the generated file, a `CLIENT_SPEC` variable holds the resolved transport: either a URL string or a `StdioTransport` with the command and arguments baked in. Every invocation connects through this spec, so the script works without any external configuration.

### Parameter Handling

Parameters are mapped intelligently based on their complexity:

**Simple types** (`string`, `integer`, `number`, `boolean`) become typed Python parameters with clean flags:
```bash
python cli.py call-tool get_forecast --city London --days 3
```

**Arrays of simple types** (`array` with `string`/`integer`/`number`/`boolean` items) become `list[T]` parameters that accept multiple flags:
```bash
python cli.py call-tool tag_items --tags python --tags fastapi --tags mcp
```

**Complex types** (objects, nested arrays, or unions) accept JSON strings. The tool's `--help` displays the full JSON schema so you know exactly what structure to pass:
```bash
python cli.py call-tool create_user \
--name John \
--metadata '{"role": "admin", "dept": "engineering"}'
```

Required parameters are mandatory flags; optional ones default to their schema default or `None`. Empty values are filtered out before calling the server.

Beyond tool commands, the script includes generic commands that work regardless of what the server exposes: `list-tools`, `list-resources`, `read-resource`, `list-prompts`, and `get-prompt`. These connect to the server at runtime, so they always reflect the server's current state even if the tools have changed since generation.

## Editing the Output

The most common edit is changing `CLIENT_SPEC`. If you generated from a local dev server and want to point at production, just change the string. If you generated from a discovered name and want to pin the transport, replace it with an explicit URL or `StdioTransport`.

Beyond that, it's a regular Python file. You can add commands, change the output formatting, integrate it into a larger application, or strip out the parts you don't need. The helper functions (`_call_tool`, `_print_tool_result`) are thin wrappers around `fastmcp.Client` that are easy to adapt.

The generated script requires `fastmcp` as a dependency. If the script lives outside a project that already has fastmcp installed, `uv run` is the easiest way to run it without permanent installation:

```bash
uv run --with fastmcp python cli.py call-tool get_forecast --city London
```
1 change: 1 addition & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@
"pages": [
"clients/client",
"clients/cli",
"clients/generate-cli",
"clients/transports",
{
"group": "Core Operations",
Expand Down
2 changes: 2 additions & 0 deletions src/fastmcp/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import fastmcp
from fastmcp.cli import run as run_module
from fastmcp.cli.client import call_command, discover_command, list_command
from fastmcp.cli.generate import generate_cli_command
from fastmcp.cli.install import install_app
from fastmcp.cli.tasks import tasks_app
from fastmcp.utilities.cli import is_already_in_uv_subprocess, load_and_merge_config
Expand Down Expand Up @@ -957,6 +958,7 @@ async def prepare(
app.command(list_command, name="list")
app.command(call_command, name="call")
app.command(discover_command, name="discover")
app.command(generate_cli_command, name="generate-cli")


if __name__ == "__main__":
Expand Down
Loading
Loading