@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.
- package/LICENSE +21 -0
- package/README.md +68 -0
- package/SKILL.md +45 -0
- package/package.json +14 -0
- 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();
|