On this page
MCP tool server for PixelWeaver.
#server.src.pixelweaver.mcp_server
#server.src.pixelweaver.mcp_server
MCP tool server for PixelWeaver.
Exposes PixelWeaver operations as MCP tools so that LLMs (via Claude Code, Claude Desktop, etc.) can programmatically create and edit pixel art.
Runs as a separate process using stdio transport. Start with: pixelweaver mcp [--data-dir ./projects]
Architecture ------------ The MCP server does NOT own its own state. Before each tool execution it pulls the latest state from the collaboration server (HTTP on port 7779), runs the tool logic locally, then pushes the modified state back. The collab server broadcasts a full-state patch to all WebSocket clients so the frontend sees every MCP mutation in real time.
The collaboration server must be running for MCP to work. If it is unreachable the MCP server returns a clear error to the LLM.
#_ensure_initialized
def _ensure_initialized() -> MCPCommandRegistryLazily initialize the local state/registry on first tool call.
Unlike the old architecture, we do NOT load projects from disk here. State is synced from the collab server before each tool execution (see _sync_from_collab / _push_to_collab in the handler wrapper).
#_sync_from_collab
async def _sync_from_collab() -> NonePull the latest state from the collab server into _state.
Raises CollabServerUnreachableError if the server cannot be reached.
#_push_to_collab
async def _push_to_collab() -> NonePush the current _state to the collab server (triggers WS broadcast).
Raises CollabServerUnreachableError if the server cannot be reached.
#mcp_lifespan
async def mcp_lifespan(server: FastMCP)Initialize state when the MCP server starts.
#_format_result
def _format_result(result: dict[str, Any]) -> list[TextContent | ImageContent]Convert a registry result dict into MCP content blocks.
Mutation results include a thumbnail as an ImageContent block. All results include a TextContent block with the JSON data.
#_error_result
def _error_result(message: str) -> CallToolResultBuild an isError CallToolResult carrying an error payload.
#_dispatch_tool_call
async def _dispatch_tool_call(tool_name: str, arguments: dict[str, Any]) -> CallToolResultExecute one MCP tool through the pull -> execute -> push flow.
- Pull the latest state from the collab server.
- Execute the tool locally via the registry with the FLAT arguments.
- Push modified state back (mutating tools that succeeded).
A tool-level {"success": false} result maps to isError=True so clients see logical failures as MCP errors, not transport successes.
#_register_tool_handlers
def _register_tool_handlers(registry: MCPCommandRegistry) -> NoneRegister list_tools/call_tool on the low-level MCP server.
The handlers close over registry for the tool definitions but resolve the live registry inside _dispatch_tool_call so state stays consistent.
#init_mcp_tools
def init_mcp_tools() -> NoneRegister all tools/resources onto the MCP server.
Call this explicitly from the server startup path. Importing this module no longer has side effects; this keeps tests and tooling from paying the cost of initializing the MCP registry unless they actually need it.
#run_mcp_server
def run_mcp_server() -> NoneRun the MCP server on stdio transport (blocking).