@curless/mcp-server 0.1.10

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 ADDED
@@ -0,0 +1,244 @@
1
+ # @curless/mcp-server
2
+
3
+ MCP server that lets any local AI agent (Claude Desktop, Cursor, Cline,
4
+ LangChain, custom Python script, …) drive a Curless wallet + virtual card to
5
+ order coffee, book hotels, and buy office supplies through real merchant
6
+ endpoints.
7
+
8
+ > **v0.1 = single-user demo mode.** Every install shares the same demo
9
+ > backend wallets. No real money — all charges are simulated. v0.2
10
+ > introduces per-user API keys and real on-chain USDC.
11
+
12
+ ---
13
+
14
+ ## What you get
15
+
16
+ 18 MCP tools that wrap the Curless REST API:
17
+
18
+ | Category | Tools |
19
+ |---|---|
20
+ | VCC | `create_vcc`, `top_up_vcc`, `update_vcc_limit`, `get_vcc_info` |
21
+ | Wallet | `get_wallet_info`, `fund_wallet` |
22
+ | Coffee (Nowwa) | `list_coffees`, `order_coffee` |
23
+ | Hotel (Club Med) | `list_properties`, `list_rooms`, `book_room` |
24
+ | Office supplies | `list_supplies`, `check_inventory`, `order_supplies`, `set_reorder_rule`, `list_supply_orders` |
25
+ | Receipts | `list_transactions` |
26
+
27
+ Each tool is a thin HTTPS wrapper — no LLM involvement inside the MCP
28
+ server. Your local agent decides what to call.
29
+
30
+ ---
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ npm install -g @curless/mcp-server
36
+ ```
37
+
38
+ Or run on-demand via `npx`:
39
+
40
+ ```bash
41
+ npx @curless/mcp-server
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Configure your client
47
+
48
+ ### Claude Desktop
49
+
50
+ Edit `~/Library/Application Support/Claude/claude_desktop_config.json`
51
+ (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
52
+
53
+ ```json
54
+ {
55
+ "mcpServers": {
56
+ "curless": {
57
+ "command": "npx",
58
+ "args": ["-y", "@curless/mcp-server"]
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ Restart Claude Desktop. The 18 tools appear under the 🔌 menu.
65
+
66
+ ### Cursor
67
+
68
+ Add to `~/.cursor/mcp.json`:
69
+
70
+ ```json
71
+ {
72
+ "mcpServers": {
73
+ "curless": {
74
+ "command": "npx",
75
+ "args": ["-y", "@curless/mcp-server"]
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ ### Cline (VS Code extension)
82
+
83
+ Open the Cline panel → Settings → MCP Servers → paste the same JSON.
84
+
85
+ ### Custom (Python / LangChain / etc.)
86
+
87
+ Spawn the binary with stdio transport. See the
88
+ [MCP client examples](https://modelcontextprotocol.io/docs/clients) for
89
+ your framework.
90
+
91
+ ---
92
+
93
+ ## Try it
94
+
95
+ Once connected, ask your agent:
96
+
97
+ - **"Create a VCC with a $50 limit"** → mints a card
98
+ - **"Top up the card with $20"** → moves USDC wallet → card
99
+ - **"Order a Coconut Latte"** → charges, returns order_id
100
+ - **"Book Club Med Bali for June 1-4"** → real booking flow
101
+ - **"A4 paper is running low, order 5 reams"** → procurement flow
102
+ - **"What's my wallet balance?"** → quick check
103
+
104
+ The agent will discover the right tool from the description. Names work
105
+ in Chinese too: "订一杯椰漾拿铁", "帮我订巴厘岛 Club Med, 9月1日入住, 9月4日退房", etc.
106
+
107
+ ---
108
+
109
+ ## Configuration
110
+
111
+ All optional, set as environment variables:
112
+
113
+ | Variable | Default | Purpose |
114
+ |---|---|---|
115
+ | `CURLESS_BASE_URL` | `https://openclaw.curless.ai` | Backend host. Point to staging or a self-hosted Curless. |
116
+ | `CURLESS_WALLET_ID` | `wal-002` | Which demo wallet to use. (`wal-001` TravelBot · `wal-002` ProcureAI · `wal-003` BookingBot · `wal-004` SupplyAgent) |
117
+ | `CURLESS_AGENT_ID` | `agent-002` | Default agent identity stamped on minted VCCs. |
118
+ | `CURLESS_STAMP_PREFIX` | `mcp` | Prefix on `agent_id` so MCP-originated records (e.g. `mcp-agent-002`) are distinguishable from web-UI traffic in the merchant dashboard. |
119
+ | `CURLESS_AUTO_OPEN_URLS` | unset | If `true` or `1`, terminal actions (order_coffee / book_room / order_supplies) auto-launch the storefront confirmation page in the user's default browser. Off by default — the URL is still surfaced as a clickable link in the tool response. |
120
+
121
+ ### Storefront page after order
122
+
123
+ The web UI animates an embedded iframe through `catalog → cart → order
124
+ detail` after a successful purchase. Desktop MCP clients (Claude
125
+ Desktop, Cursor, Cline) don't have an iframe context, so each terminal
126
+ tool now returns a `view_url` plus a markdown `🔗 Open in browser:`
127
+ content block that clients render as a clickable link. Click → real
128
+ browser opens to the order page on nowwa / clubmed / procurement.
129
+
130
+ For agents that should open the page without user interaction (kiosk
131
+ demos, hands-free walkthroughs), set `CURLESS_AUTO_OPEN_URLS=true`.
132
+ The MCP server then spawns the OS browser via `open` / `xdg-open` /
133
+ `start` the moment a successful order returns.
134
+
135
+ Example in `claude_desktop_config.json`:
136
+
137
+ ```json
138
+ {
139
+ "mcpServers": {
140
+ "curless": {
141
+ "command": "npx",
142
+ "args": ["-y", "@curless/mcp-server"],
143
+ "env": {
144
+ "CURLESS_WALLET_ID": "wal-004",
145
+ "CURLESS_STAMP_PREFIX": "my-bot",
146
+ "CURLESS_AUTO_OPEN_URLS": "true"
147
+ }
148
+ }
149
+ }
150
+ }
151
+ ```
152
+
153
+ ---
154
+
155
+ ## Tool semantics
156
+
157
+ ### State maintained by the server
158
+
159
+ The server keeps one piece of state in memory across tool calls: the
160
+ **most recently created VCC id**. So a typical session looks like:
161
+
162
+ ```
163
+ > Create a VCC for $100
164
+ → vcc-abc123 (server remembers this)
165
+ > Top up the card with $30
166
+ → no vcc_id needed; uses vcc-abc123
167
+ > Order a Cold Brew
168
+ → no vcc_id needed; uses vcc-abc123
169
+ ```
170
+
171
+ If you have multiple VCCs in flight, pass `vcc_id` explicitly.
172
+
173
+ ### Per-tool agent stamping
174
+
175
+ To keep merchant analytics sane, orders are tagged with the appropriate
176
+ agent based on tool category:
177
+
178
+ | Tool category | agent_id stamped on records |
179
+ |---|---|
180
+ | Coffee (`order_coffee`) | `${CURLESS_STAMP_PREFIX}-agent-002` |
181
+ | Hotel (`book_room`) | `${CURLESS_STAMP_PREFIX}-agent-001` |
182
+ | Supplies (`order_supplies`, `set_reorder_rule`, …) | `${CURLESS_STAMP_PREFIX}-agent-004` |
183
+ | VCC mint | `${CURLESS_STAMP_PREFIX}-${CURLESS_AGENT_ID}` |
184
+
185
+ With the default `STAMP_PREFIX=mcp`, your MCP-driven traffic is searchable
186
+ as `mcp-agent-*` in the Curless merchant dashboard.
187
+
188
+ ### Scheduled orders
189
+
190
+ `order_supplies` accepts `scheduled_for: "2026-07-21"`. Scheduled orders
191
+ are queued, not charged immediately, and don't require a VCC.
192
+
193
+ ### Error envelope
194
+
195
+ Every tool returns either a success object or an error envelope:
196
+
197
+ ```json
198
+ { "error": "insufficient_funds", "code": "VCC_INSUFFICIENT", "hint": "Top up via top_up_vcc and retry." }
199
+ ```
200
+
201
+ `isError: true` is set on the MCP response so your client can short-circuit
202
+ on failure.
203
+
204
+ ---
205
+
206
+ ## Limitations of v0.1
207
+
208
+ - **Shared wallets.** All installs share `wal-001..wal-004`. Your agent can
209
+ see other users' orders. **Do not use real money.**
210
+ - **No real money flow.** USDC transfers are simulated server-side. Card
211
+ charges go through `merchantChargeService` but no card rail is involved.
212
+ - **No retries / circuit breaker.** The client makes one attempt per call
213
+ and surfaces the error.
214
+ - **No webhook callbacks.** Long-running scheduled orders complete
215
+ silently — no push notifications.
216
+
217
+ v0.2 will address these via API keys, real on-chain settlement, and
218
+ webhook delivery.
219
+
220
+ ---
221
+
222
+ ## Development
223
+
224
+ ```bash
225
+ git clone https://github.com/onelabs-spark/curless-agent-payment.git
226
+ cd curless-agent-payment/mcp-server
227
+ npm install
228
+ npm run dev # uses tsx — restart on save
229
+ ```
230
+
231
+ To test against a local backend:
232
+
233
+ ```bash
234
+ CURLESS_BASE_URL=http://localhost:3000 npm run dev
235
+ ```
236
+
237
+ To smoke-test individual tools without a chat client, use any MCP
238
+ inspector (e.g. `npx @modelcontextprotocol/inspector npx @curless/mcp-server`).
239
+
240
+ ---
241
+
242
+ ## License
243
+
244
+ MIT
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Thin HTTP client for the Curless REST API.
3
+ *
4
+ * v0.1 is single-user demo mode — there's no per-user authentication.
5
+ * Every install of this MCP server talks to the same shared Curless
6
+ * backend and uses the same demo wallets. Production multi-user mode
7
+ * will swap the constructor to take an API key and add Bearer auth.
8
+ */
9
+ export declare class HttpError extends Error {
10
+ readonly status: number;
11
+ readonly body: unknown;
12
+ constructor(status: number, body: unknown);
13
+ }
14
+ export interface CurlessClientOptions {
15
+ /** Base URL — defaults to env CURLESS_BASE_URL or production. */
16
+ baseUrl?: string;
17
+ /** Per-request timeout in ms. Defaults to 30s. */
18
+ timeoutMs?: number;
19
+ }
20
+ export declare class CurlessClient {
21
+ private readonly baseUrl;
22
+ private readonly timeoutMs;
23
+ constructor(opts?: CurlessClientOptions);
24
+ /** GET with optional query params. */
25
+ get<T = unknown>(path: string, params?: Record<string, unknown>): Promise<T>;
26
+ /** POST JSON body. */
27
+ post<T = unknown>(path: string, body: unknown): Promise<T>;
28
+ /** PUT JSON body. */
29
+ put<T = unknown>(path: string, body: unknown): Promise<T>;
30
+ private fetch;
31
+ }
32
+ /**
33
+ * Most Curless endpoints wrap their payload in `{ success, data }`.
34
+ * This helper peels that off, falling through if the response is
35
+ * already the bare object.
36
+ */
37
+ export declare function unwrap<T>(res: unknown): T;
package/dist/client.js ADDED
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Thin HTTP client for the Curless REST API.
3
+ *
4
+ * v0.1 is single-user demo mode — there's no per-user authentication.
5
+ * Every install of this MCP server talks to the same shared Curless
6
+ * backend and uses the same demo wallets. Production multi-user mode
7
+ * will swap the constructor to take an API key and add Bearer auth.
8
+ */
9
+ export class HttpError extends Error {
10
+ status;
11
+ body;
12
+ constructor(status, body) {
13
+ const preview = typeof body === 'string' ? body.slice(0, 200) : JSON.stringify(body).slice(0, 200);
14
+ super(`HTTP ${status}: ${preview}`);
15
+ this.status = status;
16
+ this.body = body;
17
+ this.name = 'HttpError';
18
+ }
19
+ }
20
+ export class CurlessClient {
21
+ baseUrl;
22
+ timeoutMs;
23
+ constructor(opts = {}) {
24
+ const fromEnv = process.env.CURLESS_BASE_URL;
25
+ this.baseUrl = (opts.baseUrl ?? fromEnv ?? 'https://openclaw.curless.ai').replace(/\/$/, '');
26
+ this.timeoutMs = opts.timeoutMs ?? 30_000;
27
+ }
28
+ /** GET with optional query params. */
29
+ async get(path, params) {
30
+ const url = new URL(this.baseUrl + path);
31
+ if (params) {
32
+ for (const [k, v] of Object.entries(params)) {
33
+ if (v !== undefined && v !== null && v !== '')
34
+ url.searchParams.set(k, String(v));
35
+ }
36
+ }
37
+ return this.fetch(url.toString(), { method: 'GET' });
38
+ }
39
+ /** POST JSON body. */
40
+ async post(path, body) {
41
+ return this.fetch(this.baseUrl + path, {
42
+ method: 'POST',
43
+ headers: { 'Content-Type': 'application/json' },
44
+ body: JSON.stringify(body),
45
+ });
46
+ }
47
+ /** PUT JSON body. */
48
+ async put(path, body) {
49
+ return this.fetch(this.baseUrl + path, {
50
+ method: 'PUT',
51
+ headers: { 'Content-Type': 'application/json' },
52
+ body: JSON.stringify(body),
53
+ });
54
+ }
55
+ async fetch(url, init) {
56
+ const ac = new AbortController();
57
+ const timer = setTimeout(() => ac.abort(), this.timeoutMs);
58
+ try {
59
+ const res = await fetch(url, { ...init, signal: ac.signal });
60
+ const text = await res.text();
61
+ let parsed = text;
62
+ try {
63
+ parsed = text ? JSON.parse(text) : null;
64
+ }
65
+ catch {
66
+ // Non-JSON response — keep text as the body.
67
+ }
68
+ if (!res.ok) {
69
+ throw new HttpError(res.status, parsed);
70
+ }
71
+ return parsed;
72
+ }
73
+ finally {
74
+ clearTimeout(timer);
75
+ }
76
+ }
77
+ }
78
+ /**
79
+ * Most Curless endpoints wrap their payload in `{ success, data }`.
80
+ * This helper peels that off, falling through if the response is
81
+ * already the bare object.
82
+ */
83
+ export function unwrap(res) {
84
+ if (res && typeof res === 'object' && 'data' in res) {
85
+ return res.data;
86
+ }
87
+ return res;
88
+ }
89
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Curless MCP Server — lets any MCP-aware client (Claude Desktop, Cursor,
4
+ * Cline, custom LangChain agent, …) drive a Curless wallet + VCC to order
5
+ * coffee, book hotels, and buy office supplies through the Curless backend.
6
+ *
7
+ * v0.1 = single-user demo mode. All installs share the same demo wallets
8
+ * (wal-001..wal-004). v0.2 will introduce per-user API keys.
9
+ */
10
+ export {};