@bsvkey/inference-mcp 1.1.2 → 1.2.0
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 -5
- package/package.json +2 -2
- package/server.js +20 -5
package/README.md
CHANGED
|
@@ -14,11 +14,14 @@ optional `@bsvkey/x402-bsv-client` + `@bsv/sdk` packages (installed with this on
|
|
|
14
14
|
|
|
15
15
|
**Verifiable metering.** Each `infer` call returns a signed usage receipt, and the
|
|
16
16
|
tool auto-verifies it offline (with the optional packages installed): the result
|
|
17
|
-
includes `receiptVerified` (`true`, `false` + `receiptCheck`,
|
|
18
|
-
verifier isn't installed). It recovers the broker key (pinned
|
|
19
|
-
`GET /v1/receipt-key`)
|
|
20
|
-
replay/gap), and running totals within the funded amount
|
|
21
|
-
|
|
17
|
+
includes `receiptVerified` and `meterVerified` (`true`, `false` + `receiptCheck`,
|
|
18
|
+
or `null` if the verifier isn't installed). It recovers the broker key (pinned
|
|
19
|
+
from `GET /v1/receipt-key`); checks channel binding, a monotonic sequence (no
|
|
20
|
+
replay/gap), and running totals within the funded amount; **recomputes the charge**
|
|
21
|
+
from the published rate (you can never be overcharged); and **recomputes the token
|
|
22
|
+
count from the exact bytes** of your system/prompt and the completion, under the
|
|
23
|
+
pinned `bsvkey-meter/1` tokenizer. So the channel payment is on-chain and both the
|
|
24
|
+
meter and the charge are auditable, without trusting the broker's word. Spec:
|
|
22
25
|
https://inference.bsvkey.com/usage-receipts.md
|
|
23
26
|
|
|
24
27
|
## Quick start
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bsvkey/inference-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
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
5
|
"mcpName": "io.github.BSVKey/inference-mcp",
|
|
6
6
|
"type": "module",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"engines": { "node": ">=18" },
|
|
10
10
|
"files": ["server.js", "SKILL.md", "README.md", "LICENSE"],
|
|
11
11
|
"optionalDependencies": {
|
|
12
|
-
"@bsvkey/x402-bsv-client": "^0.
|
|
12
|
+
"@bsvkey/x402-bsv-client": "^0.4.0",
|
|
13
13
|
"@bsv/sdk": "^2"
|
|
14
14
|
},
|
|
15
15
|
"keywords": ["mcp", "model-context-protocol", "bsv", "inference", "pay-per-token", "http-402", "x402", "claude", "grok", "micropayments", "agents"],
|
package/server.js
CHANGED
|
@@ -63,7 +63,7 @@ async function brokerReceiptKey() {
|
|
|
63
63
|
try { const r = await http('GET', '/receipt-key'); _brokerKey = r.ok ? (r.json.receiptPubKey || null) : null; } catch { _brokerKey = null; }
|
|
64
64
|
return _brokerKey;
|
|
65
65
|
}
|
|
66
|
-
async function verifyUsageReceipt(receipt) {
|
|
66
|
+
async function verifyUsageReceipt(receipt, bytes = {}) {
|
|
67
67
|
if (!receipt) return { verified: null, reason: 'no receipt returned' };
|
|
68
68
|
const V = await loadVerifier();
|
|
69
69
|
if (!V) return { verified: null, reason: 'verifier not installed (npm i @bsvkey/x402-bsv-client)' };
|
|
@@ -71,6 +71,18 @@ async function verifyUsageReceipt(receipt) {
|
|
|
71
71
|
if (!one.ok) return { verified: false, reason: one.reason };
|
|
72
72
|
const pinned = await brokerReceiptKey();
|
|
73
73
|
if (pinned && one.signer !== pinned) return { verified: false, reason: 'signer_not_pinned_broker_key' };
|
|
74
|
+
// The charge recomputes from the receipt's own rates (overcharge is a dispute).
|
|
75
|
+
if (typeof V.verifyCharge === 'function') {
|
|
76
|
+
const c = V.verifyCharge(receipt);
|
|
77
|
+
if (!c.ok) return { verified: false, reason: `charge:${c.reason}` };
|
|
78
|
+
}
|
|
79
|
+
// The meter: recompute token counts + byte digests from the exact bytes we hold.
|
|
80
|
+
let meterVerified = null;
|
|
81
|
+
if (typeof V.verifyMeter === 'function' && (bytes.prompt !== undefined || bytes.completion !== undefined)) {
|
|
82
|
+
const mv = V.verifyMeter(receipt, bytes);
|
|
83
|
+
if (!mv.ok) return { verified: false, reason: `meter:${mv.reason}`, meterVerified: false };
|
|
84
|
+
meterVerified = true;
|
|
85
|
+
}
|
|
74
86
|
if (receipt.cumSats > receipt.fundedSats) return { verified: false, reason: 'cumSats_exceeds_funded' };
|
|
75
87
|
const prev = _seen.get(receipt.channelId);
|
|
76
88
|
if (prev) {
|
|
@@ -80,7 +92,7 @@ async function verifyUsageReceipt(receipt) {
|
|
|
80
92
|
if (receipt.cumTokens !== prev.cumTokens + receipt.inputTokens + receipt.outputTokens) return { verified: false, reason: 'cumTokens_does_not_reconcile' };
|
|
81
93
|
}
|
|
82
94
|
_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' }) };
|
|
95
|
+
return { verified: true, meterVerified, ...(prev ? {} : { note: 'baseline: signature + funded-conservation checked; seq continuity verified from here' }) };
|
|
84
96
|
}
|
|
85
97
|
|
|
86
98
|
export const TOOLS = [
|
|
@@ -179,18 +191,21 @@ async function callTool(name, args = {}) {
|
|
|
179
191
|
throw new Error(typeof msg === 'string' ? msg : JSON.stringify(msg));
|
|
180
192
|
}
|
|
181
193
|
const x = r.json.x_bsv || {};
|
|
182
|
-
const
|
|
194
|
+
const completion = r.json.choices?.[0]?.message?.content ?? '';
|
|
195
|
+
const rc = await verifyUsageReceipt(x.usageReceipt, { system: args.system, prompt: String(args.prompt || ''), completion });
|
|
183
196
|
return {
|
|
184
197
|
model: r.json.model || args.model,
|
|
185
|
-
completion
|
|
198
|
+
completion,
|
|
186
199
|
charge: x.charge,
|
|
187
200
|
routedTo: x.routedTo,
|
|
188
201
|
balanceSatsAfter: x.balanceSatsAfter,
|
|
189
202
|
truncated: x.truncated || false,
|
|
190
203
|
// Offline-verified signed usage receipt (see usage-receipt spec). null =
|
|
191
204
|
// not checked (verifier not installed); false w/ receiptCheck = a real
|
|
192
|
-
// mismatch, treat the meter as untrusted for this call.
|
|
205
|
+
// mismatch, treat the meter as untrusted for this call. meterVerified is
|
|
206
|
+
// true when the token count was recomputed from the exact bytes.
|
|
193
207
|
receiptVerified: rc.verified,
|
|
208
|
+
meterVerified: rc.meterVerified,
|
|
194
209
|
...(rc.reason ? { receiptCheck: rc.reason } : {}),
|
|
195
210
|
usageReceipt: x.usageReceipt,
|
|
196
211
|
};
|