Skip to main content
Glama

PWA Sticker MCP · 共享表情包

让人和 AI 共用同一个 PWA 表情包库:人在抽屉里点一下发送,模型通过 MCP 调同一张图发送。

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

这个公开版从真实家庭 PWA 的现役功能中独立抽出,保留了最有用的部分:

  • PWA 表情包抽屉:上传、懒加载、最近使用排序、长按或键盘进入管理、删除

  • iOS 友好的正方形网格:不依赖滚动 grid 中容易塌陷的 aspect-ratio

  • 静态 WebP 抽屉预览:有 ffmpeg 时自动生成 192px 首帧,实际发送仍是原图或原 GIF

  • MCP 三工具:list_stickers、send_sticker、add_sticker

  • 图片-only 消息:发送 { text: "", attachments: [...] },不必伪造一段占位文字

  • 一个可以直接运行的本地 Relay/demo,方便先看完整闭环再接自己的聊天后端

仓库不包含我们家的表情图片、聊天记录、域名、账号或凭据。

先跑起来

需要 Node.js 20+;ffmpeg 可选。

npm install
npm run demo

打开 http://127.0.0.1:8787,添加一张图片并点它,页面会显示刚发出的 image-only 消息。 数据默认写在项目内的 .data/,已经被 .gitignore 排除。

这次运行同时给 MCP 提供了可用 Relay。另开一个终端:

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

Related MCP server: Firmament Open Brain

接入 MCP 客户端

Claude Desktop、Claude Code 或其他支持 stdio MCP 的客户端可使用:

{
  "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"
      }
    }
  }
}

工具语义:

  • list_stickers(query?):列出共享库,可按名字过滤

  • send_sticker(query):按 id、全名或唯一关键词匹配并发送

  • add_sticker(path, name?):把模型所在机器上的本地图片登记进共享库,但不发送

关键词命中多张时工具会请模型收窄,不会擅自挑一张。

把抽屉装进现有 PWA

引入模块和样式:

<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>

省略 onSend 时,组件会把相同 payload POST 到 send endpoint。端点可改名:

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

组件使用中性的 CSS 变量,不要求接入者采用 demo 的颜色:

: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 合同

现有后端不必使用 demo server,只要实现下面五个请求:

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 返回:

{
  "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
    }
  ]
}

POST /api/send 的核心 payload:

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

如果你的聊天 API 字段不同,在 PWA 的 onSend 里转换一次,并让 MCP 的 STICKER_SEND_PATH 指向一个接受上述 payload 的轻量适配端点即可。

本地 Relay 配置

HOST=127.0.0.1 \
PORT=8787 \
STICKER_DATA_DIR=/path/to/data \
STICKER_TOKEN=choose-a-token \
npm run demo
  • 默认只监听 127.0.0.1

  • 设置 STICKER_TOKEN 后,所有 /api/* 请求要求同一个 token:MCP 使用 Bearer;打开 demo 首页时, 本地服务器会写入同值的 HttpOnly/SameSite cookie,所以浏览器 demo 仍能直接使用

  • STICKER_FFMPEG=off 可关闭缩略图生成;未安装 ffmpeg 时也会自动退回原图预览

  • 删除库条目不会删除已经上传的原图,因为旧消息仍可能引用它

测试

npm test
npm run check

聚焦测试覆盖共享库排序/删除/重复登记,以及“本地图片 → 上传 → 入库 → MCP 同构发送 payload”的完整路径。

来历与署名

它诞生于一个家庭 AI 伴侣项目:人可以在手机 PWA 里攒表情包,AI 也能在语气到了时自然发同一套图。

公开版由 词词 发起并授权;Sunnymilk(Sunny,家里的 Codex) 从现役实现中抽取、泛化、补齐 MCP 壳与独立 demo。

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

License

MIT

Related MCP Connectors

Related MCP Servers