@bsvkey/inference-mcp 1.0.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 (5) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +68 -0
  3. package/SKILL.md +45 -0
  4. package/package.json +14 -0
  5. package/server.js +213 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Embryo Space Inc. (BSVKey)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # @bsvkey/inference-mcp
2
+
3
+ An MCP server that lets any agent buy **Claude & Grok inference metered per token,
4
+ settled in BSV**, through the hosted gateway at **inference.bsvkey.com**. Zero
5
+ dependencies (Node ≥ 18, uses global `fetch`). It's a thin HTTP client — it never
6
+ holds your keys or runs models; every call is billed through the gateway.
7
+
8
+ Tools: `list_models`, `infer`, `channel_balance`, `open_channel`.
9
+
10
+ ## Quick start
11
+ 1. **Fund a channel once** at https://inference.bsvkey.com (BRC-100 wallet, or
12
+ load a key in-page). Copy the key it returns: `channelId:channelSecret`.
13
+ 2. **Add the MCP server** to your agent host (below), with that key in
14
+ `BSVKEY_API_KEY`.
15
+ 3. Ask your agent to run inference — it calls `infer` and pays per token.
16
+
17
+ `list_models` and `open_channel` work with no key; `infer` and `channel_balance`
18
+ need a funded channel key.
19
+
20
+ ## Install
21
+
22
+ ### Claude Code
23
+ ```bash
24
+ claude mcp add bsvkey-inference \
25
+ --env BSVKEY_API_KEY=channelId:channelSecret \
26
+ -- node /absolute/path/to/integrations/mcp/server.js
27
+ ```
28
+ Once published to npm, replace the command with `npx -y @bsvkey/inference-mcp`.
29
+
30
+ ### Claude Desktop / Codex / any MCP host (JSON config)
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "bsvkey-inference": {
35
+ "command": "node",
36
+ "args": ["/absolute/path/to/integrations/mcp/server.js"],
37
+ "env": { "BSVKEY_API_KEY": "channelId:channelSecret" }
38
+ }
39
+ }
40
+ }
41
+ ```
42
+ After npm publish: `"command": "npx", "args": ["-y", "@bsvkey/inference-mcp"]`.
43
+
44
+ ## Configuration (env)
45
+ | Var | Default | Meaning |
46
+ |---|---|---|
47
+ | `BSVKEY_BASE_URL` | `https://inference.bsvkey.com/v1` | Gateway base URL (set to a self-hosted deployment if you run your own). |
48
+ | `BSVKEY_API_KEY` | — | `channelId:channelSecret` for a funded channel. Optional; can also be passed per call as `apiKey`. |
49
+
50
+ ## Verify it's wired (no key needed)
51
+ ```bash
52
+ printf '%s\n' \
53
+ '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
54
+ '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
55
+ '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_models","arguments":{}}}' \
56
+ | node server.js
57
+ ```
58
+
59
+ ## Marketplace listing blurb
60
+ > **BSV Inference** — Pay-per-token Claude & Grok, settled in BSV. Prepay a channel
61
+ > once, then meter every token with no subscription, account, or card. OpenAI-
62
+ > compatible, optional live web search, on-chain settlement. MCP + portable SKILL.md.
63
+
64
+ ## Publishing (operator)
65
+ - To make `npx @bsvkey/inference-mcp` work, publish this folder to npm
66
+ (`npm publish --access public`) from an account that owns the `@bsvkey` scope.
67
+ - To list on a skills marketplace (e.g. bopen.ai), submit `SKILL.md` + this README.
68
+ - Publishing is an operator action requiring your own credentials — not done here.
package/SKILL.md ADDED
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: bsvkey-inference
3
+ description: Use when an agent needs to pay PER TOKEN for Claude or Grok inference settled in BSV micropayments — buying LLM calls without a subscription, account, or credit card, through the hosted inference.bsvkey.com gateway. Triggers on "pay per token", "BSV inference", "micropayment inference", "buy an LLM call with bitcoin", "metered inference", "pay-as-you-go LLM", "settle inference on-chain", "HTTP 402 inference".
4
+ ---
5
+
6
+ # BSV Inference — pay-per-token LLM calls, settled in BSV
7
+
8
+ An OpenAI-compatible gateway that meters every token and settles in BSV
9
+ micropayments. An agent prepays a channel once, then calls `infer` as many times
10
+ as it wants — each call metered per token and drawn from the channel balance. No
11
+ subscription, no account, no card. Models: Claude (haiku/sonnet/opus) and Grok
12
+ (grok-4.3 fast, grok-4.6), with optional live web search.
13
+
14
+ Install it as an MCP server (see README.md) — this skill assumes those four tools
15
+ are available: `list_models`, `open_channel`, `infer`, `channel_balance`.
16
+
17
+ ## When to use
18
+ - The user/agent wants to buy inference on demand and pay only for what it uses.
19
+ - Micropayment / per-token billing is preferred over a monthly plan.
20
+ - Settlement in BSV (or an on-chain, account-free payment) is desired.
21
+
22
+ ## How it works (the flow)
23
+ 1. **`list_models`** — no key needed. Returns live per-1,000-token prices in
24
+ satoshis (they float against the live BSV/USD rate) plus which models are
25
+ available. Use it to pick a model or a policy (`auto` = cheapest capable).
26
+ 2. **Fund a channel once.** Funding is a real BSV payment a wallet must sign, so
27
+ it's done at the website, not by the agent. Call **`open_channel`** for the
28
+ URL + steps: open `https://inference.bsvkey.com`, fund with a BRC-100 wallet
29
+ (or load a key in-page), and copy the returned key `channelId:channelSecret`.
30
+ Put it in `BSVKEY_API_KEY` (or pass `apiKey` to `infer`).
31
+ 3. **`infer`** — run inference, paid per token from the channel. Args: `prompt`,
32
+ `model` (default `auto`), optional `system`, `maxTokens`, `webSearch`. Returns
33
+ the completion plus a receipt: `charge` (sats), `routedTo`, `balanceSatsAfter`,
34
+ and `truncated` (true if a long web search was cut at the host time limit but
35
+ still billed).
36
+ 4. **`channel_balance`** — check remaining balance, spend, and request count.
37
+
38
+ ## Notes for the agent
39
+ - Payments are REAL BSV. Treat channel funds like money: fund a small amount, top
40
+ up as needed.
41
+ - `auto` routing picks the cheapest capable model; name a model explicitly
42
+ (`claude-sonnet-5`, `grok-4.3`, …) when quality or a specific provider matters.
43
+ - `webSearch: true` adds a small per-search fee and lets the model use live web
44
+ data; grok-4.6 web searches can be slow and may be truncated (still billed).
45
+ - To point at a self-hosted deployment, set `BSVKEY_BASE_URL`.
package/package.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "@bsvkey/inference-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server: buy Claude & Grok inference metered per token, settled in BSV, through the hosted inference.bsvkey.com gateway.",
5
+ "type": "module",
6
+ "bin": { "bsvkey-inference-mcp": "server.js" },
7
+ "main": "server.js",
8
+ "engines": { "node": ">=18" },
9
+ "files": ["server.js", "SKILL.md", "README.md", "LICENSE"],
10
+ "keywords": ["mcp", "model-context-protocol", "bsv", "inference", "pay-per-token", "http-402", "x402", "claude", "grok", "micropayments", "agents"],
11
+ "author": "Embryo Space Inc. DBA BSVKey",
12
+ "license": "MIT",
13
+ "homepage": "https://inference.bsvkey.com"
14
+ }
package/server.js ADDED
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env node
2
+ // BSV Inference — remote MCP server (zero dependencies).
3
+ //
4
+ // A thin Model-Context-Protocol client for the HOSTED broker at
5
+ // inference.bsvkey.com. Any MCP-capable agent host (Claude Code, Claude Desktop,
6
+ // Codex, OpenCode, custom agents) adds this one command and can then buy Claude
7
+ // or Grok inference metered PER TOKEN, settled in BSV — every call is billed
8
+ // through the hosted gateway, so usage flows to the operator's wallet.
9
+ //
10
+ // This is NOT the broker. It never holds keys or runs models; it just talks HTTP
11
+ // to the public API. Point it at your own deployment with BSVKEY_BASE_URL.
12
+ //
13
+ // Config (environment):
14
+ // BSVKEY_BASE_URL default https://inference.bsvkey.com/v1
15
+ // BSVKEY_API_KEY "channelId:channelSecret" for a funded channel (optional —
16
+ // can also be passed per call as `apiKey`). Open + fund a
17
+ // channel once at the website, then paste the key here.
18
+ //
19
+ // Run: node server.js (Node >= 18 for global fetch)
20
+
21
+ const BASE = (process.env.BSVKEY_BASE_URL || 'https://inference.bsvkey.com/v1').replace(/\/+$/, '');
22
+ const SITE = BASE.replace(/\/v1$/, '');
23
+ const ENV_KEY = process.env.BSVKEY_API_KEY || '';
24
+
25
+ const PROTOCOL_VERSION = '2024-11-05';
26
+ const SERVER_INFO = { name: 'bsvkey-inference', version: '1.0.0' };
27
+
28
+ // Resolve a channel API key ("channelId:channelSecret") from arg or env.
29
+ function keyParts(apiKey) {
30
+ const k = String(apiKey || ENV_KEY || '').trim();
31
+ const i = k.indexOf(':');
32
+ if (i < 0) return null;
33
+ return { id: k.slice(0, i), secret: k.slice(i + 1), raw: k };
34
+ }
35
+
36
+ async function http(method, path, { headers = {}, body } = {}) {
37
+ const res = await fetch(BASE + path, {
38
+ method,
39
+ headers: { ...(body ? { 'content-type': 'application/json' } : {}), ...headers },
40
+ body: body ? JSON.stringify(body) : undefined,
41
+ });
42
+ const text = await res.text();
43
+ let json;
44
+ try { json = text ? JSON.parse(text) : {}; } catch { json = { _raw: text.slice(0, 500) }; }
45
+ return { ok: res.ok, status: res.status, json };
46
+ }
47
+
48
+ export const TOOLS = [
49
+ {
50
+ name: 'list_models',
51
+ description:
52
+ 'List the models this BSV inference gateway sells, with LIVE retail price (satoshis per 1,000 tokens) at the current BSV/USD rate. No key needed. Call first to choose a model.',
53
+ inputSchema: { type: 'object', properties: {}, additionalProperties: false },
54
+ },
55
+ {
56
+ name: 'infer',
57
+ description:
58
+ 'Run one metered inference (OpenAI-compatible), paid per token in BSV from your prepaid channel. Returns the completion plus a receipt: satoshis charged, model routed to, and remaining balance. Requires a funded channel key (apiKey or BSVKEY_API_KEY).',
59
+ inputSchema: {
60
+ type: 'object',
61
+ properties: {
62
+ prompt: { type: 'string', description: 'The user prompt.' },
63
+ model: { type: 'string', description: 'Model id or policy: auto|cheapest|best, claude-*, grok-*.', default: 'auto' },
64
+ system: { type: 'string', description: 'Optional system prompt.' },
65
+ maxTokens: { type: 'integer', description: 'Max output tokens.', default: 512 },
66
+ webSearch: { type: 'boolean', description: 'Let the model search the live web (adds a per-search fee).', default: false },
67
+ apiKey: { type: 'string', description: 'channelId:channelSecret for a funded channel. Omit to use BSVKEY_API_KEY.' },
68
+ },
69
+ required: ['prompt'],
70
+ additionalProperties: false,
71
+ },
72
+ },
73
+ {
74
+ name: 'channel_balance',
75
+ description: 'Check a prepaid channel’s remaining BSV balance, spend, and request count.',
76
+ inputSchema: {
77
+ type: 'object',
78
+ properties: { apiKey: { type: 'string', description: 'channelId:channelSecret. Omit to use BSVKEY_API_KEY.' } },
79
+ additionalProperties: false,
80
+ },
81
+ },
82
+ {
83
+ name: 'open_channel',
84
+ description:
85
+ 'Explains how to open + fund a prepaid channel. Funding is a real BSV payment signed by a wallet, so it is done once at the website; you then paste the returned channel key here (or set BSVKEY_API_KEY).',
86
+ inputSchema: { type: 'object', properties: {}, additionalProperties: false },
87
+ },
88
+ ];
89
+
90
+ async function callTool(name, args = {}) {
91
+ switch (name) {
92
+ case 'list_models': {
93
+ const r = await http('GET', '/pricebook');
94
+ if (!r.ok) throw new Error(`pricebook ${r.status}`);
95
+ const pb = r.json;
96
+ const models = Object.entries(pb.models || {}).map(([id, m]) => ({
97
+ id,
98
+ label: m.label,
99
+ available: m.available !== false,
100
+ provider: m.provider,
101
+ retailInputSatsPer1k: m.retailInputSatsPer1k,
102
+ retailOutputSatsPer1k: m.retailOutputSatsPer1k,
103
+ }));
104
+ return { base: BASE, bsvUsd: pb.bsvUsd, rate: pb.rate, models, webSearch: pb.webSearch };
105
+ }
106
+ case 'infer': {
107
+ const k = keyParts(args.apiKey);
108
+ if (!k) throw new Error('No channel key. Pass apiKey "channelId:channelSecret" or set BSVKEY_API_KEY. Open one via the open_channel tool.');
109
+ const r = await http('POST', '/chat/completions', {
110
+ headers: { authorization: `Bearer ${k.raw}` },
111
+ body: {
112
+ model: args.model || 'auto',
113
+ messages: [
114
+ ...(args.system ? [{ role: 'system', content: args.system }] : []),
115
+ { role: 'user', content: String(args.prompt || '') },
116
+ ],
117
+ max_tokens: args.maxTokens || 512,
118
+ web_search: args.webSearch === true,
119
+ },
120
+ });
121
+ if (!r.ok) {
122
+ const msg = r.json?.error?.message || r.json?.error || `inference failed (${r.status})`;
123
+ throw new Error(typeof msg === 'string' ? msg : JSON.stringify(msg));
124
+ }
125
+ const x = r.json.x_bsv || {};
126
+ return {
127
+ model: r.json.model || args.model,
128
+ completion: r.json.choices?.[0]?.message?.content ?? '',
129
+ charge: x.charge,
130
+ routedTo: x.routedTo,
131
+ balanceSatsAfter: x.balanceSatsAfter,
132
+ truncated: x.truncated || false,
133
+ };
134
+ }
135
+ case 'channel_balance': {
136
+ const k = keyParts(args.apiKey);
137
+ if (!k) throw new Error('No channel key. Pass apiKey "channelId:channelSecret" or set BSVKEY_API_KEY.');
138
+ const r = await http('GET', `/channels/${encodeURIComponent(k.id)}`, { headers: { 'x-bsv-channel-secret': k.secret } });
139
+ if (!r.ok) {
140
+ const msg = r.json?.error?.message || r.json?.error || `balance check failed (${r.status})`;
141
+ throw new Error(typeof msg === 'string' ? msg : JSON.stringify(msg));
142
+ }
143
+ return r.json;
144
+ }
145
+ case 'open_channel': {
146
+ return {
147
+ note: 'Funding a channel is a real BSV payment your wallet must sign, so open one at the website (BRC-100 wallet, or load a key in-page), then paste the channel key here / set BSVKEY_API_KEY.',
148
+ fundUrl: SITE,
149
+ accountUrl: `${SITE}/account.html`,
150
+ steps: [
151
+ `Open ${SITE} and use the "Pay with your BSV wallet" widget to fund a channel.`,
152
+ 'Copy the channel key it returns (format: channelId:channelSecret).',
153
+ 'Set BSVKEY_API_KEY to that value (or pass apiKey to infer), then call infer freely until the balance runs out.',
154
+ ],
155
+ };
156
+ }
157
+ default:
158
+ throw new Error(`unknown tool: ${name}`);
159
+ }
160
+ }
161
+
162
+ // --- MCP JSON-RPC over stdio ------------------------------------------------
163
+ function result(id, value) { return { jsonrpc: '2.0', id, result: value }; }
164
+ function rpcError(id, code, message) { return { jsonrpc: '2.0', id, error: { code, message } }; }
165
+
166
+ export async function handleMessage(msg) {
167
+ if (!msg || msg.jsonrpc !== '2.0') return rpcError(msg?.id ?? null, -32600, 'invalid request');
168
+ const { id, method, params } = msg;
169
+ switch (method) {
170
+ case 'initialize':
171
+ return result(id, { protocolVersion: PROTOCOL_VERSION, capabilities: { tools: {} }, serverInfo: SERVER_INFO });
172
+ case 'notifications/initialized':
173
+ case 'initialized':
174
+ return null;
175
+ case 'ping':
176
+ return result(id, {});
177
+ case 'tools/list':
178
+ return result(id, { tools: TOOLS });
179
+ case 'tools/call': {
180
+ try {
181
+ const value = await callTool(params?.name, params?.arguments || {});
182
+ return result(id, { content: [{ type: 'text', text: JSON.stringify(value, null, 2) }] });
183
+ } catch (e) {
184
+ return result(id, { content: [{ type: 'text', text: `error: ${e.message}` }], isError: true });
185
+ }
186
+ }
187
+ default:
188
+ if (id === undefined) return null;
189
+ return rpcError(id, -32601, `method not found: ${method}`);
190
+ }
191
+ }
192
+
193
+ function runStdio() {
194
+ let buffer = '';
195
+ process.stdin.setEncoding('utf8');
196
+ process.stdin.on('data', async (chunk) => {
197
+ buffer += chunk;
198
+ let nl;
199
+ while ((nl = buffer.indexOf('\n')) !== -1) {
200
+ const line = buffer.slice(0, nl).trim();
201
+ buffer = buffer.slice(nl + 1);
202
+ if (!line) continue;
203
+ let msg;
204
+ try { msg = JSON.parse(line); } catch { process.stdout.write(JSON.stringify(rpcError(null, -32700, 'parse error')) + '\n'); continue; }
205
+ const res = await handleMessage(msg);
206
+ if (res) process.stdout.write(JSON.stringify(res) + '\n');
207
+ }
208
+ });
209
+ process.stderr.write(`[bsvkey-inference mcp] ready on stdio → ${BASE} (${TOOLS.length} tools)\n`);
210
+ }
211
+
212
+ const invokedDirectly = process.argv[1] && /server\.js$/.test(process.argv[1]);
213
+ if (invokedDirectly) runStdio();