Skip to main content
Glama
index.ts15.7 kB
/** * PinePaper MCP Server * * Main server implementation that exposes PinePaper tools via the Model Context Protocol. * * This server can operate in two modes: * 1. Local (stdio): For Claude Desktop and local AI agents * 2. Cloud (SSE/HTTP): For cloud-hosted premium features (future) * * Supports i18n for 41 locales matching PinePaper Studio. */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { PINEPAPER_TOOLS, getLocalizedTools } from './tools/definitions.js'; import { handleToolCall } from './tools/handlers.js'; import { I18nManager, initI18n, SupportedLocale, DEFAULT_LOCALE, } from './i18n/index.js'; // ============================================================================= // SERVER INFO // ============================================================================= const SERVER_INFO = { name: 'pinepaper-mcp', version: '1.0.0', description: 'MCP Server for PinePaper Studio - Create animated graphics with AI', }; // ============================================================================= // RESOURCES (Documentation & Examples) // ============================================================================= const RESOURCES = [ { uri: 'pinepaper://docs/getting-started', name: 'Getting Started Guide', description: 'Introduction to PinePaper MCP tools', mimeType: 'text/markdown', }, { uri: 'pinepaper://docs/relations', name: 'Relations Guide', description: 'How to use relations for behavior-driven animation', mimeType: 'text/markdown', }, { uri: 'pinepaper://docs/generators', name: 'Background Generators', description: 'Available procedural background generators', mimeType: 'text/markdown', }, { uri: 'pinepaper://examples/solar-system', name: 'Solar System Example', description: 'Create an animated solar system with orbiting planets', mimeType: 'text/markdown', }, ]; const RESOURCE_CONTENTS: Record<string, string> = { 'pinepaper://docs/getting-started': `# Getting Started with PinePaper MCP ## Overview PinePaper is a canvas-based animation tool. This MCP server lets you create and animate graphics using natural language. ## Quick Start 1. **Connect to browser**: Use \`pinepaper_browser_connect\` to launch PinePaper Studio 2. **Clear the canvas**: Use \`pinepaper_clear_canvas\` or \`pinepaper_refresh_page\` to start fresh 3. **Create items**: Use \`pinepaper_create_item\` to add text, shapes, or graphics 4. **Add animation**: Use \`pinepaper_add_relation\` for behavior-based animation 5. **Verify**: Use \`pinepaper_browser_screenshot\` to see your work 6. **Export**: Use \`pinepaper_export_svg\` to save your work ## IMPORTANT: Welcome Template First-time visitors to PinePaper Studio see a **welcome template** with example items on the canvas. Before creating your own content: ### To Clear the Welcome Template: - **Option 1**: Use \`pinepaper_clear_canvas\` to remove all items - **Option 2**: Use \`pinepaper_refresh_page\` to reload the page (most reliable) - After clearing, verify with \`pinepaper_get_items\` that the canvas is empty The welcome template only appears once. After refreshing or clearing, it won't appear again. ## Key Concepts ### Items Everything on the canvas is an item with a registry ID (e.g., "item_1"). Use this ID to reference items in other operations. **Creating Items:** - Use \`pinepaper_create_item\` with \`itemType\` (text, circle, star, rectangle, etc.) - Specify \`position\` as {x, y} coordinates - Add \`properties\` for styling (color, fontSize, radius, etc.) ### Relations (Primary Animation Method) Relations describe HOW items behave relative to each other. This is the **recommended way** to create animations: - \`orbits\`: Circular motion around another item - \`follows\`: Move toward another item with smoothing - \`attached_to\`: Fixed offset from parent item - \`points_at\`: Rotate to face another item - \`maintains_distance\`: Keep fixed distance from target **Verifying Animations:** After adding a relation, use \`pinepaper_browser_screenshot\` to verify the animation is working. Check that: 1. Items are positioned correctly 2. Animation is visibly moving (take multiple screenshots at intervals) 3. The relation appears in \`pinepaper_query_relations\` ### Deleting Items - Use \`pinepaper_delete_item\` with the item's registry ID - Use \`pinepaper_get_items\` first to find item IDs - If deletion fails, try \`pinepaper_clear_canvas\` to remove everything ### Generators Procedural backgrounds like sunburst, waves, grid patterns. ## Example Workflow \`\`\` 1. Connect: pinepaper_browser_connect 2. Clear canvas: pinepaper_clear_canvas (removes welcome template) 3. Verify empty: pinepaper_get_items (should return count: 0) 4. Create sun: pinepaper_create_item type=circle, color=#fbbf24, radius=60 5. Create earth: pinepaper_create_item type=circle, color=#3b82f6, radius=20 6. Add orbit: pinepaper_add_relation earth orbits sun, radius=150 7. Screenshot: pinepaper_browser_screenshot (verify animation) \`\`\` `, 'pinepaper://docs/relations': `# Relations Guide Relations are the PRIMARY way to add animation in PinePaper. They describe behavior, not keyframes. ## Why Relations? 1. **Natural Language**: "moon orbits earth" is easier than specifying every frame 2. **Compositional**: Multiple relations can work together 3. **Training Data**: Relations generate instruction/code pairs for LLM fine-tuning ## Available Relations ### orbits Circular motion around target. - \`radius\`: Orbit radius in pixels - \`speed\`: Rotation speed (1 = normal) - \`direction\`: 'clockwise' or 'counterclockwise' - \`phase\`: Starting angle offset in degrees ### follows Move toward target with smoothing. - \`offset\`: [x, y] offset from target - \`smoothing\`: 0-1 (lower = more responsive) - \`delay\`: Seconds of lag ### attached_to Fixed offset from target (parent-child). - \`offset\`: [x, y] fixed offset - \`inherit_rotation\`: Also rotate with parent ### maintains_distance Stay at fixed distance from target. - \`distance\`: Target distance in pixels - \`strength\`: How strongly to maintain (0-1) ### points_at Rotate to face target. - \`offset_angle\`: Additional rotation - \`smoothing\`: Rotation smoothing ### mirrors Mirror position across axis. - \`axis\`: 'vertical', 'horizontal', or 'both' - \`center\`: [x, y] mirror center ### parallax Move relative to target by depth. - \`depth\`: 0-1 (0 = no movement, 1 = full movement) - \`origin\`: [x, y] parallax origin ### bounds_to Stay within target's bounds. - \`padding\`: Inner padding - \`bounce\`: Bounce off edges ## Verifying Animations Are Working After adding a relation, **always verify** that the animation is actually running: ### Method 1: Take Multiple Screenshots Take 2-3 screenshots with a small delay between them: \`\`\` 1. pinepaper_browser_screenshot (save position) 2. Wait 1-2 seconds 3. pinepaper_browser_screenshot (compare position) \`\`\` If the orbiting item has moved, the animation is working. ### Method 2: Query Relations Use \`pinepaper_query_relations\` to confirm the relation was added: \`\`\` pinepaper_query_relations itemId=earth direction=outgoing \`\`\` Should return the orbits relation. ### Method 3: Check Item Animation State Use \`pinepaper_get_items\` with filter \`hasRelation: true\` to see animated items. ### Troubleshooting Animation Issues If animations don't appear to be working: 1. Verify both source and target items exist with \`pinepaper_get_items\` 2. Check the relation was added with \`pinepaper_query_relations\` 3. Try refreshing the page with \`pinepaper_refresh_page\` and recreating 4. For keyframe animations, ensure \`pinepaper_play_timeline\` was called ## Composition Example An item can have multiple relations: \`\`\` // Planet orbits star AND label stays attached addRelation(planet, star, 'orbits', { radius: 200, speed: 0.5 }) addRelation(label, planet, 'attached_to', { offset: [0, -30] }) \`\`\` The label will follow the planet as it orbits the star! `, 'pinepaper://docs/generators': `# Background Generators Procedural backgrounds that create dynamic patterns. ## Available Generators ### drawSunburst Radial rays from center. - \`rayCount\`: Number of rays (default: 16) - \`colors\`: Array of ray colors - \`bgColor\`: Background color - \`animated\`: Enable rotation ### drawSunsetScene Animated sunset with clouds. - \`sunColor\`: Sun fill color - \`skyColors\`: Array for gradient - \`cloudCount\`: Number of clouds ### drawGrid Geometric grid patterns. - \`gridType\`: 'lines', 'dots', or 'squares' - \`spacing\`: Cell size - \`lineColor\`: Grid color ### drawWaves Layered wave pattern. - \`waveCount\`: Number of layers - \`colors\`: Wave colors - \`amplitude\`: Wave height - \`animated\`: Animate waves ### drawCircuit Tech circuit board. - \`lineColor\`: Circuit lines - \`nodeColor\`: Junction color - \`density\`: 0-1 complexity - \`animated\`: Enable bolt effect `, 'pinepaper://examples/solar-system': `# Solar System Example Create an animated solar system with orbiting planets. ## Step by Step ### 1. Set up the canvas \`\`\` Set background color to #0f172a (dark blue) \`\`\` ### 2. Create the sun \`\`\` Create a yellow circle at position (400, 300) with radius 60 → This creates item_1 (the sun) \`\`\` ### 3. Create Earth \`\`\` Create a blue circle at position (550, 300) with radius 20 → This creates item_2 (Earth) \`\`\` ### 4. Create the Moon \`\`\` Create a gray circle at position (590, 300) with radius 8 → This creates item_3 (Moon) \`\`\` ### 5. Add orbital relations \`\`\` Add relation: item_2 (Earth) orbits item_1 (Sun) at radius 150, speed 0.3 Add relation: item_3 (Moon) orbits item_2 (Earth) at radius 40, speed 0.8 \`\`\` ### 6. Add labels (optional) \`\`\` Create text "Sun" at (400, 250) Add relation: text attached_to item_1 with offset [0, -70] \`\`\` ## Result The Earth orbits the Sun, and the Moon orbits the Earth, all with smooth automatic animation! ## Training Data This scene generates training pairs like: - "Earth orbits Sun at radius 150" → \`app.addRelation('item_2', 'item_1', 'orbits', {radius: 150})\` `, }; // ============================================================================= // SERVER OPTIONS // ============================================================================= export interface ServerOptions { locale?: SupportedLocale; /** Enable browser execution mode - executes code in real browser via Puppeteer */ browserMode?: boolean; } // ============================================================================= // SERVER CREATION // ============================================================================= export async function createServer(options: ServerOptions = {}): Promise<Server> { // Initialize i18n with specified locale const locale = options.locale || detectLocaleFromEnvironment(); const i18n = await initI18n(locale); const server = new Server(SERVER_INFO, { capabilities: { tools: {}, resources: {}, }, }); // --------------------------------------------------------------------------- // TOOL HANDLERS // --------------------------------------------------------------------------- // List available tools (with optional localization) server.setRequestHandler(ListToolsRequestSchema, async () => { // Tool descriptions are kept in English for AI understanding // but can be localized if needed for UI display return { tools: PINEPAPER_TOOLS, }; }); // Handle tool calls with i18n support for errors and success messages // Browser mode enables live execution in PinePaper Studio const executeInBrowser = options.browserMode ?? true; // Default to browser mode server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; return handleToolCall(name, args as Record<string, unknown>, { i18n, executeInBrowser, }); }); // --------------------------------------------------------------------------- // RESOURCE HANDLERS // --------------------------------------------------------------------------- // List available resources server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: RESOURCES, }; }); // Read resource content server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params; const content = RESOURCE_CONTENTS[uri]; if (!content) { throw new Error(`Resource not found: ${uri}`); } return { contents: [ { uri, mimeType: 'text/markdown', text: content, }, ], }; }); return server; } // ============================================================================= // LOCALE DETECTION // ============================================================================= /** * Detect locale from environment variables * Checks PINEPAPER_LOCALE, LANG, LC_ALL, LC_MESSAGES */ function detectLocaleFromEnvironment(): SupportedLocale { // Check explicit PinePaper locale setting const pinepaperLocale = process.env.PINEPAPER_LOCALE; if (pinepaperLocale && isValidLocale(pinepaperLocale)) { return pinepaperLocale as SupportedLocale; } // Check standard locale environment variables const envLocale = process.env.LC_ALL || process.env.LC_MESSAGES || process.env.LANG || ''; // Parse locale string (e.g., "en_US.UTF-8" -> "en") const localePart = envLocale.split('.')[0]; // Remove encoding const langPart = localePart.split('_')[0]; // Get language code if (isValidLocale(langPart)) { return langPart as SupportedLocale; } // Check for regional variants (e.g., "zh_CN" -> "zh-CN") const regionalLocale = localePart.replace('_', '-'); if (isValidLocale(regionalLocale)) { return regionalLocale as SupportedLocale; } return DEFAULT_LOCALE; } /** * Check if a string is a valid supported locale */ function isValidLocale(locale: string): boolean { const SUPPORTED = [ 'en', 'es', 'fr', 'de', 'it', 'pt', 'pt-BR', 'nl', 'pl', 'ru', 'uk', 'zh-CN', 'zh-TW', 'ja', 'ko', 'th', 'vi', 'id', 'ms', 'tl', 'hi', 'bn', 'ta', 'te', 'mr', 'gu', 'kn', 'ml', 'pa', 'ar', 'he', 'fa', 'tr', 'sv', 'da', 'no', 'fi', 'cs', 'el', 'hu', 'ro', ]; return SUPPORTED.includes(locale); } // ============================================================================= // MAIN ENTRY POINT // ============================================================================= async function main(): Promise<void> { const locale = detectLocaleFromEnvironment(); const server = await createServer({ locale }); const transport = new StdioServerTransport(); console.error('PinePaper MCP Server starting...'); console.error(`Version: ${SERVER_INFO.version}`); console.error(`Locale: ${locale}`); console.error(`Tools: ${PINEPAPER_TOOLS.length}`); console.error(`Resources: ${RESOURCES.length}`); await server.connect(transport); console.error('PinePaper MCP Server running on stdio'); } // Run if executed directly main().catch((error) => { console.error('Fatal error:', error); process.exit(1); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pinepaper/mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server