Tank Fight MCP
Serves the canonical Tank Fight specification, enabling clients to query game parameters, maze layouts, and rules via MCP tools.
README
Tank Fight MCP — the game spec as a service
An MCP server that serves the canonical specification for Tank Fight: field and window size, tank and bullet stats, spawn rules, scoring, controls, menu behaviour, AI strategies, the difficulty ramp, and a destructible maze per level.
It runs as a local service on a port. Any MCP client can connect, ask what tools it offers, and query the specification — while building the game in Java, in TypeScript, or in anything else.
What it's for
Two jobs, and the second matters more than it first looks.
1. Keep independent implementations in agreement. Two builds of the same game, written separately, drift the moment they need a number nobody wrote down. Both asking the same service how big a tank is removes the guesswork.
2. Stop the model inventing requirements. Every detail a spec leaves out is
a place where an AI coding assistant quietly picks something plausible and moves
on — do bullets stop at walls? can allies shoot each other? what happens when a
player is destroyed mid-round? It rarely mentions that it decided. Each of
those is answered here, so the question becomes a lookup instead of a guess, and
open_questions names what genuinely isn't decided, so the honest answer is
"ask a human" rather than a confident invention.
Run it
Requires uv.
uv sync
uv run server.py
That serves the specification at http://127.0.0.1:8082/mcp. Use --port to
listen elsewhere, --host to bind another interface, and --stdio for clients
that only speak stdio.
Connect a client
Any MCP client, any number of them at once. For Claude Code:
claude mcp add --transport http tank-spec http://localhost:8082/mcp
Add --scope project to record it in the repository you're building in, so
everyone working on that build gets the same specification. Then /mcp to
confirm the connection, and the client can ask the server what tools exist and
how to call them — nothing here needs configuring per client.
Tools
| Tool | Answers |
|---|---|
list_sections() |
What's in the spec, one line each — start here |
get_spec(section) |
One section, or a dotted path like entities.bullet.damage |
search_spec(query) |
"Does X happen?" — finds the rule that decides it |
resolve_level(level) |
What level N means: total enemies, max concurrent, spawn interval, which maze |
get_maze(level) |
That level's brick layout, expanded into tiles, with an ASCII picture |
open_questions() |
What the spec deliberately hasn't decided |
What this server deliberately does not do
It has no idea who is connected or what they are building, and that is on purpose. It knows about the specification; clients know about the server. Nothing points the other way.
So checking whether an implementation still matches the spec is the client's job. A client can read the values out of its own code and compare them against what these tools return — it is sitting in that repository and the server is not. A server that reached into a checkout to inspect it would have to know the language, the file layout and the path, and would need updating every time another implementation appeared.
The same reasoning is why the spec is served rather than shipped as a file:
- It's outside every implementation. A session working in one repository can't read another's files, but it can call a service.
- It's queried, not dumped.
search_spec("friendly fire")returns one rule. Loading the whole specification into a model's context to answer one question is exactly the waste this avoids. - Some answers are computed, not stored. Levels are generated from a ramp
formula — level 6 exists in no file.
resolve_levelis the only correct way to ask, and its integer truncation is load-bearing: a floating-point version disagrees at four of the eight levels.
Destructible brick and per-level mazes
Everything inside the border is brick, and brick can be shot away — by both sides, since sides matter for tanks and never for walls. A tile is 20x20 and takes 40 damage, so two standard bullets open a hole that tanks then drive through. The border itself is immune.
Each level has its own maze, so the level number is the only difficulty knob: it sets how many enemies arrive, how fast, and the terrain they arrive into. Level 1 is nearly open; level 8 is dense enough that shooting a path is usually faster than finding one.
get_maze(level) returns the rectangles, the expanded tile list, and a picture:
########################################
#EE................EE................EE#
#EE................EE................EE#
#......................................#
#......BBBBBBBBBBB....BBBBBBBBBBB......#
...
#.............AA...AA...AA.............#
########################################
Use the expanded tiles rather than expanding the rectangles yourself — that step, and the rule for which tile a bullet damages when it straddles two, are where two builds most easily end up digging different holes from the same shots.
Adding a maze is just adding rectangles under mazes.levels in the YAML. The
test suite then holds it to the invariants that make a level playable:
tile-aligned, inside the area, non-overlapping, spawn and ally boxes clear, and
every entry point reachable on foot without destroying anything — digging is a
shortcut, never a requirement.
Editing the spec
spec/game-spec.yaml is the whole thing, and it's meant to be edited by hand —
that's the point. The server re-reads it on every call, so a change takes effect
immediately without restarting anything.
The specification was seeded from a working implementation and is independent of it from that point on. It leads: change a value here first, then make the implementations follow.
Tests
uv run pytest
tests/test_spec.py pins the spec's values and holds every maze to the
playability invariants. tests/test_server.py drives the server through an MCP
client — discover the tools, read their descriptions, call them.
tests/test_http.py starts the real process on a port and connects to it over
HTTP, the way a client does.
Layout
spec/game-spec.yaml the specification — the actual source of truth
server.py MCP server: tool definitions, their descriptions, and the transport
tank_spec/spec.py loading, searching, level maths, maze expansion
tests/ spec and maze invariants, MCP integration, HTTP transport
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.
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.
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.
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.