@pixel-normal-edit/mcp 2.0.4 → 2.0.6

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.
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * command-bus.js — Firebase-backed command bus for MCP
4
+ *
5
+ * Sends a JSON command payload to Firestore under the current session,
6
+ * then waits for the browser editor to respond with success/error.
7
+ */
8
+ const crypto = require('crypto');
9
+ const { doc, setDoc, onSnapshot } = require('firebase/firestore');
10
+ const { db } = require('./firebase');
11
+
12
+ const SESSION = process.argv[2] || process.env.MCP_SESSION || 'default-session';
13
+ const TIMEOUT = parseInt(process.env.MCP_TIMEOUT || '20000');
14
+
15
+ /**
16
+ * Send a command payload to the browser via Firestore and wait for response.
17
+ * @param {Object} payload - The command action and parameters
18
+ * @returns {Promise<Object>} MCP-compatible response object
19
+ */
20
+ async function sendCommand(payload) {
21
+ const id = crypto.randomUUID();
22
+ const ref = doc(db, 'mcp_sessions', SESSION, 'commands', id);
23
+ await setDoc(ref, { ...payload, status: 'pending', timestamp: Date.now() });
24
+
25
+ return new Promise((resolve) => {
26
+ const timer = setTimeout(() => {
27
+ unsub();
28
+ resolve({
29
+ isError: true,
30
+ content: [{ type: 'text', text: `⏱ Timeout (${TIMEOUT}ms). Is the browser tab open with session: ${SESSION}?` }]
31
+ });
32
+ }, TIMEOUT);
33
+
34
+ const unsub = onSnapshot(ref, (snap) => {
35
+ const d = snap.data();
36
+ if (!d) return;
37
+ if (d.status === 'success') {
38
+ clearTimeout(timer); unsub();
39
+ const out = d.result !== undefined ? JSON.stringify(d.result, null, 2) : '✓ Done';
40
+ resolve({ content: [{ type: 'text', text: out }] });
41
+ } else if (d.status === 'error') {
42
+ clearTimeout(timer); unsub();
43
+ resolve({ isError: true, content: [{ type: 'text', text: `❌ ${d.error}` }] });
44
+ }
45
+ });
46
+ });
47
+ }
48
+
49
+ module.exports = { sendCommand, SESSION };
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * firebase.js — Firebase initialization for MCP Firebase Bridge
4
+ *
5
+ * Initializes and exports the Firestore database instance.
6
+ * All Firebase config is hardcoded (safe as these are public API keys).
7
+ */
8
+ const { initializeApp } = require('firebase/app');
9
+ const { getFirestore } = require('firebase/firestore');
10
+ const path = require('path');
11
+ const dotenv = require('dotenv');
12
+
13
+ dotenv.config({ path: path.join(__dirname, '..', '..', '.env') });
14
+
15
+ const app = initializeApp({
16
+ apiKey: 'AIzaSyBSrvCt58Jhsh14wbC2bD2KLFUUVbAVim0',
17
+ authDomain: 'pixel-normal-edit.firebaseapp.com',
18
+ projectId: 'pixel-normal-edit',
19
+ storageBucket: 'pixel-normal-edit.firebasestorage.app',
20
+ messagingSenderId: '397075334229',
21
+ appId: '1:397075334229:web:b02eede3fc7b41d02f80dc',
22
+ });
23
+
24
+ const db = getFirestore(app);
25
+
26
+ module.exports = { db };
package/core/server.js ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * server.js — MCP Server instance factory
4
+ *
5
+ * Creates and configures the McpServer instance.
6
+ * Provides a tool registration factory for consistent tool definitions.
7
+ */
8
+ const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
9
+ const { sendCommand } = require('./command-bus');
10
+
11
+ /**
12
+ * Create a new MCP server instance
13
+ * @returns {McpServer}
14
+ */
15
+ function createServer() {
16
+ return new McpServer({ name: 'PixelNormalEdit', version: '2.0.0' });
17
+ }
18
+
19
+ /**
20
+ * Register a single-command tool on the server
21
+ * @param {McpServer} server - The MCP server instance
22
+ * @param {string} name - Tool name
23
+ * @param {string} desc - Tool description
24
+ * @param {Object} schema - Zod schema for parameters
25
+ * @param {Function} mapToCmd - Function mapping params to command payload
26
+ */
27
+ function registerTool(server, name, desc, schema, mapToCmd) {
28
+ server.tool(name, desc, schema, async (params) => sendCommand(mapToCmd(params)));
29
+ }
30
+
31
+ module.exports = { createServer, registerTool, sendCommand };
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * domains/art-tree/index.js — Art Tree Module Aggregator
4
+ *
5
+ * Aggregates and exports all Art Tree module components.
6
+ * To add new features to this module, create a new file and add it here.
7
+ *
8
+ * Module namespace: art_tree_* (all tools prefixed with art_tree_)
9
+ */
10
+ const shapes = require('./shapes');
11
+ const lessons = require('./lessons');
12
+ const tools = require('./tools');
13
+
14
+ /**
15
+ * Register all Art Tree MCP tools on the server
16
+ * @param {McpServer} server
17
+ */
18
+ function registerAll(server) {
19
+ tools.register(server);
20
+ }
21
+
22
+ module.exports = {
23
+ shapes,
24
+ lessons,
25
+ tools,
26
+ registerAll,
27
+ };
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * lessons.js — Art Tree: Basic Shapes Lessons
4
+ *
5
+ * Layer: Lesson API (built on top of Shape API)
6
+ *
7
+ * Provides structured lessons that guide learners through
8
+ * drawing basic geometric shapes step by step.
9
+ * Each lesson returns a sequence of command payloads.
10
+ */
11
+ const shapes = require('./shapes');
12
+
13
+ /**
14
+ * Lesson 1: Lines — Introduction to drawing straight lines.
15
+ * Teaches horizontal, vertical, and diagonal lines.
16
+ * @param {number} canvasSize - Canvas dimension (assumes square)
17
+ * @param {string} color - Primary color for the lesson
18
+ * @returns {Array<Object>} Array of command payloads
19
+ */
20
+ function lessonLines(canvasSize = 32, color = '#ff0000') {
21
+ const mid = Math.floor(canvasSize / 2);
22
+ const margin = Math.floor(canvasSize * 0.15);
23
+ return [
24
+ // Horizontal line
25
+ shapes.drawLine(margin, mid, canvasSize - margin, mid, color),
26
+ // Vertical line
27
+ shapes.drawLine(mid, margin, mid, canvasSize - margin, '#0000ff'),
28
+ // Diagonal (top-left to bottom-right)
29
+ shapes.drawLine(margin, margin, canvasSize - margin, canvasSize - margin, '#00aa00'),
30
+ // Diagonal (top-right to bottom-left)
31
+ shapes.drawLine(canvasSize - margin, margin, margin, canvasSize - margin, '#ff8800'),
32
+ ];
33
+ }
34
+
35
+ /**
36
+ * Lesson 2: Squares & Rectangles — Understanding right angles and proportions.
37
+ * @param {number} canvasSize - Canvas dimension
38
+ * @param {string} color - Primary color
39
+ * @returns {Array<Object>} Array of command payloads
40
+ */
41
+ function lessonSquares(canvasSize = 32, color = '#1565c0') {
42
+ const margin = Math.floor(canvasSize * 0.1);
43
+ const small = Math.floor(canvasSize * 0.2);
44
+ const large = Math.floor(canvasSize * 0.35);
45
+ return [
46
+ // Small square (outline)
47
+ shapes.drawSquare(margin, margin, small, color, false),
48
+ // Large square (filled)
49
+ shapes.drawSquare(canvasSize - margin - large, margin, large, '#ff7043', true),
50
+ // Rectangle (horizontal)
51
+ shapes.drawRectangle(margin, canvasSize - margin - small, large + small, small, '#4caf50', false),
52
+ // Rectangle (vertical, filled)
53
+ shapes.drawRectangle(canvasSize - margin - small, canvasSize - margin - large, small, large, '#9c27b0', true),
54
+ ];
55
+ }
56
+
57
+ /**
58
+ * Lesson 3: Circles — Learning curves and symmetry.
59
+ * @param {number} canvasSize - Canvas dimension
60
+ * @param {string} color - Primary color
61
+ * @returns {Array<Object>} Array of command payloads
62
+ */
63
+ function lessonCircles(canvasSize = 32, color = '#e91e63') {
64
+ const mid = Math.floor(canvasSize / 2);
65
+ const r1 = Math.floor(canvasSize * 0.15);
66
+ const r2 = Math.floor(canvasSize * 0.3);
67
+ return [
68
+ // Small circle (outline)
69
+ shapes.drawCircle(mid, mid, r1, color, false),
70
+ // Large circle (filled)
71
+ shapes.drawCircle(mid, mid, r2, '#2196f3', true),
72
+ // Small filled circle offset
73
+ shapes.drawCircle(mid + r1, mid - r1, Math.floor(r1 / 2), '#ff9800', true),
74
+ ];
75
+ }
76
+
77
+ /**
78
+ * Lesson 4: Ellipses — Understanding oval shapes and proportions.
79
+ * @param {number} canvasSize - Canvas dimension
80
+ * @param {string} color - Primary color
81
+ * @returns {Array<Object>} Array of command payloads
82
+ */
83
+ function lessonEllipses(canvasSize = 32, color = '#9c27b0') {
84
+ const mid = Math.floor(canvasSize / 2);
85
+ const rx = Math.floor(canvasSize * 0.3);
86
+ const ry = Math.floor(canvasSize * 0.15);
87
+ return [
88
+ // Horizontal ellipse (outline)
89
+ shapes.drawEllipse(mid, mid, rx, ry, color, false),
90
+ // Vertical ellipse (filled)
91
+ shapes.drawEllipse(mid, mid, ry, rx, '#4caf50', true),
92
+ // Small circle-like ellipse
93
+ shapes.drawEllipse(mid + rx, mid - ry, Math.floor(rx / 3), Math.floor(ry / 2), '#ff5722', true),
94
+ ];
95
+ }
96
+
97
+ /**
98
+ * Lesson 5: Triangles — Three-point shapes and stability.
99
+ * @param {number} canvasSize - Canvas dimension
100
+ * @param {string} color - Primary color
101
+ * @returns {Array<Object>} Array of command payloads
102
+ */
103
+ function lessonTriangles(canvasSize = 32, color = '#ff5722') {
104
+ const mid = Math.floor(canvasSize / 2);
105
+ const size = Math.floor(canvasSize * 0.4);
106
+ const margin = Math.floor(canvasSize * 0.1);
107
+ return [
108
+ // Equilateral triangle (outline)
109
+ shapes.drawEquilateralTriangle(mid, mid, size, color, false),
110
+ // Right triangle (filled)
111
+ shapes.drawTriangle([
112
+ { x: margin, y: canvasSize - margin },
113
+ { x: margin, y: margin },
114
+ { x: Math.floor(canvasSize * 0.35), y: canvasSize - margin },
115
+ ], '#3f51b5', true),
116
+ // Inverted triangle
117
+ shapes.drawEquilateralTriangle(mid, mid, Math.floor(size * 0.6), '#009688', true),
118
+ ];
119
+ }
120
+
121
+ /**
122
+ * Lesson 6: Polygons — Multi-sided shapes.
123
+ * @param {number} canvasSize - Canvas dimension
124
+ * @param {string} color - Primary color
125
+ * @returns {Array<Object>} Array of command payloads
126
+ */
127
+ function lessonPolygons(canvasSize = 32, color = '#607d8b') {
128
+ const mid = Math.floor(canvasSize / 2);
129
+ const r = Math.floor(canvasSize * 0.35);
130
+ return [
131
+ // Pentagon
132
+ shapes.drawRegularPolygon(mid, mid, 5, r, '#4caf50', false),
133
+ // Hexagon (filled)
134
+ shapes.drawRegularPolygon(mid, mid, 6, Math.floor(r * 0.7), '#ff9800', true),
135
+ // Octagon
136
+ shapes.drawRegularPolygon(mid, mid, 8, Math.floor(r * 0.5), '#9c27b0', false),
137
+ ];
138
+ }
139
+
140
+ /**
141
+ * Lesson 7: Composition — Combining basic shapes to create simple objects.
142
+ * @param {number} canvasSize - Canvas dimension
143
+ * @returns {Array<Object>} Array of command payloads
144
+ */
145
+ function lessonComposition(canvasSize = 32) {
146
+ const mid = Math.floor(canvasSize / 2);
147
+ const q1 = Math.floor(canvasSize * 0.25);
148
+ const q3 = Math.floor(canvasSize * 0.75);
149
+ const small = Math.floor(canvasSize * 0.1);
150
+ return [
151
+ // House: square body
152
+ shapes.drawSquare(q1, mid, q3 - q1, '#8d6e63', true),
153
+ // House: triangle roof
154
+ shapes.drawTriangle([
155
+ { x: q1 - 1, y: mid },
156
+ { x: mid, y: q1 },
157
+ { x: q3 + 1, y: mid },
158
+ ], '#d32f2f', true),
159
+ // Sun: circle
160
+ shapes.drawCircle(canvasSize - small - 2, small + 2, small, '#ffc107', true),
161
+ // Tree: rectangle trunk
162
+ shapes.drawRectangle(q1 + small, canvasSize - small - 2, Math.floor(small / 2), small, '#5d4037', true),
163
+ // Tree: circle top
164
+ shapes.drawCircle(q1 + Math.floor(small / 4), canvasSize - small - 4, Math.floor(small / 2), '#388e3c', true),
165
+ ];
166
+ }
167
+
168
+ /**
169
+ * Get all available lessons with metadata.
170
+ * @returns {Array<{id: string, name: string, description: string, fn: Function}>}
171
+ */
172
+ function getLessonCatalog() {
173
+ return [
174
+ { id: 'lines', name: 'Đường thẳng', description: 'Vẽ đường thẳng ngang, dọc và chéo', fn: lessonLines },
175
+ { id: 'squares', name: 'Hình vuông & Hình chữ nhật', description: 'Hiểu về góc vuông và tỷ lệ', fn: lessonSquares },
176
+ { id: 'circles', name: 'Hình tròn', description: 'Làm quen với đường cong và đối xứng', fn: lessonCircles },
177
+ { id: 'ellipses', name: 'Hình elip', description: 'Hiểu về hình bầu dục và tỷ lệ', fn: lessonEllipses },
178
+ { id: 'triangles', name: 'Hình tam giác', description: 'Hình ba cạnh và sự ổn định', fn: lessonTriangles },
179
+ { id: 'polygons', name: 'Đa giác', description: 'Hình nhiều cạnh', fn: lessonPolygons },
180
+ { id: 'composition', name: 'Tổng hợp', description: 'Kết hợp các hình cơ bản tạo vật thể đơn giản', fn: lessonComposition },
181
+ ];
182
+ }
183
+
184
+ module.exports = {
185
+ lessonLines,
186
+ lessonSquares,
187
+ lessonCircles,
188
+ lessonEllipses,
189
+ lessonTriangles,
190
+ lessonPolygons,
191
+ lessonComposition,
192
+ getLessonCatalog,
193
+ };
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * shapes.js — Art Tree: Basic Shapes API
4
+ *
5
+ * Layer: Shape API (built on top of Core Drawing Primitives)
6
+ *
7
+ * Provides higher-level shape drawing functions that compose
8
+ * the primitive drawLine, drawRect, drawCircle, drawEllipse,
9
+ * drawPolygon calls into more meaningful geometric shapes
10
+ * suitable for art lessons.
11
+ *
12
+ * All functions return command payloads compatible with sendCommand.
13
+ */
14
+
15
+ const PRIMITIVE_ACTIONS = {
16
+ LINE: 'drawLine',
17
+ RECT: 'drawRect',
18
+ CIRCLE: 'drawCircle',
19
+ ELLIPSE: 'drawEllipse',
20
+ POLYGON: 'drawPolygon',
21
+ PIXEL: 'drawPixel',
22
+ };
23
+
24
+ /**
25
+ * Draw a straight line between two points.
26
+ * @param {number} x0 - Start X
27
+ * @param {number} y0 - Start Y
28
+ * @param {number} x1 - End X
29
+ * @param {number} y1 - End Y
30
+ * @param {string} color - Hex color
31
+ * @returns {Object} Command payload
32
+ */
33
+ function drawLine(x0, y0, x1, y1, color) {
34
+ return { action: PRIMITIVE_ACTIONS.LINE, x0, y0, x1, y1, color };
35
+ }
36
+
37
+ /**
38
+ * Draw a square (aligned to axes).
39
+ * @param {number} x - Top-left X
40
+ * @param {number} y - Top-left Y
41
+ * @param {number} size - Side length
42
+ * @param {string} color - Hex color
43
+ * @param {boolean} [filled=false] - Fill interior
44
+ * @returns {Object} Command payload
45
+ */
46
+ function drawSquare(x, y, size, color, filled = false) {
47
+ return { action: PRIMITIVE_ACTIONS.RECT, x, y, w: size, h: size, color, filled };
48
+ }
49
+
50
+ /**
51
+ * Draw a rectangle (aligned to axes).
52
+ * @param {number} x - Top-left X
53
+ * @param {number} y - Top-left Y
54
+ * @param {number} w - Width
55
+ * @param {number} h - Height
56
+ * @param {string} color - Hex color
57
+ * @param {boolean} [filled=false] - Fill interior
58
+ * @returns {Object} Command payload
59
+ */
60
+ function drawRectangle(x, y, w, h, color, filled = false) {
61
+ return { action: PRIMITIVE_ACTIONS.RECT, x, y, w, h, color, filled };
62
+ }
63
+
64
+ /**
65
+ * Draw a circle.
66
+ * @param {number} cx - Center X
67
+ * @param {number} cy - Center Y
68
+ * @param {number} r - Radius
69
+ * @param {string} color - Hex color
70
+ * @param {boolean} [filled=false] - Fill interior
71
+ * @returns {Object} Command payload
72
+ */
73
+ function drawCircle(cx, cy, r, color, filled = false) {
74
+ return { action: PRIMITIVE_ACTIONS.CIRCLE, cx, cy, r, color, filled };
75
+ }
76
+
77
+ /**
78
+ * Draw an ellipse.
79
+ * @param {number} cx - Center X
80
+ * @param {number} cy - Center Y
81
+ * @param {number} rx - Horizontal radius
82
+ * @param {number} ry - Vertical radius
83
+ * @param {string} color - Hex color
84
+ * @param {boolean} [filled=false] - Fill interior
85
+ * @returns {Object} Command payload
86
+ */
87
+ function drawEllipse(cx, cy, rx, ry, color, filled = false) {
88
+ return { action: PRIMITIVE_ACTIONS.ELLIPSE, cx, cy, rx, ry, color, filled };
89
+ }
90
+
91
+ /**
92
+ * Draw a triangle from three vertices.
93
+ * @param {Array<{x: number, y: number}>} vertices - Three vertices
94
+ * @param {string} color - Hex color
95
+ * @param {boolean} [filled=false] - Fill interior
96
+ * @returns {Object} Command payload
97
+ */
98
+ function drawTriangle(vertices, color, filled = false) {
99
+ if (vertices.length !== 3) {
100
+ throw new Error('Triangle requires exactly 3 vertices');
101
+ }
102
+ return { action: PRIMITIVE_ACTIONS.POLYGON, points: vertices, color, filled };
103
+ }
104
+
105
+ /**
106
+ * Draw a regular triangle (equilateral) centered at (cx, cy).
107
+ * @param {number} cx - Center X
108
+ * @param {number} cy - Center Y
109
+ * @param {number} sideLength - Length of each side
110
+ * @param {string} color - Hex color
111
+ * @param {boolean} [filled=false] - Fill interior
112
+ * @returns {Object} Command payload
113
+ */
114
+ function drawEquilateralTriangle(cx, cy, sideLength, color, filled = false) {
115
+ const h = (Math.sqrt(3) / 2) * sideLength;
116
+ const vertices = [
117
+ { x: Math.round(cx), y: Math.round(cy - h * 2 / 3) },
118
+ { x: Math.round(cx - sideLength / 2), y: Math.round(cy + h / 3) },
119
+ { x: Math.round(cx + sideLength / 2), y: Math.round(cy + h / 3) },
120
+ ];
121
+ return drawTriangle(vertices, color, filled);
122
+ }
123
+
124
+ /**
125
+ * Draw a polygon from a list of points.
126
+ * @param {Array<{x: number, y: number}>} points - Array of vertices (min 3)
127
+ * @param {string} color - Hex color
128
+ * @param {boolean} [filled=false] - Fill interior
129
+ * @returns {Object} Command payload
130
+ */
131
+ function drawPolygon(points, color, filled = false) {
132
+ return { action: PRIMITIVE_ACTIONS.POLYGON, points, color, filled };
133
+ }
134
+
135
+ /**
136
+ * Draw a regular polygon centered at (cx, cy).
137
+ * @param {number} cx - Center X
138
+ * @param {number} cy - Center Y
139
+ * @param {number} sides - Number of sides (3+)
140
+ * @param {number} radius - Distance from center to vertices
141
+ * @param {string} color - Hex color
142
+ * @param {boolean} [filled=false] - Fill interior
143
+ * @returns {Object} Command payload
144
+ */
145
+ function drawRegularPolygon(cx, cy, sides, radius, color, filled = false) {
146
+ const angleStep = (2 * Math.PI) / sides;
147
+ const points = [];
148
+ // Start from top (-PI/2) for natural orientation
149
+ const startAngle = -Math.PI / 2;
150
+ for (let i = 0; i < sides; i++) {
151
+ const angle = startAngle + i * angleStep;
152
+ points.push({
153
+ x: Math.round(cx + radius * Math.cos(angle)),
154
+ y: Math.round(cy + radius * Math.sin(angle)),
155
+ });
156
+ }
157
+ return drawPolygon(points, color, filled);
158
+ }
159
+
160
+ /**
161
+ * Draw a grid of squares (useful for pixel art foundation lessons).
162
+ * @param {number} startX - Top-left X
163
+ * @param {number} startY - Top-left Y
164
+ * @param {number} cols - Number of columns
165
+ * @param {number} rows - Number of rows
166
+ * @param {number} cellSize - Size of each cell
167
+ * @param {string} color - Grid line color
168
+ * @returns {Array<Object>} Array of command payloads
169
+ */
170
+ function drawGrid(startX, startY, cols, rows, cellSize, color) {
171
+ const commands = [];
172
+ // Horizontal lines
173
+ for (let r = 0; r <= rows; r++) {
174
+ const y = startY + r * cellSize;
175
+ commands.push(drawLine(startX, y, startX + cols * cellSize, y, color));
176
+ }
177
+ // Vertical lines
178
+ for (let c = 0; c <= cols; c++) {
179
+ const x = startX + c * cellSize;
180
+ commands.push(drawLine(x, startY, x, startY + rows * cellSize, color));
181
+ }
182
+ return commands;
183
+ }
184
+
185
+ module.exports = {
186
+ PRIMITIVE_ACTIONS,
187
+ drawLine,
188
+ drawSquare,
189
+ drawRectangle,
190
+ drawCircle,
191
+ drawEllipse,
192
+ drawTriangle,
193
+ drawEquilateralTriangle,
194
+ drawPolygon,
195
+ drawRegularPolygon,
196
+ drawGrid,
197
+ };