sbor-mcp 1.1.4 → 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 +23 -1
- package/package.json +7 -2
- package/server.json +9 -3
- package/server.mjs +66 -11
package/README.md
CHANGED
|
@@ -32,17 +32,37 @@ It starts and waits silently. That is success. Ctrl+C to exit.
|
|
|
32
32
|
|
|
33
33
|
Claude Desktop: add that to `claude_desktop_config.json` and restart.
|
|
34
34
|
|
|
35
|
+
## Or connect by address, with nothing to install
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
https://mcp.sbor.xyz/mcp
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Streamable HTTP, no key, no sign-in. Works as a custom connector in Claude on
|
|
42
|
+
the web and in any MCP client that accepts a URL. The remote server is generated
|
|
43
|
+
from this one, so both give the same answers.
|
|
44
|
+
|
|
35
45
|
## Tools
|
|
36
46
|
|
|
37
47
|
| Tool | What it answers |
|
|
38
48
|
|---|---|
|
|
39
|
-
| `get_rate` | What does capital cost on Stacks right now |
|
|
49
|
+
| `get_rate` | What does capital cost on Stacks right now, and what does borrowing USDC against bitcoin cost |
|
|
40
50
|
| `compare_rate` | Is this offer above or below the market, and by how much |
|
|
41
51
|
| `list_markets` | Which venues make up the rate, with utilization and depth |
|
|
42
52
|
| `get_history` | How has the rate moved |
|
|
43
53
|
| `compare_chains` | How Stacks compares with Ethereum, Base, Hyperliquid, Solana and SOFR |
|
|
44
54
|
| `get_methodology` | How the number is built, and what it excludes |
|
|
45
55
|
|
|
56
|
+
All six tools are read-only.
|
|
57
|
+
|
|
58
|
+
## Benchmarks
|
|
59
|
+
|
|
60
|
+
`SBOR-USD`, `SBOR-BTC` and `SBOR-STX` are the Stacks indices. `BTC-COLLATERAL-USDC`
|
|
61
|
+
is a reference, not an SBOR index: what it costs to borrow USDC against bitcoin
|
|
62
|
+
wrapped by a custodian (cbBTC, WBTC), from the Morpho markets on Base and
|
|
63
|
+
Ethereum whose only collateral is that bitcoin. Use it to check a USDC loan
|
|
64
|
+
against bitcoin on those chains.
|
|
65
|
+
|
|
46
66
|
## Recommended use
|
|
47
67
|
|
|
48
68
|
**Use SBOR to stop, never to start.** Before borrowing, call `compare_rate`. If
|
|
@@ -81,3 +101,5 @@ writes, no telemetry. Set `SBOR_BASE` to point at a different host.
|
|
|
81
101
|
|
|
82
102
|
MIT. The published fixing is free to read. See
|
|
83
103
|
[llms.txt](https://sbor.xyz/llms.txt) for the full integration policy.
|
|
104
|
+
|
|
105
|
+
*Last updated 25 September 2026.*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sbor-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Check a lending rate before your agent borrows. The benchmark rate for Stacks, read from contract state. Free, no key, no rate limit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,5 +36,10 @@
|
|
|
36
36
|
"engines": {
|
|
37
37
|
"node": ">=20"
|
|
38
38
|
},
|
|
39
|
-
"mcpName": "xyz.sbor/sbor"
|
|
39
|
+
"mcpName": "xyz.sbor/sbor",
|
|
40
|
+
"files": [
|
|
41
|
+
"server.mjs",
|
|
42
|
+
"server.json",
|
|
43
|
+
"README.md"
|
|
44
|
+
]
|
|
40
45
|
}
|
package/server.json
CHANGED
|
@@ -7,17 +7,23 @@
|
|
|
7
7
|
"source": "github",
|
|
8
8
|
"subfolder": "mcp"
|
|
9
9
|
},
|
|
10
|
-
"version": "1.
|
|
10
|
+
"version": "1.2.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "sbor-mcp",
|
|
15
|
-
"version": "1.
|
|
15
|
+
"version": "1.2.0",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"title": "SBOR",
|
|
22
|
-
"websiteUrl": "https://sbor.xyz"
|
|
22
|
+
"websiteUrl": "https://sbor.xyz",
|
|
23
|
+
"remotes": [
|
|
24
|
+
{
|
|
25
|
+
"type": "streamable-http",
|
|
26
|
+
"url": "https://mcp.sbor.xyz/mcp"
|
|
27
|
+
}
|
|
28
|
+
]
|
|
23
29
|
}
|
package/server.mjs
CHANGED
|
@@ -18,13 +18,24 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
18
18
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
19
19
|
import { z } from "zod";
|
|
20
20
|
|
|
21
|
-
const VERSION = "1.
|
|
21
|
+
const VERSION = "1.2.0";
|
|
22
22
|
const BASE = process.env.SBOR_BASE || "https://sbor.xyz";
|
|
23
23
|
const UA = `sbor-mcp/${VERSION}`;
|
|
24
24
|
const TIMEOUT_MS = 10_000;
|
|
25
25
|
const STALE_AFTER_HOURS = 48;
|
|
26
26
|
const INDICES = ["SBOR-USD", "SBOR-BTC", "SBOR-STX"];
|
|
27
27
|
|
|
28
|
+
/* The cost of borrowing USDC against bitcoin, from the Morpho Blue markets on
|
|
29
|
+
Base and Ethereum whose only collateral is cbBTC or WBTC. A reference, not an
|
|
30
|
+
SBOR index, published from 25 September 2026. It is a benchmark an agent can
|
|
31
|
+
compare against, so the tools that take a benchmark accept it too. */
|
|
32
|
+
const BTC_REF = "BTC-COLLATERAL-USDC";
|
|
33
|
+
const BENCHMARKS = [...INDICES, BTC_REF];
|
|
34
|
+
const BTC_REF_WHAT = "a reference, not an SBOR index: what it costs to borrow USDC against bitcoin wrapped by a custodian (cbBTC, WBTC), from the Morpho markets on Base and Ethereum whose only collateral is that bitcoin";
|
|
35
|
+
|
|
36
|
+
/* All SBOR tools only read published data. */
|
|
37
|
+
const READ_ONLY = { readOnlyHint: true, openWorldHint: false };
|
|
38
|
+
|
|
28
39
|
/* Small cache so an agent asking three questions in a row makes one request. */
|
|
29
40
|
const cache = new Map();
|
|
30
41
|
async function getJson(path, ttlMs = 60_000){
|
|
@@ -72,6 +83,7 @@ const server = new McpServer({ name: "sbor", version: VERSION });
|
|
|
72
83
|
/* ------------------------------------------------------------------ */
|
|
73
84
|
server.registerTool("get_rate", {
|
|
74
85
|
title: "Get the current SBOR fixing",
|
|
86
|
+
annotations: READ_ONLY,
|
|
75
87
|
description:
|
|
76
88
|
"The benchmark borrow and supply rate for lending on Stacks, read from " +
|
|
77
89
|
"lending contract state. Use this to judge whether a lending offer is good: " +
|
|
@@ -79,15 +91,22 @@ server.registerTool("get_rate", {
|
|
|
79
91
|
"supplying below the supply rate means earning less. Returns every currency " +
|
|
80
92
|
"index unless one is named. Always read the freshness line first.",
|
|
81
93
|
inputSchema: {
|
|
82
|
-
index: z.enum(
|
|
94
|
+
index: z.enum(BENCHMARKS).optional().describe(`Currency index, or ${BTC_REF} for borrowing USDC against bitcoin on Base and Ethereum. Omit for all of them.`)
|
|
83
95
|
}
|
|
84
96
|
}, async ({ index }) => {
|
|
85
97
|
try {
|
|
86
98
|
const d = await getJson("/api/v1/latest.json");
|
|
99
|
+
const ref = d.bitcoinCollateralUsdc;
|
|
100
|
+
const refLine = ref && typeof ref.borrow === "number"
|
|
101
|
+
? `${BTC_REF}: borrow ${pct(ref.borrow)}, supply ${pct(ref.supply)}, depth ${usd(ref.depthUsd)}. This is ${BTC_REF_WHAT}.`
|
|
102
|
+
: `${BTC_REF}: not published in the current fixing${ref ? ", because not every market could be read" : ""}. Treat it as unknown, never as zero.`;
|
|
103
|
+
if (index === BTC_REF)
|
|
104
|
+
return text([freshness(d), refLine, `Source: ${BASE}/api/v1/latest.json`].join("\n"));
|
|
87
105
|
if (index && !d.indices[index])
|
|
88
106
|
return text(`${index} is not published in the current fixing. `
|
|
89
|
-
+ `When a market cannot be read,
|
|
90
|
-
+ `
|
|
107
|
+
+ `When a market cannot be read, or its rate is not set by the market, SBOR `
|
|
108
|
+
+ `omits the index rather than publishing a figure it cannot stand behind. `
|
|
109
|
+
+ `Treat it as unknown, never as zero. `
|
|
91
110
|
+ `Published today: ${Object.keys(d.indices).join(", ")}.`);
|
|
92
111
|
|
|
93
112
|
const wanted = index ? { [index]: d.indices[index] } : d.indices;
|
|
@@ -104,6 +123,7 @@ server.registerTool("get_rate", {
|
|
|
104
123
|
freshness(d),
|
|
105
124
|
...lines,
|
|
106
125
|
omitted.length ? `Not published: ${omitted.join(", ")}. A market that cannot be read is omitted, not estimated.` : "",
|
|
126
|
+
index ? "" : refLine,
|
|
107
127
|
d.poxReference ? `Proof of Transfer staking yield ${pct(d.poxReference.apy)}. A staking yield, not a lending rate. Never add it to one.` : "",
|
|
108
128
|
`Basis: ${d.basis}`,
|
|
109
129
|
`Source: ${BASE}/api/v1/latest.json`
|
|
@@ -116,18 +136,21 @@ server.registerTool("get_rate", {
|
|
|
116
136
|
/* ------------------------------------------------------------------ */
|
|
117
137
|
server.registerTool("compare_rate", {
|
|
118
138
|
title: "Compare a rate against the SBOR benchmark",
|
|
139
|
+
annotations: READ_ONLY,
|
|
119
140
|
description:
|
|
120
141
|
"Given a rate you have been offered, say whether it is above or below the " +
|
|
121
142
|
"market for that currency, and by how much. This is the main reason SBOR exists. " +
|
|
122
143
|
"Recommended use: if a borrow offer is more than 50 basis points above the " +
|
|
123
144
|
"benchmark, stop and ask a human. Use SBOR to stop, never to start. " +
|
|
145
|
+
`For a USDC loan against bitcoin (cbBTC or WBTC) on Base or Ethereum, compare with ${BTC_REF}; ` +
|
|
146
|
+
"for lending on Stacks, with the index for the currency. " +
|
|
124
147
|
"This tool refuses rather than guesses: an error result means there is no " +
|
|
125
148
|
"trustworthy answer, not that the rate is bad.",
|
|
126
149
|
inputSchema: {
|
|
127
150
|
rate: z.number().gt(0).lte(100)
|
|
128
151
|
.describe("The rate offered, as a percentage between 0 and 100. 4.2 means 4.2%, not 0.042."),
|
|
129
152
|
side: z.enum(["borrow", "supply"]).describe("Whether you would be borrowing or supplying."),
|
|
130
|
-
index: z.enum(
|
|
153
|
+
index: z.enum(BENCHMARKS).describe(`Which benchmark: a Stacks currency index, or ${BTC_REF} for borrowing USDC against bitcoin on Base and Ethereum.`)
|
|
131
154
|
}
|
|
132
155
|
}, async ({ rate, side, index }) => {
|
|
133
156
|
try {
|
|
@@ -143,7 +166,16 @@ server.registerTool("compare_rate", {
|
|
|
143
166
|
if (a > STALE_AFTER_HOURS)
|
|
144
167
|
return refuse(`The last fixing is ${a.toFixed(1)} hours old, past the ${STALE_AFTER_HOURS} hour limit. No verdict on stale data. Fall back to your own logic.`);
|
|
145
168
|
|
|
146
|
-
|
|
169
|
+
/* The bitcoin-collateral reference has the same shape where it matters:
|
|
170
|
+
a borrow and supply rate, and markets. */
|
|
171
|
+
const isRef = index === BTC_REF;
|
|
172
|
+
const ref = d.bitcoinCollateralUsdc;
|
|
173
|
+
const ix = isRef
|
|
174
|
+
? (ref && typeof ref.borrow === "number"
|
|
175
|
+
? { borrow: ref.borrow, supply: ref.supply, venues: ref.markets.map(m => m.chain),
|
|
176
|
+
markets: ref.markets.map(m => ({ ...m, venue: `Morpho on ${m.chain}`, asset: `${m.collateral}/USDC` })) }
|
|
177
|
+
: null)
|
|
178
|
+
: d.indices[index];
|
|
147
179
|
if (!ix)
|
|
148
180
|
return refuse(`${index} is not published in the current fixing, so there is no benchmark to compare against. `
|
|
149
181
|
+ `Treat this as unknown, not as zero. Published today: ${Object.keys(d.indices).join(", ")}.`);
|
|
@@ -185,7 +217,8 @@ server.registerTool("compare_rate", {
|
|
|
185
217
|
`${index} ${side} benchmark is ${pct(bench)}. Your ${rate.toFixed(2)}% is ${verdict}.`,
|
|
186
218
|
stop,
|
|
187
219
|
best ? `Best constituent today: ${best.venue} ${best.asset} at ${pct(best[side])}, utilization ${pct(best.utilization)}.` : "",
|
|
188
|
-
ix.venues.length === 1 ? `Note: this index covers one venue, so it is a reading of that venue rather than a market average.` : "",
|
|
220
|
+
!isRef && ix.venues.length === 1 ? `Note: this index covers one venue, so it is a reading of that venue rather than a market average.` : "",
|
|
221
|
+
isRef ? `${BTC_REF} is ${BTC_REF_WHAT}.` : "",
|
|
189
222
|
freshness(d)
|
|
190
223
|
].filter(Boolean).join("\n"));
|
|
191
224
|
} catch (e) { return fail(e); }
|
|
@@ -196,15 +229,26 @@ server.registerTool("compare_rate", {
|
|
|
196
229
|
/* ------------------------------------------------------------------ */
|
|
197
230
|
server.registerTool("list_markets", {
|
|
198
231
|
title: "List the lending markets behind a rate",
|
|
232
|
+
annotations: READ_ONLY,
|
|
199
233
|
description:
|
|
200
234
|
"Every venue and asset in an index, with its borrow rate, supply rate, " +
|
|
201
235
|
"utilization, depth and weight. Utilization explains why a rate sits where it does.",
|
|
202
236
|
inputSchema: {
|
|
203
|
-
index: z.enum(
|
|
237
|
+
index: z.enum(BENCHMARKS).optional().describe(`Currency index, or ${BTC_REF}. Omit for the Stacks indices.`)
|
|
204
238
|
}
|
|
205
239
|
}, async ({ index }) => {
|
|
206
240
|
try {
|
|
207
241
|
const d = await getJson("/api/v1/latest.json");
|
|
242
|
+
if (index === BTC_REF){
|
|
243
|
+
const ref = d.bitcoinCollateralUsdc;
|
|
244
|
+
if (!ref?.markets?.length) return text(`${BTC_REF} is not published in the current fixing. Treat it as unknown, not as zero.`);
|
|
245
|
+
return text([freshness(d), "", `${BTC_REF}, ${BTC_REF_WHAT}.`, ...ref.markets.map(m =>
|
|
246
|
+
` Morpho on ${m.chain}, ${m.collateral}/USDC: borrow ${pct(m.borrow)}, supply ${pct(m.supply)}, `
|
|
247
|
+
+ `utilization ${pct(m.utilization)}, depth ${usd(m.depthUsd)}`
|
|
248
|
+
+ (typeof m.weight === "number" ? `, weight ${(m.weight * 100).toFixed(1)}%` : "")),
|
|
249
|
+
ref.notRead ? `Not read today: ${ref.notRead.join("; ")}. The reference is withheld until every market is read.` : "",
|
|
250
|
+
"", `Source: ${BASE}/api/v1/latest.json`].filter(Boolean).join("\n"));
|
|
251
|
+
}
|
|
208
252
|
const entries = index
|
|
209
253
|
? (d.indices[index] ? [[index, d.indices[index]]] : [])
|
|
210
254
|
: Object.entries(d.indices);
|
|
@@ -225,6 +269,7 @@ server.registerTool("list_markets", {
|
|
|
225
269
|
/* ------------------------------------------------------------------ */
|
|
226
270
|
server.registerTool("get_history", {
|
|
227
271
|
title: "Get the SBOR history",
|
|
272
|
+
annotations: READ_ONLY,
|
|
228
273
|
description:
|
|
229
274
|
"Daily fixings since the index began. Use this to see whether a rate is " +
|
|
230
275
|
"unusual, or how the cost of capital has moved. Means are given per " +
|
|
@@ -279,11 +324,14 @@ server.registerTool("get_history", {
|
|
|
279
324
|
/* ------------------------------------------------------------------ */
|
|
280
325
|
server.registerTool("compare_chains", {
|
|
281
326
|
title: "Compare Stacks rates against the same markets on other chains",
|
|
327
|
+
annotations: READ_ONLY,
|
|
282
328
|
description:
|
|
283
329
|
"The same asset classes on the largest lending markets on Ethereum, Base, " +
|
|
284
|
-
"Hyperliquid and Solana, plus SOFR, the US repo rate
|
|
285
|
-
"
|
|
286
|
-
"
|
|
330
|
+
"Hyperliquid and Solana, plus SOFR, the US repo rate, and what it costs to " +
|
|
331
|
+
"borrow USDC against bitcoin on Base and Ethereum. Context only: none of " +
|
|
332
|
+
"these is ever a constituent of an SBOR index. Aave on Ethereum and Base and " +
|
|
333
|
+
"the bitcoin-collateral markets are read from contract state; the other venues " +
|
|
334
|
+
"come from DefiLlama, so small differences are expected for those.",
|
|
287
335
|
inputSchema: {
|
|
288
336
|
index: z.enum(INDICES).optional().describe("Only markets comparable to this index. Omit for all.")
|
|
289
337
|
}
|
|
@@ -306,9 +354,15 @@ server.registerTool("compare_chains", {
|
|
|
306
354
|
+ ` A cheaper rate on Stacks reflects lower utilization, not lower risk.`
|
|
307
355
|
: "";
|
|
308
356
|
|
|
357
|
+
const ref = d.bitcoinCollateralUsdc;
|
|
358
|
+
const refLine = ref && typeof ref.borrow === "number" && (!index || index === "SBOR-USD")
|
|
359
|
+
? `Borrowing USDC against bitcoin (${BTC_REF}): ${pct(ref.borrow)}, depth ${usd(ref.depthUsd)}, from Morpho on Base and Ethereum, read from the contracts. A dollar on Stacks is borrowed against any crypto collateral, not only bitcoin.`
|
|
360
|
+
: "";
|
|
361
|
+
|
|
309
362
|
return text([
|
|
310
363
|
freshness(d), "",
|
|
311
364
|
"Stacks", ...stacks, "",
|
|
365
|
+
...(refLine ? [refLine, ""] : []),
|
|
312
366
|
ext.length ? "Elsewhere" : "", ...ext, ext.length ? "" : "",
|
|
313
367
|
sofr,
|
|
314
368
|
d.externalReference?.note ?? ""
|
|
@@ -321,6 +375,7 @@ server.registerTool("compare_chains", {
|
|
|
321
375
|
/* ------------------------------------------------------------------ */
|
|
322
376
|
server.registerTool("get_methodology", {
|
|
323
377
|
title: "How SBOR is calculated",
|
|
378
|
+
annotations: READ_ONLY,
|
|
324
379
|
description:
|
|
325
380
|
"The full methodology and integration policy: how the fixing is built, " +
|
|
326
381
|
"what is excluded and why, and what SBOR will and will not do. Read this " +
|