@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.
Files changed (3) hide show
  1. package/README.md +8 -5
  2. package/package.json +2 -2
  3. 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`, 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:
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.1.2",
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.3.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 rc = await verifyUsageReceipt(x.usageReceipt);
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: r.json.choices?.[0]?.message?.content ?? '',
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
  };