@codespar/mcp-nequi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # MCP Nequi
2
+
3
+ MCP server for **Nequi** — Colombia's leading digital wallet with 50M+ users, powered by Bancolombia. Supports push payments, QR payments, and subscriptions.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Set your credentials
9
+ export NEQUI_API_KEY="your-api-key"
10
+ export NEQUI_CLIENT_ID="your-client-id"
11
+ export NEQUI_CLIENT_SECRET="your-client-secret"
12
+
13
+ # Run via stdio
14
+ npx tsx packages/colombia/nequi/src/index.ts
15
+
16
+ # Run via HTTP
17
+ npx tsx packages/colombia/nequi/src/index.ts --http
18
+ ```
19
+
20
+ ## Environment Variables
21
+
22
+ | Variable | Required | Description |
23
+ |----------|----------|-------------|
24
+ | `NEQUI_API_KEY` | Yes | API key from Nequi developer portal |
25
+ | `NEQUI_CLIENT_ID` | Yes | OAuth2 client ID |
26
+ | `NEQUI_CLIENT_SECRET` | Yes | OAuth2 client secret |
27
+ | `NEQUI_ENV` | No | `"sandbox"` or `"production"` (default: sandbox) |
28
+ | `MCP_HTTP` | No | Set to `"true"` to enable HTTP transport |
29
+ | `MCP_PORT` | No | HTTP port (default: 3000) |
30
+
31
+ ## Tools
32
+
33
+ | Tool | Description |
34
+ |------|-------------|
35
+ | `create_push_payment` | Send a push payment notification to a Nequi user |
36
+ | `get_payment_status` | Check payment status |
37
+ | `create_qr_payment` | Generate a QR code for payment |
38
+ | `reverse_payment` | Reverse a completed payment |
39
+ | `get_subscription` | Get subscription details |
40
+ | `unsubscribe` | Cancel a subscription |
41
+
42
+ ## Auth
43
+
44
+ Uses **OAuth2 client credentials** flow. The server obtains an access token using client ID and secret, and includes the API key in every request header.
45
+
46
+ ## API Reference
47
+
48
+ - [Nequi API Docs](https://docs.conecta.nequi.com.co/)
49
+
50
+ ---
51
+
52
+ **Enterprise?** Contact us at [codespar.com](https://codespar.com) for dedicated support, custom integrations, and SLAs.
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Server for Nequi — Colombian digital wallet (50M+ users, by Bancolombia).
4
+ *
5
+ * Tools:
6
+ * - create_push_payment: Send a push payment notification to a Nequi user
7
+ * - get_payment_status: Check payment status
8
+ * - create_qr_payment: Generate a QR code payment
9
+ * - reverse_payment: Reverse a payment
10
+ * - get_subscription: Get subscription details
11
+ * - unsubscribe: Cancel a subscription
12
+ *
13
+ * Environment:
14
+ * NEQUI_API_KEY — API key
15
+ * NEQUI_CLIENT_ID — OAuth2 client ID
16
+ * NEQUI_CLIENT_SECRET — OAuth2 client secret
17
+ * NEQUI_ENV — "sandbox" or "production" (default: sandbox)
18
+ */
19
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,308 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Server for Nequi — Colombian digital wallet (50M+ users, by Bancolombia).
4
+ *
5
+ * Tools:
6
+ * - create_push_payment: Send a push payment notification to a Nequi user
7
+ * - get_payment_status: Check payment status
8
+ * - create_qr_payment: Generate a QR code payment
9
+ * - reverse_payment: Reverse a payment
10
+ * - get_subscription: Get subscription details
11
+ * - unsubscribe: Cancel a subscription
12
+ *
13
+ * Environment:
14
+ * NEQUI_API_KEY — API key
15
+ * NEQUI_CLIENT_ID — OAuth2 client ID
16
+ * NEQUI_CLIENT_SECRET — OAuth2 client secret
17
+ * NEQUI_ENV — "sandbox" or "production" (default: sandbox)
18
+ */
19
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
20
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
21
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
22
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
23
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
24
+ const API_KEY = process.env.NEQUI_API_KEY || "";
25
+ const CLIENT_ID = process.env.NEQUI_CLIENT_ID || "";
26
+ const CLIENT_SECRET = process.env.NEQUI_CLIENT_SECRET || "";
27
+ const NEQUI_ENV = process.env.NEQUI_ENV || "sandbox";
28
+ const BASE_URL = NEQUI_ENV === "production"
29
+ ? "https://api.nequi.com"
30
+ : "https://api.sandbox.nequi.com";
31
+ let cachedToken = null;
32
+ let tokenExpiry = 0;
33
+ async function getOAuthToken() {
34
+ if (cachedToken && Date.now() < tokenExpiry)
35
+ return cachedToken;
36
+ const res = await fetch(`${BASE_URL}/oauth2/token`, {
37
+ method: "POST",
38
+ headers: {
39
+ "Content-Type": "application/x-www-form-urlencoded",
40
+ "Authorization": `Basic ${Buffer.from(CLIENT_ID + ":" + CLIENT_SECRET).toString("base64")}`,
41
+ },
42
+ body: "grant_type=client_credentials",
43
+ });
44
+ if (!res.ok)
45
+ throw new Error(`Nequi OAuth failed: ${res.status}`);
46
+ const data = await res.json();
47
+ cachedToken = data.access_token;
48
+ tokenExpiry = Date.now() + (data.expires_in - 60) * 1000;
49
+ return cachedToken;
50
+ }
51
+ async function nequiRequest(method, path, body) {
52
+ const token = await getOAuthToken();
53
+ const headers = {
54
+ "Content-Type": "application/json",
55
+ "Accept": "application/json",
56
+ "Authorization": `Bearer ${token}`,
57
+ "x-api-key": API_KEY,
58
+ };
59
+ const res = await fetch(`${BASE_URL}${path}`, {
60
+ method,
61
+ headers,
62
+ body: body ? JSON.stringify(body) : undefined,
63
+ });
64
+ if (!res.ok) {
65
+ const err = await res.text();
66
+ throw new Error(`Nequi API ${res.status}: ${err}`);
67
+ }
68
+ return res.json();
69
+ }
70
+ const server = new Server({ name: "mcp-nequi", version: "0.1.0" }, { capabilities: { tools: {} } });
71
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
72
+ tools: [
73
+ {
74
+ name: "create_push_payment",
75
+ description: "Send a push payment notification to a Nequi user",
76
+ inputSchema: {
77
+ type: "object",
78
+ properties: {
79
+ phone_number: { type: "string", description: "Nequi phone number (10 digits)" },
80
+ code: { type: "string", description: "Merchant payment code" },
81
+ value: { type: "string", description: "Payment amount in COP" },
82
+ merchant_id: { type: "string", description: "Merchant ID" },
83
+ message: { type: "string", description: "Payment message/description" },
84
+ },
85
+ required: ["phone_number", "code", "value"],
86
+ },
87
+ },
88
+ {
89
+ name: "get_payment_status",
90
+ description: "Check the status of a payment",
91
+ inputSchema: {
92
+ type: "object",
93
+ properties: {
94
+ code: { type: "string", description: "Payment code" },
95
+ merchant_id: { type: "string", description: "Merchant ID" },
96
+ },
97
+ required: ["code"],
98
+ },
99
+ },
100
+ {
101
+ name: "create_qr_payment",
102
+ description: "Generate a QR code for payment",
103
+ inputSchema: {
104
+ type: "object",
105
+ properties: {
106
+ code: { type: "string", description: "Payment code" },
107
+ value: { type: "string", description: "Payment amount in COP" },
108
+ merchant_id: { type: "string", description: "Merchant ID" },
109
+ message: { type: "string", description: "Payment description" },
110
+ },
111
+ required: ["code", "value"],
112
+ },
113
+ },
114
+ {
115
+ name: "reverse_payment",
116
+ description: "Reverse a completed payment",
117
+ inputSchema: {
118
+ type: "object",
119
+ properties: {
120
+ phone_number: { type: "string", description: "Nequi phone number" },
121
+ code: { type: "string", description: "Original payment code" },
122
+ value: { type: "string", description: "Amount to reverse" },
123
+ merchant_id: { type: "string", description: "Merchant ID" },
124
+ transaction_id: { type: "string", description: "Original transaction ID" },
125
+ },
126
+ required: ["phone_number", "code", "value", "transaction_id"],
127
+ },
128
+ },
129
+ {
130
+ name: "get_subscription",
131
+ description: "Get subscription details for a phone number",
132
+ inputSchema: {
133
+ type: "object",
134
+ properties: {
135
+ phone_number: { type: "string", description: "Nequi phone number" },
136
+ code: { type: "string", description: "Subscription code" },
137
+ merchant_id: { type: "string", description: "Merchant ID" },
138
+ },
139
+ required: ["phone_number", "code"],
140
+ },
141
+ },
142
+ {
143
+ name: "unsubscribe",
144
+ description: "Cancel a subscription",
145
+ inputSchema: {
146
+ type: "object",
147
+ properties: {
148
+ phone_number: { type: "string", description: "Nequi phone number" },
149
+ code: { type: "string", description: "Subscription code" },
150
+ merchant_id: { type: "string", description: "Merchant ID" },
151
+ token: { type: "string", description: "Subscription token" },
152
+ },
153
+ required: ["phone_number", "code", "token"],
154
+ },
155
+ },
156
+ ],
157
+ }));
158
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
159
+ const { name, arguments: args } = request.params;
160
+ try {
161
+ switch (name) {
162
+ case "create_push_payment":
163
+ return { content: [{ type: "text", text: JSON.stringify(await nequiRequest("POST", "/payments/v2/-services-paymentservice-unregisteredpayment", {
164
+ RequestMessage: {
165
+ RequestHeader: { Channel: "PNP04-C001", RequestDate: new Date().toISOString(), MessageID: `MCP-${Date.now()}`, ClientID: CLIENT_ID },
166
+ RequestBody: {
167
+ any: {
168
+ unregisteredPaymentRQ: {
169
+ phoneNumber: args?.phone_number,
170
+ code: args?.code,
171
+ value: args?.value,
172
+ merchantId: args?.merchant_id,
173
+ message: args?.message,
174
+ },
175
+ },
176
+ },
177
+ },
178
+ }), null, 2) }] };
179
+ case "get_payment_status":
180
+ return { content: [{ type: "text", text: JSON.stringify(await nequiRequest("POST", "/payments/v2/-services-paymentservice-getstatuspayment", {
181
+ RequestMessage: {
182
+ RequestHeader: { Channel: "PNP04-C001", RequestDate: new Date().toISOString(), MessageID: `MCP-${Date.now()}`, ClientID: CLIENT_ID },
183
+ RequestBody: {
184
+ any: {
185
+ getStatusPaymentRQ: {
186
+ codeQR: args?.code,
187
+ merchantId: args?.merchant_id,
188
+ },
189
+ },
190
+ },
191
+ },
192
+ }), null, 2) }] };
193
+ case "create_qr_payment":
194
+ return { content: [{ type: "text", text: JSON.stringify(await nequiRequest("POST", "/payments/v2/-services-paymentservice-generatecodeqr", {
195
+ RequestMessage: {
196
+ RequestHeader: { Channel: "PNP04-C001", RequestDate: new Date().toISOString(), MessageID: `MCP-${Date.now()}`, ClientID: CLIENT_ID },
197
+ RequestBody: {
198
+ any: {
199
+ generateCodeQRRQ: {
200
+ code: args?.code,
201
+ value: args?.value,
202
+ merchantId: args?.merchant_id,
203
+ message: args?.message,
204
+ },
205
+ },
206
+ },
207
+ },
208
+ }), null, 2) }] };
209
+ case "reverse_payment":
210
+ return { content: [{ type: "text", text: JSON.stringify(await nequiRequest("POST", "/payments/v2/-services-reverseservices-reversetransaction", {
211
+ RequestMessage: {
212
+ RequestHeader: { Channel: "PNP04-C001", RequestDate: new Date().toISOString(), MessageID: `MCP-${Date.now()}`, ClientID: CLIENT_ID },
213
+ RequestBody: {
214
+ any: {
215
+ reverseTransactionRQ: {
216
+ phoneNumber: args?.phone_number,
217
+ code: args?.code,
218
+ value: args?.value,
219
+ merchantId: args?.merchant_id,
220
+ transactionId: args?.transaction_id,
221
+ },
222
+ },
223
+ },
224
+ },
225
+ }), null, 2) }] };
226
+ case "get_subscription":
227
+ return { content: [{ type: "text", text: JSON.stringify(await nequiRequest("POST", "/payments/v2/-services-subscriptionpaymentservice-getsubscription", {
228
+ RequestMessage: {
229
+ RequestHeader: { Channel: "PNP04-C001", RequestDate: new Date().toISOString(), MessageID: `MCP-${Date.now()}`, ClientID: CLIENT_ID },
230
+ RequestBody: {
231
+ any: {
232
+ getSubscriptionRQ: {
233
+ phoneNumber: args?.phone_number,
234
+ code: args?.code,
235
+ merchantId: args?.merchant_id,
236
+ },
237
+ },
238
+ },
239
+ },
240
+ }), null, 2) }] };
241
+ case "unsubscribe":
242
+ return { content: [{ type: "text", text: JSON.stringify(await nequiRequest("POST", "/payments/v2/-services-subscriptionpaymentservice-deletesubscription", {
243
+ RequestMessage: {
244
+ RequestHeader: { Channel: "PNP04-C001", RequestDate: new Date().toISOString(), MessageID: `MCP-${Date.now()}`, ClientID: CLIENT_ID },
245
+ RequestBody: {
246
+ any: {
247
+ deleteSubscriptionRQ: {
248
+ phoneNumber: args?.phone_number,
249
+ code: args?.code,
250
+ merchantId: args?.merchant_id,
251
+ token: args?.token,
252
+ },
253
+ },
254
+ },
255
+ },
256
+ }), null, 2) }] };
257
+ default:
258
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
259
+ }
260
+ }
261
+ catch (err) {
262
+ return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
263
+ }
264
+ });
265
+ async function main() {
266
+ if (process.argv.includes("--http") || process.env.MCP_HTTP === "true") {
267
+ const { default: express } = await import("express");
268
+ const { randomUUID } = await import("node:crypto");
269
+ const app = express();
270
+ app.use(express.json());
271
+ const transports = new Map();
272
+ app.get("/health", (_req, res) => res.json({ status: "ok", sessions: transports.size }));
273
+ app.post("/mcp", async (req, res) => {
274
+ const sid = req.headers["mcp-session-id"];
275
+ if (sid && transports.has(sid)) {
276
+ await transports.get(sid).handleRequest(req, res, req.body);
277
+ return;
278
+ }
279
+ if (!sid && isInitializeRequest(req.body)) {
280
+ const t = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (id) => { transports.set(id, t); } });
281
+ t.onclose = () => { if (t.sessionId)
282
+ transports.delete(t.sessionId); };
283
+ const s = new Server({ name: "mcp-nequi", version: "0.1.0" }, { capabilities: { tools: {} } });
284
+ server._requestHandlers.forEach((v, k) => s._requestHandlers.set(k, v));
285
+ server._notificationHandlers?.forEach((v, k) => s._notificationHandlers.set(k, v));
286
+ await s.connect(t);
287
+ await t.handleRequest(req, res, req.body);
288
+ return;
289
+ }
290
+ res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request" }, id: null });
291
+ });
292
+ app.get("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"]; if (sid && transports.has(sid))
293
+ await transports.get(sid).handleRequest(req, res);
294
+ else
295
+ res.status(400).send("Invalid session"); });
296
+ app.delete("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"]; if (sid && transports.has(sid))
297
+ await transports.get(sid).handleRequest(req, res);
298
+ else
299
+ res.status(400).send("Invalid session"); });
300
+ const port = Number(process.env.MCP_PORT) || 3000;
301
+ app.listen(port, () => { console.error(`MCP HTTP server on http://localhost:${port}/mcp`); });
302
+ }
303
+ else {
304
+ const transport = new StdioServerTransport();
305
+ await server.connect(transport);
306
+ }
307
+ }
308
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@codespar/mcp-nequi",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Nequi — Colombian digital wallet (by Bancolombia)",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "mcp-nequi": "dist/index.js"
9
+ },
10
+ "files": ["dist"],
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "start": "node dist/index.js"
14
+ },
15
+ "dependencies": {
16
+ "@modelcontextprotocol/sdk": "^1.0.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "^22.0.0",
20
+ "typescript": "^5.8.0"
21
+ },
22
+ "license": "MIT",
23
+ "keywords": ["nequi", "wallet", "colombia", "bancolombia", "payments", "mcp"],
24
+ "mcpName": "io.github.codespar/mcp-nequi"
25
+ }