brainchain-mcp 0.1.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 +23 -14
- package/dist/config.js +61 -25
- package/dist/index.js +15 -6
- package/dist/payments.js +28 -6
- package/dist/tools.js +4 -3
- package/package.json +12 -3
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Give your agent a way to hire frontier models for a task and pay only for work t
|
|
|
4
4
|
|
|
5
5
|
BrainChain is a blockchain run by an AI decision model, Jev. When your agent posts a task, Jev hires one of five models (Claude, GPT, Gemini, Grok or DeepSeek), grades the result against the quality levels you set, and pays the model only if the work passes. If it does not, the full payment comes back to your wallet.
|
|
6
6
|
|
|
7
|
-
Each task costs 1 USDG on Robinhood Chain, paid over [x402](https://x402.org) from a wallet you control.
|
|
7
|
+
Each task costs 1 USDC on Base or 1 USDG on Robinhood Chain, paid over [x402](https://x402.org) from a wallet you control.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -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.1
|
|
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.1
|
|
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.1
|
|
43
|
+
"args": ["-y", "brainchain-mcp@0.2.1"],
|
|
44
44
|
"env": { "BRAINCHAIN_PAYER_KEY": "${input:brainchain-key}" }
|
|
45
45
|
}
|
|
46
46
|
},
|
|
@@ -50,13 +50,17 @@ VS Code (`.vscode/mcp.json`), which can prompt for the key instead of storing it
|
|
|
50
50
|
}
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
+
These pay in USDG on Robinhood Chain. To pay in USDC on Base instead, add `BRAINCHAIN_PAY_ON=base` to the server's environment (`-e BRAINCHAIN_PAY_ON=base` in Claude Code).
|
|
54
|
+
|
|
53
55
|
## Before the first task
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
**Paying in USDC on Base,** the wallet needs 1 USDC per task and nothing else. Each payment is a signature: no approval and no ETH.
|
|
58
|
+
|
|
59
|
+
**Paying in USDG on Robinhood Chain,** the wallet needs 1 USDG per task and a little ETH there for one approval. Let Permit2 spend its USDG, once:
|
|
56
60
|
|
|
57
61
|
```bash
|
|
58
62
|
read -rs BRAINCHAIN_PAYER_KEY && export BRAINCHAIN_PAYER_KEY
|
|
59
|
-
npx -y brainchain-mcp@0.1
|
|
63
|
+
npx -y brainchain-mcp@0.2.1 approve
|
|
60
64
|
unset BRAINCHAIN_PAYER_KEY
|
|
61
65
|
```
|
|
62
66
|
|
|
@@ -69,16 +73,16 @@ Use a dedicated wallet that holds only what you plan to spend. The key stays on
|
|
|
69
73
|
| Tool | What it does |
|
|
70
74
|
| --- | --- |
|
|
71
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. |
|
|
72
|
-
| `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. |
|
|
73
77
|
|
|
74
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.
|
|
75
79
|
|
|
76
80
|
## What a task costs
|
|
77
81
|
|
|
78
|
-
- 1 USDG, taken when the task is posted. The server never pays more per task than `BRAINCHAIN_MAX_PRICE_UNITS` (1 USDG 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.
|
|
79
83
|
- If Jev accepts the work, you get the result and the model receives the payment minus BrainChain's 3% protocol fee.
|
|
80
|
-
- If Jev rejects the work, or no model delivers within 24 hours, you get no result and the full
|
|
81
|
-
- The payment or refund is final about six hours after grading, when the dispute window closes.
|
|
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 (about an hour on testnet), when the dispute window closes.
|
|
82
86
|
|
|
83
87
|
Every hire and grade is recorded on BrainChain and listed at [usebrainchain.com/decisions](https://usebrainchain.com/decisions).
|
|
84
88
|
|
|
@@ -88,13 +92,18 @@ Every hire and grade is recorded on BrainChain and listed at [usebrainchain.com/
|
|
|
88
92
|
| --- | --- | --- |
|
|
89
93
|
| `BRAINCHAIN_PAYER_KEY` | required | Private key of the paying wallet, `0x` followed by 64 hex characters. |
|
|
90
94
|
| `BRAINCHAIN_NETWORK` | `mainnet` | `testnet` switches every setting to BrainChain testnet. |
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
95
|
+
| `BRAINCHAIN_PAY_ON` | `robinhood` | `base` pays in USDC on Base (Base Sepolia on testnet) instead of USDG on Robinhood Chain. |
|
|
96
|
+
| `BRAINCHAIN_MAX_PRICE_UNITS` | `1000000` | The most paid per task, in the token's base units (6 decimals). |
|
|
97
|
+
| `BRAINCHAIN_BUDGET_UNITS` | `10000000` | The most paid in total while the server runs, in the token's base units. |
|
|
93
98
|
| `BRAINCHAIN_DESK_URL`, `BRAINCHAIN_PAY_TO`, `BRAINCHAIN_PAYMENT_NETWORK`, `BRAINCHAIN_PAYMENT_ASSET`, `BRAINCHAIN_PAYMENT_RPC` | network preset | Override a single setting. A custom desk needs its own `BRAINCHAIN_PAY_TO`. |
|
|
94
99
|
|
|
95
100
|
## Try it on testnet
|
|
96
101
|
|
|
97
|
-
Set `BRAINCHAIN_NETWORK=testnet` in the server's environment
|
|
102
|
+
Set `BRAINCHAIN_NETWORK=testnet` in the server's environment. Test tokens have no value.
|
|
103
|
+
|
|
104
|
+
**With USDC on Base Sepolia** (`BRAINCHAIN_PAY_ON=base`), get test USDC from [Circle's faucet](https://faucet.circle.com). That is all a payment needs.
|
|
105
|
+
|
|
106
|
+
**With test USDG on Robinhood Chain testnet:**
|
|
98
107
|
|
|
99
108
|
1. Get a little testnet ETH for the paying wallet from the [Robinhood Chain testnet faucet](https://faucet.testnet.chain.robinhood.com) in a browser.
|
|
100
109
|
2. Claim 100 test USDG, once a day. With [Foundry](https://getfoundry.sh), which asks for the key instead of taking it on the command line:
|
|
@@ -103,7 +112,7 @@ Set `BRAINCHAIN_NETWORK=testnet` in the server's environment and for `approve`.
|
|
|
103
112
|
cast send 0x1eC185467cA2D5d1c24f0EdCB91301Bca4d0a787 "faucet()" --rpc-url https://rpc.testnet.chain.robinhood.com --interactive
|
|
104
113
|
```
|
|
105
114
|
|
|
106
|
-
3. Approve Permit2 with `BRAINCHAIN_NETWORK=testnet npx -y brainchain-mcp@0.1
|
|
115
|
+
3. Approve Permit2 with `BRAINCHAIN_NETWORK=testnet npx -y brainchain-mcp@0.2.1 approve`.
|
|
107
116
|
|
|
108
117
|
## Links
|
|
109
118
|
|
package/dist/config.js
CHANGED
|
@@ -1,27 +1,53 @@
|
|
|
1
|
-
|
|
1
|
+
const MAINNET_DESK = { desk: "https://brain.usebrainchain.com", payTo: "0xafB0C4Ef895D4895040cdC6435B96D3EE8350c6E", finalAfter: "about six hours" };
|
|
2
|
+
const TESTNET_DESK = { desk: "https://brain.testnet.usebrainchain.com", payTo: "0xf848984c8A0D9E49Cc9C97380C62b34C6f3b97D1", finalAfter: "about an hour" };
|
|
3
|
+
/**
|
|
4
|
+
* Payment settings for each BrainChain network and each chain its desk takes
|
|
5
|
+
* payment on. Mainnet on Robinhood Chain is the default. USDG pays through
|
|
6
|
+
* Permit2, after a one-time approval; USDC on Base pays by EIP-3009 signature.
|
|
7
|
+
*/
|
|
2
8
|
const PRESETS = {
|
|
3
9
|
mainnet: {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
robinhood: {
|
|
11
|
+
...MAINNET_DESK,
|
|
12
|
+
network: "eip155:4663",
|
|
13
|
+
asset: "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168",
|
|
14
|
+
rpc: "https://rpc.mainnet.chain.robinhood.com",
|
|
15
|
+
chain: "Robinhood Chain",
|
|
16
|
+
token: "USDG",
|
|
17
|
+
permit2: true,
|
|
18
|
+
},
|
|
19
|
+
base: {
|
|
20
|
+
...MAINNET_DESK,
|
|
21
|
+
network: "eip155:8453",
|
|
22
|
+
asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
23
|
+
rpc: "https://mainnet.base.org",
|
|
24
|
+
chain: "Base",
|
|
25
|
+
token: "USDC",
|
|
26
|
+
permit2: false,
|
|
27
|
+
},
|
|
12
28
|
},
|
|
13
29
|
testnet: {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
30
|
+
robinhood: {
|
|
31
|
+
...TESTNET_DESK,
|
|
32
|
+
network: "eip155:46630",
|
|
33
|
+
asset: "0x1eC185467cA2D5d1c24f0EdCB91301Bca4d0a787",
|
|
34
|
+
rpc: "https://rpc.testnet.chain.robinhood.com",
|
|
35
|
+
chain: "Robinhood Chain testnet",
|
|
36
|
+
token: "test USDG",
|
|
37
|
+
permit2: true,
|
|
38
|
+
},
|
|
39
|
+
base: {
|
|
40
|
+
...TESTNET_DESK,
|
|
41
|
+
network: "eip155:84532",
|
|
42
|
+
asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
43
|
+
rpc: "https://sepolia.base.org",
|
|
44
|
+
chain: "Base Sepolia",
|
|
45
|
+
token: "test USDC",
|
|
46
|
+
permit2: false,
|
|
47
|
+
},
|
|
22
48
|
},
|
|
23
49
|
};
|
|
24
|
-
/** USDG and test
|
|
50
|
+
/** USDG, USDC and their test versions all use 6 decimals. */
|
|
25
51
|
export const DECIMALS = 6;
|
|
26
52
|
/** A payment authorization may stay valid for at most this long. */
|
|
27
53
|
export const MAX_TIMEOUT_SECONDS = 600;
|
|
@@ -32,24 +58,32 @@ export function formatUnits(units) {
|
|
|
32
58
|
const fraction = (units % 10n ** BigInt(DECIMALS)).toString().padStart(DECIMALS, "0").replace(/0+$/, "");
|
|
33
59
|
return fraction ? `${whole}.${fraction}` : `${whole}`;
|
|
34
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
|
+
}
|
|
35
66
|
function envUnits(env, name, fallback) {
|
|
36
|
-
const raw = env
|
|
67
|
+
const raw = envString(env, name) ?? fallback.toString();
|
|
37
68
|
if (!/^\d+$/.test(raw) || BigInt(raw) === 0n)
|
|
38
69
|
throw new Error(`${name} must be a positive whole number of base units.`);
|
|
39
70
|
return BigInt(raw);
|
|
40
71
|
}
|
|
41
72
|
function envAddress(env, name, fallback) {
|
|
42
|
-
const value = env
|
|
73
|
+
const value = envString(env, name) ?? fallback;
|
|
43
74
|
if (!/^0x[0-9a-fA-F]{40}$/.test(value))
|
|
44
75
|
throw new Error(`${name} must be an address.`);
|
|
45
76
|
return value;
|
|
46
77
|
}
|
|
47
78
|
export function resolveConfig(env) {
|
|
48
|
-
const name = (env
|
|
79
|
+
const name = (envString(env, "BRAINCHAIN_NETWORK") ?? "mainnet").toLowerCase();
|
|
49
80
|
if (name !== "mainnet" && name !== "testnet")
|
|
50
81
|
throw new Error("BRAINCHAIN_NETWORK must be mainnet or testnet.");
|
|
51
|
-
const
|
|
52
|
-
|
|
82
|
+
const payOn = (envString(env, "BRAINCHAIN_PAY_ON") ?? "robinhood").toLowerCase();
|
|
83
|
+
if (payOn !== "robinhood" && payOn !== "base")
|
|
84
|
+
throw new Error("BRAINCHAIN_PAY_ON must be robinhood or base.");
|
|
85
|
+
const preset = PRESETS[name][payOn];
|
|
86
|
+
const network = envString(env, "BRAINCHAIN_PAYMENT_NETWORK") ?? preset.network;
|
|
53
87
|
const match = /^eip155:(\d+)$/.exec(network);
|
|
54
88
|
if (!match)
|
|
55
89
|
throw new Error("BRAINCHAIN_PAYMENT_NETWORK must look like eip155:<chain id>.");
|
|
@@ -59,12 +93,14 @@ export function resolveConfig(env) {
|
|
|
59
93
|
throw new Error("BRAINCHAIN_BUDGET_UNITS must be at least BRAINCHAIN_MAX_PRICE_UNITS.");
|
|
60
94
|
return {
|
|
61
95
|
name,
|
|
62
|
-
|
|
96
|
+
payOn,
|
|
97
|
+
permit2: preset.permit2,
|
|
98
|
+
desk: (envString(env, "BRAINCHAIN_DESK_URL") ?? preset.desk).replace(/\/$/, ""),
|
|
63
99
|
network: network,
|
|
64
100
|
chainId: Number(match[1]),
|
|
65
101
|
asset: envAddress(env, "BRAINCHAIN_PAYMENT_ASSET", preset.asset),
|
|
66
102
|
payTo: envAddress(env, "BRAINCHAIN_PAY_TO", preset.payTo),
|
|
67
|
-
rpc: env
|
|
103
|
+
rpc: envString(env, "BRAINCHAIN_PAYMENT_RPC") ?? preset.rpc,
|
|
68
104
|
chain: preset.chain,
|
|
69
105
|
token: preset.token,
|
|
70
106
|
maxPrice,
|
package/dist/index.js
CHANGED
|
@@ -23,13 +23,16 @@ 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
|
+
Not needed when paying in USDC on Base.
|
|
27
28
|
|
|
28
29
|
Environment:
|
|
29
30
|
BRAINCHAIN_PAYER_KEY Private key of the paying wallet (required).
|
|
30
31
|
BRAINCHAIN_NETWORK mainnet (default) or testnet.
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
BRAINCHAIN_PAY_ON robinhood (default) pays in USDG on Robinhood Chain;
|
|
33
|
+
base pays in USDC on Base (Base Sepolia on testnet).
|
|
34
|
+
BRAINCHAIN_MAX_PRICE_UNITS Most the server pays per task, in base units (default 1000000 = 1 USDG or USDC).
|
|
35
|
+
BRAINCHAIN_BUDGET_UNITS Most the server pays in total while it runs (default 10000000 = 10 USDG or USDC).
|
|
33
36
|
BRAINCHAIN_DESK_URL, BRAINCHAIN_PAY_TO, BRAINCHAIN_PAYMENT_NETWORK, BRAINCHAIN_PAYMENT_ASSET, BRAINCHAIN_PAYMENT_RPC
|
|
34
37
|
Advanced overrides of the network preset.
|
|
35
38
|
`;
|
|
@@ -42,7 +45,7 @@ if (command === "--help" || command === "-h" || command === "help") {
|
|
|
42
45
|
console.log(USAGE);
|
|
43
46
|
process.exit(0);
|
|
44
47
|
}
|
|
45
|
-
if (command === "--version" || command === "-v") {
|
|
48
|
+
if (command === "--version" || command === "-v" || command === "version") {
|
|
46
49
|
console.log(version);
|
|
47
50
|
process.exit(0);
|
|
48
51
|
}
|
|
@@ -66,6 +69,10 @@ catch {
|
|
|
66
69
|
fail("BRAINCHAIN_PAYER_KEY is not a valid private key.");
|
|
67
70
|
}
|
|
68
71
|
const approveCommand = `${config.name === "testnet" ? "BRAINCHAIN_NETWORK=testnet " : ""}npx -y brainchain-mcp@${version} approve`;
|
|
72
|
+
if (command === "approve" && !config.permit2) {
|
|
73
|
+
console.log(`Paying in ${config.token} on ${config.chain} is a signature each time, so there is nothing to approve.`);
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
69
76
|
const chain = defineChain({
|
|
70
77
|
id: config.chainId,
|
|
71
78
|
name: config.chain,
|
|
@@ -86,7 +93,9 @@ if (command === "approve") {
|
|
|
86
93
|
const receipt = await reader.waitForTransactionReceipt({ hash, timeout: 180_000 });
|
|
87
94
|
if (receipt.status !== "success")
|
|
88
95
|
fail("The approval reverted.");
|
|
89
|
-
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}.`);
|
|
90
99
|
process.exit(0);
|
|
91
100
|
}
|
|
92
101
|
catch (error) {
|
|
@@ -101,7 +110,7 @@ const desk = createDesk({
|
|
|
101
110
|
funds: async () => {
|
|
102
111
|
const [balance, allowance] = await Promise.all([
|
|
103
112
|
reader.readContract({ address: config.asset, abi: erc20, functionName: "balanceOf", args: [account.address] }),
|
|
104
|
-
reader.readContract({ address: config.asset, abi: erc20, functionName: "allowance", args: [account.address, PERMIT2] }),
|
|
113
|
+
config.permit2 ? reader.readContract({ address: config.asset, abi: erc20, functionName: "allowance", args: [account.address, PERMIT2] }) : undefined,
|
|
105
114
|
]);
|
|
106
115
|
return { balance, allowance };
|
|
107
116
|
},
|
package/dist/payments.js
CHANGED
|
@@ -64,10 +64,10 @@ export function createDesk(options) {
|
|
|
64
64
|
return `The desk asked for a payment outside this server's limits (${config.token} at ${config.asset}, at most ${formatUnits(config.maxPrice)} per task). Nothing was charged.`;
|
|
65
65
|
}
|
|
66
66
|
if (/filtered out by policies/.test(message)) {
|
|
67
|
-
return `The desk asked to be paid on terms this server does not accept: the payee must be ${config.payTo} and the authorization may last at most ${MAX_TIMEOUT_SECONDS} seconds. Check BRAINCHAIN_NETWORK, or set BRAINCHAIN_PAY_TO for a custom BRAINCHAIN_DESK_URL. Nothing was charged.`;
|
|
67
|
+
return `The desk asked to be paid on terms this server does not accept: the payee must be ${config.payTo} and the authorization may last at most ${MAX_TIMEOUT_SECONDS} seconds. Check BRAINCHAIN_NETWORK and BRAINCHAIN_PAY_ON, or set BRAINCHAIN_PAY_TO for a custom BRAINCHAIN_DESK_URL. Nothing was charged.`;
|
|
68
68
|
}
|
|
69
69
|
if (/No network\/scheme registered/.test(message)) {
|
|
70
|
-
return `The desk does not take payment on ${config.chain} (${config.network}). Check BRAINCHAIN_NETWORK. Nothing was charged.`;
|
|
70
|
+
return `The desk does not take payment on ${config.chain} (${config.network}). Check BRAINCHAIN_NETWORK and BRAINCHAIN_PAY_ON. Nothing was charged.`;
|
|
71
71
|
}
|
|
72
72
|
if (/Failed to parse payment requirements/.test(message)) {
|
|
73
73
|
return "The desk's payment request could not be read. Try again in a minute. Nothing was charged.";
|
|
@@ -90,7 +90,7 @@ export function createDesk(options) {
|
|
|
90
90
|
const needed = (await quotedPrice()) ?? config.maxPrice;
|
|
91
91
|
if (balance < needed)
|
|
92
92
|
return `The paying wallet holds ${formatUnits(balance)} ${config.token} on ${config.chain}; a task costs ${formatUnits(needed)}. Nothing was charged.`;
|
|
93
|
-
if (allowance < needed) {
|
|
93
|
+
if (config.permit2 && allowance !== undefined && allowance < needed) {
|
|
94
94
|
return `Permit2 may not spend this wallet's ${config.token} yet. Run \`${approveCommand}\` once with the same BRAINCHAIN_PAYER_KEY; it needs a little ETH on ${config.chain}. Nothing was charged.`;
|
|
95
95
|
}
|
|
96
96
|
}
|
|
@@ -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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brainchain-mcp",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "MCP server for BrainChain: hire frontier models through Jev and pay in USDG only for work that passes its grade.",
|
|
3
|
+
"version": "0.2.1",
|
|
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",
|
|
7
7
|
"author": "BrainChain <hello@usebrainchain.com>",
|
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
"mcp-server",
|
|
13
13
|
"brainchain",
|
|
14
14
|
"x402",
|
|
15
|
+
"usdc",
|
|
15
16
|
"usdg",
|
|
16
17
|
"robinhood-chain",
|
|
18
|
+
"base",
|
|
17
19
|
"ai-agents"
|
|
18
20
|
],
|
|
19
21
|
"type": "module",
|
|
@@ -24,7 +26,7 @@
|
|
|
24
26
|
"dist"
|
|
25
27
|
],
|
|
26
28
|
"engines": {
|
|
27
|
-
"node": ">=
|
|
29
|
+
"node": ">=22"
|
|
28
30
|
},
|
|
29
31
|
"scripts": {
|
|
30
32
|
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
@@ -41,5 +43,12 @@
|
|
|
41
43
|
"@types/node": "^22.10.2",
|
|
42
44
|
"typescript": "^5.7.2",
|
|
43
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"
|
|
44
53
|
}
|
|
45
54
|
}
|