Skip to main content
Glama

mcp-puzzlemaster

A small MCP server that solves word puzzles — Spelling Bee, crosswords, Wordle — against the full 172,823-word ENABLE1 dictionary, and writes an audit row to Postgres for every call it serves.

It exists to make one argument concrete: once several agents share a single MCP server, you get things you cannot get from tools scattered across three codebases. One dictionary, loaded once, for every caller. One seam that sees every invocation. One table you can graph and export.

Built with FastMCP and deployed to Prefect Horizon (formerly FastMCP Cloud).


Tools

Every tool takes an explicit agent_name. See Agent identity for why that is honest telemetry and not authentication.

Tool

Arguments

Returns

solve_spelling_bee

agent_name, letters (7 letters), center

every valid word with its score, the total, and any pangrams

solve_crossword_pattern

agent_name, pattern (e.g. C_O__W_RD)

every dictionary word matching the pattern

solve_wordle

agent_name, guesses, feedback, length=5

the remaining candidate words

usage_graph

agent_name, group_by ("agent" or "tool")

an ASCII bar chart of logged usage

export_results

agent_name, limit (1–1000, default 200)

the audit log as CSV text

Puzzle rules, as implemented

  • Spelling Bee. Words are ≥ 4 letters, must contain the center letter, and may use only the 7 allowed letters — but may reuse them freely, so the check is set(word) <= allowed, not a multiset one. A 4-letter word scores 1 point flat (not 4 — this is the classic trap); 5+ letters score 1 point per letter; a pangram earns +7 on top. Worked example: VALIDTY with center V yields 34 words / 171 points, and the pangram VALIDITY. The New York Times' curated answer for the same puzzle was 21 words / 119 points — a public word list over-generates against editorial curation, which is a real and useful lesson about deterministic tools versus curated ground truth.

  • Crossword. _ means unknown. The word length is derived from the pattern, so there is no separate length argument. C_O__W_RD["CROSSWORD"].

  • Wordle. Feedback is one character per letter: g right letter and right spot, y right letter wrong spot, b absent. Rather than hand-coding constraint rules — which is exactly where duplicate letters go wrong — a candidate survives only if replaying each guess against it reproduces the feedback that was actually seen. CRANE / gybbb leaves 34 candidates.

export_results returns CSV content, not a file path

The obvious design writes a CSV to disk and returns the path. That is wrong for a server you reach over HTTPS: the file lands on the server's filesystem, which the caller cannot read and which is wiped on the next deploy. So the tool returns the CSV itself — header row plus one row per invocation — which the caller can save, paste into a spreadsheet, or read directly.

Two caps keep that safe:

  • at most 1000 rows per call (limit, default 200, most recent first, then returned oldest-first), and

  • a 4 MB response ceiling, below the 6 MB hard cap on hosted MCP responses, after which the CSV ends with a # truncated at N of M rows comment line.

Audit cells are capped at 1000 characters

Each of the inputs and outputs columns holds at most 1000 characters of JSON. Anything larger collapses to a still-valid JSON object recording the original size and a short preview:

{"truncated_chars": 41297, "preview": "{\"candidates\": [\"CABLE\", ..."}

This matters more than it sounds. A single Wordle result serialized whole runs to roughly 9 KB, and a one-letter crossword pattern matches thousands of words — a handful of those rows makes the exported CSV unreadable in any spreadsheet. The cap is a promise about the stored value: the preview is shrunk until the finished JSON object fits in 1000 characters, escaping included.

The audit trail is an audit trail and a ready-made eval dataset: every row is an input/output pair that nobody had to write by hand. That only exists because the calls funnel through one place.


Related MCP server: PostgreSQL MCP Server

Environment variables

Name

Required

Purpose

DATABASE_URL

for the audit log

Postgres connection string, e.g. postgresql://user:password@host:5432/dbname. The role needs CREATE on the database (to create the mcp_training schema on first run) and read/write on that schema thereafter.

There are no other configuration variables, and no secrets are committed to this repository. If DATABASE_URL is unset or the database is unreachable:

  • the solvers still work. A logging failure must never break a tool call, so a failed audit write is reported to stderr and swallowed. Repeats are collapsed so a standing misconfiguration cannot flood the logs.

  • usage_graph and export_results fail with a ToolError naming DATABASE_URL and what to set it to — they have nothing to report without it.

The schema

Everything lives in — and is qualified to — a schema of its own, mcp_training. Point DATABASE_URL at an empty database and the server creates it on startup; point it at a database that already has it and the startup DDL is a no-op.

CREATE SCHEMA IF NOT EXISTS mcp_training;

CREATE TABLE IF NOT EXISTS mcp_training.invocations (
  id          BIGSERIAL   PRIMARY KEY,
  ts          TIMESTAMPTZ NOT NULL DEFAULT now(),
  agent_name  TEXT        NOT NULL,
  tool        TEXT        NOT NULL,
  inputs      JSONB       NOT NULL,
  outputs     JSONB       NOT NULL,
  duration_ms INTEGER     NOT NULL,
  ok          BOOLEAN     NOT NULL
);

CREATE INDEX IF NOT EXISTS invocations_ts_idx    ON mcp_training.invocations (ts DESC);
CREATE INDEX IF NOT EXISTS invocations_agent_idx ON mcp_training.invocations (agent_name);

Every statement the server issues names mcp_training.invocations in full. Nothing depends on search_path, and the server never touches an object outside its own schema — it is expected to share a database instance with unrelated applications, where a bare table name is a genuine hazard rather than a style preference.

A connection that fails at startup is retried at most once every 30 seconds rather than being written off for the life of the process, and connections come from a small pool (1–4), not one per call.

Postgres rather than SQLite because the deployment target has an ephemeral filesystem: a local database file disappears on redeploy, and "monitor usage over time" is meaningless if the counter resets.


Run it locally

Python 3.12 (3.11–3.14 all work).

git clone https://github.com/overclock-accelerator/mcp-puzzlemaster
cd mcp-puzzlemaster
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

Verify it — no database and no API key needed:

python test_server.py

This drives the server with FastMCP's in-memory Client(mcp), which speaks the real MCP protocol to the server object in the same process: no subprocess, no port, no network. Without DATABASE_URL it checks the solvers and asserts that the reporting tools fail with a clear message; with DATABASE_URL set it also round-trips the audit log through Postgres — including a check, run with search_path deliberately set to pg_catalog, that rows really do land in mcp_training.invocations and not wherever the connection's search path happened to point.

Run it over stdio (what a local MCP client speaks):

python server.py

Inspect what a client will see — this is the same inspection the deployment build runs, so if it fails here it will fail there:

fastmcp inspect server.py:mcp

Point Claude Code at your local copy:

claude mcp add puzzlemaster -- python /absolute/path/to/server.py

Deploying to Prefect Horizon

Horizon builds from a Git repository and redeploys on every push to the default branch.

  1. Push this repository to GitHub and sign in at horizon.prefect.io.

  2. Connect the Horizon GitHub App and grant it access to the repository. For a repository owned by a GitHub organization, the person completing that popup must be an admin of that organization.

  3. Create a server, and set the entrypoint explicitly to:

    server.py:mcp

    The build system has no default entrypoint — this is the most common reason a first build never goes live.

  4. Add DATABASE_URL under Settings → Environment Variables. Environment variables are injected at build time as well as runtime, so changing one requires a new build and deploy; editing the value does not touch the running server.

  5. The server goes live at https://<name>.fastmcp.app/mcp over Streamable HTTP.

What this repository does to satisfy the platform:

  • .python-version pins 3.12. Horizon supports 3.11–3.14. It does not support 3.10.

  • requirements.txt is discovered automatically and pins fastmcp exactly. fastmcp must be a dependency even though the platform runs the server for you — it uses the fastmcp CLI to install, inspect, and launch it.

  • data/enable1.txt is committed (~1.7 MB) and resolved as Path(__file__).parent / "data" / "enable1.txt", never from the working directory. Committed files ship inside the build artifact; the ephemeral- filesystem warning applies to files the server writes at runtime.

  • The word list loads at import time, which is a deliberate trade: it adds to cold start once instead of adding latency to every request.

  • Nothing is ever printed to stdout. Under stdio transport stdout is the JSON-RPC channel and a single stray print corrupts the stream. All diagnostics go to stderr, which the platform surfaces as server logs.

  • if __name__ == "__main__": mcp.run() is kept for local stdio testing. The deployed object is the module-level mcp; the platform serves it over HTTPS itself.

Connect a client (Horizon authentication is on by default, so callers present a bearer key or sign in interactively):

claude mcp add --transport http puzzlemaster https://<name>.fastmcp.app/mcp

Agent identity

agent_name is an ordinary tool parameter, which means the model fills it in. It is honest attribution for telemetry, not authentication — a model can get it wrong, and a hostile one can simply lie. It is the right baseline for a server that also runs over stdio, where there is no header channel at all.

Over HTTP the production answer is different: inject the caller's identity from the authenticated request with FastMCP's Depends(), which excludes the parameter from the generated schema entirely so the model can neither see nor set it. Horizon's gateway also records the acting user or service account for every request before it reaches this code, independently of what the model claims.


Layout

mcp-puzzlemaster/
├── server.py           # the whole server — entrypoint is server.py:mcp
├── test_server.py      # in-memory protocol tests, no key and no DB required
├── requirements.txt    # pinned
├── .python-version     # 3.12
└── data/enable1.txt    # ENABLE1, 172,823 words, public domain

The solver function bodies in server.py are byte-identical to their reference implementations and are meant to stay that way. The @mcp.tool wrappers around them are the seam: they add agent identity, hand in the shared word list, and translate ValueError into ToolError. Add a sixth tool tomorrow and it is audited for free, because the audit lives in middleware at on_call_tool rather than inside any solver.

Credits

data/enable1.txt is the ENABLE1 word list, which is in the public domain.

License

MIT — see LICENSE. Copyright (c) 2026 Overclock Accelerator.

A
license - permissive license
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    A serverless backend that enables natural language querying of a Postgres database, converting user questions into SQL and returning structured, UI-friendly responses.
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables natural language interaction with PostgreSQL databases, supporting query execution, schema management, data operations, user management, and database maintenance with secure remote access via HTTP/SSE transport.
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables querying PostgreSQL and MySQL databases using natural language, with RESTful endpoints for listing tables, describing schemas, and executing read-only queries.
    1

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Overclock-Accelerator/mcp-puzzlemaster'

If you have feedback or need assistance with the MCP directory API, please join our Discord server