arcbounty-mcp 0.3.0 → 0.3.2

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,62 +1,62 @@
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=""
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,156 +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
- "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
- ```
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,15 +1,12 @@
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";
9
+ import { ArcBountyAgent } from "arcbounty-agent-sdk";
13
10
  var pkg = createRequire(import.meta.url)("../package.json");
14
11
  var TAG = `[${pkg.name}]`;
15
12
  var KNOWN_NETWORKS = [
@@ -63,246 +60,9 @@ if (!agent) process.exit(1);
63
60
  var hasSigner = Boolean(
64
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"]
65
62
  );
66
- function summarize(m) {
67
- return {
68
- jobId: m.jobId.toString(),
69
- reward: agent.formatUsdc(m.reward),
70
- category: m.category,
71
- tags: m.tags,
72
- deadline: new Date(Number(m.deadline) * 1e3).toISOString(),
73
- agentOnly: m.agentOnly,
74
- humanOnly: m.humanOnly,
75
- isTaken: m.isTaken,
76
- resolved: m.resolved,
77
- hasSubmission: m.submittedResultHash.length > 0,
78
- descriptionCid: m.ipfsDescHash,
79
- assignedProvider: m.assignedProvider,
80
- poster: m.poster,
81
- // V4 worker bond: taking this bounty requires posting a refundable USDC
82
- // bond (refunded at submit_work; forfeited only on take-and-vanish).
83
- requireWorkerBond: m.requireWorkerBond,
84
- ...m.requireWorkerBond ? { workerBondUsdc: agent.formatUsdc(m.workerBond > 0n ? m.workerBond : workerBondFor(m.reward)) } : {}
85
- };
86
- }
87
- function json(data) {
88
- return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
89
- }
90
- function errorResult(err) {
91
- const message = err instanceof Error ? err.message : String(err);
92
- return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
93
- }
94
63
  var net = agent.network;
95
64
  var BRAND = net.brand.name;
96
- var 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.`;
97
- var server = new McpServer({ name: BRAND.toLowerCase(), version: pkg.version });
98
- server.registerTool(
99
- "list_open_bounties",
100
- {
101
- 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.`,
102
- inputSchema: z.object({
103
- category: z.enum(["dev", "design", "content", "data", "other"]).optional().describe("Filter by category. Omit for all categories."),
104
- agentOnly: z.boolean().optional().describe("If true, only bounties restricted to ERC-8004 agents."),
105
- humanOnly: z.boolean().optional().describe("If true, only bounties restricted to humans."),
106
- minReward: z.number().optional().describe("Minimum reward in USDC dollars."),
107
- maxReward: z.number().optional().describe("Maximum reward in USDC dollars."),
108
- limit: z.number().int().min(1).max(100).optional().describe("Max results (default 20).")
109
- })
110
- },
111
- async ({ category, agentOnly, humanOnly, minReward, maxReward, limit }) => {
112
- try {
113
- const bounties = await agent.listOpenBounties({
114
- category,
115
- agentOnly,
116
- humanOnly,
117
- minReward,
118
- maxReward,
119
- limit: limit ?? 20
120
- });
121
- return json(bounties.map(summarize));
122
- } catch (err) {
123
- return errorResult(err);
124
- }
125
- }
126
- );
127
- server.registerTool(
128
- "get_bounty",
129
- {
130
- description: "Get full details for one bounty by jobId, including its description fetched from IPFS.",
131
- inputSchema: z.object({ jobId: z.string().describe("The bounty's jobId, as a string (it's a uint256 on-chain).") })
132
- },
133
- async ({ jobId }) => {
134
- try {
135
- const meta = await agent.getBounty(BigInt(jobId));
136
- let description = "";
137
- try {
138
- description = await agent.getBountyDescription(BigInt(jobId));
139
- } catch {
140
- description = "(failed to fetch description from IPFS gateways)";
141
- }
142
- return json({ ...summarize(meta), description });
143
- } catch (err) {
144
- return errorResult(err);
145
- }
146
- }
147
- );
148
- server.registerTool(
149
- "get_reputation",
150
- {
151
- description: "Get an ERC-8004 agent's on-chain reputation score (average score, total feedbacks, total jobs).",
152
- inputSchema: z.object({
153
- agentId: z.string().optional().describe("Agent's ERC-8004 id. Omit to use this server's own configured agent.")
154
- })
155
- },
156
- async ({ agentId }) => {
157
- try {
158
- const rep = await agent.getReputation(agentId !== void 0 ? BigInt(agentId) : void 0);
159
- return json({
160
- averageScore: rep.averageScore.toString(),
161
- totalFeedbacks: rep.totalFeedbacks.toString(),
162
- totalJobs: rep.totalJobs.toString()
163
- });
164
- } catch (err) {
165
- return errorResult(err);
166
- }
167
- }
168
- );
169
- if (hasSigner) {
170
- server.registerTool(
171
- "register_agent",
172
- {
173
- 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,
174
- inputSchema: z.object({
175
- name: z.string(),
176
- description: z.string(),
177
- agent_type: z.string().optional(),
178
- capabilities: z.array(z.string()).optional(),
179
- preferred_categories: z.array(z.enum(["dev", "design", "content", "data", "other"])).optional(),
180
- min_reward_usdc: z.number().optional(),
181
- max_reward_usdc: z.number().optional()
182
- })
183
- },
184
- async (args) => {
185
- try {
186
- const metadata = {
187
- name: args.name,
188
- description: args.description,
189
- agent_type: args.agent_type,
190
- capabilities: args.capabilities,
191
- arcbounty: {
192
- preferred_categories: args.preferred_categories,
193
- min_reward_usdc: args.min_reward_usdc,
194
- max_reward_usdc: args.max_reward_usdc
195
- }
196
- };
197
- const metadataURI = await pinAgentMetadata(metadata);
198
- const agentId = await agent.register(metadataURI);
199
- return json({ agentId: agentId.toString(), metadataURI, address: agent.address });
200
- } catch (err) {
201
- return errorResult(err);
202
- }
203
- }
204
- );
205
- server.registerTool(
206
- "get_agent_info",
207
- { description: "Get this server's own configured agent identity, address, and reputation." },
208
- async () => {
209
- try {
210
- const info = await agent.getAgentInfo();
211
- return json({
212
- agentId: info.agentId.toString(),
213
- address: info.address,
214
- metadataURI: info.metadataURI,
215
- reputation: {
216
- averageScore: info.reputation.averageScore.toString(),
217
- totalFeedbacks: info.reputation.totalFeedbacks.toString(),
218
- totalJobs: info.reputation.totalJobs.toString()
219
- }
220
- });
221
- } catch (err) {
222
- return errorResult(err);
223
- }
224
- }
225
- );
226
- server.registerTool(
227
- "get_my_bounties",
228
- { description: "List bounties currently assigned to this server's configured wallet as worker." },
229
- async () => {
230
- try {
231
- const mine = await agent.getMyBounties();
232
- return json(mine.map(summarize));
233
- } catch (err) {
234
- return errorResult(err);
235
- }
236
- }
237
- );
238
- server.registerTool(
239
- "get_pending_actions",
240
- {
241
- 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."
242
- },
243
- async () => {
244
- try {
245
- const actions = await agent.getPendingActions();
246
- return json(actions.map((a) => ({
247
- kind: a.kind,
248
- jobId: a.jobId.toString(),
249
- message: a.message,
250
- bounty: summarize(a.meta)
251
- })));
252
- } catch (err) {
253
- return errorResult(err);
254
- }
255
- }
256
- );
257
- server.registerTool(
258
- "take_bounty",
259
- {
260
- 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,
261
- inputSchema: z.object({ jobId: z.string() })
262
- },
263
- async ({ jobId }) => {
264
- try {
265
- const result = await agent.takeBounty(BigInt(jobId));
266
- return json({ txHash: result.hash });
267
- } catch (err) {
268
- return errorResult(err);
269
- }
270
- }
271
- );
272
- server.registerTool(
273
- "submit_work",
274
- {
275
- 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.",
276
- inputSchema: z.object({
277
- jobId: z.string(),
278
- text: z.string().describe("The deliverable, as markdown/plain text.")
279
- })
280
- },
281
- async ({ jobId, text }) => {
282
- try {
283
- const result = await agent.submitWork(BigInt(jobId), { text });
284
- return json({ txHash: result.hash });
285
- } catch (err) {
286
- return errorResult(err);
287
- }
288
- }
289
- );
290
- server.registerTool(
291
- "auto_approve",
292
- {
293
- 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.",
294
- inputSchema: z.object({ jobId: z.string() })
295
- },
296
- async ({ jobId }) => {
297
- try {
298
- const result = await agent.autoApprove(BigInt(jobId));
299
- return json({ txHash: result.hash });
300
- } catch (err) {
301
- return errorResult(err);
302
- }
303
- }
304
- );
305
- }
65
+ var server = createMcpServer({ agent, hasSigner, version: pkg.version });
306
66
  async function main() {
307
67
  const transport = new StdioServerTransport();
308
68
  await server.connect(transport);
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "arcbounty-mcp",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "mcpName": "io.github.Sofiia7/arcbounty-mcp",
5
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",
@@ -23,7 +23,7 @@
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",
@@ -49,7 +49,7 @@
49
49
  "license": "MIT",
50
50
  "dependencies": {
51
51
  "@modelcontextprotocol/sdk": "^1.29.0",
52
- "arcbounty-agent-sdk": "^0.6.2",
52
+ "arcbounty-agent-sdk": "^0.6.3",
53
53
  "viem": "^2.0.0",
54
54
  "zod": "^3.23.0"
55
55
  },
@@ -58,5 +58,11 @@
58
58
  "tsup": "^8.0.0",
59
59
  "tsx": "^4.0.0",
60
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"
61
67
  }
62
68
  }