@codespar/mcp-circle 0.1.0 → 0.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 +4 -0
- package/dist/index.js +299 -46
- package/package.json +4 -3
- package/server.json +30 -0
- package/src/__tests__/index.test.ts +50 -0
- package/src/index.ts +304 -39
package/README.md
CHANGED
|
@@ -107,6 +107,10 @@ Want to contribute? [Open a PR](https://github.com/codespar/mcp-dev-brasil) or [
|
|
|
107
107
|
- [MCP Dev Brasil](https://github.com/codespar/mcp-dev-brasil)
|
|
108
108
|
- [Landing Page](https://codespar.dev/mcp)
|
|
109
109
|
|
|
110
|
+
## Enterprise
|
|
111
|
+
|
|
112
|
+
Need governance, budget limits, and audit trails for agent payments? [CodeSpar Enterprise](https://codespar.dev/enterprise) adds policy engine, payment routing, and compliance templates on top of these MCP servers.
|
|
113
|
+
|
|
110
114
|
## License
|
|
111
115
|
|
|
112
116
|
MIT
|
package/dist/index.js
CHANGED
|
@@ -3,15 +3,28 @@
|
|
|
3
3
|
* MCP Server for Circle — USDC stablecoin infrastructure.
|
|
4
4
|
*
|
|
5
5
|
* Tools:
|
|
6
|
-
* - create_wallet: Create a new Circle wallet
|
|
6
|
+
* - create_wallet: Create a new Circle business-account wallet
|
|
7
7
|
* - get_wallet: Get wallet details by ID
|
|
8
|
+
* - list_wallets: List all wallets
|
|
8
9
|
* - create_payment: Accept a USDC payment
|
|
9
10
|
* - get_payment: Get payment details by ID
|
|
10
11
|
* - create_payout: Create a payout (USDC to fiat)
|
|
11
12
|
* - get_payout: Get payout details by ID
|
|
13
|
+
* - list_payouts: List payouts with filters
|
|
12
14
|
* - create_transfer: Create a USDC transfer between wallets
|
|
13
15
|
* - get_transfer: Get transfer details by ID
|
|
14
|
-
* -
|
|
16
|
+
* - list_transfers: List transfers with filters
|
|
17
|
+
* - create_card: Register card data for on-ramp
|
|
18
|
+
* - get_card: Get card details by ID
|
|
19
|
+
* - list_cards: List cards
|
|
20
|
+
* - list_settlements: List settlements
|
|
21
|
+
* - get_settlement: Get settlement details by ID
|
|
22
|
+
* - list_chargebacks: List chargebacks
|
|
23
|
+
* - get_chargeback: Get chargeback by ID
|
|
24
|
+
* - create_subscription: Register a notification subscription (webhook)
|
|
25
|
+
* - list_subscriptions: List notification subscriptions
|
|
26
|
+
* - delete_subscription: Remove a notification subscription
|
|
27
|
+
* - get_balance: Get business-account balance
|
|
15
28
|
* - list_transactions: List transactions with filters
|
|
16
29
|
*
|
|
17
30
|
* Environment:
|
|
@@ -19,6 +32,8 @@
|
|
|
19
32
|
*/
|
|
20
33
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
21
34
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
35
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
36
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
22
37
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
23
38
|
const API_KEY = process.env.CIRCLE_API_KEY || "";
|
|
24
39
|
const BASE_URL = "https://api.circle.com/v1";
|
|
@@ -37,12 +52,29 @@ async function circleRequest(method, path, body) {
|
|
|
37
52
|
}
|
|
38
53
|
return res.json();
|
|
39
54
|
}
|
|
40
|
-
const server = new Server({ name: "mcp-circle", version: "0.
|
|
55
|
+
const server = new Server({ name: "mcp-circle", version: "0.2.0" }, { capabilities: { tools: {} } });
|
|
56
|
+
const amountSchema = {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
amount: { type: "string", description: "Amount (e.g. '10.00')" },
|
|
60
|
+
currency: { type: "string", description: "Currency (USD)" },
|
|
61
|
+
},
|
|
62
|
+
required: ["amount", "currency"],
|
|
63
|
+
};
|
|
64
|
+
const sourceDestSchema = (label) => ({
|
|
65
|
+
type: "object",
|
|
66
|
+
properties: {
|
|
67
|
+
id: { type: "string", description: `${label} ID` },
|
|
68
|
+
type: { type: "string", description: `${label} type (e.g. wallet, card, ach, wire, blockchain)` },
|
|
69
|
+
},
|
|
70
|
+
required: ["id", "type"],
|
|
71
|
+
});
|
|
41
72
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
42
73
|
tools: [
|
|
74
|
+
// Wallets
|
|
43
75
|
{
|
|
44
76
|
name: "create_wallet",
|
|
45
|
-
description: "Create a new Circle wallet",
|
|
77
|
+
description: "Create a new Circle business-account wallet",
|
|
46
78
|
inputSchema: {
|
|
47
79
|
type: "object",
|
|
48
80
|
properties: {
|
|
@@ -55,14 +87,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
55
87
|
{
|
|
56
88
|
name: "get_wallet",
|
|
57
89
|
description: "Get wallet details by ID",
|
|
90
|
+
inputSchema: {
|
|
91
|
+
type: "object",
|
|
92
|
+
properties: { id: { type: "string", description: "Wallet ID" } },
|
|
93
|
+
required: ["id"],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "list_wallets",
|
|
98
|
+
description: "List all Circle wallets",
|
|
58
99
|
inputSchema: {
|
|
59
100
|
type: "object",
|
|
60
101
|
properties: {
|
|
61
|
-
|
|
102
|
+
pageSize: { type: "number", description: "Number of results per page" },
|
|
103
|
+
pageBefore: { type: "string", description: "Cursor for previous page" },
|
|
104
|
+
pageAfter: { type: "string", description: "Cursor for next page" },
|
|
62
105
|
},
|
|
63
|
-
required: ["id"],
|
|
64
106
|
},
|
|
65
107
|
},
|
|
108
|
+
// Payments
|
|
66
109
|
{
|
|
67
110
|
name: "create_payment",
|
|
68
111
|
description: "Accept a USDC payment via Circle",
|
|
@@ -70,9 +113,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
70
113
|
type: "object",
|
|
71
114
|
properties: {
|
|
72
115
|
idempotencyKey: { type: "string", description: "Unique idempotency key (UUID)" },
|
|
73
|
-
amount: {
|
|
74
|
-
source: {
|
|
116
|
+
amount: { ...amountSchema, description: "Payment amount" },
|
|
117
|
+
source: { ...sourceDestSchema("Source"), description: "Payment source" },
|
|
75
118
|
description: { type: "string", description: "Payment description" },
|
|
119
|
+
verification: { type: "string", description: "Verification method (cvv, three_d_secure, none)" },
|
|
120
|
+
metadata: { type: "object", description: "Payment metadata (email, phoneNumber, sessionId, ipAddress)" },
|
|
76
121
|
},
|
|
77
122
|
required: ["idempotencyKey", "amount", "source"],
|
|
78
123
|
},
|
|
@@ -82,12 +127,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
82
127
|
description: "Get payment details by ID",
|
|
83
128
|
inputSchema: {
|
|
84
129
|
type: "object",
|
|
85
|
-
properties: {
|
|
86
|
-
id: { type: "string", description: "Payment ID" },
|
|
87
|
-
},
|
|
130
|
+
properties: { id: { type: "string", description: "Payment ID" } },
|
|
88
131
|
required: ["id"],
|
|
89
132
|
},
|
|
90
133
|
},
|
|
134
|
+
// Payouts
|
|
91
135
|
{
|
|
92
136
|
name: "create_payout",
|
|
93
137
|
description: "Create a payout from Circle (USDC to fiat)",
|
|
@@ -95,8 +139,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
95
139
|
type: "object",
|
|
96
140
|
properties: {
|
|
97
141
|
idempotencyKey: { type: "string", description: "Unique idempotency key (UUID)" },
|
|
98
|
-
amount: {
|
|
99
|
-
destination: {
|
|
142
|
+
amount: { ...amountSchema, description: "Payout amount" },
|
|
143
|
+
destination: { ...sourceDestSchema("Destination"), description: "Payout destination (bank account)" },
|
|
144
|
+
metadata: { type: "object", description: "Payout metadata (beneficiaryEmail)" },
|
|
100
145
|
},
|
|
101
146
|
required: ["idempotencyKey", "amount", "destination"],
|
|
102
147
|
},
|
|
@@ -104,24 +149,39 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
104
149
|
{
|
|
105
150
|
name: "get_payout",
|
|
106
151
|
description: "Get payout details by ID",
|
|
152
|
+
inputSchema: {
|
|
153
|
+
type: "object",
|
|
154
|
+
properties: { id: { type: "string", description: "Payout ID" } },
|
|
155
|
+
required: ["id"],
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: "list_payouts",
|
|
160
|
+
description: "List payouts with optional filters",
|
|
107
161
|
inputSchema: {
|
|
108
162
|
type: "object",
|
|
109
163
|
properties: {
|
|
110
|
-
|
|
164
|
+
source: { type: "string", description: "Filter by source wallet ID" },
|
|
165
|
+
destination: { type: "string", description: "Filter by destination ID" },
|
|
166
|
+
type: { type: "string", description: "Filter by type (wire, ach, sen)" },
|
|
167
|
+
status: { type: "string", description: "Filter by status (pending, complete, failed)" },
|
|
168
|
+
from: { type: "string", description: "Start date (ISO 8601)" },
|
|
169
|
+
to: { type: "string", description: "End date (ISO 8601)" },
|
|
170
|
+
pageSize: { type: "number", description: "Results per page" },
|
|
111
171
|
},
|
|
112
|
-
required: ["id"],
|
|
113
172
|
},
|
|
114
173
|
},
|
|
174
|
+
// Transfers
|
|
115
175
|
{
|
|
116
176
|
name: "create_transfer",
|
|
117
|
-
description: "Create a USDC transfer between Circle wallets",
|
|
177
|
+
description: "Create a USDC transfer between Circle wallets (or to blockchain address)",
|
|
118
178
|
inputSchema: {
|
|
119
179
|
type: "object",
|
|
120
180
|
properties: {
|
|
121
181
|
idempotencyKey: { type: "string", description: "Unique idempotency key (UUID)" },
|
|
122
|
-
amount: {
|
|
123
|
-
source: {
|
|
124
|
-
destination: {
|
|
182
|
+
amount: { ...amountSchema, description: "Transfer amount" },
|
|
183
|
+
source: { ...sourceDestSchema("Source"), description: "Transfer source (wallet)" },
|
|
184
|
+
destination: { ...sourceDestSchema("Destination"), description: "Transfer destination (wallet or blockchain)" },
|
|
125
185
|
},
|
|
126
186
|
required: ["idempotencyKey", "amount", "source", "destination"],
|
|
127
187
|
},
|
|
@@ -129,17 +189,143 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
129
189
|
{
|
|
130
190
|
name: "get_transfer",
|
|
131
191
|
description: "Get transfer details by ID",
|
|
192
|
+
inputSchema: {
|
|
193
|
+
type: "object",
|
|
194
|
+
properties: { id: { type: "string", description: "Transfer ID" } },
|
|
195
|
+
required: ["id"],
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "list_transfers",
|
|
200
|
+
description: "List transfers with optional filters",
|
|
201
|
+
inputSchema: {
|
|
202
|
+
type: "object",
|
|
203
|
+
properties: {
|
|
204
|
+
walletId: { type: "string", description: "Filter by wallet ID" },
|
|
205
|
+
sourceWalletId: { type: "string", description: "Filter by source wallet ID" },
|
|
206
|
+
destinationWalletId: { type: "string", description: "Filter by destination wallet ID" },
|
|
207
|
+
from: { type: "string", description: "Start date (ISO 8601)" },
|
|
208
|
+
to: { type: "string", description: "End date (ISO 8601)" },
|
|
209
|
+
pageSize: { type: "number", description: "Results per page" },
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
// Cards
|
|
214
|
+
{
|
|
215
|
+
name: "create_card",
|
|
216
|
+
description: "Register card data for on-ramp payments",
|
|
132
217
|
inputSchema: {
|
|
133
218
|
type: "object",
|
|
134
219
|
properties: {
|
|
135
|
-
|
|
220
|
+
idempotencyKey: { type: "string", description: "Unique idempotency key (UUID)" },
|
|
221
|
+
keyId: { type: "string", description: "Public key ID used to encrypt card data" },
|
|
222
|
+
encryptedData: { type: "string", description: "PGP-encrypted card number + CVV" },
|
|
223
|
+
billingDetails: { type: "object", description: "Billing address details (name, city, country, line1, postalCode, district)" },
|
|
224
|
+
expMonth: { type: "number", description: "Card expiration month (1-12)" },
|
|
225
|
+
expYear: { type: "number", description: "Card expiration year (4-digit)" },
|
|
226
|
+
metadata: { type: "object", description: "Card metadata (email, phoneNumber, sessionId, ipAddress)" },
|
|
136
227
|
},
|
|
228
|
+
required: ["idempotencyKey", "keyId", "encryptedData", "billingDetails", "expMonth", "expYear"],
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
name: "get_card",
|
|
233
|
+
description: "Get card details by ID",
|
|
234
|
+
inputSchema: {
|
|
235
|
+
type: "object",
|
|
236
|
+
properties: { id: { type: "string", description: "Card ID" } },
|
|
137
237
|
required: ["id"],
|
|
138
238
|
},
|
|
139
239
|
},
|
|
240
|
+
{
|
|
241
|
+
name: "list_cards",
|
|
242
|
+
description: "List registered cards",
|
|
243
|
+
inputSchema: {
|
|
244
|
+
type: "object",
|
|
245
|
+
properties: {
|
|
246
|
+
pageSize: { type: "number", description: "Results per page" },
|
|
247
|
+
pageBefore: { type: "string", description: "Cursor for previous page" },
|
|
248
|
+
pageAfter: { type: "string", description: "Cursor for next page" },
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
// Settlements
|
|
253
|
+
{
|
|
254
|
+
name: "list_settlements",
|
|
255
|
+
description: "List settlements (card payment batches)",
|
|
256
|
+
inputSchema: {
|
|
257
|
+
type: "object",
|
|
258
|
+
properties: {
|
|
259
|
+
from: { type: "string", description: "Start date (ISO 8601)" },
|
|
260
|
+
to: { type: "string", description: "End date (ISO 8601)" },
|
|
261
|
+
pageSize: { type: "number", description: "Results per page" },
|
|
262
|
+
pageBefore: { type: "string", description: "Cursor for previous page" },
|
|
263
|
+
pageAfter: { type: "string", description: "Cursor for next page" },
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
name: "get_settlement",
|
|
269
|
+
description: "Get settlement details by ID",
|
|
270
|
+
inputSchema: {
|
|
271
|
+
type: "object",
|
|
272
|
+
properties: { id: { type: "string", description: "Settlement ID" } },
|
|
273
|
+
required: ["id"],
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
// Chargebacks
|
|
277
|
+
{
|
|
278
|
+
name: "list_chargebacks",
|
|
279
|
+
description: "List chargebacks",
|
|
280
|
+
inputSchema: {
|
|
281
|
+
type: "object",
|
|
282
|
+
properties: {
|
|
283
|
+
paymentId: { type: "string", description: "Filter by payment ID" },
|
|
284
|
+
from: { type: "string", description: "Start date (ISO 8601)" },
|
|
285
|
+
to: { type: "string", description: "End date (ISO 8601)" },
|
|
286
|
+
pageSize: { type: "number", description: "Results per page" },
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: "get_chargeback",
|
|
292
|
+
description: "Get chargeback details by ID",
|
|
293
|
+
inputSchema: {
|
|
294
|
+
type: "object",
|
|
295
|
+
properties: { id: { type: "string", description: "Chargeback ID" } },
|
|
296
|
+
required: ["id"],
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
// Notification subscriptions (webhooks)
|
|
300
|
+
{
|
|
301
|
+
name: "create_subscription",
|
|
302
|
+
description: "Register a notification subscription (webhook)",
|
|
303
|
+
inputSchema: {
|
|
304
|
+
type: "object",
|
|
305
|
+
properties: {
|
|
306
|
+
endpoint: { type: "string", description: "HTTPS webhook endpoint URL" },
|
|
307
|
+
},
|
|
308
|
+
required: ["endpoint"],
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: "list_subscriptions",
|
|
313
|
+
description: "List notification subscriptions (webhooks)",
|
|
314
|
+
inputSchema: { type: "object", properties: {} },
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: "delete_subscription",
|
|
318
|
+
description: "Delete a notification subscription",
|
|
319
|
+
inputSchema: {
|
|
320
|
+
type: "object",
|
|
321
|
+
properties: { id: { type: "string", description: "Subscription ID" } },
|
|
322
|
+
required: ["id"],
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
// Balance + transactions
|
|
140
326
|
{
|
|
141
327
|
name: "get_balance",
|
|
142
|
-
description: "Get account balance",
|
|
328
|
+
description: "Get business-account balance",
|
|
143
329
|
inputSchema: { type: "object", properties: {} },
|
|
144
330
|
},
|
|
145
331
|
{
|
|
@@ -160,46 +346,78 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
160
346
|
},
|
|
161
347
|
],
|
|
162
348
|
}));
|
|
349
|
+
function buildQuery(args, keys) {
|
|
350
|
+
const params = new URLSearchParams();
|
|
351
|
+
if (args) {
|
|
352
|
+
for (const k of keys) {
|
|
353
|
+
const v = args[k];
|
|
354
|
+
if (v !== undefined && v !== null && v !== "")
|
|
355
|
+
params.set(k, String(v));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const q = params.toString();
|
|
359
|
+
return q ? `?${q}` : "";
|
|
360
|
+
}
|
|
163
361
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
164
362
|
const { name, arguments: args } = request.params;
|
|
165
363
|
try {
|
|
166
364
|
switch (name) {
|
|
365
|
+
// Wallets
|
|
167
366
|
case "create_wallet":
|
|
168
|
-
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("POST", "/wallets", args), null, 2) }] };
|
|
367
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("POST", "/businessAccount/wallets", args), null, 2) }] };
|
|
169
368
|
case "get_wallet":
|
|
170
369
|
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/wallets/${args?.id}`), null, 2) }] };
|
|
370
|
+
case "list_wallets":
|
|
371
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/wallets${buildQuery(args, ["pageSize", "pageBefore", "pageAfter"])}`), null, 2) }] };
|
|
372
|
+
// Payments
|
|
171
373
|
case "create_payment":
|
|
172
374
|
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("POST", "/payments", args), null, 2) }] };
|
|
173
375
|
case "get_payment":
|
|
174
376
|
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/payments/${args?.id}`), null, 2) }] };
|
|
377
|
+
// Payouts
|
|
175
378
|
case "create_payout":
|
|
176
379
|
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("POST", "/payouts", args), null, 2) }] };
|
|
177
380
|
case "get_payout":
|
|
178
381
|
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/payouts/${args?.id}`), null, 2) }] };
|
|
382
|
+
case "list_payouts":
|
|
383
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/payouts${buildQuery(args, ["source", "destination", "type", "status", "from", "to", "pageSize"])}`), null, 2) }] };
|
|
384
|
+
// Transfers
|
|
179
385
|
case "create_transfer":
|
|
180
386
|
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("POST", "/transfers", args), null, 2) }] };
|
|
181
387
|
case "get_transfer":
|
|
182
388
|
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/transfers/${args?.id}`), null, 2) }] };
|
|
389
|
+
case "list_transfers":
|
|
390
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/transfers${buildQuery(args, ["walletId", "sourceWalletId", "destinationWalletId", "from", "to", "pageSize"])}`), null, 2) }] };
|
|
391
|
+
// Cards
|
|
392
|
+
case "create_card":
|
|
393
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("POST", "/cards", args), null, 2) }] };
|
|
394
|
+
case "get_card":
|
|
395
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/cards/${args?.id}`), null, 2) }] };
|
|
396
|
+
case "list_cards":
|
|
397
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/cards${buildQuery(args, ["pageSize", "pageBefore", "pageAfter"])}`), null, 2) }] };
|
|
398
|
+
// Settlements
|
|
399
|
+
case "list_settlements":
|
|
400
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/settlements${buildQuery(args, ["from", "to", "pageSize", "pageBefore", "pageAfter"])}`), null, 2) }] };
|
|
401
|
+
case "get_settlement":
|
|
402
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/settlements/${args?.id}`), null, 2) }] };
|
|
403
|
+
// Chargebacks
|
|
404
|
+
case "list_chargebacks":
|
|
405
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/chargebacks${buildQuery(args, ["paymentId", "from", "to", "pageSize"])}`), null, 2) }] };
|
|
406
|
+
case "get_chargeback":
|
|
407
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/chargebacks/${args?.id}`), null, 2) }] };
|
|
408
|
+
// Notification subscriptions
|
|
409
|
+
case "create_subscription":
|
|
410
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("POST", "/notifications/subscriptions", args), null, 2) }] };
|
|
411
|
+
case "list_subscriptions":
|
|
412
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", "/notifications/subscriptions"), null, 2) }] };
|
|
413
|
+
case "delete_subscription":
|
|
414
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("DELETE", `/notifications/subscriptions/${args?.id}`), null, 2) }] };
|
|
415
|
+
// Balance
|
|
183
416
|
case "get_balance":
|
|
184
|
-
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", "/balances"), null, 2) }] };
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
params.set("type", String(args.type));
|
|
189
|
-
if (args?.status)
|
|
190
|
-
params.set("status", String(args.status));
|
|
191
|
-
if (args?.from)
|
|
192
|
-
params.set("from", String(args.from));
|
|
193
|
-
if (args?.to)
|
|
194
|
-
params.set("to", String(args.to));
|
|
195
|
-
if (args?.pageSize)
|
|
196
|
-
params.set("pageSize", String(args.pageSize));
|
|
197
|
-
if (args?.pageBefore)
|
|
198
|
-
params.set("pageBefore", String(args.pageBefore));
|
|
199
|
-
if (args?.pageAfter)
|
|
200
|
-
params.set("pageAfter", String(args.pageAfter));
|
|
201
|
-
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/transactions?${params}`), null, 2) }] };
|
|
202
|
-
}
|
|
417
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", "/businessAccount/balances"), null, 2) }] };
|
|
418
|
+
// Transactions
|
|
419
|
+
case "list_transactions":
|
|
420
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/transactions${buildQuery(args, ["type", "status", "from", "to", "pageSize", "pageBefore", "pageAfter"])}`), null, 2) }] };
|
|
203
421
|
default:
|
|
204
422
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
205
423
|
}
|
|
@@ -209,11 +427,46 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
209
427
|
}
|
|
210
428
|
});
|
|
211
429
|
async function main() {
|
|
212
|
-
if (
|
|
213
|
-
|
|
214
|
-
|
|
430
|
+
if (process.argv.includes("--http") || process.env.MCP_HTTP === "true") {
|
|
431
|
+
const { default: express } = await import("express");
|
|
432
|
+
const { randomUUID } = await import("node:crypto");
|
|
433
|
+
const app = express();
|
|
434
|
+
app.use(express.json());
|
|
435
|
+
const transports = new Map();
|
|
436
|
+
app.get("/health", (_req, res) => res.json({ status: "ok", sessions: transports.size }));
|
|
437
|
+
app.post("/mcp", async (req, res) => {
|
|
438
|
+
const sid = req.headers["mcp-session-id"];
|
|
439
|
+
if (sid && transports.has(sid)) {
|
|
440
|
+
await transports.get(sid).handleRequest(req, res, req.body);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (!sid && isInitializeRequest(req.body)) {
|
|
444
|
+
const t = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (id) => { transports.set(id, t); } });
|
|
445
|
+
t.onclose = () => { if (t.sessionId)
|
|
446
|
+
transports.delete(t.sessionId); };
|
|
447
|
+
const s = new Server({ name: "mcp-circle", version: "0.2.0" }, { capabilities: { tools: {} } });
|
|
448
|
+
server._requestHandlers.forEach((v, k) => s._requestHandlers.set(k, v));
|
|
449
|
+
server._notificationHandlers?.forEach((v, k) => s._notificationHandlers.set(k, v));
|
|
450
|
+
await s.connect(t);
|
|
451
|
+
await t.handleRequest(req, res, req.body);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request" }, id: null });
|
|
455
|
+
});
|
|
456
|
+
app.get("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"]; if (sid && transports.has(sid))
|
|
457
|
+
await transports.get(sid).handleRequest(req, res);
|
|
458
|
+
else
|
|
459
|
+
res.status(400).send("Invalid session"); });
|
|
460
|
+
app.delete("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"]; if (sid && transports.has(sid))
|
|
461
|
+
await transports.get(sid).handleRequest(req, res);
|
|
462
|
+
else
|
|
463
|
+
res.status(400).send("Invalid session"); });
|
|
464
|
+
const port = Number(process.env.MCP_PORT) || 3000;
|
|
465
|
+
app.listen(port, () => { console.error(`MCP HTTP server on http://localhost:${port}/mcp`); });
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
const transport = new StdioServerTransport();
|
|
469
|
+
await server.connect(transport);
|
|
215
470
|
}
|
|
216
|
-
const transport = new StdioServerTransport();
|
|
217
|
-
await server.connect(transport);
|
|
218
471
|
}
|
|
219
472
|
main().catch(console.error);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codespar/mcp-circle",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server for Circle — USDC payments, wallets, payouts, transfers",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "MCP server for Circle — USDC payments, wallets, payouts, transfers, cards, settlements, chargebacks, webhooks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -26,5 +26,6 @@
|
|
|
26
26
|
"crypto",
|
|
27
27
|
"stablecoin",
|
|
28
28
|
"payments"
|
|
29
|
-
]
|
|
29
|
+
],
|
|
30
|
+
"mcpName": "io.github.codespar/mcp-circle"
|
|
30
31
|
}
|
package/server.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.codespar/mcp-circle",
|
|
4
|
+
"description": "MCP server for Circle — USDC payments, wallets, payouts, transfers",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/codespar/mcp-dev-brasil",
|
|
7
|
+
"source": "github",
|
|
8
|
+
"subfolder": "packages/crypto/circle"
|
|
9
|
+
},
|
|
10
|
+
"version": "0.2.0",
|
|
11
|
+
"packages": [
|
|
12
|
+
{
|
|
13
|
+
"registryType": "npm",
|
|
14
|
+
"identifier": "@codespar/mcp-circle",
|
|
15
|
+
"version": "0.2.0",
|
|
16
|
+
"transport": {
|
|
17
|
+
"type": "stdio"
|
|
18
|
+
},
|
|
19
|
+
"environmentVariables": [
|
|
20
|
+
{
|
|
21
|
+
"name": "CIRCLE_API_KEY",
|
|
22
|
+
"description": "API key for circle",
|
|
23
|
+
"isRequired": true,
|
|
24
|
+
"format": "string",
|
|
25
|
+
"isSecret": true
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
|
|
3
|
+
let listToolsHandler: Function;
|
|
4
|
+
let callToolHandler: Function;
|
|
5
|
+
|
|
6
|
+
vi.mock("@modelcontextprotocol/sdk/server/index.js", () => {
|
|
7
|
+
class FakeServer {
|
|
8
|
+
constructor() {}
|
|
9
|
+
setRequestHandler(schema: any, handler: Function) {
|
|
10
|
+
if (JSON.stringify(schema).includes("tools/list")) listToolsHandler = handler;
|
|
11
|
+
if (JSON.stringify(schema).includes("tools/call")) callToolHandler = handler;
|
|
12
|
+
}
|
|
13
|
+
connect() { return Promise.resolve(); }
|
|
14
|
+
}
|
|
15
|
+
return { Server: FakeServer };
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
vi.mock("@modelcontextprotocol/sdk/server/stdio.js", () => ({ StdioServerTransport: class {} }));
|
|
19
|
+
|
|
20
|
+
process.env.CIRCLE_API_KEY = "test-key";
|
|
21
|
+
|
|
22
|
+
const mockFetch = vi.fn();
|
|
23
|
+
global.fetch = mockFetch as any;
|
|
24
|
+
|
|
25
|
+
beforeEach(async () => {
|
|
26
|
+
vi.resetModules();
|
|
27
|
+
listToolsHandler = undefined as any;
|
|
28
|
+
callToolHandler = undefined as any;
|
|
29
|
+
mockFetch.mockReset();
|
|
30
|
+
global.fetch = mockFetch as any;
|
|
31
|
+
await import("../index.js");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("mcp-circle", () => {
|
|
35
|
+
it("should register 10 tools", async () => {
|
|
36
|
+
const result = await listToolsHandler();
|
|
37
|
+
expect(result.tools).toHaveLength(10);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should call correct API endpoint for create_wallet", async () => {
|
|
41
|
+
mockFetch.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ data: { walletId: "w1" } }) });
|
|
42
|
+
|
|
43
|
+
await callToolHandler({ params: { name: "create_wallet", arguments: { idempotencyKey: "key1" } } });
|
|
44
|
+
|
|
45
|
+
const [url, opts] = mockFetch.mock.calls[0];
|
|
46
|
+
expect(url).toContain("api.circle.com/v1/wallets");
|
|
47
|
+
expect(opts.method).toBe("POST");
|
|
48
|
+
expect(opts.headers.Authorization).toBe("Bearer test-key");
|
|
49
|
+
});
|
|
50
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -4,15 +4,28 @@
|
|
|
4
4
|
* MCP Server for Circle — USDC stablecoin infrastructure.
|
|
5
5
|
*
|
|
6
6
|
* Tools:
|
|
7
|
-
* - create_wallet: Create a new Circle wallet
|
|
7
|
+
* - create_wallet: Create a new Circle business-account wallet
|
|
8
8
|
* - get_wallet: Get wallet details by ID
|
|
9
|
+
* - list_wallets: List all wallets
|
|
9
10
|
* - create_payment: Accept a USDC payment
|
|
10
11
|
* - get_payment: Get payment details by ID
|
|
11
12
|
* - create_payout: Create a payout (USDC to fiat)
|
|
12
13
|
* - get_payout: Get payout details by ID
|
|
14
|
+
* - list_payouts: List payouts with filters
|
|
13
15
|
* - create_transfer: Create a USDC transfer between wallets
|
|
14
16
|
* - get_transfer: Get transfer details by ID
|
|
15
|
-
* -
|
|
17
|
+
* - list_transfers: List transfers with filters
|
|
18
|
+
* - create_card: Register card data for on-ramp
|
|
19
|
+
* - get_card: Get card details by ID
|
|
20
|
+
* - list_cards: List cards
|
|
21
|
+
* - list_settlements: List settlements
|
|
22
|
+
* - get_settlement: Get settlement details by ID
|
|
23
|
+
* - list_chargebacks: List chargebacks
|
|
24
|
+
* - get_chargeback: Get chargeback by ID
|
|
25
|
+
* - create_subscription: Register a notification subscription (webhook)
|
|
26
|
+
* - list_subscriptions: List notification subscriptions
|
|
27
|
+
* - delete_subscription: Remove a notification subscription
|
|
28
|
+
* - get_balance: Get business-account balance
|
|
16
29
|
* - list_transactions: List transactions with filters
|
|
17
30
|
*
|
|
18
31
|
* Environment:
|
|
@@ -21,6 +34,8 @@
|
|
|
21
34
|
|
|
22
35
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
23
36
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
37
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
38
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
24
39
|
import {
|
|
25
40
|
CallToolRequestSchema,
|
|
26
41
|
ListToolsRequestSchema,
|
|
@@ -46,15 +61,34 @@ async function circleRequest(method: string, path: string, body?: unknown): Prom
|
|
|
46
61
|
}
|
|
47
62
|
|
|
48
63
|
const server = new Server(
|
|
49
|
-
{ name: "mcp-circle", version: "0.
|
|
64
|
+
{ name: "mcp-circle", version: "0.2.0" },
|
|
50
65
|
{ capabilities: { tools: {} } }
|
|
51
66
|
);
|
|
52
67
|
|
|
68
|
+
const amountSchema = {
|
|
69
|
+
type: "object",
|
|
70
|
+
properties: {
|
|
71
|
+
amount: { type: "string", description: "Amount (e.g. '10.00')" },
|
|
72
|
+
currency: { type: "string", description: "Currency (USD)" },
|
|
73
|
+
},
|
|
74
|
+
required: ["amount", "currency"],
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const sourceDestSchema = (label: string) => ({
|
|
78
|
+
type: "object",
|
|
79
|
+
properties: {
|
|
80
|
+
id: { type: "string", description: `${label} ID` },
|
|
81
|
+
type: { type: "string", description: `${label} type (e.g. wallet, card, ach, wire, blockchain)` },
|
|
82
|
+
},
|
|
83
|
+
required: ["id", "type"],
|
|
84
|
+
});
|
|
85
|
+
|
|
53
86
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
54
87
|
tools: [
|
|
88
|
+
// Wallets
|
|
55
89
|
{
|
|
56
90
|
name: "create_wallet",
|
|
57
|
-
description: "Create a new Circle wallet",
|
|
91
|
+
description: "Create a new Circle business-account wallet",
|
|
58
92
|
inputSchema: {
|
|
59
93
|
type: "object",
|
|
60
94
|
properties: {
|
|
@@ -67,14 +101,26 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
67
101
|
{
|
|
68
102
|
name: "get_wallet",
|
|
69
103
|
description: "Get wallet details by ID",
|
|
104
|
+
inputSchema: {
|
|
105
|
+
type: "object",
|
|
106
|
+
properties: { id: { type: "string", description: "Wallet ID" } },
|
|
107
|
+
required: ["id"],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "list_wallets",
|
|
112
|
+
description: "List all Circle wallets",
|
|
70
113
|
inputSchema: {
|
|
71
114
|
type: "object",
|
|
72
115
|
properties: {
|
|
73
|
-
|
|
116
|
+
pageSize: { type: "number", description: "Number of results per page" },
|
|
117
|
+
pageBefore: { type: "string", description: "Cursor for previous page" },
|
|
118
|
+
pageAfter: { type: "string", description: "Cursor for next page" },
|
|
74
119
|
},
|
|
75
|
-
required: ["id"],
|
|
76
120
|
},
|
|
77
121
|
},
|
|
122
|
+
|
|
123
|
+
// Payments
|
|
78
124
|
{
|
|
79
125
|
name: "create_payment",
|
|
80
126
|
description: "Accept a USDC payment via Circle",
|
|
@@ -82,9 +128,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
82
128
|
type: "object",
|
|
83
129
|
properties: {
|
|
84
130
|
idempotencyKey: { type: "string", description: "Unique idempotency key (UUID)" },
|
|
85
|
-
amount: {
|
|
86
|
-
source: {
|
|
131
|
+
amount: { ...amountSchema, description: "Payment amount" },
|
|
132
|
+
source: { ...sourceDestSchema("Source"), description: "Payment source" },
|
|
87
133
|
description: { type: "string", description: "Payment description" },
|
|
134
|
+
verification: { type: "string", description: "Verification method (cvv, three_d_secure, none)" },
|
|
135
|
+
metadata: { type: "object", description: "Payment metadata (email, phoneNumber, sessionId, ipAddress)" },
|
|
88
136
|
},
|
|
89
137
|
required: ["idempotencyKey", "amount", "source"],
|
|
90
138
|
},
|
|
@@ -94,12 +142,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
94
142
|
description: "Get payment details by ID",
|
|
95
143
|
inputSchema: {
|
|
96
144
|
type: "object",
|
|
97
|
-
properties: {
|
|
98
|
-
id: { type: "string", description: "Payment ID" },
|
|
99
|
-
},
|
|
145
|
+
properties: { id: { type: "string", description: "Payment ID" } },
|
|
100
146
|
required: ["id"],
|
|
101
147
|
},
|
|
102
148
|
},
|
|
149
|
+
|
|
150
|
+
// Payouts
|
|
103
151
|
{
|
|
104
152
|
name: "create_payout",
|
|
105
153
|
description: "Create a payout from Circle (USDC to fiat)",
|
|
@@ -107,8 +155,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
107
155
|
type: "object",
|
|
108
156
|
properties: {
|
|
109
157
|
idempotencyKey: { type: "string", description: "Unique idempotency key (UUID)" },
|
|
110
|
-
amount: {
|
|
111
|
-
destination: {
|
|
158
|
+
amount: { ...amountSchema, description: "Payout amount" },
|
|
159
|
+
destination: { ...sourceDestSchema("Destination"), description: "Payout destination (bank account)" },
|
|
160
|
+
metadata: { type: "object", description: "Payout metadata (beneficiaryEmail)" },
|
|
112
161
|
},
|
|
113
162
|
required: ["idempotencyKey", "amount", "destination"],
|
|
114
163
|
},
|
|
@@ -116,24 +165,40 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
116
165
|
{
|
|
117
166
|
name: "get_payout",
|
|
118
167
|
description: "Get payout details by ID",
|
|
168
|
+
inputSchema: {
|
|
169
|
+
type: "object",
|
|
170
|
+
properties: { id: { type: "string", description: "Payout ID" } },
|
|
171
|
+
required: ["id"],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: "list_payouts",
|
|
176
|
+
description: "List payouts with optional filters",
|
|
119
177
|
inputSchema: {
|
|
120
178
|
type: "object",
|
|
121
179
|
properties: {
|
|
122
|
-
|
|
180
|
+
source: { type: "string", description: "Filter by source wallet ID" },
|
|
181
|
+
destination: { type: "string", description: "Filter by destination ID" },
|
|
182
|
+
type: { type: "string", description: "Filter by type (wire, ach, sen)" },
|
|
183
|
+
status: { type: "string", description: "Filter by status (pending, complete, failed)" },
|
|
184
|
+
from: { type: "string", description: "Start date (ISO 8601)" },
|
|
185
|
+
to: { type: "string", description: "End date (ISO 8601)" },
|
|
186
|
+
pageSize: { type: "number", description: "Results per page" },
|
|
123
187
|
},
|
|
124
|
-
required: ["id"],
|
|
125
188
|
},
|
|
126
189
|
},
|
|
190
|
+
|
|
191
|
+
// Transfers
|
|
127
192
|
{
|
|
128
193
|
name: "create_transfer",
|
|
129
|
-
description: "Create a USDC transfer between Circle wallets",
|
|
194
|
+
description: "Create a USDC transfer between Circle wallets (or to blockchain address)",
|
|
130
195
|
inputSchema: {
|
|
131
196
|
type: "object",
|
|
132
197
|
properties: {
|
|
133
198
|
idempotencyKey: { type: "string", description: "Unique idempotency key (UUID)" },
|
|
134
|
-
amount: {
|
|
135
|
-
source: {
|
|
136
|
-
destination: {
|
|
199
|
+
amount: { ...amountSchema, description: "Transfer amount" },
|
|
200
|
+
source: { ...sourceDestSchema("Source"), description: "Transfer source (wallet)" },
|
|
201
|
+
destination: { ...sourceDestSchema("Destination"), description: "Transfer destination (wallet or blockchain)" },
|
|
137
202
|
},
|
|
138
203
|
required: ["idempotencyKey", "amount", "source", "destination"],
|
|
139
204
|
},
|
|
@@ -141,17 +206,148 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
141
206
|
{
|
|
142
207
|
name: "get_transfer",
|
|
143
208
|
description: "Get transfer details by ID",
|
|
209
|
+
inputSchema: {
|
|
210
|
+
type: "object",
|
|
211
|
+
properties: { id: { type: "string", description: "Transfer ID" } },
|
|
212
|
+
required: ["id"],
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: "list_transfers",
|
|
217
|
+
description: "List transfers with optional filters",
|
|
218
|
+
inputSchema: {
|
|
219
|
+
type: "object",
|
|
220
|
+
properties: {
|
|
221
|
+
walletId: { type: "string", description: "Filter by wallet ID" },
|
|
222
|
+
sourceWalletId: { type: "string", description: "Filter by source wallet ID" },
|
|
223
|
+
destinationWalletId: { type: "string", description: "Filter by destination wallet ID" },
|
|
224
|
+
from: { type: "string", description: "Start date (ISO 8601)" },
|
|
225
|
+
to: { type: "string", description: "End date (ISO 8601)" },
|
|
226
|
+
pageSize: { type: "number", description: "Results per page" },
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
// Cards
|
|
232
|
+
{
|
|
233
|
+
name: "create_card",
|
|
234
|
+
description: "Register card data for on-ramp payments",
|
|
235
|
+
inputSchema: {
|
|
236
|
+
type: "object",
|
|
237
|
+
properties: {
|
|
238
|
+
idempotencyKey: { type: "string", description: "Unique idempotency key (UUID)" },
|
|
239
|
+
keyId: { type: "string", description: "Public key ID used to encrypt card data" },
|
|
240
|
+
encryptedData: { type: "string", description: "PGP-encrypted card number + CVV" },
|
|
241
|
+
billingDetails: { type: "object", description: "Billing address details (name, city, country, line1, postalCode, district)" },
|
|
242
|
+
expMonth: { type: "number", description: "Card expiration month (1-12)" },
|
|
243
|
+
expYear: { type: "number", description: "Card expiration year (4-digit)" },
|
|
244
|
+
metadata: { type: "object", description: "Card metadata (email, phoneNumber, sessionId, ipAddress)" },
|
|
245
|
+
},
|
|
246
|
+
required: ["idempotencyKey", "keyId", "encryptedData", "billingDetails", "expMonth", "expYear"],
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: "get_card",
|
|
251
|
+
description: "Get card details by ID",
|
|
252
|
+
inputSchema: {
|
|
253
|
+
type: "object",
|
|
254
|
+
properties: { id: { type: "string", description: "Card ID" } },
|
|
255
|
+
required: ["id"],
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: "list_cards",
|
|
260
|
+
description: "List registered cards",
|
|
261
|
+
inputSchema: {
|
|
262
|
+
type: "object",
|
|
263
|
+
properties: {
|
|
264
|
+
pageSize: { type: "number", description: "Results per page" },
|
|
265
|
+
pageBefore: { type: "string", description: "Cursor for previous page" },
|
|
266
|
+
pageAfter: { type: "string", description: "Cursor for next page" },
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
// Settlements
|
|
272
|
+
{
|
|
273
|
+
name: "list_settlements",
|
|
274
|
+
description: "List settlements (card payment batches)",
|
|
275
|
+
inputSchema: {
|
|
276
|
+
type: "object",
|
|
277
|
+
properties: {
|
|
278
|
+
from: { type: "string", description: "Start date (ISO 8601)" },
|
|
279
|
+
to: { type: "string", description: "End date (ISO 8601)" },
|
|
280
|
+
pageSize: { type: "number", description: "Results per page" },
|
|
281
|
+
pageBefore: { type: "string", description: "Cursor for previous page" },
|
|
282
|
+
pageAfter: { type: "string", description: "Cursor for next page" },
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
name: "get_settlement",
|
|
288
|
+
description: "Get settlement details by ID",
|
|
289
|
+
inputSchema: {
|
|
290
|
+
type: "object",
|
|
291
|
+
properties: { id: { type: "string", description: "Settlement ID" } },
|
|
292
|
+
required: ["id"],
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
// Chargebacks
|
|
297
|
+
{
|
|
298
|
+
name: "list_chargebacks",
|
|
299
|
+
description: "List chargebacks",
|
|
144
300
|
inputSchema: {
|
|
145
301
|
type: "object",
|
|
146
302
|
properties: {
|
|
147
|
-
|
|
303
|
+
paymentId: { type: "string", description: "Filter by payment ID" },
|
|
304
|
+
from: { type: "string", description: "Start date (ISO 8601)" },
|
|
305
|
+
to: { type: "string", description: "End date (ISO 8601)" },
|
|
306
|
+
pageSize: { type: "number", description: "Results per page" },
|
|
148
307
|
},
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: "get_chargeback",
|
|
312
|
+
description: "Get chargeback details by ID",
|
|
313
|
+
inputSchema: {
|
|
314
|
+
type: "object",
|
|
315
|
+
properties: { id: { type: "string", description: "Chargeback ID" } },
|
|
149
316
|
required: ["id"],
|
|
150
317
|
},
|
|
151
318
|
},
|
|
319
|
+
|
|
320
|
+
// Notification subscriptions (webhooks)
|
|
321
|
+
{
|
|
322
|
+
name: "create_subscription",
|
|
323
|
+
description: "Register a notification subscription (webhook)",
|
|
324
|
+
inputSchema: {
|
|
325
|
+
type: "object",
|
|
326
|
+
properties: {
|
|
327
|
+
endpoint: { type: "string", description: "HTTPS webhook endpoint URL" },
|
|
328
|
+
},
|
|
329
|
+
required: ["endpoint"],
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
name: "list_subscriptions",
|
|
334
|
+
description: "List notification subscriptions (webhooks)",
|
|
335
|
+
inputSchema: { type: "object", properties: {} },
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
name: "delete_subscription",
|
|
339
|
+
description: "Delete a notification subscription",
|
|
340
|
+
inputSchema: {
|
|
341
|
+
type: "object",
|
|
342
|
+
properties: { id: { type: "string", description: "Subscription ID" } },
|
|
343
|
+
required: ["id"],
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
|
|
347
|
+
// Balance + transactions
|
|
152
348
|
{
|
|
153
349
|
name: "get_balance",
|
|
154
|
-
description: "Get account balance",
|
|
350
|
+
description: "Get business-account balance",
|
|
155
351
|
inputSchema: { type: "object", properties: {} },
|
|
156
352
|
},
|
|
157
353
|
{
|
|
@@ -173,40 +369,89 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
173
369
|
],
|
|
174
370
|
}));
|
|
175
371
|
|
|
372
|
+
function buildQuery(args: Record<string, unknown> | undefined, keys: string[]): string {
|
|
373
|
+
const params = new URLSearchParams();
|
|
374
|
+
if (args) {
|
|
375
|
+
for (const k of keys) {
|
|
376
|
+
const v = args[k];
|
|
377
|
+
if (v !== undefined && v !== null && v !== "") params.set(k, String(v));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const q = params.toString();
|
|
381
|
+
return q ? `?${q}` : "";
|
|
382
|
+
}
|
|
383
|
+
|
|
176
384
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
177
385
|
const { name, arguments: args } = request.params;
|
|
178
386
|
|
|
179
387
|
try {
|
|
180
388
|
switch (name) {
|
|
389
|
+
// Wallets
|
|
181
390
|
case "create_wallet":
|
|
182
|
-
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("POST", "/wallets", args), null, 2) }] };
|
|
391
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("POST", "/businessAccount/wallets", args), null, 2) }] };
|
|
183
392
|
case "get_wallet":
|
|
184
393
|
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/wallets/${args?.id}`), null, 2) }] };
|
|
394
|
+
case "list_wallets":
|
|
395
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/wallets${buildQuery(args as any, ["pageSize", "pageBefore", "pageAfter"])}`), null, 2) }] };
|
|
396
|
+
|
|
397
|
+
// Payments
|
|
185
398
|
case "create_payment":
|
|
186
399
|
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("POST", "/payments", args), null, 2) }] };
|
|
187
400
|
case "get_payment":
|
|
188
401
|
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/payments/${args?.id}`), null, 2) }] };
|
|
402
|
+
|
|
403
|
+
// Payouts
|
|
189
404
|
case "create_payout":
|
|
190
405
|
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("POST", "/payouts", args), null, 2) }] };
|
|
191
406
|
case "get_payout":
|
|
192
407
|
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/payouts/${args?.id}`), null, 2) }] };
|
|
408
|
+
case "list_payouts":
|
|
409
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/payouts${buildQuery(args as any, ["source", "destination", "type", "status", "from", "to", "pageSize"])}`), null, 2) }] };
|
|
410
|
+
|
|
411
|
+
// Transfers
|
|
193
412
|
case "create_transfer":
|
|
194
413
|
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("POST", "/transfers", args), null, 2) }] };
|
|
195
414
|
case "get_transfer":
|
|
196
415
|
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/transfers/${args?.id}`), null, 2) }] };
|
|
416
|
+
case "list_transfers":
|
|
417
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/transfers${buildQuery(args as any, ["walletId", "sourceWalletId", "destinationWalletId", "from", "to", "pageSize"])}`), null, 2) }] };
|
|
418
|
+
|
|
419
|
+
// Cards
|
|
420
|
+
case "create_card":
|
|
421
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("POST", "/cards", args), null, 2) }] };
|
|
422
|
+
case "get_card":
|
|
423
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/cards/${args?.id}`), null, 2) }] };
|
|
424
|
+
case "list_cards":
|
|
425
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/cards${buildQuery(args as any, ["pageSize", "pageBefore", "pageAfter"])}`), null, 2) }] };
|
|
426
|
+
|
|
427
|
+
// Settlements
|
|
428
|
+
case "list_settlements":
|
|
429
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/settlements${buildQuery(args as any, ["from", "to", "pageSize", "pageBefore", "pageAfter"])}`), null, 2) }] };
|
|
430
|
+
case "get_settlement":
|
|
431
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/settlements/${args?.id}`), null, 2) }] };
|
|
432
|
+
|
|
433
|
+
// Chargebacks
|
|
434
|
+
case "list_chargebacks":
|
|
435
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/chargebacks${buildQuery(args as any, ["paymentId", "from", "to", "pageSize"])}`), null, 2) }] };
|
|
436
|
+
case "get_chargeback":
|
|
437
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/chargebacks/${args?.id}`), null, 2) }] };
|
|
438
|
+
|
|
439
|
+
// Notification subscriptions
|
|
440
|
+
case "create_subscription":
|
|
441
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("POST", "/notifications/subscriptions", args), null, 2) }] };
|
|
442
|
+
case "list_subscriptions":
|
|
443
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", "/notifications/subscriptions"), null, 2) }] };
|
|
444
|
+
case "delete_subscription":
|
|
445
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("DELETE", `/notifications/subscriptions/${args?.id}`), null, 2) }] };
|
|
446
|
+
|
|
447
|
+
// Balance
|
|
197
448
|
case "get_balance":
|
|
198
|
-
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", "/balances"), null, 2) }] };
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (args?.to) params.set("to", String(args.to));
|
|
205
|
-
if (args?.pageSize) params.set("pageSize", String(args.pageSize));
|
|
206
|
-
if (args?.pageBefore) params.set("pageBefore", String(args.pageBefore));
|
|
207
|
-
if (args?.pageAfter) params.set("pageAfter", String(args.pageAfter));
|
|
208
|
-
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/transactions?${params}`), null, 2) }] };
|
|
209
|
-
}
|
|
449
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", "/businessAccount/balances"), null, 2) }] };
|
|
450
|
+
|
|
451
|
+
// Transactions
|
|
452
|
+
case "list_transactions":
|
|
453
|
+
return { content: [{ type: "text", text: JSON.stringify(await circleRequest("GET", `/transactions${buildQuery(args as any, ["type", "status", "from", "to", "pageSize", "pageBefore", "pageAfter"])}`), null, 2) }] };
|
|
454
|
+
|
|
210
455
|
default:
|
|
211
456
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
212
457
|
}
|
|
@@ -216,12 +461,32 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
216
461
|
});
|
|
217
462
|
|
|
218
463
|
async function main() {
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
|
|
464
|
+
if (process.argv.includes("--http") || process.env.MCP_HTTP === "true") {
|
|
465
|
+
const { default: express } = await import("express");
|
|
466
|
+
const { randomUUID } = await import("node:crypto");
|
|
467
|
+
const app = express();
|
|
468
|
+
app.use(express.json());
|
|
469
|
+
const transports = new Map<string, StreamableHTTPServerTransport>();
|
|
470
|
+
app.get("/health", (_req: any, res: any) => res.json({ status: "ok", sessions: transports.size }));
|
|
471
|
+
app.post("/mcp", async (req: any, res: any) => {
|
|
472
|
+
const sid = req.headers["mcp-session-id"] as string | undefined;
|
|
473
|
+
if (sid && transports.has(sid)) { await transports.get(sid)!.handleRequest(req, res, req.body); return; }
|
|
474
|
+
if (!sid && isInitializeRequest(req.body)) {
|
|
475
|
+
const t = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (id) => { transports.set(id, t); } });
|
|
476
|
+
t.onclose = () => { if (t.sessionId) transports.delete(t.sessionId); };
|
|
477
|
+
const s = new Server({ name: "mcp-circle", version: "0.2.0" }, { capabilities: { tools: {} } }); (server as any)._requestHandlers.forEach((v: any, k: any) => (s as any)._requestHandlers.set(k, v)); (server as any)._notificationHandlers?.forEach((v: any, k: any) => (s as any)._notificationHandlers.set(k, v)); await s.connect(t);
|
|
478
|
+
await t.handleRequest(req, res, req.body); return;
|
|
479
|
+
}
|
|
480
|
+
res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request" }, id: null });
|
|
481
|
+
});
|
|
482
|
+
app.get("/mcp", async (req: any, res: any) => { const sid = req.headers["mcp-session-id"] as string; if (sid && transports.has(sid)) await transports.get(sid)!.handleRequest(req, res); else res.status(400).send("Invalid session"); });
|
|
483
|
+
app.delete("/mcp", async (req: any, res: any) => { const sid = req.headers["mcp-session-id"] as string; if (sid && transports.has(sid)) await transports.get(sid)!.handleRequest(req, res); else res.status(400).send("Invalid session"); });
|
|
484
|
+
const port = Number(process.env.MCP_PORT) || 3000;
|
|
485
|
+
app.listen(port, () => { console.error(`MCP HTTP server on http://localhost:${port}/mcp`); });
|
|
486
|
+
} else {
|
|
487
|
+
const transport = new StdioServerTransport();
|
|
488
|
+
await server.connect(transport);
|
|
222
489
|
}
|
|
223
|
-
const transport = new StdioServerTransport();
|
|
224
|
-
await server.connect(transport);
|
|
225
490
|
}
|
|
226
491
|
|
|
227
492
|
main().catch(console.error);
|