mcp-servers-cli
Inspect, call and drive any MCP server from the command line. Point it at a local process, a
remote HTTP endpoint, or an entry in a claude_desktop_config.json-style file, and it lists the
tools, resources and prompts the server exposes — then lets you exercise them.
Installation
Run it without installing anything, with uv:
uvx mcp-servers-cli inspect --stdio "uvx mcp-server-fetch"
Or install it once, with the Anthropic backend if you need it:
uv tool install mcp-servers-cli
uv tool install 'mcp-servers-cli[anthropic]'
From a clone, for development:
uv sync --all-groups
uv run mcp-servers-cli --help
Commands
Every command takes exactly one target: --stdio, --http or --config with --server.
# What does this server expose?
mcp-servers-cli inspect --stdio "uv run server.py"
mcp-servers-cli inspect --http https://example.com/mcp
mcp-servers-cli inspect --config config.json --server fetch
# Call one tool
mcp-servers-cli call fetch '{"url": "https://example.com"}' --stdio "uvx mcp-server-fetch"
# Read one resource
mcp-servers-cli read "tasks://stats" --stdio "uv run server.py"
# Inspect, then stay interactive
mcp-servers-cli repl --stdio "uv run server.py"
# Let a model use the tools to answer
mcp-servers-cli agent "What is 2 + 3?" --model qwen3.5:4b --stdio "uv run server.py"
Inside the REPL: call <tool> <json>, read <uri>, list, quit.
Tool results are printed as JSON from their structured content when the tool declares one, so
a tool returning a list prints that list, not MCP's {"result": ...} wrapper. Resource
contents and text results that hold JSON are printed as that JSON. inspect lists resource
templates such as report://{path} apart from fixed resources.
Agent
agent hands the server's tools to a model and lets it call them until it answers in text. Each
call is printed as it happens, then the answer:
-> add {"a": 2, "b": 3}
<- add ok (0.1s)
The sum is 5.
--modelis required: small local models can mishandle nested arguments, and a silent default would hide that behind a plausible failure.--backend ollama(default) talks to the server named byOLLAMA_HOST, localhost otherwise.--backend anthropicneeds the extra,uvx --from 'mcp-servers-cli[anthropic]' mcp-servers-cli, and readsANTHROPIC_API_KEYfrom the environment.- A failing tool, an unknown tool name or an invented argument does not stop the run: the model reads the error or gets the cleaned call, and can correct itself.
--max-steps(default 10) bounds the model turns; running out is an error, not a silent stop.--dry-runasks the model once and prints the calls it would make, running none: a safe first look at a server whose tools write or delete.--trace run.jsonlwrites one JSON object per executed call (time, tool, arguments, duration, error flag, a 500-character excerpt of the result) and a closingendline with the model turns, the number of calls and of failed calls, and the total duration.- When a tool call failed, a warning follows the answer on stderr: a model can answer as if its calls had worked (see below).
A trace reads with any JSON tool, for instance the slowest calls first:
jq -r 'select(.event == "tool") | "\(.duration_ms) ms \(.tool)"' run.jsonl | sort -rn
Arguments and results are written as they are: keep traces out of version control when a server handles secrets.
Model requirements
Measured on 24 September 2026 against mcpserver-template (in-memory backend), three runs per
model, with one prompt: add three tasks, complete one, list the pending ones. It takes a string
argument, an integer read from an earlier result, and a nested object (filter_tasks).
| Model | Correct answers | Tool calls | Failed calls | Model turns |
|---|---|---|---|---|
qwen3.5:4b-mlx |
3 / 3 | 5 | 0 | 4 |
llama3.2:3b |
0 / 3 | 1 to 3 | 1 to 2 | 2 |
llama3.2:3b failed every run. Its first planned call was filter_tasks with invented fields,
before any task existed. In the run examined in detail, that filter was rejected and
complete_task targeted a task that did not exist; yet each of the three answers described the
work as done. That is the failure to watch for with small models: not a crash, but a fluent and
false answer. Read the <- lines, or the warning printed after the answer, before trusting it.
qwen3.5:4b-mlx sent the three add_task calls in one turn. Twice it filtered on the server
({"status": "pending"}); once it fetched every task and filtered the result itself. Both gave
the right answer.
Unknown top-level arguments are dropped before a call; an invented field inside a nested object
is left to fail. Dropping title from a mistaken filter would widen it to every task and return
a wrong answer that looks right.
Client capabilities
MCP lets a server ask its client for three things during a call: a completion from the client's
model (sampling), an answer from the user (elicitation), and the directories it may work in
(roots). mcp-servers-cli declares none of them. A server that asks gets a one-line refusal,
Sampling not supported, Elicitation not supported or List roots not supported: call
prints it as an error, agent hands it to the model as a failed call.
FastMCP 4 negotiates the 2026-07-28 protocol revision by default. On such a connection a server no longer sends these requests itself: its tool returns an input-required result (SEP-2322) listing what it needs, and the client answers before the tool runs again. The refusals above are what such a server receives.
Handlers will come with the first server of this portfolio that needs one: sampling bridged to
the agent backend, roots from the command line. Elicitation needs someone at the keyboard,
which agent does not assume.
Configuring the server you launch
A stdio server runs as a subprocess, and the MCP SDK forwards only a whitelist of environment
variables to it — HOME, LOGNAME, PATH, SHELL, TERM, USER. Anything else the server
reads from its environment is silently absent, and it falls back to its defaults.
--env is how you pass the rest:
mcp-servers-cli call add_task '{"title": "buy milk"}' \
--env TASK_BACKEND=sqlite --env DB_PATH=tasks.db \
--stdio "uv run --directory ../mcpserver-template mcpserver-template"
~ and $VARS are expanded in the command and in every argument, including entries read from
a config file.
For a remote server, MCP_TOKEN is sent as a bearer token when set.
Noisy servers
A stdio server writes its own logs to stderr, and they land in your terminal. Servers built on older MCP SDKs answer FastMCP's capability probe with a wall of validation errors before falling back to the legacy protocol — the inspection still succeeds, but the output is buried.
--quiet discards that stream. It is not the default on purpose: when a server fails to start,
the reason is in its first stderr line, and hiding it turns a clear error into a bare
"Connection closed".
Errors
A failure prints one line naming its cause and exits with status 1:
error: Client failed to connect: [Errno 2] No such file or directory: 'uvx'
When a stdio server dies while starting, its own stderr line comes first and the error line points
to it. Set MCP_SERVERS_CLI_DEBUG=1 to get the full traceback instead.
Configuration file
The --config mode reads the mcpServers format used by Claude Desktop:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "${HOME}/Developer"]
},
"fetch": { "command": "uvx", "args": ["mcp-server-fetch"] },
"remote": { "url": "https://example.com/mcp" }
}
}
Project structure
src/mcp_servers_cli/
├── transports.py one builder per transport, plus path expansion
├── inspection.py reads a server into dataclasses
├── rendering.py turns inspection data, results and agent progress into output
├── repl.py interactive loop over a connected client
├── errors.py turns a failure into one line
├── llm.py provider-neutral conversation model and the LLMBackend protocol
├── backends/ one module per provider, translating to and from that model
├── agent.py the tool loop: model turns and tool calls, printing nothing
├── trace.py JSON Lines record of an agent run
└── cli.py cyclopts commands
Inspection returns data and never prints; rendering never talks to a server. That is what lets the tests run an in-memory FastMCP server and assert on structures rather than on captured stdout.
Adding a transport means adding a builder in transports.py and a target option in cli.py,
without touching the existing ones.
Only backends/ imports an LLM SDK; everything else works on the neutral types of llm.py.
Adding a provider means adding one module there and one line in backends/__init__.py.
Tests
uv run pytest
No network and no LLM. The inspection and agent-loop tests run against an in-memory FastMCP
server with a scripted model; the transport tests check expansion rules; the rendering tests
capture a rich console; the backend tests translate real SDK objects through a stand-in client;
the trace tests write to a temporary directory.
Two kinds of test start a process: the error tests launch a command that does not exist, and the
agent command is tested end to end against tests/fixtures/add_server.py over stdio.
Dependencies
| Package | Role |
|---|---|
fastmcp |
MCP client and transports |
cyclopts |
Commands and help, from type hints |
rich |
Tables and JSON highlighting |
ollama |
Local models, the default backend |
anthropic |
Anthropic backend, optional: mcp-servers-cli[anthropic] |
cyclopts and rich already ship in FastMCP's dependency tree; they are declared explicitly
rather than relied on transitively.
Metadata
Release files for mcp-servers-cli 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mcp_servers_cli-0.1.1.tar.gz | 112.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mcp_servers_cli-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 135.4 kB
Release files / mcp_servers_cli-0.1.1.tar.gz
| Download URL | mcp_servers_cli-0.1.1.tar.gz |
|---|---|
| Size | 112.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4cb95320dd52744d64e9024508d7c7764640a97c8132c3909cc670501f3de6d5
|
|
BLAKE2b-256 checksum How to use checksums |
73369706984f815e58a4e992b0535be937bbcaea46d1e464927946345bc4c71e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / mcp_servers_cli-0.1.1-py3-none-any.whl
| Download URL | mcp_servers_cli-0.1.1-py3-none-any.whl |
|---|---|
| Size | 23.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7bbe563c285883e974cbc299e24ddfe5fa02e0c4554d308b5e321344d524ab0f
|
|
BLAKE2b-256 checksum How to use checksums |
da0c4a2331b4aefbfa70768ced0362db91b6e4a675b5c8cf14816bb91c36c49c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|