@agentpickup/mcp 0.3.1 → 0.3.3
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 -3
- package/dist/server.d.ts +3 -1
- package/dist/server.js +51 -23
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @agentpickup/mcp
|
|
2
2
|
|
|
3
|
-
An MCP server for [agentPickup](https://
|
|
3
|
+
An MCP server for [agentPickup](https://agentpickup.app) shops. It lets an AI agent find
|
|
4
4
|
pickup shops near its user, see what they sell, order, pay by card or in USDC over x402, and hand its user the
|
|
5
5
|
QR they show at the counter to collect.
|
|
6
6
|
|
|
@@ -10,8 +10,8 @@ QR they show at the counter to collect.
|
|
|
10
10
|
|---|---|
|
|
11
11
|
| `find_shops` | Shops near a point (`lat`/`lng`), in a suburb (`place`) or selling something (`query`), from the agentPickup directory: each one's `shop_url`, address, hours, open now, distance and matching items |
|
|
12
12
|
| `get_shop` | Items and prices, the pickup location, and how the shop takes payment |
|
|
13
|
-
| `create_pickup_order` | Creates an order. Needs the user's email, where the shop sends the receipt and pickup QR. A test shop marks it paid straight away, with no money moving |
|
|
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 |
|
|
13
|
+
| `create_pickup_order` | Creates an order. Needs the user's email, where the shop sends the receipt and pickup QR. A test shop marks it paid straight away, with no money moving. Where the shop takes cards, `payment: "card"` returns a checkout link for the user to pay on instead |
|
|
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. For a card order, returns its checkout link |
|
|
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
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 |
|
|
@@ -62,6 +62,7 @@ items and the total with the user before paying.
|
|
|
62
62
|
import { createMcpHandler, walletPayer } from "@agentpickup/mcp";
|
|
63
63
|
|
|
64
64
|
const mcp = createMcpHandler({ shopUrl: "https://orders.yourshop.com" }); // no wallet: pay_order quotes
|
|
65
|
+
// `instructions` adds your own notes to what agents read on connecting, e.g. which of your shops take what.
|
|
65
66
|
app.all("/mcp", (c) => mcp(c.req.raw)); // Streamable HTTP, stateless, any Request/Response runtime
|
|
66
67
|
```
|
|
67
68
|
|
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.
|
|
3
|
+
export declare const VERSION = "0.3.3";
|
|
4
4
|
export interface PickupMcpOptions {
|
|
5
5
|
/** The agentPickup shop this server orders from. */
|
|
6
6
|
shopUrl: string;
|
|
@@ -20,6 +20,8 @@ export interface PickupMcpOptions {
|
|
|
20
20
|
fetch?: Fetch;
|
|
21
21
|
/** The wallet that pays. Without one, pay_order returns the price and pay link for another x402 client. */
|
|
22
22
|
payer?: Payer;
|
|
23
|
+
/** Notes added to the instructions agents read on connecting, e.g. which of this host's shops take what. */
|
|
24
|
+
instructions?: string;
|
|
23
25
|
}
|
|
24
26
|
export declare function createPickupMcpServer(opts: PickupMcpOptions): McpServer;
|
|
25
27
|
/** A Streamable HTTP endpoint (stateless, JSON responses) for hosting the server, e.g. at /mcp. */
|
package/dist/server.js
CHANGED
|
@@ -4,15 +4,17 @@ 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.3.
|
|
7
|
+
export const VERSION = "0.3.3";
|
|
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.
|
|
11
11
|
0. find_shops (when available): shops near the user that sell what they want. Pass a shop's shop_url to the other tools.
|
|
12
12
|
1. get_shop: see what the shop sells, its pickup location and how it takes payment.
|
|
13
13
|
2. create_pickup_order: needs the user's email, where the shop sends the pickup QR.
|
|
14
|
-
3. pay_order: pays from the
|
|
15
|
-
|
|
14
|
+
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
|
|
15
|
+
own x402 wallet. Where the shop takes cards, payment: "card" on
|
|
16
|
+
create_pickup_order gives a checkout link for the user to pay on instead. Where it takes test payments (get_shop says
|
|
17
|
+
so), payment: "test" runs the whole flow with no money moving. Confirm the items and total with the user first.
|
|
16
18
|
4. Give the user the pass link and pickup code. They show the QR at the counter to collect.
|
|
17
19
|
get_pickup_pass fetches the pass again later; order_status says whether it's ready or collected.
|
|
18
20
|
Group orders (a coffee run): start_group_order gives a code to share; each person's agent calls join_group_order and
|
|
@@ -74,7 +76,13 @@ export function createPickupMcpServer(opts) {
|
|
|
74
76
|
const shopArg = opts.allowOtherShops || opts.directoryUrl
|
|
75
77
|
? { shop_url: z.string().optional().describe(`An agentPickup shop's URL, as find_shops returns it. Defaults to ${home}.`) }
|
|
76
78
|
: {};
|
|
77
|
-
const server = new McpServer({
|
|
79
|
+
const server = new McpServer({
|
|
80
|
+
name: "agentpickup",
|
|
81
|
+
title: "agentPickup",
|
|
82
|
+
version: VERSION,
|
|
83
|
+
websiteUrl: "https://agentpickup.app",
|
|
84
|
+
icons: [{ src: "https://agentpickup.app/logo.png", mimeType: "image/png", sizes: ["1024x1024"] }],
|
|
85
|
+
}, { instructions: opts.instructions ? `${INSTRUCTIONS}\n${opts.instructions}` : INSTRUCTIONS });
|
|
78
86
|
if (opts.directoryUrl) {
|
|
79
87
|
const directoryUrl = opts.directoryUrl;
|
|
80
88
|
server.registerTool("find_shops", {
|
|
@@ -90,7 +98,7 @@ export function createPickupMcpServer(opts) {
|
|
|
90
98
|
open_now: z.boolean().optional().describe("Only shops open now (shops without listed hours stay in)."),
|
|
91
99
|
limit: z.number().int().min(1).max(50).optional(),
|
|
92
100
|
},
|
|
93
|
-
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
101
|
+
annotations: { title: "Find pickup shops", readOnlyHint: true, openWorldHint: true },
|
|
94
102
|
}, guarded(async (args) => {
|
|
95
103
|
if ((args.lat === undefined) !== (args.lng === undefined))
|
|
96
104
|
throw new ToolError("Send lat and lng together.");
|
|
@@ -141,7 +149,7 @@ export function createPickupMcpServer(opts) {
|
|
|
141
149
|
title: "See what a shop sells",
|
|
142
150
|
description: "What the shop sells (item ids and prices), where to collect, and which payments it takes.",
|
|
143
151
|
inputSchema: { ...shopArg },
|
|
144
|
-
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
152
|
+
annotations: { title: "See what a shop sells", readOnlyHint: true, openWorldHint: true },
|
|
145
153
|
}, guarded(async (args) => {
|
|
146
154
|
const shop = await shopFor(args.shop_url);
|
|
147
155
|
const data = await call(shop, "/api/catalog");
|
|
@@ -179,12 +187,13 @@ export function createPickupMcpServer(opts) {
|
|
|
179
187
|
email: z.string().email().describe("The user's email. The pickup QR goes here."),
|
|
180
188
|
name: z.string().max(100).optional().describe("Name for the order, shown to staff"),
|
|
181
189
|
payment: z
|
|
182
|
-
.enum(["agent", "card"])
|
|
190
|
+
.enum(["agent", "card", "test"])
|
|
183
191
|
.optional()
|
|
184
|
-
.describe("Who pays: agent (USDC over x402, or a test payment at a test shop)
|
|
192
|
+
.describe("Who pays: agent (USDC over x402, or a test payment at a test shop), card (the user pays on a checkout link), " +
|
|
193
|
+
"or test (where the shop takes test payments: the whole flow with no money moving). Default: agent where the shop takes it, else card."),
|
|
185
194
|
...shopArg,
|
|
186
195
|
},
|
|
187
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
196
|
+
annotations: { title: "Order for pickup", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
188
197
|
}, guarded(async (args) => {
|
|
189
198
|
const shop = await shopFor(args.shop_url);
|
|
190
199
|
// The agent pays over x402 where the shop takes it, a test shop marks the order paid straight away, and a
|
|
@@ -192,9 +201,15 @@ export function createPickupMcpServer(opts) {
|
|
|
192
201
|
const { payments } = (await call(shop, "/api/catalog"));
|
|
193
202
|
const agentRail = payments.x402 ? "x402" : payments.test ? "test" : null;
|
|
194
203
|
const cardRail = payments.square ? "square" : payments.card ? "card" : null;
|
|
195
|
-
const rail = args.payment === "
|
|
196
|
-
|
|
197
|
-
|
|
204
|
+
const rail = args.payment === "test" ? (payments.test ? "test" : null)
|
|
205
|
+
: args.payment === "card" ? cardRail
|
|
206
|
+
: args.payment === "agent" ? agentRail
|
|
207
|
+
: (agentRail ?? cardRail);
|
|
208
|
+
if (!rail) {
|
|
209
|
+
throw new ToolError(args.payment === "test" ? "This shop doesn't take test payments."
|
|
210
|
+
: args.payment === "card" ? "This shop doesn't take cards."
|
|
211
|
+
: "This shop doesn't take agent payments.");
|
|
212
|
+
}
|
|
198
213
|
const data = await call(shop, "/api/orders", {
|
|
199
214
|
method: "POST",
|
|
200
215
|
headers: { "Content-Type": "application/json" },
|
|
@@ -219,7 +234,7 @@ export function createPickupMcpServer(opts) {
|
|
|
219
234
|
? `Pay an order in USDC over x402 from wallet ${opts.payer.address}. This spends money: confirm the items and total with the user first. Returns the pickup pass link.`
|
|
220
235
|
: "No wallet is set up here, so this returns the price and pay link for another x402 client to pay.",
|
|
221
236
|
inputSchema: { order_id: z.string(), ...shopArg },
|
|
222
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
237
|
+
annotations: { title: "Pay for an order", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
223
238
|
}, guarded(async (args) => {
|
|
224
239
|
const shop = await shopFor(args.shop_url);
|
|
225
240
|
const status = (await call(shop, `/api/orders/${encodeURIComponent(args.order_id)}`));
|
|
@@ -239,12 +254,25 @@ export function createPickupMcpServer(opts) {
|
|
|
239
254
|
const header = res.headers.get("PAYMENT-REQUIRED");
|
|
240
255
|
const required = (header ? JSON.parse(Buffer.from(header, "base64").toString("utf8")) : await res.json());
|
|
241
256
|
const accept = required.accepts?.[0];
|
|
242
|
-
|
|
243
|
-
|
|
257
|
+
// Other ways to finish, for an agent with no wallet of its own (a chat app on the web, say).
|
|
258
|
+
const { payments } = (await call(shop, "/api/catalog").catch(() => ({ payments: {} })));
|
|
259
|
+
const others = [
|
|
260
|
+
...(payments.square || payments.card ? ['payment "card" gives the user a checkout link to pay on'] : []),
|
|
261
|
+
...(payments.test ? ['payment "test" runs the whole flow with no money moving'] : []),
|
|
262
|
+
];
|
|
263
|
+
return reply(`Waiting for payment: ${usd(accept?.amount)} USDC on ${accept?.network}. No wallet is set up in this server: pay the link ` +
|
|
264
|
+
`from the user's own x402 wallet (such as Coinbase's agent wallet), or set AGENTPICKUP_WALLET_KEY where this server runs.` +
|
|
265
|
+
(others.length ? ` Or create the order again with create_pickup_order: ${others.join("; ")}.` : ""), { orderId: args.order_id, payUrl, amount: usd(accept?.amount), network: accept?.network, asset: accept?.asset, payTo: accept?.payTo });
|
|
244
266
|
}
|
|
245
267
|
const paid = await opts.payer.pay({ id: args.order_id, payUrl }, doFetch);
|
|
246
|
-
if (!paid.ok)
|
|
247
|
-
|
|
268
|
+
if (!paid.ok) {
|
|
269
|
+
// A demo or test shop can still show the user the whole flow, with a test payment instead.
|
|
270
|
+
const { payments } = (await call(shop, "/api/catalog").catch(() => ({ payments: {} })));
|
|
271
|
+
throw new ToolError(paid.error +
|
|
272
|
+
(payments.test
|
|
273
|
+
? ' This shop also takes test payments, which move no money: call create_pickup_order again with payment "test" to run the whole flow.'
|
|
274
|
+
: ""));
|
|
275
|
+
}
|
|
248
276
|
return reply(`Paid ${paid.amount ?? ""} USDC. Pickup pass: ${paid.passUrl}` +
|
|
249
277
|
(paid.emailedTo ? ` (also emailed to ${paid.emailedTo})` : "") +
|
|
250
278
|
`. The user shows its QR at the counter.`, { orderId: args.order_id, ...paid, ok: undefined });
|
|
@@ -253,7 +281,7 @@ export function createPickupMcpServer(opts) {
|
|
|
253
281
|
title: "Check an order",
|
|
254
282
|
description: "Whether an order is waiting for payment, paid, ready to collect, collected or expired.",
|
|
255
283
|
inputSchema: { order_id: z.string(), ...shopArg },
|
|
256
|
-
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
284
|
+
annotations: { title: "Check an order", readOnlyHint: true, openWorldHint: true },
|
|
257
285
|
}, guarded(async (args) => {
|
|
258
286
|
const shop = await shopFor(args.shop_url);
|
|
259
287
|
const { order } = (await call(shop, `/api/orders/${encodeURIComponent(args.order_id)}`));
|
|
@@ -271,7 +299,7 @@ export function createPickupMcpServer(opts) {
|
|
|
271
299
|
title: "Get the pickup pass",
|
|
272
300
|
description: "The pass link, pickup code and QR content for an order this wallet paid, by signing in with the wallet (Sign-In-With-X).",
|
|
273
301
|
inputSchema: { order_id: z.string(), ...shopArg },
|
|
274
|
-
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
302
|
+
annotations: { title: "Get the pickup pass", readOnlyHint: true, openWorldHint: true },
|
|
275
303
|
}, guarded(async (args) => {
|
|
276
304
|
if (!opts.payer)
|
|
277
305
|
throw new ToolError("No wallet is set up here. The pass was emailed to the buyer when the order was paid.");
|
|
@@ -301,7 +329,7 @@ export function createPickupMcpServer(opts) {
|
|
|
301
329
|
closes_in_minutes: z.number().int().min(5).max(240).optional().describe("Default 15"),
|
|
302
330
|
...shopArg,
|
|
303
331
|
},
|
|
304
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
332
|
+
annotations: { title: "Start a group order", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
305
333
|
}, guarded(async (args) => {
|
|
306
334
|
const shop = await shopFor(args.shop_url);
|
|
307
335
|
const data = (await call(shop, "/api/groups", {
|
|
@@ -323,7 +351,7 @@ export function createPickupMcpServer(opts) {
|
|
|
323
351
|
name: z.string().min(1).max(60).describe("The user's name, shown to staff on their items"),
|
|
324
352
|
...shopArg,
|
|
325
353
|
},
|
|
326
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
354
|
+
annotations: { title: "Join a group order", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
327
355
|
}, guarded(async (args) => {
|
|
328
356
|
const shop = await shopFor(args.shop_url);
|
|
329
357
|
const { payments } = (await call(shop, "/api/catalog"));
|
|
@@ -344,7 +372,7 @@ export function createPickupMcpServer(opts) {
|
|
|
344
372
|
title: "Check a group order",
|
|
345
373
|
description: "Who's in a group order, what they ordered, who has paid, and whether it has closed and been collected.",
|
|
346
374
|
inputSchema: { code: z.string().min(4).max(12), ...shopArg },
|
|
347
|
-
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
375
|
+
annotations: { title: "Check a group order", readOnlyHint: true, openWorldHint: true },
|
|
348
376
|
}, guarded(async (args) => {
|
|
349
377
|
const shop = await shopFor(args.shop_url);
|
|
350
378
|
const { group } = (await call(shop, `/api/groups/${encodeURIComponent(args.code)}`));
|
|
@@ -354,7 +382,7 @@ export function createPickupMcpServer(opts) {
|
|
|
354
382
|
title: "Close a group order now",
|
|
355
383
|
description: "Close a group order early, as its organiser. Unpaid shares are left out; the shop gets the rest as one order.",
|
|
356
384
|
inputSchema: { code: z.string().min(4).max(12), organiser_key: z.string().min(8), ...shopArg },
|
|
357
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
385
|
+
annotations: { title: "Close a group order now", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
358
386
|
}, guarded(async (args) => {
|
|
359
387
|
const shop = await shopFor(args.shop_url);
|
|
360
388
|
const { group } = (await call(shop, `/api/groups/${encodeURIComponent(args.code)}/close`, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentpickup/mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
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",
|
|
@@ -39,12 +39,12 @@
|
|
|
39
39
|
],
|
|
40
40
|
"repository": {
|
|
41
41
|
"type": "git",
|
|
42
|
-
"url": "git+https://github.com/
|
|
42
|
+
"url": "git+https://github.com/Tokenise-au/agentpickup-sdk.git",
|
|
43
43
|
"directory": "packages/mcp"
|
|
44
44
|
},
|
|
45
45
|
"homepage": "https://agentpickup.app",
|
|
46
46
|
"bugs": {
|
|
47
|
-
"url": "https://github.com/
|
|
47
|
+
"url": "https://github.com/Tokenise-au/agentpickup-sdk/issues"
|
|
48
48
|
},
|
|
49
49
|
"author": "Dave Hallett",
|
|
50
50
|
"license": "FSL-1.1-ALv2",
|