@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 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
- * - get_balance: Get wallet balance
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.1.0" }, { capabilities: { tools: {} } });
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
- id: { type: "string", description: "Wallet ID" },
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: { type: "object", properties: { amount: { type: "string", description: "Amount (e.g. '10.00')" }, currency: { type: "string", description: "Currency (USD)" } }, required: ["amount", "currency"], description: "Payment amount" },
74
- source: { type: "object", properties: { id: { type: "string", description: "Source ID" }, type: { type: "string", description: "Source type (e.g. card, ach)" } }, required: ["id", "type"], description: "Payment 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: { type: "object", properties: { amount: { type: "string", description: "Amount" }, currency: { type: "string", description: "Currency (USD)" } }, required: ["amount", "currency"], description: "Payout amount" },
99
- destination: { type: "object", properties: { id: { type: "string", description: "Destination ID (bank account)" }, type: { type: "string", description: "Destination type (e.g. wire)" } }, required: ["id", "type"], description: "Payout 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
- id: { type: "string", description: "Payout ID" },
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: { type: "object", properties: { amount: { type: "string", description: "Amount" }, currency: { type: "string", description: "Currency (USD)" } }, required: ["amount", "currency"], description: "Transfer amount" },
123
- source: { type: "object", properties: { id: { type: "string", description: "Source wallet ID" }, type: { type: "string", description: "Source type (wallet)" } }, required: ["id", "type"], description: "Transfer source" },
124
- destination: { type: "object", properties: { id: { type: "string", description: "Destination wallet ID" }, type: { type: "string", description: "Destination type (wallet, blockchain)" } }, required: ["id", "type"], description: "Transfer 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
- id: { type: "string", description: "Transfer ID" },
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
- case "list_transactions": {
186
- const params = new URLSearchParams();
187
- if (args?.type)
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 (!API_KEY) {
213
- console.error("CIRCLE_API_KEY environment variable is required");
214
- process.exit(1);
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.1.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
- * - get_balance: Get wallet balance
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.1.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
- id: { type: "string", description: "Wallet ID" },
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: { type: "object", properties: { amount: { type: "string", description: "Amount (e.g. '10.00')" }, currency: { type: "string", description: "Currency (USD)" } }, required: ["amount", "currency"], description: "Payment amount" },
86
- source: { type: "object", properties: { id: { type: "string", description: "Source ID" }, type: { type: "string", description: "Source type (e.g. card, ach)" } }, required: ["id", "type"], description: "Payment 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: { type: "object", properties: { amount: { type: "string", description: "Amount" }, currency: { type: "string", description: "Currency (USD)" } }, required: ["amount", "currency"], description: "Payout amount" },
111
- destination: { type: "object", properties: { id: { type: "string", description: "Destination ID (bank account)" }, type: { type: "string", description: "Destination type (e.g. wire)" } }, required: ["id", "type"], description: "Payout 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
- id: { type: "string", description: "Payout ID" },
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: { type: "object", properties: { amount: { type: "string", description: "Amount" }, currency: { type: "string", description: "Currency (USD)" } }, required: ["amount", "currency"], description: "Transfer amount" },
135
- source: { type: "object", properties: { id: { type: "string", description: "Source wallet ID" }, type: { type: "string", description: "Source type (wallet)" } }, required: ["id", "type"], description: "Transfer source" },
136
- destination: { type: "object", properties: { id: { type: "string", description: "Destination wallet ID" }, type: { type: "string", description: "Destination type (wallet, blockchain)" } }, required: ["id", "type"], description: "Transfer 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
- id: { type: "string", description: "Transfer ID" },
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
- case "list_transactions": {
200
- const params = new URLSearchParams();
201
- if (args?.type) params.set("type", String(args.type));
202
- if (args?.status) params.set("status", String(args.status));
203
- if (args?.from) params.set("from", String(args.from));
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 (!API_KEY) {
220
- console.error("CIRCLE_API_KEY environment variable is required");
221
- process.exit(1);
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);