@spendctl/mcp-server 0.0.0-stage → 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/LICENSE +83 -0
- package/README.md +8 -2
- package/THIRD_PARTY_NOTICES +2082 -0
- package/bin/spendctl-mcp.js +3 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +110 -0
- package/dist/index.js.map +1 -0
- package/package.json +31 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { type AgentClientOptions } from "@spendctl/client";
|
|
3
|
+
/**
|
|
4
|
+
* MCP tools that let an agent spend through spendctl. The server is a thin client
|
|
5
|
+
* of the sidecar: it holds no keys and cannot approve anything. Identity comes
|
|
6
|
+
* from SPENDCTL_AGENT (+ SPENDCTL_AGENT_TOKEN) or the options.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createMcpServer(options?: AgentClientOptions): McpServer;
|
|
9
|
+
export declare function serveStdio(options?: AgentClientOptions): Promise<McpServer>;
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAe,KAAK,kBAAkB,EAAe,MAAM,kBAAkB,CAAC;AASrF;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,kBAAuB,GAAG,SAAS,CA6G3E;AAED,wBAAsB,UAAU,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,SAAS,CAAC,CAIrF"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { AgentClient, readRefusal } from "@spendctl/client";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
const MAX_BODY_CHARS = 20_000;
|
|
6
|
+
function text(content, isError = false) {
|
|
7
|
+
return { content: [{ type: "text", text: content }], isError };
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* MCP tools that let an agent spend through spendctl. The server is a thin client
|
|
11
|
+
* of the sidecar: it holds no keys and cannot approve anything. Identity comes
|
|
12
|
+
* from SPENDCTL_AGENT (+ SPENDCTL_AGENT_TOKEN) or the options.
|
|
13
|
+
*/
|
|
14
|
+
export function createMcpServer(options = {}) {
|
|
15
|
+
const client = new AgentClient(options);
|
|
16
|
+
const server = new McpServer({ name: "spendctl", version: "0.1.0" });
|
|
17
|
+
server.registerTool("pay", {
|
|
18
|
+
title: "Fetch a paid URL",
|
|
19
|
+
description: "Fetch an HTTP(S) URL that may require payment (HTTP 402: x402 or MPP). spendctl applies your budget, allowlist and approval policy outside your control, pays if allowed, and returns the response. If it refuses or needs a human, the result explains why — do not try to pay another way.",
|
|
20
|
+
inputSchema: {
|
|
21
|
+
url: z.string().url().describe("Absolute http(s) URL"),
|
|
22
|
+
method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).default("GET"),
|
|
23
|
+
headers: z.record(z.string(), z.string()).optional().describe("Extra request headers"),
|
|
24
|
+
body: z.string().optional().describe("Request body (for POST/PUT/PATCH)"),
|
|
25
|
+
idempotencyKey: z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Reuse the same key when retrying so you are never charged twice"),
|
|
29
|
+
dryRun: z.boolean().optional().describe("Only report what spendctl would do; never pay"),
|
|
30
|
+
},
|
|
31
|
+
annotations: { openWorldHint: true },
|
|
32
|
+
}, async ({ url, method, headers, body, idempotencyKey, dryRun }) => {
|
|
33
|
+
const requestHeaders = new Headers(headers ?? {});
|
|
34
|
+
if (idempotencyKey)
|
|
35
|
+
requestHeaders.set("idempotency-key", idempotencyKey);
|
|
36
|
+
if (dryRun)
|
|
37
|
+
requestHeaders.set("x-spendctl-dry-run", "1");
|
|
38
|
+
let response;
|
|
39
|
+
try {
|
|
40
|
+
response = await client.fetch(url, { method, headers: requestHeaders, body });
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
return text(`spendctl sidecar unreachable: ${error.message}. Ask a human to run \`spendctl start\`.`, true);
|
|
44
|
+
}
|
|
45
|
+
const refusal = await readRefusal(response);
|
|
46
|
+
if (refusal) {
|
|
47
|
+
const lines = [refusal.message];
|
|
48
|
+
if (refusal.approval)
|
|
49
|
+
lines.push(`Approval id: ${refusal.approval}. A human must run \`spendctl approve ${refusal.approval}\`.`);
|
|
50
|
+
if (refusal.attempt)
|
|
51
|
+
lines.push(`Ledger entry: ${refusal.attempt}`);
|
|
52
|
+
return text(lines.join("\n"), refusal.error !== "spendctl_dry_run");
|
|
53
|
+
}
|
|
54
|
+
const payload = await response.text();
|
|
55
|
+
const decision = response.headers.get("x-spendctl-decision");
|
|
56
|
+
const amount = response.headers.get("x-spendctl-amount");
|
|
57
|
+
const summary = decision
|
|
58
|
+
? `HTTP ${response.status} · paid ${amount ?? "?"} (${decision}) · receipt ${response.headers.get("x-spendctl-attempt")}`
|
|
59
|
+
: `HTTP ${response.status} · no payment needed`;
|
|
60
|
+
const clipped = payload.length > MAX_BODY_CHARS ? `${payload.slice(0, MAX_BODY_CHARS)}\n…[truncated]` : payload;
|
|
61
|
+
return text(`${summary}\n\n${clipped}`, !response.ok);
|
|
62
|
+
});
|
|
63
|
+
server.registerTool("check_budget", {
|
|
64
|
+
title: "Check remaining budget",
|
|
65
|
+
description: "Show this agent's spending limits, what has been spent in each window, and what is left.",
|
|
66
|
+
inputSchema: {},
|
|
67
|
+
annotations: { readOnlyHint: true },
|
|
68
|
+
}, async () => {
|
|
69
|
+
try {
|
|
70
|
+
const budget = await client.budget();
|
|
71
|
+
const lines = [
|
|
72
|
+
`Agent: ${budget.agent} (${budget.mode})`,
|
|
73
|
+
`Per request: ${budget.perRequest ?? "no limit"}`,
|
|
74
|
+
`Human approval at or above: ${budget.requireApprovalAbove ?? "never"}`,
|
|
75
|
+
`When a budget would be exceeded: ${budget.onBudgetExceeded === "approve" ? "ask a human" : "refuse"}`,
|
|
76
|
+
`Allowed hosts: ${budget.allow.join(", ")}`,
|
|
77
|
+
...budget.windows.map((w) => `${w.window}: spent ${w.spent} of ${w.limit}, ${w.remaining} left`),
|
|
78
|
+
];
|
|
79
|
+
return text(lines.join("\n"));
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
return text(error.message, true);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
server.registerTool("list_receipts", {
|
|
86
|
+
title: "List receipts",
|
|
87
|
+
description: "List this agent's recent payment attempts (paid, refused, waiting for approval) with amounts and transaction hashes.",
|
|
88
|
+
inputSchema: { limit: z.number().int().min(1).max(100).default(10) },
|
|
89
|
+
annotations: { readOnlyHint: true },
|
|
90
|
+
}, async ({ limit }) => {
|
|
91
|
+
try {
|
|
92
|
+
const receipts = await client.receipts(limit);
|
|
93
|
+
if (!receipts.length)
|
|
94
|
+
return text("No payments yet.");
|
|
95
|
+
return text(receipts
|
|
96
|
+
.map((r) => `${r.created_at_utc} ${r.id} ${r.method} ${r.host}${r.path} ${r.usd_value ? `$${r.usd_value}` : "-"} ${r.status}${r.tx_hash ? ` tx ${r.tx_hash}` : r.reasons ? ` (${r.reasons})` : ""}`)
|
|
97
|
+
.join("\n"));
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
return text(error.message, true);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
return server;
|
|
104
|
+
}
|
|
105
|
+
export async function serveStdio(options = {}) {
|
|
106
|
+
const server = createMcpServer(options);
|
|
107
|
+
await server.connect(new StdioServerTransport());
|
|
108
|
+
return server;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spendctl/mcp-server",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server exposing spendctl to agents: pay, check_budget, list_receipts. Holds no keys.",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"spendctl-mcp": "./bin/spendctl-mcp.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"bin",
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md",
|
|
22
|
+
"THIRD_PARTY_NOTICES"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=22.13"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
29
|
+
"@spendctl/client": "^0.1.0",
|
|
30
|
+
"zod": "^4.6.7"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://spendctl.dev",
|
|
33
|
+
"scripts": {}
|
|
6
34
|
}
|