pixelweaver 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.development +1 -0
- package/.github/workflows/ci.yml +22 -0
- package/.github/workflows/publish.yml +18 -0
- package/.prettierignore +5 -0
- package/.prettierrc +16 -0
- package/.python-version +1 -0
- package/.rlsbl/bases/.github/workflows/ci.yml +21 -0
- package/.rlsbl/bases/.github/workflows/publish.yml +18 -0
- package/.rlsbl/bases/.rlsbl/changes/unreleased.jsonl +0 -0
- package/.rlsbl/bases/.rlsbl/hooks/post-release.sh +8 -0
- package/.rlsbl/bases/.rlsbl/hooks/pre-checks.sh +5 -0
- package/.rlsbl/bases/.rlsbl/hooks/pre-release.sh +8 -0
- package/.rlsbl/bases/.rlsbl/lint/go.toml +17 -0
- package/.rlsbl/bases/.rlsbl/lint/npm.toml +19 -0
- package/.rlsbl/bases/.rlsbl/lint/python.toml +25 -0
- package/.rlsbl/bases/CHANGELOG.md +5 -0
- package/.rlsbl/bases/LICENSE +21 -0
- package/.rlsbl/changes/.validated +1 -0
- package/.rlsbl/changes/0.1.0.jsonl +85 -0
- package/.rlsbl/changes/0.1.0.md +13 -0
- package/.rlsbl/changes/unreleased.jsonl +0 -0
- package/.rlsbl/config.json +6 -0
- package/.rlsbl/hashes.json +14 -0
- package/.rlsbl/hooks/post-release.sh +8 -0
- package/.rlsbl/hooks/pre-checks.sh +5 -0
- package/.rlsbl/hooks/pre-release.sh +8 -0
- package/.rlsbl/lint/go.toml +17 -0
- package/.rlsbl/lint/npm.toml +19 -0
- package/.rlsbl/lint/python.toml +25 -0
- package/.rlsbl/releases/unreleased.toml +0 -0
- package/.rlsbl/releases/v0.1.0.toml +3 -0
- package/.rlsbl/version +1 -0
- package/.selfdoc/hashes/hashes.json +146 -0
- package/.strictcli/schema.json +227 -0
- package/CHANGELOG.md +17 -0
- package/CLAUDE.md +100 -0
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/assets/icon.png +0 -0
- package/docs/_README.md +117 -0
- package/docs/cli-config.md +35 -0
- package/docs/cli-dev.md +21 -0
- package/docs/cli-diagnose.md +12 -0
- package/docs/cli-index.md +30 -0
- package/docs/cli-list.md +18 -0
- package/docs/cli-mcp.md +18 -0
- package/docs/cli-new.md +26 -0
- package/docs/cli-open.md +21 -0
- package/docs/cli-serve.md +21 -0
- package/docs/cli-stop.md +12 -0
- package/docs/gen-index.md +36 -0
- package/docs/index.md +13 -0
- package/docs/server-src-pixelweaver-__main__.md +12 -0
- package/docs/server-src-pixelweaver-autosave.md +12 -0
- package/docs/server-src-pixelweaver-bridge.md +12 -0
- package/docs/server-src-pixelweaver-cli.md +12 -0
- package/docs/server-src-pixelweaver-config.md +12 -0
- package/docs/server-src-pixelweaver-connections.md +12 -0
- package/docs/server-src-pixelweaver-main.md +12 -0
- package/docs/server-src-pixelweaver-mcp_bridge.md +12 -0
- package/docs/server-src-pixelweaver-mcp_drawing_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_export_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_frame_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_history_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_layer_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_lock.md +12 -0
- package/docs/server-src-pixelweaver-mcp_project_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_read_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_registry.md +12 -0
- package/docs/server-src-pixelweaver-mcp_resources.md +12 -0
- package/docs/server-src-pixelweaver-mcp_server.md +12 -0
- package/docs/server-src-pixelweaver-protocol.md +12 -0
- package/docs/server-src-pixelweaver-state.md +12 -0
- package/docs/server-src-pixelweaver-storage.md +12 -0
- package/docs/server-src-pixelweaver-websocket.md +12 -0
- package/docs/server-src-pixelweaver.md +12 -0
- package/e2e/app-launch.test.ts +35 -0
- package/e2e/menus.test.ts +26 -0
- package/e2e/tools.test.ts +27 -0
- package/e2e/undo-redo.test.ts +11 -0
- package/eslint.config.js +62 -0
- package/index.html +13 -0
- package/package.json +48 -0
- package/playwright.config.ts +19 -0
- package/plugins/builtin/.gitkeep +0 -0
- package/plugins/builtin/advanced-fill-tool.ts +146 -0
- package/plugins/builtin/circle-tool.ts +186 -0
- package/plugins/builtin/diamond-tool.ts +182 -0
- package/plugins/builtin/dither-tool.ts +186 -0
- package/plugins/builtin/drawing-primitives-plugin.ts +362 -0
- package/plugins/builtin/drawing-utils.test.ts +495 -0
- package/plugins/builtin/drawing-utils.ts +431 -0
- package/plugins/builtin/effects/blur.ts +97 -0
- package/plugins/builtin/effects/color-effects.ts +278 -0
- package/plugins/builtin/effects/flip.ts +83 -0
- package/plugins/builtin/effects/glow.ts +118 -0
- package/plugins/builtin/effects/outline.ts +110 -0
- package/plugins/builtin/effects/rotate.ts +357 -0
- package/plugins/builtin/effects/scale.ts +258 -0
- package/plugins/builtin/effects/shadow.ts +111 -0
- package/plugins/builtin/effects/sharpen.ts +102 -0
- package/plugins/builtin/effects.test.ts +715 -0
- package/plugins/builtin/eraser-tool.ts +23 -0
- package/plugins/builtin/eyedropper-tool.ts +83 -0
- package/plugins/builtin/fill-tool.ts +93 -0
- package/plugins/builtin/gradient-tool.ts +204 -0
- package/plugins/builtin/gradient.test.ts +142 -0
- package/plugins/builtin/importers/aseprite-importer-plugin.ts +174 -0
- package/plugins/builtin/importers/aseprite-parser.ts +497 -0
- package/plugins/builtin/importers/piskel-importer-plugin.ts +222 -0
- package/plugins/builtin/importers/sky-spec-plugin.ts +409 -0
- package/plugins/builtin/line-tool.ts +267 -0
- package/plugins/builtin/make-stroke-tool.ts +271 -0
- package/plugins/builtin/noise-dither.test.ts +151 -0
- package/plugins/builtin/noise-tool.ts +131 -0
- package/plugins/builtin/pattern-stamp-tool.ts +162 -0
- package/plugins/builtin/pencil-tool.ts +25 -0
- package/plugins/builtin/rect-tool.ts +179 -0
- package/plugins/builtin/selection-tool.ts +388 -0
- package/plugins/builtin/selection.test.ts +195 -0
- package/plugins/builtin/tool-plugins.test.ts +529 -0
- package/public/favicon.svg +7 -0
- package/public/sw.js +91 -0
- package/pyproject.toml +49 -0
- package/scripts/eslint-wave-a-list.sh +24 -0
- package/scripts/eslint-wave-a-status.sh +25 -0
- package/scripts/fix-index-signature-access.py +127 -0
- package/scripts/fix-unchecked-index.py +128 -0
- package/scripts/fix-unchecked-vars.py +127 -0
- package/scripts/fix-wave-a-bangs.py +36 -0
- package/scripts/fix-wave-a-templates.py +108 -0
- package/scripts/fix-wave-b-void-expr.py +167 -0
- package/scripts/generate-command-params.py +540 -0
- package/scripts/migrate-single-frame-to-multi.py +171 -0
- package/scripts/smoke-test.sh +77 -0
- package/selfdoc.json +10 -0
- package/server/README.md +0 -0
- package/server/src/pixelweaver/__init__.py +1 -0
- package/server/src/pixelweaver/__main__.py +4 -0
- package/server/src/pixelweaver/autosave.py +114 -0
- package/server/src/pixelweaver/bridge.py +127 -0
- package/server/src/pixelweaver/cli.py +199 -0
- package/server/src/pixelweaver/config.py +24 -0
- package/server/src/pixelweaver/connections.py +54 -0
- package/server/src/pixelweaver/main.py +271 -0
- package/server/src/pixelweaver/mcp_bridge.py +189 -0
- package/server/src/pixelweaver/mcp_drawing_tools.py +178 -0
- package/server/src/pixelweaver/mcp_export_tools.py +291 -0
- package/server/src/pixelweaver/mcp_frame_tools.py +423 -0
- package/server/src/pixelweaver/mcp_history_tools.py +106 -0
- package/server/src/pixelweaver/mcp_layer_tools.py +64 -0
- package/server/src/pixelweaver/mcp_lock.py +37 -0
- package/server/src/pixelweaver/mcp_project_tools.py +302 -0
- package/server/src/pixelweaver/mcp_read_tools.py +163 -0
- package/server/src/pixelweaver/mcp_registry.py +247 -0
- package/server/src/pixelweaver/mcp_resources.py +312 -0
- package/server/src/pixelweaver/mcp_server.py +234 -0
- package/server/src/pixelweaver/protocol.py +219 -0
- package/server/src/pixelweaver/state.py +267 -0
- package/server/src/pixelweaver/storage.py +293 -0
- package/server/src/pixelweaver/websocket.py +282 -0
- package/server/tests/__init__.py +0 -0
- package/server/tests/conftest.py +17 -0
- package/server/tests/test_api.py +96 -0
- package/server/tests/test_bridge.py +161 -0
- package/server/tests/test_health.py +9 -0
- package/server/tests/test_integration.py +86 -0
- package/server/tests/test_mcp_bridge.py +293 -0
- package/server/tests/test_mcp_lock.py +34 -0
- package/server/tests/test_mcp_registry.py +679 -0
- package/server/tests/test_mcp_resources.py +648 -0
- package/server/tests/test_protocol.py +125 -0
- package/server/tests/test_state.py +87 -0
- package/server/tests/test_storage.py +306 -0
- package/server/tests/test_websocket.py +275 -0
- package/src/App.svelte +107 -0
- package/src/app.css +215 -0
- package/src/lib/animation/AnimationPreviewPanel.svelte +667 -0
- package/src/lib/animation/animation-commands.test.ts +228 -0
- package/src/lib/animation/animation-commands.ts +540 -0
- package/src/lib/animation/animation-preview-panel-plugin.ts +25 -0
- package/src/lib/animation/animation-preview.svelte.ts +151 -0
- package/src/lib/animation/clipboard.ts +134 -0
- package/src/lib/animation/frame-model.svelte.ts +437 -0
- package/src/lib/animation/frame-model.test.ts +314 -0
- package/src/lib/animation/frame-selection.svelte.ts +77 -0
- package/src/lib/animation/frame-tags.svelte.ts +238 -0
- package/src/lib/animation/frame-tags.test.ts +136 -0
- package/src/lib/animation/import.test.ts +141 -0
- package/src/lib/animation/import.ts +112 -0
- package/src/lib/animation/spritesheet-export.test.ts +239 -0
- package/src/lib/animation/spritesheet-export.ts +314 -0
- package/src/lib/canvas/CanvasViewport.svelte +216 -0
- package/src/lib/canvas/canvas-init-plugin.ts +178 -0
- package/src/lib/canvas/canvas-renderer.ts +408 -0
- package/src/lib/canvas/canvas-state.svelte.ts +232 -0
- package/src/lib/canvas/canvas-state.test.ts +139 -0
- package/src/lib/canvas/input-handler.ts +221 -0
- package/src/lib/canvas/input-plugin.ts +150 -0
- package/src/lib/canvas/onion-skin.ts +94 -0
- package/src/lib/canvas/pixel-buffer.test.ts +249 -0
- package/src/lib/canvas/pixel-buffer.ts +151 -0
- package/src/lib/canvas/render-state.svelte.ts +18 -0
- package/src/lib/canvas/shape-preview-state.svelte.ts +36 -0
- package/src/lib/canvas/tile-mode.test.ts +53 -0
- package/src/lib/canvas/tile-mode.ts +92 -0
- package/src/lib/canvas/viewport-utils.ts +24 -0
- package/src/lib/canvas/zoom-utils.ts +31 -0
- package/src/lib/color/.gitkeep +0 -0
- package/src/lib/color/color-commands.ts +87 -0
- package/src/lib/color/color-state.svelte.ts +98 -0
- package/src/lib/color/color-state.test.ts +91 -0
- package/src/lib/color/color-utils.test.ts +220 -0
- package/src/lib/color/color-utils.ts +243 -0
- package/src/lib/color/palette-state.svelte.ts +127 -0
- package/src/lib/color/palette-state.test.ts +154 -0
- package/src/lib/color/palette.ts +79 -0
- package/src/lib/core/bootstrap.ts +66 -0
- package/src/lib/core/command-params.generated.ts +1549 -0
- package/src/lib/core/command-params.ts +20 -0
- package/src/lib/core/command-runner.ts +79 -0
- package/src/lib/core/commands.ts +134 -0
- package/src/lib/core/dispatcher.test.ts +548 -0
- package/src/lib/core/dispatcher.ts +361 -0
- package/src/lib/core/index.test.ts +7 -0
- package/src/lib/core/notification-state.svelte.ts +119 -0
- package/src/lib/core/plugin-api.ts +210 -0
- package/src/lib/core/plugin-discovery.test.ts +53 -0
- package/src/lib/core/plugin-discovery.ts +65 -0
- package/src/lib/core/plugin-loader.test.ts +159 -0
- package/src/lib/core/plugin-loader.ts +240 -0
- package/src/lib/core/plugin-types.ts +286 -0
- package/src/lib/core/registries.svelte.ts +74 -0
- package/src/lib/core/tool-options-state.svelte.ts +61 -0
- package/src/lib/dock/DockLayout.svelte +375 -0
- package/src/lib/dock/TabAddMenu.svelte +90 -0
- package/src/lib/dock/dock-persistence.ts +46 -0
- package/src/lib/dock/dock-plugin.ts +49 -0
- package/src/lib/dock/dock-presets.ts +156 -0
- package/src/lib/dock/dock-state.svelte.ts +77 -0
- package/src/lib/dock/dock-types.ts +2 -0
- package/src/lib/dock/dockview-theme.css +226 -0
- package/src/lib/dock/dockview-theme.ts +17 -0
- package/src/lib/dock/header-action-renderer.ts +77 -0
- package/src/lib/edit/clipboard-state.ts +34 -0
- package/src/lib/export/download.ts +201 -0
- package/src/lib/export/png-metadata.ts +181 -0
- package/src/lib/history/ActionLogPanel.svelte +418 -0
- package/src/lib/history/action-log-panel-plugin.ts +24 -0
- package/src/lib/history/action-log.svelte.ts +172 -0
- package/src/lib/history/action-log.test.ts +168 -0
- package/src/lib/history/history-commands.ts +403 -0
- package/src/lib/history/macros.svelte.ts +320 -0
- package/src/lib/history/macros.test.ts +224 -0
- package/src/lib/history/replay-engine.test.ts +241 -0
- package/src/lib/history/replay-engine.ts +149 -0
- package/src/lib/history/spec-format.test.ts +250 -0
- package/src/lib/history/spec-format.ts +210 -0
- package/src/lib/iso/SeamCheckerPanel.svelte +251 -0
- package/src/lib/iso/iso-math.test.ts +77 -0
- package/src/lib/iso/iso-math.ts +77 -0
- package/src/lib/iso/seam-checker-panel-plugin.ts +25 -0
- package/src/lib/iso/seam-checker.test.ts +126 -0
- package/src/lib/iso/seam-checker.ts +93 -0
- package/src/lib/iso/tessellation.ts +67 -0
- package/src/lib/layers/compositor.test.ts +193 -0
- package/src/lib/layers/compositor.ts +175 -0
- package/src/lib/layers/layer-commands.test.ts +263 -0
- package/src/lib/layers/layer-commands.ts +429 -0
- package/src/lib/layers/layer-tree.svelte.ts +516 -0
- package/src/lib/layers/layer-tree.test.ts +383 -0
- package/src/lib/layers/layer-types.ts +56 -0
- package/src/lib/leveleditor/LevelEditorViewport.svelte +1808 -0
- package/src/lib/leveleditor/MapPropertiesPanel.svelte +266 -0
- package/src/lib/leveleditor/TilePickerPanel.svelte +324 -0
- package/src/lib/leveleditor/depth-sort.test.ts +70 -0
- package/src/lib/leveleditor/depth-sort.ts +39 -0
- package/src/lib/leveleditor/level-editor-commands.ts +353 -0
- package/src/lib/leveleditor/level-editor-viewport-plugin.ts +25 -0
- package/src/lib/leveleditor/map-properties-panel-plugin.ts +25 -0
- package/src/lib/leveleditor/map-state.svelte.ts +372 -0
- package/src/lib/leveleditor/map-state.test.ts +243 -0
- package/src/lib/leveleditor/tile-picker-panel-plugin.ts +25 -0
- package/src/lib/leveleditor/tile-source-registry.svelte.ts +91 -0
- package/src/lib/leveleditor/tiled-export.test.ts +144 -0
- package/src/lib/leveleditor/tiled-export.ts +374 -0
- package/src/lib/pywebview.d.ts +15 -0
- package/src/lib/recovery/.gitkeep +0 -0
- package/src/lib/recovery/idb-store.ts +118 -0
- package/src/lib/recovery/recovery-manager.test.ts +321 -0
- package/src/lib/recovery/recovery-manager.ts +115 -0
- package/src/lib/recovery/recovery-plugin.ts +55 -0
- package/src/lib/recovery/recovery-state.svelte.ts +21 -0
- package/src/lib/recovery/sw-plugin.ts +18 -0
- package/src/lib/recovery/sw-register.ts +17 -0
- package/src/lib/save/directory-format.ts +42 -0
- package/src/lib/save/project-snapshot.ts +139 -0
- package/src/lib/save/recent-projects.ts +56 -0
- package/src/lib/save/save-state.svelte.ts +35 -0
- package/src/lib/save/storage.ts +167 -0
- package/src/lib/save/zip-format.ts +45 -0
- package/src/lib/shortcuts/.gitkeep +0 -0
- package/src/lib/shortcuts/ShortcutEditorPanel.svelte +690 -0
- package/src/lib/shortcuts/default-bindings.ts +61 -0
- package/src/lib/shortcuts/shortcut-editor-panel-plugin.ts +25 -0
- package/src/lib/shortcuts/shortcut-init.ts +380 -0
- package/src/lib/shortcuts/shortcut-manager.test.ts +466 -0
- package/src/lib/shortcuts/shortcut-manager.ts +281 -0
- package/src/lib/shortcuts/shortcut-state.svelte.ts +78 -0
- package/src/lib/shortcuts/shortcuts-plugin.ts +17 -0
- package/src/lib/sync/patch-applicator.ts +300 -0
- package/src/lib/sync/patch-types.ts +65 -0
- package/src/lib/sync/project-manager.ts +108 -0
- package/src/lib/sync/sync-init.ts +152 -0
- package/src/lib/sync/sync-plugin.ts +19 -0
- package/src/lib/sync/sync-state.svelte.ts +56 -0
- package/src/lib/sync/ws-client.test.ts +604 -0
- package/src/lib/sync/ws-client.ts +574 -0
- package/src/lib/tools/.gitkeep +0 -0
- package/src/lib/ui/.gitkeep +0 -0
- package/src/lib/ui/AboutDialog.svelte +113 -0
- package/src/lib/ui/ColorPicker.svelte +761 -0
- package/src/lib/ui/ContextMenu.svelte +216 -0
- package/src/lib/ui/ExportDialog.svelte +747 -0
- package/src/lib/ui/FrameStrip.svelte +854 -0
- package/src/lib/ui/LayerPanel.svelte +810 -0
- package/src/lib/ui/MenuBar.svelte +590 -0
- package/src/lib/ui/NewProjectDialog.svelte +803 -0
- package/src/lib/ui/PluginManagerPanel.svelte +475 -0
- package/src/lib/ui/PromptDialog.svelte +252 -0
- package/src/lib/ui/RecoveryDialog.svelte +295 -0
- package/src/lib/ui/ResizeDialog.svelte +416 -0
- package/src/lib/ui/StatusBar.svelte +145 -0
- package/src/lib/ui/ToolbarPanel.svelte +488 -0
- package/src/lib/ui/animation-menu-commands.ts +194 -0
- package/src/lib/ui/command-palette/CommandPalette.svelte +232 -0
- package/src/lib/ui/command-palette/command-palette-plugin.ts +30 -0
- package/src/lib/ui/command-palette/command-palette-state.svelte.ts +190 -0
- package/src/lib/ui/command-palette/command-palette.test.ts +129 -0
- package/src/lib/ui/dialog-state.svelte.ts +70 -0
- package/src/lib/ui/edit-commands.ts +271 -0
- package/src/lib/ui/file-commands.ts +275 -0
- package/src/lib/ui/file-open.ts +99 -0
- package/src/lib/ui/help-commands.ts +93 -0
- package/src/lib/ui/image-commands.ts +181 -0
- package/src/lib/ui/layer-menu-commands.ts +420 -0
- package/src/lib/ui/menu-builder.ts +224 -0
- package/src/lib/ui/notifications/NotificationBanner.svelte +137 -0
- package/src/lib/ui/notifications/notification-plugin.ts +29 -0
- package/src/lib/ui/notifications/notification-state.svelte.ts +9 -0
- package/src/lib/ui/plugin-manager-panel-plugin.ts +26 -0
- package/src/lib/ui/plugin-state.svelte.ts +62 -0
- package/src/lib/ui/select-commands.ts +75 -0
- package/src/lib/ui/theme-plugin.ts +18 -0
- package/src/lib/ui/theme.svelte.ts +45 -0
- package/src/lib/ui/theme.test.ts +51 -0
- package/src/lib/ui/toolbar-config.ts +90 -0
- package/src/lib/ui/toolbar-plugin.ts +39 -0
- package/src/lib/ui/view-commands.ts +629 -0
- package/src/lib/variants/BisectionExportDialog.svelte +500 -0
- package/src/lib/variants/VariantPanel.svelte +822 -0
- package/src/lib/variants/bisection-export.test.ts +113 -0
- package/src/lib/variants/bisection-export.ts +148 -0
- package/src/lib/variants/palette-extraction.test.ts +111 -0
- package/src/lib/variants/palette-extraction.ts +84 -0
- package/src/lib/variants/palette-interpolation.test.ts +113 -0
- package/src/lib/variants/palette-interpolation.ts +87 -0
- package/src/lib/variants/palette-swap.test.ts +101 -0
- package/src/lib/variants/palette-swap.ts +114 -0
- package/src/lib/variants/variant-commands.ts +594 -0
- package/src/lib/variants/variant-panel-plugin.ts +27 -0
- package/src/lib/variants/variant-randomizer.ts +101 -0
- package/src/lib/variants/variant-state.svelte.ts +166 -0
- package/src/lib/variants/variant-state.test.ts +138 -0
- package/src/main.ts +14 -0
- package/src/vite-env.d.ts +3 -0
- package/svelte.config.js +2 -0
- package/todo/.done/audit-design-decisions.md +812 -0
- package/todo/.done/audit-implementation-plan.md +1235 -0
- package/todo/.done/happy-path-polish.md +177 -0
- package/todo/.done/pixelweaver-full-build.md +937 -0
- package/todo/.done/server-multi-frame-design.md +405 -0
- package/todo/.done/typed-dispatcher-design.md +435 -0
- package/todo/.done/unified-toolbar-and-action-system.md +323 -0
- package/todo/.obsolete/comprehensive-audit-obsolete-items.md +33 -0
- package/todo/.obsolete/tauri-desktop-bundle.md +424 -0
- package/todo/comprehensive-audit.md +1085 -0
- package/tsconfig.app.json +26 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +20 -0
- package/uv.lock +1167 -0
- package/vite.config.ts +32 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""MCP tool server for PixelWeaver.
|
|
2
|
+
|
|
3
|
+
Exposes PixelWeaver operations as MCP tools so that LLMs (via Claude Code,
|
|
4
|
+
Claude Desktop, etc.) can programmatically create and edit pixel art.
|
|
5
|
+
|
|
6
|
+
Runs as a separate process using stdio transport. Start with:
|
|
7
|
+
pixelweaver mcp [--data-dir ./projects]
|
|
8
|
+
|
|
9
|
+
Architecture
|
|
10
|
+
------------
|
|
11
|
+
The MCP server does NOT own its own state. Before each tool execution it
|
|
12
|
+
pulls the latest state from the collaboration server (HTTP on port 7779),
|
|
13
|
+
runs the tool logic locally, then pushes the modified state back. The
|
|
14
|
+
collab server broadcasts a full-state patch to all WebSocket clients so
|
|
15
|
+
the frontend sees every MCP mutation in real time.
|
|
16
|
+
|
|
17
|
+
The collaboration server must be running for MCP to work. If it is
|
|
18
|
+
unreachable the MCP server returns a clear error to the LLM.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import logging
|
|
25
|
+
from contextlib import asynccontextmanager
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
from mcp.server.fastmcp import FastMCP
|
|
29
|
+
from mcp.types import ImageContent, TextContent
|
|
30
|
+
|
|
31
|
+
from pixelweaver.connections import ConnectionManager
|
|
32
|
+
from pixelweaver.mcp_bridge import (
|
|
33
|
+
CollabServerUnreachableError,
|
|
34
|
+
deserialize_into_state,
|
|
35
|
+
pull_full_state,
|
|
36
|
+
push_full_state,
|
|
37
|
+
serialize_state,
|
|
38
|
+
)
|
|
39
|
+
from pixelweaver.mcp_registry import MCPCommandRegistry
|
|
40
|
+
from pixelweaver.mcp_resources import (
|
|
41
|
+
init_mcp_resources,
|
|
42
|
+
init_mcp_subscriptions,
|
|
43
|
+
notify_resource_subscribers,
|
|
44
|
+
)
|
|
45
|
+
from pixelweaver.state import ServerState
|
|
46
|
+
|
|
47
|
+
logger = logging.getLogger(__name__)
|
|
48
|
+
|
|
49
|
+
# Module-level instances used by the MCP server process.
|
|
50
|
+
# State is populated from the collab server on each tool call -- not from disk.
|
|
51
|
+
_state: ServerState | None = None
|
|
52
|
+
_connections: ConnectionManager | None = None
|
|
53
|
+
_registry: MCPCommandRegistry | None = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _ensure_initialized() -> MCPCommandRegistry:
|
|
57
|
+
"""Lazily initialize the local state/registry on first tool call.
|
|
58
|
+
|
|
59
|
+
Unlike the old architecture, we do NOT load projects from disk here.
|
|
60
|
+
State is synced from the collab server before each tool execution
|
|
61
|
+
(see _sync_from_collab / _push_to_collab in the handler wrapper).
|
|
62
|
+
"""
|
|
63
|
+
global _state, _connections, _registry
|
|
64
|
+
if _registry is not None:
|
|
65
|
+
return _registry
|
|
66
|
+
|
|
67
|
+
_state = ServerState()
|
|
68
|
+
# ConnectionManager is still needed by MCPCommandRegistry's constructor,
|
|
69
|
+
# but its broadcast calls are effectively no-ops (no WS clients connect
|
|
70
|
+
# to the stdio process). State propagation happens via the HTTP push.
|
|
71
|
+
_connections = ConnectionManager()
|
|
72
|
+
_registry = MCPCommandRegistry(_state, _connections)
|
|
73
|
+
return _registry
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
async def _sync_from_collab() -> None:
|
|
77
|
+
"""Pull the latest state from the collab server into _state.
|
|
78
|
+
|
|
79
|
+
Raises CollabServerUnreachableError if the server cannot be reached.
|
|
80
|
+
"""
|
|
81
|
+
assert _state is not None
|
|
82
|
+
data = await pull_full_state()
|
|
83
|
+
deserialize_into_state(_state, data)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
async def _push_to_collab() -> None:
|
|
87
|
+
"""Push the current _state to the collab server (triggers WS broadcast).
|
|
88
|
+
|
|
89
|
+
Raises CollabServerUnreachableError if the server cannot be reached.
|
|
90
|
+
"""
|
|
91
|
+
assert _state is not None
|
|
92
|
+
state_dict = serialize_state(_state)
|
|
93
|
+
await push_full_state(state_dict)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
# FastMCP server
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
@asynccontextmanager
|
|
101
|
+
async def mcp_lifespan(server: FastMCP):
|
|
102
|
+
"""Initialize state when the MCP server starts."""
|
|
103
|
+
_ensure_initialized()
|
|
104
|
+
yield
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
mcp = FastMCP(
|
|
108
|
+
"PixelWeaver",
|
|
109
|
+
instructions=(
|
|
110
|
+
"PixelWeaver is a pixel art sprite editor. Use these tools to create "
|
|
111
|
+
"projects, draw shapes, manage layers and frames, and export artwork. "
|
|
112
|
+
"Start by creating a project with create_project, then use drawing "
|
|
113
|
+
"tools to add pixel art. Use get_canvas_thumbnail to see the result. "
|
|
114
|
+
"NOTE: The PixelWeaver collaboration server must be running on port "
|
|
115
|
+
"7779 for these tools to work."
|
|
116
|
+
),
|
|
117
|
+
lifespan=mcp_lifespan,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _format_result(result: dict[str, Any]) -> list[TextContent | ImageContent]:
|
|
122
|
+
"""Convert a registry result dict into MCP content blocks.
|
|
123
|
+
|
|
124
|
+
Mutation results include a thumbnail as an ImageContent block.
|
|
125
|
+
All results include a TextContent block with the JSON data.
|
|
126
|
+
"""
|
|
127
|
+
content: list[TextContent | ImageContent] = []
|
|
128
|
+
|
|
129
|
+
# Extract and remove thumbnail before serializing to text
|
|
130
|
+
thumbnail_b64 = result.pop("thumbnail", None)
|
|
131
|
+
|
|
132
|
+
content.append(TextContent(type="text", text=json.dumps(result, indent=2)))
|
|
133
|
+
|
|
134
|
+
if thumbnail_b64:
|
|
135
|
+
content.append(ImageContent(
|
|
136
|
+
type="image",
|
|
137
|
+
data=thumbnail_b64,
|
|
138
|
+
mimeType="image/png",
|
|
139
|
+
))
|
|
140
|
+
|
|
141
|
+
return content
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# ---------------------------------------------------------------------------
|
|
145
|
+
# Auto-register all tools from the registry
|
|
146
|
+
# ---------------------------------------------------------------------------
|
|
147
|
+
# We register a generic handler for each tool defined in the registry.
|
|
148
|
+
# The tool definitions (names, descriptions, schemas) come from the registry;
|
|
149
|
+
# the actual FastMCP tool functions delegate to registry.execute_tool().
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _make_tool_handler(tool_name: str):
|
|
153
|
+
"""Create an async handler closure for a specific tool name.
|
|
154
|
+
|
|
155
|
+
Each handler:
|
|
156
|
+
1. Pulls latest state from the collab server
|
|
157
|
+
2. Executes the tool locally via the registry
|
|
158
|
+
3. Pushes modified state back (for mutating tools)
|
|
159
|
+
If the collab server is unreachable, returns a clear error.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
async def handler(**kwargs: Any) -> list[TextContent | ImageContent]:
|
|
163
|
+
registry = _ensure_initialized()
|
|
164
|
+
tool_def = registry.get_tool_def(tool_name)
|
|
165
|
+
|
|
166
|
+
# Pull latest state from the collab server
|
|
167
|
+
try:
|
|
168
|
+
await _sync_from_collab()
|
|
169
|
+
except CollabServerUnreachableError as exc:
|
|
170
|
+
return _format_result({
|
|
171
|
+
"success": False,
|
|
172
|
+
"error": (
|
|
173
|
+
f"Collab server unreachable: {exc}. "
|
|
174
|
+
"Make sure the PixelWeaver server is running "
|
|
175
|
+
"(pixelweaver serve --port 7779)."
|
|
176
|
+
),
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
# Execute the tool locally
|
|
180
|
+
result = await registry.execute_tool(tool_name, kwargs)
|
|
181
|
+
|
|
182
|
+
# Push modified state back if the tool mutates state
|
|
183
|
+
if tool_def and tool_def.mutates and result.get("success", False):
|
|
184
|
+
try:
|
|
185
|
+
await _push_to_collab()
|
|
186
|
+
except CollabServerUnreachableError as exc:
|
|
187
|
+
# Tool succeeded locally but failed to propagate -- warn
|
|
188
|
+
result["warning"] = (
|
|
189
|
+
f"Tool executed but failed to sync to collab server: {exc}"
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Notify subscribed MCP clients that resources have changed.
|
|
193
|
+
await notify_resource_subscribers(mcp)
|
|
194
|
+
|
|
195
|
+
return _format_result(result)
|
|
196
|
+
|
|
197
|
+
# FastMCP uses the function name as the tool name
|
|
198
|
+
handler.__name__ = tool_name
|
|
199
|
+
handler.__qualname__ = tool_name
|
|
200
|
+
return handler
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def init_mcp_tools() -> None:
|
|
204
|
+
"""Register all tools from the registry onto the FastMCP server.
|
|
205
|
+
|
|
206
|
+
Call this explicitly from the server startup path. Importing this module
|
|
207
|
+
no longer has side effects; this keeps tests and tooling from paying the
|
|
208
|
+
cost of initializing the MCP registry unless they actually need it.
|
|
209
|
+
"""
|
|
210
|
+
registry = _ensure_initialized()
|
|
211
|
+
for tool_def_dict in registry.get_tool_definitions():
|
|
212
|
+
name = tool_def_dict["name"]
|
|
213
|
+
desc = tool_def_dict["description"]
|
|
214
|
+
handler = _make_tool_handler(name)
|
|
215
|
+
# Use add_tool with the raw handler; FastMCP will expose it
|
|
216
|
+
mcp.add_tool(handler, name=name, description=desc)
|
|
217
|
+
|
|
218
|
+
# Register MCP resources (project state, canvas, frame, palette)
|
|
219
|
+
init_mcp_resources(mcp)
|
|
220
|
+
|
|
221
|
+
# Wire up resource subscription/unsubscription handlers and
|
|
222
|
+
# advertise subscribe=True in the server capabilities.
|
|
223
|
+
init_mcp_subscriptions(mcp)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# ---------------------------------------------------------------------------
|
|
227
|
+
# Entry point
|
|
228
|
+
# ---------------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def run_mcp_server() -> None:
|
|
232
|
+
"""Run the MCP server on stdio transport (blocking)."""
|
|
233
|
+
init_mcp_tools()
|
|
234
|
+
mcp.run(transport="stdio")
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""Pydantic models for the PixelWeaver WebSocket JSON protocol.
|
|
2
|
+
|
|
3
|
+
Client -> Server message types:
|
|
4
|
+
command -- dispatch a drawing/editing command
|
|
5
|
+
sync_request -- request full state snapshot
|
|
6
|
+
undo -- undo last command
|
|
7
|
+
redo -- redo last undone command
|
|
8
|
+
|
|
9
|
+
Server -> Client message types:
|
|
10
|
+
command_ack -- command accepted
|
|
11
|
+
command_reject -- command rejected with reason
|
|
12
|
+
state_sync -- full state snapshot
|
|
13
|
+
command_broadcast -- a command executed by another client
|
|
14
|
+
error -- generic error
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import Any, Literal
|
|
20
|
+
|
|
21
|
+
from pydantic import BaseModel, Field
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Client -> Server
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CommandPayload(BaseModel):
|
|
29
|
+
"""The inner command object sent by the client."""
|
|
30
|
+
|
|
31
|
+
type: str
|
|
32
|
+
plugin: str
|
|
33
|
+
version: str
|
|
34
|
+
params: dict[str, Any] = Field(default_factory=dict)
|
|
35
|
+
id: str
|
|
36
|
+
timestamp: int
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class CommandMessage(BaseModel):
|
|
40
|
+
"""Client dispatches a command for execution."""
|
|
41
|
+
|
|
42
|
+
type: Literal["command"]
|
|
43
|
+
id: str
|
|
44
|
+
command: CommandPayload
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class SyncRequestMessage(BaseModel):
|
|
48
|
+
"""Client requests a full state snapshot."""
|
|
49
|
+
|
|
50
|
+
type: Literal["sync_request"]
|
|
51
|
+
id: str
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class UndoMessage(BaseModel):
|
|
55
|
+
"""Client requests undo of the last command."""
|
|
56
|
+
|
|
57
|
+
type: Literal["undo"]
|
|
58
|
+
id: str
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class RedoMessage(BaseModel):
|
|
62
|
+
"""Client requests redo of the last undone command."""
|
|
63
|
+
|
|
64
|
+
type: Literal["redo"]
|
|
65
|
+
id: str
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# Union discriminator for incoming messages
|
|
69
|
+
ClientMessage = CommandMessage | SyncRequestMessage | UndoMessage | RedoMessage
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
# Server -> Client
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class CommandAckMessage(BaseModel):
|
|
78
|
+
"""Server confirms a command was accepted and executed."""
|
|
79
|
+
|
|
80
|
+
type: Literal["command_ack"] = "command_ack"
|
|
81
|
+
id: str
|
|
82
|
+
command_id: str
|
|
83
|
+
success: Literal[True] = True
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class CommandRejectMessage(BaseModel):
|
|
87
|
+
"""Server rejects a command with a reason."""
|
|
88
|
+
|
|
89
|
+
type: Literal["command_reject"] = "command_reject"
|
|
90
|
+
id: str
|
|
91
|
+
command_id: str
|
|
92
|
+
error: str
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class StateSyncMessage(BaseModel):
|
|
96
|
+
"""Full state snapshot sent to the client."""
|
|
97
|
+
|
|
98
|
+
type: Literal["state_sync"] = "state_sync"
|
|
99
|
+
id: str
|
|
100
|
+
state: dict[str, Any]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class CommandBroadcastMessage(BaseModel):
|
|
104
|
+
"""Broadcast of a command executed by another client."""
|
|
105
|
+
|
|
106
|
+
type: Literal["command_broadcast"] = "command_broadcast"
|
|
107
|
+
command: dict[str, Any]
|
|
108
|
+
source: str
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class ErrorMessage(BaseModel):
|
|
112
|
+
"""Generic error message."""
|
|
113
|
+
|
|
114
|
+
type: Literal["error"] = "error"
|
|
115
|
+
id: str
|
|
116
|
+
error: str
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
ServerMessage = (
|
|
120
|
+
CommandAckMessage
|
|
121
|
+
| CommandRejectMessage
|
|
122
|
+
| StateSyncMessage
|
|
123
|
+
| CommandBroadcastMessage
|
|
124
|
+
| ErrorMessage
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
# Data Patches -- server-to-client state synchronization
|
|
130
|
+
# ---------------------------------------------------------------------------
|
|
131
|
+
# When the server modifies state (via MCP or other operations), it broadcasts
|
|
132
|
+
# patches describing the pre-computed results. The frontend applies these
|
|
133
|
+
# directly without re-executing commands.
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class StatePatch(BaseModel):
|
|
137
|
+
"""Base for all state patches."""
|
|
138
|
+
|
|
139
|
+
type: str
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class PixelPatch(StatePatch):
|
|
143
|
+
"""A patch to pixel data on a specific layer/frame."""
|
|
144
|
+
|
|
145
|
+
type: Literal["pixel"] = "pixel"
|
|
146
|
+
layer_id: str
|
|
147
|
+
frame_index: int
|
|
148
|
+
data: list[int] # flat RGBA
|
|
149
|
+
width: int
|
|
150
|
+
height: int
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class LayerPatch(StatePatch):
|
|
154
|
+
"""A patch to the layer tree structure."""
|
|
155
|
+
|
|
156
|
+
type: Literal["layer"] = "layer"
|
|
157
|
+
action: str # add, remove, update
|
|
158
|
+
layer: dict[str, Any] | None = None
|
|
159
|
+
layer_id: str | None = None
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class CanvasPatch(StatePatch):
|
|
163
|
+
"""A patch to canvas dimensions."""
|
|
164
|
+
|
|
165
|
+
type: Literal["canvas"] = "canvas"
|
|
166
|
+
width: int
|
|
167
|
+
height: int
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class FramePatch(StatePatch):
|
|
171
|
+
"""A patch to frame structure (add/remove/reorder/set_duration)."""
|
|
172
|
+
|
|
173
|
+
type: Literal["frame"] = "frame"
|
|
174
|
+
action: str
|
|
175
|
+
frame_index: int | None = None
|
|
176
|
+
frame_data: dict[str, Any] | None = None
|
|
177
|
+
from_index: int | None = None
|
|
178
|
+
to_index: int | None = None
|
|
179
|
+
duration_ms: int | None = None
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class FullStatePatch(StatePatch):
|
|
183
|
+
"""A full state replacement (initial sync or large MCP operations)."""
|
|
184
|
+
|
|
185
|
+
type: Literal["full_state"] = "full_state"
|
|
186
|
+
snapshot: dict[str, Any]
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class PatchMessage(BaseModel):
|
|
190
|
+
"""Wrapper message broadcast via WebSocket carrying one or more patches."""
|
|
191
|
+
|
|
192
|
+
type: Literal["state_patch"] = "state_patch"
|
|
193
|
+
project_name: str
|
|
194
|
+
patches: list[dict[str, Any]] # StatePatch dicts
|
|
195
|
+
command_id: str | None = None
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# ---------------------------------------------------------------------------
|
|
199
|
+
# Parsing helper
|
|
200
|
+
# ---------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def parse_client_message(raw: dict[str, Any]) -> ClientMessage:
|
|
204
|
+
"""Parse a raw dict into one of the client message types.
|
|
205
|
+
|
|
206
|
+
Raises ValueError if the message is malformed or has an unknown type.
|
|
207
|
+
"""
|
|
208
|
+
msg_type = raw.get("type")
|
|
209
|
+
match msg_type:
|
|
210
|
+
case "command":
|
|
211
|
+
return CommandMessage.model_validate(raw)
|
|
212
|
+
case "sync_request":
|
|
213
|
+
return SyncRequestMessage.model_validate(raw)
|
|
214
|
+
case "undo":
|
|
215
|
+
return UndoMessage.model_validate(raw)
|
|
216
|
+
case "redo":
|
|
217
|
+
return RedoMessage.model_validate(raw)
|
|
218
|
+
case _:
|
|
219
|
+
raise ValueError(f"Unknown message type: {msg_type!r}")
|