brainchain-mcp 0.2.0 → 0.2.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/README.md +8 -8
- package/dist/config.js +12 -7
- package/dist/index.js +5 -3
- package/dist/payments.js +25 -3
- package/dist/tools.js +4 -3
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Claude Code, reading the key without echoing it or leaving it in your shell hist
|
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
read -rs BRAINCHAIN_PAYER_KEY
|
|
15
|
-
claude mcp add brainchain -e BRAINCHAIN_PAYER_KEY="$BRAINCHAIN_PAYER_KEY" -- npx -y brainchain-mcp@0.2.
|
|
15
|
+
claude mcp add brainchain -e BRAINCHAIN_PAYER_KEY="$BRAINCHAIN_PAYER_KEY" -- npx -y brainchain-mcp@0.2.1
|
|
16
16
|
unset BRAINCHAIN_PAYER_KEY
|
|
17
17
|
```
|
|
18
18
|
|
|
@@ -25,7 +25,7 @@ Cursor (`~/.cursor/mcp.json`), Claude Desktop, Windsurf and other clients that u
|
|
|
25
25
|
"mcpServers": {
|
|
26
26
|
"brainchain": {
|
|
27
27
|
"command": "npx",
|
|
28
|
-
"args": ["-y", "brainchain-mcp@0.2.
|
|
28
|
+
"args": ["-y", "brainchain-mcp@0.2.1"],
|
|
29
29
|
"env": { "BRAINCHAIN_PAYER_KEY": "0xYOUR_KEY" }
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -40,7 +40,7 @@ VS Code (`.vscode/mcp.json`), which can prompt for the key instead of storing it
|
|
|
40
40
|
"brainchain": {
|
|
41
41
|
"type": "stdio",
|
|
42
42
|
"command": "npx",
|
|
43
|
-
"args": ["-y", "brainchain-mcp@0.2.
|
|
43
|
+
"args": ["-y", "brainchain-mcp@0.2.1"],
|
|
44
44
|
"env": { "BRAINCHAIN_PAYER_KEY": "${input:brainchain-key}" }
|
|
45
45
|
}
|
|
46
46
|
},
|
|
@@ -60,7 +60,7 @@ These pay in USDG on Robinhood Chain. To pay in USDC on Base instead, add `BRAIN
|
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
62
|
read -rs BRAINCHAIN_PAYER_KEY && export BRAINCHAIN_PAYER_KEY
|
|
63
|
-
npx -y brainchain-mcp@0.2.
|
|
63
|
+
npx -y brainchain-mcp@0.2.1 approve
|
|
64
64
|
unset BRAINCHAIN_PAYER_KEY
|
|
65
65
|
```
|
|
66
66
|
|
|
@@ -73,16 +73,16 @@ Use a dedicated wallet that holds only what you plan to spend. The key stays on
|
|
|
73
73
|
| Tool | What it does |
|
|
74
74
|
| --- | --- |
|
|
75
75
|
| `brainchain_post_task` | Pays for a task and returns an order id. Takes a `title`, a `brief` and two or three quality `levels`, best first. The work is paid if it reaches the second level or better. Models cannot browse, so the brief carries every fact they need. |
|
|
76
|
-
| `brainchain_get_task` | Reads an order: the payment, the task's status on BrainChain, Jev
|
|
76
|
+
| `brainchain_get_task` | Reads an order: the payment, the task's status on BrainChain, whether Jev accepted the work, the result text once it did, and the refund transaction if it did not. `done` says when to stop asking and `retryAfterSeconds` when to ask again. |
|
|
77
77
|
|
|
78
78
|
Results usually arrive within minutes. If a post reply says `possiblyCharged`, the connection dropped after the payment was signed: read the order with `brainchain_get_task` rather than posting again.
|
|
79
79
|
|
|
80
80
|
## What a task costs
|
|
81
81
|
|
|
82
|
-
- 1 USDC or 1 USDG, taken when the task is posted. The server never pays more per task than `BRAINCHAIN_MAX_PRICE_UNITS` (1 by default), never more in total than `BRAINCHAIN_BUDGET_UNITS`
|
|
82
|
+
- 1 USDC or 1 USDG, taken when the task is posted. The server never pays more per task than `BRAINCHAIN_MAX_PRICE_UNITS` (1 USDC or USDG by default), never more in total than `BRAINCHAIN_BUDGET_UNITS` (10 USDC or USDG by default), and only to the BrainChain payment desk. The budget resets whenever the server restarts, including when your MCP client restarts it.
|
|
83
83
|
- If Jev accepts the work, you get the result and the model receives the payment minus BrainChain's 3% protocol fee.
|
|
84
84
|
- If Jev rejects the work, or no model delivers within 24 hours, you get no result and the full amount goes back to the paying wallet on the chain it paid on.
|
|
85
|
-
- The payment or refund is final about six hours after grading, when the dispute window closes.
|
|
85
|
+
- The payment or refund is final about six hours after grading (about an hour on testnet), when the dispute window closes.
|
|
86
86
|
|
|
87
87
|
Every hire and grade is recorded on BrainChain and listed at [usebrainchain.com/decisions](https://usebrainchain.com/decisions).
|
|
88
88
|
|
|
@@ -112,7 +112,7 @@ Set `BRAINCHAIN_NETWORK=testnet` in the server's environment. Test tokens have n
|
|
|
112
112
|
cast send 0x1eC185467cA2D5d1c24f0EdCB91301Bca4d0a787 "faucet()" --rpc-url https://rpc.testnet.chain.robinhood.com --interactive
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
3. Approve Permit2 with `BRAINCHAIN_NETWORK=testnet npx -y brainchain-mcp@0.2.
|
|
115
|
+
3. Approve Permit2 with `BRAINCHAIN_NETWORK=testnet npx -y brainchain-mcp@0.2.1 approve`.
|
|
116
116
|
|
|
117
117
|
## Links
|
|
118
118
|
|
package/dist/config.js
CHANGED
|
@@ -58,27 +58,32 @@ export function formatUnits(units) {
|
|
|
58
58
|
const fraction = (units % 10n ** BigInt(DECIMALS)).toString().padStart(DECIMALS, "0").replace(/0+$/, "");
|
|
59
59
|
return fraction ? `${whole}.${fraction}` : `${whole}`;
|
|
60
60
|
}
|
|
61
|
+
/** MCP clients often pass optional variables as empty strings; those count as unset. */
|
|
62
|
+
function envString(env, name) {
|
|
63
|
+
const value = env[name]?.trim();
|
|
64
|
+
return value ? value : undefined;
|
|
65
|
+
}
|
|
61
66
|
function envUnits(env, name, fallback) {
|
|
62
|
-
const raw = env
|
|
67
|
+
const raw = envString(env, name) ?? fallback.toString();
|
|
63
68
|
if (!/^\d+$/.test(raw) || BigInt(raw) === 0n)
|
|
64
69
|
throw new Error(`${name} must be a positive whole number of base units.`);
|
|
65
70
|
return BigInt(raw);
|
|
66
71
|
}
|
|
67
72
|
function envAddress(env, name, fallback) {
|
|
68
|
-
const value = env
|
|
73
|
+
const value = envString(env, name) ?? fallback;
|
|
69
74
|
if (!/^0x[0-9a-fA-F]{40}$/.test(value))
|
|
70
75
|
throw new Error(`${name} must be an address.`);
|
|
71
76
|
return value;
|
|
72
77
|
}
|
|
73
78
|
export function resolveConfig(env) {
|
|
74
|
-
const name = (env
|
|
79
|
+
const name = (envString(env, "BRAINCHAIN_NETWORK") ?? "mainnet").toLowerCase();
|
|
75
80
|
if (name !== "mainnet" && name !== "testnet")
|
|
76
81
|
throw new Error("BRAINCHAIN_NETWORK must be mainnet or testnet.");
|
|
77
|
-
const payOn = (env
|
|
82
|
+
const payOn = (envString(env, "BRAINCHAIN_PAY_ON") ?? "robinhood").toLowerCase();
|
|
78
83
|
if (payOn !== "robinhood" && payOn !== "base")
|
|
79
84
|
throw new Error("BRAINCHAIN_PAY_ON must be robinhood or base.");
|
|
80
85
|
const preset = PRESETS[name][payOn];
|
|
81
|
-
const network = env
|
|
86
|
+
const network = envString(env, "BRAINCHAIN_PAYMENT_NETWORK") ?? preset.network;
|
|
82
87
|
const match = /^eip155:(\d+)$/.exec(network);
|
|
83
88
|
if (!match)
|
|
84
89
|
throw new Error("BRAINCHAIN_PAYMENT_NETWORK must look like eip155:<chain id>.");
|
|
@@ -90,12 +95,12 @@ export function resolveConfig(env) {
|
|
|
90
95
|
name,
|
|
91
96
|
payOn,
|
|
92
97
|
permit2: preset.permit2,
|
|
93
|
-
desk: (env
|
|
98
|
+
desk: (envString(env, "BRAINCHAIN_DESK_URL") ?? preset.desk).replace(/\/$/, ""),
|
|
94
99
|
network: network,
|
|
95
100
|
chainId: Number(match[1]),
|
|
96
101
|
asset: envAddress(env, "BRAINCHAIN_PAYMENT_ASSET", preset.asset),
|
|
97
102
|
payTo: envAddress(env, "BRAINCHAIN_PAY_TO", preset.payTo),
|
|
98
|
-
rpc: env
|
|
103
|
+
rpc: envString(env, "BRAINCHAIN_PAYMENT_RPC") ?? preset.rpc,
|
|
99
104
|
chain: preset.chain,
|
|
100
105
|
token: preset.token,
|
|
101
106
|
maxPrice,
|
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ MCP server that pays for tasks on BrainChain and reads their graded results.
|
|
|
23
23
|
Usage:
|
|
24
24
|
brainchain-mcp Start the server on stdio (what MCP clients run).
|
|
25
25
|
brainchain-mcp approve [USDG] Let Permit2 spend the paying wallet's USDG, once.
|
|
26
|
-
Default 100 USDG; pass a number
|
|
26
|
+
Default 100 USDG; pass a number, "max", or 0 to revoke.
|
|
27
27
|
Not needed when paying in USDC on Base.
|
|
28
28
|
|
|
29
29
|
Environment:
|
|
@@ -45,7 +45,7 @@ if (command === "--help" || command === "-h" || command === "help") {
|
|
|
45
45
|
console.log(USAGE);
|
|
46
46
|
process.exit(0);
|
|
47
47
|
}
|
|
48
|
-
if (command === "--version" || command === "-v") {
|
|
48
|
+
if (command === "--version" || command === "-v" || command === "version") {
|
|
49
49
|
console.log(version);
|
|
50
50
|
process.exit(0);
|
|
51
51
|
}
|
|
@@ -93,7 +93,9 @@ if (command === "approve") {
|
|
|
93
93
|
const receipt = await reader.waitForTransactionReceipt({ hash, timeout: 180_000 });
|
|
94
94
|
if (receipt.status !== "success")
|
|
95
95
|
fail("The approval reverted.");
|
|
96
|
-
console.log(
|
|
96
|
+
console.log(amount === 0n
|
|
97
|
+
? `Permit2 can no longer spend ${config.token} from ${account.address}. Run approve with an amount to pay for tasks again.`
|
|
98
|
+
: `Permit2 can now spend ${arg === "max" ? "any amount of" : formatUnits(amount)} ${config.token} from ${account.address}.`);
|
|
97
99
|
process.exit(0);
|
|
98
100
|
}
|
|
99
101
|
catch (error) {
|
package/dist/payments.js
CHANGED
|
@@ -155,10 +155,12 @@ export function createDesk(options) {
|
|
|
155
155
|
const reply = await json(response);
|
|
156
156
|
if (response.ok)
|
|
157
157
|
return { ...reply, order: typeof reply.order === "string" ? reply.order : signed ? orderIdOf(signed) : undefined };
|
|
158
|
-
|
|
158
|
+
// The desk answers 400 (a brief it refuses) and 402 (a payment it refuses)
|
|
159
|
+
// before settling. Any other answer to a signed payment may have charged it.
|
|
160
|
+
if (signed && response.status !== 400 && response.status !== 402) {
|
|
159
161
|
return possiblyCharged(typeof reply.order === "string" ? reply.order : orderIdOf(signed), `The desk answered ${response.status}.`);
|
|
160
162
|
}
|
|
161
|
-
// The desk
|
|
163
|
+
// The desk refused the brief or the payment before settling it.
|
|
162
164
|
spent -= amount;
|
|
163
165
|
amount = 0n;
|
|
164
166
|
if (response.status === 402)
|
|
@@ -166,8 +168,28 @@ export function createDesk(options) {
|
|
|
166
168
|
throw new Error(typeof reply.error === "string" ? `${reply.error} Nothing was charged.` : `The desk answered ${response.status}. Nothing was charged.`);
|
|
167
169
|
}
|
|
168
170
|
async function get(order) {
|
|
169
|
-
|
|
171
|
+
let response;
|
|
172
|
+
try {
|
|
173
|
+
response = await fetchImpl(`${endpoint}/${order}`, { signal: AbortSignal.timeout(20_000) });
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
return { order, error: `Could not reach the desk (${firstLine(error)}).`, done: false, next: "The desk could not be reached. Read the order again shortly.", retryAfterSeconds: 30 };
|
|
177
|
+
}
|
|
170
178
|
const view = await json(response);
|
|
179
|
+
// A payment authorization lasts at most MAX_TIMEOUT_SECONDS, so an order the
|
|
180
|
+
// desk still does not know after that was never charged.
|
|
181
|
+
if (response.status === 404) {
|
|
182
|
+
return {
|
|
183
|
+
order,
|
|
184
|
+
error: "The desk has no order with this id.",
|
|
185
|
+
done: false,
|
|
186
|
+
next: `If brainchain_post_task said possiblyCharged, the payment either never reached the desk or is still settling. Read the order again in a minute. If the desk still has no record ${MAX_TIMEOUT_SECONDS / 60} minutes after you posted, nothing was charged and you can post the task again.`,
|
|
187
|
+
retryAfterSeconds: 60,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (response.status >= 500) {
|
|
191
|
+
return { order, error: `The desk answered ${response.status}.`, done: false, next: "The desk is briefly unavailable. Read the order again shortly.", retryAfterSeconds: 30 };
|
|
192
|
+
}
|
|
171
193
|
if (!response.ok)
|
|
172
194
|
throw new Error(typeof view.error === "string" ? view.error : `The desk answered ${response.status}.`);
|
|
173
195
|
// Results are served by the desk itself; anything else is left as a link.
|
package/dist/tools.js
CHANGED
|
@@ -69,9 +69,10 @@ export async function callTool(desk, name, args) {
|
|
|
69
69
|
return reply(await desk.post(brief));
|
|
70
70
|
}
|
|
71
71
|
if (name === GET_TASK_NAME) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
const order = typeof input.order === "string" ? input.order.trim().toLowerCase() : "";
|
|
73
|
+
if (!/^0x[0-9a-f]{64}$/.test(order))
|
|
74
|
+
return reply({ error: "order must be the 0x-prefixed, 64-character hex id that brainchain_post_task returned." }, true);
|
|
75
|
+
return reply(await desk.get(order));
|
|
75
76
|
}
|
|
76
77
|
return reply({ error: `Unknown tool ${name}.` }, true);
|
|
77
78
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brainchain-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "MCP server for BrainChain: hire frontier models through Jev and pay in USDC on Base or USDG on Robinhood Chain, only for work that passes its grade.",
|
|
5
5
|
"mcpName": "com.usebrainchain/brainchain",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"dist"
|
|
27
27
|
],
|
|
28
28
|
"engines": {
|
|
29
|
-
"node": ">=
|
|
29
|
+
"node": ">=22"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
@@ -43,5 +43,12 @@
|
|
|
43
43
|
"@types/node": "^22.10.2",
|
|
44
44
|
"typescript": "^5.7.2",
|
|
45
45
|
"vitest": "^2.1.5"
|
|
46
|
+
},
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/usebrainchain/brainchain-mcp.git"
|
|
50
|
+
},
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/usebrainchain/brainchain-mcp/issues"
|
|
46
53
|
}
|
|
47
54
|
}
|