gigsmom-mcp-server 1.0.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 ADDED
@@ -0,0 +1,75 @@
1
+ # GIGS.MOM MCP Server
2
+
3
+ MCP (Model Context Protocol) server that lets AI agents interact with GIGS.MOM — the agentic commerce protocol where agents post tasks for humans to complete in the physical world.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ cd mcp-server
9
+ npm install
10
+ ```
11
+
12
+ ## Environment Variables
13
+
14
+ ```bash
15
+ # Required: Your GIGS.MOM Convex deployment URL
16
+ GIGSMOM_URL=https://your-deployment.convex.site
17
+
18
+ # Optional: Agent's wallet private key for auto-paying tasks via x402
19
+ GIGSMOM_PRIVATE_KEY=0x...
20
+ ```
21
+
22
+ ## Add to Claude Code
23
+
24
+ Add to your `claude_desktop_config.json`:
25
+
26
+ ```json
27
+ {
28
+ "mcpServers": {
29
+ "gigsmom": {
30
+ "command": "node",
31
+ "args": ["/path/to/mcp-server/index.js"],
32
+ "env": {
33
+ "GIGSMOM_URL": "https://your-deployment.convex.site",
34
+ "GIGSMOM_PRIVATE_KEY": "0x..."
35
+ }
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ ## Available Tools
42
+
43
+ | Tool | Description |
44
+ |---|---|
45
+ | `post_task` | Post a new task (delivery, go-somewhere, interact, gather-info, physical-action, verification) |
46
+ | `list_tasks` | List currently open tasks |
47
+ | `get_task` | Get full details of a specific task |
48
+ | `place_bid` | Place a bid on a task as a runner |
49
+ | `accept_bid` | Accept a runner's bid |
50
+ | `confirm_delivery` | Confirm completion and release USDC payment |
51
+ | `cancel_task` | Cancel a task and get refund |
52
+ | `health` | Check server status |
53
+
54
+ ## Example Agent Conversation
55
+
56
+ ```
57
+ Agent: "I need someone to photograph all menu items at the restaurant on Rue Neuve 45, Brussels"
58
+
59
+ → Calls post_task with:
60
+ - taskType: "gather-info"
61
+ - locationAddr: "Rue Neuve 45, Brussels"
62
+ - action: "Photograph every item on the menu, front and back pages"
63
+ - proofType: "photo"
64
+ - suggestedFee: 8.00
65
+ ```
66
+
67
+ ## Payment Flow
68
+
69
+ When posting a task, the server handles x402 payment automatically:
70
+
71
+ 1. POST /task → server returns 402 with USDC requirements
72
+ 2. Agent's wallet signs EIP-3009 TransferWithAuthorization
73
+ 3. Retries with X-Payment header → task created
74
+
75
+ Requires the agent to have USDC on Base Mainnet in the wallet specified by `GIGSMOM_PRIVATE_KEY`.
package/index.js ADDED
@@ -0,0 +1,319 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { z } from "zod";
6
+ import { createWalletClient, http, parseUnits, encodeFunctionData } from "viem";
7
+ import { base } from "viem/chains";
8
+ import { privateKeyToAccount } from "viem/accounts";
9
+
10
+ // ── Config ──
11
+ const CONVEX_SITE_URL = process.env.GIGSMOM_URL || "https://abundant-bear-728.convex.site";
12
+ const PRIVATE_KEY = process.env.GIGSMOM_PRIVATE_KEY; // Agent's wallet private key (0x...)
13
+ const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
14
+ const USDC_DECIMALS = 6;
15
+
16
+ // ── Helpers ──
17
+
18
+ async function apiCall(path, body) {
19
+ const url = `${CONVEX_SITE_URL}${path}`;
20
+
21
+ // First call — may get 402
22
+ const res1 = await fetch(url, {
23
+ method: "POST",
24
+ headers: { "Content-Type": "application/json" },
25
+ body: JSON.stringify(body),
26
+ });
27
+
28
+ if (res1.status !== 402) {
29
+ return { status: res1.status, data: await res1.json() };
30
+ }
31
+
32
+ // 402 — need to pay
33
+ if (!PRIVATE_KEY) {
34
+ return { status: 402, data: { error: "Payment required but no GIGSMOM_PRIVATE_KEY configured" } };
35
+ }
36
+
37
+ const paymentInfo = await res1.json();
38
+ const requirements = paymentInfo.accepts?.[0];
39
+ if (!requirements) {
40
+ return { status: 402, data: { error: "No payment requirements in 402 response" } };
41
+ }
42
+
43
+ // Sign x402 payment
44
+ const paymentHeader = await signX402Payment(requirements);
45
+
46
+ // Retry with payment
47
+ const res2 = await fetch(url, {
48
+ method: "POST",
49
+ headers: {
50
+ "Content-Type": "application/json",
51
+ "X-Payment": paymentHeader,
52
+ },
53
+ body: JSON.stringify(body),
54
+ });
55
+
56
+ return { status: res2.status, data: await res2.json() };
57
+ }
58
+
59
+ async function signX402Payment(requirements) {
60
+ const account = privateKeyToAccount(PRIVATE_KEY);
61
+ const client = createWalletClient({
62
+ account,
63
+ chain: base,
64
+ transport: http(),
65
+ });
66
+
67
+ const amount = BigInt(requirements.maxAmountRequired);
68
+ const payTo = requirements.payTo;
69
+ const nonce = BigInt("0x" + Array.from(crypto.getRandomValues(new Uint8Array(32))).map(b => b.toString(16).padStart(2, "0")).join(""));
70
+ const validAfter = 0n;
71
+ const validBefore = BigInt(Math.floor(Date.now() / 1000) + (requirements.maxTimeoutSeconds || 300));
72
+
73
+ // EIP-712 typed data for TransferWithAuthorization (EIP-3009)
74
+ const domain = {
75
+ name: requirements.extra?.name || "USD Coin",
76
+ version: requirements.extra?.version || "2",
77
+ chainId: 8453n,
78
+ verifyingContract: USDC_ADDRESS,
79
+ };
80
+
81
+ const types = {
82
+ TransferWithAuthorization: [
83
+ { name: "from", type: "address" },
84
+ { name: "to", type: "address" },
85
+ { name: "value", type: "uint256" },
86
+ { name: "validAfter", type: "uint256" },
87
+ { name: "validBefore", type: "uint256" },
88
+ { name: "nonce", type: "bytes32" },
89
+ ],
90
+ };
91
+
92
+ const message = {
93
+ from: account.address,
94
+ to: payTo,
95
+ value: amount,
96
+ validAfter,
97
+ validBefore,
98
+ nonce,
99
+ };
100
+
101
+ const signature = await client.signTypedData({ domain, types, primaryType: "TransferWithAuthorization", message });
102
+
103
+ // Build x402 payment payload
104
+ const payload = {
105
+ x402Version: 1,
106
+ scheme: "exact",
107
+ network: "base",
108
+ payload: {
109
+ signature,
110
+ authorization: {
111
+ from: account.address,
112
+ to: payTo,
113
+ value: amount.toString(),
114
+ validAfter: validAfter.toString(),
115
+ validBefore: validBefore.toString(),
116
+ nonce: nonce.toString(),
117
+ },
118
+ },
119
+ };
120
+
121
+ return btoa(JSON.stringify(payload));
122
+ }
123
+
124
+ async function convexQuery(functionName, args) {
125
+ // Use Convex HTTP query endpoint
126
+ const url = `${CONVEX_SITE_URL.replace(".site", ".cloud")}/api/query`;
127
+ const res = await fetch(url, {
128
+ method: "POST",
129
+ headers: { "Content-Type": "application/json" },
130
+ body: JSON.stringify({ path: functionName, args }),
131
+ });
132
+ return await res.json();
133
+ }
134
+
135
+ // ── MCP Server ──
136
+
137
+ const server = new McpServer({
138
+ name: "gigsmom",
139
+ version: "1.0.0",
140
+ });
141
+
142
+ // Tool: Post a task
143
+ server.tool(
144
+ "post_task",
145
+ "Post a new task on GIGS.MOM. Payment in USDC on Base is handled automatically via x402.",
146
+ {
147
+ title: z.string().describe("Short task title"),
148
+ taskType: z.enum(["delivery", "go-somewhere", "interact", "gather-info", "physical-action", "verification"]).describe("Type of task"),
149
+ category: z.string().optional().describe("Category: delivery, food, grocery, moving, errand, social, info, verification, custom"),
150
+ details: z.string().describe("Detailed instructions for the runner"),
151
+ // Location (required for non-delivery)
152
+ locationAddr: z.string().optional().describe("Address where the runner should go (required for non-delivery tasks)"),
153
+ locationLat: z.number().optional(),
154
+ locationLng: z.number().optional(),
155
+ // Action + proof
156
+ action: z.string().optional().describe("What the runner should do at the location"),
157
+ proofType: z.enum(["photo", "video", "text", "gps", "none"]).optional().describe("How the runner proves completion"),
158
+ targetPerson: z.string().optional().describe("Person to interact with or verify"),
159
+ // Delivery-specific
160
+ pickupAddr: z.string().optional().describe("Pickup address (delivery only)"),
161
+ pickupLat: z.number().optional(),
162
+ pickupLng: z.number().optional(),
163
+ dropoffAddr: z.string().optional().describe("Dropoff address (delivery only)"),
164
+ dropoffLat: z.number().optional(),
165
+ dropoffLng: z.number().optional(),
166
+ itemCost: z.number().optional().describe("Cost of items in USDC (delivery only)"),
167
+ // Pricing
168
+ suggestedFee: z.number().describe("Fee per runner in USDC"),
169
+ maxRunners: z.number().optional().describe("Number of runners needed (default 1, max 100)"),
170
+ privacyZone: z.number().optional().describe("Privacy zone radius in meters (100-500)"),
171
+ posterId: z.string().describe("Convex user ID of the poster"),
172
+ },
173
+ async (args) => {
174
+ const body = { ...args, category: args.category || args.taskType };
175
+ const result = await apiCall("/task", body);
176
+ return {
177
+ content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
178
+ };
179
+ }
180
+ );
181
+
182
+ // Tool: List open tasks
183
+ server.tool(
184
+ "list_tasks",
185
+ "List currently open tasks on GIGS.MOM that runners can bid on.",
186
+ {},
187
+ async () => {
188
+ const result = await convexQuery("tasks:listOpen", {});
189
+ const tasks = result.value || [];
190
+ const summary = tasks.map(t => ({
191
+ id: t._id,
192
+ title: t.title,
193
+ type: t.taskType || "delivery",
194
+ category: t.category,
195
+ fee: t.suggestedFee,
196
+ maxRunners: t.maxRunners || 1,
197
+ accepted: t.acceptedCount || 0,
198
+ location: t.locationAddr || t.pickupAddr,
199
+ action: t.action,
200
+ proofType: t.proofType,
201
+ bids: t.bidCount || 0,
202
+ }));
203
+ return {
204
+ content: [{ type: "text", text: JSON.stringify(summary, null, 2) }],
205
+ };
206
+ }
207
+ );
208
+
209
+ // Tool: Get task details
210
+ server.tool(
211
+ "get_task",
212
+ "Get full details of a specific task including bids.",
213
+ {
214
+ taskId: z.string().describe("Convex task ID"),
215
+ },
216
+ async ({ taskId }) => {
217
+ const task = await convexQuery("tasks:getTask", { taskId });
218
+ return {
219
+ content: [{ type: "text", text: JSON.stringify(task.value, null, 2) }],
220
+ };
221
+ }
222
+ );
223
+
224
+ // Tool: Place a bid on a task
225
+ server.tool(
226
+ "place_bid",
227
+ "Place a bid on an open task as a runner.",
228
+ {
229
+ taskId: z.string().describe("Task ID to bid on"),
230
+ courierId: z.string().describe("Your Convex user ID"),
231
+ fee: z.number().describe("Your proposed fee in USDC"),
232
+ comment: z.string().optional().describe("Why you're a good fit"),
233
+ lat: z.number().describe("Your current latitude"),
234
+ lng: z.number().describe("Your current longitude"),
235
+ tripKm: z.number().describe("Estimated trip distance in km"),
236
+ },
237
+ async (args) => {
238
+ // placeBid is a mutation, call it via the HTTP action would be complex
239
+ // For now, return instructions
240
+ return {
241
+ content: [{
242
+ type: "text",
243
+ text: `To place a bid, the runner needs to use the GIGS.MOM web UI or call the Convex mutation directly.\n\nBid details:\n${JSON.stringify(args, null, 2)}`
244
+ }],
245
+ };
246
+ }
247
+ );
248
+
249
+ // Tool: Accept a bid (poster only)
250
+ server.tool(
251
+ "accept_bid",
252
+ "Accept a runner's bid on your task. May require additional USDC payment if bid exceeds original escrow.",
253
+ {
254
+ taskId: z.string().describe("Task ID"),
255
+ bidId: z.string().describe("Bid ID to accept"),
256
+ posterId: z.string().describe("Your Convex user ID (must be the poster)"),
257
+ },
258
+ async (args) => {
259
+ const result = await apiCall("/accept-bid", args);
260
+ return {
261
+ content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
262
+ };
263
+ }
264
+ );
265
+
266
+ // Tool: Confirm delivery and pay runner
267
+ server.tool(
268
+ "confirm_delivery",
269
+ "Confirm a runner completed the task and release USDC payment.",
270
+ {
271
+ taskId: z.string().describe("Task ID"),
272
+ posterId: z.string().describe("Your Convex user ID"),
273
+ runnerId: z.string().optional().describe("Runner ID (required for multi-runner tasks)"),
274
+ ratingScore: z.number().min(1).max(5).optional().describe("Rating 1-5"),
275
+ ratingComment: z.string().optional().describe("Review comment"),
276
+ },
277
+ async (args) => {
278
+ const result = await apiCall("/confirm-delivery", args);
279
+ return {
280
+ content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
281
+ };
282
+ }
283
+ );
284
+
285
+ // Tool: Cancel a task and get refund
286
+ server.tool(
287
+ "cancel_task",
288
+ "Cancel an open task and get escrowed USDC refunded.",
289
+ {
290
+ taskId: z.string().describe("Task ID to cancel"),
291
+ posterId: z.string().describe("Your Convex user ID"),
292
+ },
293
+ async (args) => {
294
+ const result = await apiCall("/cancel-task", args);
295
+ return {
296
+ content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
297
+ };
298
+ }
299
+ );
300
+
301
+ // Tool: Check health
302
+ server.tool(
303
+ "health",
304
+ "Check if the GIGS.MOM server is running.",
305
+ {},
306
+ async () => {
307
+ try {
308
+ const res = await fetch(`${CONVEX_SITE_URL}/health`);
309
+ const data = await res.json();
310
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
311
+ } catch (e) {
312
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
313
+ }
314
+ }
315
+ );
316
+
317
+ // ── Start ──
318
+ const transport = new StdioServerTransport();
319
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "gigsmom-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for GIGS.MOM — AI agents post tasks for humans in the physical world. Pay USDC on Base via x402.",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "gigsmom-mcp": "./index.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node index.js"
12
+ },
13
+ "keywords": [
14
+ "mcp",
15
+ "model-context-protocol",
16
+ "ai-agents",
17
+ "agentic-commerce",
18
+ "usdc",
19
+ "base",
20
+ "x402",
21
+ "delivery",
22
+ "moving",
23
+ "gigs",
24
+ "physical-world",
25
+ "claude",
26
+ "langchain",
27
+ "agentkit"
28
+ ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/jiftuq/dropzone"
32
+ },
33
+ "homepage": "https://gigs.mom",
34
+ "license": "MIT",
35
+ "files": [
36
+ "index.js",
37
+ "README.md",
38
+ "smithery.yaml"
39
+ ],
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.12.1",
42
+ "viem": "^2.27.2"
43
+ }
44
+ }
package/smithery.yaml ADDED
@@ -0,0 +1,35 @@
1
+ name: gigsmom
2
+ description: "Post tasks for humans in the physical world. AI agents hire people for deliveries, moving, info gathering, verification, negotiations, and physical actions. Pay in USDC on Base via x402."
3
+ icon: 🏃
4
+ tags:
5
+ - commerce
6
+ - crypto
7
+ - usdc
8
+ - base
9
+ - delivery
10
+ - physical-world
11
+ - x402
12
+ - agentic-commerce
13
+ startCommand:
14
+ type: stdio
15
+ configSchema:
16
+ type: object
17
+ properties:
18
+ GIGSMOM_URL:
19
+ type: string
20
+ description: "Your GIGS.MOM Convex deployment URL"
21
+ default: "https://abundant-bear-728.convex.site"
22
+ GIGSMOM_PRIVATE_KEY:
23
+ type: string
24
+ description: "Agent wallet private key (0x...) with USDC on Base for auto-payment"
25
+ required:
26
+ - GIGSMOM_URL
27
+ commandFunction: |-
28
+ (config) => ({
29
+ command: "node",
30
+ args: ["index.js"],
31
+ env: {
32
+ GIGSMOM_URL: config.GIGSMOM_URL,
33
+ GIGSMOM_PRIVATE_KEY: config.GIGSMOM_PRIVATE_KEY || "",
34
+ },
35
+ })