Skip to main content
Glama

PWA Sticker MCP · Shared Sticker Pack

Let humans and AI share the same PWA sticker library: a person taps to send from the drawer, and a model calls the same image via MCP.

The human-facing PWA drawer and the AI-facing MCP server share one sticker library and one attachment contract.

This public edition is extracted from the live features of a real family PWA, keeping the most useful parts:

  • PWA sticker drawer: upload, lazy loading, most-recently-used sorting, long-press or keyboard to enter management, delete

  • iOS-friendly square grid: does not rely on aspect-ratio, which tends to collapse in scrolling grids

  • Static WebP drawer preview: auto-generates a 192px first frame when ffmpeg is available; the actual send still uses the original image or original GIF

  • Three MCP tools: list_stickers, send_sticker, add_sticker

  • Image-only messages: send { text: "", attachments: [...] } without fabricating placeholder text

  • A local Relay/demo that runs out of the box, so you can see the full loop before wiring up your own chat backend

The repository does not contain our family's sticker images, chat logs, domain, accounts, or credentials.

Run it first

Requires Node.js 20+; ffmpeg is optional.

npm install
npm run demo

Open http://127.0.0.1:8787, add an image and click it, and the page will show the image-only message just sent. Data is written to .data/ inside the project by default, which is already excluded by .gitignore.

This run also provides a usable Relay for MCP. Open another terminal:

STICKER_RELAY_URL=http://127.0.0.1:8787 npm run mcp

Related MCP server: sticker-mcp

Connecting to an MCP client

Claude Desktop, Claude Code, or other clients that support stdio MCP can use:

{
  "mcpServers": {
    "stickers": {
      "command": "node",
      "args": ["/absolute/path/to/pwa-sticker-mcp/src/mcp-server.js"],
      "env": {
        "STICKER_RELAY_URL": "https://chat.example.com",
        "STICKER_TOKEN": "your-relay-token",
        "STICKER_STICKERS_PATH": "/api/stickers",
        "STICKER_UPLOAD_PATH": "/api/upload",
        "STICKER_SEND_PATH": "/api/send"
      }
    }
  }
}

Tool semantics:

  • list_stickers(query?): lists the shared library, optionally filtered by name

  • send_sticker(query): matches by id, full name, or unique keyword and sends

  • add_sticker(path, name?): registers a local image on the model's machine into the shared library without sending

When a keyword matches multiple stickers, the tool asks the model to narrow down rather than picking one on its own.

Installing the drawer into an existing PWA

Import the module and styles:

<link rel="stylesheet" href="/sticker-drawer.css">

<div id="stickerDrawer"></div>
<button
  id="stickerButton"
  type="button"
  aria-controls="stickerDrawer"
  aria-expanded="false"
  aria-pressed="false"
>
  表情包
</button>

<script type="module">
  import { StickerDrawer } from '/sticker-drawer.js'

  new StickerDrawer({
    root: document.querySelector('#stickerDrawer'),
    trigger: document.querySelector('#stickerButton'),
    apiBase: 'https://chat.example.com',
    headers: () => ({ Authorization: `Bearer ${getRelayToken()}` }),
    async onSend(attachment) {
      await sendMessage({ text: '', attachments: [attachment] })
    },
    onNotice(message, kind) {
      showToast(message, kind)
    }
  })
</script>

When onSend is omitted, the component POSTs the same payload to the send endpoint. The endpoint can be renamed:

new StickerDrawer({
  root,
  trigger,
  endpoints: {
    stickers: '/app/stickers',
    upload: '/app/upload',
    send: '/app/send'
  }
})

The component uses neutral CSS variables and does not require adopters to use the demo's colors:

:root {
  --psm-surface: #171717;
  --psm-surface-muted: #242424;
  --psm-ink: #fafafa;
  --psm-muted: #b8b8b8;
  --psm-accent: #f1b657;
  --psm-danger: #e26363;
  --psm-line: #343434;
}

Relay API contract

Existing backends do not need to use the demo server; they only need to implement the five requests below:

GET    /api/stickers
POST   /api/upload?name=<filename>        raw image body
POST   /api/stickers                      register uploaded metadata
POST   /api/stickers/<id>/use             update recent-use order
DELETE /api/stickers/<id>
POST   /api/send                          send an image attachment

GET /api/stickers returns:

{
  "stickers": [
    {
      "id": "abc123",
      "name": "wave hello",
      "url": "/uploads/wave.gif",
      "thumb_url": "/uploads/sticker-thumb-wave.webp",
      "mime": "image/gif",
      "width": 320,
      "height": 240,
      "last_used": 1787800000
    }
  ]
}

The core payload of POST /api/send:

{
  "text": "",
  "attachments": [
    {
      "url": "/uploads/wave.gif",
      "name": "wave hello",
      "mime": "image/gif",
      "kind": "image",
      "width": 320,
      "height": 240
    }
  ]
}

If your chat API uses different fields, transform once in the PWA's onSend, and point the MCP's STICKER_SEND_PATH at a lightweight adapter endpoint that accepts the above payload.

Local Relay configuration

HOST=127.0.0.1 \
PORT=8787 \
STICKER_DATA_DIR=/path/to/data \
STICKER_TOKEN=choose-a-token \
npm run demo
  • Listens on 127.0.0.1 by default

  • When STICKER_TOKEN is set, all /api/* requests require the same token: MCP uses Bearer; when opening the demo homepage, the local server writes an HttpOnly/SameSite cookie with the same value, so the browser demo still works directly

  • STICKER_FFMPEG=off disables thumbnail generation; it also falls back to original-image previews when ffmpeg is not installed

  • Deleting a library entry does not delete the already-uploaded original image, because old messages may still reference it

Tests

npm test
npm run check

The focused tests cover shared-library sorting/deletion/duplicate registration, as well as the full path of "local image → upload → register → MCP sends an isomorphic payload".

Origin and credits

It was born from a family AI companion project: people can collect stickers in a phone PWA, and the AI can naturally send the same set of images when the tone calls for it.

Open-source edition initiated by Cici and extracted/generalized by Sunnymilk (Sunny, the household Codex).

Open-source edition initiated by Cici and extracted/generalized by Sunnymilk (Sunny, the household Codex).

License

MIT

A
license - permissive license
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • Your memory, everywhere AI goes. Build knowledge once, access it via MCP anywhere.

  • Shared, governed long-term memory for AI agents across tools and sessions via MCP and REST.

  • Cross-vendor AI memory over MCP. One semantic store, readable and writeable from every MCP client.

View all MCP Connectors

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/gobly2333/pwa-sticker-mcp'

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