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 +62 -44
- package/README.md +156 -140
- package/dist/chunk-3Z4VDWWC.js +258 -0
- package/dist/index.js +24 -261
- package/dist/tools.d.ts +23 -0
- package/dist/tools.js +6 -0
- package/package.json +17 -7
package/.env.example
CHANGED
|
@@ -1,44 +1,62 @@
|
|
|
1
|
-
# Which
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
#
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
|
|
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
|
-
[](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
|
-
"
|
|
44
|
-
"AGENT_PRIVATE_KEY": "0x..."
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
`
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
1
|
+
# arcbounty-mcp
|
|
2
|
+
|
|
3
|
+
[](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 {
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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,
|
|
44
|
+
return new ArcBountyAgent({ network, privateKey, rpcUrl });
|
|
49
45
|
}
|
|
50
46
|
console.error(
|
|
51
|
-
|
|
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,
|
|
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(
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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(
|
|
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(
|
|
74
|
+
console.error(`${TAG} fatal:`, err);
|
|
312
75
|
process.exit(1);
|
|
313
76
|
});
|
package/dist/tools.d.ts
ADDED
|
@@ -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
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arcbounty-mcp",
|
|
3
|
-
"version": "0.
|
|
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
|
|
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": "
|
|
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.
|
|
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
|
}
|