fitnesse-mcp
An MCP server that exposes FitNesse's REST responders as tools, enabling reading wiki pages, running tests and suites, managing the files section, and inspecting test history on a FitNesse instance.
README
fitnesse-mcp
An MCP server that exposes FitNesse's REST responders as tools, letting an MCP client read wiki pages, run tests and suites, manage the files section, and inspect test history on a FitNesse instance.
Copyright (c) 2026 netcare GmbH. Released under the MIT License.
Requirements
- VS Code with the Dev Containers extension, plus Docker — the devcontainer supplies the Python toolchain and dependencies
- A reachable FitNesse instance
Working outside the devcontainer? You'll need Python 3.12+ and FastMCP 4, which is currently a prerelease and must be pinned exactly:
pip install "fastmcp==4.0.0b2"
Using uv? fastmcp is a thin wrapper that depends on fastmcp-slim at the same
version, and uv only allows prereleases for packages you name explicitly:
[project]
dependencies = ["fastmcp==4.0.0b2"]
[tool.uv]
constraint-dependencies = ["fastmcp-slim==4.0.0b2"]
Pin exactly, not
>=4.0.0b1. Each beta in the v4 line has carried breaking changes. This project tracks the prerelease and the pin will move at GA.
Quickstart
1. Open the project in its devcontainer.
git clone https://github.com/netcare-io/fitnesse-mcp.git
cd fitnesse-mcp
code .
VS Code detects .devcontainer/ and prompts to reopen in the container — accept
it, or run Dev Containers: Reopen in Container from the command palette
(F1). The first build takes a few minutes; later starts are quick. Python and
all dependencies are installed inside the container, so there's nothing to set
up on your host.
2. Point the server at your FitNesse instance.
Run the remaining commands in the container's terminal:
export FITNESSE_BASE_URL=http://your-fitnesse-host:8080
export FITNESSE_READONLY=1 # recommended for a first run
fastmcp run server.py
localhost in that URL refers to the container, not your host — see
Troubleshooting if the connection is refused.
3. Verify it works.
Start the inspector and call fitnesse_get_raw with page_path=FrontPage:
fastmcp-inspector
A successful call returns "ok": true and the raw wiki markup of the page.
Security
This server gives an LLM client the ability to delete pages, purge test history, and roll back versions on your FitNesse instance. Two controls limit that, and both are opt-in:
- Start with
FITNESSE_READONLY=1. This hides every write, execute, and control tool, leaving the 23 read-only tools. Open it up deliberately once you know which operations you actually want the model to perform. fitnesse_shutdownis not registered at all unlessFITNESSE_ALLOW_SHUTDOWNis set. It stops the FitNesse server.
Two further notes:
- Uploads are disabled unless
FITNESSE_UPLOAD_ROOTis set, andfitnesse_upload_filewill only read files resolving inside that directory. - Credentials in
claude_desktop_config.jsonare stored in cleartext. For shared machines, prefer the HTTP pattern below with the credentials in the server's own environment.
Environment variables
| Variable | Default | Description |
|---|---|---|
FITNESSE_BASE_URL |
http://localhost:8080 |
FitNesse server base URL |
FITNESSE_USERNAME |
(none) | Basic Auth username |
FITNESSE_PASSWORD |
(none) | Basic Auth password |
FITNESSE_READONLY |
false |
1/true/yes/on hides all write, execute, and control tools |
FITNESSE_ALLOW_SHUTDOWN |
false |
1/true/yes/on exposes fitnesse_shutdown |
FITNESSE_UPLOAD_ROOT |
(none — uploads disabled) | Absolute host path; fitnesse_upload_file only reads files beneath it |
FITNESSE_MAX_RESPONSE_BYTES |
1048576 (1 MB) |
Responses above this are truncated and flagged "truncated": true |
FITNESSE_MAX_UPLOAD_BYTES |
10485760 (10 MB) |
Uploads above this are rejected |
Tools
42 tools by default (43 with FITNESSE_ALLOW_SHUTDOWN), each mapping to one
FitNesse responder. Under FITNESSE_READONLY, only the 23 read tools are
exposed.
<details> <summary><b>Full tool list</b></summary>
Pages — read
| Tool | Responder |
|---|---|
fitnesse_get_page |
getPage |
fitnesse_get_raw |
raw |
fitnesse_get_page_data |
pageData |
fitnesse_get_packet |
packet — all tables on a page, as JSON |
fitnesse_get_properties |
properties |
fitnesse_get_variables |
variables |
fitnesse_list_names |
names |
fitnesse_edit_page |
edit — with redirect/nonExistent options |
fitnesse_get_page_content |
edit — convenience alias |
fitnesse_get_new_page_form |
new |
fitnesse_get_refactor_screen |
refactor |
fitnesse_get_rss |
rss |
Pages — write
| Tool | Responder |
|---|---|
fitnesse_add_child_page |
addChild |
fitnesse_save_page_content |
saveData (POST form data) |
fitnesse_save_properties |
saveProperties (POST form data) |
fitnesse_rename_page |
renamePage |
fitnesse_move_page |
movePage |
fitnesse_delete_page |
deletePage |
fitnesse_manage_symlink |
symlink |
fitnesse_import_pages |
import |
fitnesse_import_and_view |
importAndView |
fitnesse_publish |
publish |
Tests
| Tool | Responder | Mode |
|---|---|---|
fitnesse_run_test |
test |
execute |
fitnesse_run_suite |
suite — suite filters, debug, nochunk |
execute |
fitnesse_get_instruction |
instruction — Slim instructions |
read |
fitnesse_stop_test |
stoptest |
control |
fitnesse_shutdown |
shutdown (needs FITNESSE_ALLOW_SHUTDOWN) |
control |
History & versions
| Tool | Responder | Mode |
|---|---|---|
fitnesse_get_test_history |
testHistory |
read |
fitnesse_get_page_history |
pageHistory |
read |
fitnesse_compare_history |
compareHistory |
read |
fitnesse_get_versions |
versions |
read |
fitnesse_view_version |
viewVersion |
read |
fitnesse_rollback_version |
rollback |
write |
fitnesse_purge_history |
purgeHistory |
write |
Search
| Tool | Responder |
|---|---|
fitnesse_search |
search — read |
fitnesse_execute_search_properties |
executeSearchProperties — read |
fitnesse_get_search_form |
searchForm — read |
fitnesse_where_used |
whereUsed — read |
Files section
| Tool | Responder | Mode |
|---|---|---|
fitnesse_list_files |
files |
read |
fitnesse_create_dir |
createDir |
write |
fitnesse_upload_file |
upload (POST multipart) |
write |
fitnesse_rename_file |
renameFile |
write |
fitnesse_delete_file |
deleteFile |
write |
</details>
Flag-style FitNesse inputs are supported by passing None as a query param
value — for example {"nohistory": None} produces ?nohistory.
Connecting an MCP client
Pattern 1 — stdio (simple, local)
The client launches the server as a subprocess and talks over stdin/stdout. No server process to manage.
{
"mcpServers": {
"fitnesse": {
"command": "fastmcp",
"args": ["run", "server.py"],
"env": {
"FITNESSE_BASE_URL": "http://your-fitnesse-host:8080",
"FITNESSE_USERNAME": "your-username",
"FITNESSE_PASSWORD": "your-password",
"FITNESSE_READONLY": "1"
}
}
}
}
Running the server from a Docker image instead? -i keeps stdin open, and each
-e forwards one variable from the env block into the container. Every
variable you set in env needs its own -e flag — anything missing here is
silently ignored inside the container:
{
"mcpServers": {
"fitnesse": {
"command": "docker",
"args": [
"run", "--rm", "-i",
"-e", "FITNESSE_BASE_URL",
"-e", "FITNESSE_USERNAME",
"-e", "FITNESSE_PASSWORD",
"-e", "FITNESSE_READONLY",
"fitnesse-mcp:latest"
],
"env": {
"FITNESSE_BASE_URL": "http://your-fitnesse-host:8080",
"FITNESSE_USERNAME": "your-username",
"FITNESSE_PASSWORD": "your-password",
"FITNESSE_READONLY": "1"
}
}
}
}
Pattern 2 — HTTP (shared, production)
Use this when several clients share one server instance, or when the server runs in Docker. Credentials live in the server's environment rather than in each client's config.
FITNESSE_BASE_URL=http://your-fitnesse-host:8080 \
FITNESSE_USERNAME=your-username \
FITNESSE_PASSWORD=your-password \
fastmcp run server.py --transport http
Listens on port 8000 by default; override with --port.
Most clients can point at the URL directly:
{
"mcpServers": {
"fitnesse": { "url": "http://127.0.0.1:8000/mcp" }
}
}
For clients that only speak stdio, fastmcp run <url> proxies to it.
Interactive testing
fastmcp-inspector
Open the exact URL printed in the terminal — it carries a
?MCP_INSPECTOR_API_TOKEN=... query parameter.
In devcontainers (especially running as root), the wrapper suppresses noisy keyring/DBus warnings:
./scripts/run-inspector.sh
INSPECTOR_RAW_LOGS=1 ./scripts/run-inspector.sh # unfiltered logs
Troubleshooting
401 Unauthorized — either FITNESSE_USERNAME/FITNESSE_PASSWORD are
wrong, or FitNesse isn't configured for authentication and is rejecting the
header. Confirm with curl -u user:pass "$FITNESSE_BASE_URL/FrontPage?responder=raw".
Connection refused — check FITNESSE_BASE_URL. Inside a container,
localhost is the container, not your host; use host.docker.internal (Docker
Desktop) or the host's LAN address.
A write tool is missing — FITNESSE_READONLY is set. Note that any value
other than 1/true/yes/on counts as unset.
"truncated": true in a response — the body exceeded
FITNESSE_MAX_RESPONSE_BYTES and was cut. Common on suite runs with
includehtml. Raise the limit or narrow the request.
Invalid path — the page path contained ?, #, .., or a null byte.
FitNesse page paths are dotted (FrontPage.MySuite.MyTest) with no leading
slash.
ImportError on startup — almost certainly the FastMCP version. This
project targets 4.0.0b2 exactly; v3 and the v4 alphas will not import.
Development
pip install -e ".[dev]"
pytest tests/
The test suite runs the server in-process via fastmcp.Client, so it also
verifies the pieces that only fail at call time: dependency injection of
timeouts, tag-based tool visibility, and path-injection rejection. Run it after
any FastMCP version bump — it doubles as the upgrade tripwire.
Devcontainer
The devcontainer (see Quickstart) builds from a multi-stage
Dockerfile at .devcontainer/Dockerfile:
devcontainer— used by VS Code for developmentproduction— used by Docker Compose for deployment
Production with Docker Compose
docker compose -f docker-compose.prod.yml up --build -d
Env vars come from a .env file alongside docker-compose.prod.yml:
FITNESSE_BASE_URL=http://your-fitnesse-host:8080
FITNESSE_USERNAME=your-username
FITNESSE_PASSWORD=your-password
FITNESSE_READONLY=1
# FITNESSE_ALLOW_SHUTDOWN=1
# FITNESSE_UPLOAD_ROOT=/data/uploads
The server is then available at http://localhost:8000/mcp.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.