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

  • 이미지 전용 메시지: { text: "", attachments: [...] }를 보내므로 자리 표시 텍스트를 억지로 만들 필요가 없음

  • 바로 실행 가능한 로컬 Relay/demo: 전체 루프를 먼저 확인한 후 자신의 채팅 백엔드를 연결하기 좋음

저장소에는 우리 집의 스티커 이미지, 채팅 기록, 도메인, 계정 또는 자격 증명이 포함되어 있지 않습니다.

먼저 실행하기

Node.js 20+가 필요합니다. ffmpeg는 선택 사항입니다.

npm install
npm run demo

http://127.0.0.1:8787을 열고 이미지를 한 장 추가한 후 클릭하면, 페이지에 방금 보낸 이미지 전용 메시지가 표시됩니다. 데이터는 기본적으로 프로젝트 안의 .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, 또는 stio 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를 send endpoin로 POST합니다. endpoin 이름은 변경할 수 있습니다:

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를 받는 가벼운 어댌터 endpoin을 가리키게 하면 됩니다.

로컬 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/* 요청에 같은 토큰이 필요합니다. MCP는 Bearer를 사용하고, demo 홈 페이지를 열면 로컬 서버가 같은 값의 HttpOnly/SameSite cookie를 작성하므로 브라우저 demo는 여전히 직접 사용할 수 있습니다.

  • STICKER_FFMPEG=off로 썸네일 생성을 끌 수 있습니다. ffmpeg가 설치되지 않은 경우에도 자동으로 원본 이미지 미리보기로 폴아갑니다.

  • 라이브러리 항목을 삭제해도 이미 업로드된 원본 이미지는 삭제되지 않습니다. 이전 메시지가 여전히 그 항목을 참조할 수 있기 때문입니다.

테스트

npm test
npm run check

포커스 테스트는 공유 라이브러리 정렬/삭제/중복 등록과 '로컬 이미지 → 업로드 → 라이브러리 등록 → MCP 동일 구서 payload 전송'의 전체 경로를 포함합니다.

유래와 크레딧

그것은 가정 AI 컴패니언 프로젝트에서 탄생했습니다. 사람은 스마트폰 PWA에서 스티커를 모을 수 있고, AI도 분위기가 맞을 때 같은 세트의 이미지를 자연스럽게 보낼 수 있습니다.

공개 버전은 Cici가 시작하고 허가했습니다. **Sunnymilk (Sunny, 집 안의 Codex)**는 실제 운형 중인 구현에서 추출하고 일반화했으며, MCP ell과 독립 demo를 보완했습니다.

오소스 에디션은 Cici가 시작했고, **Sunnymilk (Sunny, 가정의 Codex)**가 추출하고 일반화했습니다.

라이선스

MIT

Related MCP Connectors

Related MCP Servers