letagentpay-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.
Files changed (4) hide show
  1. package/README.md +77 -0
  2. package/index.js +18 -0
  3. package/package.json +35 -0
  4. package/server.js +140 -0
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # letagentpay-mcp
2
+
3
+ MCP server for [LetAgentPay](https://letagentpay.com) — AI agent spending policy middleware. Works with Claude Desktop, Claude Code, Cursor, OpenClaw, and any MCP-compatible client.
4
+
5
+ ## Setup
6
+
7
+ Add to your MCP client config:
8
+
9
+ ```json
10
+ {
11
+ "mcpServers": {
12
+ "letagentpay": {
13
+ "command": "npx",
14
+ "args": ["-y", "letagentpay-mcp"],
15
+ "env": {
16
+ "LETAGENTPAY_TOKEN": "agt_xxx"
17
+ }
18
+ }
19
+ }
20
+ }
21
+ ```
22
+
23
+ Get your agent token at [letagentpay.com](https://letagentpay.com) or from your self-hosted instance.
24
+
25
+ ### Self-hosted
26
+
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "letagentpay": {
31
+ "command": "npx",
32
+ "args": ["-y", "letagentpay-mcp"],
33
+ "env": {
34
+ "LETAGENTPAY_TOKEN": "agt_xxx",
35
+ "LETAGENTPAY_API_URL": "http://localhost:8000/api/v1"
36
+ }
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ ## Tools
43
+
44
+ | Tool | Description |
45
+ |------|-------------|
46
+ | `request_purchase` | Submit a purchase request for policy evaluation |
47
+ | `check_budget` | View current budget, spent, held, and remaining |
48
+ | `list_categories` | List valid purchase categories |
49
+ | `my_requests` | Check status of a specific purchase request |
50
+ | `list_requests` | List purchase requests with optional status filter |
51
+ | `confirm_purchase` | Confirm purchase completion (or failure) |
52
+
53
+ Every purchase request goes through 8 deterministic policy checks: agent status, category, per-request limit, schedule, daily/weekly/monthly limits, and total budget.
54
+
55
+ ## Environment Variables
56
+
57
+ | Variable | Required | Default |
58
+ |----------|----------|---------|
59
+ | `LETAGENTPAY_TOKEN` | Yes | — |
60
+ | `LETAGENTPAY_API_URL` | No | `https://api.letagentpay.com/api/v1` |
61
+
62
+ ## SDKs
63
+
64
+ For programmatic integration without MCP:
65
+
66
+ - **Python**: `pip install letagentpay`
67
+ - **TypeScript**: `npm install letagentpay`
68
+
69
+ ## Links
70
+
71
+ - [LetAgentPay](https://letagentpay.com)
72
+ - [GitHub](https://github.com/LetAgentPay/letagentpay)
73
+ - [OpenClaw Skill](https://github.com/LetAgentPay/letagentpay-openclaw)
74
+
75
+ ## License
76
+
77
+ MIT
package/index.js ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { createApiClient, createServer } from "./server.js";
5
+
6
+ const TOKEN = process.env.LETAGENTPAY_TOKEN;
7
+ const API_URL = process.env.LETAGENTPAY_API_URL || "https://api.letagentpay.com/api/v1";
8
+
9
+ if (!TOKEN) {
10
+ console.error("LETAGENTPAY_TOKEN environment variable is required");
11
+ process.exit(1);
12
+ }
13
+
14
+ const apiCall = createApiClient(TOKEN, API_URL);
15
+ const server = createServer(apiCall);
16
+
17
+ const transport = new StdioServerTransport();
18
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "letagentpay-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for LetAgentPay — AI agent spending policy middleware",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "letagentpay-mcp": "./index.js"
8
+ },
9
+ "type": "module",
10
+ "files": [
11
+ "index.js",
12
+ "server.js"
13
+ ],
14
+ "scripts": {
15
+ "test": "vitest run",
16
+ "test:watch": "vitest"
17
+ },
18
+ "keywords": ["mcp", "letagentpay", "ai-agent", "purchases", "budget", "policy", "openclaw", "claude"],
19
+ "author": "LetAgentPay <hello@letagentpay.com>",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/LetAgentPay/letagentpay-mcp"
24
+ },
25
+ "homepage": "https://letagentpay.com",
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "dependencies": {
30
+ "@modelcontextprotocol/sdk": "^1.12.1"
31
+ },
32
+ "devDependencies": {
33
+ "vitest": "^3.1.1"
34
+ }
35
+ }
package/server.js ADDED
@@ -0,0 +1,140 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+
4
+ export function createApiClient(token, apiUrl) {
5
+ return async function apiCall(path, options = {}) {
6
+ const res = await fetch(`${apiUrl}/agent-api${path}`, {
7
+ headers: {
8
+ Authorization: `Bearer ${token}`,
9
+ "Content-Type": "application/json",
10
+ ...options.headers,
11
+ },
12
+ ...options,
13
+ });
14
+
15
+ const data = await res.json();
16
+ if (!res.ok) {
17
+ throw new Error(data.detail || `API error: ${res.status}`);
18
+ }
19
+ return data;
20
+ };
21
+ }
22
+
23
+ export function createServer(apiCall) {
24
+ const server = new McpServer({
25
+ name: "letagentpay",
26
+ version: "0.1.0",
27
+ });
28
+
29
+ server.tool(
30
+ "request_purchase",
31
+ "Request approval for a purchase. Returns request ID and status (auto_approved, pending, or rejected).",
32
+ {
33
+ amount: z.number().positive().describe("Purchase amount in account currency"),
34
+ category: z.string().describe("Purchase category (use list_categories to see valid options)"),
35
+ merchant_name: z.string().optional().describe("Merchant or store name"),
36
+ description: z.string().optional().describe("What is being purchased"),
37
+ agent_comment: z.string().optional().describe("Why this purchase is needed — shown to the human reviewer"),
38
+ },
39
+ async ({ amount, category, merchant_name, description, agent_comment }) => {
40
+ const result = await apiCall("/requests", {
41
+ method: "POST",
42
+ body: JSON.stringify({ amount, category, merchant_name, description, agent_comment }),
43
+ });
44
+ return {
45
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
46
+ };
47
+ },
48
+ );
49
+
50
+ server.tool(
51
+ "check_budget",
52
+ "Check the current budget, amount spent, and remaining balance.",
53
+ {},
54
+ async () => {
55
+ const result = await apiCall("/budget");
56
+ return {
57
+ content: [
58
+ {
59
+ type: "text",
60
+ text: `Budget: $${result.budget}\nSpent: $${result.spent}\nRemaining: $${result.remaining}`,
61
+ },
62
+ ],
63
+ };
64
+ },
65
+ );
66
+
67
+ server.tool(
68
+ "list_categories",
69
+ "List all valid purchase categories.",
70
+ {},
71
+ async () => {
72
+ const result = await apiCall("/categories");
73
+ return {
74
+ content: [
75
+ {
76
+ type: "text",
77
+ text: `Valid categories:\n${result.categories.map((c) => `- ${c}`).join("\n")}`,
78
+ },
79
+ ],
80
+ };
81
+ },
82
+ );
83
+
84
+ server.tool(
85
+ "my_requests",
86
+ "Get the status of a specific purchase request by ID.",
87
+ {
88
+ request_id: z.string().describe("The purchase request ID"),
89
+ },
90
+ async ({ request_id }) => {
91
+ const result = await apiCall(`/requests/${request_id}`);
92
+ return {
93
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
94
+ };
95
+ },
96
+ );
97
+
98
+ server.tool(
99
+ "list_requests",
100
+ "List agent's purchase requests with optional status filter.",
101
+ {
102
+ status: z.string().optional().describe("Filter by status: pending, auto_approved, approved, rejected, expired, completed, failed"),
103
+ limit: z.number().optional().describe("Max results to return (default 20, max 100)"),
104
+ offset: z.number().optional().describe("Offset for pagination (default 0)"),
105
+ },
106
+ async ({ status, limit, offset }) => {
107
+ const params = new URLSearchParams();
108
+ if (status) params.set("status", status);
109
+ if (limit != null) params.set("limit", String(limit));
110
+ if (offset != null) params.set("offset", String(offset));
111
+ const query = params.toString();
112
+ const result = await apiCall(`/requests${query ? `?${query}` : ""}`);
113
+ return {
114
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
115
+ };
116
+ },
117
+ );
118
+
119
+ server.tool(
120
+ "confirm_purchase",
121
+ "Confirm that a purchase was completed (or failed). Call after an approved request.",
122
+ {
123
+ request_id: z.string().describe("The purchase request ID"),
124
+ success: z.boolean().describe("Whether the purchase was successful"),
125
+ actual_amount: z.number().optional().describe("Actual amount if different from requested"),
126
+ receipt_url: z.string().optional().describe("URL to receipt or confirmation"),
127
+ },
128
+ async ({ request_id, success, actual_amount, receipt_url }) => {
129
+ const result = await apiCall(`/requests/${request_id}/confirm`, {
130
+ method: "POST",
131
+ body: JSON.stringify({ success, actual_amount, receipt_url }),
132
+ });
133
+ return {
134
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
135
+ };
136
+ },
137
+ );
138
+
139
+ return server;
140
+ }