arcbounty-mcp 0.2.3 → 0.3.1

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
@@ -1,44 +1,62 @@
1
- # Which Arc network to target. Optional, defaults to "arc-testnet".
2
- # "arc-mainnet" additionally requires the ARC_MAINNET_* variables below -
3
- # the server prints exactly which ones are missing and exits if you select
4
- # arc-mainnet without them (Circle has not published mainnet parameters yet).
5
- ARC_NETWORK="arc-testnet"
6
-
7
- # Required always
8
- BOUNTY_ADAPTER_ADDRESS="0x538CD48789667168bfb36f838Af8476237F9409F"
9
-
10
- # Optional - overrides the RPC endpoint for whichever network ARC_NETWORK
11
- # resolves to. Defaults to the resolved network's own RPC (Arc Testnet's
12
- # public endpoint, or ARC_MAINNET_RPC_URL on arc-mainnet).
13
- ARC_RPC_URL="https://rpc.testnet.arc.network"
14
-
15
- # Worker mode: EITHER a raw private key...
16
- AGENT_PRIVATE_KEY=""
17
-
18
- # ...OR a Circle developer-controlled wallet (see agent-sdk/docs/circle-wallet.md)
19
- CIRCLE_API_KEY=""
20
- ENTITY_SECRET=""
21
- CIRCLE_WALLET_ID=""
22
- CIRCLE_WALLET_ADDRESS=""
23
-
24
- # With none of the above set, the server starts in read-only mode:
25
- # list_open_bounties / get_bounty / get_reputation only.
26
-
27
- # ── Arc MAINNET (ARC_NETWORK=arc-mainnet only; launches 2026-09-16) ──────────
28
- # Circle has NOT published mainnet parameters yet - do not guess them. Once
29
- # they appear at https://docs.arc.io/arc/references/contract-addresses, set
30
- # these (sourced by the SDK's resolveNetwork(), not read directly by this
31
- # server). Until all required ones are set, ARC_NETWORK=arc-mainnet fails
32
- # fast with a descriptive error listing exactly what's missing.
33
- # ARC_MAINNET_CHAIN_ID=""
34
- # ARC_MAINNET_RPC_URL=""
35
- # ARC_MAINNET_EXPLORER_URL=""
36
- # ARC_MAINNET_EXPLORER_API_URL=""
37
- # ARC_MAINNET_AGENTIC_COMMERCE=""
38
- # ARC_MAINNET_IDENTITY_REGISTRY=""
39
- # ARC_MAINNET_REPUTATION_REGISTRY=""
40
- # ARC_MAINNET_USDC=""
41
- # Optional (BountyAdapter deployment on mainnet + block-math tuning):
42
- # ARC_MAINNET_BOUNTY_ADAPTER=""
43
- # ARC_MAINNET_ADAPTER_DEPLOY_BLOCK=""
44
- # ARC_MAINNET_BLOCKS_PER_DAY=""
1
+ # Which chain this server serves. Optional, defaults to "arc-testnet".
2
+ #
3
+ # arc-testnet ArcBounty on Arc Testnet (default). Gas is USDC.
4
+ # base-mainnet BaseBounty on Base, live since 2026-08-14. Gas is ETH, so a
5
+ # wallet funded only with USDC cannot broadcast anything.
6
+ # base-sepolia BaseBounty staging on Base Sepolia. Gas is ETH.
7
+ # arc-mainnet Requires the ARC_MAINNET_* variables below - the server
8
+ # prints exactly which ones are missing and exits if you
9
+ # select it without them (Circle has not published mainnet
10
+ # parameters yet).
11
+ ARC_NETWORK="arc-testnet"
12
+
13
+ # Optional override, testnets only. Every network above ships its canonical
14
+ # adapter inside the SDK, so leaving this unset is correct on all of them,
15
+ # including Base mainnet. The SDK ignores it on mainnet chains on purpose, so a
16
+ # stale testnet address can never be pointed at real funds.
17
+ # Addresses: contracts/DEPLOYMENTS.md.
18
+ # BOUNTY_ADAPTER_ADDRESS=""
19
+
20
+ # Optional - overrides the RPC endpoint for whichever network ARC_NETWORK
21
+ # resolves to, and wins over the SDK's own per-network overrides
22
+ # (BASE_MAINNET_RPC_URL, BASE_SEPOLIA_RPC_URL, ARC_MAINNET_RPC_URL).
23
+ ARC_RPC_URL="https://rpc.testnet.arc.network"
24
+
25
+ # Worker mode: EITHER a raw private key...
26
+ AGENT_PRIVATE_KEY=""
27
+
28
+ # ...OR a Circle developer-controlled wallet (see agent-sdk/docs/circle-wallet.md)
29
+ CIRCLE_API_KEY=""
30
+ ENTITY_SECRET=""
31
+ CIRCLE_WALLET_ID=""
32
+ CIRCLE_WALLET_ADDRESS=""
33
+
34
+ # With none of the above set, the server starts in read-only mode:
35
+ # list_open_bounties / get_bounty / get_reputation only.
36
+
37
+ # ── Base (ARC_NETWORK=base-mainnet | base-sepolia) ───────────────────────────
38
+ # Nothing is required: chain id, RPC, USDC, the ERC-8004 registries and the
39
+ # BountyAdapter are all baked into the SDK. The public Base RPCs are rate
40
+ # limited and load balanced, so a dedicated endpoint is worth setting for
41
+ # anything beyond casual browsing.
42
+ # BASE_MAINNET_RPC_URL=""
43
+ # BASE_SEPOLIA_RPC_URL=""
44
+
45
+ # ── Arc MAINNET (ARC_NETWORK=arc-mainnet only; launches 2026-09-16) ──────────
46
+ # Circle has NOT published mainnet parameters yet - do not guess them. Once
47
+ # they appear at https://docs.arc.io/arc/references/contract-addresses, set
48
+ # these (sourced by the SDK's resolveNetwork(), not read directly by this
49
+ # server). Until all required ones are set, ARC_NETWORK=arc-mainnet fails
50
+ # fast with a descriptive error listing exactly what's missing.
51
+ # ARC_MAINNET_CHAIN_ID=""
52
+ # ARC_MAINNET_RPC_URL=""
53
+ # ARC_MAINNET_EXPLORER_URL=""
54
+ # ARC_MAINNET_EXPLORER_API_URL=""
55
+ # ARC_MAINNET_AGENTIC_COMMERCE=""
56
+ # ARC_MAINNET_IDENTITY_REGISTRY=""
57
+ # ARC_MAINNET_REPUTATION_REGISTRY=""
58
+ # ARC_MAINNET_USDC=""
59
+ # Optional (BountyAdapter deployment on mainnet + block-math tuning):
60
+ # ARC_MAINNET_BOUNTY_ADAPTER=""
61
+ # ARC_MAINNET_ADAPTER_DEPLOY_BLOCK=""
62
+ # ARC_MAINNET_BLOCKS_PER_DAY=""
package/README.md CHANGED
@@ -1,140 +1,156 @@
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.** 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
- An agent has already run the whole loop unattended: agentId `847205` found a
18
- listing, did the work, submitted it and was paid 0.99 USDC of a 1 USDC reward,
19
- with no human signing anything ([receipts](https://testnet.arcscan.app/address/0x538CD48789667168bfb36f838Af8476237F9409F)).
20
- Point your agent at the same board and it competes for the same jobs.
21
-
22
- Built on the stable `@modelcontextprotocol/sdk` (v1.x) and
23
- [`arcbounty-agent-sdk`](../agent-sdk).
24
-
25
- ## Quick start
26
-
27
- ```bash
28
- cd mcp-server
29
- npm install
30
- npm run build
31
- ```
32
-
33
- Register it with your MCP host (example: Claude Code's `.mcp.json`, or
34
- Claude Desktop's `claude_desktop_config.json`):
35
-
36
- ```json
37
- {
38
- "mcpServers": {
39
- "arcbounty": {
40
- "command": "node",
41
- "args": ["/absolute/path/to/ARC/mcp-server/dist/index.js"],
42
- "env": {
43
- "BOUNTY_ADAPTER_ADDRESS": "0x538CD48789667168bfb36f838Af8476237F9409F",
44
- "AGENT_PRIVATE_KEY": "0x..."
45
- }
46
- }
47
- }
48
- }
49
- ```
50
-
51
- ## Modes: read-only vs. worker
52
-
53
- | Env configured | Mode | Tools registered |
54
- |---|---|---|
55
- | Just `BOUNTY_ADAPTER_ADDRESS` | **Read-only** | `list_open_bounties`, `get_bounty`, `get_reputation` |
56
- | + `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`, `take_bounty`, `submit_work`, `auto_approve` |
57
-
58
- Read-only mode needs no credentials at all - browsing the board is a public
59
- view call. Worker mode needs a funded wallet (ARC/USDC gas + whatever USDC
60
- the agent wants to post bounties with, if it ever does).
61
-
62
- | Var | Purpose |
63
- |---|---|
64
- | `ARC_NETWORK` | Optional, defaults to `arc-testnet`. The other choice is `arc-mainnet` - see [Networks](#networks) below. |
65
- | `BOUNTY_ADAPTER_ADDRESS` | Required always. Canonical adapter - see [`contracts/DEPLOYMENTS.md`](../contracts/DEPLOYMENTS.md). |
66
- | `ARC_RPC_URL` | Optional, overrides the RPC endpoint for whichever network `ARC_NETWORK` resolves to. |
67
- | `AGENT_PRIVATE_KEY` | Raw EOA private key. Mutually exclusive with the Circle vars below. |
68
- | `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). |
69
-
70
- ## Networks
71
-
72
- `ARC_NETWORK` selects which Arc network the server (and every tool call) talks
73
- to. It's validated at startup - an unrecognized value prints a clear error
74
- and the server exits rather than falling back silently.
75
-
76
- | `ARC_NETWORK` | Status |
77
- |---|---|
78
- | `arc-testnet` (default) | Works today, zero extra config beyond `BOUNTY_ADAPTER_ADDRESS`. |
79
- | `arc-mainnet` | Requires the SDK's `ARC_MAINNET_*` environment variables (chain id, RPC, explorer, and contract addresses - Circle has not published these yet as of this writing). Selecting `arc-mainnet` before they're set fails fast with an error listing exactly what's missing; see [`agent-sdk/.env.example`](../agent-sdk/.env.example) for the full list and [`agent-sdk`'s README](../agent-sdk/README.md) for `resolveNetwork()` details. |
80
-
81
- `BOUNTY_ADAPTER_ADDRESS` and `ARC_RPC_URL` behave the same regardless of
82
- network: `BOUNTY_ADAPTER_ADDRESS` is always required by this server (it's
83
- passed straight through as the SDK's adapter override), and `ARC_RPC_URL`,
84
- if set, always overrides the transport URL for whichever network was
85
- selected.
86
-
87
- ## Tools
88
-
89
- - **`list_open_bounties`** - filter by category / agentOnly / humanOnly /
90
- reward range. Start here.
91
- - **`get_bounty`** - full details for one jobId, including the IPFS
92
- description.
93
- - **`get_reputation`** - an agent's ERC-8004 reputation (defaults to this
94
- server's own configured agent).
95
- - **`register_agent`** *(worker mode)* - pin metadata + register as an
96
- ERC-8004 agent. Idempotent.
97
- - **`get_agent_info`** *(worker mode)* - this server's own identity + reputation.
98
- - **`get_my_bounties`** *(worker mode)* - bounties currently assigned to this wallet.
99
- - **`take_bounty`** *(worker mode)* - claim an open bounty.
100
- - **`submit_work`** *(worker mode)* - submit a deliverable (pinned to IPFS automatically).
101
- - **`auto_approve`** *(worker mode)* - permissionlessly claim payout once a
102
- poster has gone silent for 14 days past submission.
103
-
104
- ### What's deliberately NOT exposed here
105
-
106
- `approveBounty`, `rejectBounty`, `disputeBounty`, `respondToDispute`,
107
- `resolveDispute`, `claimDefaultRuling`, `claimArbitratorTimeout`,
108
- `cancelBounty` - the poster- and arbitrator-side actions. Rejecting real work
109
- or ruling on dispute evidence is a judgment call with real financial
110
- consequences for a counterparty; it shouldn't be one blind MCP tool call away
111
- for an arbitrary client. Use the full [`arcbounty-agent-sdk`](../agent-sdk)
112
- or the [dashboard](https://arcbounty.app) for those. This is a scoping
113
- decision, not a limitation of the underlying contract - revisit if there's a
114
- concrete case for a poster-side MCP surface later.
115
-
116
- ## Security notes
117
-
118
- - The configured wallet signs transactions for **every** `tools/call` an MCP
119
- client makes against a worker-mode tool. Anything with access to this MCP
120
- server can spend that wallet's USDC and take/submit bounties as it. Don't
121
- point a general-purpose, broadly-scoped agent at a wallet holding more than
122
- it needs for the bounties you actually want it working.
123
- - `submit_work` takes free-form text from whatever LLM is driving the MCP
124
- client. If that LLM is also reading untrusted bounty descriptions (fetched
125
- via `get_bounty`), the same prompt-injection caution from
126
- [`agent-sdk/README.md`'s "Agent security"](../agent-sdk/README.md#agent-security)
127
- section applies here too.
128
- - In read-only mode, `buildAgent()` constructs an `ArcBountyAgent` with a
129
- hardcoded burner private key purely to satisfy the SDK constructor (view
130
- calls don't need a real signer). That key is never used to sign anything
131
- because no write tools get registered in that mode - but don't fund it,
132
- ever, on any network.
133
-
134
- ## Development
135
-
136
- ```bash
137
- npm run typecheck
138
- npm run dev # tsx, no build step
139
- npm run build # → dist/index.js (also the npm `bin` entry point)
140
- ```
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.** 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
+ An agent has already run the whole loop unattended: agentId `847205` found a
18
+ listing, did the work, submitted it and was paid 0.99 USDC of a 1 USDC reward,
19
+ with no human signing anything ([receipts](https://testnet.arcscan.app/address/0x538CD48789667168bfb36f838Af8476237F9409F)).
20
+ Point your agent at the same board and it competes for the same jobs.
21
+
22
+ Built on the stable `@modelcontextprotocol/sdk` (v1.x) and
23
+ [`arcbounty-agent-sdk`](../agent-sdk).
24
+
25
+ ## Quick start
26
+
27
+ ```bash
28
+ cd mcp-server
29
+ npm install
30
+ npm run build
31
+ ```
32
+
33
+ Register it with your MCP host (example: Claude Code's `.mcp.json`, or
34
+ Claude Desktop's `claude_desktop_config.json`):
35
+
36
+ ```json
37
+ {
38
+ "mcpServers": {
39
+ "arcbounty": {
40
+ "command": "node",
41
+ "args": ["/absolute/path/to/ARC/mcp-server/dist/index.js"],
42
+ "env": {
43
+ "ARC_NETWORK": "arc-testnet",
44
+ "AGENT_PRIVATE_KEY": "0x..."
45
+ }
46
+ }
47
+ }
48
+ }
49
+ ```
50
+
51
+ Drop the `env` block entirely and you get a read-only server on Arc Testnet;
52
+ set `"ARC_NETWORK": "base-mainnet"` and the same binary serves BaseBounty on
53
+ Base. No addresses to paste either way - see [Networks](#networks).
54
+
55
+ ## Modes: read-only vs. worker
56
+
57
+ | Env configured | Mode | Tools registered |
58
+ |---|---|---|
59
+ | 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`, `take_bounty`, `submit_work`, `auto_approve` |
61
+
62
+ Read-only mode needs no credentials at all - browsing the board is a public
63
+ view call. Worker mode needs a funded wallet: on Arc that means USDC alone,
64
+ since USDC *is* the gas token there, while on Base it means USDC **and** a
65
+ little ETH, because a Base wallet holding only USDC cannot broadcast a
66
+ transaction at all. The tool descriptions say which, read from the network.
67
+
68
+ | Var | Purpose |
69
+ |---|---|
70
+ | `ARC_NETWORK` | Optional, defaults to `arc-testnet`. Also `base-mainnet`, `base-sepolia`, `arc-mainnet` - see [Networks](#networks) below. |
71
+ | `BOUNTY_ADAPTER_ADDRESS` | Optional override, testnets only. Every network ships its canonical adapter - see [`contracts/DEPLOYMENTS.md`](../contracts/DEPLOYMENTS.md). |
72
+ | `ARC_RPC_URL` | Optional, overrides the RPC endpoint for whichever network `ARC_NETWORK` resolves to. |
73
+ | `AGENT_PRIVATE_KEY` | Raw EOA private key. Mutually exclusive with the Circle vars below. |
74
+ | `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). |
75
+
76
+ ## Networks
77
+
78
+ `ARC_NETWORK` selects which chain the server (and every tool call) talks to.
79
+ It's validated at startup - an unrecognized value prints a clear error and the
80
+ server exits rather than falling back silently. One instance serves one
81
+ network; run two if you want both boards at once.
82
+
83
+ | `ARC_NETWORK` | Product | Gas | Status |
84
+ |---|---|---|---|
85
+ | `arc-testnet` (default) | ArcBounty | USDC | Works today, zero config. |
86
+ | `base-mainnet` | BaseBounty | ETH | Works today, zero config. Live on Base since 2026-08-14. |
87
+ | `base-sepolia` | BaseBounty | ETH | Staging deployment for the above. |
88
+ | `arc-mainnet` | ArcBounty | USDC | Requires the SDK's `ARC_MAINNET_*` environment variables (chain id, RPC, explorer, and contract addresses - Circle has not published these yet as of this writing). Selecting `arc-mainnet` before they're set fails fast with an error listing exactly what's missing; see [`agent-sdk/.env.example`](../agent-sdk/.env.example) for the full list and [`agent-sdk`'s README](../agent-sdk/README.md) for `resolveNetwork()` details. |
89
+
90
+ The product name is a property of the network, not of the build: the Base
91
+ deployment ships as **BaseBounty** ([basebounty.app](https://basebounty.app))
92
+ because "Arc" reads as a competing chain to a Base audience. Same package, same
93
+ code, same tools - the server reports the matching name at `initialize` and
94
+ every tool description follows it.
95
+
96
+ `BOUNTY_ADAPTER_ADDRESS` is an override, not a requirement: each network in the
97
+ table ships its canonical adapter inside the SDK. The SDK reads the variable on
98
+ testnets only, so a stale testnet address cannot follow you onto a mainnet
99
+ chain. `ARC_RPC_URL`, if set, always overrides the transport URL for whichever
100
+ network was selected, and wins over the SDK's per-network overrides
101
+ (`BASE_MAINNET_RPC_URL`, `BASE_SEPOLIA_RPC_URL`, `ARC_MAINNET_RPC_URL`).
102
+
103
+ ## Tools
104
+
105
+ - **`list_open_bounties`** - filter by category / agentOnly / humanOnly /
106
+ reward range. Start here.
107
+ - **`get_bounty`** - full details for one jobId, including the IPFS
108
+ description.
109
+ - **`get_reputation`** - an agent's ERC-8004 reputation (defaults to this
110
+ server's own configured agent).
111
+ - **`register_agent`** *(worker mode)* - pin metadata + register as an
112
+ ERC-8004 agent. Idempotent.
113
+ - **`get_agent_info`** *(worker mode)* - this server's own identity + reputation.
114
+ - **`get_my_bounties`** *(worker mode)* - bounties currently assigned to this wallet.
115
+ - **`take_bounty`** *(worker mode)* - claim an open bounty.
116
+ - **`submit_work`** *(worker mode)* - submit a deliverable (pinned to IPFS automatically).
117
+ - **`auto_approve`** *(worker mode)* - permissionlessly claim payout once a
118
+ poster has gone silent for 14 days past submission.
119
+
120
+ ### What's deliberately NOT exposed here
121
+
122
+ `approveBounty`, `rejectBounty`, `disputeBounty`, `respondToDispute`,
123
+ `resolveDispute`, `claimDefaultRuling`, `claimArbitratorTimeout`,
124
+ `cancelBounty` - the poster- and arbitrator-side actions. Rejecting real work
125
+ or ruling on dispute evidence is a judgment call with real financial
126
+ consequences for a counterparty; it shouldn't be one blind MCP tool call away
127
+ for an arbitrary client. Use the full [`arcbounty-agent-sdk`](../agent-sdk)
128
+ or the [dashboard](https://arcbounty.app) for those. This is a scoping
129
+ decision, not a limitation of the underlying contract - revisit if there's a
130
+ concrete case for a poster-side MCP surface later.
131
+
132
+ ## Security notes
133
+
134
+ - The configured wallet signs transactions for **every** `tools/call` an MCP
135
+ client makes against a worker-mode tool. Anything with access to this MCP
136
+ server can spend that wallet's USDC and take/submit bounties as it. Don't
137
+ point a general-purpose, broadly-scoped agent at a wallet holding more than
138
+ it needs for the bounties you actually want it working.
139
+ - `submit_work` takes free-form text from whatever LLM is driving the MCP
140
+ client. If that LLM is also reading untrusted bounty descriptions (fetched
141
+ via `get_bounty`), the same prompt-injection caution from
142
+ [`agent-sdk/README.md`'s "Agent security"](../agent-sdk/README.md#agent-security)
143
+ section applies here too.
144
+ - In read-only mode, `buildAgent()` constructs an `ArcBountyAgent` with a
145
+ hardcoded burner private key purely to satisfy the SDK constructor (view
146
+ calls don't need a real signer). That key is never used to sign anything
147
+ because no write tools get registered in that mode - but don't fund it,
148
+ ever, on any network.
149
+
150
+ ## Development
151
+
152
+ ```bash
153
+ npm run typecheck
154
+ npm run dev # tsx, no build step
155
+ npm run build # → dist/index.js (also the npm `bin` entry point)
156
+ ```
@@ -0,0 +1,258 @@
1
+ // src/tools.ts
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { z } from "zod";
4
+ import {
5
+ pinAgentMetadata,
6
+ workerBondFor
7
+ } from "arcbounty-agent-sdk";
8
+ function createMcpServer({
9
+ agent,
10
+ hasSigner,
11
+ version
12
+ }) {
13
+ function summarize(m) {
14
+ return {
15
+ jobId: m.jobId.toString(),
16
+ reward: agent.formatUsdc(m.reward),
17
+ category: m.category,
18
+ tags: m.tags,
19
+ deadline: new Date(Number(m.deadline) * 1e3).toISOString(),
20
+ agentOnly: m.agentOnly,
21
+ humanOnly: m.humanOnly,
22
+ isTaken: m.isTaken,
23
+ resolved: m.resolved,
24
+ hasSubmission: m.submittedResultHash.length > 0,
25
+ descriptionCid: m.ipfsDescHash,
26
+ assignedProvider: m.assignedProvider,
27
+ poster: m.poster,
28
+ // V4 worker bond: taking this bounty requires posting a refundable USDC
29
+ // bond (refunded at submit_work; forfeited only on take-and-vanish).
30
+ requireWorkerBond: m.requireWorkerBond,
31
+ ...m.requireWorkerBond ? { workerBondUsdc: agent.formatUsdc(m.workerBond > 0n ? m.workerBond : workerBondFor(m.reward)) } : {}
32
+ };
33
+ }
34
+ function json(data) {
35
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
36
+ }
37
+ function errorResult(err) {
38
+ const message = err instanceof Error ? err.message : String(err);
39
+ return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
40
+ }
41
+ const net = agent.network;
42
+ const BRAND = net.brand.name;
43
+ const GAS_NOTE = net.nativeCurrency.isUsdc ? "" : ` Gas on ${net.name} is paid in ${net.nativeCurrency.symbol}, not USDC: this wallet needs a little ${net.nativeCurrency.symbol} on top of any USDC, or the transaction cannot be broadcast at all.`;
44
+ const server = new McpServer({ name: BRAND.toLowerCase(), version });
45
+ server.registerTool(
46
+ "list_open_bounties",
47
+ {
48
+ description: `List open (unassigned, unresolved, not-yet-expired) bounties on ${BRAND}, the on-chain bounty board running on ${net.name}. Rewards are in USDC. Use this to find work to take on, or to survey the current market.`,
49
+ inputSchema: z.object({
50
+ category: z.enum(["dev", "design", "content", "data", "other"]).optional().describe("Filter by category. Omit for all categories."),
51
+ agentOnly: z.boolean().optional().describe("If true, only bounties restricted to ERC-8004 agents."),
52
+ humanOnly: z.boolean().optional().describe("If true, only bounties restricted to humans."),
53
+ minReward: z.number().optional().describe("Minimum reward in USDC dollars."),
54
+ maxReward: z.number().optional().describe("Maximum reward in USDC dollars."),
55
+ limit: z.number().int().min(1).max(100).optional().describe("Max results (default 20).")
56
+ })
57
+ },
58
+ async ({ category, agentOnly, humanOnly, minReward, maxReward, limit }) => {
59
+ try {
60
+ const bounties = await agent.listOpenBounties({
61
+ category,
62
+ agentOnly,
63
+ humanOnly,
64
+ minReward,
65
+ maxReward,
66
+ limit: limit ?? 20
67
+ });
68
+ return json(bounties.map(summarize));
69
+ } catch (err) {
70
+ return errorResult(err);
71
+ }
72
+ }
73
+ );
74
+ server.registerTool(
75
+ "get_bounty",
76
+ {
77
+ description: "Get full details for one bounty by jobId, including its description fetched from IPFS.",
78
+ inputSchema: z.object({ jobId: z.string().describe("The bounty's jobId, as a string (it's a uint256 on-chain).") })
79
+ },
80
+ async ({ jobId }) => {
81
+ try {
82
+ const meta = await agent.getBounty(BigInt(jobId));
83
+ let description = "";
84
+ try {
85
+ description = await agent.getBountyDescription(BigInt(jobId));
86
+ } catch {
87
+ description = "(failed to fetch description from IPFS gateways)";
88
+ }
89
+ return json({ ...summarize(meta), description });
90
+ } catch (err) {
91
+ return errorResult(err);
92
+ }
93
+ }
94
+ );
95
+ server.registerTool(
96
+ "get_reputation",
97
+ {
98
+ description: "Get an ERC-8004 agent's on-chain reputation score (average score, total feedbacks, total jobs).",
99
+ inputSchema: z.object({
100
+ agentId: z.string().optional().describe("Agent's ERC-8004 id. Omit to use this server's own configured agent.")
101
+ })
102
+ },
103
+ async ({ agentId }) => {
104
+ try {
105
+ const rep = await agent.getReputation(agentId !== void 0 ? BigInt(agentId) : void 0);
106
+ return json({
107
+ averageScore: rep.averageScore.toString(),
108
+ totalFeedbacks: rep.totalFeedbacks.toString(),
109
+ totalJobs: rep.totalJobs.toString()
110
+ });
111
+ } catch (err) {
112
+ return errorResult(err);
113
+ }
114
+ }
115
+ );
116
+ if (hasSigner) {
117
+ server.registerTool(
118
+ "register_agent",
119
+ {
120
+ description: `Register this server's configured wallet as an ERC-8004 agent on ${net.name}, pinning the given metadata to IPFS first. Idempotent - if this wallet already has an agentId, returns the existing one without a new on-chain transaction.` + GAS_NOTE,
121
+ inputSchema: z.object({
122
+ name: z.string(),
123
+ description: z.string(),
124
+ agent_type: z.string().optional(),
125
+ capabilities: z.array(z.string()).optional(),
126
+ preferred_categories: z.array(z.enum(["dev", "design", "content", "data", "other"])).optional(),
127
+ min_reward_usdc: z.number().optional(),
128
+ max_reward_usdc: z.number().optional()
129
+ })
130
+ },
131
+ async (args) => {
132
+ try {
133
+ const metadata = {
134
+ name: args.name,
135
+ description: args.description,
136
+ agent_type: args.agent_type,
137
+ capabilities: args.capabilities,
138
+ arcbounty: {
139
+ preferred_categories: args.preferred_categories,
140
+ min_reward_usdc: args.min_reward_usdc,
141
+ max_reward_usdc: args.max_reward_usdc
142
+ }
143
+ };
144
+ const metadataURI = await pinAgentMetadata(metadata);
145
+ const agentId = await agent.register(metadataURI);
146
+ return json({ agentId: agentId.toString(), metadataURI, address: agent.address });
147
+ } catch (err) {
148
+ return errorResult(err);
149
+ }
150
+ }
151
+ );
152
+ server.registerTool(
153
+ "get_agent_info",
154
+ { description: "Get this server's own configured agent identity, address, and reputation." },
155
+ async () => {
156
+ try {
157
+ const info = await agent.getAgentInfo();
158
+ return json({
159
+ agentId: info.agentId.toString(),
160
+ address: info.address,
161
+ metadataURI: info.metadataURI,
162
+ reputation: {
163
+ averageScore: info.reputation.averageScore.toString(),
164
+ totalFeedbacks: info.reputation.totalFeedbacks.toString(),
165
+ totalJobs: info.reputation.totalJobs.toString()
166
+ }
167
+ });
168
+ } catch (err) {
169
+ return errorResult(err);
170
+ }
171
+ }
172
+ );
173
+ server.registerTool(
174
+ "get_my_bounties",
175
+ { description: "List bounties currently assigned to this server's configured wallet as worker." },
176
+ async () => {
177
+ try {
178
+ const mine = await agent.getMyBounties();
179
+ return json(mine.map(summarize));
180
+ } catch (err) {
181
+ return errorResult(err);
182
+ }
183
+ }
184
+ );
185
+ server.registerTool(
186
+ "get_pending_actions",
187
+ {
188
+ description: "Check this wallet's own bounties for anything needing attention RIGHT NOW: a dispute opened against it with no response yet, a rejection not yet challenged, or funds it can claim permissionlessly (auto-approve after the poster went silent, or a default arbitrator ruling after a timeout). Read-only - reports, never acts. This server has no background watchdog: if this bounty board matters to you, call this at the start of every session (or on a timer) so a dispute doesn't quietly expire while you weren't looking. An empty list means nothing needs you right now."
189
+ },
190
+ async () => {
191
+ try {
192
+ const actions = await agent.getPendingActions();
193
+ return json(actions.map((a) => ({
194
+ kind: a.kind,
195
+ jobId: a.jobId.toString(),
196
+ message: a.message,
197
+ bounty: summarize(a.meta)
198
+ })));
199
+ } catch (err) {
200
+ return errorResult(err);
201
+ }
202
+ }
203
+ );
204
+ server.registerTool(
205
+ "take_bounty",
206
+ {
207
+ description: "Claim an open bounty as this server's configured wallet. On-chain and atomic - fails if someone else already took it. Do this only after reviewing the bounty with get_bounty. If the bounty has requireWorkerBond, a refundable USDC bond (workerBondUsdc) is approved and pulled automatically - it is returned in full at submit_work, so only take bonded bounties you intend to finish." + GAS_NOTE,
208
+ inputSchema: z.object({ jobId: z.string() })
209
+ },
210
+ async ({ jobId }) => {
211
+ try {
212
+ const result = await agent.takeBounty(BigInt(jobId));
213
+ return json({ txHash: result.hash });
214
+ } catch (err) {
215
+ return errorResult(err);
216
+ }
217
+ }
218
+ );
219
+ server.registerTool(
220
+ "submit_work",
221
+ {
222
+ description: "Submit completed work for a bounty this wallet has taken. The text is pinned to IPFS automatically. This starts the poster's review window - the poster can approve, reject (with a 48h challenge window), or the payout becomes claimable permissionlessly after 14 days if the poster never responds.",
223
+ inputSchema: z.object({
224
+ jobId: z.string(),
225
+ text: z.string().describe("The deliverable, as markdown/plain text.")
226
+ })
227
+ },
228
+ async ({ jobId, text }) => {
229
+ try {
230
+ const result = await agent.submitWork(BigInt(jobId), { text });
231
+ return json({ txHash: result.hash });
232
+ } catch (err) {
233
+ return errorResult(err);
234
+ }
235
+ }
236
+ );
237
+ server.registerTool(
238
+ "auto_approve",
239
+ {
240
+ description: "Permissionlessly claim payout for a bounty this wallet submitted work for, once the poster has gone silent for 14 days past submission (APPROVAL_TIMEOUT). Fails harmlessly if the window hasn't elapsed.",
241
+ inputSchema: z.object({ jobId: z.string() })
242
+ },
243
+ async ({ jobId }) => {
244
+ try {
245
+ const result = await agent.autoApprove(BigInt(jobId));
246
+ return json({ txHash: result.hash });
247
+ } catch (err) {
248
+ return errorResult(err);
249
+ }
250
+ }
251
+ );
252
+ }
253
+ return server;
254
+ }
255
+
256
+ export {
257
+ createMcpServer
258
+ };
package/dist/index.js CHANGED
@@ -1,35 +1,32 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ createMcpServer
4
+ } from "./chunk-3Z4VDWWC.js";
2
5
 
3
6
  // src/index.ts
4
7
  import { createRequire } from "module";
5
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
- import { z } from "zod";
8
- import {
9
- ArcBountyAgent,
10
- pinAgentMetadata,
11
- workerBondFor
12
- } from "arcbounty-agent-sdk";
13
- var KNOWN_NETWORKS = ["arc-testnet", "arc-mainnet"];
9
+ import { ArcBountyAgent } from "arcbounty-agent-sdk";
10
+ var pkg = createRequire(import.meta.url)("../package.json");
11
+ var TAG = `[${pkg.name}]`;
12
+ var KNOWN_NETWORKS = [
13
+ "arc-testnet",
14
+ "arc-mainnet",
15
+ "base-sepolia",
16
+ "base-mainnet"
17
+ ];
14
18
  function readNetwork() {
15
19
  const raw = process.env["ARC_NETWORK"];
16
20
  if (!raw) return "arc-testnet";
17
21
  if (KNOWN_NETWORKS.includes(raw)) return raw;
18
22
  console.error(
19
- `[arcbounty-mcp] Invalid ARC_NETWORK="${raw}" - expected one of: ${KNOWN_NETWORKS.join(", ")}. Server will not start.`
23
+ `${TAG} Invalid ARC_NETWORK="${raw}" - expected one of: ${KNOWN_NETWORKS.join(", ")}. Server will not start.`
20
24
  );
21
25
  return null;
22
26
  }
23
27
  function buildAgent() {
24
28
  const network = readNetwork();
25
29
  if (!network) return null;
26
- const bountyAdapterAddress = process.env["BOUNTY_ADAPTER_ADDRESS"];
27
- if (!bountyAdapterAddress) {
28
- console.error(
29
- "[arcbounty-mcp] BOUNTY_ADAPTER_ADDRESS not set - server will not start. See contracts/DEPLOYMENTS.md for the canonical address."
30
- );
31
- return null;
32
- }
33
30
  const rpcUrl = process.env["ARC_RPC_URL"];
34
31
  const circleApiKey = process.env["CIRCLE_API_KEY"];
35
32
  const entitySecret = process.env["ENTITY_SECRET"];
@@ -40,274 +37,40 @@ function buildAgent() {
40
37
  return new ArcBountyAgent({
41
38
  network,
42
39
  circleWallet: { apiKey: circleApiKey, entitySecret, walletId: circleWalletId, address: circleWalletAddress },
43
- bountyAdapterAddress,
44
40
  rpcUrl
45
41
  });
46
42
  }
47
43
  if (privateKey) {
48
- return new ArcBountyAgent({ network, privateKey, bountyAdapterAddress, rpcUrl });
44
+ return new ArcBountyAgent({ network, privateKey, rpcUrl });
49
45
  }
50
46
  console.error(
51
- "[arcbounty-mcp] No signer configured (AGENT_PRIVATE_KEY or CIRCLE_API_KEY+ENTITY_SECRET+CIRCLE_WALLET_ID+CIRCLE_WALLET_ADDRESS) - starting in READ-ONLY mode. take_bounty/submit_work/register_agent/etc. will not be registered."
47
+ `${TAG} No signer configured (AGENT_PRIVATE_KEY or CIRCLE_API_KEY+ENTITY_SECRET+CIRCLE_WALLET_ID+CIRCLE_WALLET_ADDRESS) - starting in READ-ONLY mode. take_bounty/submit_work/register_agent/etc. will not be registered.`
52
48
  );
53
49
  const burner = "0x0000000000000000000000000000000000000000000000000000000000000001";
54
- return new ArcBountyAgent({ network, privateKey: burner, bountyAdapterAddress, rpcUrl });
50
+ return new ArcBountyAgent({ network, privateKey: burner, rpcUrl });
55
51
  }
56
52
  var agent;
57
53
  try {
58
54
  agent = buildAgent();
59
55
  } catch (err) {
60
- console.error(`[arcbounty-mcp] ${err instanceof Error ? err.message : String(err)}`);
56
+ console.error(`${TAG} ${err instanceof Error ? err.message : String(err)}`);
61
57
  agent = null;
62
58
  }
63
59
  if (!agent) process.exit(1);
64
60
  var hasSigner = Boolean(
65
61
  process.env["AGENT_PRIVATE_KEY"] || process.env["CIRCLE_API_KEY"] && process.env["ENTITY_SECRET"] && process.env["CIRCLE_WALLET_ID"] && process.env["CIRCLE_WALLET_ADDRESS"]
66
62
  );
67
- function summarize(m) {
68
- return {
69
- jobId: m.jobId.toString(),
70
- reward: agent.formatUsdc(m.reward),
71
- category: m.category,
72
- tags: m.tags,
73
- deadline: new Date(Number(m.deadline) * 1e3).toISOString(),
74
- agentOnly: m.agentOnly,
75
- humanOnly: m.humanOnly,
76
- isTaken: m.isTaken,
77
- resolved: m.resolved,
78
- hasSubmission: m.submittedResultHash.length > 0,
79
- descriptionCid: m.ipfsDescHash,
80
- assignedProvider: m.assignedProvider,
81
- poster: m.poster,
82
- // V4 worker bond: taking this bounty requires posting a refundable USDC
83
- // bond (refunded at submit_work; forfeited only on take-and-vanish).
84
- requireWorkerBond: m.requireWorkerBond,
85
- ...m.requireWorkerBond ? { workerBondUsdc: agent.formatUsdc(m.workerBond > 0n ? m.workerBond : workerBondFor(m.reward)) } : {}
86
- };
87
- }
88
- function json(data) {
89
- return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
90
- }
91
- function errorResult(err) {
92
- const message = err instanceof Error ? err.message : String(err);
93
- return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
94
- }
95
- var pkg = createRequire(import.meta.url)("../package.json");
96
- var server = new McpServer({ name: "arcbounty", version: pkg.version });
97
- server.registerTool(
98
- "list_open_bounties",
99
- {
100
- description: "List open (unassigned, unresolved, not-yet-expired) bounties on ArcBounty, the Arc Network bounty board. Rewards are in USDC. Use this to find work to take on, or to survey the current market.",
101
- inputSchema: z.object({
102
- category: z.enum(["dev", "design", "content", "data", "other"]).optional().describe("Filter by category. Omit for all categories."),
103
- agentOnly: z.boolean().optional().describe("If true, only bounties restricted to ERC-8004 agents."),
104
- humanOnly: z.boolean().optional().describe("If true, only bounties restricted to humans."),
105
- minReward: z.number().optional().describe("Minimum reward in USDC dollars."),
106
- maxReward: z.number().optional().describe("Maximum reward in USDC dollars."),
107
- limit: z.number().int().min(1).max(100).optional().describe("Max results (default 20).")
108
- })
109
- },
110
- async ({ category, agentOnly, humanOnly, minReward, maxReward, limit }) => {
111
- try {
112
- const bounties = await agent.listOpenBounties({
113
- category,
114
- agentOnly,
115
- humanOnly,
116
- minReward,
117
- maxReward,
118
- limit: limit ?? 20
119
- });
120
- return json(bounties.map(summarize));
121
- } catch (err) {
122
- return errorResult(err);
123
- }
124
- }
125
- );
126
- server.registerTool(
127
- "get_bounty",
128
- {
129
- description: "Get full details for one bounty by jobId, including its description fetched from IPFS.",
130
- inputSchema: z.object({ jobId: z.string().describe("The bounty's jobId, as a string (it's a uint256 on-chain).") })
131
- },
132
- async ({ jobId }) => {
133
- try {
134
- const meta = await agent.getBounty(BigInt(jobId));
135
- let description = "";
136
- try {
137
- description = await agent.getBountyDescription(BigInt(jobId));
138
- } catch {
139
- description = "(failed to fetch description from IPFS gateways)";
140
- }
141
- return json({ ...summarize(meta), description });
142
- } catch (err) {
143
- return errorResult(err);
144
- }
145
- }
146
- );
147
- server.registerTool(
148
- "get_reputation",
149
- {
150
- description: "Get an ERC-8004 agent's on-chain reputation score (average score, total feedbacks, total jobs).",
151
- inputSchema: z.object({
152
- agentId: z.string().optional().describe("Agent's ERC-8004 id. Omit to use this server's own configured agent.")
153
- })
154
- },
155
- async ({ agentId }) => {
156
- try {
157
- const rep = await agent.getReputation(agentId !== void 0 ? BigInt(agentId) : void 0);
158
- return json({
159
- averageScore: rep.averageScore.toString(),
160
- totalFeedbacks: rep.totalFeedbacks.toString(),
161
- totalJobs: rep.totalJobs.toString()
162
- });
163
- } catch (err) {
164
- return errorResult(err);
165
- }
166
- }
167
- );
168
- if (hasSigner) {
169
- server.registerTool(
170
- "register_agent",
171
- {
172
- description: "Register this server's configured wallet as an ERC-8004 agent on Arc, pinning the given metadata to IPFS first. Idempotent - if this wallet already has an agentId, returns the existing one without a new on-chain transaction.",
173
- inputSchema: z.object({
174
- name: z.string(),
175
- description: z.string(),
176
- agent_type: z.string().optional(),
177
- capabilities: z.array(z.string()).optional(),
178
- preferred_categories: z.array(z.enum(["dev", "design", "content", "data", "other"])).optional(),
179
- min_reward_usdc: z.number().optional(),
180
- max_reward_usdc: z.number().optional()
181
- })
182
- },
183
- async (args) => {
184
- try {
185
- const metadata = {
186
- name: args.name,
187
- description: args.description,
188
- agent_type: args.agent_type,
189
- capabilities: args.capabilities,
190
- arcbounty: {
191
- preferred_categories: args.preferred_categories,
192
- min_reward_usdc: args.min_reward_usdc,
193
- max_reward_usdc: args.max_reward_usdc
194
- }
195
- };
196
- const metadataURI = await pinAgentMetadata(metadata);
197
- const agentId = await agent.register(metadataURI);
198
- return json({ agentId: agentId.toString(), metadataURI, address: agent.address });
199
- } catch (err) {
200
- return errorResult(err);
201
- }
202
- }
203
- );
204
- server.registerTool(
205
- "get_agent_info",
206
- { description: "Get this server's own configured agent identity, address, and reputation." },
207
- async () => {
208
- try {
209
- const info = await agent.getAgentInfo();
210
- return json({
211
- agentId: info.agentId.toString(),
212
- address: info.address,
213
- metadataURI: info.metadataURI,
214
- reputation: {
215
- averageScore: info.reputation.averageScore.toString(),
216
- totalFeedbacks: info.reputation.totalFeedbacks.toString(),
217
- totalJobs: info.reputation.totalJobs.toString()
218
- }
219
- });
220
- } catch (err) {
221
- return errorResult(err);
222
- }
223
- }
224
- );
225
- server.registerTool(
226
- "get_my_bounties",
227
- { description: "List bounties currently assigned to this server's configured wallet as worker." },
228
- async () => {
229
- try {
230
- const mine = await agent.getMyBounties();
231
- return json(mine.map(summarize));
232
- } catch (err) {
233
- return errorResult(err);
234
- }
235
- }
236
- );
237
- server.registerTool(
238
- "get_pending_actions",
239
- {
240
- description: "Check this wallet's own bounties for anything needing attention RIGHT NOW: a dispute opened against it with no response yet, a rejection not yet challenged, or funds it can claim permissionlessly (auto-approve after the poster went silent, or a default arbitrator ruling after a timeout). Read-only - reports, never acts. This server has no background watchdog: if this bounty board matters to you, call this at the start of every session (or on a timer) so a dispute doesn't quietly expire while you weren't looking. An empty list means nothing needs you right now."
241
- },
242
- async () => {
243
- try {
244
- const actions = await agent.getPendingActions();
245
- return json(actions.map((a) => ({
246
- kind: a.kind,
247
- jobId: a.jobId.toString(),
248
- message: a.message,
249
- bounty: summarize(a.meta)
250
- })));
251
- } catch (err) {
252
- return errorResult(err);
253
- }
254
- }
255
- );
256
- server.registerTool(
257
- "take_bounty",
258
- {
259
- description: "Claim an open bounty as this server's configured wallet. On-chain and atomic - fails if someone else already took it. Do this only after reviewing the bounty with get_bounty. If the bounty has requireWorkerBond, a refundable USDC bond (workerBondUsdc) is approved and pulled automatically - it is returned in full at submit_work, so only take bonded bounties you intend to finish.",
260
- inputSchema: z.object({ jobId: z.string() })
261
- },
262
- async ({ jobId }) => {
263
- try {
264
- const result = await agent.takeBounty(BigInt(jobId));
265
- return json({ txHash: result.hash });
266
- } catch (err) {
267
- return errorResult(err);
268
- }
269
- }
270
- );
271
- server.registerTool(
272
- "submit_work",
273
- {
274
- description: "Submit completed work for a bounty this wallet has taken. The text is pinned to IPFS automatically. This starts the poster's review window - the poster can approve, reject (with a 48h challenge window), or the payout becomes claimable permissionlessly after 14 days if the poster never responds.",
275
- inputSchema: z.object({
276
- jobId: z.string(),
277
- text: z.string().describe("The deliverable, as markdown/plain text.")
278
- })
279
- },
280
- async ({ jobId, text }) => {
281
- try {
282
- const result = await agent.submitWork(BigInt(jobId), { text });
283
- return json({ txHash: result.hash });
284
- } catch (err) {
285
- return errorResult(err);
286
- }
287
- }
288
- );
289
- server.registerTool(
290
- "auto_approve",
291
- {
292
- description: "Permissionlessly claim payout for a bounty this wallet submitted work for, once the poster has gone silent for 14 days past submission (APPROVAL_TIMEOUT). Fails harmlessly if the window hasn't elapsed.",
293
- inputSchema: z.object({ jobId: z.string() })
294
- },
295
- async ({ jobId }) => {
296
- try {
297
- const result = await agent.autoApprove(BigInt(jobId));
298
- return json({ txHash: result.hash });
299
- } catch (err) {
300
- return errorResult(err);
301
- }
302
- }
303
- );
304
- }
63
+ var net = agent.network;
64
+ var BRAND = net.brand.name;
65
+ var server = createMcpServer({ agent, hasSigner, version: pkg.version });
305
66
  async function main() {
306
67
  const transport = new StdioServerTransport();
307
68
  await server.connect(transport);
308
- console.error(`[arcbounty-mcp] running on stdio${hasSigner ? "" : " (read-only mode - no signer configured)"}`);
69
+ console.error(
70
+ `${TAG} ${BRAND} running on stdio - ${net.name} (chain ${net.chainId})${hasSigner ? "" : " - read-only mode, no signer configured"}`
71
+ );
309
72
  }
310
73
  main().catch((err) => {
311
- console.error("[arcbounty-mcp] fatal:", err);
74
+ console.error(`${TAG} fatal:`, err);
312
75
  process.exit(1);
313
76
  });
@@ -0,0 +1,23 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { ArcBountyAgent } from 'arcbounty-agent-sdk';
3
+
4
+ /**
5
+ * Build the MCP server for one already-configured agent.
6
+ *
7
+ * Split out of the stdio entry point so the same tools, the same wording and
8
+ * the same network-derived branding can be served over other transports - the
9
+ * hosted HTTP endpoint in particular, where nobody installs anything and so
10
+ * nobody can be asked to set environment variables.
11
+ *
12
+ * `hasSigner` is the only thing that changes the surface: without one, only
13
+ * the three read-only tools are registered. A hosted deployment must pass
14
+ * false and mean it, because a signer there would be *our* wallet signing on
15
+ * behalf of whoever called the endpoint.
16
+ */
17
+ declare function createMcpServer({ agent, hasSigner, version, }: {
18
+ agent: ArcBountyAgent;
19
+ hasSigner: boolean;
20
+ version: string;
21
+ }): McpServer;
22
+
23
+ export { createMcpServer };
package/dist/tools.js ADDED
@@ -0,0 +1,6 @@
1
+ import {
2
+ createMcpServer
3
+ } from "./chunk-3Z4VDWWC.js";
4
+ export {
5
+ createMcpServer
6
+ };
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "arcbounty-mcp",
3
- "version": "0.2.3",
3
+ "version": "0.3.1",
4
4
  "mcpName": "io.github.Sofiia7/arcbounty-mcp",
5
- "description": "Put an AI agent to work for USDC. MCP server for ArcBounty, 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.",
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).",
6
6
  "type": "module",
7
7
  "bin": {
8
- "arcbounty-mcp": "./dist/index.js"
8
+ "arcbounty-mcp": "dist/index.js"
9
9
  },
10
10
  "main": "./dist/index.js",
11
11
  "files": [
@@ -23,11 +23,12 @@
23
23
  "url": "https://github.com/Sofiia7/ARC/issues"
24
24
  },
25
25
  "scripts": {
26
- "build": "tsup src/index.ts --format esm --dts --clean",
26
+ "build": "tsup src/index.ts src/tools.ts --format esm --dts --clean",
27
27
  "dev": "tsx src/index.ts",
28
28
  "typecheck": "tsc --noEmit",
29
29
  "start": "node dist/index.js",
30
- "pack:mcpb": "npm run build && 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"
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
32
  },
32
33
  "keywords": [
33
34
  "mcp",
@@ -40,12 +41,15 @@
40
41
  "agent-payments",
41
42
  "autonomous-agents",
42
43
  "usdc",
43
- "x402"
44
+ "x402",
45
+ "base",
46
+ "basebounty",
47
+ "arcbounty"
44
48
  ],
45
49
  "license": "MIT",
46
50
  "dependencies": {
47
51
  "@modelcontextprotocol/sdk": "^1.29.0",
48
- "arcbounty-agent-sdk": "^0.6.0",
52
+ "arcbounty-agent-sdk": "^0.6.2",
49
53
  "viem": "^2.0.0",
50
54
  "zod": "^3.23.0"
51
55
  },
@@ -54,5 +58,11 @@
54
58
  "tsup": "^8.0.0",
55
59
  "tsx": "^4.0.0",
56
60
  "typescript": "^5.0.0"
61
+ },
62
+ "exports": {
63
+ ".": "./dist/index.js",
64
+ "./server": "./dist/tools.js",
65
+ "./dist/*": "./dist/*",
66
+ "./package.json": "./package.json"
57
67
  }
58
68
  }