@bsvkey/inference-mcp 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/package.json +21 -21
- package/server.js +45 -0
package/README.md
CHANGED
|
@@ -12,6 +12,15 @@ Two ways to pay: a **prepaid channel** (`infer`, fund once, draw down per token)
|
|
|
12
12
|
with its own key). `x402_infer` needs a funded WIF (`wif` arg or `BSVKEY_WIF`) and the
|
|
13
13
|
optional `@bsvkey/x402-bsv-client` + `@bsv/sdk` packages (installed with this one).
|
|
14
14
|
|
|
15
|
+
**Verifiable metering.** Each `infer` call returns a signed usage receipt, and the
|
|
16
|
+
tool auto-verifies it offline (with the optional packages installed): the result
|
|
17
|
+
includes `receiptVerified` (`true`, `false` + `receiptCheck`, or `null` if the
|
|
18
|
+
verifier isn't installed). It recovers the broker key (pinned from
|
|
19
|
+
`GET /v1/receipt-key`), and checks channel binding, a monotonic sequence (no
|
|
20
|
+
replay/gap), and running totals within the funded amount. So the channel payment
|
|
21
|
+
is on-chain and the meter is auditable, without trusting the broker's word. Spec:
|
|
22
|
+
https://inference.bsvkey.com/usage-receipts.md
|
|
23
|
+
|
|
15
24
|
## Quick start
|
|
16
25
|
1. **Fund a channel once** at https://inference.bsvkey.com (BRC-100 wallet, or
|
|
17
26
|
load a key in-page). Copy the key it returns: `channelId:channelSecret`.
|
package/package.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@bsvkey/inference-mcp",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "MCP server: buy Claude & Grok inference metered per token, settled in BSV, through the hosted inference.bsvkey.com gateway. Channels or per-call x402.",
|
|
5
|
-
"mcpName": "io.github.BSVKey/inference-mcp",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"bin": { "bsvkey-inference-mcp": "server.js" },
|
|
8
|
-
"main": "server.js",
|
|
9
|
-
"engines": { "node": ">=18" },
|
|
10
|
-
"files": ["server.js", "SKILL.md", "README.md", "LICENSE"],
|
|
11
|
-
"optionalDependencies": {
|
|
12
|
-
"@bsvkey/x402-bsv-client": "^0.
|
|
13
|
-
"@bsv/sdk": "^
|
|
14
|
-
},
|
|
15
|
-
"keywords": ["mcp", "model-context-protocol", "bsv", "inference", "pay-per-token", "http-402", "x402", "claude", "grok", "micropayments", "agents"],
|
|
16
|
-
"author": "Embryo Space Inc. DBA BSVKey",
|
|
17
|
-
"license": "MIT",
|
|
18
|
-
"homepage": "https://inference.bsvkey.com",
|
|
19
|
-
"repository": { "type": "git", "url": "git+https://github.com/BSVKey/inference-mcp.git" },
|
|
20
|
-
"bugs": { "url": "https://github.com/BSVKey/inference-mcp/issues" }
|
|
21
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@bsvkey/inference-mcp",
|
|
3
|
+
"version": "1.1.2",
|
|
4
|
+
"description": "MCP server: buy Claude & Grok inference metered per token, settled in BSV, through the hosted inference.bsvkey.com gateway. Channels or per-call x402.",
|
|
5
|
+
"mcpName": "io.github.BSVKey/inference-mcp",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": { "bsvkey-inference-mcp": "server.js" },
|
|
8
|
+
"main": "server.js",
|
|
9
|
+
"engines": { "node": ">=18" },
|
|
10
|
+
"files": ["server.js", "SKILL.md", "README.md", "LICENSE"],
|
|
11
|
+
"optionalDependencies": {
|
|
12
|
+
"@bsvkey/x402-bsv-client": "^0.3.0",
|
|
13
|
+
"@bsv/sdk": "^2"
|
|
14
|
+
},
|
|
15
|
+
"keywords": ["mcp", "model-context-protocol", "bsv", "inference", "pay-per-token", "http-402", "x402", "claude", "grok", "micropayments", "agents"],
|
|
16
|
+
"author": "Embryo Space Inc. DBA BSVKey",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"homepage": "https://inference.bsvkey.com",
|
|
19
|
+
"repository": { "type": "git", "url": "git+https://github.com/BSVKey/inference-mcp.git" },
|
|
20
|
+
"bugs": { "url": "https://github.com/BSVKey/inference-mcp/issues" }
|
|
21
|
+
}
|
package/server.js
CHANGED
|
@@ -45,6 +45,44 @@ async function http(method, path, { headers = {}, body } = {}) {
|
|
|
45
45
|
return { ok: res.ok, status: res.status, json };
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// Best-effort, offline verification of the broker's signed usage receipt. Local
|
|
49
|
+
// only (a secp256k1 recovery + sha256): no payment, no network beyond a one-time
|
|
50
|
+
// key fetch, no effect on the x402 path. Gated on the optional
|
|
51
|
+
// @bsvkey/x402-bsv-client verifier so the server keeps zero REQUIRED deps: if it
|
|
52
|
+
// isn't installed we return verified:null (skipped), never an error.
|
|
53
|
+
let _verifier; // module | false | undefined
|
|
54
|
+
let _brokerKey; // hex | null | undefined
|
|
55
|
+
const _seen = new Map(); // channelId -> { seq, cumSats, cumTokens } (this session)
|
|
56
|
+
async function loadVerifier() {
|
|
57
|
+
if (_verifier !== undefined) return _verifier;
|
|
58
|
+
try { _verifier = await import('@bsvkey/x402-bsv-client/usage-receipt'); } catch { _verifier = false; }
|
|
59
|
+
return _verifier;
|
|
60
|
+
}
|
|
61
|
+
async function brokerReceiptKey() {
|
|
62
|
+
if (_brokerKey !== undefined) return _brokerKey;
|
|
63
|
+
try { const r = await http('GET', '/receipt-key'); _brokerKey = r.ok ? (r.json.receiptPubKey || null) : null; } catch { _brokerKey = null; }
|
|
64
|
+
return _brokerKey;
|
|
65
|
+
}
|
|
66
|
+
async function verifyUsageReceipt(receipt) {
|
|
67
|
+
if (!receipt) return { verified: null, reason: 'no receipt returned' };
|
|
68
|
+
const V = await loadVerifier();
|
|
69
|
+
if (!V) return { verified: null, reason: 'verifier not installed (npm i @bsvkey/x402-bsv-client)' };
|
|
70
|
+
const one = await V.verifyReceipt(receipt);
|
|
71
|
+
if (!one.ok) return { verified: false, reason: one.reason };
|
|
72
|
+
const pinned = await brokerReceiptKey();
|
|
73
|
+
if (pinned && one.signer !== pinned) return { verified: false, reason: 'signer_not_pinned_broker_key' };
|
|
74
|
+
if (receipt.cumSats > receipt.fundedSats) return { verified: false, reason: 'cumSats_exceeds_funded' };
|
|
75
|
+
const prev = _seen.get(receipt.channelId);
|
|
76
|
+
if (prev) {
|
|
77
|
+
// Continuity across calls this session actually observed.
|
|
78
|
+
if (receipt.seq !== prev.seq + 1) return { verified: false, reason: receipt.seq === prev.seq ? 'replayed_seq' : (receipt.seq < prev.seq ? 'seq_regressed' : 'seq_gap') };
|
|
79
|
+
if (receipt.cumSats !== prev.cumSats + receipt.sats) return { verified: false, reason: 'cumSats_does_not_reconcile' };
|
|
80
|
+
if (receipt.cumTokens !== prev.cumTokens + receipt.inputTokens + receipt.outputTokens) return { verified: false, reason: 'cumTokens_does_not_reconcile' };
|
|
81
|
+
}
|
|
82
|
+
_seen.set(receipt.channelId, { seq: receipt.seq, cumSats: receipt.cumSats, cumTokens: receipt.cumTokens });
|
|
83
|
+
return { verified: true, ...(prev ? {} : { note: 'baseline: signature + funded-conservation checked; seq continuity verified from here' }) };
|
|
84
|
+
}
|
|
85
|
+
|
|
48
86
|
export const TOOLS = [
|
|
49
87
|
{
|
|
50
88
|
name: 'list_models',
|
|
@@ -141,6 +179,7 @@ async function callTool(name, args = {}) {
|
|
|
141
179
|
throw new Error(typeof msg === 'string' ? msg : JSON.stringify(msg));
|
|
142
180
|
}
|
|
143
181
|
const x = r.json.x_bsv || {};
|
|
182
|
+
const rc = await verifyUsageReceipt(x.usageReceipt);
|
|
144
183
|
return {
|
|
145
184
|
model: r.json.model || args.model,
|
|
146
185
|
completion: r.json.choices?.[0]?.message?.content ?? '',
|
|
@@ -148,6 +187,12 @@ async function callTool(name, args = {}) {
|
|
|
148
187
|
routedTo: x.routedTo,
|
|
149
188
|
balanceSatsAfter: x.balanceSatsAfter,
|
|
150
189
|
truncated: x.truncated || false,
|
|
190
|
+
// Offline-verified signed usage receipt (see usage-receipt spec). null =
|
|
191
|
+
// not checked (verifier not installed); false w/ receiptCheck = a real
|
|
192
|
+
// mismatch, treat the meter as untrusted for this call.
|
|
193
|
+
receiptVerified: rc.verified,
|
|
194
|
+
...(rc.reason ? { receiptCheck: rc.reason } : {}),
|
|
195
|
+
usageReceipt: x.usageReceipt,
|
|
151
196
|
};
|
|
152
197
|
}
|
|
153
198
|
case 'channel_balance': {
|