npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

foodchain-mcp

v1.1.7

Published

MCP server that lets an AI agent play Food Chain Magnate alongside humans.

Readme

foodchain-mcp — Food Chain Magnate agent bridge (MCP server)

A thin Model Context Protocol server that lets an AI agent (any MCP client) play Food Chain Magnate in a real multiplayer game, taking one seat alongside human players.

It is a bridge, not a bot: it holds one authenticated Socket.IO connection to the FCM server (exactly like the browser), mirrors the game state, and exposes tools to read state and submit moves. The FCM server stays the rules authority — illegal moves are rejected and the reason is surfaced back. All the reasoning lives in the MCP client (the agent).

Install

No clone or build needed — add one stdio entry to your MCP client (defaults target the public production deployment):

{
  "mcpServers": {
    "fcm": { "command": "npx", "args": ["-y", "foodchain-mcp"] }
  }
}

On the first join_game the bridge opens your system browser for Keycloak login; the refresh token is cached locally so it won't ask again. To target a different deployment, set the env vars below.

Compatibility: the bridge is released lockstep with the FCM server — version X.Y.Z targets server X.Y.Z. Use the package version that matches your server deployment.

How a game works

The AI fills one seat in an otherwise-normal human game:

  1. A human creates a room in the browser and shares the 6-character code — or the agent hosts it: create_game makes the room and returns the code to share, and start_game launches it once everyone has joined and readied.

  2. In an MCP client configured with this server, you kick the agent off with a prompt (the server ships tools only — no bundled prompt). Example:

    You're playing Food Chain Magnate. Call join_game for room ABC123, then loop wait_for_turn → (decide) → take_action until the game ends. Only call get_state when you have a real decision and get_board only to place something; confirmations are just wait_for_turntake_action. Play to win and confirm promptly so you don't stall the table.

  3. join_game auto-readies the agent's seat. A human host clicks Start. The agent acts only on its own turns; its moves render on everyone's board, its reasoning shows in the MCP chat.

Tools

The tools are pull-based: responses stay lean by default and the agent fetches heavy detail (the board) only when a move needs it. This keeps the conversation transcript small over a long game, which keeps each turn fast.

| Tool | Purpose | |------|---------| | join_game({ roomCode, displayName?, chain?, username?, seatTag?, observer? }) | Authenticate, connect, join by code, auto-ready. Pass observer: true to spectate; otherwise falls back to an observer seat (reported explicitly) only if no player seat is free. | | create_game({ displayName?, chain?, username?, seatTag? }) | Authenticate, connect, and create a new room, taking the (auto-readied) host seat. Returns the 6-character roomCode to share; watch the lobby roster via get_state. | | start_game() | Host only: start the game created with create_game (needs ≥2 seated players, all ready). Returns the opening setup digest. | | get_state({ full? }) | Lean seat-relative snapshot: phase, money, food, your hand + org chart, opponents (named hand, org chart, claimed milestones), available milestones, per-phase actionHints, recentLog, and boardRev. No board geometry/ASCII — call get_board for that. { full: true } embeds the board + ASCII too. | | get_board({ ascii? }) | The structured board for placement: houses (number + cells + demand), gardens, restaurants, campaigns, and static drinkSources + size — exact [row,col]. Returns rev; re-call only when boardRev advances. { ascii: true } adds the visual overview. | | get_raw_state() | Escape hatch: the full projected GameState JSON. | | wait_for_turn({ timeoutMs? }) | Block until it's your turn, the game ends, or timeout (default 90s). Returns a status + the lean snapshot; re-call to keep waiting. Observer seats wake on each live state change (observed: true) instead. | | take_action({ action, full? }) | Submit one move. action.type is the action name (recruit, placeRestaurant, confirmDinnertime, skipFood, …); other fields are the payload. Returns a curated delta of what changed (money, hand, milestones, board mutations, new log lines) + new phase + boardRev — not a full dump. On rejection: the error + actionHints. { full: true } returns the whole digest instead. | | get_log({ limit? }) | Recent game-log lines. | | get_rules({ section? }) | The FCM rulebook. Omit section for the full text, or pass a heading (e.g. Dinnertime, Recruit, Marketing, Milestones) for one section. | | describe_actions({ type? }) | Exact parameter structure for take_action — every action's params (name, type, required, meaning, where to read the value from get_state) + an example. Self-contained; use it instead of guessing. |

How the agent learns the game

Two channels, both MCP-native — no kickoff prompt required:

  • Server instructions (always-on): on connect the server hands the client a compact primer — what FCM is, the pull-based join_game → wait_for_turn → (decide) → take_action loop, the phase flow, the action types per phase, and how/when to read the board. Most clients feed this to the model automatically.
  • get_rules tool (on-demand): the full rulebook, or a single section.

wait_for_turn and get_state both carry actionHints (legal options/budgets for the current phase), so for confirmations and simple skips the agent can act straight from wait_for_turn without any extra read.

Authentication

The bridge acquires a Keycloak token via the first method that applies:

  1. Cached refresh token — silent; survives restarts (see cache below).
  2. Password grant — used when FCM_USERNAME + FCM_PASSWORD are set. For headless/CI runs that can't open a browser.
  3. Browser login (Authorization Code + PKCE) — the default when no password is configured: on first join_game the system browser opens for Keycloak login and the redirect is captured on a loopback port. No password is stored in the config.

Token cache. The refresh token is stored at ${XDG_CONFIG_HOME:-~/.config}/fcm-mcp/tokens.json (file mode 0600), so the browser opens only once. Delete that file to force a fresh login / switch accounts.

Keycloak client for browser login. Use a public client with PKCE (S256) and a registered loopback redirect URI (http://127.0.0.1/*). Set it via KEYCLOAK_CLIENT_ID.

Configuration (env)

| Var | Default | Meaning | |-----|---------|---------| | FCM_SERVER_URL | production | FCM Socket.IO server URL | | KEYCLOAK_ISSUER | production realm | Keycloak realm issuer URL | | KEYCLOAK_CLIENT_ID | production client | Keycloak client id (public PKCE client) | | FCM_USERNAME | (unset) | Account for password login | | FCM_PASSWORD | (unset) | Password — set this to use password login instead of the browser |

Pointing at another deployment:

"fcm": {
  "command": "npx",
  "args": ["-y", "foodchain-mcp"],
  "env": {
    "FCM_SERVER_URL": "https://your-fcm-server",
    "KEYCLOAK_ISSUER": "https://your-keycloak/realms/<realm>",
    "KEYCLOAK_CLIENT_ID": "<public-pkce-client>"
  }
}

Seat label

So the table can tell which account an agent runs on, the seat is named <name> [<owner full name>]<name> is the agent name from join_game's displayName (default Agent), and <owner full name> is the authenticating account's name (given_name family_name from the token, falling back to name, then the username). e.g. join_game({ roomCode, displayName: "Gordon" }) shows up as Gordon [Luis Hsu].

An agent can take its own seat even on the same account a human is using, via seatTag (default mcp) — the human (untagged) and the agent (tagged) each get a distinct seat. Each Keycloak identity+tag maps to exactly one seat.

Spectating (observer agents)

Call join_game({ roomCode, observer: true }) to join as a spectator. An observer can't take_action, but it sees the full board via get_state / get_raw_state / get_log, and wait_for_turn wakes on every live state change (returns observed: true) — so a commentator/analyst agent can react turn-by-turn. The watcher loop is simply:

wait_for_turn → get_state → (commentate) → repeat   # until gameOver

(Without observer: true, a normal join only becomes an observer as a fallback when no player seat is available, and reports that in the result.)

Local development (from source)

npm install
npm run build -w mcp          # type-check build → mcp/dist
npm run bundle -w mcp         # single-file bundle → mcp/dist/fcm-mcp.mjs
npm test -w mcp               # unit tests
node mcp/scripts/list-tools.mjs   # spawn the server and list its tools

Point your MCP client at the local build with env overrides for your dev stack:

"fcm": {
  "command": "node",
  "args": ["/path/to/FCM/mcp/dist/index.js"],
  "env": {
    "FCM_SERVER_URL": "http://localhost:3000",
    "KEYCLOAK_ISSUER": "https://your-dev-keycloak/realms/<dev-realm>",
    "KEYCLOAK_CLIENT_ID": "<dev-pkce-client>"
  }
}

For password login against a dev realm, add FCM_USERNAME/FCM_PASSWORD.

Troubleshooting

  • Agent never gets a turn / "waiting for host" — a human host must click Start, and every player must be ready. join_game auto-readies the agent.
  • Joined as observer — the room was full or already in progress; start a fresh room.
  • Browser login rejected — the Keycloak client must be public, PKCE-enabled, and have a loopback redirect URI (http://127.0.0.1/*) registered.
  • Switch accounts — delete ~/.config/fcm-mcp/tokens.json to force a fresh login.