Skip to content

feat: MCP agent for AI-powered Magic gameplay - #34

Open
reifocS wants to merge 26 commits into
mainfrom
feat/maginet-agent
Open

feat: MCP agent for AI-powered Magic gameplay#34
reifocS wants to merge 26 commits into
mainfrom
feat/maginet-agent

Conversation

@reifocS

@reifocS reifocS commented Mar 28, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add maginet-agent/ Node.js package — an MCP server that lets Claude Code play or advise on Magic: The Gathering games on a live Maginet board
  • Add browser-side WebSocket transport (src/sync/transport/websocket.ts) implementing the existing SyncTransport interface from @vescofire/peersync
  • Add "Connect Agent" UI panel to the setup screen for one-click agent connection

Two modes

  • Opponent mode — Agent loads its own deck, draws cards, plays them on the board. You see its moves appear in real time.
  • Copilot mode — Agent observes your game state (read-only) and gives advice when asked.

16 MCP tools

Observation: getGameState, getHand, getBoardState, getDeckInfo
Actions: loadDeck, loadSnapshot, drawCard, mulligan, playCard, tapCard, untapAll, sendToHand, sendToDeck, shuffleDeck, addCounter, removeCounter, flipCard, transformCard

How it works

  1. Claude Code spawns the MCP server (node maginet-agent/dist/index.js --port 3210)
  2. Browser connects via WebSocket to ws://localhost:3210
  3. Shapes sync through the existing diff/patch/snapshot channel system
  4. Agent decisions come from LLM reasoning — no built-in strategy engine

Test plan

  • 32 agent-side tests passing (state, visibility, server, MCP tools, e2e integration)
  • 26 browser-side tests passing (including 5 new WebSocket transport tests)
  • Lint clean (0 errors, 0 warnings)
  • Both packages build successfully
  • Manual test: start MCP server, open Maginet, click "Connect Agent", verify connection indicator

reifocS added 14 commits March 28, 2026 08:54
MCP server that lets Claude Code play MTG on a live Maginet board.
Two modes: opponent (plays against human) and copilot (read-only advice).
Connects via WebSocket transport alongside existing PeerJS sync.
12 tasks covering: package scaffold, game state, visibility filter,
Scryfall client, WebSocket server, MCP tools, CLI entry, browser
transport, peerStore wiring, setup UI, and e2e integration test.
Plain TypeScript class (no React/Zustand) that manages the agent's deck,
hand and board shapes with actionId tracking; mirrors useCardReducer and
useShapeStore patterns for the Node.js MCP context.
Implements AgentWebSocketServer (server.ts) with start/stop lifecycle,
single-client connection tracking, bidirectional JSON message passing,
and connect/disconnect event hooks. Covered by 4 integration tests.
Implements createToolHandlers() with 16 game tools (draw, play, tap,
counter management, etc.) wired to AgentGameState, plus TOOL_DEFINITIONS
array for MCP registration. Includes full TDD test suite (10 tests).
Wires AgentGameState, AgentWebSocketServer, and McpServer together.
Parses --port/--visibility CLI args, registers all MCP tools (including
loadDeck via Scryfall and loadSnapshot), and connects over stdio.
Adds zod dependency and fixes null-check in server.ts address handling.
Adds an AI Agent panel to the multiplayer step in SetupScreen that lets
users connect/disconnect a local MCP agent by port number (default 3210).
- Remove unused waitFor/wait helpers from e2e integration test
- Replace `server as any` with `server as unknown as AgentWebSocketServer` in test files
- Remove unused ToolContext import from mcp.test.ts
- Rename unused _args params to _ in mcp.ts handler functions
- Fix no-this-alias in websocket.test.ts by using a shared wsRef container object
- Remove unused _peerId parameter names from websocket.ts connect/disconnect
- Add argsIgnorePattern/varsIgnorePattern to ESLint config for _ prefix convention
@vercel

vercel Bot commented Mar 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
excalightdraw Ready Ready Preview, Comment Mar 28, 2026 11:34am

Adds an "Agent" pill button in the top-left toolbar next to "Connect".
Click to connect to the local MCP agent, click again to disconnect.
Shows "Agent On" with active styling when connected.
- Add CardMeta (name, typeLine, oracleText, manaCost, power, toughness)
  to Scryfall cards and expose via getHand/getBoardState
- Fix sync message handling: properly parse channel-snapshot and
  channel-patch payloads with nested structure
- Broadcast agent shape changes to browser on mutation and on connect
- Add placeText and updateText MCP tools for board annotations
- Track agent peer IDs in peerStore for proper connection filtering
- Add .mcp.json config for local development
- Track action-log and action-log-snapshot messages in the MCP server
- Add getActionLog MCP tool to query recent opponent actions
- Forward browser action-log entries to the agent via sendAgentMessage
- Add sendAgentMessage helper to peerStore for agent-directed messages
- Null ws reference on close in browser transport (prevents stale ref)
- Call syncClient.stop() on unexpected disconnect in peerStore
- Close previous client before accepting new one in agent server
  (handles browser page refresh without orphaning old socket)
sendMessage now forwards to the agent sync client, so custom messages
(action-log, heartbeat, card-state-sync) reach the agent without
manual sendAgentMessage calls. Remove sendAgentMessage — dead code.
Remove peer?.id guards on sendMessage calls so the agent receives
action-log, random-event, and card-state-sync messages even when
no PeerJS peer is connected (solo-vs-agent mode).
- Add lastPlayedSrcs to CardState and cardSrcs to ActionLogEntry
- Add moveShape and placeRect MCP tools
- Add fetchCardMetaByImageUrl for looking up opponent card metadata
- Add registerCardMeta and cardNames to action log entries
- Critical: fix Scryfall deduplication — deduplicate names before API
  call, expand results back to requested counts (4x Lightning Bolt now
  correctly produces 4 cards)
- Fix biased shuffle — replace sort-based shuffle with Fisher-Yates
- Fix untapAll N broadcasts — add batch updateAgentShapes method,
  untapAll now notifies listeners once instead of N times
- Fix card_faces null safety — filter faces without image_uris
- Fix loadSnapshot — clear existing shapes before loading new ones
- Fix unbounded actionLog — cap at 200 entries
- Fix WS server error handler — persist after startup instead of only
  during the start() promise
- Validate --visibility CLI argument
- Add clearAgentShapes method to AgentGameState
- Fix pushActionLog calling itself instead of actionLog.push (would
  stack overflow on first action log message)
- Replace .substr(2, 9) with .slice(2, 11) in all three files
- Add debugMessages MCP tool and WS message logging for sync debugging
- Send hand card data (id, src) in card-state-sync payload for copilot
- Use explicit opponentHandCount field in visibility filter
- Resolve unknown opponent card metadata via Scryfall image URL lookup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant