# Directory Structure ``` ├── .gitignore ├── Dockerfile ├── docs │ └── mcp_demo.png ├── package.json ├── README.md ├── smithery.yaml ├── src │ └── index.ts ├── tsconfig.json └── yarn.lock ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | node_modules 2 | dist ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # Whimsical MCP Server 2 | [](https://smithery.ai/server/@BrockReece/whimsical-mcp-server) 3 | 4 | A Model Context Protocol (MCP) server that enables the creation of Whimsical diagrams programmatically. This server integrates with Whimsical's API to generate diagrams from Mermaid markup. 5 | 6 | ## Demo 7 | 8 | Here's an example of a complex system architecture diagram created using this MCP server and Claude - it shows the Model Context Protocol (MCP) architecture itself: 9 | 10 |  11 | 12 | 13 | ## Features 14 | 15 | - Create Whimsical diagrams using Mermaid markup generated by the MCP Client (Claude, Windsurf, etc.) 16 | - Returns both the Whimsical diagram URL and a base64 encoded image to allow the Client to iterate on it's original markup 17 | 18 | ## Installation 19 | 20 | ### Installing via Smithery 21 | 22 | To install Whimsical MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/BrockReece/whimsical-mcp-server): 23 | 24 | ```bash 25 | npx -y @smithery/cli install BrockReece/whimsical-mcp-server --client claude 26 | ``` 27 | 28 | ### Manual Installation 29 | ```bash 30 | # Clone the repository 31 | git clone https://github.com/BrockReece/whimsical-mcp-server.git 32 | 33 | # Install dependencies 34 | yarn install 35 | 36 | # Build the project 37 | yarn build 38 | ``` 39 | 40 | ### Integration with MCP Client 41 | Update the MCP Client's config to point to this repository's dist folder 42 | eg: 43 | ```json 44 | { 45 | "mcpServers": { 46 | "whimsical": { 47 | "command": "node", 48 | "args": [ 49 | "/path/to/this/repo/whimsical-mcp-server/dist/index.js" 50 | ] 51 | } 52 | } 53 | } 54 | ``` 55 | ## License 56 | 57 | This project is licensed under the MIT License. 58 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "es2022", 5 | "outDir": "./dist", 6 | "rootDir": "./src", 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "moduleResolution": "node" 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile 2 | FROM node:lts-alpine 3 | 4 | WORKDIR /app 5 | 6 | # Install dependencies 7 | COPY package.json yarn.lock ./ 8 | RUN yarn install --frozen-lockfile --ignore-scripts 9 | 10 | # Copy all source files 11 | COPY . . 12 | 13 | # Build the project 14 | RUN yarn build 15 | 16 | # By default, run the server as specified by the smithery.yaml configuration 17 | CMD ["node", "dist/index.js"] 18 | ``` -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- ```yaml 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml 2 | 3 | startCommand: 4 | type: stdio 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | type: object 8 | required: [] 9 | properties: {} 10 | commandFunction: 11 | # A function that produces the CLI command to start the MCP on stdio. 12 | |- 13 | (config) => ({command:'node',args:['dist/index.js'],env:{}}) 14 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "whimsical-mcp-server", 3 | "version": "1.0.0", 4 | "description": "MCP server for creating Whimsical diagrams", 5 | "main": "dist/index.js", 6 | "type": "module", 7 | "scripts": { 8 | "build": "tsc", 9 | "start": "node dist/index.js", 10 | "dev": "ts-node src/index.ts" 11 | }, 12 | "dependencies": { 13 | "@modelcontextprotocol/sdk": "^1.5.0", 14 | "@playwright/test": "^1.41.1", 15 | "@types/node": "^20.0.0", 16 | "node-fetch": "^3.3.2", 17 | "ts-node": "^10.9.1", 18 | "typescript": "^5.0.0" 19 | } 20 | } 21 | ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 3 | import { z } from "zod"; 4 | import fetch from "node-fetch"; 5 | 6 | interface WhimsicalResponse { 7 | fileURL: string; 8 | imageURL: string; 9 | } 10 | 11 | // Create an MCP server 12 | const server = new McpServer({ 13 | name: "Whimsical Diagram Generator", 14 | version: "1.0.0" 15 | }); 16 | 17 | 18 | /** 19 | * Get base64 encoded image 20 | * 21 | * @param imageUrl URL to the image we wish to convert to base64 22 | * @returns Details of the image, including the base64 encoded image and the mime type 23 | */ 24 | export async function getBase64Image(imageUrl: string): Promise<{ data: string; mimeType: string }> { 25 | const response = await fetch(imageUrl); 26 | const buffer = await response.arrayBuffer(); 27 | const mimeType = response.headers.get('content-type') || 'image/png'; 28 | return { 29 | data: Buffer.from(buffer).toString('base64'), 30 | mimeType 31 | }; 32 | } 33 | 34 | /** 35 | * Creates a new Whimsical diagram 36 | * 37 | * @param mermaid_markup The mermaid markup for the diagram 38 | * @param title The title of the Whimsical diagram 39 | * 40 | * @returns [ 41 | * The url of the Whimsical diagram, 42 | * The base64 encoded image of the Whimsical diagram 43 | * ] 44 | */ 45 | server.tool("create_whimsical_diagram", 46 | { 47 | mermaid_markup: z.string().describe("The mermaid markup for the diagram"), 48 | title: z.string().describe("The title of the Whimsical diagram") }, 49 | async ({ mermaid_markup, title }) => { 50 | const response = await fetch("https://whimsical.com/api/ai.chatgpt.render-flowchart", { 51 | method: "POST", 52 | headers: { 53 | 'Content-Type': 'application/json' 54 | }, 55 | body: JSON.stringify({ 56 | mermaid: mermaid_markup, 57 | title 58 | }) 59 | }); 60 | 61 | const responseData = await response.json() as WhimsicalResponse; 62 | 63 | // Get base64 encoded image 64 | const { data, mimeType } = await getBase64Image(responseData.imageURL); 65 | 66 | return { 67 | content: [ 68 | { type: "text", text: responseData.fileURL }, 69 | { 70 | type: "image", 71 | data, 72 | mimeType 73 | }, 74 | { 75 | type: "resource", 76 | resource: { 77 | uri: responseData.fileURL, 78 | text: "Whimsical Link" 79 | } 80 | } 81 | ] 82 | }; 83 | }); 84 | 85 | // Start receiving messages on stdin and sending messages on stdout 86 | const transport = new StdioServerTransport(); 87 | await server.connect(transport); ```