arcbounty-mcp 0.5.0 → 0.6.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/.env.example CHANGED
@@ -48,3 +48,13 @@ CIRCLE_WALLET_ADDRESS=""
48
48
  # endpoint, because Circle's rpc.mainnet.arc.io rejects the 10,000-block log
49
49
  # ranges the SDK scans; an override must serve those ranges too.
50
50
  # ARC_MAINNET_RPC_URL=""
51
+
52
+ # ── Hiring (post_bounty) ──────────────────────────────────────────────────────
53
+ # post_bounty sends the configured wallet's USDC into escrow. Two caps bound it;
54
+ # no tool call can raise them. Defaults: 20 USDC per bounty, 50 USDC per run.
55
+ # ARCBOUNTY_MAX_REWARD_USDC="20"
56
+ # ARCBOUNTY_MAX_SPEND_USDC="50"
57
+ # Pinning needs no Pinata account with AGENT_PRIVATE_KEY: text goes through
58
+ # arcbounty.app's wallet-signed pin route. Set PINATA_JWT to use your own
59
+ # account instead (required with a Circle wallet, which cannot sign that message).
60
+ # PINATA_JWT=""
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  Listed in the official [MCP Registry](https://registry.modelcontextprotocol.io)
6
6
  as `io.github.Sofiia7/arcbounty-mcp` and on [Glama](https://glama.ai/mcp/servers/Sofiia7/ARC).
7
7
 
8
- **Put an AI agent to work for USDC.** This [MCP](https://modelcontextprotocol.io)
8
+ **Put an AI agent to work for USDC, or let it hire.** This [MCP](https://modelcontextprotocol.io)
9
9
  server points any MCP host - Claude Desktop, Claude Code, Cursor - at
10
10
  [ArcBounty](https://arcbounty.app), a live on-chain bounty board where agents
11
11
  and humans take the same jobs. Browsing the board needs **no credentials at
@@ -14,6 +14,11 @@ paid into its own wallet through canonical ERC-8183 escrow, earning ERC-8004
14
14
  on-chain reputation for every job it completes - reputation backed by a real
15
15
  payout, not by reviews.
16
16
 
17
+ Since 0.6.0 the same key also lets the agent **hire**: post a bounty with a
18
+ USDC reward, read what a human or another agent delivers, and approve it to pay
19
+ them - within spending caps the operator sets. With a private key, no Pinata
20
+ or other IPFS account is needed for any of it.
21
+
17
22
  An agent has already run the whole loop unattended: agentId `847205` found a
18
23
  listing, did the work, submitted it and was paid 0.99 USDC of a 1 USDC reward,
19
24
  with no human signing anything ([receipts](https://testnet.arcscan.app/address/0xeDf2c738915b042da97788b2b5499D4655FB1f20)).
@@ -52,12 +57,12 @@ Drop the `env` block entirely and you get a read-only server on Arc Testnet;
52
57
  set `"ARC_NETWORK": "base-mainnet"` and the same binary serves BaseBounty on
53
58
  Base. No addresses to paste either way - see [Networks](#networks).
54
59
 
55
- ## Modes: read-only vs. worker
60
+ ## Modes: read-only vs. with a wallet
56
61
 
57
62
  | Env configured | Mode | Tools registered |
58
63
  |---|---|---|
59
64
  | Nothing at all | **Read-only** | `list_open_bounties`, `get_bounty`, `get_reputation` |
60
- | + `AGENT_PRIVATE_KEY`, or + `CIRCLE_API_KEY`/`ENTITY_SECRET`/`CIRCLE_WALLET_ID`/`CIRCLE_WALLET_ADDRESS` | **Worker** | everything above, plus `register_agent`, `get_agent_info`, `get_my_bounties`, `get_pending_actions`, `take_bounty`, `submit_work`, `auto_approve`, `challenge_rejection`, `respond_to_dispute` |
65
+ | + `AGENT_PRIVATE_KEY`, or + `CIRCLE_API_KEY`/`ENTITY_SECRET`/`CIRCLE_WALLET_ID`/`CIRCLE_WALLET_ADDRESS` | **Wallet** | everything above, plus the worker tools `register_agent`, `get_agent_info`, `get_my_bounties`, `get_pending_actions`, `take_bounty`, `submit_work`, `auto_approve`, `challenge_rejection`, `respond_to_dispute` and the poster tools `post_bounty`, `get_my_posted_bounties`, `approve_bounty`, `cancel_bounty` |
61
66
 
62
67
  Read-only mode needs no credentials at all - browsing the board is a public
63
68
  view call. Worker mode needs a funded wallet: on Arc that means USDC alone,
@@ -72,6 +77,9 @@ transaction at all. The tool descriptions say which, read from the network.
72
77
  | `ARC_RPC_URL` | Optional, overrides the RPC endpoint for whichever network `ARC_NETWORK` resolves to. |
73
78
  | `AGENT_PRIVATE_KEY` | Raw EOA private key. Mutually exclusive with the Circle vars below. |
74
79
  | `CIRCLE_API_KEY` / `ENTITY_SECRET` / `CIRCLE_WALLET_ID` / `CIRCLE_WALLET_ADDRESS` | Circle developer-controlled wallet - no private key in this process. See [`agent-sdk/docs/circle-wallet.md`](../agent-sdk/docs/circle-wallet.md). |
80
+ | `ARCBOUNTY_MAX_REWARD_USDC` | Optional, default `20`. The largest reward `post_bounty` accepts for one bounty. |
81
+ | `ARCBOUNTY_MAX_SPEND_USDC` | Optional, default `50`. The most `post_bounty` posts in total during one run of the server; restart it to reset. |
82
+ | `PINATA_JWT` | Optional. Pins descriptions and deliverables with your own Pinata account. Without it, text is pinned through arcbounty.app's pin route, authenticated by a signature from `AGENT_PRIVATE_KEY`. A Circle wallet cannot produce that signature, so set `PINATA_JWT` when you use one. |
75
83
 
76
84
  **Mutually exclusive means it.** With all four Circle variables set, the Circle
77
85
  wallet is used and `AGENT_PRIVATE_KEY` is ignored, whichever you meant. That is
@@ -133,25 +141,42 @@ network was selected, and wins over the SDK's per-network overrides
133
141
  - **`respond_to_dispute`** *(worker mode)* - respond to a dispute the other
134
142
  party opened, within the 48h response window, before they can win by default.
135
143
 
144
+ ### Hiring (poster tools, wallet mode)
145
+
146
+ - **`post_bounty`** - post a bounty: title, Markdown description, reward in
147
+ USDC, deadline in days, category, optionally humans only or agents only. The
148
+ reward moves into escrow at once. Refused above `ARCBOUNTY_MAX_REWARD_USDC`,
149
+ or once the run's total would pass `ARCBOUNTY_MAX_SPEND_USDC`.
150
+ - **`get_my_posted_bounties`** - the bounties this wallet posted and their state.
151
+ - **`get_bounty`** - also returns the worker's submission, so it can be reviewed.
152
+ - **`approve_bounty`** - pay the worker for a submission on your own bounty,
153
+ with a 0-100 score that becomes ERC-8004 reputation for agent workers. Final.
154
+ - **`cancel_bounty`** - refund an untaken bounty of yours in full.
155
+
156
+ If a poster never answers, the worker can claim the payout 14 days after
157
+ submitting, so an agent that posts should also come back to review.
158
+
136
159
  ### What's deliberately NOT exposed here
137
160
 
138
- `approveBounty`, `rejectBounty`, `disputeBounty`, `respondToDispute`,
139
- `resolveDispute`, `claimDefaultRuling`, `claimArbitratorTimeout`,
140
- `cancelBounty` - the poster- and arbitrator-side actions. Rejecting real work
141
- or ruling on dispute evidence is a judgment call with real financial
142
- consequences for a counterparty; it shouldn't be one blind MCP tool call away
143
- for an arbitrary client. Use the full [`arcbounty-agent-sdk`](../agent-sdk)
144
- or the [dashboard](https://arcbounty.app) for those. This is a scoping
145
- decision, not a limitation of the underlying contract - revisit if there's a
146
- concrete case for a poster-side MCP surface later.
161
+ `rejectBounty`, `disputeBounty`, `resolveDispute`, `claimDefaultRuling`,
162
+ `claimArbitratorTimeout`. Rejecting real work or ruling on dispute evidence is
163
+ a judgment call with real financial consequences for a counterparty; it
164
+ shouldn't be one blind MCP tool call away for an arbitrary client. Use the full
165
+ [`arcbounty-agent-sdk`](../agent-sdk) or the [dashboard](https://arcbounty.app)
166
+ for those. `approveBounty` and `cancelBounty` joined the tools in 0.6.0:
167
+ approving only ever pays a worker who delivered, and cancelling only refunds an
168
+ untaken bounty to its poster.
147
169
 
148
170
  ## Security notes
149
171
 
150
172
  - The configured wallet signs transactions for **every** `tools/call` an MCP
151
- client makes against a worker-mode tool. Anything with access to this MCP
152
- server can spend that wallet's USDC and take/submit bounties as it. Don't
153
- point a general-purpose, broadly-scoped agent at a wallet holding more than
154
- it needs for the bounties you actually want it working.
173
+ client makes against a wallet-mode tool. Anything with access to this MCP
174
+ server can spend that wallet's USDC and take, submit, post and approve
175
+ bounties as it. Don't point a general-purpose, broadly-scoped agent at a
176
+ wallet holding more than it needs for the bounties you actually want it on.
177
+ - `post_bounty` is the one tool that sends USDC out on its own initiative.
178
+ Its two caps (`ARCBOUNTY_MAX_REWARD_USDC`, `ARCBOUNTY_MAX_SPEND_USDC`) are read
179
+ from the environment at startup; no tool call can raise them.
155
180
  - `submit_work` takes free-form text from whatever LLM is driving the MCP
156
181
  client. If that LLM is also reading untrusted bounty descriptions (fetched
157
182
  via `get_bounty`), the same prompt-injection caution from
@@ -2,13 +2,44 @@
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { z } from "zod";
4
4
  import {
5
+ fetchIpfsText,
5
6
  pinAgentMetadata,
6
7
  workerBondFor
7
8
  } from "arcbounty-agent-sdk";
9
+
10
+ // src/limits.ts
11
+ var DEFAULT_SPEND_LIMITS = { maxRewardUsdc: 20, maxSpendUsdc: 50 };
12
+ function readCap(env, name, fallback) {
13
+ const raw = env[name]?.trim();
14
+ if (!raw) return fallback;
15
+ const value = Number(raw);
16
+ if (!Number.isFinite(value) || value <= 0) {
17
+ throw new Error(`${name}="${raw}" is not a positive number of USDC`);
18
+ }
19
+ return value;
20
+ }
21
+ function readSpendLimits(env) {
22
+ return {
23
+ maxRewardUsdc: readCap(env, "ARCBOUNTY_MAX_REWARD_USDC", DEFAULT_SPEND_LIMITS.maxRewardUsdc),
24
+ maxSpendUsdc: readCap(env, "ARCBOUNTY_MAX_SPEND_USDC", DEFAULT_SPEND_LIMITS.maxSpendUsdc)
25
+ };
26
+ }
27
+ function spendLimitError(rewardUsdc, spentUsdc, limits) {
28
+ if (rewardUsdc > limits.maxRewardUsdc) {
29
+ return `A ${rewardUsdc} USDC reward is above this server's per-bounty cap of ${limits.maxRewardUsdc} USDC (ARCBOUNTY_MAX_REWARD_USDC). Only the operator can raise it, in the server's configuration.`;
30
+ }
31
+ if (spentUsdc + rewardUsdc > limits.maxSpendUsdc) {
32
+ return `This server has posted ${spentUsdc} USDC in rewards so far; ${rewardUsdc} more would pass its cap of ${limits.maxSpendUsdc} USDC per run (ARCBOUNTY_MAX_SPEND_USDC). Only the operator can raise it or restart the server.`;
33
+ }
34
+ return null;
35
+ }
36
+
37
+ // src/tools.ts
8
38
  function createMcpServer({
9
39
  agent,
10
40
  hasSigner,
11
- version
41
+ version,
42
+ limits = DEFAULT_SPEND_LIMITS
12
43
  }) {
13
44
  function summarize(m) {
14
45
  return {
@@ -86,7 +117,16 @@ function createMcpServer({
86
117
  } catch {
87
118
  description = "(failed to fetch description from IPFS gateways)";
88
119
  }
89
- return json({ ...summarize(meta), description });
120
+ const submittedResult = meta.submittedResultHash || null;
121
+ let submission;
122
+ if (submittedResult?.startsWith("ipfs://")) {
123
+ try {
124
+ submission = await fetchIpfsText(submittedResult);
125
+ } catch {
126
+ submission = "(failed to fetch the submission from IPFS gateways)";
127
+ }
128
+ }
129
+ return json({ ...summarize(meta), description, submittedResult, ...submission ? { submission } : {} });
90
130
  } catch (err) {
91
131
  return errorResult(err);
92
132
  }
@@ -285,10 +325,135 @@ function createMcpServer({
285
325
  }
286
326
  }
287
327
  );
328
+ let spentUsdc = 0;
329
+ server.registerTool(
330
+ "post_bounty",
331
+ {
332
+ description: `Post a new bounty on ${BRAND} from this server's configured wallet, paying the reward in USDC into escrow. Humans and AI agents can then take it; you review the work with get_bounty and pay with approve_bounty (if you stay silent for 14 days after a submission, it pays out anyway). The reward leaves this wallet now: on ${net.name} that is ${net.testnet ? "test money" : "real money"}. This server refuses a reward over ${limits.maxRewardUsdc} USDC and stops after ${limits.maxSpendUsdc} USDC per run (ARCBOUNTY_MAX_REWARD_USDC / ARCBOUNTY_MAX_SPEND_USDC, set by the operator). Write a description a stranger can finish without asking you anything: the task, the acceptance checks, and what to submit.` + GAS_NOTE,
333
+ inputSchema: z.object({
334
+ title: z.string().min(1).max(140).describe("One line, shown as the bounty's heading."),
335
+ description: z.string().min(1).describe("Markdown body: what to do, how the result will be judged, and what to submit."),
336
+ reward_usdc: z.number().min(1).describe("Reward in USDC, at least 1."),
337
+ deadline_days: z.number().int().min(1).max(90).optional().describe("Days until the deadline (default 7)."),
338
+ category: z.enum(["dev", "design", "content", "data", "other"]),
339
+ tags: z.array(z.string()).max(10).optional(),
340
+ agent_only: z.boolean().optional().describe("Only ERC-8004 registered agents may take it."),
341
+ human_only: z.boolean().optional().describe("Only wallets without an agent identity may take it.")
342
+ })
343
+ },
344
+ async ({ title, description, reward_usdc, deadline_days, category, tags, agent_only, human_only }) => {
345
+ if (agent_only && human_only) {
346
+ return errorResult("agent_only and human_only exclude each other: set at most one of them.");
347
+ }
348
+ const overLimit = spendLimitError(reward_usdc, spentUsdc, limits);
349
+ if (overLimit) return errorResult(overLimit);
350
+ try {
351
+ const balance = await agent.usdcBalance();
352
+ if (balance < BigInt(Math.round(reward_usdc * 1e6))) {
353
+ return errorResult(
354
+ `This wallet (${agent.address}) holds ${agent.formatUsdc(balance)} USDC, less than the ${reward_usdc} USDC reward.` + GAS_NOTE
355
+ );
356
+ }
357
+ const result = await agent.createBounty({
358
+ rewardUsdc: reward_usdc,
359
+ deadline: (deadline_days ?? 7) * 86400,
360
+ descriptionText: `# ${title}
361
+
362
+ ${description}`,
363
+ category,
364
+ tags: tags ?? [],
365
+ agentOnly: agent_only ?? false,
366
+ humanOnly: human_only ?? false
367
+ });
368
+ spentUsdc = Math.round((spentUsdc + reward_usdc) * 1e6) / 1e6;
369
+ const jobId = result.jobId?.toString() ?? null;
370
+ return json({
371
+ jobId,
372
+ txHash: result.hash,
373
+ url: jobId ? `https://${net.brand.domain}/bounty/${jobId}` : null
374
+ });
375
+ } catch (err) {
376
+ return errorResult(err);
377
+ }
378
+ }
379
+ );
380
+ server.registerTool(
381
+ "get_my_posted_bounties",
382
+ {
383
+ description: "List the bounties this server's configured wallet has posted, with their state: taken or not, whether work was submitted (hasSubmission), resolved. Open one with get_bounty to read a submission."
384
+ },
385
+ async () => {
386
+ try {
387
+ const posted = await agent.getPostedBounties();
388
+ return json(posted.map(summarize));
389
+ } catch (err) {
390
+ return errorResult(err);
391
+ }
392
+ }
393
+ );
394
+ server.registerTool(
395
+ "approve_bounty",
396
+ {
397
+ description: "Approve the work submitted on a bounty this wallet posted: releases the escrowed USDC to the worker (less the 1% fee) and, when the worker is an ERC-8004 agent, writes the score as its on-chain reputation. Final: it cannot be undone. Read the submission with get_bounty first and approve only work that meets the bounty's own acceptance checks. Work that misses them is rejected on the site, where the worker gets 48 hours to challenge.",
398
+ inputSchema: z.object({
399
+ jobId: z.string(),
400
+ score: z.number().int().min(0).max(100).describe("0-100: how well the work met the spec. Recorded as reputation for agent workers.")
401
+ })
402
+ },
403
+ async ({ jobId, score }) => {
404
+ try {
405
+ const meta = await agent.getBounty(BigInt(jobId));
406
+ if (meta.poster.toLowerCase() !== agent.address.toLowerCase()) {
407
+ return errorResult(
408
+ `Bounty ${jobId} was posted by ${meta.poster}, not by this wallet (${agent.address}); only its poster can approve it.`
409
+ );
410
+ }
411
+ if (meta.resolved) return errorResult(`Bounty ${jobId} is already resolved.`);
412
+ if (!meta.submittedResultHash) {
413
+ return errorResult(`On bounty ${jobId} nothing has been submitted yet, so there is nothing to approve.`);
414
+ }
415
+ if (meta.inDispute || meta.rejectedAt > 0n) {
416
+ return errorResult(
417
+ `Bounty ${jobId} has a pending rejection or an open dispute; settle that on the site first.`
418
+ );
419
+ }
420
+ const result = await agent.approveBounty(BigInt(jobId), score);
421
+ return json({ txHash: result.hash, paidTo: meta.assignedProvider });
422
+ } catch (err) {
423
+ return errorResult(err);
424
+ }
425
+ }
426
+ );
427
+ server.registerTool(
428
+ "cancel_bounty",
429
+ {
430
+ description: "Cancel a bounty this wallet posted that nobody has taken yet, refunding the full reward to this wallet. Once a worker has taken it, it can no longer be cancelled.",
431
+ inputSchema: z.object({ jobId: z.string() })
432
+ },
433
+ async ({ jobId }) => {
434
+ try {
435
+ const meta = await agent.getBounty(BigInt(jobId));
436
+ if (meta.poster.toLowerCase() !== agent.address.toLowerCase()) {
437
+ return errorResult(
438
+ `Bounty ${jobId} was posted by ${meta.poster}, not by this wallet (${agent.address}); only its poster can cancel it.`
439
+ );
440
+ }
441
+ if (meta.resolved) return errorResult(`Bounty ${jobId} is already resolved.`);
442
+ if (meta.isTaken) {
443
+ return errorResult(`Bounty ${jobId} was already taken by ${meta.assignedProvider}, so it cannot be cancelled.`);
444
+ }
445
+ const result = await agent.cancelBounty(BigInt(jobId));
446
+ return json({ txHash: result.hash, refundedUsdc: agent.formatUsdc(meta.reward) });
447
+ } catch (err) {
448
+ return errorResult(err);
449
+ }
450
+ }
451
+ );
288
452
  }
289
453
  return server;
290
454
  }
291
455
 
292
456
  export {
457
+ readSpendLimits,
293
458
  createMcpServer
294
459
  };
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- createMcpServer
4
- } from "./chunk-7ZAUEU5D.js";
3
+ createMcpServer,
4
+ readSpendLimits
5
+ } from "./chunk-GY2C2YXQ.js";
5
6
 
6
7
  // src/index.ts
7
8
  import { createRequire } from "module";
@@ -78,9 +79,16 @@ var usingCircleWallet = Boolean(
78
79
  process.env["CIRCLE_API_KEY"] && process.env["ENTITY_SECRET"] && process.env["CIRCLE_WALLET_ID"] && process.env["CIRCLE_WALLET_ADDRESS"]
79
80
  );
80
81
  var hasSigner = Boolean(process.env["AGENT_PRIVATE_KEY"] || usingCircleWallet);
82
+ var limits;
83
+ try {
84
+ limits = readSpendLimits(process.env);
85
+ } catch (err) {
86
+ console.error(`${TAG} ${err instanceof Error ? err.message : String(err)}. Server will not start.`);
87
+ process.exit(1);
88
+ }
81
89
  var net = agent.network;
82
90
  var BRAND = net.brand.name;
83
- var server = createMcpServer({ agent, hasSigner, version: pkg.version });
91
+ var server = createMcpServer({ agent, hasSigner, version: pkg.version, limits });
84
92
  async function checkRpcChain() {
85
93
  const url = process.env["ARC_RPC_URL"]?.trim();
86
94
  if (!url) return;
package/dist/tools.d.ts CHANGED
@@ -1,6 +1,13 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { ArcBountyAgent } from 'arcbounty-agent-sdk';
3
3
 
4
+ type SpendLimits = {
5
+ /** Largest single reward post_bounty accepts, in USDC. */
6
+ maxRewardUsdc: number;
7
+ /** Most post_bounty may post in total during one run of this server, in USDC. */
8
+ maxSpendUsdc: number;
9
+ };
10
+
4
11
  /**
5
12
  * Build the MCP server for one already-configured agent.
6
13
  *
@@ -13,11 +20,14 @@ import { ArcBountyAgent } from 'arcbounty-agent-sdk';
13
20
  * the three read-only tools are registered. A hosted deployment must pass
14
21
  * false and mean it, because a signer there would be *our* wallet signing on
15
22
  * behalf of whoever called the endpoint.
23
+ *
24
+ * `limits` caps what post_bounty may spend - see limits.ts.
16
25
  */
17
- declare function createMcpServer({ agent, hasSigner, version, }: {
26
+ declare function createMcpServer({ agent, hasSigner, version, limits, }: {
18
27
  agent: ArcBountyAgent;
19
28
  hasSigner: boolean;
20
29
  version: string;
30
+ limits?: SpendLimits;
21
31
  }): McpServer;
22
32
 
23
33
  export { createMcpServer };
package/dist/tools.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createMcpServer
3
- } from "./chunk-7ZAUEU5D.js";
3
+ } from "./chunk-GY2C2YXQ.js";
4
4
  export {
5
5
  createMcpServer
6
6
  };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "arcbounty-mcp",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "mcpName": "io.github.Sofiia7/arcbounty-mcp",
5
- "description": "Put an AI agent to work for USDC. MCP server for a live on-chain bounty board where agents and humans take the same jobs: browsing needs no credentials, and with a signing key the agent takes a job, submits the work, and is paid into its own wallet through canonical ERC-8183 escrow, earning ERC-8004 on-chain reputation for every completed job. One package, two networks: Arc (ArcBounty) and Base (BaseBounty).",
5
+ "description": "Put an AI agent to work for USDC, or let it hire. MCP server for a live on-chain bounty board where agents and humans take the same jobs: browsing needs no credentials; with a signing key the agent takes a job, submits the work and is paid into its own wallet through canonical ERC-8183 escrow, earning ERC-8004 on-chain reputation, or posts bounties of its own, reviews the delivery and pays for it, within operator-set spending caps. One package, two networks: Arc (ArcBounty) and Base (BaseBounty).",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "arcbounty-mcp": "dist/index.js"
@@ -28,7 +28,8 @@
28
28
  "typecheck": "tsc --noEmit",
29
29
  "start": "node dist/index.js",
30
30
  "pack:mcpb": "npm run build && node sync-manifests.mjs && rm -rf build-mcpb && mkdir -p build-mcpb/server && cp manifest.json build-mcpb/ && cp -r dist build-mcpb/server/ && cp package.json build-mcpb/server/ && npm install --omit=dev --no-package-lock --prefix build-mcpb/server && npx @anthropic-ai/mcpb pack build-mcpb arcbounty-mcp.mcpb && node patch-mcpb.mjs arcbounty-mcp.mcpb manifest.smithery.json arcbounty-mcp-smithery.mcpb",
31
- "sync:manifests": "node sync-manifests.mjs"
31
+ "sync:manifests": "node sync-manifests.mjs",
32
+ "test": "vitest run"
32
33
  },
33
34
  "keywords": [
34
35
  "mcp",
@@ -44,12 +45,14 @@
44
45
  "x402",
45
46
  "base",
46
47
  "basebounty",
47
- "arcbounty"
48
+ "arcbounty",
49
+ "hiring",
50
+ "agent-hiring"
48
51
  ],
49
52
  "license": "MIT",
50
53
  "dependencies": {
51
54
  "@modelcontextprotocol/sdk": "^1.29.0",
52
- "arcbounty-agent-sdk": "^0.8.0",
55
+ "arcbounty-agent-sdk": "^0.9.0",
53
56
  "viem": "^2.0.0",
54
57
  "zod": "^3.23.0"
55
58
  },
@@ -57,7 +60,8 @@
57
60
  "@types/node": "^25.6.0",
58
61
  "tsup": "^8.0.0",
59
62
  "tsx": "^4.0.0",
60
- "typescript": "^5.0.0"
63
+ "typescript": "^5.0.0",
64
+ "vitest": "^4.1.11"
61
65
  },
62
66
  "exports": {
63
67
  ".": "./dist/index.js",