魂斗罗 MCP Demo
by code-cheers
README.md
# 魂斗罗 MCP Demo
This project follows the architecture from Chris Nager's DOOM MCP App write-up, but uses an original placeholder browser game with Contra-style horizontal shooting.
The important part is not "MCP creates a game." The point is:
> MCP can return a live app UI, not just text or JSON. In compatible clients, the game appears inside the conversation itself.
No original Contra assets, music, characters, levels, or ROM data are included.
## Main Flow
- `create_game_session`: creates an inline game session and returns MCP App metadata so compatible hosts can render the game inline. It intentionally does not expose the browser fallback URL.
- `get_game_launch_url`: creates a signed `/game/play?token=...` URL for hosts that cannot render MCP Apps inline.
- `/game/play?token=...`: browser fallback route. The token is enough to boot the same game session without durable server-side session storage.
- `ui://contra-style/mcp-app.html`: the inline MCP App resource. It listens for the `create_game_session` tool result, extracts the session, and runs the game directly in the host iframe.
Optional extension tools:
- `update-game-state`: the MCP App can publish score, HP, position, enemies, ammo, and stage.
- `get-game-state`: reads the latest state.
- `suggest-next-move`: returns a Chinese gameplay suggestion from the latest state.
## Run
```bash
npm install
npm run build
npm run serve
```
MCP endpoint:
```text
http://localhost:3001/mcp
```
## Use From Codex Or Another MCP Host
Ask the host to call:
```text
create_game_session
```
If the host supports MCP Apps, it should render the game inline from:
```text
ui://contra-style/mcp-app.html
```
That is the primary demo: the host calls a tool, and the tool result causes a playable game UI to appear in the chat surface. Do not show or open a fallback link in this path.
If inline rendering is unavailable, call:
```text
get_game_launch_url
```
Then open the returned `/game/play?token=...` URL.
## Use From Claude Desktop
Claude Desktop renders MCP App UI resources inline in the chat (the client advertises the `io.modelcontextprotocol/ui` capability with `text/html;profile=mcp-app`). The matching local config:
`~/Library/Application Support/Claude/claude_desktop_config.json`
```json
{
"mcpServers": {
"contra-demo": {
"command": "npx",
"args": ["-y", "mcp-remote@latest", "http://localhost:3001/mcp", "--allow-http"]
}
}
}
```
The `mcp-remote` bridge runs as a stdio child process inside Claude Desktop and forwards JSON-RPC to the local HTTP endpoint. The `--allow-http` flag is required because `mcp-remote` defaults to refusing non-HTTPS URLs. Quit and reopen Claude Desktop after editing the config, then ask Claude to call `create_game_session` — the playable game appears as a widget in the conversation.
> **Note:** the *Custom Connectors* UI on `claude.ai` (web) is **Remote MCP** — those requests originate from Anthropic's servers and cannot reach `localhost`. Use Claude Desktop with the file-based config above for a local URL, or expose the server over a public tunnel for the web Connector route.
### Run as a background service (macOS LaunchAgent)
If you start the server from an interactive shell or a Claude Code session, it dies when you quit that process. To keep it always-on, register it with `launchd`:
`~/Library/LaunchAgents/com.contra-demo.mcp.plist`
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key> <string>com.contra-demo.mcp</string>
<key>ProgramArguments</key> <array>
<string>/PATH/TO/npm</string>
<string>run</string>
<string>serve</string>
</array>
<key>WorkingDirectory</key> <string>/PATH/TO/deep-mcp</string>
<key>EnvironmentVariables</key><dict>
<key>PATH</key> <string>/PATH/TO/node/bin:/usr/bin:/bin</string>
</dict>
<key>RunAtLoad</key> <true/>
<key>KeepAlive</key> <true/>
<key>StandardOutPath</key> <string>/tmp/contra-demo-mcp.out.log</string>
<key>StandardErrorPath</key> <string>/tmp/contra-demo-mcp.err.log</string>
</dict>
</plist>
```
```bash
launchctl load -w ~/Library/LaunchAgents/com.contra-demo.mcp.plist # start + auto-start at login
launchctl list | grep contra-demo # check
launchctl unload -w ~/Library/LaunchAgents/com.contra-demo.mcp.plist # stop
```
`launchd` is independent of any GUI app, so quitting Claude Desktop / Terminal / IDE no longer kills the server. `KeepAlive=true` also restarts it if it crashes.
## Browser Fallback
After `npm run build && npm run serve`, call `get_game_launch_url` and open the returned URL.
For quick UI development only:
```bash
npm run dev
```
That opens the game without a signed session, so it is useful for visual testing but not the main demo flow.
## Controls
- Move: `WASD` or arrow keys
- Shoot: `Space` or `J`
- Restart: `R`
## Video Talking Points
- This is modeled after the DOOM MCP App architecture, not a from-scratch game-dev tutorial.
- The headline is: MCP can return an interactive game UI inside the conversation.
- The primary path is inline-only; the signed browser launch URL is exposed only by `get_game_launch_url`.
- The MCP App runs the game directly in the host iframe; it does not iframe another page inside the app.
- The real MCP lesson: protocol + UI resource + signed launch flow can turn a web capability into an AI-client-native app.
## Verification
```bash
npm run check
npm run build
curl -sS -X POST http://localhost:3001/mcp \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
--data '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
```
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues