mcp-servers-cli 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_servers_cli-0.1.0/.github/dependabot.yml +13 -0
- mcp_servers_cli-0.1.0/.github/workflows/ci.yml +21 -0
- mcp_servers_cli-0.1.0/.github/workflows/release.yml +27 -0
- mcp_servers_cli-0.1.0/.gitignore +226 -0
- mcp_servers_cli-0.1.0/.pre-commit-config.yaml +22 -0
- mcp_servers_cli-0.1.0/.python-version +1 -0
- mcp_servers_cli-0.1.0/LICENSE +21 -0
- mcp_servers_cli-0.1.0/PKG-INFO +263 -0
- mcp_servers_cli-0.1.0/README.md +239 -0
- mcp_servers_cli-0.1.0/config.json +15 -0
- mcp_servers_cli-0.1.0/pyproject.toml +48 -0
- mcp_servers_cli-0.1.0/ruff.toml +5 -0
- mcp_servers_cli-0.1.0/src/mcp_servers_cli/__init__.py +0 -0
- mcp_servers_cli-0.1.0/src/mcp_servers_cli/agent.py +136 -0
- mcp_servers_cli-0.1.0/src/mcp_servers_cli/backends/__init__.py +28 -0
- mcp_servers_cli-0.1.0/src/mcp_servers_cli/backends/anthropic.py +86 -0
- mcp_servers_cli-0.1.0/src/mcp_servers_cli/backends/ollama.py +94 -0
- mcp_servers_cli-0.1.0/src/mcp_servers_cli/cli.py +233 -0
- mcp_servers_cli-0.1.0/src/mcp_servers_cli/errors.py +30 -0
- mcp_servers_cli-0.1.0/src/mcp_servers_cli/inspection.py +108 -0
- mcp_servers_cli-0.1.0/src/mcp_servers_cli/llm.py +51 -0
- mcp_servers_cli-0.1.0/src/mcp_servers_cli/rendering.py +108 -0
- mcp_servers_cli-0.1.0/src/mcp_servers_cli/repl.py +55 -0
- mcp_servers_cli-0.1.0/src/mcp_servers_cli/trace.py +103 -0
- mcp_servers_cli-0.1.0/src/mcp_servers_cli/transports.py +79 -0
- mcp_servers_cli-0.1.0/tests/fixtures/add_server.py +15 -0
- mcp_servers_cli-0.1.0/tests/test_agent.py +186 -0
- mcp_servers_cli-0.1.0/tests/test_backend_anthropic.py +91 -0
- mcp_servers_cli-0.1.0/tests/test_backend_ollama.py +89 -0
- mcp_servers_cli-0.1.0/tests/test_backends.py +28 -0
- mcp_servers_cli-0.1.0/tests/test_cli.py +46 -0
- mcp_servers_cli-0.1.0/tests/test_cli_agent.py +118 -0
- mcp_servers_cli-0.1.0/tests/test_config.py +24 -0
- mcp_servers_cli-0.1.0/tests/test_errors.py +49 -0
- mcp_servers_cli-0.1.0/tests/test_inspection.py +50 -0
- mcp_servers_cli-0.1.0/tests/test_metadata.py +13 -0
- mcp_servers_cli-0.1.0/tests/test_rendering.py +61 -0
- mcp_servers_cli-0.1.0/tests/test_repl.py +19 -0
- mcp_servers_cli-0.1.0/tests/test_trace.py +43 -0
- mcp_servers_cli-0.1.0/tests/test_transports.py +48 -0
- mcp_servers_cli-0.1.0/uv.lock +1688 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
jobs:
|
|
7
|
+
test:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
steps:
|
|
10
|
+
- uses: actions/checkout@v7
|
|
11
|
+
- uses: astral-sh/setup-uv@v7
|
|
12
|
+
with:
|
|
13
|
+
enable-cache: true
|
|
14
|
+
- run: uv python install 3.12
|
|
15
|
+
- run: uv sync --all-groups
|
|
16
|
+
- run: uv run ruff check .
|
|
17
|
+
- run: uv run ruff format --check .
|
|
18
|
+
- run: uv run pytest
|
|
19
|
+
- run: |
|
|
20
|
+
uv export --format requirements-txt --no-hashes -o /tmp/req.txt
|
|
21
|
+
uvx pip-audit -r /tmp/req.txt --no-deps
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
environment: pypi
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write
|
|
13
|
+
contents: read
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v7
|
|
16
|
+
- uses: astral-sh/setup-uv@v7
|
|
17
|
+
- name: Refuse a tag that differs from the package version
|
|
18
|
+
run: |
|
|
19
|
+
version="$(uv version --short)"
|
|
20
|
+
if [ "${GITHUB_REF_NAME#v}" != "$version" ]; then
|
|
21
|
+
echo "::error::tag ${GITHUB_REF_NAME} does not match package version ${version}"
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
- run: uv build
|
|
25
|
+
- name: Smoke-test the built wheel
|
|
26
|
+
run: uvx --from "$(ls dist/*.whl)" mcp-servers-cli --version
|
|
27
|
+
- run: uv publish --trusted-publishing always
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
*.lcov
|
|
51
|
+
.hypothesis/
|
|
52
|
+
.pytest_cache/
|
|
53
|
+
cover/
|
|
54
|
+
|
|
55
|
+
# Translations
|
|
56
|
+
*.mo
|
|
57
|
+
*.pot
|
|
58
|
+
|
|
59
|
+
# Django stuff:
|
|
60
|
+
*.log
|
|
61
|
+
local_settings.py
|
|
62
|
+
db.sqlite3
|
|
63
|
+
db.sqlite3-journal
|
|
64
|
+
|
|
65
|
+
# Flask stuff:
|
|
66
|
+
instance/
|
|
67
|
+
.webassets-cache
|
|
68
|
+
|
|
69
|
+
# Scrapy stuff:
|
|
70
|
+
.scrapy
|
|
71
|
+
|
|
72
|
+
# Sphinx documentation
|
|
73
|
+
docs/_build/
|
|
74
|
+
|
|
75
|
+
# PyBuilder
|
|
76
|
+
.pybuilder/
|
|
77
|
+
target/
|
|
78
|
+
|
|
79
|
+
# Jupyter Notebook
|
|
80
|
+
.ipynb_checkpoints
|
|
81
|
+
|
|
82
|
+
# IPython
|
|
83
|
+
profile_default/
|
|
84
|
+
ipython_config.py
|
|
85
|
+
|
|
86
|
+
# pyenv
|
|
87
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
88
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
89
|
+
# .python-version
|
|
90
|
+
|
|
91
|
+
# pipenv
|
|
92
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
93
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
94
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
95
|
+
# install all needed dependencies.
|
|
96
|
+
# Pipfile.lock
|
|
97
|
+
|
|
98
|
+
# UV
|
|
99
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
100
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
101
|
+
# commonly ignored for libraries.
|
|
102
|
+
# uv.lock
|
|
103
|
+
|
|
104
|
+
# poetry
|
|
105
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
106
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
107
|
+
# commonly ignored for libraries.
|
|
108
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
109
|
+
# poetry.lock
|
|
110
|
+
# poetry.toml
|
|
111
|
+
|
|
112
|
+
# pdm
|
|
113
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
114
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
115
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
116
|
+
# pdm.lock
|
|
117
|
+
# pdm.toml
|
|
118
|
+
.pdm-python
|
|
119
|
+
.pdm-build/
|
|
120
|
+
|
|
121
|
+
# pixi
|
|
122
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
123
|
+
# pixi.lock
|
|
124
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
125
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
126
|
+
.pixi/*
|
|
127
|
+
!.pixi/config.toml
|
|
128
|
+
|
|
129
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
130
|
+
__pypackages__/
|
|
131
|
+
|
|
132
|
+
# Celery stuff
|
|
133
|
+
celerybeat-schedule*
|
|
134
|
+
celerybeat.pid
|
|
135
|
+
|
|
136
|
+
# Redis
|
|
137
|
+
*.rdb
|
|
138
|
+
*.aof
|
|
139
|
+
*.pid
|
|
140
|
+
|
|
141
|
+
# RabbitMQ
|
|
142
|
+
mnesia/
|
|
143
|
+
rabbitmq/
|
|
144
|
+
rabbitmq-data/
|
|
145
|
+
|
|
146
|
+
# ActiveMQ
|
|
147
|
+
activemq-data/
|
|
148
|
+
|
|
149
|
+
# SageMath parsed files
|
|
150
|
+
*.sage.py
|
|
151
|
+
|
|
152
|
+
# Environments
|
|
153
|
+
.env
|
|
154
|
+
.envrc
|
|
155
|
+
.venv
|
|
156
|
+
env/
|
|
157
|
+
venv/
|
|
158
|
+
ENV/
|
|
159
|
+
env.bak/
|
|
160
|
+
venv.bak/
|
|
161
|
+
|
|
162
|
+
# Spyder project settings
|
|
163
|
+
.spyderproject
|
|
164
|
+
.spyproject
|
|
165
|
+
|
|
166
|
+
# Rope project settings
|
|
167
|
+
.ropeproject
|
|
168
|
+
|
|
169
|
+
# mkdocs documentation
|
|
170
|
+
/site
|
|
171
|
+
|
|
172
|
+
# mypy
|
|
173
|
+
.mypy_cache/
|
|
174
|
+
.dmypy.json
|
|
175
|
+
dmypy.json
|
|
176
|
+
|
|
177
|
+
# Pyre type checker
|
|
178
|
+
.pyre/
|
|
179
|
+
|
|
180
|
+
# pytype static type analyzer
|
|
181
|
+
.pytype/
|
|
182
|
+
|
|
183
|
+
# Cython debug symbols
|
|
184
|
+
cython_debug/
|
|
185
|
+
|
|
186
|
+
# PyCharm
|
|
187
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
188
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
189
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
190
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
191
|
+
# .idea/
|
|
192
|
+
|
|
193
|
+
# Abstra
|
|
194
|
+
# Abstra is an AI-powered process automation framework.
|
|
195
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
196
|
+
# Learn more at https://abstra.io/docs
|
|
197
|
+
.abstra/
|
|
198
|
+
|
|
199
|
+
# Visual Studio Code
|
|
200
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore that
|
|
201
|
+
# can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
202
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer, you
|
|
203
|
+
# could uncomment the following to ignore the entire vscode folder
|
|
204
|
+
# .vscode/
|
|
205
|
+
# Temporary file for partial code execution
|
|
206
|
+
tempCodeRunnerFile.py
|
|
207
|
+
|
|
208
|
+
# Ruff stuff:
|
|
209
|
+
.ruff_cache/
|
|
210
|
+
|
|
211
|
+
# PyPI configuration file
|
|
212
|
+
.pypirc
|
|
213
|
+
|
|
214
|
+
# Marimo
|
|
215
|
+
marimo/_static/
|
|
216
|
+
marimo/_lsp/
|
|
217
|
+
__marimo__/
|
|
218
|
+
|
|
219
|
+
# Streamlit
|
|
220
|
+
.streamlit/secrets.toml
|
|
221
|
+
|
|
222
|
+
# local
|
|
223
|
+
.DS_Store
|
|
224
|
+
.env
|
|
225
|
+
*.db
|
|
226
|
+
.venv/
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.16.8
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: [--fix]
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
9
|
+
rev: v6.0.0
|
|
10
|
+
hooks:
|
|
11
|
+
- id: check-added-large-files
|
|
12
|
+
args: [--maxkb=5000]
|
|
13
|
+
- id: end-of-file-fixer
|
|
14
|
+
- id: trailing-whitespace
|
|
15
|
+
- id: check-yaml
|
|
16
|
+
- repo: local
|
|
17
|
+
hooks:
|
|
18
|
+
- id: gitleaks
|
|
19
|
+
name: gitleaks
|
|
20
|
+
entry: gitleaks git --pre-commit --staged --redact
|
|
21
|
+
language: system
|
|
22
|
+
pass_filenames: false
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ztp
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: mcp-servers-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Inspect, call and drive any MCP server from the command line
|
|
5
|
+
Project-URL: Homepage, https://github.com/zerotropism/mcp-servers-cli
|
|
6
|
+
Project-URL: Issues, https://github.com/zerotropism/mcp-servers-cli/issues
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: agent,cli,llm,mcp,model-context-protocol
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Testing
|
|
16
|
+
Requires-Python: >=3.12
|
|
17
|
+
Requires-Dist: cyclopts>=4
|
|
18
|
+
Requires-Dist: fastmcp<5,>=4
|
|
19
|
+
Requires-Dist: ollama>=0.6.2
|
|
20
|
+
Requires-Dist: rich>=14
|
|
21
|
+
Provides-Extra: anthropic
|
|
22
|
+
Requires-Dist: anthropic>=1.8.0; extra == 'anthropic'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# mcp-servers-cli
|
|
26
|
+
|
|
27
|
+
Inspect, call and drive any MCP server from the command line. Point it at a local process, a
|
|
28
|
+
remote HTTP endpoint, or an entry in a `claude_desktop_config.json`-style file, and it lists the
|
|
29
|
+
tools, resources and prompts the server exposes — then lets you exercise them.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
Run it without installing anything, with [uv](https://docs.astral.sh/uv/):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
uvx mcp-servers-cli inspect --stdio "uvx mcp-server-fetch"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or install it once, with the Anthropic backend if you need it:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uv tool install mcp-servers-cli
|
|
43
|
+
uv tool install 'mcp-servers-cli[anthropic]'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
From a clone, for development:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
uv sync --all-groups
|
|
50
|
+
uv run mcp-servers-cli --help
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Commands
|
|
54
|
+
|
|
55
|
+
Every command takes exactly one target: `--stdio`, `--http` or `--config` with `--server`.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# What does this server expose?
|
|
59
|
+
mcp-servers-cli inspect --stdio "uv run server.py"
|
|
60
|
+
mcp-servers-cli inspect --http https://example.com/mcp
|
|
61
|
+
mcp-servers-cli inspect --config config.json --server fetch
|
|
62
|
+
|
|
63
|
+
# Call one tool
|
|
64
|
+
mcp-servers-cli call fetch '{"url": "https://example.com"}' --stdio "uvx mcp-server-fetch"
|
|
65
|
+
|
|
66
|
+
# Read one resource
|
|
67
|
+
mcp-servers-cli read "tasks://stats" --stdio "uv run server.py"
|
|
68
|
+
|
|
69
|
+
# Inspect, then stay interactive
|
|
70
|
+
mcp-servers-cli repl --stdio "uv run server.py"
|
|
71
|
+
|
|
72
|
+
# Let a model use the tools to answer
|
|
73
|
+
mcp-servers-cli agent "What is 2 + 3?" --model qwen3.5:4b --stdio "uv run server.py"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Inside the REPL: `call <tool> <json>`, `read <uri>`, `list`, `quit`.
|
|
77
|
+
|
|
78
|
+
## Agent
|
|
79
|
+
|
|
80
|
+
`agent` hands the server's tools to a model and lets it call them until it answers in text. Each
|
|
81
|
+
call is printed as it happens, then the answer:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
-> add {"a": 2, "b": 3}
|
|
85
|
+
<- add ok (0.1s)
|
|
86
|
+
The sum is 5.
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- `--model` is required: small local models can mishandle nested arguments, and a silent default
|
|
90
|
+
would hide that behind a plausible failure.
|
|
91
|
+
- `--backend ollama` (default) talks to the server named by `OLLAMA_HOST`, localhost otherwise.
|
|
92
|
+
- `--backend anthropic` needs the extra, `uvx --from 'mcp-servers-cli[anthropic]' mcp-servers-cli`,
|
|
93
|
+
and reads `ANTHROPIC_API_KEY` from the environment.
|
|
94
|
+
- A failing tool, an unknown tool name or an invented argument does not stop the run: the model
|
|
95
|
+
reads the error or gets the cleaned call, and can correct itself.
|
|
96
|
+
- `--max-steps` (default 10) bounds the model turns; running out is an error, not a silent stop.
|
|
97
|
+
- `--dry-run` asks the model once and prints the calls it would make, running none: a safe first
|
|
98
|
+
look at a server whose tools write or delete.
|
|
99
|
+
- `--trace run.jsonl` writes one JSON object per executed call (time, tool, arguments, duration,
|
|
100
|
+
error flag, a 500-character excerpt of the result) and a closing `end` line with the model
|
|
101
|
+
turns, the number of calls and of failed calls, and the total duration.
|
|
102
|
+
- When a tool call failed, a warning follows the answer on stderr: a model can answer as if its
|
|
103
|
+
calls had worked (see below).
|
|
104
|
+
|
|
105
|
+
A trace reads with any JSON tool, for instance the slowest calls first:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
jq -r 'select(.event == "tool") | "\(.duration_ms) ms \(.tool)"' run.jsonl | sort -rn
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Arguments and results are written as they are: keep traces out of version control when a server
|
|
112
|
+
handles secrets.
|
|
113
|
+
|
|
114
|
+
## Model requirements
|
|
115
|
+
|
|
116
|
+
Measured on 24 September 2026 against `mcpserver-template` (in-memory backend), three runs per
|
|
117
|
+
model, with one prompt: add three tasks, complete one, list the pending ones. It takes a string
|
|
118
|
+
argument, an integer read from an earlier result, and a nested object (`filter_tasks`).
|
|
119
|
+
|
|
120
|
+
| Model | Correct answers | Tool calls | Failed calls | Model turns |
|
|
121
|
+
|------------------|-----------------|------------|--------------|-------------|
|
|
122
|
+
| `qwen3.5:4b-mlx` | 3 / 3 | 5 | 0 | 4 |
|
|
123
|
+
| `llama3.2:3b` | 0 / 3 | 1 to 3 | 1 to 2 | 2 |
|
|
124
|
+
|
|
125
|
+
`llama3.2:3b` failed every run. Its first planned call was `filter_tasks` with invented fields,
|
|
126
|
+
before any task existed. In the run examined in detail, that filter was rejected and
|
|
127
|
+
`complete_task` targeted a task that did not exist; yet each of the three answers described the
|
|
128
|
+
work as done. That is the failure to watch for with small models: not a crash, but a fluent and
|
|
129
|
+
false answer. Read the `<-` lines, or the warning printed after the answer, before trusting it.
|
|
130
|
+
|
|
131
|
+
`qwen3.5:4b-mlx` sent the three `add_task` calls in one turn. Twice it filtered on the server
|
|
132
|
+
(`{"status": "pending"}`); once it fetched every task and filtered the result itself. Both gave
|
|
133
|
+
the right answer.
|
|
134
|
+
|
|
135
|
+
Unknown top-level arguments are dropped before a call; an invented field inside a nested object
|
|
136
|
+
is left to fail. Dropping `title` from a mistaken filter would widen it to every task and return
|
|
137
|
+
a wrong answer that looks right.
|
|
138
|
+
|
|
139
|
+
## Client capabilities
|
|
140
|
+
|
|
141
|
+
MCP lets a server ask its client for three things during a call: a completion from the client's
|
|
142
|
+
model (sampling), an answer from the user (elicitation), and the directories it may work in
|
|
143
|
+
(roots). `mcp-servers-cli` declares none of them. A server that asks gets a one-line refusal,
|
|
144
|
+
`Sampling not supported`, `Elicitation not supported` or `List roots not supported`: `call`
|
|
145
|
+
prints it as an error, `agent` hands it to the model as a failed call.
|
|
146
|
+
|
|
147
|
+
FastMCP 4 negotiates the 2026-07-28 protocol revision by default. On such a connection a server
|
|
148
|
+
no longer sends these requests itself: its tool returns an input-required result (SEP-2322)
|
|
149
|
+
listing what it needs, and the client answers before the tool runs again. The refusals above
|
|
150
|
+
are what such a server receives.
|
|
151
|
+
|
|
152
|
+
Handlers will come with the first server of this portfolio that needs one: sampling bridged to
|
|
153
|
+
the `agent` backend, roots from the command line. Elicitation needs someone at the keyboard,
|
|
154
|
+
which `agent` does not assume.
|
|
155
|
+
|
|
156
|
+
## Configuring the server you launch
|
|
157
|
+
|
|
158
|
+
A stdio server runs as a subprocess, and the MCP SDK forwards only a whitelist of environment
|
|
159
|
+
variables to it — `HOME`, `LOGNAME`, `PATH`, `SHELL`, `TERM`, `USER`. Anything else the server
|
|
160
|
+
reads from its environment is silently absent, and it falls back to its defaults.
|
|
161
|
+
|
|
162
|
+
`--env` is how you pass the rest:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
mcp-servers-cli call add_task '{"title": "buy milk"}' \
|
|
166
|
+
--env TASK_BACKEND=sqlite --env DB_PATH=tasks.db \
|
|
167
|
+
--stdio "uv run --directory ../mcpserver-template mcpserver-template"
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`~` and `$VARS` are expanded in the command and in every argument, including entries read from
|
|
171
|
+
a config file.
|
|
172
|
+
|
|
173
|
+
For a remote server, `MCP_TOKEN` is sent as a bearer token when set.
|
|
174
|
+
|
|
175
|
+
## Noisy servers
|
|
176
|
+
|
|
177
|
+
A stdio server writes its own logs to stderr, and they land in your terminal. Servers built on
|
|
178
|
+
older MCP SDKs answer FastMCP's capability probe with a wall of validation errors before falling
|
|
179
|
+
back to the legacy protocol — the inspection still succeeds, but the output is buried.
|
|
180
|
+
|
|
181
|
+
`--quiet` discards that stream. It is not the default on purpose: when a server fails to start,
|
|
182
|
+
the reason is in its first stderr line, and hiding it turns a clear error into a bare
|
|
183
|
+
"Connection closed".
|
|
184
|
+
|
|
185
|
+
## Errors
|
|
186
|
+
|
|
187
|
+
A failure prints one line naming its cause and exits with status 1:
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
error: Client failed to connect: [Errno 2] No such file or directory: 'uvx'
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
When a stdio server dies while starting, its own stderr line comes first and the error line points
|
|
194
|
+
to it. Set `MCP_SERVERS_CLI_DEBUG=1` to get the full traceback instead.
|
|
195
|
+
|
|
196
|
+
## Configuration file
|
|
197
|
+
|
|
198
|
+
The `--config` mode reads the `mcpServers` format used by Claude Desktop:
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"mcpServers": {
|
|
203
|
+
"filesystem": {
|
|
204
|
+
"command": "npx",
|
|
205
|
+
"args": ["-y", "@modelcontextprotocol/server-filesystem", "${HOME}/Developer"]
|
|
206
|
+
},
|
|
207
|
+
"fetch": { "command": "uvx", "args": ["mcp-server-fetch"] },
|
|
208
|
+
"remote": { "url": "https://example.com/mcp" }
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Project structure
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
src/mcp_servers_cli/
|
|
217
|
+
├── transports.py one builder per transport, plus path expansion
|
|
218
|
+
├── inspection.py reads a server into dataclasses
|
|
219
|
+
├── rendering.py turns inspection data, results and agent progress into output
|
|
220
|
+
├── repl.py interactive loop over a connected client
|
|
221
|
+
├── errors.py turns a failure into one line
|
|
222
|
+
├── llm.py provider-neutral conversation model and the LLMBackend protocol
|
|
223
|
+
├── backends/ one module per provider, translating to and from that model
|
|
224
|
+
├── agent.py the tool loop: model turns and tool calls, printing nothing
|
|
225
|
+
├── trace.py JSON Lines record of an agent run
|
|
226
|
+
└── cli.py cyclopts commands
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Inspection returns data and never prints; rendering never talks to a server. That is what lets
|
|
230
|
+
the tests run an in-memory FastMCP server and assert on structures rather than on captured
|
|
231
|
+
stdout.
|
|
232
|
+
|
|
233
|
+
Adding a transport means adding a builder in `transports.py` and a target option in `cli.py`,
|
|
234
|
+
without touching the existing ones.
|
|
235
|
+
|
|
236
|
+
Only `backends/` imports an LLM SDK; everything else works on the neutral types of `llm.py`.
|
|
237
|
+
Adding a provider means adding one module there and one line in `backends/__init__.py`.
|
|
238
|
+
|
|
239
|
+
## Tests
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
uv run pytest
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
No network and no LLM. The inspection and agent-loop tests run against an in-memory FastMCP
|
|
246
|
+
server with a scripted model; the transport tests check expansion rules; the rendering tests
|
|
247
|
+
capture a rich console; the backend tests translate real SDK objects through a stand-in client;
|
|
248
|
+
the trace tests write to a temporary directory.
|
|
249
|
+
Two kinds of test start a process: the error tests launch a command that does not exist, and the
|
|
250
|
+
agent command is tested end to end against `tests/fixtures/add_server.py` over stdio.
|
|
251
|
+
|
|
252
|
+
## Dependencies
|
|
253
|
+
|
|
254
|
+
| Package | Role |
|
|
255
|
+
|------------|---------------------------------------|
|
|
256
|
+
| `fastmcp` | MCP client and transports |
|
|
257
|
+
| `cyclopts` | Commands and help, from type hints |
|
|
258
|
+
| `rich` | Tables and JSON highlighting |
|
|
259
|
+
| `ollama` | Local models, the default backend |
|
|
260
|
+
| `anthropic` | Anthropic backend, optional: `mcp-servers-cli[anthropic]` |
|
|
261
|
+
|
|
262
|
+
`cyclopts` and `rich` already ship in FastMCP's dependency tree; they are declared explicitly
|
|
263
|
+
rather than relied on transitively.
|