ASCII Art Meets AI: Building an MCP Server for Monodraw

ASCII Art Meets AI: Building an MCP Server for Monodraw

I love ASCII art. There's something wonderfully retro about making diagrams with box-drawing characters and careful spacing. And Monodraw on macOS is fantastic for creating them—it's like having an IDE for text art.

But you know what would be even better? Having AI assistants use Monodraw to create diagrams for me.

So I built an MCP server that does exactly that.

What's MCP?

Model Context Protocol (MCP) is Anthropic's standard for giving AI assistants access to external tools. Instead of prompt-hacking your way to functionality, you expose capabilities through a clean JSON-RPC interface. The AI can then call your tools, passing structured parameters and receiving structured responses.

It's like giving Claude (or any compatible assistant) superpowers—but organized, auditable superpowers.

The Problem: Diagrams Are Annoying

When I'm working with an AI assistant and need a diagram, there's always friction:

  1. "Draw me a diagram" → AI outputs ASCII art in a code block
  2. It looks... fine, but it's just monospace text, not carefully crafted
  3. If I want to edit it, I'm hand-adjusting spaces
  4. If I want to export it properly, I'm copying to another tool

What I really wanted: AI describes what it wants → Monodraw creates it → I get actual ASCII art I can edit and export.

The Solution: AppleScript + MCP

Monodraw is scriptable via AppleScript (bless macOS for maintaining this ancient magic). I can:

  • Create new canvases
  • Add shapes and text
  • Export as text or PNG
  • Read back the contents

Wrapping this in an MCP server was straightforward:

const server = new McpServer({
  name: 'mcp-monodraw',
  version: '0.1.0',
});

server.tool('create_diagram', {
  description: 'Create an ASCII diagram in Monodraw',
  parameters: z.object({
    title: z.string().optional(),
    elements: z.array(z.object({
      type: z.enum(['box', 'line', 'text', 'arrow']),
      x: z.number(),
      y: z.number(),
      width: z.number().optional(),
      height: z.number().optional(),
      content: z.string().optional(),
    })),
  }),
  handler: async ({ title, elements }) => {
    const script = buildAppleScript(title, elements);
    const result = await runAppleScript(script);
    return { success: true, content: result };
  },
});

The magic is in buildAppleScript, which translates high-level element descriptions into Monodraw commands:

function buildAppleScript(title: string | undefined, elements: Element[]): string {
  let script = `
    tell application "Monodraw"
      make new document
      ${title ? `set name of document 1 to "${title}"` : ''}
  `;

  for (const el of elements) {
    switch (el.type) {
      case 'box':
        script += `
          make new rectangle at document 1
          set frame of rectangle -1 to {${el.x}, ${el.y}, ${el.width}, ${el.height}}
        `;
        break;
      case 'text':
        script += `
          make new text block at document 1
          set content of text block -1 to "${el.content}"
          set position of text block -1 to {${el.x}, ${el.y}}
        `;
        break;
      // ... arrows, lines, etc.
    }
  }

  script += `
      return ASCII text of document 1
    end tell
  `;

  return script;
}

Using It

Once the server is running, any MCP-compatible assistant can use it. Here's what happens when I ask Claude to create an architecture diagram:

Me: "Create a diagram showing a web client connecting to an API server, which connects to both a database and a cache."

Claude: calls mcp-monodraw.create_diagram with structured elements

Result:

┌──────────────┐      ┌───────────────┐
│  Web Client  │─────▶│  API Server   │
└──────────────┘      └───────┬───────┘
                              │
                    ┌─────────┴─────────┐
                    │                   │
                    ▼                   ▼
            ┌──────────────┐    ┌──────────────┐
            │   Database   │    │    Cache     │
            └──────────────┘    └──────────────┘

And it's not just a code block—it's an actual Monodraw document I can open, edit, and export.

The Joy of Unnecessary Projects

Is this necessary? Of course not. I could just... draw the diagram myself. Or ask the AI to output ASCII in a code block. Or use Mermaid/PlantUML.

But there's something delightful about the workflow:

  1. Think in concepts: "show me client-server with caching"
  2. Get actual editable art

No manual sizing. No pixel-pushing. Just describe what I want and watch it appear in Monodraw's canvas.

Plus, it's a great example of what MCP enables. The AI doesn't need to know AppleScript. It doesn't need to know how Monodraw works. It just needs to describe elements in JSON, and my server handles the translation.

What's Next

The current implementation is basic—boxes, lines, text, arrows. But Monodraw can do so much more:

  • Curved lines and connectors
  • Multiple line styles (dotted, dashed, thick)
  • Unicode box-drawing variants
  • Layer management
  • Export to SVG/PNG

I'm also considering adding a read_diagram tool that lets the AI analyze existing Monodraw documents. Imagine: "What does this architecture look like?" with the AI actually parsing the ASCII art.

The Bigger Picture

MCP is changing how I think about AI tooling. Instead of cramming everything into the context window or hoping the AI can figure out CLI tools from documentation, I can build clean, typed interfaces that expose exactly the capabilities I want.

mcp-monodraw is a silly example—ASCII diagrams are hardly mission-critical. But the pattern is powerful. Any app that's scriptable (and on macOS, that's a lot of them) can become an AI tool.

What weird, wonderful tools will people build? I'm excited to find out.

And in the meantime, I'm going to keep asking Claude to draw me ASCII architecture diagrams, because it genuinely sparks joy.


The code is at github.com/Caryyon/mcp-monodraw if you want to play with it. Requires macOS and Monodraw. Yes, that's a pretty narrow audience. No, I don't care.