@agentpickup/mcp 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,6 +17,7 @@ QR they show at the counter to collect.
17
17
  | `start_group_order` | Starts a group order (a coffee run) with a closing time; returns the code to share and a key to close it early |
18
18
  | `join_group_order` | Adds the user's items to a group order by its code; pay the share with `pay_order` before it closes |
19
19
  | `group_order_status` | Who's in, what they ordered, who has paid, and whether the shop's order is ready or collected |
20
+ | `share_agentpickup` | A QR and link for friends to add agentPickup to their assistant (as a card where the chat shows cards), for everyone or with one café picked |
20
21
  | `close_group_order` | Closes a group order early, as its organiser; the shop gets one combined order and the organiser its QR |
21
22
 
22
23
  ## The pickup pass card
@@ -26,7 +27,8 @@ among them), `create_pickup_order`, `order_status` and `get_pickup_pass` show th
26
27
  it's unpaid, then the QR and pickup code, kept up to date until it's collected. When the shop marks it ready, the QR's
27
28
  tile turns lime. The card is one self-contained page (`ui://agentpickup/pass-<version>.html`) that loads nothing from
28
29
  anywhere else. In apps without cards, the agent gives the user the pass link and pickup code. The shop emails the pass
29
- either way.
30
+ either way. The card's "Invite a friend" link, and `share_agentpickup`'s own card, point at the add page
31
+ (`addPageUrl`, default https://agentpickup.app/add) so friends can add it too.
30
32
 
31
33
  ## Use it
32
34
 
package/dist/card.d.ts CHANGED
@@ -20,6 +20,8 @@ export interface PassCard {
20
20
  pickupCode?: string | null;
21
21
  /** The QR as SVG, ink on transparent, so the tile behind it sets the colour. */
22
22
  qrSvg?: string;
23
+ /** The add page's share mode, for "Invite a friend". */
24
+ inviteUrl?: string;
23
25
  }
24
26
  /** The card's page. Its version goes in the handshake, so a host's logs say which card it ran. */
25
27
  export declare function passCardHtml(version: string): string;
package/dist/card.js CHANGED
@@ -54,7 +54,8 @@ body{font-family:var(--font-sans,ui-sans-serif,system-ui,-apple-system,"Segoe UI
54
54
  button{font:inherit;font-size:14px;border-radius:10px;padding:10px 14px;cursor:pointer;border:1px solid var(--line);background:var(--surface);color:var(--text)}
55
55
  button.pay{background:var(--lime);color:var(--ink);border-color:var(--lime);font-weight:600}
56
56
  button.link{background:none;border:none;padding:0;color:var(--muted);text-decoration:underline;font-size:13px}
57
- .foot{font-size:11px;color:var(--muted);margin-top:12px;text-align:right}
57
+ .foot{font-size:11px;color:var(--muted);margin-top:12px;display:flex;justify-content:space-between;align-items:center;gap:8px}
58
+ .foot button.link{font-size:12px}
58
59
  @media (prefers-reduced-motion:reduce){.pour{transition:none}.wrap.ready::after{animation:none;opacity:1}}
59
60
  </style>
60
61
  </head>
@@ -221,7 +222,7 @@ button.link{background:none;border:none;padding:0;color:var(--muted);text-decora
221
222
  if (status === "awaiting_payment" && s.checkoutUrl) actions += '<button class="pay" data-open="checkoutUrl">Pay' + (s.total ? " " + esc(s.total) : "") + " by card</button>";
222
223
  if (live && s.passUrl) actions += '<button class="link" data-open="passUrl">Open the pass</button>';
223
224
  if (actions) h += '<div class="row">' + actions + "</div>";
224
- h += '<div class="foot">agentPickup</div></div>';
225
+ h += '<div class="foot">' + (s.inviteUrl ? '<button class="link" data-open="inviteUrl">Invite a friend</button>' : "<span></span>") + "<span>agentPickup</span></div></div>";
225
226
  return h;
226
227
  }
227
228
  function render() {
package/dist/server.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { type Fetch, type Payer } from "./wallet.js";
3
- export declare const VERSION = "0.4.0";
3
+ export declare const VERSION = "0.4.2";
4
4
  export interface PickupMcpOptions {
5
5
  /** The agentPickup shop this server orders from. */
6
6
  shopUrl: string;
@@ -22,6 +22,11 @@ export interface PickupMcpOptions {
22
22
  payer?: Payer;
23
23
  /** Notes added to the instructions agents read on connecting, e.g. which of this host's shops take what. */
24
24
  instructions?: string;
25
+ /**
26
+ * The page where people add agentPickup to their assistant, which share_agentpickup and the pass card's
27
+ * "Invite a friend" link point to. Default https://agentpickup.app/add.
28
+ */
29
+ addPageUrl?: string;
25
30
  }
26
31
  export declare function createPickupMcpServer(opts: PickupMcpOptions): McpServer;
27
32
  /** A Streamable HTTP endpoint (stateless, JSON responses) for hosting the server, e.g. at /mcp. */
package/dist/server.js CHANGED
@@ -5,11 +5,14 @@ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/
5
5
  import QRCode from "qrcode";
6
6
  import { z } from "zod";
7
7
  import { CARD_META_KEY, CARD_MIME_TYPE, passCardHtml } from "./card.js";
8
+ import { SHARE_META_KEY, shareCardHtml } from "./share-card.js";
8
9
  import { errorOf, usd } from "./wallet.js";
9
- export const VERSION = "0.4.0";
10
+ export const VERSION = "0.4.2";
10
11
  // The pickup pass card (MCP Apps). The version is in the URI so hosts that cache cards pick up a new one.
11
12
  const CARD_URI = `ui://agentpickup/pass-${VERSION}.html`;
12
13
  const CARD_TOOL_META = { ui: { resourceUri: CARD_URI }, "ui/resourceUri": CARD_URI };
14
+ const SHARE_URI = `ui://agentpickup/share-${VERSION}.html`;
15
+ const SHARE_TOOL_META = { ui: { resourceUri: SHARE_URI }, "ui/resourceUri": SHARE_URI };
13
16
  // Shop names for the card, so polling order_status needn't read the catalog each time. Shared across the
14
17
  // per-request servers a hosted endpoint makes.
15
18
  const shopNames = new Map();
@@ -20,25 +23,29 @@ const rememberShop = (shop, name) => {
20
23
  shopNames.clear();
21
24
  shopNames.set(shop, name);
22
25
  };
26
+ /** A shop's name as the add page's ?shop= matches it: "Demo Café" -> "demo-cafe". */
27
+ const slugOf = (name) => name.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
23
28
  /** The QR in ink on transparent: the card's tile behind it is white, or lime once the order is ready. */
24
29
  const qrSvg = (content) => QRCode.toString(content, { type: "svg", errorCorrectionLevel: "M", margin: 0, color: { dark: "#0a0b0dff", light: "#00000000" } });
25
30
  class ToolError extends Error {
26
31
  }
27
- const INSTRUCTIONS = `agentPickup lets you buy things online for the user to collect in person.
28
- 0. find_shops (when available): shops near the user that sell what they want. Pass a shop's shop_url to the other tools.
29
- 1. get_shop: see what the shop sells, its pickup location and how it takes payment.
30
- 2. create_pickup_order: needs the user's email; the shop emails the pickup pass there too. It returns a claim:
31
- keep it with the order id.
32
- 3. pay_order: pays in USDC over x402 from the wallet set up here; with none, it hands back the pay link for the user's
33
- own x402 wallet. Where the shop takes cards, payment: "card" on
34
- create_pickup_order gives a checkout link for the user to pay on instead. Where it takes test payments (get_shop says
35
- so), payment: "test" runs the whole flow with no money moving. Confirm the items and total with the user first.
36
- 4. The pass: where the chat shows cards, the order appears as a card that becomes the pickup pass (QR and code)
37
- once it's paid and keeps itself up to date until it's collected. Otherwise give the user the pass link and pickup
38
- code. They show the QR at the counter, or say the code, to collect.
39
- get_pickup_pass (with the claim) fetches the pass again; order_status (with the claim) says whether it's ready.
40
- Group orders (a coffee run): start_group_order gives a code to share; each person's agent calls join_group_order and
41
- pays its own share with pay_order; at closing time the shop gets one combined order and the organiser gets its QR.
32
+ const INSTRUCTIONS = `agentPickup lets you buy things online for the user to collect in person.
33
+ 0. find_shops (when available): shops near the user that sell what they want. Pass a shop's shop_url to the other tools.
34
+ 1. get_shop: see what the shop sells, its pickup location and how it takes payment.
35
+ 2. create_pickup_order: needs the user's email; the shop emails the pickup pass there too. It returns a claim:
36
+ keep it with the order id.
37
+ 3. pay_order: pays in USDC over x402 from the wallet set up here; with none, it hands back the pay link for the user's
38
+ own x402 wallet. Where the shop takes cards, payment: "card" on
39
+ create_pickup_order gives a checkout link for the user to pay on instead. Where it takes test payments (get_shop says
40
+ so), payment: "test" runs the whole flow with no money moving. Confirm the items and total with the user first.
41
+ 4. The pass: where the chat shows cards, the order appears as a card that becomes the pickup pass (QR and code)
42
+ once it's paid and keeps itself up to date until it's collected. Otherwise give the user the pass link and pickup
43
+ code. They show the QR at the counter, or say the code, to collect.
44
+ get_pickup_pass (with the claim) fetches the pass again; order_status (with the claim) says whether it's ready.
45
+ share_agentpickup: when the user wants to share agentPickup itself, invite a friend or get friends using it, shows a
46
+ QR and link for them. That isn't a group order: start_group_order is only for ordering together at one shop.
47
+ Group orders (a coffee run): start_group_order gives a code to share; each person's agent calls join_group_order and
48
+ pays its own share with pay_order; at closing time the shop gets one combined order and the organiser gets its QR.
42
49
  group_order_status shows who's in; close_group_order closes it early.`;
43
50
  const money = (cents, currency) => new Intl.NumberFormat("en-US", { style: "currency", currency: currency.toUpperCase() }).format(cents / 100);
44
51
  const reply = (summary, data, card) => ({
@@ -123,10 +130,22 @@ export function createPickupMcpServer(opts) {
123
130
  total: money(order.totalCents, order.currency),
124
131
  collectBy: order.collectBy,
125
132
  collectedAt: order.collectedAt ?? null,
133
+ inviteUrl: addLinks(shop).sharePageUrl,
126
134
  ...extra,
127
135
  ...(pass ? { passUrl: pass.passUrl, pickupCode: pass.pickupCode, qrSvg: await qrSvg(pass.qrContent) } : {}),
128
136
  };
129
137
  }
138
+ /** The add page for sharing: for one of this host's shops when given (?shop=<slug>), else everyone's. */
139
+ const addPage = (opts.addPageUrl ?? "https://agentpickup.app/add").replace(/\/$/, "");
140
+ function addLinks(shop) {
141
+ let slug = "";
142
+ if (shop) {
143
+ const u = new URL(shop);
144
+ slug = u.pathname.match(/\/v\/([^/]+)/)?.[1] ?? (u.pathname === "/" || u.pathname === "" ? slugOf(shopNames.get(shop) ?? "") : "");
145
+ }
146
+ const q = slug ? `shop=${encodeURIComponent(slug)}` : "";
147
+ return { url: `${addPage}${q ? `?${q}` : ""}`, sharePageUrl: `${addPage}?share=1${q ? `&${q}` : ""}` };
148
+ }
130
149
  const claimArg = z
131
150
  .string()
132
151
  .max(200)
@@ -411,6 +430,43 @@ export function createPickupMcpServer(opts) {
411
430
  await shopName(shop);
412
431
  return reply(`Pickup pass: ${pass.passUrl}, pickup code ${pass.pickupCode}. The user shows the QR at the counter, or says the code.`, { passUrl: pass.passUrl, pickupCode: pass.pickupCode, qrContent: pass.qrContent, status: pass.order?.status }, pass.order ? await cardFor(shop, pass.order, { claim: args.claim }, pass) : undefined);
413
432
  }));
433
+ // ---------- sharing ----------
434
+ server.registerResource("share-agentpickup", SHARE_URI, {
435
+ title: "Share agentPickup",
436
+ description: "A QR and link for friends to add agentPickup to their assistant.",
437
+ mimeType: CARD_MIME_TYPE,
438
+ _meta: { ui: { prefersBorder: false } },
439
+ }, async () => ({
440
+ contents: [{ uri: SHARE_URI, mimeType: CARD_MIME_TYPE, text: shareCardHtml(VERSION), _meta: { ui: { prefersBorder: false } } }],
441
+ }));
442
+ server.registerTool("share_agentpickup", {
443
+ title: "Share agentPickup",
444
+ description: "A QR code and link the user can share so friends can add agentPickup to Claude, ChatGPT or Grok and order too. " +
445
+ "Use when the user asks how to share agentPickup, invite friends, or get others using it. Name a shop to share it with that café picked.",
446
+ inputSchema: { ...shopArg },
447
+ annotations: { title: "Share agentPickup", readOnlyHint: true, openWorldHint: false },
448
+ _meta: SHARE_TOOL_META,
449
+ }, guarded(async (args) => {
450
+ const shop = args.shop_url ? await shopFor(args.shop_url) : undefined;
451
+ const name = shop ? await shopName(shop) : undefined;
452
+ const links = addLinks(shop);
453
+ const card = {
454
+ v: 1,
455
+ ...links,
456
+ qrSvg: await QRCode.toString(links.url, { type: "svg", errorCorrectionLevel: "M", margin: 0, color: { dark: "#0a0b0dff", light: "#00000000" } }),
457
+ ...(name ? { shopName: name } : {}),
458
+ };
459
+ return {
460
+ content: [
461
+ {
462
+ type: "text",
463
+ text: `Share this link: ${links.url}. A friend opens it (or scans its QR off the user's screen), adds agentPickup to Claude, ChatGPT or Grok, ` +
464
+ `then just asks for their usual${name ? ` at ${name}` : ""}. ${links.sharePageUrl} opens the phone's share sheet and has a sticker to print.`,
465
+ },
466
+ ],
467
+ _meta: { [SHARE_META_KEY]: card },
468
+ };
469
+ }));
414
470
  const closesIn = (g) => {
415
471
  const minutes = Math.max(0, Math.round((g.closesAt - Date.now()) / 60_000));
416
472
  return g.status === "open" ? `closes in ${minutes} min` : g.status === "sent" ? "closed and sent to the shop" : "cancelled";
@@ -421,8 +477,9 @@ export function createPickupMcpServer(opts) {
421
477
  };
422
478
  server.registerTool("start_group_order", {
423
479
  title: "Start a group order",
424
- description: "Start a group order (a coffee run) at a shop. Returns a code to share: each person's agent joins with it and pays for " +
425
- "their own items. When it closes, the shop gets one combined order and the organiser is emailed its pickup QR.",
480
+ description: "Start a group order (a coffee run) at a shop, when the user wants to order together with others now. Returns a code to " +
481
+ "share: each person's agent joins with it and pays for their own items. When it closes, the shop gets one combined order " +
482
+ "and the organiser is emailed its pickup QR. To share agentPickup itself with a friend, use share_agentpickup instead.",
426
483
  inputSchema: {
427
484
  organiser_name: z.string().min(1).max(60).describe("Who collects, shown to staff"),
428
485
  organiser_email: z.string().email().describe("Where the group's pickup QR goes"),
@@ -0,0 +1,13 @@
1
+ export declare const SHARE_META_KEY = "agentpickup/share";
2
+ export interface ShareCard {
3
+ v: 1;
4
+ /** The add page, maybe for one café (?shop=). */
5
+ url: string;
6
+ /** The add page's share mode: the share sheet and the sticker. */
7
+ sharePageUrl: string;
8
+ /** The QR as SVG, ink on transparent, for the white tile. */
9
+ qrSvg: string;
10
+ /** A café to name, when the link is for one. */
11
+ shopName?: string;
12
+ }
13
+ export declare function shareCardHtml(version: string): string;
@@ -0,0 +1,142 @@
1
+ // The share card (MCP Apps): what share_agentpickup shows when someone asks how to get friends using agentPickup.
2
+ // The counter sticker as a card: a QR to the add page that a friend scans off the screen, the link to copy, and
3
+ // buttons that open the add page's share mode (the phone's share sheet, and the printable sticker). Self-contained,
4
+ // like the pass card: no scripts, fonts or images from anywhere else, and the same small MCP Apps bridge.
5
+ export const SHARE_META_KEY = "agentpickup/share";
6
+ const PAGE = String.raw `<!doctype html>
7
+ <html lang="en">
8
+ <head>
9
+ <meta charset="utf-8">
10
+ <meta name="viewport" content="width=device-width,initial-scale=1">
11
+ <title>Share agentPickup</title>
12
+ <style>
13
+ :root{--bg:#0a0b0d;--text:#eef0f3;--muted:#b7bdc6;--dim:#8b929c;--line:#2a2f36;--lime:#b5f23d;--ink:#0a0b0d}
14
+ html,body{margin:0;padding:0;background:transparent}
15
+ body{font-family:var(--font-sans,ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif);padding:2px;-webkit-font-smoothing:antialiased}
16
+ .card{box-sizing:border-box;max-width:420px;margin:0 auto;background:var(--bg);color:var(--text);border-radius:18px;padding:20px 20px 16px}
17
+ .top{display:flex;align-items:center;gap:8px;font-weight:700;font-size:15px}
18
+ .top svg{width:22px;height:22px;border-radius:5px}
19
+ .top .dim{color:var(--dim)}
20
+ h1{margin:14px 0 0;font-size:34px;line-height:.95;font-weight:850;letter-spacing:-.02em;text-transform:uppercase}
21
+ h1 em{font-style:normal;color:var(--lime)}
22
+ .sub{margin:8px 0 0;color:var(--muted);font-size:15px}
23
+ .row{display:flex;gap:16px;align-items:flex-end;margin-top:16px}
24
+ .tile{background:#fff;border-radius:14px;padding:12px;width:156px;height:156px;flex:none;box-sizing:border-box}
25
+ .tile svg{display:block;width:100%;height:100%}
26
+ ol{list-style:none;margin:0;padding:0;counter-reset:n;color:var(--muted);font-size:15px}
27
+ li{counter-increment:n;margin:0 0 8px;display:flex;align-items:center;gap:8px}
28
+ li::before{content:counter(n);width:22px;height:22px;border-radius:50%;border:1.5px solid var(--line);display:inline-flex;align-items:center;justify-content:center;font:600 12px ui-monospace,Menlo,monospace;color:var(--lime);flex:none}
29
+ .link{margin-top:14px;display:flex;gap:8px;align-items:center}
30
+ .link code{flex:1;min-width:0;overflow-wrap:anywhere;font:13px ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--text);background:#16191d;border:1px solid var(--line);border-radius:10px;padding:9px 10px;user-select:all;-webkit-user-select:all}
31
+ button{font-weight:600;font-size:14px;font-family:inherit;border-radius:10px;padding:10px 14px;cursor:pointer;border:1px solid var(--line);background:transparent;color:var(--text);white-space:nowrap}
32
+ button.lime{background:var(--lime);color:var(--ink);border-color:var(--lime)}
33
+ .actions{display:flex;gap:8px;margin-top:10px;flex-wrap:wrap}
34
+ .actions button{flex:1}
35
+ .note{margin:10px 0 0;font-size:12px;color:var(--dim)}
36
+ @media (max-width:360px){.row{flex-direction:column;align-items:flex-start}}
37
+ </style>
38
+ </head>
39
+ <body>
40
+ <div id="root"></div>
41
+ <script>
42
+ (function () {
43
+ var VERSION = "__VERSION__";
44
+ var root = document.getElementById("root");
45
+ var state = null, canOpen = false, nextId = 1, pending = {}, lastHeight = -1;
46
+ var host = window.parent;
47
+ function send(msg) { try { host.postMessage(msg, "*"); } catch (e) {} }
48
+ function request(method, params) {
49
+ var id = nextId++;
50
+ send({ jsonrpc: "2.0", id: id, method: method, params: params });
51
+ return new Promise(function (resolve, reject) {
52
+ pending[id] = { resolve: resolve, reject: reject };
53
+ setTimeout(function () { if (pending[id]) { delete pending[id]; reject(new Error("timed out")); } }, 30000);
54
+ });
55
+ }
56
+ function notify(method, params) { send({ jsonrpc: "2.0", method: method, params: params }); }
57
+ window.addEventListener("message", function (e) {
58
+ if (host !== window && e.source !== host) return;
59
+ var m = e.data;
60
+ if (!m || m.jsonrpc !== "2.0") return;
61
+ if (m.id != null && !m.method) {
62
+ var p = pending[m.id];
63
+ if (p) { delete pending[m.id]; if (m.error) p.reject(m.error); else p.resolve(m.result); }
64
+ return;
65
+ }
66
+ if (m.method === "ui/notifications/tool-result") return onResult(m.params);
67
+ if (m.method === "ui/notifications/host-context-changed") { applyContext(m.params || {}); return; }
68
+ if (m.method === "ui/resource-teardown" && m.id != null) return send({ jsonrpc: "2.0", id: m.id, result: {} });
69
+ if (m.method === "ping" && m.id != null) return send({ jsonrpc: "2.0", id: m.id, result: {} });
70
+ if (m.id != null && m.method && m.method.indexOf("ui/notifications/") !== 0) send({ jsonrpc: "2.0", id: m.id, error: { code: -32601, message: "Method not found" } });
71
+ });
72
+ function applyContext(ctx) {
73
+ var vars = ctx.styles && ctx.styles.variables;
74
+ if (vars && typeof vars["--font-sans"] === "string") document.documentElement.style.setProperty("--font-sans", vars["--font-sans"]);
75
+ }
76
+ function openLink(url) {
77
+ if (!url) return;
78
+ if (canOpen) request("ui/open-link", { url: url }).catch(function () { window.open(url, "_blank", "noopener"); });
79
+ else window.open(url, "_blank", "noopener");
80
+ }
81
+ function onResult(result) {
82
+ var data = result && result._meta && result._meta["agentpickup/share"];
83
+ if (data && data.url) state = data;
84
+ render();
85
+ }
86
+ function esc(s) { return String(s == null ? "" : s).replace(/[&<>"']/g, function (c) { return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]; }); }
87
+ function safeSvg(svg) { return typeof svg === "string" && /^<svg[\s>]/.test(svg) && !/<script|<foreignObject|\son[a-z]+=/i.test(svg) ? svg : ""; }
88
+ function copy(text, button) {
89
+ var done = function () { button.textContent = "Copied"; setTimeout(function () { button.textContent = "Copy"; }, 1600); };
90
+ try {
91
+ if (navigator.clipboard && navigator.clipboard.writeText) return navigator.clipboard.writeText(text).then(done, function () { select(); });
92
+ } catch (e) {}
93
+ select();
94
+ }
95
+ function select() {
96
+ var code = document.getElementById("url");
97
+ if (!code || !window.getSelection) return;
98
+ var range = document.createRange();
99
+ range.selectNodeContents(code);
100
+ var sel = window.getSelection();
101
+ sel.removeAllRanges();
102
+ sel.addRange(range);
103
+ }
104
+ function render() {
105
+ if (!state) { root.innerHTML = ""; return reportSize(); }
106
+ var svg = safeSvg(state.qrSvg);
107
+ var shown = state.url.replace(/^https?:\/\//, "");
108
+ root.innerHTML =
109
+ '<div class="card">' +
110
+ '<div class="top"><svg viewBox="0 0 32 32" aria-hidden="true"><rect width="32" height="32" fill="#16191d"/><rect x="7" y="7" width="7" height="7" rx="1.5" fill="#fff"/><rect x="18" y="7" width="7" height="7" rx="1.5" fill="#fff"/><rect x="7" y="18" width="7" height="7" rx="1.5" fill="#fff"/><path d="M18.5 21.5l2.5 2.5 4.5-5" fill="none" stroke="#b5f23d" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/></svg><span><span class="dim">agent</span>Pickup</span></div>' +
111
+ "<h1>" + (state.shopName ? "Order from " + esc(state.shopName) + "<br>with your <em>AI</em>" : "Order ahead<br>from your <em>AI</em>") + "</h1>" +
112
+ '<p class="sub">Claude, ChatGPT or Grok</p>' +
113
+ '<div class="row">' + (svg ? '<div class="tile">' + svg.replace("<svg", '<svg role="img" aria-label="QR code to add agentPickup"') + "</div>" : "") +
114
+ "<ol><li>Scan</li><li>Add agentPickup</li><li>Ask for your usual</li></ol></div>" +
115
+ '<div class="link"><code id="url">' + esc(shown) + '</code><button type="button" id="copy">Copy</button></div>' +
116
+ '<div class="actions"><button class="lime" type="button" id="share">Share</button><button type="button" id="sticker">Get the sticker</button></div>' +
117
+ '<p class="note">A friend scans the QR off your screen, or you send them the link.</p>' +
118
+ "</div>";
119
+ document.getElementById("copy").addEventListener("click", function (e) { copy(state.url, e.currentTarget); });
120
+ document.getElementById("share").addEventListener("click", function () { openLink(state.sharePageUrl); });
121
+ document.getElementById("sticker").addEventListener("click", function () { openLink(state.sharePageUrl + (state.sharePageUrl.indexOf("?") >= 0 ? "&" : "?") + "sticker=1"); });
122
+ reportSize();
123
+ }
124
+ function reportSize() {
125
+ var height = Math.ceil(document.body.getBoundingClientRect().height);
126
+ if (height !== lastHeight) { lastHeight = height; notify("ui/notifications/size-changed", { height: height }); }
127
+ }
128
+ if (window.ResizeObserver) new ResizeObserver(reportSize).observe(document.body);
129
+ request("ui/initialize", { appInfo: { name: "agentpickup-share", version: VERSION }, appCapabilities: {}, protocolVersion: "2026-01-26" })
130
+ .then(function (r) {
131
+ canOpen = Boolean(r && r.hostCapabilities && r.hostCapabilities.openLinks);
132
+ applyContext((r && r.hostContext) || {});
133
+ notify("ui/notifications/initialized", {});
134
+ render();
135
+ }, function () { render(); });
136
+ })();
137
+ </script>
138
+ </body>
139
+ </html>`;
140
+ export function shareCardHtml(version) {
141
+ return PAGE.replace("__VERSION__", version);
142
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentpickup/mcp",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "MCP server for agentPickup: let an AI agent find pickup shops near its user, order, pay by card or in USDC over x402, and hand its user the QR to collect in person.",
5
5
  "mcpName": "io.github.dav3trad3r/agentpickup",
6
6
  "type": "module",