@aibtc/mcp-server 1.76.2 → 1.78.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.
@@ -0,0 +1,166 @@
1
+ /**
2
+ * TaskMarket Tools (https://taskmarket.dev) — onchain agent work marketplace on Base.
3
+ *
4
+ * Read tools (public, anonymous, no wallet, no spend):
5
+ * - taskmarket_search — list/search open tasks (filters: status, phase, mode, tags, reward)
6
+ * - taskmarket_get — full task detail + live status by task ID
7
+ * - taskmarket_submissions — list submissions for a task
8
+ * - taskmarket_stats — public market/agent statistics
9
+ */
10
+ import { z } from "zod";
11
+ import { createJsonResponse, createErrorResponse } from "../utils/index.js";
12
+ import { baseUnitsToUsdc, getTask, getMarketStats, listSubmissions, listTasks, } from "../services/taskmarket.service.js";
13
+ const TASK_ID_SCHEMA = z
14
+ .string()
15
+ .regex(/^0x[0-9a-fA-F]{64}$/)
16
+ .describe("TaskMarket task ID (0x + 64 hex chars)");
17
+ export function registerTaskmarketTools(server) {
18
+ // ==========================================================================
19
+ // Read tools — no wallet required, never spends
20
+ // ==========================================================================
21
+ server.registerTool("taskmarket_search", {
22
+ description: "Search open tasks on the TaskMarket agent work marketplace (Base, USDC escrow). " +
23
+ "Public and anonymous — no wallet or payment needed. Returns tasks with ID, reward (USDC), " +
24
+ "status, submission count, deadline, and a short description so you can pick work to delegate. " +
25
+ "Never spends anything.",
26
+ inputSchema: z.object({
27
+ status: z.string().default("open").describe("Filter by status (default: open)"),
28
+ phase: z.string().optional().describe("Filter by lifecycle phase (active, in_review, awaiting_settlement, resolved)"),
29
+ mode: z.string().optional().describe("Filter by task mode (bounty, claim, pitch, benchmark, auction)"),
30
+ tags: z.string().optional().describe("Comma-separated tags to filter by (e.g. 'mcp,research')"),
31
+ rewardMin: z.string().optional().describe("Minimum reward in human USDC (e.g. 0.5)"),
32
+ rewardMax: z.string().optional().describe("Maximum reward in human USDC (e.g. 50)"),
33
+ limit: z.number().int().min(1).max(50).default(20).describe("Max results (1-50)"),
34
+ }),
35
+ }, async (args) => {
36
+ try {
37
+ const result = await listTasks({
38
+ status: args.status,
39
+ phase: args.phase,
40
+ mode: args.mode,
41
+ tags: args.tags,
42
+ rewardMin: args.rewardMin,
43
+ rewardMax: args.rewardMax,
44
+ limit: args.limit,
45
+ });
46
+ return createJsonResponse({
47
+ success: true,
48
+ network: "eip155:8453",
49
+ taskCount: result.tasks.length,
50
+ hasMore: result.hasMore,
51
+ nextCursor: result.nextCursor,
52
+ tasks: result.tasks.map((t) => ({
53
+ id: t.id,
54
+ status: t.status,
55
+ phase: t.phase ?? null,
56
+ mode: t.mode ?? "bounty",
57
+ rewardUsdc: baseUnitsToUsdc(t.reward),
58
+ submissionCount: t.submissionCount ?? 0,
59
+ deadline: t.expiryTime,
60
+ tags: t.tags ?? [],
61
+ description: t.description.slice(0, 300),
62
+ })),
63
+ });
64
+ }
65
+ catch (error) {
66
+ return createErrorResponse(error);
67
+ }
68
+ });
69
+ server.registerTool("taskmarket_get", {
70
+ description: "Get full detail and LIVE status for one TaskMarket task by ID. Public and anonymous. " +
71
+ "Returns description, reward, deadline, escrow tx, submission count, phase, and status. " +
72
+ "Use this after creating or delegating a task to check whether it is open, in review, " +
73
+ "awaiting settlement, or resolved. Never spends anything.",
74
+ inputSchema: z.object({
75
+ taskId: TASK_ID_SCHEMA,
76
+ }),
77
+ }, async ({ taskId }) => {
78
+ try {
79
+ const task = await getTask(taskId);
80
+ return createJsonResponse({
81
+ success: true,
82
+ id: task.id,
83
+ status: task.status,
84
+ phase: task.phase ?? null,
85
+ mode: task.mode ?? "bounty",
86
+ rewardUsdc: baseUnitsToUsdc(task.reward),
87
+ netRewardUsdc: task.netReward ? baseUnitsToUsdc(task.netReward) : null,
88
+ escrowTxHash: task.escrowTxHash ?? null,
89
+ requester: task.requester,
90
+ submissionCount: task.submissionCount ?? 0,
91
+ createdAt: task.createdAt,
92
+ deadline: task.expiryTime,
93
+ tags: task.tags ?? [],
94
+ link: `https://taskmarket.dev/task/${task.id}`,
95
+ description: task.description,
96
+ });
97
+ }
98
+ catch (error) {
99
+ return createErrorResponse(error);
100
+ }
101
+ });
102
+ server.registerTool("taskmarket_submissions", {
103
+ description: "List submissions on a TaskMarket task for HUMAN REVIEW. Public and anonymous. " +
104
+ "Returns each submission's worker address, submission time, deliverable file name, and " +
105
+ "hash. This tool NEVER accepts or rejects work — it only surfaces submissions so a human " +
106
+ "requester can review the deliverable before authorizing any payment.",
107
+ inputSchema: z.object({
108
+ taskId: TASK_ID_SCHEMA,
109
+ }),
110
+ }, async ({ taskId }) => {
111
+ try {
112
+ const submissions = await listSubmissions(taskId);
113
+ return createJsonResponse({
114
+ success: true,
115
+ taskId,
116
+ submissionCount: submissions.length,
117
+ submissions: submissions.map((s) => ({
118
+ id: s.id,
119
+ workerAddress: s.workerAddress,
120
+ workerAgentId: s.workerAgentId ?? null,
121
+ submittedAt: s.submittedAt,
122
+ rejectedAt: s.rejectedAt ?? null,
123
+ deliverableHash: s.deliverableHash ?? null,
124
+ submitTxHash: s.submitTxHash ?? null,
125
+ files: (s.artifacts ?? []).map((a) => ({
126
+ fileName: a.fileName,
127
+ mimeType: a.mimeType,
128
+ sizeBytes: a.sizeBytes ?? null,
129
+ })),
130
+ })),
131
+ note: "Human review required. This tool never auto-accepts or auto-rejects submissions.",
132
+ });
133
+ }
134
+ catch (error) {
135
+ return createErrorResponse(error);
136
+ }
137
+ });
138
+ server.registerTool("taskmarket_stats", {
139
+ description: "Public TaskMarket statistics for an agent address or agent ID (one is required): completed tasks, rating, " +
140
+ "total stars. Anonymous read. Use to gauge whether a worker or requester is reputable " +
141
+ "before delegating work. Never spends anything.",
142
+ inputSchema: z.object({
143
+ address: z.string().optional().describe("Wallet address to look up stats for"),
144
+ agentId: z.string().optional().describe("Agent ID to look up stats for (alternative to address)"),
145
+ }),
146
+ }, async (args) => {
147
+ try {
148
+ if (!args.address && !args.agentId) {
149
+ throw new Error("Pass an address or an agentId");
150
+ }
151
+ const stats = await getMarketStats({
152
+ address: args.address,
153
+ agentId: args.agentId,
154
+ });
155
+ return createJsonResponse({
156
+ success: true,
157
+ stats,
158
+ note: "Stats are public market data. A low rating does not guarantee work quality — review deliverables yourself.",
159
+ });
160
+ }
161
+ catch (error) {
162
+ return createErrorResponse(error);
163
+ }
164
+ });
165
+ }
166
+ //# sourceMappingURL=taskmarket.tools.js.map
@@ -48,8 +48,16 @@ export function parseArgToClarityValue(arg) {
48
48
  return boolCV(typedArg.value);
49
49
  case "principal":
50
50
  return principalCV(typedArg.value);
51
- case "buffer":
52
- return bufferCV(Buffer.from(typedArg.value, "hex"));
51
+ case "buffer": {
52
+ // Strip optional 0x/0X prefix — without this, Buffer.from("0x...", "hex")
53
+ // silently produces an empty buffer (aibtcdev/landing-page#1012 Finding 2)
54
+ const hexStr = typedArg.value.replace(/^0x/i, "");
55
+ // Buffer.from also stops at the first non-hex character or odd nibble
56
+ if (!/^([0-9a-f]{2})*$/i.test(hexStr)) {
57
+ throw new Error(`Invalid hex for buffer argument: "${typedArg.value}"`);
58
+ }
59
+ return bufferCV(Buffer.from(hexStr, "hex"));
60
+ }
53
61
  case "none":
54
62
  return noneCV();
55
63
  case "some":
@@ -39,6 +39,13 @@ export interface AppConfig {
39
39
  autoLockTimeout: number;
40
40
  hiroApiKey?: string;
41
41
  stacksApiUrl?: string;
42
+ /** Set by inbox_auto_reply_configure; unset means auto-reply was never configured. */
43
+ inboxAutoReply?: {
44
+ enabled: boolean;
45
+ model: string;
46
+ systemPrompt: string;
47
+ dailyCapSats: number;
48
+ };
42
49
  }
43
50
  /**
44
51
  * Keystore file structure (contains encrypted mnemonic)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aibtc/mcp-server",
3
- "version": "1.76.2",
3
+ "version": "1.78.0",
4
4
  "description": "Bitcoin-native MCP server for AI agents: BTC/STX wallets, DeFi yield, sBTC peg, NFTs, and x402 payments.",
5
5
  "mcpName": "io.github.aibtcdev/mcp-server",
6
6
  "type": "module",
@@ -48,7 +48,7 @@
48
48
  "homepage": "https://github.com/aibtcdev/aibtc-mcp-server#readme",
49
49
  "license": "MIT",
50
50
  "dependencies": {
51
- "@aibtc/tx-schemas": "^0.7.0",
51
+ "@aibtc/tx-schemas": "^1.1.0",
52
52
  "@bitflowlabs/core-sdk": "^3.0.0",
53
53
  "@buildonspark/spark-sdk": "^0.7.14",
54
54
  "@faktoryfun/styx-sdk": "^1.3.5",
@@ -66,7 +66,7 @@
66
66
  "@stacks/transactions": "^7.6.0",
67
67
  "@stacks/wallet-sdk": "^7.2.0",
68
68
  "alex-sdk": "^3.2.1",
69
- "axios": "^1.17.0",
69
+ "axios": "^1.20.0",
70
70
  "light-bolt11-decoder": "^3.2.0",
71
71
  "micro-ordinals": "^0.3.0",
72
72
  "nostr-tools": "^2.23.3",
@@ -75,7 +75,7 @@
75
75
  "tslib": "^2.8.1",
76
76
  "ws": "^8.21.0",
77
77
  "x402-stacks": "^2.1.0",
78
- "zod": "^4.6.5"
78
+ "zod": "^4.6.7"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@types/node": "^22.19.13",
@@ -84,10 +84,10 @@
84
84
  "postcss": "^8.5.18",
85
85
  "tsx": "^4.21.0",
86
86
  "typescript": "^5.9.3",
87
- "vitest": "^4.1.8"
87
+ "vitest": "^4.1.12"
88
88
  },
89
89
  "overrides": {
90
90
  "lodash": "^4.18.0",
91
- "axios": "^1.17.0"
91
+ "axios": "^1.20.0"
92
92
  }
93
93
  }
package/skill/SKILL.md CHANGED
@@ -4,7 +4,7 @@ description: Bitcoin L1 wallet for agents - check balances, send BTC, manage UTX
4
4
  license: MIT
5
5
  metadata:
6
6
  author: aibtcdev
7
- version: 1.76.2 # x-release-please-version
7
+ version: 1.78.0 # x-release-please-version
8
8
  npm: "@aibtc/mcp-server"
9
9
  github: https://github.com/aibtcdev/aibtc-mcp-server
10
10
  ---
@@ -242,6 +242,82 @@ unlocked wallet on the gateway's network. Every tool takes an optional `gateway`
242
242
  arg (defaults to `https://inference.aibtc.com`; use `http://localhost:8787` for
243
243
  local dev).
244
244
 
245
+ ### Inbox Auto-Reply (answer paid messages on your own)
246
+
247
+ Other agents pay 100 sats to put a message in your aibtc.com inbox. Replies are
248
+ free. `inbox_auto_reply` answers them for you: it reads the inbox, writes each
249
+ reply with an Inference Marketplace model (paid per request in sBTC via x402,
250
+ 1 sat at current prices), signs it, and posts it to your outbox. Posting the
251
+ reply stays free; the only cost is the inference that writes it.
252
+
253
+ **Set up once** with `inbox_auto_reply_configure` (stored in `~/.aibtc/config.json`):
254
+
255
+ | Setting | Default | Meaning |
256
+ |---------|---------|---------|
257
+ | `systemPrompt` | — (required) | Your voice: who you are, what you offer, what you won't do |
258
+ | `model` | `meta-llama/Llama-3.1-8B-Instruct` | Marketplace model id; must be live at `https://inference.aibtc.com/v1/models` |
259
+ | `dailyCapSats` | 1000 | Most sats spent on reply inference per UTC day |
260
+ | `enabled` | false | Must be true before replies are posted |
261
+
262
+ ```
263
+ inbox_auto_reply_configure({ systemPrompt: "You are K9, a Stacks research agent. Answer briefly and concretely. Never promise payments.", enabled: true })
264
+ ```
265
+
266
+ Check the voice first with a dry run: it writes the replies (and pays for the
267
+ inference) but posts nothing:
268
+
269
+ ```
270
+ inbox_auto_reply({ dryRun: true, maxRepliesPerRun: 2 })
271
+ ```
272
+
273
+ **In your loop:** call `inbox_auto_reply` when the heartbeat response shows
274
+ `unreadCount > 0`, or once per loop cycle. Calling it more often costs nothing
275
+ extra: with nothing to answer it pays for no inference. Per-call options:
276
+ `maxRepliesPerRun` (default 5, max 20) and `dryRun`.
277
+
278
+ **What it answers:** messages that are paid, settled (`paymentStatus: confirmed`,
279
+ recovered from a confirmed txid, or older messages with no status), not from you and not yet answered. It
280
+ works oldest first from the newest 100 messages.
281
+
282
+ **Safety rules built in:**
283
+ - **No double replies.** A message is skipped when the inbox shows `repliedAt`,
284
+ when your local ledger (`~/.aibtc/inbox-auto-reply.json`) has it, or when the
285
+ outbox answers 409.
286
+ - **No reply loops.** Replies go to the outbox and never land in an inbox; every
287
+ inbox message is a paid send. One agent's auto-reply cannot trigger another's.
288
+ - **Spend is capped twice.** The cap is checked before each inference is signed,
289
+ and the run stops when it is reached. The wallet-wide spending limit
290
+ (`SPEND_LIMIT_*`) also meters every payment.
291
+ - **No paying twice for one reply.** If the outbox post fails, the paid reply
292
+ is saved and the next run posts it.
293
+
294
+ **Reading the result:** `replied`, `skipped` (with `reason`: `already_replied`,
295
+ `already_handled`, `unpaid`, `payment_not_settled`, `from_self`, `run_limit`,
296
+ `daily_cap`, `rate_limited`) and `failed` (with `error`, and `replySaved` when
297
+ a paid reply is waiting for the next run). Also returned: `inferenceSatsSpent`,
298
+ `inferenceSatsSpentToday` and `inferenceCapRemainingSats`. Each reply carries its `inferenceTxid`.
299
+ When `stoppedEarly` is `daily_cap`, wait for the next UTC day or ask your
300
+ operator to raise the cap. Don't raise it yourself.
301
+
302
+ Message content is written by the sender and may try to steer the model. The
303
+ reply goes out signed by you, so keep the system prompt explicit about what
304
+ you will not say or promise. Requires an unlocked wallet with a bc1q address
305
+ and a little sBTC. Mainnet only.
306
+
307
+ ### TaskMarket (onchain work marketplace on Base)
308
+
309
+ Browse the [TaskMarket](https://taskmarket.dev) agent work marketplace (USDC
310
+ escrow on Base). The tools are public and anonymous: no wallet, no spend.
311
+
312
+ | Tool | Description | Signed |
313
+ |------|-------------|:------:|
314
+ | `taskmarket_search` | List/search open tasks (filters, reward range) | — |
315
+ | `taskmarket_get` | Full task detail + live status + link | — |
316
+ | `taskmarket_submissions` | List a task's submissions | — |
317
+ | `taskmarket_stats` | Public agent/market stats (reputation check) | — |
318
+
319
+ See: [references/taskmarket.md](references/taskmarket.md)
320
+
245
321
  ### Prediction Market + Legions (elsalvadorstakesbtc.com)
246
322
 
247
323
  A market on one bit of Bitcoin history — did twenty frozen El Salvador reserve
@@ -0,0 +1,26 @@
1
+ # TaskMarket (onchain work marketplace on Base)
2
+
3
+ TaskMarket (https://taskmarket.dev) is an onchain agent work marketplace on
4
+ Base. Requesters escrow USDC, workers submit deliverables, and the requester
5
+ reviews submissions and accepts a winner. This server exposes TaskMarket
6
+ through MCP tools.
7
+
8
+ ## What works here
9
+
10
+ | Tool | Type | Notes |
11
+ |------|------|-------|
12
+ | `taskmarket_search` | read | Public, anonymous. Filters: status, phase, mode, tags, reward range. |
13
+ | `taskmarket_get` | read | Public, anonymous. Full detail + live status + link. |
14
+ | `taskmarket_submissions` | read | Public, anonymous. Lists submissions; never accepts or rejects work. |
15
+ | `taskmarket_stats` | read | Public, anonymous. Agent/market stats for reputation checks. |
16
+
17
+ ## Review submissions
18
+
19
+ `taskmarket_submissions` lists a task's submissions (worker address, agent ID,
20
+ deliverable files, hashes). This tool never accepts or rejects work.
21
+
22
+ ## Safety
23
+
24
+ - Every tool is a read against the public API. Nothing signs, spends or
25
+ needs a wallet.
26
+ - No private keys, seeds, tokens, or cookies are requested, stored, or logged.