Random MCP Server based on Random Server
1.5K
An MCP server that returns random JSON
things — people, words, values, coordinates, and an always-empty list. It is a
Python / FastMCP adaptation of the Node/Express
random-server REST API: each REST route family becomes an
MCP tool.
Built with Python, uv, FastMCP, and make.
The REST server seeds a fixed pool of records when it starts and serves them at
/v1/<kind>, /v1/<kind>/count, and /v1/<kind>/:id. This server does the
same, exposing the pool through a small set of tools parameterized by kind
(people, words, values, coords, empty):
| REST route | MCP tool |
|---|---|
GET / | server_info() |
GET /v1/<kind> | list_records(kind, count?) |
GET /v1/<kind>/count | count_records(kind) |
GET /v1/<kind>/:id | get_record(kind, id) (1-based) |
| (restart to reseed) | regenerate(seed?) — opt-in |
Records are seeded at start-up, so a given id is stable until you call
regenerate. Pass a fixed seed (tool arg or RANDOM_SEED) for reproducible
data.
The regenerate tool is disabled by default — see
The regenerate tool is opt-in.
random-serverx-api-key auth. The REST server's optional API_KEY guard on /v1
routes is not ported — MCP transports handle authentication differently
(add FastMCP auth if a
networked deployment needs it). The APP_NAME, seeding, and per-kind count
behavior are preserved./api-docs explorer has no MCP equivalent; tool
schemas are discoverable through the MCP protocol itself (e.g. make dev).// get_record(kind="people", id=1)
{ "type": "people", "prefix": "Mr.", "first": "Augustus", "last": "Gomez",
"age": 42, "birthday": "7/8/1959", "gender": "male", "zip": "74948-0928",
"ssnFour": "0791", "phone": "(509) 504-8066", "email": "[email protected]" }
// get_record(kind="words", id=1) -> { "type": "words", "value": "cezuwdi" }
// get_record(kind="values", id=1) -> { "type": "values", "name": "dafe", "value": -415365907192.2176 }
// get_record(kind="coords", id=1) -> { "type": "coords", "latitude": 88.43647, "longitude": -93.31203 }
Each server process builds one pool of records per kind when it starts, then serves that pool for the life of the process. What stays stable and what changes follows from that:
get_record(kind, id) returns the same record, and
list_records returns the same pool in the same order. Ask for person 1
today and it's identical on the next call — until the pool is rebuilt (below).RANDOM_SEED
set, the factory picks a random seed at start-up, so restarting the server
(or launching a fresh container) yields a different pool. Set
RANDOM_SEED to a fixed value and every restart rebuilds the same pool, so
records survive reboots.RANDOM_SEED.RANDOM_SEED to make them line up.regenerate rebuilds the shared pool for everyone on that instance.
regenerate(seed=N) is reproducible (same seed → same pool); regenerate()
with no argument picks a new random seed. Either way it mutates the shared
state, so on a multi-client instance it changes the records every other
connected client sees too. Because of that it is disabled by default
(see below).server_info reports the active seed. Capture that value and feed it
back via RANDOM_SEED (at start-up) or regenerate(seed=…) to reproduce a
pool you liked later.| Scenario | Same records? |
|---|---|
| Same instance, repeated calls, no restart | ✅ Yes |
| Same long-running instance, different clients | ✅ Yes |
After a restart / new container, no RANDOM_SEED | ❌ No (reshuffled) |
After a restart / new container, fixed RANDOM_SEED | ✅ Yes |
| Two separate unseeded instances | ❌ No |
Two instances, both pinned to the same RANDOM_SEED | ✅ Yes |
After regenerate() (no seed) | ❌ No (reshuffled) |
After regenerate(seed=N) with a seed you used before | ✅ Yes |
The two regenerate rows assume ALLOW_REGENERATE is set; otherwise the tool is
unavailable (opt-in) and only a restart rebuilds
the pool.
regenerate tool is opt-inBecause regenerate reseeds the one shared pool the whole process serves,
on a multi-user instance a single caller can reshuffle the records out from
under everyone else — no isolation, last-writer-wins. To prevent that, the tool
is disabled by default: when ALLOW_REGENERATE is unset the tool is not
registered at all, so it doesn't appear in the tool list and can't be called
(server_info reports "allow_regenerate": false).
Enable it only where reseeding is safe:
ALLOW_REGENERATE=1 make run # single-user stdio
docker run --rm -p 8000:8000 -e ALLOW_REGENERATE=1 ghcr.io/mitchallen/random-mcp-server:latest
Guidance by deployment shape:
RANDOM_SEED if you need a specific reproducible pool,
and treat the data as read-only. This is why the Docker image (which defaults
to shared HTTP) ships with regenerate off.ALLOW_REGENERATE=1.Requires uv.
make install # create .venv and sync deps
make test # run the test suite
make run # run the server over stdio
make help lists every target.
uv run random-mcp-server
# or
make run
make run-http # PORT defaults to 8000
PORT=9000 make run-http
make inspect # print a summary: name, version, tool count
make dev # launch the interactive FastMCP Inspector (web UI)
All configuration is via environment variables:
| Variable | Default | Purpose |
|---|---|---|
APP_NAME | random-mcp-server | Name reported by server_info |
RANDOM_COUNT | 25 | Records generated per kind at start-up |
RANDOM_SEED | (random) | Fixed seed for reproducible pools |
ALLOW_REGENERATE | (off) | Expose the regenerate tool (see below) |
MCP_TRANSPORT | stdio | stdio, http, or sse |
HOST | 127.0.0.1 | Bind address for http/sse |
PORT | 8000 | Bind port for http/sse |
ALLOW_REGENERATE accepts 1/true/yes/on (case-insensitive) to enable.
This section is for developers working from a checkout of this repo. It runs the server straight from your local source via uv, so code changes take effect on the next launch. If you only have the Docker image or a remote deployment, skip to Using a published image or a remote server.
Point a stdio-based client (e.g. Claude Desktop, Claude Code) at the console
script. Example claude_desktop_config.json entry using uv:
{
"mcpServers": {
"random": {
"command": "uv",
"args": ["run", "--directory", "/absolute/path/to/random-mcp-server", "random-mcp-server"]
}
}
}
With Claude Code:
claude mcp add random -- uv run --directory "$PWD" random-mcp-server
Confirm it's connected with claude mcp list (or /mcp inside a session).
Once the server is added, just ask in plain language — Claude picks the right tool. The tool it invokes is shown in parentheses.
server_info)list_records with kind="people", count=3)get_record with kind="people", id=1)count_records with kind="coords")list_records with kind="words")list_records with kind="coords", count=5)regenerate with seed=42, then get_record)regenerate)get_record with kind="values", id=4)Handy because records are seeded and stable: ask for a person by id, use it to seed a test fixture, and it stays the same until you ask Claude to reseed. Pass a fixed seed (e.g. "reseed with 42") when you need reproducible data.
The two regenerate prompts only work when the server was started with
ALLOW_REGENERATE=1 (the opt-in flag);
otherwise the tool isn't exposed and Claude won't have it to call.
This section is for consumers who are not building from source — you have the published Docker image, or someone has deployed the server for you. No Python, uv, or checkout required. Pick the option that matches how the server reaches you.
The client starts a fresh container per session and talks to it over stdio. Use
-i (keep stdin open) and force the stdio transport, since the image defaults to
HTTP. The image is published to two registries, so pick one:
// GitHub Container Registry (GHCR)
{
"mcpServers": {
"random": {
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT=stdio",
"ghcr.io/mitchallen/random-mcp-server:latest"]
}
}
}
// Docker Hub
{
"mcpServers": {
"random": {
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT=stdio",
"mitchallen/random-mcp-server:latest"]
}
}
}
Claude Code equivalent — again, pick a registry:
# GitHub Container Registry (GHCR)
claude mcp add random -- docker run -i --rm -e MCP_TRANSPORT=stdio ghcr.io/mitchallen/random-mcp-server:latest
# Docker Hub
claude mcp add random -- docker run -i --rm -e MCP_TRANSPORT=stdio mitchallen/random-mcp-server:latest
(Pin a version like :0.1.3 in place of :latest for a reproducible setup.)
Scope — local (default) vs user. claude mcp add registers the server
in the current project only. Add --scope user (-s user) to register it once
for every project on your machine instead:
# GHCR, available across all your projects
claude mcp add --scope user random -- docker run -i --rm -e MCP_TRANSPORT=stdio ghcr.io/mitchallen/random-mcp-server:latest
# Docker Hub, available across all your projects
claude mcp add --scope user random -- docker run -i --rm -e MCP_TRANSPORT=stdio mitchallen/random-mcp-server:latest
(Scopes are local — this project, the default; project — shared via a
checked-in .mcp.json; and user — all your projects.)
Start the container once (it serves HTTP by default) from either registry, then point an HTTP-capable client at it:
# GitHub Container Registry (GHCR)
docker run -d --rm -p 8000:8000 --name random-mcp ghcr.io/mitchallen/random-mcp-server:latest
# Docker Hub
docker run -d --rm -p 8000:8000 --name random-mcp mitchallen/random-mcp-server:latest
Claude Code (native HTTP transport) — the client connects over HTTP, so the command is the same regardless of which registry you pulled from:
claude mcp add --transport http random http://localhost:8000/mcp
Add --scope user (-s user) to register it for every project on your
machine instead of just the current one:
claude mcp add --scope user --transport http random http://localhost:8000/mcp
For clients that only speak stdio, bridge to the HTTP endpoint with
mcp-remote:
{
"mcpServers": {
"random": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://localhost:8000/mcp"]
}
}
}
If the server is hosted elsewhere, use its public URL — everything else matches Option B. There's no image to pull here (the host already runs it, from whichever registry they chose), so registry choice doesn't apply on your side:
claude mcp add --transport http random https://random-mcp.example.com/mcp
Add --scope user (-s user) to register it across all your projects:
claude mcp add --scope user --transport http random https://random-mcp.example.com/mcp
{
"mcpServers": {
"random": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://random-mcp.example.com/mcp"]
}
}
}
Notes for remote use:
x-api-key guard
isn't ported). If you expose it beyond localhost, put it behind a reverse proxy,
gateway, or network policy that enforces access — or add
FastMCP auth./mcp (no trailing slash). Requesting /mcp/ works too
but returns a 307 redirect to /mcp, so point clients at /mcp to skip the
extra round-trip.FastMCP ships rate-limiting middleware and it would be a few lines to wire in, but this server deliberately doesn't:
get_client_id is meaningful).The example prompts above work the same once the server is connected by any of these methods.
The setups below were exercised against the published image (:0.1.3) with a
real client — connect, initialize, list tools, and call a tool — not just
assumed. Legend: ✅ connected end-to-end · ☑️ server/endpoint proven, that exact
client wiring not run here.
| Setup | Transport | How it was verified | Status |
|---|---|---|---|
| Docker image, client-launched (Option A) | stdio | Piped an MCP initialize into docker run -i -e MCP_TRANSPORT=stdio …; got a valid response reporting v0.1.3. | ✅ |
| Long-running HTTP container (Option B) | HTTP | FastMCP network client against http://localhost:8000/mcp (listed all 5 tools, called get_record/count_records). | ✅ |
| Long-running HTTP container, Claude Code | HTTP | claude mcp add --transport http … → claude mcp list reported ✔ Connected. | ✅ |
| Local dev, console script (from source) | stdio | Server proven through the in-memory FastMCP client and the test suite; the uv run stdio launch is the same entry point. | ☑️ |
| Remote deployment (Option C) | HTTP | Identical to Option B but with a public URL; the HTTP endpoint is proven, a hosted instance was not stood up. | ☑️ |
stdio-only client via mcp-remote bridge | HTTP (bridged) | Documented from standard mcp-remote usage; not run here. | ☑️ |
Published multi-platform (linux/amd64, linux/arm64) images are available
from two registries:
ghcr.io/mitchallen/random-mcp-servermitchallen/random-mcp-serverThe image runs the server over streamable HTTP by default (MCP_TRANSPORT=http,
HOST=0.0.0.0, PORT=8000) so it's reachable on a published port.
It's built on a distroless Chainguard/Wolfi
Python base — no shell or package manager, runs as a non-root user, and scans
0 known vulnerabilities. Every build is gated by a Trivy scan (fails on
fixable CRITICAL/HIGH) and the published :latest is re-scanned daily; see
CI / Publish.
docker pull ghcr.io/mitchallen/random-mcp-server:latest
# or from Docker Hub
docker pull mitchallen/random-mcp-server:latest
Both registries also publish version tags (e.g. :0.2.4); prefer a pinned
version over :latest for reproducible deployments.
docker run --rm -p 8000:8000 --name random-mcp ghcr.io/mitchallen/random-mcp-server:latest
Then connect an HTTP MCP client to http://localhost:8000/mcp.
Convenience targets pull and run the published image in your local Docker environment — handy for smoke-testing a release without a local build:
make docker-test # up + smoke + down in one shot (exits non-zero on failure)
make docker-up # pull + run ghcr.io/mitchallen latest, detached
make docker-smoke # MCP `initialize` handshake — passes if the server responds
make docker-logs # follow the container logs
make docker-down # stop it
make docker-up TAG=0.1.1 # pin a version
make docker-up REGISTRY=docker.io/mitchallen # pull from Docker Hub instead
make docker-up HTTP_PORT=9000 # publish on a different host port
Pass any of the configuration variables with -e:
docker run --rm -p 9000:9000 \
-e PORT=9000 \
-e APP_NAME=my-random \
-e RANDOM_COUNT=50 \
-e RANDOM_SEED=42 \
ghcr.io/mitchallen/random-mcp-server:latest
To run over stdio inside the container instead (e.g. when another process attaches to it), override the transport:
docker run --rm -i -e MCP_TRANSPORT=stdio ghcr.io/mitchallen/random-mcp-server:latest
make docker-build # docker build -t random-mcp-server .
make docker-run # serves http on localhost:8000
Two GitHub Actions workflows live in .github/workflows/:
test — runs on every push/PR to main: the unit suite
(uv sync --frozen then pytest --ignore=tests/test_bdd.py).bdd — runs on every push/PR to main in its own workflow: the
pytest-bdd scenarios (pytest tests/test_bdd.py), so they pass or fail
and report (and badge) independently of the unit suite.publish — triggered by pushing a v* tag. Builds a multi-platform
(linux/amd64, linux/arm64) image and pushes it to the GitHub Container
Registry as ghcr.io/mitchallen/random-mcp-server with both the version and
latest tags, then runs make docker-test against the just-published image as
a post-publish smoke check (the job fails if the released image doesn't answer
an MCP initialize). It uses the built-in GITHUB_TOKEN, so no extra secrets
are needed.publish-dockerhub — also triggered by a v* tag. Pushes the same
multi-platform image to Docker Hub as mitchallen/random-mcp-server, runs the
same make docker-test post-publish smoke check against it, and syncs this
README to the Docker Hub repo description. Requires two repository secrets and
a pre-created Docker Hub repository (see below).The publish-dockerhub workflow needs:
A Docker Hub repository named mitchallen/random-mcp-server.
Two repository secrets — set them with the GitHub CLI:
gh secret set DOCKERHUB_USERNAME --repo mitchallen/random-mcp-server
gh secret set DOCKERHUB_TOKEN --repo mitchallen/random-mcp-server # a Docker Hub access token
Until both secrets exist, the publish-dockerhub job will fail on tag pushes
while the GHCR publish job continues to work on its own.
To cut a release, use the release target — it bumps version in
pyproject.toml (and uv.lock), commits, tags, and pushes, which triggers both
publish workflows:
make release # patch bump (default)
make release BUMP=minor # or minor / major
The target refuses to run unless the working tre
Content type
Image
Digest
sha256:f03a82359…
Size
55.4 MB
Last updated
about 2 months ago
docker pull mitchallen/random-mcp-server