Skip to main content
Glama

shooting-game MCP server

Lets Claude (via Claude Desktop or Claude Code) join a real match of the shooting game as the second player and actually play it — reading the arena through a real, live browser and moving/shooting through the same inputs a human would use.

It does not re-implement any game logic. Each tool call drives a headless (or visible) Chromium instance pointed at the real Angular app via Playwright, reading state out of the DOM and sending real keyboard input — the same trick used to test the vs computer bot in this repo. That keeps it perfectly in sync with whatever the game actually does, with nothing to duplicate or drift.

How a match works

  1. A human opens the game and clicks "vs claude (mcp)" on the home screen. This creates a normal friendly match and shows a match code (the same underlying flow as "create match", just with different instructions on screen).

  2. The human gives Claude that code.

  3. Claude calls join_match with the code — this is exactly the "join match by id" flow a second human player would use. Claude is always the second player to join (PLAYER_B).

  4. Claude calls get_state, move, shoot, and optionally screenshot to actually play.

Related MCP server: the-mogiyoon-mcp

Setup

npm install       # also downloads a Chromium build for Playwright
npm run test:smoke   # optional: verifies the whole pipeline against a live game

Environment variables

Variable

Default

Purpose

GAME_CLIENT_URL

https://shooting-game-eight.vercel.app

Base URL of the Angular client to connect to. Point this at http://localhost:4200 to play against a local ng serve.

MCP_HEADLESS

false

Set to true to run the browser headless. Leave it false if you want to actually watch Claude play.

Register with Claude Code

claude mcp add shooting-game -- node /absolute/path/to/mcp-server/index.js

(or add it to a project's .mcp.json with the same command/args.)

Register with Claude Desktop

Add an entry to claude_desktop_config.json:

{
  "mcpServers": {
    "shooting-game": {
      "command": "node",
      "args": ["/absolute/path/to/mcp-server/index.js"],
      "env": {
        "GAME_CLIENT_URL": "https://shooting-game-eight.vercel.app",
        "MCP_HEADLESS": "false"
      }
    }
  }
}

Hosted connector (claude.ai)

There's a second entry point, http-server.js, that serves the same tools over the MCP Streamable HTTP transport instead of stdio — this is what lets someone add the game as a custom connector in claude.ai (Settings → Connectors → Add custom connector), rather than only from Claude Code/Desktop on the machine that's running the process.

Unlike the stdio entry point, this one is multi-tenant: it hands each connecting client its own GameSession (so unrelated users don't share a browser/match), tracked by the standard Mcp-Session-Id session header. An idle session (no tool calls for SESSION_IDLE_TIMEOUT_MS, default 15 minutes) has its browser closed automatically to free resources.

npm run start:http   # listens on $PORT (default 3000), endpoint is POST /mcp

Deploying (Render): this needs a persistent Node process with Chromium installed, not a serverless function — build the included Dockerfile (based on the official mcr.microsoft.com/playwright image, pinned to the same Playwright version as package.json) as a Render Web Service. No extra configuration is required: Render sets $PORT automatically, and the image already sets MCP_HEADLESS=true.

Then in claude.ai: Settings → Connectors → Add custom connector → paste https://<your-render-app>.onrender.com/mcp as the URL, no authentication.

Resource limits: because the connector has no auth, anyone with the URL can open a session, each of which holds a real Chromium process. Two knobs guard against that:

Variable

Default

Purpose

MAX_CONCURRENT_SESSIONS

6

Hard cap on simultaneous browser sessions across all connected clients; join_match fails with a "Server busy" error past this.

SESSION_IDLE_TIMEOUT_MS

900000 (15 min)

How long a session can go without a tool call before its browser is closed and the slot freed.

Tools

Tool

Purpose

join_match({ code? })

Join a friendly match by code (or random matchmaking if omitted). Launches the browser session.

get_state()

Round, score, your position/health, opponent's position/health, arena size, any round/match-end announcement.

move({ direction, holdMs? })

Hold ArrowUp/ArrowDown/ArrowLeft/ArrowRight. Also sets facing — see below.

shoot({ holdMs? })

Fire in the direction you last moved.

screenshot()

PNG of the current arena, if Claude would rather look than read coordinates.

leave_match()

Disconnects and closes the browser.

There is no separate aim. The game only supports firing in whichever of the 4 cardinal directions you last moved in — rotation and shot direction are the same thing. To land a hit, get on the same row or column as the opponent (within roughly half a player-width) and shoot while facing that way.

Notes

  • The stdio entry point (index.js) holds exactly one browser session for its whole process lifetime — call leave_match before join_match-ing again if you want a clean session. The HTTP entry point (http-server.js) gives each connected client its own session instead (see above).

  • Because the real Node relay server has no server-side game state, hit detection happens independently in each player's own browser from the same relayed inputs. There's no separate "who's the source of truth" concern here beyond what the game already has for any two human players.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

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

Related MCP Connectors

Related MCP Servers

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/Achtrick/shooting-game-mcp'

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