PrototypeAn MCP server for WhatsApp agents

Images your agentcan send.

Your WhatsApp agent sends data and gets back an image for the chat, in one tool call: a ticket for a customer, a sales chart for the owner. The model writes the data. Templates draw it and the math is done in code.

Formats

Two templates today. Rendered live by the service.

Ticket for Café Tortoni: four items, discount, 21% IVA and total, paid with Mercado Pago

Ticket render_ticket

For the customer. Items, discount, tax and payment; totals computed in integer cents on the server.

Bar chart of a week of sales, Saturday highest in green, with the weekly total

Chart render_chart

For the owner. Up to 14 bars with values, the highest in green, and the total underneath.

How it works

One tool call. An image message comes out.

WhatsApp agents can already send images, but the image has to exist at a URL first. This fills that gap: the agent generates the image from the conversation.

01

The agent collects the order

Store, items, quantities, prices, payment method. Whatever the conversation already has.

02

It calls render_ticket

POST /tickets validates the order, computes subtotal, discount, IVA and total, and returns an image_url.

03

The agent sends it as an image

The URL goes straight into an image message. The customer gets a receipt, not a wall of text.

CustomerWhatsApp app WhatsAppMeta Cloud API WhatsApp agentLLM + tools Tickets serviceCloudflare Worker "me pasás el ticket?" webhook: new message tool render_ticket → POST /tickets { image_url, totals } send image(image_url) GET /t/<ticket>.png PNG, cached forever receipt image in the chat reads the order from the chat validate itemstotals in integer cents HTML → PNGsatori + resvg 12 34 56 78
API

JSON in. Image URL out.

No storage: the ticket lives in the URL, so every image is cacheable forever. A ticket too big for WhatsApp's 2,048-character image_url limit is a 413, and bad input is a 400 the agent can read and fix.

requestPOST /tickets
curl -X POST $HOST/tickets \
  -H 'authorization: Bearer $KEY' \
  -H 'content-type: application/json' \
  -d '{
  "store": { "name": "Café Tortoni" },
  "items": [
    { "name": "Café con leche", "qty": 2, "price": 4200 },
    { "name": "Medialunas",     "qty": 3, "price": 1500 }
  ],
  "taxRate": 0.21,
  "payment": "Mercado Pago"
}'
response200
{
  "image_url": "https://…/t/eyJzdG9y….png",
  "totals": {
    "subtotal": 12900,
    "discount": 0,
    "tax": 2709,
    "total": 15609
  }
}

// then, in the agent:
send_image({ image_url, caption: "Tu ticket 👇" })
Install

Add it to your agent. One URL and a key.

Any agent that speaks MCP: add the server, send the result as an image, add one line of prompt.

01

Add the MCP server

Streamable HTTP. The client lists render_ticket and render_chart with their schemas. Keys are issued by hand while this is a prototype: ask for one.

Name
Quick Images MCP
Server URL
https://quick-images-mcp.fguespe.workers.dev/mcp
Headers (JSON)
{ "Authorization": "Bearer <your key>" }
02

One line per tool in the system prompt

Paste the ones you use, plus your own business data: menu, prices, sales.

render_ticket
Tickets: call render_ticket and send the image_url as an image, with the caption "Order for <customer>. Total: $<total>" using the total the tool returns.
render_chart
Sales: find the tickets in this chat and in past chats. Call render_chart with one point per ticket (label: customer, value: total) and send it the same way, with the tool's total in the caption. If there are no tickets, say so. Sales and tickets are the same thing.
Prefer webhook tools? Same two tools, pasted field by field.
A

Webhook tool: render_ticket

Name
render_ticket
Description
Render the order ticket as an image. Returns image_url and totals.
Method + URL
POST https://quick-images-mcp.fguespe.workers.dev/tickets
Body schema
{
  "type": "object",
  "properties": {
    "store": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        }
      },
      "required": [
        "name"
      ]
    },
    "customer": {
      "type": "string",
      "description": "Customer name"
    },
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "qty": {
            "type": "number"
          },
          "price": {
            "type": "number"
          }
        },
        "required": [
          "name",
          "qty",
          "price"
        ]
      }
    },
    "currency": {
      "type": "string"
    },
    "payment": {
      "type": "string",
      "description": "Payment method, e.g. Mercado Pago"
    }
  },
  "required": [
    "store",
    "items"
  ]
}
B

Webhook tool: render_chart

Name
render_chart
Description
Render a bar chart (e.g. sales per day) as an image. Returns image_url and total.
Method + URL
POST https://quick-images-mcp.fguespe.workers.dev/charts
Body schema
{
  "type": "object",
  "properties": {
    "title": {
      "type": "string"
    },
    "subtitle": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "points": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string"
          },
          "value": {
            "type": "number"
          }
        },
        "required": [
          "label",
          "value"
        ]
      }
    }
  },
  "required": [
    "title",
    "points"
  ]
}
Try it

Edit the order. Watch the ticket.

This calls the same endpoint the agent does.

rendering…
Why

Why images in a WhatsApp chat.

Totals in code

The model passes items and prices. Subtotal, discount, tax and total are computed in integer cents on the server, so the number on the receipt is the number you charge.

Readable at a glance

Five lines of items as text get lost in a chat. A receipt is a format people already trust and screenshot.

Same pattern, more formats

Tickets and charts today: POST /charts turns a list of numbers into a bar chart for backoffice questions like "how did we sell this week?" (example). Order status and booking confirmations are the same pattern.