ext-apps/examples/arcade-server
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-01-29 20:55:57 +02:00
..
.gitignore examples: add MCP Apps arcade server (#346) 2026-01-29 19:15:32 +02:00
game-processor.ts examples: add MCP Apps arcade server (#346) 2026-01-29 19:15:32 +02:00
grid-cell.png examples: add arcade example to servers (#399) 2026-01-29 20:55:57 +02:00
index.ts examples: add MCP Apps arcade server (#346) 2026-01-29 19:15:32 +02:00
package.json examples: add MCP Apps arcade server (#346) 2026-01-29 19:15:32 +02:00
README.md examples: add MCP Apps arcade server (#346) 2026-01-29 19:15:32 +02:00
search.ts examples: add MCP Apps arcade server (#346) 2026-01-29 19:15:32 +02:00
server.ts examples: add arcade example to servers (#399) 2026-01-29 20:55:57 +02:00
tsconfig.json examples: add MCP Apps arcade server (#346) 2026-01-29 19:15:32 +02:00

Example: Arcade Server

An MCP Apps server that lets you browse and play classic arcade games from archive.org directly in an MCP-enabled host.

Overview

This example demonstrates serving external HTML content as an MCP App resource. The resource is a static loader that uses the MCP Apps protocol to receive tool arguments, then fetches the processed game HTML from a server endpoint. This pattern allows the same resource to display different games based on tool input.

Key techniques:

  • MCP Apps protocol handshake (ui/initialize → ui/notifications/tool-input) to receive game ID dynamically
  • Server-side HTML fetching and processing per game ID
  • <base href> tag for resolving relative URLs against archive.org
  • baseUriDomains CSP metadata to allow the base tag
  • Rewriting ES module import() to classic <script src> loading (for srcdoc iframe compatibility)
  • Local script endpoint to bypass CORS restrictions in sandboxed iframes

Key Files

  • server.ts - MCP server with tool and resource registration
  • index.ts - HTTP transport and Express setup
  • game-processor.ts - Fetches and processes archive.org HTML
  • search.ts - Archive.org search with smart fallbacks

Getting Started

npm install
npm run dev

The server starts on http://localhost:3002/mcp by default. Set the PORT environment variable to change it.

MCP Client Configuration

{
  "mcpServers": {
    "arcade": {
      "url": "http://localhost:3002/mcp"
    }
  }
}

Tools

Tool Description UI
search_games Search archive.org for arcade games by name No
get_game_by_id Load and play a specific game Yes

How It Works

1. Host calls search_games → Server queries archive.org API → Returns game list
2. Host calls get_game_by_id → Tool validates gameId and returns success
3. Host reads resource → Gets static loader with MCP Apps protocol handler
4. View performs ui/initialize handshake with host
5. Host sends tool-input with gameId → View fetches /game-html/:gameId
6. Server fetches embed HTML from archive.org and processes it:
   - Removes archive.org's <base> tag
   - Injects <base href="https://archive.org/"> for URL resolution
   - Rewrites ES module import() to <script src> loading
   - Fetches emulation.min.js, patches it, serves from local endpoint
   - Injects layout CSS for full-viewport display
7. Game runs: emulator loads ROM, initializes MAME, game is playable

Why the Processing?

Archive.org's game embed pages use ES module import() for loading the emulation engine. In srcdoc iframes (used by MCP hosts), import() fails because the iframe has a null origin. The server works around this by:

  1. Fetching emulation.min.js server-side and replacing import() with window.loadScript()
  2. Serving the patched script from a local Express endpoint (/scripts/emulation.js)
  3. Using <script src> which is not subject to CORS restrictions, unlike fetch() or import()

Example Game IDs

  • arcade_20pacgal - Ms. Pac-Man / Galaga
  • arcade_galaga - Galaga
  • arcade_sf2 - Street Fighter II
  • doom-play - The Ultimate DOOM