mcp-painter
by flrngel
Verified
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListResourcesRequestSchema,
ListToolsRequestSchema,
ReadResourceRequestSchema,
CallToolResult,
TextContent,
ImageContent, // Import ImageContent
Tool,
} from "@modelcontextprotocol/sdk/types.js";
// Import the drawing tool logic from drawingTool.ts (compiled to .js)
import * as drawingTool from './drawingTool.js'; // Adjust path if needed - assuming you compile drawingTool.ts to drawingTool.js in the same directory
// Define drawing tools - updated descriptions to reflect PNG output
const TOOLS = [
{
name: "drawing_generateCanvas",
description: "Generate a new drawing canvas with specified width and height.",
inputSchema: {
type: "object",
properties: {
width: { type: "number", description: "Width of the canvas in pixels" },
height: { type: "number", description: "Height of the canvas in pixels" },
},
required: ["width", "height"],
},
},
{
name: "drawing_fillRectangle",
description: "Fill a rectangle on the drawing canvas with a specified color and coordinates.",
inputSchema: {
type: "object",
properties: {
x: { type: "number", description: "X coordinate of the top-left corner of the rectangle" },
y: { type: "number", description: "Y coordinate of the top-left corner of the rectangle" },
width: { type: "number", description: "Width of the rectangle" },
height: { type: "number", description: "Height of the rectangle" },
color: {
type: "object",
description: "Color to fill the rectangle with (RGB)",
properties: {
r: { type: "number", description: "Red component (0-255)" },
g: { type: "number", description: "Green component (0-255)" },
b: { type: "number", description: "Blue component (0-255)" },
a: { type: "number", description: "Alpha component (0-255, optional, default 255)" },
},
required: ["r", "g", "b"],
},
},
required: ["x", "y", "width", "height", "color"],
},
},
{
name: "drawing_getCanvasPng", // Changed name to reflect PNG output
description: "Get the current drawing canvas as a PNG image (base64 encoded).", // Updated description
inputSchema: {
type: "object",
properties: {}, // No input needed to get canvas data
required: [],
},
},
{
name: "drawing_getCanvasData",
description: "Get the current pixel data of the drawing canvas as JSON.",
inputSchema: {
type: "object",
properties: {}, // No input needed to get canvas data
required: [],
},
},
];
// Global state for drawing canvas
let currentCanvas: drawingTool.Canvas | null = null;
async function handleToolCall(name: string, args: any): Promise<CallToolResult> {
switch (name) {
// ... (your puppeteer tool cases from the example if you kept them) ...
case "drawing_generateCanvas":
try {
currentCanvas = drawingTool.generateCanvas(args.width, args.height);
return {
content: [{
type: "text",
text: `Canvas generated with width: ${args.width}, height: ${args.height}`,
}],
isError: false,
};
} catch (error) {
return {
content: [{
type: "text",
text: `Failed to generate canvas: ${(error as Error).message}`,
}],
isError: true,
};
}
case "drawing_fillRectangle":
if (!currentCanvas) {
return {
content: [{
type: "text",
text: "Error: No canvas generated. Please use 'drawing_generateCanvas' first.",
}],
isError: true,
};
}
try {
drawingTool.fillRectangle(
currentCanvas,
args.x,
args.y,
args.width,
args.height,
args.color
);
return {
content: [{
type: "text",
text: `Filled rectangle at (${args.x}, ${args.y}) with dimensions ${args.width}x${args.height} and color RGB(${args.color.r},${args.color.g},${args.color.b})`,
}],
isError: false,
};
} catch (error) {
return {
content: [{
type: "text",
text: `Failed to fill rectangle: ${(error as Error).message}`,
}],
isError: true,
};
}
case "drawing_getCanvasPng": // Updated tool name
if (!currentCanvas) {
return {
content: [{
type: "text",
text: "Error: No canvas generated. Please use 'drawing_generateCanvas' first.",
}],
isError: true,
};
}
try {
const base64Png = await drawingTool.getCanvasPngBase64(currentCanvas); // Use getCanvasPngBase64
return {
content: [
{
type: "text",
text: "PNG image of the canvas (base64 encoded):", // Informative text
},
{
type: "image", // Use 'image' content type
data: base64Png,
mimeType: "image/png",
} as ImageContent,
],
isError: false,
};
} catch (error) {
return {
content: [{
type: "text",
text: `Failed to get canvas PNG data: ${(error as Error).message}`,
}],
isError: true,
};
}
case "drawing_getCanvasData":
if (!currentCanvas) {
return {
content: [{
type: "text",
text: "Error: No canvas generated. Please use 'drawing_generateCanvas' first.",
}],
isError: true,
};
}
try {
const canvasData = drawingTool.getCanvasData(currentCanvas); // Use getCanvasData
// Return canvas data as JSON text content
return {
content: [{
type: "text",
text: JSON.stringify(canvasData, null, 2), // Stringify for readable JSON
}],
isError: false,
};
} catch (error) {
return {
content: [{
type: "text",
text: `Failed to get canvas data: ${(error as Error).message}`,
}],
isError: true,
};
}
default:
return {
content: [{
type: "text",
text: `Unknown tool: ${name}`,
}],
isError: true,
};
}
}
const server = new Server(
{
name: "example-servers/drawing",
version: "0.1.0",
},
{
capabilities: {
resources: {},
tools: {},
},
},
);
// Setup request handlers
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
// ... (your puppeteer resources if you kept them) ...
{
uri: "drawing://canvas_png", // Changed URI to reflect PNG output
mimeType: "image/png", // Mime type for PNG image
name: "Current Canvas as PNG Image", // Updated name
},
{
uri: "drawing://canvas_data", // Example URI for canvas data
mimeType: "application/json", // Indicate JSON format
name: "Current Canvas Data (JSON)",
},
],
}));
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const uri = request.params.uri.toString();
if (uri === "drawing://canvas_png") { // Handle PNG resource
if (!currentCanvas) {
throw new Error("No canvas data available. Generate a canvas first.");
}
const base64Png = await drawingTool.getCanvasPngBase64(currentCanvas); // Use getCanvasPngBase64
return {
contents: [{
uri,
mimeType: "image/png",
blob: base64Png, // Use 'blob' for base64 image data
}],
};
}
if (uri === "drawing://canvas_data") {
if (!currentCanvas) {
throw new Error("No canvas data available. Generate a canvas first.");
}
const canvasData = drawingTool.getCanvasData(currentCanvas);
return {
contents: [{
uri,
mimeType: "application/json",
text: JSON.stringify(canvasData, null, 2),
}],
};
}
// ... (your puppeteer resource handling if you kept them) ...
throw new Error(`Resource not found: ${uri}`);
});
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: TOOLS,
}));
server.setRequestHandler(CallToolRequestSchema, async (request) =>
handleToolCall(request.params.name, request.params.arguments ?? {})
);
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
runServer().catch(console.error);
process.stdin.on("close", () => {
console.error("Drawing MCP Server closed");
server.close();
});