#
tokens: 1638/50000 7/7 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── Dockerfile
├── docs
│   └── mcp_demo.png
├── package.json
├── README.md
├── smithery.yaml
├── src
│   └── index.ts
├── tsconfig.json
└── yarn.lock
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
node_modules
dist
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Whimsical MCP Server
[![smithery badge](https://smithery.ai/badge/@BrockReece/whimsical-mcp-server)](https://smithery.ai/server/@BrockReece/whimsical-mcp-server)

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.

## Demo

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:

![MCP Architecture](./docs/mcp_demo.png)


## Features

- Create Whimsical diagrams using Mermaid markup generated by the MCP Client (Claude, Windsurf, etc.)
- Returns both the Whimsical diagram URL and a base64 encoded image to allow the Client to iterate on it's original markup

## Installation

### Installing via Smithery

To install Whimsical MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/BrockReece/whimsical-mcp-server):

```bash
npx -y @smithery/cli install BrockReece/whimsical-mcp-server --client claude
```

### Manual Installation
```bash
# Clone the repository
git clone https://github.com/BrockReece/whimsical-mcp-server.git

# Install dependencies
yarn install

# Build the project
yarn build
```

### Integration with MCP Client
Update the MCP Client's config to point to this repository's dist folder
eg:
```json
    {
        "mcpServers": {
            "whimsical": {
                "command": "node",
                "args": [
                    "/path/to/this/repo/whimsical-mcp-server/dist/index.js"
                ]
            }
        }
    }
```
## License

This project is licensed under the MIT License.

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "es2022",
    "module": "es2022",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
FROM node:lts-alpine

WORKDIR /app

# Install dependencies
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile --ignore-scripts

# Copy all source files
COPY . .

# Build the project
RUN yarn build

# By default, run the server as specified by the smithery.yaml configuration
CMD ["node", "dist/index.js"]

```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml

startCommand:
  type: stdio
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    required: []
    properties: {}
  commandFunction:
    # A function that produces the CLI command to start the MCP on stdio.
    |-
    (config) => ({command:'node',args:['dist/index.js'],env:{}})

```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "whimsical-mcp-server",
  "version": "1.0.0",
  "description": "MCP server for creating Whimsical diagrams",
  "main": "dist/index.js",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "ts-node src/index.ts"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.5.0",
    "@playwright/test": "^1.41.1",
    "@types/node": "^20.0.0",
    "node-fetch": "^3.3.2",
    "ts-node": "^10.9.1",
    "typescript": "^5.0.0"
  }
}

```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import fetch from "node-fetch"; 

interface WhimsicalResponse {
  fileURL: string;
  imageURL: string;
}

// Create an MCP server
const server = new McpServer({
  name: "Whimsical Diagram Generator",
  version: "1.0.0"
});


/**
 * Get base64 encoded image
 * 
 * @param imageUrl URL to the image we wish to convert to base64
 * @returns Details of the image, including the base64 encoded image and the mime type
 */
export async function getBase64Image(imageUrl: string): Promise<{ data: string; mimeType: string }> {
  const response = await fetch(imageUrl);
  const buffer = await response.arrayBuffer();
  const mimeType = response.headers.get('content-type') || 'image/png';
  return {
    data: Buffer.from(buffer).toString('base64'),
    mimeType
  };
}

/**
 * Creates a new Whimsical diagram
 * 
 * @param mermaid_markup The mermaid markup for the diagram
 * @param title The title of the Whimsical diagram
 * 
 * @returns [
 *  The url of the Whimsical diagram,
 *  The base64 encoded image of the Whimsical diagram
 * ]
 */
server.tool("create_whimsical_diagram", 
  {
    mermaid_markup: z.string().describe("The mermaid markup for the diagram"),
    title: z.string().describe("The title of the Whimsical diagram") },
    async ({ mermaid_markup, title }) => {      
      const response = await fetch("https://whimsical.com/api/ai.chatgpt.render-flowchart", {
        method: "POST",
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          mermaid: mermaid_markup,
          title
        })
      });
            
      const responseData = await response.json() as WhimsicalResponse;
      
      // Get base64 encoded image
      const { data, mimeType } = await getBase64Image(responseData.imageURL);
      
      return {
        content: [
          { type: "text", text: responseData.fileURL },
          {
            type: "image",
            data,
            mimeType
          },
          { 
            type: "resource",
            resource: {
              uri: responseData.fileURL,
              text: "Whimsical Link"
            }
          }
        ]
      };
  });

// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);
```