@agentpickup/mcp 0.2.0 → 0.3.0
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 +4 -0
- package/dist/server.d.ts +6 -1
- package/dist/server.js +143 -21
- package/package.json +62 -62
package/README.md
CHANGED
|
@@ -14,6 +14,10 @@ show at the counter to collect.
|
|
|
14
14
|
| `pay_order` | Pays the order's x402 link from your wallet and returns the pass link. With no wallet set up, returns the price and pay link instead |
|
|
15
15
|
| `order_status` | Waiting for payment, paid, ready, collected or expired |
|
|
16
16
|
| `get_pickup_pass` | Fetches the pass link, pickup code and QR again by signing in as the paying wallet (Sign-In-With-X) |
|
|
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
|
+
| `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
|
+
| `group_order_status` | Who's in, what they ordered, who has paid, and whether the shop's order is ready or collected |
|
|
20
|
+
| `close_group_order` | Closes a group order early, as its organiser; the shop gets one combined order and the organiser its QR |
|
|
17
21
|
|
|
18
22
|
## Use it
|
|
19
23
|
|
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.
|
|
3
|
+
export declare const VERSION = "0.3.0";
|
|
4
4
|
export interface PickupMcpOptions {
|
|
5
5
|
/** The agentPickup shop this server orders from. */
|
|
6
6
|
shopUrl: string;
|
|
@@ -11,6 +11,11 @@ export interface PickupMcpOptions {
|
|
|
11
11
|
allowOtherShops?: boolean;
|
|
12
12
|
/** An agentPickup directory (<host>/directory/v1/shops). Adds find_shops: shops near someone that sell what they want. */
|
|
13
13
|
directoryUrl?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Shops on other hosts a hosted server may still order from, such as agentPickup shops its directory has checked.
|
|
16
|
+
* Called with the shop's base URL.
|
|
17
|
+
*/
|
|
18
|
+
trustedShop?: (shopBase: string) => boolean | Promise<boolean>;
|
|
14
19
|
/** How requests reach the shop. Defaults to global fetch; a shop hosting its own server passes its app's fetch. */
|
|
15
20
|
fetch?: Fetch;
|
|
16
21
|
/** The wallet that pays. Without one, pay_order returns the price and pay link for another x402 client. */
|
package/dist/server.js
CHANGED
|
@@ -4,7 +4,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
4
4
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { errorOf, usd } from "./wallet.js";
|
|
7
|
-
export const VERSION = "0.
|
|
7
|
+
export const VERSION = "0.3.0";
|
|
8
8
|
class ToolError extends Error {
|
|
9
9
|
}
|
|
10
10
|
const INSTRUCTIONS = `agentPickup lets you buy things online for the user to collect in person.
|
|
@@ -13,7 +13,10 @@ const INSTRUCTIONS = `agentPickup lets you buy things online for the user to col
|
|
|
13
13
|
2. create_pickup_order: needs the user's email, where the shop sends the pickup QR.
|
|
14
14
|
3. pay_order: pays from the configured wallet in USDC over x402. Confirm the items and total with the user first.
|
|
15
15
|
4. Give the user the pass link and pickup code. They show the QR at the counter to collect.
|
|
16
|
-
get_pickup_pass fetches the pass again later; order_status says whether it's ready or collected
|
|
16
|
+
get_pickup_pass fetches the pass again later; order_status says whether it's ready or collected.
|
|
17
|
+
Group orders (a coffee run): start_group_order gives a code to share; each person's agent calls join_group_order and
|
|
18
|
+
pays its own share with pay_order; at closing time the shop gets one combined order and the organiser gets its QR.
|
|
19
|
+
group_order_status shows who's in; close_group_order closes it early.`;
|
|
17
20
|
const money = (cents, currency) => new Intl.NumberFormat("en-US", { style: "currency", currency: currency.toUpperCase() }).format(cents / 100);
|
|
18
21
|
const reply = (summary, data) => ({
|
|
19
22
|
content: [{ type: "text", text: `${summary}\n\n${JSON.stringify(data, null, 2)}` }],
|
|
@@ -28,7 +31,7 @@ export function createPickupMcpServer(opts) {
|
|
|
28
31
|
const homeUrl = new URL(opts.shopUrl);
|
|
29
32
|
const home = shopBase(homeUrl);
|
|
30
33
|
const doFetch = opts.fetch ?? ((req) => fetch(req));
|
|
31
|
-
function shopFor(shopUrl) {
|
|
34
|
+
async function shopFor(shopUrl) {
|
|
32
35
|
if (!shopUrl)
|
|
33
36
|
return home;
|
|
34
37
|
let url;
|
|
@@ -41,11 +44,14 @@ export function createPickupMcpServer(opts) {
|
|
|
41
44
|
// Shops on this server's own host: the home shop and its venues.
|
|
42
45
|
if (url.origin === homeUrl.origin)
|
|
43
46
|
return shopBase(url);
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
if (opts.allowOtherShops || opts.trustedShop) {
|
|
48
|
+
if (url.protocol !== "https:" && url.hostname !== "localhost")
|
|
49
|
+
throw new ToolError("Shops must be served over https.");
|
|
50
|
+
if (opts.allowOtherShops || (await opts.trustedShop(shopBase(url))))
|
|
51
|
+
return shopBase(url);
|
|
52
|
+
throw new ToolError(`This server orders from shops on ${homeUrl.origin} and agentPickup shops its directory lists.`);
|
|
53
|
+
}
|
|
54
|
+
throw new ToolError(`This server orders from shops on ${homeUrl.origin} only.`);
|
|
49
55
|
}
|
|
50
56
|
async function call(shop, path, init) {
|
|
51
57
|
const res = await doFetch(new Request(`${shop}${path}`, init));
|
|
@@ -105,6 +111,8 @@ export function createPickupMcpServer(opts) {
|
|
|
105
111
|
s.distanceKm !== undefined ? `${s.distanceKm} km` : null,
|
|
106
112
|
s.openNow === true ? "open now" : s.openNow === false ? "closed now" : null,
|
|
107
113
|
s.testOnly ? "test shop, no real money" : null,
|
|
114
|
+
s.source?.kind === "listed" ? `listed by ${s.source.domain}` : null,
|
|
115
|
+
s.order?.via === "ucp" ? "orders through its UCP checkout" : s.order?.via === "web" ? "orders on its own website" : null,
|
|
108
116
|
].filter(Boolean);
|
|
109
117
|
const items = s.items.slice(0, 3).map((i) => `${i.name} ${money(i.priceCents, s.currency)}`).join(", ");
|
|
110
118
|
return `- ${s.name}${facts.length ? ` (${facts.join(", ")})` : ""}${s.location.address ? `, ${s.location.address}` : ""}${items ? `: ${items}` : ""}`;
|
|
@@ -118,6 +126,10 @@ export function createPickupMcpServer(opts) {
|
|
|
118
126
|
openNow: s.openNow,
|
|
119
127
|
testOnly: s.testOnly,
|
|
120
128
|
payments: { x402: s.payments.x402?.network ?? null, test: s.payments.test },
|
|
129
|
+
listedBy: s.source?.kind === "listed" ? s.source.domain : null,
|
|
130
|
+
// agentpickup: use get_shop and create_pickup_order. ucp or web: order there instead.
|
|
131
|
+
orderVia: s.order?.via ?? "agentpickup",
|
|
132
|
+
...(s.order?.via === "ucp" ? { ucpProfile: s.order.profile } : s.order?.via === "web" ? { orderUrl: s.order.url } : {}),
|
|
121
133
|
items: s.items.map((i) => ({ id: i.id, name: i.name, price: money(i.priceCents, s.currency) })),
|
|
122
134
|
moreItems: Math.max(0, s.itemCount - s.items.length),
|
|
123
135
|
})),
|
|
@@ -130,7 +142,7 @@ export function createPickupMcpServer(opts) {
|
|
|
130
142
|
inputSchema: { ...shopArg },
|
|
131
143
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
132
144
|
}, guarded(async (args) => {
|
|
133
|
-
const shop = shopFor(args.shop_url);
|
|
145
|
+
const shop = await shopFor(args.shop_url);
|
|
134
146
|
const data = await call(shop, "/api/catalog");
|
|
135
147
|
const merchant = data.merchant;
|
|
136
148
|
const currency = String(data.currency ?? "usd");
|
|
@@ -141,33 +153,47 @@ export function createPickupMcpServer(opts) {
|
|
|
141
153
|
description: i.description,
|
|
142
154
|
}));
|
|
143
155
|
const payments = data.payments;
|
|
156
|
+
const cards = Boolean(payments.square || payments.card);
|
|
157
|
+
const ways = [
|
|
158
|
+
...(payments.x402 ? [`USDC over x402 on ${payments.x402.network}, paid by the agent`] : []),
|
|
159
|
+
...(cards ? ["cards, paid by the user on a checkout link"] : []),
|
|
160
|
+
...(payments.test ? ["test payments, with no money moving"] : []),
|
|
161
|
+
];
|
|
144
162
|
return reply(`${merchant.name}${merchant.location?.name ? `, collect at ${merchant.location.name}` : ""}. ` +
|
|
145
|
-
(
|
|
146
|
-
?
|
|
147
|
-
: payments.test
|
|
163
|
+
(!ways.length
|
|
164
|
+
? "Doesn't take agent payments."
|
|
165
|
+
: ways.length === 1 && payments.test
|
|
148
166
|
? "A test shop: orders are marked paid without money moving."
|
|
149
|
-
:
|
|
167
|
+
: `Takes ${ways.join("; ")}.`), { shop, merchant: merchant.name, location: merchant.location, currency, items, x402: payments.x402 ?? null, cards, test: Boolean(payments.test) });
|
|
150
168
|
}));
|
|
151
169
|
server.registerTool("create_pickup_order", {
|
|
152
170
|
title: "Order for pickup",
|
|
153
171
|
description: "Create an order to collect in person. Returns the order id and total; pay it with pay_order. " +
|
|
154
|
-
"The shop emails the pickup QR to `email` once it's paid."
|
|
172
|
+
"The shop emails the pickup QR to `email` once it's paid. Where the shop takes cards, `payment: \"card\"` " +
|
|
173
|
+
"returns a checkout link for the user to pay on instead.",
|
|
155
174
|
inputSchema: {
|
|
156
175
|
items: z
|
|
157
176
|
.array(z.object({ id: z.string().describe("Item id from get_shop"), qty: z.number().int().min(1).max(20) }))
|
|
158
177
|
.min(1),
|
|
159
178
|
email: z.string().email().describe("The user's email. The pickup QR goes here."),
|
|
160
179
|
name: z.string().max(100).optional().describe("Name for the order, shown to staff"),
|
|
180
|
+
payment: z
|
|
181
|
+
.enum(["agent", "card"])
|
|
182
|
+
.optional()
|
|
183
|
+
.describe("Who pays: agent (USDC over x402, or a test payment at a test shop) or card (the user pays on a checkout link). Default: agent where the shop takes it, else card."),
|
|
161
184
|
...shopArg,
|
|
162
185
|
},
|
|
163
186
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
164
187
|
}, guarded(async (args) => {
|
|
165
|
-
const shop = shopFor(args.shop_url);
|
|
166
|
-
//
|
|
188
|
+
const shop = await shopFor(args.shop_url);
|
|
189
|
+
// The agent pays over x402 where the shop takes it, a test shop marks the order paid straight away, and a
|
|
190
|
+
// shop that takes cards gives the user a checkout link.
|
|
167
191
|
const { payments } = (await call(shop, "/api/catalog"));
|
|
168
|
-
const
|
|
192
|
+
const agentRail = payments.x402 ? "x402" : payments.test ? "test" : null;
|
|
193
|
+
const cardRail = payments.square ? "square" : payments.card ? "card" : null;
|
|
194
|
+
const rail = args.payment === "card" ? cardRail : args.payment === "agent" ? agentRail : (agentRail ?? cardRail);
|
|
169
195
|
if (!rail)
|
|
170
|
-
throw new ToolError("This shop doesn't take agent payments.");
|
|
196
|
+
throw new ToolError(args.payment === "card" ? "This shop doesn't take cards." : "This shop doesn't take agent payments.");
|
|
171
197
|
const data = await call(shop, "/api/orders", {
|
|
172
198
|
method: "POST",
|
|
173
199
|
headers: { "Content-Type": "application/json" },
|
|
@@ -179,6 +205,10 @@ export function createPickupMcpServer(opts) {
|
|
|
179
205
|
return reply(`Test order ${order.id}: ${order.items.map((i) => `${i.qty}× ${i.name}`).join(", ")}, ${money(order.totalCents, order.currency)}, ` +
|
|
180
206
|
`marked paid with no money moving. The shop emails ${args.email} the receipt and pickup QR; the pass is at ${next.passUrl}.`, { shop, orderId: order.id, status: order.status, test: true, total: money(order.totalCents, order.currency), items: order.items, passUrl: next.passUrl });
|
|
181
207
|
}
|
|
208
|
+
if (rail === "square" || rail === "card") {
|
|
209
|
+
return reply(`Order ${order.id}: ${order.items.map((i) => `${i.qty}× ${i.name}`).join(", ")}, ${money(order.totalCents, order.currency)}. ` +
|
|
210
|
+
`Give the user this link to pay by card: ${next.url}. Once they've paid, the shop emails ${args.email} the receipt and pickup QR.`, { shop, orderId: order.id, total: money(order.totalCents, order.currency), items: order.items, payBy: "card", checkoutUrl: next.url, collectBy: order.collectBy });
|
|
211
|
+
}
|
|
182
212
|
return reply(`Order ${order.id}: ${order.items.map((i) => `${i.qty}× ${i.name}`).join(", ")}, ${money(order.totalCents, order.currency)}. ` +
|
|
183
213
|
`Confirm with the user, then call pay_order.`, { shop, orderId: order.id, total: money(order.totalCents, order.currency), items: order.items, payUrl: next.url, collectBy: order.collectBy });
|
|
184
214
|
}));
|
|
@@ -190,7 +220,15 @@ export function createPickupMcpServer(opts) {
|
|
|
190
220
|
inputSchema: { order_id: z.string(), ...shopArg },
|
|
191
221
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
192
222
|
}, guarded(async (args) => {
|
|
193
|
-
const shop = shopFor(args.shop_url);
|
|
223
|
+
const shop = await shopFor(args.shop_url);
|
|
224
|
+
const status = (await call(shop, `/api/orders/${encodeURIComponent(args.order_id)}`));
|
|
225
|
+
if (status.order.rail === "square" || status.order.rail === "card") {
|
|
226
|
+
if (status.order.status !== "awaiting_payment")
|
|
227
|
+
return reply(`Order ${args.order_id} is already paid (${status.order.status}).`, { orderId: args.order_id, status: status.order.status });
|
|
228
|
+
return reply(status.checkoutUrl
|
|
229
|
+
? `This order is paid by card: give the user ${status.checkoutUrl} to pay on.`
|
|
230
|
+
: "This order is paid by card, on the checkout link create_pickup_order returned.", { orderId: args.order_id, payBy: "card", checkoutUrl: status.checkoutUrl ?? null });
|
|
231
|
+
}
|
|
194
232
|
const payUrl = `${shop}/api/orders/${encodeURIComponent(args.order_id)}/pay`;
|
|
195
233
|
if (!opts.payer) {
|
|
196
234
|
const res = await doFetch(new Request(payUrl, { method: "POST" }));
|
|
@@ -216,7 +254,7 @@ export function createPickupMcpServer(opts) {
|
|
|
216
254
|
inputSchema: { order_id: z.string(), ...shopArg },
|
|
217
255
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
218
256
|
}, guarded(async (args) => {
|
|
219
|
-
const shop = shopFor(args.shop_url);
|
|
257
|
+
const shop = await shopFor(args.shop_url);
|
|
220
258
|
const { order } = (await call(shop, `/api/orders/${encodeURIComponent(args.order_id)}`));
|
|
221
259
|
const words = {
|
|
222
260
|
awaiting_payment: "is waiting for payment",
|
|
@@ -236,13 +274,97 @@ export function createPickupMcpServer(opts) {
|
|
|
236
274
|
}, guarded(async (args) => {
|
|
237
275
|
if (!opts.payer)
|
|
238
276
|
throw new ToolError("No wallet is set up here. The pass was emailed to the buyer when the order was paid.");
|
|
239
|
-
const shop = shopFor(args.shop_url);
|
|
277
|
+
const shop = await shopFor(args.shop_url);
|
|
240
278
|
const res = await opts.payer.signIn(`${shop}/api/orders/${encodeURIComponent(args.order_id)}/pass`, doFetch);
|
|
241
279
|
if (!res.ok)
|
|
242
280
|
throw new ToolError(await errorOf(res, `The shop answered ${res.status}.`));
|
|
243
281
|
const pass = (await res.json());
|
|
244
282
|
return reply(`Pickup pass: ${pass.passUrl}, pickup code ${pass.pickupCode}.`, pass);
|
|
245
283
|
}));
|
|
284
|
+
const closesIn = (g) => {
|
|
285
|
+
const minutes = Math.max(0, Math.round((g.closesAt - Date.now()) / 60_000));
|
|
286
|
+
return g.status === "open" ? `closes in ${minutes} min` : g.status === "sent" ? "closed and sent to the shop" : "cancelled";
|
|
287
|
+
};
|
|
288
|
+
const groupSummary = (g) => {
|
|
289
|
+
const paid = g.members.filter((m) => m.status !== "awaiting_payment" && m.status !== "cancelled");
|
|
290
|
+
return `${g.title} (code ${g.code}), ${closesIn(g)}: ${paid.length} of ${g.members.length} paid, ${money(g.paidCents, g.currency)} so far.`;
|
|
291
|
+
};
|
|
292
|
+
server.registerTool("start_group_order", {
|
|
293
|
+
title: "Start a group order",
|
|
294
|
+
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 " +
|
|
295
|
+
"their own items. When it closes, the shop gets one combined order and the organiser is emailed its pickup QR.",
|
|
296
|
+
inputSchema: {
|
|
297
|
+
organiser_name: z.string().min(1).max(60).describe("Who collects, shown to staff"),
|
|
298
|
+
organiser_email: z.string().email().describe("Where the group's pickup QR goes"),
|
|
299
|
+
title: z.string().max(60).optional().describe('e.g. "Friday coffee run"'),
|
|
300
|
+
closes_in_minutes: z.number().int().min(5).max(240).optional().describe("Default 15"),
|
|
301
|
+
...shopArg,
|
|
302
|
+
},
|
|
303
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
304
|
+
}, guarded(async (args) => {
|
|
305
|
+
const shop = await shopFor(args.shop_url);
|
|
306
|
+
const data = (await call(shop, "/api/groups", {
|
|
307
|
+
method: "POST",
|
|
308
|
+
headers: { "Content-Type": "application/json" },
|
|
309
|
+
body: JSON.stringify({ name: args.organiser_name, email: args.organiser_email, title: args.title, closesInMinutes: args.closes_in_minutes }),
|
|
310
|
+
}));
|
|
311
|
+
return reply(`Started ${data.group.title}: share code ${data.group.code}. Everyone's agent joins with join_group_order and the code; it ${closesIn(data.group)}. ` +
|
|
312
|
+
`Keep organiser_key to close it early.`, { shop, code: data.group.code, closesAt: data.group.closesAt, organiser_key: data.organiserKey });
|
|
313
|
+
}));
|
|
314
|
+
server.registerTool("join_group_order", {
|
|
315
|
+
title: "Join a group order",
|
|
316
|
+
description: "Add the user's items to a group order with its code. Returns the order id; pay it with pay_order before the group closes. " +
|
|
317
|
+
"The organiser collects everything, so the user gets a receipt, not a QR.",
|
|
318
|
+
inputSchema: {
|
|
319
|
+
code: z.string().min(4).max(12).describe("The group's code"),
|
|
320
|
+
items: z.array(z.object({ id: z.string().describe("Item id from get_shop"), qty: z.number().int().min(1).max(20) })).min(1),
|
|
321
|
+
email: z.string().email().describe("The user's email, for their receipt"),
|
|
322
|
+
name: z.string().min(1).max(60).describe("The user's name, shown to staff on their items"),
|
|
323
|
+
...shopArg,
|
|
324
|
+
},
|
|
325
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
326
|
+
}, guarded(async (args) => {
|
|
327
|
+
const shop = await shopFor(args.shop_url);
|
|
328
|
+
const { payments } = (await call(shop, "/api/catalog"));
|
|
329
|
+
const rail = payments.x402 ? "x402" : payments.test ? "test" : null;
|
|
330
|
+
if (!rail)
|
|
331
|
+
throw new ToolError("This shop doesn't take agent payments.");
|
|
332
|
+
const data = (await call(shop, `/api/groups/${encodeURIComponent(args.code)}/orders`, {
|
|
333
|
+
method: "POST",
|
|
334
|
+
headers: { "Content-Type": "application/json" },
|
|
335
|
+
body: JSON.stringify({ items: args.items, email: args.email, name: args.name, rail }),
|
|
336
|
+
}));
|
|
337
|
+
const what = `${data.order.items.map((i) => `${i.qty}× ${i.name}`).join(", ")}, ${money(data.order.totalCents, data.order.currency)}`;
|
|
338
|
+
return reply(rail === "test"
|
|
339
|
+
? `Added to ${data.group.title}: ${what}, paid with a test payment (no money moved). ${data.group.organiser} collects it.`
|
|
340
|
+
: `Added to ${data.group.title}: ${what}. Confirm with the user, then pay it with pay_order (order ${data.order.id}) before the group closes.`, { shop, orderId: data.order.id, group: groupSummary(data.group), payUrl: data.next.url ?? null, test: rail === "test" });
|
|
341
|
+
}));
|
|
342
|
+
server.registerTool("group_order_status", {
|
|
343
|
+
title: "Check a group order",
|
|
344
|
+
description: "Who's in a group order, what they ordered, who has paid, and whether it has closed and been collected.",
|
|
345
|
+
inputSchema: { code: z.string().min(4).max(12), ...shopArg },
|
|
346
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
347
|
+
}, guarded(async (args) => {
|
|
348
|
+
const shop = await shopFor(args.shop_url);
|
|
349
|
+
const { group } = (await call(shop, `/api/groups/${encodeURIComponent(args.code)}`));
|
|
350
|
+
return reply(groupSummary(group) + (group.combined ? ` The shop's order is ${group.combined.status}.` : ""), group);
|
|
351
|
+
}));
|
|
352
|
+
server.registerTool("close_group_order", {
|
|
353
|
+
title: "Close a group order now",
|
|
354
|
+
description: "Close a group order early, as its organiser. Unpaid shares are left out; the shop gets the rest as one order.",
|
|
355
|
+
inputSchema: { code: z.string().min(4).max(12), organiser_key: z.string().min(8), ...shopArg },
|
|
356
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
357
|
+
}, guarded(async (args) => {
|
|
358
|
+
const shop = await shopFor(args.shop_url);
|
|
359
|
+
const { group } = (await call(shop, `/api/groups/${encodeURIComponent(args.code)}/close`, {
|
|
360
|
+
method: "POST",
|
|
361
|
+
headers: { "Content-Type": "application/json" },
|
|
362
|
+
body: JSON.stringify({ organiserKey: args.organiser_key }),
|
|
363
|
+
}));
|
|
364
|
+
return reply(group.status === "sent"
|
|
365
|
+
? `Closed: ${groupSummary(group)} The shop has the combined order, and ${group.organiser}'s pickup QR is on its way by email.`
|
|
366
|
+
: `Closed with nobody paid, so the group order was cancelled.`, group);
|
|
367
|
+
}));
|
|
246
368
|
return server;
|
|
247
369
|
}
|
|
248
370
|
/** A Streamable HTTP endpoint (stateless, JSON responses) for hosting the server, e.g. at /mcp. */
|
package/package.json
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@agentpickup/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server for agentPickup: let an AI agent find pickup shops near its user, order, pay over x402, and hand its user the QR to collect in person.",
|
|
5
|
-
"mcpName": "io.github.dav3trad3r/agentpickup",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"main": "./dist/index.js",
|
|
8
|
-
"types": "./dist/index.d.ts",
|
|
9
|
-
"bin": {
|
|
10
|
-
"agentpickup-mcp": "dist/cli.js"
|
|
11
|
-
},
|
|
12
|
-
"exports": {
|
|
13
|
-
".": {
|
|
14
|
-
"source": "./src/index.ts",
|
|
15
|
-
"types": "./dist/index.d.ts",
|
|
16
|
-
"default": "./dist/index.js"
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
"files": [
|
|
20
|
-
"dist",
|
|
21
|
-
"LICENSE.md",
|
|
22
|
-
"README.md"
|
|
23
|
-
],
|
|
24
|
-
"scripts": {
|
|
25
|
-
"build": "tsc -p tsconfig.build.json",
|
|
26
|
-
"prepublishOnly": "npm run build"
|
|
27
|
-
},
|
|
28
|
-
"engines": {
|
|
29
|
-
"node": ">=22.13"
|
|
30
|
-
},
|
|
31
|
-
"keywords": [
|
|
32
|
-
"mcp",
|
|
33
|
-
"model-context-protocol",
|
|
34
|
-
"x402",
|
|
35
|
-
"agentic-commerce",
|
|
36
|
-
"agent-payments",
|
|
37
|
-
"click-and-collect",
|
|
38
|
-
"pickup"
|
|
39
|
-
],
|
|
40
|
-
"repository": {
|
|
41
|
-
"type": "git",
|
|
42
|
-
"url": "git+https://github.com/dav3trad3r/agentpickup.git",
|
|
43
|
-
"directory": "packages/mcp"
|
|
44
|
-
},
|
|
45
|
-
"homepage": "https://agentpickup.app",
|
|
46
|
-
"bugs": {
|
|
47
|
-
"url": "https://github.com/dav3trad3r/agentpickup/issues"
|
|
48
|
-
},
|
|
49
|
-
"author": "Dave Hallett",
|
|
50
|
-
"license": "FSL-1.1-ALv2",
|
|
51
|
-
"publishConfig": {
|
|
52
|
-
"access": "public"
|
|
53
|
-
},
|
|
54
|
-
"dependencies": {
|
|
55
|
-
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
56
|
-
"@x402/core": "2.29.0",
|
|
57
|
-
"@x402/evm": "2.29.0",
|
|
58
|
-
"@x402/extensions": "2.29.0",
|
|
59
|
-
"viem": "^2.57.14",
|
|
60
|
-
"zod": "^4.6.7"
|
|
61
|
-
}
|
|
62
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentpickup/mcp",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "MCP server for agentPickup: let an AI agent find pickup shops near its user, order, pay over x402, and hand its user the QR to collect in person.",
|
|
5
|
+
"mcpName": "io.github.dav3trad3r/agentpickup",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"agentpickup-mcp": "dist/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"source": "./src/index.ts",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"LICENSE.md",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc -p tsconfig.build.json",
|
|
26
|
+
"prepublishOnly": "npm run build"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22.13"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"mcp",
|
|
33
|
+
"model-context-protocol",
|
|
34
|
+
"x402",
|
|
35
|
+
"agentic-commerce",
|
|
36
|
+
"agent-payments",
|
|
37
|
+
"click-and-collect",
|
|
38
|
+
"pickup"
|
|
39
|
+
],
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/dav3trad3r/agentpickup.git",
|
|
43
|
+
"directory": "packages/mcp"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://agentpickup.app",
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/dav3trad3r/agentpickup/issues"
|
|
48
|
+
},
|
|
49
|
+
"author": "Dave Hallett",
|
|
50
|
+
"license": "FSL-1.1-ALv2",
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
56
|
+
"@x402/core": "2.29.0",
|
|
57
|
+
"@x402/evm": "2.29.0",
|
|
58
|
+
"@x402/extensions": "2.29.0",
|
|
59
|
+
"viem": "^2.57.14",
|
|
60
|
+
"zod": "^4.6.7"
|
|
61
|
+
}
|
|
62
|
+
}
|