@0xdivyanshh/tab-mcp 0.1.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 +89 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +36 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +343 -0
- package/dist/server.js.map +1 -0
- package/dist/stdio.d.ts +3 -0
- package/dist/stdio.d.ts.map +1 -0
- package/dist/stdio.js +32 -0
- package/dist/stdio.js.map +1 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# @0xdivyanshh/tab-mcp
|
|
2
|
+
|
|
3
|
+
**Tier 3 · MCP server**
|
|
4
|
+
|
|
5
|
+
Tab's verbs as MCP tools, so an agent in any MCP-capable runtime can transact on a tab with no
|
|
6
|
+
bespoke code. Seven tools; **only `tab_spend` moves money.**
|
|
7
|
+
|
|
8
|
+
## The decision the old README left open — settled
|
|
9
|
+
|
|
10
|
+
It offered two options: a standalone server (this), or documentation for loading
|
|
11
|
+
`@0xdivyanshh/tab-agentkit-plugin` into `@hashgraph/hedera-agent-kit-mcp`. **Shipping option 1.** Option 2 is
|
|
12
|
+
the better ecosystem story and it is not the one to ship first:
|
|
13
|
+
|
|
14
|
+
- It depends on **two** things that do not exist — the plugin is unwritten — plus an external
|
|
15
|
+
server accepting third-party plugins in a shape nobody here has verified.
|
|
16
|
+
- It cannot be driven by a judge in one step. This can: three lines of config and Claude Desktop is
|
|
17
|
+
spending on a Hedera tab.
|
|
18
|
+
- It is **additive later.** A plugin can wrap the same `@0xdivyanshh/tab-sdk` verbs whenever the Agent Kit path
|
|
19
|
+
is confirmed, and nothing here has to be undone.
|
|
20
|
+
|
|
21
|
+
## Use it
|
|
22
|
+
|
|
23
|
+
```jsonc
|
|
24
|
+
// claude_desktop_config.json (or any MCP client)
|
|
25
|
+
{
|
|
26
|
+
"mcpServers": {
|
|
27
|
+
"tab": {
|
|
28
|
+
"command": "node",
|
|
29
|
+
"args": [
|
|
30
|
+
"--experimental-strip-types",
|
|
31
|
+
"/absolute/path/to/ethonline/packages/mcp/src/stdio.ts"
|
|
32
|
+
],
|
|
33
|
+
"env": {
|
|
34
|
+
"TAB_GATEWAY_URL": "http://localhost:8080",
|
|
35
|
+
"TAB_ACCOUNT_ID": "0.0.10390398"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Then ask the model things like *"what can my tab afford?"*, *"buy the forecast from this URL for at
|
|
43
|
+
most 0.040000"*, or — the one worth trying — ***"why is my ceiling only 0.25?"***
|
|
44
|
+
|
|
45
|
+
That last question is the point of the whole rail. It answers with the published arithmetic and the
|
|
46
|
+
funding-graph evidence:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
0.0.10385196 0% BLOCKED
|
|
50
|
+
COMMON_FUNDER: the same account funded both this counterparty and the agent's tab
|
|
51
|
+
funded by 0.0.8812188 · tab funded by 0.0.8812188 ← SAME, which is what COMMON_FUNDER tests
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Tools
|
|
55
|
+
|
|
56
|
+
| Tool | Moves money | What it answers |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `tab_spend` | **yes** | Pay a seller, up to a cap. Returns paid / refused / failed |
|
|
59
|
+
| `tab_quote` | no | Would this be allowed? Reserves nothing |
|
|
60
|
+
| `tab_balance` | no | Position: balance, outstanding, holds, available, ceiling |
|
|
61
|
+
| `tab_ceiling` | no | The ceiling, what bound it, and the published inputs |
|
|
62
|
+
| `tab_receipts` | no | Recent ledger entries from the HCS receipt topic |
|
|
63
|
+
| `tab_counterparties` | no | Independence weights and the reason for each |
|
|
64
|
+
| `tab_health` | no | Gateway reachability, network, token, window |
|
|
65
|
+
|
|
66
|
+
Every read tool is annotated `readOnlyHint: true`; `tab_spend` is `destructiveHint: true` and
|
|
67
|
+
`idempotentHint: false`, so a client knows which one to confirm with a human.
|
|
68
|
+
|
|
69
|
+
## Invariants
|
|
70
|
+
|
|
71
|
+
- **A refusal is a structured RESULT, not an error.** `isError: true` makes a client surface a
|
|
72
|
+
failure and invites a model to retry — and a `CEILING_EXCEEDED` retried immediately is refused
|
|
73
|
+
again for the same reason. A refusal returns the rule, the evidence, and what to do instead,
|
|
74
|
+
because choosing different work is the correct response and the agent can only choose it if it is
|
|
75
|
+
told why. A `failed` outcome **is** an error, and hands back the hold id: infrastructure broke
|
|
76
|
+
after the decision to spend, so nobody knows whether the seller was paid, and a retry with a new
|
|
77
|
+
key would be a second payment.
|
|
78
|
+
- **No key material anywhere.** `McpConfig` is a URL and a tab id; `createTab` has no parameter
|
|
79
|
+
that would accept a key, and `boundaries.json` bars this package from `@hiero-ledger/sdk`. An MCP
|
|
80
|
+
server runs on a user's machine inside a client the user did not write — the surface where a
|
|
81
|
+
leaked key would hurt most, and so the one where the constraint is enforced rather than promised.
|
|
82
|
+
- **`stdout` is the protocol.** A single stray `console.log` corrupts the JSON-RPC stream and the
|
|
83
|
+
client reports a parse error rather than whatever actually went wrong. Every diagnostic goes to
|
|
84
|
+
stderr.
|
|
85
|
+
- **Same verbs, same semantics as `@0xdivyanshh/tab-sdk`.** Guidance text comes from `@0xdivyanshh/tab-protocol`, not from
|
|
86
|
+
strings invented here, so an agent that learns the rail through MCP learns the same rail.
|
|
87
|
+
- **Fails at startup on a missing `TAB_ACCOUNT_ID`.** A client shows a server as connected the
|
|
88
|
+
moment the process is alive, so one that starts happily and then refuses every call looks like a
|
|
89
|
+
broken rail rather than a missing variable.
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Tab } from '@0xdivyanshh/tab-sdk';
|
|
2
|
+
/**
|
|
3
|
+
* How the server is configured, and what it deliberately cannot be given.
|
|
4
|
+
*
|
|
5
|
+
* A URL and a tab id. There is no token, no private key, and `createTab` has no
|
|
6
|
+
* parameter that would accept one — which is the whole product claim holding at
|
|
7
|
+
* the surface most likely to be handed a credential "just for convenience".
|
|
8
|
+
* An MCP server runs on a user's machine, inside a client the user did not
|
|
9
|
+
* write, and is exactly where a key would leak.
|
|
10
|
+
*/
|
|
11
|
+
export interface McpConfig {
|
|
12
|
+
gatewayUrl: string;
|
|
13
|
+
tab: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function configFromEnv(env?: NodeJS.ProcessEnv): McpConfig;
|
|
16
|
+
export declare function clientFor(config: McpConfig): Tab;
|
|
17
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,GAAG,EAAE,MAAM,UAAU,CAAA;AAE9C;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,wBAAgB,aAAa,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,SAAS,CAqB7E;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,GAAG,GAAG,CAehD"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createTab } from '@0xdivyanshh/tab-sdk';
|
|
2
|
+
export function configFromEnv(env = process.env) {
|
|
3
|
+
const gatewayUrl = env['TAB_GATEWAY_URL'] ?? 'http://localhost:8080';
|
|
4
|
+
const tab = env['TAB_ACCOUNT_ID'] ?? '';
|
|
5
|
+
if (!tab) {
|
|
6
|
+
/*
|
|
7
|
+
* Fail at STARTUP, not on the first tool call.
|
|
8
|
+
*
|
|
9
|
+
* An MCP client shows a server as connected the moment the process is
|
|
10
|
+
* alive, so a server that starts happily and then refuses every call looks
|
|
11
|
+
* like a broken rail rather than a missing environment variable. The error
|
|
12
|
+
* goes to stderr, which stdio transports reserve for exactly this — stdout
|
|
13
|
+
* is the JSON-RPC channel and a stray line there corrupts the stream.
|
|
14
|
+
*/
|
|
15
|
+
throw new Error('TAB_ACCOUNT_ID is not set. The MCP server needs to know which tab to transact on. ' +
|
|
16
|
+
'Set it in the mcpServers entry for this server, alongside TAB_GATEWAY_URL.');
|
|
17
|
+
}
|
|
18
|
+
return { gatewayUrl, tab };
|
|
19
|
+
}
|
|
20
|
+
export function clientFor(config) {
|
|
21
|
+
return createTab({
|
|
22
|
+
baseUrl: config.gatewayUrl,
|
|
23
|
+
/*
|
|
24
|
+
* Patient, unlike the console's client.
|
|
25
|
+
*
|
|
26
|
+
* A spend waits on the SELLER, whose x402 facilitator runs a Mirror Node
|
|
27
|
+
* preflight plus signature verification — measured at 25-39s for an HTS
|
|
28
|
+
* token. An MCP client is a foreground tool an agent is waiting on, so
|
|
29
|
+
* giving up at 8s would abandon a payment that was about to succeed and
|
|
30
|
+
* leave the caller unable to tell a slow seller from a failed one.
|
|
31
|
+
*/
|
|
32
|
+
timeoutMs: 120_000,
|
|
33
|
+
maxRetries: 1,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAY,MAAM,UAAU,CAAA;AAgB9C,MAAM,UAAU,aAAa,CAAC,MAAyB,OAAO,CAAC,GAAG;IAChE,MAAM,UAAU,GAAG,GAAG,CAAC,iBAAiB,CAAC,IAAI,uBAAuB,CAAA;IACpE,MAAM,GAAG,GAAG,GAAG,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAA;IAEvC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT;;;;;;;;WAQG;QACH,MAAM,IAAI,KAAK,CACb,oFAAoF;YAClF,4EAA4E,CAC/E,CAAA;IACH,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,CAAA;AAC5B,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,MAAiB;IACzC,OAAO,SAAS,CAAC;QACf,OAAO,EAAE,MAAM,CAAC,UAAU;QAC1B;;;;;;;;WAQG;QACH,SAAS,EAAE,OAAO;QAClB,UAAU,EAAE,CAAC;KACd,CAAC,CAAA;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@0xdivyanshh/tab-mcp` — Tab's verbs as MCP tools.
|
|
3
|
+
*
|
|
4
|
+
* Tier 3. Depends on `@0xdivyanshh/tab-sdk` and nothing lower — `boundaries.json` allows
|
|
5
|
+
* only `money`, `protocol` and `sdk`, so this package is structurally incapable
|
|
6
|
+
* of signing, reading a database, or recomputing a ceiling. An MCP server runs
|
|
7
|
+
* on a user's machine inside a client the user did not write, which makes it the
|
|
8
|
+
* surface where a leaked key would hurt most and the constraint worth enforcing
|
|
9
|
+
* rather than promising.
|
|
10
|
+
*
|
|
11
|
+
* The binary is `src/stdio.ts`. This module exists so a host can embed the
|
|
12
|
+
* server on its own transport.
|
|
13
|
+
*/
|
|
14
|
+
export { clientFor, configFromEnv, type McpConfig } from './config.ts';
|
|
15
|
+
export { buildServer } from './server.ts';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAA;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@0xdivyanshh/tab-mcp` — Tab's verbs as MCP tools.
|
|
3
|
+
*
|
|
4
|
+
* Tier 3. Depends on `@0xdivyanshh/tab-sdk` and nothing lower — `boundaries.json` allows
|
|
5
|
+
* only `money`, `protocol` and `sdk`, so this package is structurally incapable
|
|
6
|
+
* of signing, reading a database, or recomputing a ceiling. An MCP server runs
|
|
7
|
+
* on a user's machine inside a client the user did not write, which makes it the
|
|
8
|
+
* surface where a leaked key would hurt most and the constraint worth enforcing
|
|
9
|
+
* rather than promising.
|
|
10
|
+
*
|
|
11
|
+
* The binary is `src/stdio.ts`. This module exists so a host can embed the
|
|
12
|
+
* server on its own transport.
|
|
13
|
+
*/
|
|
14
|
+
export { clientFor, configFromEnv } from "./config.js";
|
|
15
|
+
export { buildServer } from "./server.js";
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAkB,MAAM,aAAa,CAAA;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { toWire } from '@0xdivyanshh/tab-money';
|
|
3
|
+
import type { Tab } from '@0xdivyanshh/tab-sdk';
|
|
4
|
+
import type { McpConfig } from './config.ts';
|
|
5
|
+
export declare function buildServer(tab: Tab, config: McpConfig): McpServer;
|
|
6
|
+
/** Re-exported so a consumer can serialise an amount the way the tools expect. */
|
|
7
|
+
export { toWire };
|
|
8
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACnE,OAAO,EAAU,MAAM,EAAQ,MAAM,YAAY,CAAA;AAEjD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAEnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAqD5C,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,GAAG,SAAS,CA4WlE;AAED,kFAAkF;AAClF,OAAO,EAAE,MAAM,EAAE,CAAA"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { format, toWire, usdc } from '@0xdivyanshh/tab-money';
|
|
3
|
+
import { REFUSAL_GUIDANCE, WEIGHT_REASON_DETAIL } from '@0xdivyanshh/tab-protocol';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
/**
|
|
6
|
+
* `@0xdivyanshh/tab-mcp` — Tab's verbs as MCP tools.
|
|
7
|
+
*
|
|
8
|
+
* ## Why a standalone server, which the README left open
|
|
9
|
+
*
|
|
10
|
+
* The README offered two options: this, or documentation for loading
|
|
11
|
+
* `@0xdivyanshh/tab-agentkit-plugin` into `@hashgraph/hedera-agent-kit-mcp`. Option 2 is
|
|
12
|
+
* the better ecosystem story and it is not the one to ship first, for reasons
|
|
13
|
+
* that are about evidence rather than taste:
|
|
14
|
+
*
|
|
15
|
+
* - It depends on TWO things that do not exist — the plugin is unwritten — and
|
|
16
|
+
* on an external server accepting third-party plugins in a shape nobody here
|
|
17
|
+
* has verified.
|
|
18
|
+
* - It cannot be demonstrated by a judge in one step. This can: three lines in
|
|
19
|
+
* a client config and Claude Desktop is spending on a Hedera tab.
|
|
20
|
+
* - It is additive later. A plugin can wrap the same `@0xdivyanshh/tab-sdk` verbs whenever
|
|
21
|
+
* the Agent Kit path is confirmed, and nothing here has to be undone.
|
|
22
|
+
*
|
|
23
|
+
* So: option 1, and the reason is recorded here rather than in an ADR nobody
|
|
24
|
+
* opens.
|
|
25
|
+
*
|
|
26
|
+
* ## The one invariant
|
|
27
|
+
*
|
|
28
|
+
* **A refusal is a structured result, not an error.** `isError: true` makes an
|
|
29
|
+
* MCP client surface a failure and, worse, invites a model to retry it — and a
|
|
30
|
+
* `CEILING_EXCEEDED` retried immediately is a spend that will be refused again
|
|
31
|
+
* for the same reason. A refusal comes back as content the model can read,
|
|
32
|
+
* carrying the rule, the evidence and what to do instead, because choosing
|
|
33
|
+
* different work is the correct response and the agent can only choose it if it
|
|
34
|
+
* is told why.
|
|
35
|
+
*
|
|
36
|
+
* Infrastructure failure IS an error, and the distinction is preserved from the
|
|
37
|
+
* SDK: a refusal means the rail said no, `failed` means nobody said anything
|
|
38
|
+
* and the caller does not know whether the seller was paid.
|
|
39
|
+
*/
|
|
40
|
+
/** Amounts cross MCP as decimal strings — `bigint` does not survive JSON. */
|
|
41
|
+
const AMOUNT = z
|
|
42
|
+
.string()
|
|
43
|
+
.regex(/^\d+\.\d{6}$/, 'six decimal places, e.g. "0.040000"')
|
|
44
|
+
.describe('USDC amount as a decimal string with exactly six decimal places, e.g. "0.040000"');
|
|
45
|
+
/** Every tool returns text content; structured data goes in the same string. */
|
|
46
|
+
function text(body) {
|
|
47
|
+
return { content: [{ type: 'text', text: body }] };
|
|
48
|
+
}
|
|
49
|
+
function errorText(body) {
|
|
50
|
+
return { content: [{ type: 'text', text: body }], isError: true };
|
|
51
|
+
}
|
|
52
|
+
export function buildServer(tab, config) {
|
|
53
|
+
const server = new McpServer({ name: 'tab', version: '0.1.0' }, {
|
|
54
|
+
instructions: `Tab is a credit rail on Hedera for this agent's tab ${config.tab}. The agent holds no ` +
|
|
55
|
+
'key and signs nothing; the gateway pays sellers on its behalf against an earned ceiling. ' +
|
|
56
|
+
'Call quote before a batch to learn what is affordable. A refused spend is a normal, ' +
|
|
57
|
+
'expected answer that names the rule and what to do instead — read the guidance and choose ' +
|
|
58
|
+
'different work rather than retrying the same call.',
|
|
59
|
+
});
|
|
60
|
+
/* ── spend: the only verb that moves money ───────────────────────────────── */
|
|
61
|
+
server.registerTool('tab_spend', {
|
|
62
|
+
title: 'Spend from the tab',
|
|
63
|
+
description: 'Pay a seller through the tab, up to `max`. The gateway reserves a hold, pays, then ' +
|
|
64
|
+
'commits — so a crash cannot leave the tab overdrawn. Returns paid, refused or failed. ' +
|
|
65
|
+
'A REFUSED result is not an error: it names the rule that fired and what to do instead.',
|
|
66
|
+
inputSchema: {
|
|
67
|
+
url: z
|
|
68
|
+
.string()
|
|
69
|
+
.url()
|
|
70
|
+
.describe('The seller URL, including whatever the seller needs to be paid'),
|
|
71
|
+
max: AMOUNT.describe('The most this call may cost. The tab is debited what was actually settled, not this cap.'),
|
|
72
|
+
idempotencyKey: z
|
|
73
|
+
.string()
|
|
74
|
+
.min(8)
|
|
75
|
+
.max(64)
|
|
76
|
+
.optional()
|
|
77
|
+
.describe('Reuse the SAME key when retrying a failed call, or the retry may double-spend. ' +
|
|
78
|
+
'Generated automatically when omitted.'),
|
|
79
|
+
},
|
|
80
|
+
annotations: {
|
|
81
|
+
title: 'Spend from the tab',
|
|
82
|
+
// MOVES MONEY. The one tool here that is not safe to call speculatively,
|
|
83
|
+
// and the one an MCP client should confirm with a human.
|
|
84
|
+
readOnlyHint: false,
|
|
85
|
+
destructiveHint: true,
|
|
86
|
+
idempotentHint: false,
|
|
87
|
+
openWorldHint: true,
|
|
88
|
+
},
|
|
89
|
+
}, async ({ url, max, idempotencyKey }) => {
|
|
90
|
+
const result = await tab.spend({
|
|
91
|
+
tab: config.tab,
|
|
92
|
+
url,
|
|
93
|
+
max: usdc(max),
|
|
94
|
+
...(idempotencyKey ? { idempotencyKey } : {}),
|
|
95
|
+
});
|
|
96
|
+
if (result.outcome === 'paid') {
|
|
97
|
+
return text([
|
|
98
|
+
`PAID ${format(result.amount)} USDC to ${result.seller}.`,
|
|
99
|
+
`hold ${result.holdId}` +
|
|
100
|
+
(result.receiptSeq !== null ? ` · receipt seq ${result.receiptSeq} on HCS` : ''),
|
|
101
|
+
`took ${Math.round(result.elapsedMs / 1000)}s`,
|
|
102
|
+
'',
|
|
103
|
+
'The seller responded:',
|
|
104
|
+
typeof result.body === 'string' ? result.body : JSON.stringify(result.body, null, 2),
|
|
105
|
+
].join('\n'));
|
|
106
|
+
}
|
|
107
|
+
if (result.outcome === 'refused') {
|
|
108
|
+
/*
|
|
109
|
+
* NOT `isError`. See the header.
|
|
110
|
+
*
|
|
111
|
+
* The guidance comes from `@0xdivyanshh/tab-protocol`, not from a string invented
|
|
112
|
+
* here — the same text the HTTP API and the console show, so an agent
|
|
113
|
+
* that learns the rail through MCP learns the same rail.
|
|
114
|
+
*/
|
|
115
|
+
const evidence = result.evidence
|
|
116
|
+
? Object.entries(result.evidence)
|
|
117
|
+
.map(([k, v]) => ` ${k} ${v}`)
|
|
118
|
+
.join('\n')
|
|
119
|
+
: ' (none published)';
|
|
120
|
+
return text([
|
|
121
|
+
`REFUSED — ${result.rule}`,
|
|
122
|
+
'',
|
|
123
|
+
result.reason,
|
|
124
|
+
'',
|
|
125
|
+
'Evidence:',
|
|
126
|
+
evidence,
|
|
127
|
+
'',
|
|
128
|
+
`What to do: ${result.guidance}`,
|
|
129
|
+
result.retryable
|
|
130
|
+
? 'This rule can clear on its own — the same call may succeed later.'
|
|
131
|
+
: 'Retrying this exact call will be refused again for the same reason. Choose different work.',
|
|
132
|
+
].join('\n'));
|
|
133
|
+
}
|
|
134
|
+
/*
|
|
135
|
+
* `failed` IS an error, and says the one thing that matters.
|
|
136
|
+
*
|
|
137
|
+
* Infrastructure broke AFTER the decision to spend, so nobody knows
|
|
138
|
+
* whether the seller was paid. A caller that retries MUST reuse the hold
|
|
139
|
+
* id as its idempotency key, or the retry is a second payment.
|
|
140
|
+
*/
|
|
141
|
+
return errorText([
|
|
142
|
+
`FAILED — ${result.reason}`,
|
|
143
|
+
'',
|
|
144
|
+
'This is NOT a refusal. The rail did not say no; nobody said anything, and it is ' +
|
|
145
|
+
'unknown whether the seller was paid.',
|
|
146
|
+
`To retry safely, call tab_spend again with idempotencyKey "${result.holdId}" — the ` +
|
|
147
|
+
'gateway treats it as the same attempt. A new key would be a second payment.',
|
|
148
|
+
'The hold expires on its own, so the headroom is not lost permanently.',
|
|
149
|
+
].join('\n'));
|
|
150
|
+
});
|
|
151
|
+
/* ── quote: what can I afford, reserving nothing ─────────────────────────── */
|
|
152
|
+
server.registerTool('tab_quote', {
|
|
153
|
+
title: 'Check affordability without reserving',
|
|
154
|
+
description: 'Ask whether a spend of `max` would be allowed, WITHOUT taking a hold. Use this before ' +
|
|
155
|
+
'planning a batch: a hold taken by a quote would be headroom the agent never uses.',
|
|
156
|
+
inputSchema: { max: AMOUNT },
|
|
157
|
+
annotations: {
|
|
158
|
+
readOnlyHint: true,
|
|
159
|
+
destructiveHint: false,
|
|
160
|
+
idempotentHint: true,
|
|
161
|
+
openWorldHint: false,
|
|
162
|
+
},
|
|
163
|
+
}, async ({ max }) => {
|
|
164
|
+
const q = await tab.quote({ tab: config.tab, max: usdc(max) });
|
|
165
|
+
return text([
|
|
166
|
+
q.affordable ? `AFFORDABLE — ${format(usdc(max))} is within the tab.` : 'NOT AFFORDABLE',
|
|
167
|
+
`available ${format(q.available)}`,
|
|
168
|
+
`ceiling ${format(q.ceiling)}`,
|
|
169
|
+
`per-call cap ${format(q.perCallCap)}`,
|
|
170
|
+
...(q.wouldRefuse
|
|
171
|
+
? [
|
|
172
|
+
'',
|
|
173
|
+
`would refuse: ${q.wouldRefuse}`,
|
|
174
|
+
`${q.reason ?? ''}`,
|
|
175
|
+
REFUSAL_GUIDANCE[q.wouldRefuse],
|
|
176
|
+
]
|
|
177
|
+
: []),
|
|
178
|
+
]
|
|
179
|
+
.filter(Boolean)
|
|
180
|
+
.join('\n'));
|
|
181
|
+
});
|
|
182
|
+
/* ── the read verbs ──────────────────────────────────────────────────────── */
|
|
183
|
+
server.registerTool('tab_balance', {
|
|
184
|
+
title: 'Tab position',
|
|
185
|
+
description: 'The tab’s current position: balance (negative means the agent owes), outstanding, ' +
|
|
186
|
+
'holds, available headroom and the ceiling in force.',
|
|
187
|
+
annotations: {
|
|
188
|
+
readOnlyHint: true,
|
|
189
|
+
destructiveHint: false,
|
|
190
|
+
idempotentHint: true,
|
|
191
|
+
openWorldHint: false,
|
|
192
|
+
},
|
|
193
|
+
}, async () => {
|
|
194
|
+
const s = await tab.state(config.tab);
|
|
195
|
+
return text([
|
|
196
|
+
`tab ${s.tab}`,
|
|
197
|
+
`balance ${format(s.balance, { sign: 'always' })} (negative means the agent owes)`,
|
|
198
|
+
`outstanding ${format(s.outstanding)}`,
|
|
199
|
+
`holds ${format(s.holds)}`,
|
|
200
|
+
`available ${format(s.available)}`,
|
|
201
|
+
`ceiling ${format(s.ceiling)}`,
|
|
202
|
+
`per-call cap ${format(s.perCallCap)}`,
|
|
203
|
+
`window ${s.window} · ${s.entries} ledger entries`,
|
|
204
|
+
].join('\n'));
|
|
205
|
+
});
|
|
206
|
+
server.registerTool('tab_ceiling', {
|
|
207
|
+
title: 'The ceiling, and the arithmetic behind it',
|
|
208
|
+
description: 'The credit ceiling in force, what bound it, and the published inputs it was computed ' +
|
|
209
|
+
'from. Use this to explain to a user WHY the tab can or cannot afford something.',
|
|
210
|
+
annotations: {
|
|
211
|
+
readOnlyHint: true,
|
|
212
|
+
destructiveHint: false,
|
|
213
|
+
idempotentHint: true,
|
|
214
|
+
openWorldHint: false,
|
|
215
|
+
},
|
|
216
|
+
}, async () => {
|
|
217
|
+
const c = await tab.ceiling(config.tab);
|
|
218
|
+
if (!c.published || !c.current) {
|
|
219
|
+
return text([
|
|
220
|
+
`No ceiling published yet for ${c.tab} — normal for a tab's first minutes.`,
|
|
221
|
+
`The gateway is enforcing ${format(c.enforced)} meanwhile (the starter floor).`,
|
|
222
|
+
c.note ?? '',
|
|
223
|
+
]
|
|
224
|
+
.filter(Boolean)
|
|
225
|
+
.join('\n'));
|
|
226
|
+
}
|
|
227
|
+
const i = c.current.inputs;
|
|
228
|
+
return text([
|
|
229
|
+
`ceiling ${format(c.current.ceiling)} bound by ${c.current.binding}`,
|
|
230
|
+
`enforced now ${format(c.enforced)}` +
|
|
231
|
+
(c.enforced !== c.current.ceiling
|
|
232
|
+
? ' (the gateway polls the topic every 15s, so this is what a spend is checked against)'
|
|
233
|
+
: ''),
|
|
234
|
+
`cause ${c.current.cause}`,
|
|
235
|
+
'',
|
|
236
|
+
'Published inputs:',
|
|
237
|
+
` trailing revenue ${format(i.revenue)} (attested ${format(i.revenueAttested)} · unattested ${format(i.revenueUnattested)})`,
|
|
238
|
+
` tier ${i.tier} ×${i.multBp / 10_000}`,
|
|
239
|
+
` earned ramp ${i.rampBp / 100}%`,
|
|
240
|
+
` hard cap ${format(i.cap)}`,
|
|
241
|
+
` starter floor ${format(i.floor)}`,
|
|
242
|
+
...(i.defaulted
|
|
243
|
+
? [' DEFAULTED this tab has missed a settlement, so the ceiling is zero']
|
|
244
|
+
: []),
|
|
245
|
+
'',
|
|
246
|
+
`model ${c.current.model} · HCS seq ${c.current.seq ?? '?'} · input hash ${c.current.hash}`,
|
|
247
|
+
'Anyone can recompute this from the public topic: pnpm verify-ceiling.',
|
|
248
|
+
].join('\n'));
|
|
249
|
+
});
|
|
250
|
+
server.registerTool('tab_receipts', {
|
|
251
|
+
title: 'Recent receipts',
|
|
252
|
+
description: 'The tab’s recent ledger entries from the HCS receipt topic — holds, debits, ' +
|
|
253
|
+
'credits, refusals and settlements, newest first.',
|
|
254
|
+
inputSchema: {
|
|
255
|
+
limit: z
|
|
256
|
+
.number()
|
|
257
|
+
.int()
|
|
258
|
+
.min(1)
|
|
259
|
+
.max(50)
|
|
260
|
+
.default(10)
|
|
261
|
+
.describe('How many entries, newest first'),
|
|
262
|
+
},
|
|
263
|
+
annotations: {
|
|
264
|
+
readOnlyHint: true,
|
|
265
|
+
destructiveHint: false,
|
|
266
|
+
idempotentHint: true,
|
|
267
|
+
openWorldHint: false,
|
|
268
|
+
},
|
|
269
|
+
}, async ({ limit }) => {
|
|
270
|
+
const rows = await tab.receipts(config.tab);
|
|
271
|
+
const recent = rows.slice(-limit).reverse();
|
|
272
|
+
if (recent.length === 0)
|
|
273
|
+
return text('No receipts yet for this tab.');
|
|
274
|
+
return text(recent
|
|
275
|
+
.map((r) => {
|
|
276
|
+
const amount = r.amount !== undefined ? format(r.amount, { sign: 'always' }) : '—';
|
|
277
|
+
return [
|
|
278
|
+
r.leg.toUpperCase().padEnd(11),
|
|
279
|
+
amount.padStart(10),
|
|
280
|
+
(r.counterparty ?? '—').padEnd(14),
|
|
281
|
+
r.rule ?? '',
|
|
282
|
+
r.seq !== undefined ? `seq ${r.seq}` : 'unpublished',
|
|
283
|
+
]
|
|
284
|
+
.filter(Boolean)
|
|
285
|
+
.join(' ');
|
|
286
|
+
})
|
|
287
|
+
.join('\n'));
|
|
288
|
+
});
|
|
289
|
+
server.registerTool('tab_counterparties', {
|
|
290
|
+
title: 'Who counts as independent revenue',
|
|
291
|
+
description: 'Published independence weights for the tab’s counterparties, with the reason each ' +
|
|
292
|
+
'was discounted or blocked. This is why revenue does or does not raise the ceiling.',
|
|
293
|
+
annotations: {
|
|
294
|
+
readOnlyHint: true,
|
|
295
|
+
destructiveHint: false,
|
|
296
|
+
idempotentHint: true,
|
|
297
|
+
openWorldHint: false,
|
|
298
|
+
},
|
|
299
|
+
}, async () => {
|
|
300
|
+
const rows = await tab.counterparties(config.tab);
|
|
301
|
+
if (rows.length === 0)
|
|
302
|
+
return text('No weights published yet. Run the engine.');
|
|
303
|
+
return text(rows
|
|
304
|
+
.map((w) => [
|
|
305
|
+
`${w.counterparty} ${w.bp / 100}%${w.blocking ? ' BLOCKED' : ''}`,
|
|
306
|
+
` revenue ${format(w.revenue)} · share ${w.shareBp / 100}%`,
|
|
307
|
+
...w.reasons.map((r) => ` ${r}: ${WEIGHT_REASON_DETAIL[r]}`),
|
|
308
|
+
...(w.funder && w.tabFunder
|
|
309
|
+
? [
|
|
310
|
+
` funded by ${w.funder} · tab funded by ${w.tabFunder}` +
|
|
311
|
+
(w.funder === w.tabFunder
|
|
312
|
+
? ' ← SAME, which is what COMMON_FUNDER tests'
|
|
313
|
+
: ''),
|
|
314
|
+
]
|
|
315
|
+
: []),
|
|
316
|
+
].join('\n'))
|
|
317
|
+
.join('\n\n'));
|
|
318
|
+
});
|
|
319
|
+
server.registerTool('tab_health', {
|
|
320
|
+
title: 'Is the rail up',
|
|
321
|
+
description: 'Gateway reachability, network, token and current window.',
|
|
322
|
+
annotations: {
|
|
323
|
+
readOnlyHint: true,
|
|
324
|
+
destructiveHint: false,
|
|
325
|
+
idempotentHint: true,
|
|
326
|
+
openWorldHint: false,
|
|
327
|
+
},
|
|
328
|
+
}, async () => {
|
|
329
|
+
const h = await tab.health();
|
|
330
|
+
return text([
|
|
331
|
+
h.ok ? 'Gateway is up.' : 'Gateway responded but did not report OK.',
|
|
332
|
+
`network ${h.network} · token ${h.token} · window ${h.window}`,
|
|
333
|
+
h.demoMode ? 'DEMO MODE is on — window length is overridden for demonstration.' : '',
|
|
334
|
+
`gateway ${config.gatewayUrl} · tab ${config.tab}`,
|
|
335
|
+
]
|
|
336
|
+
.filter(Boolean)
|
|
337
|
+
.join('\n'));
|
|
338
|
+
});
|
|
339
|
+
return server;
|
|
340
|
+
}
|
|
341
|
+
/** Re-exported so a consumer can serialise an amount the way the tools expect. */
|
|
342
|
+
export { toWire };
|
|
343
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/stdio.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../src/stdio.ts"],"names":[],"mappings":""}
|
package/dist/stdio.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The stdio entry point.
|
|
4
|
+
*
|
|
5
|
+
* TAB_GATEWAY_URL=... TAB_ACCOUNT_ID=0.0.x node --experimental-strip-types src/stdio.ts
|
|
6
|
+
*
|
|
7
|
+
* ## STDOUT IS THE PROTOCOL
|
|
8
|
+
*
|
|
9
|
+
* A stdio MCP server speaks JSON-RPC on stdout, so a single stray `console.log`
|
|
10
|
+
* anywhere in the process corrupts the stream and the client reports a parse
|
|
11
|
+
* error rather than whatever actually went wrong. Every diagnostic here goes to
|
|
12
|
+
* stderr, deliberately, and nothing else in this package prints at all.
|
|
13
|
+
*
|
|
14
|
+
* That is also why `configFromEnv` throws at startup: a server that connects and
|
|
15
|
+
* then fails every call looks like a broken rail instead of a missing variable.
|
|
16
|
+
*/
|
|
17
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
18
|
+
import { clientFor, configFromEnv } from "./config.js";
|
|
19
|
+
import { buildServer } from "./server.js";
|
|
20
|
+
let config;
|
|
21
|
+
try {
|
|
22
|
+
config = configFromEnv();
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const server = buildServer(clientFor(config), config);
|
|
29
|
+
process.stderr.write(`tab-mcp · gateway ${config.gatewayUrl} · tab ${config.tab}\n` +
|
|
30
|
+
'The agent holds no key and signs nothing. 7 tools; only tab_spend moves money.\n');
|
|
31
|
+
await server.connect(new StdioServerTransport());
|
|
32
|
+
//# sourceMappingURL=stdio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../src/stdio.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAAE,SAAS,EAAE,aAAa,EAAkB,MAAM,aAAa,CAAA;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEzC,IAAI,MAAiB,CAAA;AACrB,IAAI,CAAC;IACH,MAAM,GAAG,aAAa,EAAE,CAAA;AAC1B,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACnF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC;AAED,MAAM,MAAM,GAAG,WAAW,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAA;AAErD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,qBAAqB,MAAM,CAAC,UAAU,UAAU,MAAM,CAAC,GAAG,IAAI;IAC5D,kFAAkF,CACrF,CAAA;AAED,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@0xdivyanshh/tab-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"tab-mcp": "dist/stdio.js"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@modelcontextprotocol/sdk": "^1.13.0",
|
|
18
|
+
"@0xdivyanshh/tab-money": "0.1.0",
|
|
19
|
+
"@0xdivyanshh/tab-protocol": "0.1.0",
|
|
20
|
+
"@0xdivyanshh/tab-sdk": "0.1.0",
|
|
21
|
+
"zod": "^3.25.0"
|
|
22
|
+
},
|
|
23
|
+
"description": "MCP server for Tab — give any LLM a credit line on Hedera. It holds no key.",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/0xNerd100/Tab.git",
|
|
28
|
+
"directory": "packages/mcp"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/0xNerd100/Tab#readme",
|
|
31
|
+
"files": [
|
|
32
|
+
"dist/**/*.js",
|
|
33
|
+
"dist/**/*.d.ts",
|
|
34
|
+
"dist/**/*.map",
|
|
35
|
+
"README.md"
|
|
36
|
+
],
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
}
|
|
40
|
+
}
|