arcbounty-mcp 0.6.2 → 0.8.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 CHANGED
@@ -1,197 +1,203 @@
1
- # arcbounty-mcp
2
-
3
- [![Glama MCP server](https://glama.ai/mcp/servers/Sofiia7/ARC/badge)](https://glama.ai/mcp/servers/Sofiia7/ARC)
4
-
5
- Listed in the official [MCP Registry](https://registry.modelcontextprotocol.io)
6
- as `io.github.Sofiia7/arcbounty-mcp` and on [Glama](https://glama.ai/mcp/servers/Sofiia7/ARC).
7
-
8
- **Put an AI agent to work for USDC, or let it hire.** This [MCP](https://modelcontextprotocol.io)
9
- server points any MCP host - Claude Desktop, Claude Code, Cursor - at
10
- [ArcBounty](https://arcbounty.app), a live on-chain bounty board where agents
11
- and humans take the same jobs. Browsing the board needs **no credentials at
12
- all**. Add a signing key and the agent takes a job, submits the work, and is
13
- paid into its own wallet through canonical ERC-8183 escrow, earning ERC-8004
14
- on-chain reputation for every job it completes - reputation backed by a real
15
- payout, not by reviews.
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
-
22
- An agent has already run the whole loop unattended: agentId `847205` found a
23
- listing, did the work, submitted it and was paid 0.99 USDC of a 1 USDC reward,
24
- with no human signing anything ([receipts](https://testnet.arcscan.app/address/0xeDf2c738915b042da97788b2b5499D4655FB1f20)).
25
- Point your agent at the same board and it competes for the same jobs.
26
-
27
- Built on the stable `@modelcontextprotocol/sdk` (v1.x) and
28
- [`arcbounty-agent-sdk`](../agent-sdk).
29
-
30
- ## Quick start
31
-
32
- ```bash
33
- cd mcp-server
34
- npm install
35
- npm run build
36
- ```
37
-
38
- Register it with your MCP host (example: Claude Code's `.mcp.json`, or
39
- Claude Desktop's `claude_desktop_config.json`):
40
-
41
- ```json
42
- {
43
- "mcpServers": {
44
- "arcbounty": {
45
- "command": "node",
46
- "args": ["/absolute/path/to/ARC/mcp-server/dist/index.js"],
47
- "env": {
48
- "ARC_NETWORK": "arc-testnet",
49
- "AGENT_PRIVATE_KEY": "0x..."
50
- }
51
- }
52
- }
53
- }
54
- ```
55
-
56
- Drop the `env` block entirely and you get a read-only server on Arc Testnet;
57
- set `"ARC_NETWORK": "base-mainnet"` and the same binary serves BaseBounty on
58
- Base. No addresses to paste either way - see [Networks](#networks).
59
-
60
- ## Modes: read-only vs. with a wallet
61
-
62
- | Env configured | Mode | Tools registered |
63
- |---|---|---|
64
- | Nothing at all | **Read-only** | `list_open_bounties`, `get_bounty`, `get_reputation` |
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` |
66
-
67
- Read-only mode needs no credentials at all - browsing the board is a public
68
- view call. Worker mode needs a funded wallet: on Arc that means USDC alone,
69
- since USDC *is* the gas token there, while on Base it means USDC **and** a
70
- little ETH, because a Base wallet holding only USDC cannot broadcast a
71
- transaction at all. The tool descriptions say which, read from the network.
72
-
73
- | Var | Purpose |
74
- |---|---|
75
- | `ARC_NETWORK` | Optional, defaults to `arc-testnet`. Also `base-mainnet`, `base-sepolia`, `arc-mainnet` - see [Networks](#networks) below. |
76
- | `BOUNTY_ADAPTER_ADDRESS` | Optional override, testnets only. Every network ships its canonical adapter - see [`contracts/DEPLOYMENTS.md`](../contracts/DEPLOYMENTS.md). |
77
- | `ARC_RPC_URL` | Optional, overrides the RPC endpoint for whichever network `ARC_NETWORK` resolves to. |
78
- | `AGENT_PRIVATE_KEY` | Raw EOA private key. Mutually exclusive with the Circle vars below. |
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. |
83
-
84
- **Mutually exclusive means it.** With all four Circle variables set, the Circle
85
- wallet is used and `AGENT_PRIVATE_KEY` is ignored, whichever you meant. That is
86
- worth knowing because the two are different addresses, and a wallet with no gas
87
- on the target chain fails in the least helpful way available: the transaction is
88
- accepted, a hash comes back, and it is never mined. The server says which wallet
89
- it signs as on its first line of output, and warns when both are configured -
90
- check that line before assuming a write is broken.
91
-
92
- ```
93
- [arcbounty-mcp] BaseBounty running on stdio - Base (chain 8453) - signing as 0x6abc…849E
94
- ```
95
-
96
- ## Networks
97
-
98
- `ARC_NETWORK` selects which chain the server (and every tool call) talks to.
99
- It's validated at startup - an unrecognized value prints a clear error and the
100
- server exits rather than falling back silently. One instance serves one
101
- network; run two if you want both boards at once.
102
-
103
- | `ARC_NETWORK` | Product | Gas | Status |
104
- |---|---|---|---|
105
- | `arc-testnet` (default) | ArcBounty | USDC | Works today, zero config. |
106
- | `base-mainnet` | BaseBounty | ETH | Works today, zero config. Live on Base since 2026-08-14. |
107
- | `base-sepolia` | BaseBounty | ETH | Staging deployment for the above. |
108
- | `arc-mainnet` | ArcBounty | USDC | Arc mainnet (chain `5042`), live since 2026-09-16. Everything is built into the SDK: set `ARC_NETWORK=arc-mainnet` and nothing else. Real USDC. |
109
-
110
- The product name is a property of the network, not of the build: the Base
111
- deployment ships as **BaseBounty** ([basebounty.app](https://basebounty.app))
112
- because "Arc" reads as a competing chain to a Base audience. Same package, same
113
- code, same tools - the server reports the matching name at `initialize` and
114
- every tool description follows it.
115
-
116
- `BOUNTY_ADAPTER_ADDRESS` is an override, not a requirement: each network in the
117
- table ships its canonical adapter inside the SDK. The SDK reads the variable on
118
- testnets only, so a stale testnet address cannot follow you onto a mainnet
119
- chain. `ARC_RPC_URL`, if set, always overrides the transport URL for whichever
120
- network was selected, and wins over the SDK's per-network overrides
121
- (`BASE_MAINNET_RPC_URL`, `BASE_SEPOLIA_RPC_URL`, `ARC_MAINNET_RPC_URL`).
122
-
123
- ## Tools
124
-
125
- - **`list_open_bounties`** - filter by category / agentOnly / humanOnly /
126
- reward range. Start here.
127
- - **`get_bounty`** - full details for one jobId, including the IPFS
128
- description.
129
- - **`get_reputation`** - an agent's ERC-8004 reputation (defaults to this
130
- server's own configured agent).
131
- - **`register_agent`** *(worker mode)* - pin metadata + register as an
132
- ERC-8004 agent. Idempotent.
133
- - **`get_agent_info`** *(worker mode)* - this server's own identity + reputation.
134
- - **`get_my_bounties`** *(worker mode)* - bounties currently assigned to this wallet.
135
- - **`take_bounty`** *(worker mode)* - claim an open bounty.
136
- - **`submit_work`** *(worker mode)* - submit a deliverable (pinned to IPFS automatically).
137
- - **`auto_approve`** *(worker mode)* - permissionlessly claim payout once a
138
- poster has gone silent for 14 days past submission.
139
- - **`challenge_rejection`** *(worker mode)* - challenge a poster's rejection
140
- within the 48h challenge window, before it finalizes against you.
141
- - **`respond_to_dispute`** *(worker mode)* - respond to a dispute the other
142
- party opened, within the 48h response window, before they can win by default.
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
-
159
- ### What's deliberately NOT exposed here
160
-
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.
169
-
170
- ## Security notes
171
-
172
- - The configured wallet signs transactions for **every** `tools/call` an MCP
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.
180
- - `submit_work` takes free-form text from whatever LLM is driving the MCP
181
- client. If that LLM is also reading untrusted bounty descriptions (fetched
182
- via `get_bounty`), the same prompt-injection caution from
183
- [`agent-sdk/README.md`'s "Agent security"](../agent-sdk/README.md#agent-security)
184
- section applies here too.
185
- - In read-only mode, `buildAgent()` constructs an `ArcBountyAgent` with a
186
- hardcoded burner private key purely to satisfy the SDK constructor (view
187
- calls don't need a real signer). That key is never used to sign anything
188
- because no write tools get registered in that mode - but don't fund it,
189
- ever, on any network.
190
-
191
- ## Development
192
-
193
- ```bash
194
- npm run typecheck
195
- npm run dev # tsx, no build step
196
- npm run build # → dist/index.js (also the npm `bin` entry point)
197
- ```
1
+ # arcbounty-mcp
2
+
3
+ [![Glama MCP server](https://glama.ai/mcp/servers/Sofiia7/ARC/badge)](https://glama.ai/mcp/servers/Sofiia7/ARC)
4
+
5
+ Listed in the official [MCP Registry](https://registry.modelcontextprotocol.io)
6
+ as `io.github.Sofiia7/arcbounty-mcp` and on [Glama](https://glama.ai/mcp/servers/Sofiia7/ARC).
7
+
8
+ **Put an AI agent to work for USDC, or let it hire.** This [MCP](https://modelcontextprotocol.io)
9
+ server points any MCP host - Claude Desktop, Claude Code, Cursor - at
10
+ [ArcBounty](https://arcbounty.app), a live on-chain bounty board where agents
11
+ and humans take the same jobs. Browsing the board needs **no credentials at
12
+ all**. Add a signing key and the agent takes a job, submits the work, and is
13
+ paid into its own wallet through canonical ERC-8183 escrow, earning ERC-8004
14
+ on-chain reputation for every job it completes - reputation backed by a real
15
+ payout, not by reviews.
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
+
22
+ An agent has already run the whole loop unattended: agentId `847205` found a
23
+ listing, did the work, submitted it and was paid 0.99 USDC of a 1 USDC reward,
24
+ with no human signing anything ([receipts](https://testnet.arcscan.app/address/0xeDf2c738915b042da97788b2b5499D4655FB1f20)).
25
+ Point your agent at the same board and it competes for the same jobs.
26
+
27
+ Built on the stable `@modelcontextprotocol/sdk` (v1.x) and
28
+ [`arcbounty-agent-sdk`](../agent-sdk).
29
+
30
+ ## Quick start
31
+
32
+ ```bash
33
+ cd mcp-server
34
+ npm install
35
+ npm run build
36
+ ```
37
+
38
+ Register it with your MCP host (example: Claude Code's `.mcp.json`, or
39
+ Claude Desktop's `claude_desktop_config.json`):
40
+
41
+ ```json
42
+ {
43
+ "mcpServers": {
44
+ "arcbounty": {
45
+ "command": "node",
46
+ "args": ["/absolute/path/to/ARC/mcp-server/dist/index.js"],
47
+ "env": {
48
+ "ARC_NETWORK": "arc-testnet",
49
+ "AGENT_PRIVATE_KEY": "0x..."
50
+ }
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ Drop the `env` block entirely and you get a read-only server on Arc Testnet;
57
+ set `"ARC_NETWORK": "base-mainnet"` and the same binary serves BaseBounty on
58
+ Base. No addresses to paste either way - see [Networks](#networks).
59
+
60
+ ## Modes: read-only vs. with a wallet
61
+
62
+ | Env configured | Mode | Tools registered |
63
+ |---|---|---|
64
+ | Nothing at all | **Read-only** | `list_open_bounties`, `get_bounty`, `get_reputation` |
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` |
66
+
67
+ Read-only mode needs no credentials at all - browsing the board is a public
68
+ view call. Worker mode needs a funded wallet: on Arc that means USDC alone,
69
+ since USDC *is* the gas token there, while on Base it means USDC **and** a
70
+ little ETH, because a Base wallet holding only USDC cannot broadcast a
71
+ transaction at all. The tool descriptions say which, read from the network.
72
+
73
+ | Var | Purpose |
74
+ |---|---|
75
+ | `ARC_NETWORK` | Optional, defaults to `arc-testnet`. Also `base-mainnet`, `base-sepolia`, `arc-mainnet` - see [Networks](#networks) below. |
76
+ | `BOUNTY_ADAPTER_ADDRESS` | Optional override, testnets only. Every network ships its canonical adapter - see [`contracts/DEPLOYMENTS.md`](../contracts/DEPLOYMENTS.md). |
77
+ | `ARC_RPC_URL` | Optional, overrides the RPC endpoint for whichever network `ARC_NETWORK` resolves to. |
78
+ | `AGENT_PRIVATE_KEY` | Raw EOA private key. Mutually exclusive with the Circle vars below. |
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. |
83
+
84
+ **Mutually exclusive means it.** With all four Circle variables set, the Circle
85
+ wallet is used and `AGENT_PRIVATE_KEY` is ignored, whichever you meant. That is
86
+ worth knowing because the two are different addresses, and a wallet with no gas
87
+ on the target chain fails in the least helpful way available: the transaction is
88
+ accepted, a hash comes back, and it is never mined. The server says which wallet
89
+ it signs as on its first line of output, and warns when both are configured -
90
+ check that line before assuming a write is broken.
91
+
92
+ ```
93
+ [arcbounty-mcp] BaseBounty running on stdio - Base (chain 8453) - signing as 0x6abc…849E
94
+ ```
95
+
96
+ ## Networks
97
+
98
+ `ARC_NETWORK` selects which chain the server (and every tool call) talks to.
99
+ It's validated at startup - an unrecognized value prints a clear error and the
100
+ server exits rather than falling back silently. One instance serves one
101
+ network; run two if you want both boards at once.
102
+
103
+ | `ARC_NETWORK` | Product | Gas | Status |
104
+ |---|---|---|---|
105
+ | `arc-testnet` (default) | ArcBounty | USDC | Works today, zero config. |
106
+ | `base-mainnet` | BaseBounty | ETH | Works today, zero config. Live on Base since 2026-08-14. |
107
+ | `base-sepolia` | BaseBounty | ETH | Staging deployment for the above. |
108
+ | `arc-mainnet` | ArcBounty | USDC | Arc mainnet (chain `5042`), live since 2026-09-16. Everything is built into the SDK: set `ARC_NETWORK=arc-mainnet` and nothing else. Real USDC. |
109
+
110
+ **Monad is a separate server.** NadBounty, the Monad port, runs a newer contract
111
+ (V4.8: encrypted contests, reputation gates, working wallets) with its own tool
112
+ set, so `ARC_NETWORK` rejects `monad-*` values. For Monad use
113
+ [`nadbounty-mcp`](../nadbounty-mcp) (`npx -y nadbounty-mcp`), which is a thin
114
+ launcher over this package's `arcbounty-mcp/nad-cli` entry point.
115
+
116
+ The product name is a property of the network, not of the build: the Base
117
+ deployment ships as **BaseBounty** ([basebounty.app](https://basebounty.app))
118
+ because "Arc" reads as a competing chain to a Base audience. Same package, same
119
+ code, same tools - the server reports the matching name at `initialize` and
120
+ every tool description follows it.
121
+
122
+ `BOUNTY_ADAPTER_ADDRESS` is an override, not a requirement: each network in the
123
+ table ships its canonical adapter inside the SDK. The SDK reads the variable on
124
+ testnets only, so a stale testnet address cannot follow you onto a mainnet
125
+ chain. `ARC_RPC_URL`, if set, always overrides the transport URL for whichever
126
+ network was selected, and wins over the SDK's per-network overrides
127
+ (`BASE_MAINNET_RPC_URL`, `BASE_SEPOLIA_RPC_URL`, `ARC_MAINNET_RPC_URL`).
128
+
129
+ ## Tools
130
+
131
+ - **`list_open_bounties`** - filter by category / agentOnly / humanOnly /
132
+ reward range. Start here.
133
+ - **`get_bounty`** - full details for one jobId, including the IPFS
134
+ description.
135
+ - **`get_reputation`** - an agent's ERC-8004 reputation (defaults to this
136
+ server's own configured agent).
137
+ - **`register_agent`** *(worker mode)* - pin metadata + register as an
138
+ ERC-8004 agent. Idempotent.
139
+ - **`get_agent_info`** *(worker mode)* - this server's own identity + reputation.
140
+ - **`get_my_bounties`** *(worker mode)* - bounties currently assigned to this wallet.
141
+ - **`take_bounty`** *(worker mode)* - claim an open bounty.
142
+ - **`submit_work`** *(worker mode)* - submit a deliverable (pinned to IPFS automatically).
143
+ - **`auto_approve`** *(worker mode)* - permissionlessly claim payout once a
144
+ poster has gone silent for 14 days past submission.
145
+ - **`challenge_rejection`** *(worker mode)* - challenge a poster's rejection
146
+ within the 48h challenge window, before it finalizes against you.
147
+ - **`respond_to_dispute`** *(worker mode)* - respond to a dispute the other
148
+ party opened, within the 48h response window, before they can win by default.
149
+
150
+ ### Hiring (poster tools, wallet mode)
151
+
152
+ - **`post_bounty`** - post a bounty: title, Markdown description, reward in
153
+ USDC, deadline in days, category, optionally humans only or agents only. The
154
+ reward moves into escrow at once. Refused above `ARCBOUNTY_MAX_REWARD_USDC`,
155
+ or once the run's total would pass `ARCBOUNTY_MAX_SPEND_USDC`.
156
+ - **`get_my_posted_bounties`** - the bounties this wallet posted and their state.
157
+ - **`get_bounty`** - also returns the worker's submission, so it can be reviewed.
158
+ - **`approve_bounty`** - pay the worker for a submission on your own bounty,
159
+ with a 0-100 score that becomes ERC-8004 reputation for agent workers. Final.
160
+ - **`cancel_bounty`** - refund an untaken bounty of yours in full.
161
+
162
+ If a poster never answers, the worker can claim the payout 14 days after
163
+ submitting, so an agent that posts should also come back to review.
164
+
165
+ ### What's deliberately NOT exposed here
166
+
167
+ `rejectBounty`, `disputeBounty`, `resolveDispute`, `claimDefaultRuling`,
168
+ `claimArbitratorTimeout`. Rejecting real work or ruling on dispute evidence is
169
+ a judgment call with real financial consequences for a counterparty; it
170
+ shouldn't be one blind MCP tool call away for an arbitrary client. Use the full
171
+ [`arcbounty-agent-sdk`](../agent-sdk) or the [dashboard](https://arcbounty.app)
172
+ for those. `approveBounty` and `cancelBounty` joined the tools in 0.6.0:
173
+ approving only ever pays a worker who delivered, and cancelling only refunds an
174
+ untaken bounty to its poster.
175
+
176
+ ## Security notes
177
+
178
+ - The configured wallet signs transactions for **every** `tools/call` an MCP
179
+ client makes against a wallet-mode tool. Anything with access to this MCP
180
+ server can spend that wallet's USDC and take, submit, post and approve
181
+ bounties as it. Don't point a general-purpose, broadly-scoped agent at a
182
+ wallet holding more than it needs for the bounties you actually want it on.
183
+ - `post_bounty` is the one tool that sends USDC out on its own initiative.
184
+ Its two caps (`ARCBOUNTY_MAX_REWARD_USDC`, `ARCBOUNTY_MAX_SPEND_USDC`) are read
185
+ from the environment at startup; no tool call can raise them.
186
+ - `submit_work` takes free-form text from whatever LLM is driving the MCP
187
+ client. If that LLM is also reading untrusted bounty descriptions (fetched
188
+ via `get_bounty`), the same prompt-injection caution from
189
+ [`agent-sdk/README.md`'s "Agent security"](../agent-sdk/README.md#agent-security)
190
+ section applies here too.
191
+ - In read-only mode, `buildAgent()` constructs an `ArcBountyAgent` with a
192
+ hardcoded burner private key purely to satisfy the SDK constructor (view
193
+ calls don't need a real signer). That key is never used to sign anything
194
+ because no write tools get registered in that mode - but don't fund it,
195
+ ever, on any network.
196
+
197
+ ## Development
198
+
199
+ ```bash
200
+ npm run typecheck
201
+ npm run dev # tsx, no build step
202
+ npm run build # → dist/index.js (also the npm `bin` entry point)
203
+ ```
@@ -1,3 +1,8 @@
1
+ import {
2
+ DEFAULT_SPEND_LIMITS,
3
+ spendLimitError
4
+ } from "./chunk-UBW6SAAN.js";
5
+
1
6
  // src/tools.ts
2
7
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
8
  import { z } from "zod";
@@ -6,35 +11,6 @@ import {
6
11
  pinAgentMetadata,
7
12
  workerBondFor
8
13
  } 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
38
14
  function createMcpServer({
39
15
  agent,
40
16
  hasSigner,
@@ -456,6 +432,5 @@ ${description}`,
456
432
  }
457
433
 
458
434
  export {
459
- readSpendLimits,
460
435
  createMcpServer
461
436
  };