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 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.0
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.0"],
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.0"],
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
- The paying wallet needs USDG on Robinhood Chain (1 per task) and a little ETH there for one approval. Then let Permit2 spend its USDG, once:
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.0 approve
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's grade, the result text once Jev accepts the work, and the refund transaction if it does not. `done` says when to stop asking and `retryAfterSeconds` when to ask again. |
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` while it runs (10 USDG by default), and only to the BrainChain payment desk.
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 1 USDG goes back to the paying wallet on Robinhood Chain.
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
- | `BRAINCHAIN_MAX_PRICE_UNITS` | `1000000` | The most paid per task, in USDG base units (6 decimals). |
92
- | `BRAINCHAIN_BUDGET_UNITS` | `10000000` | The most paid in total while the server runs, in USDG base units. |
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 and for `approve`. Testnet tasks cost 1 test USDG, which has no value, on Robinhood Chain testnet.
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.0 approve`.
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
- /** Payment and desk settings for each BrainChain network. Mainnet is the default. */
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
- desk: "https://brain.usebrainchain.com",
5
- network: "eip155:4663",
6
- asset: "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168",
7
- payTo: "0xafB0C4Ef895D4895040cdC6435B96D3EE8350c6E",
8
- rpc: "https://rpc.mainnet.chain.robinhood.com",
9
- chain: "Robinhood Chain",
10
- token: "USDG",
11
- finalAfter: "about six hours",
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
- desk: "https://brain.testnet.usebrainchain.com",
15
- network: "eip155:46630",
16
- asset: "0x1eC185467cA2D5d1c24f0EdCB91301Bca4d0a787",
17
- payTo: "0xf848984c8A0D9E49Cc9C97380C62b34C6f3b97D1",
18
- rpc: "https://rpc.testnet.chain.robinhood.com",
19
- chain: "Robinhood Chain testnet",
20
- token: "test USDG",
21
- finalAfter: "about an hour",
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 USDG both use 6 decimals. */
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[name] ?? fallback.toString();
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[name] ?? fallback;
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.BRAINCHAIN_NETWORK ?? "mainnet").toLowerCase();
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 preset = PRESETS[name];
52
- const network = env.BRAINCHAIN_PAYMENT_NETWORK ?? preset.network;
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
- desk: (env.BRAINCHAIN_DESK_URL ?? preset.desk).replace(/\/$/, ""),
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.BRAINCHAIN_PAYMENT_RPC ?? preset.rpc,
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 or "max".
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
- BRAINCHAIN_MAX_PRICE_UNITS Most the server pays per task, in base units (default 1000000 = 1 USDG).
32
- BRAINCHAIN_BUDGET_UNITS Most the server pays in total while it runs (default 10000000 = 10 USDG).
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(`Permit2 can now spend ${arg === "max" ? "any amount of" : formatUnits(amount)} ${config.token} from ${account.address}.`);
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
- if (signed && (response.status === 409 || response.status >= 500)) {
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 answered with a client error, so the signed payment was not settled.
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
- const response = await fetchImpl(`${endpoint}/${order}`, { signal: AbortSignal.timeout(20_000) });
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
- if (typeof input.order !== "string" || !/^0x[0-9a-f]{64}$/.test(input.order))
73
- return reply({ error: "order must be a 0x-prefixed 32-byte hex id." }, true);
74
- return reply(await desk.get(input.order));
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.0",
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": ">=20"
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
  }