@codespar/mcp-coordinadora 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,51 @@
1
+ # MCP Coordinadora
2
+
3
+ MCP server for **Coordinadora** — one of Colombia's largest courier and logistics companies, offering domestic and international shipping services.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Set your credentials
9
+ export COORDINADORA_API_KEY="your-api-key"
10
+ export COORDINADORA_NIT="your-nit"
11
+
12
+ # Run via stdio
13
+ npx tsx packages/colombia/coordinadora/src/index.ts
14
+
15
+ # Run via HTTP
16
+ npx tsx packages/colombia/coordinadora/src/index.ts --http
17
+ ```
18
+
19
+ ## Environment Variables
20
+
21
+ | Variable | Required | Description |
22
+ |----------|----------|-------------|
23
+ | `COORDINADORA_API_KEY` | Yes | API key from Coordinadora |
24
+ | `COORDINADORA_NIT` | Yes | Company NIT number |
25
+ | `MCP_HTTP` | No | Set to `"true"` to enable HTTP transport |
26
+ | `MCP_PORT` | No | HTTP port (default: 3000) |
27
+
28
+ ## Tools
29
+
30
+ | Tool | Description |
31
+ |------|-------------|
32
+ | `create_shipment` | Create a new shipment |
33
+ | `get_shipment` | Get shipment details by guide number |
34
+ | `track_shipment` | Track a shipment |
35
+ | `get_rates` | Get shipping rates/quotes |
36
+ | `list_cities` | List available cities for shipping |
37
+ | `create_pickup` | Schedule a pickup |
38
+ | `get_coverage` | Check coverage for a location |
39
+ | `cancel_shipment` | Cancel a shipment |
40
+
41
+ ## Auth
42
+
43
+ Uses **API key + NIT header** authentication. Both the API key and company NIT are sent as custom headers with every request.
44
+
45
+ ## API Reference
46
+
47
+ - [Coordinadora API Docs](https://www.coordinadora.com/portafolio-de-servicios/soluciones-tecnologicas/)
48
+
49
+ ---
50
+
51
+ **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 Coordinadora — major Colombian courier/logistics company.
4
+ *
5
+ * Tools:
6
+ * - create_shipment: Create a new shipment
7
+ * - get_shipment: Get shipment details
8
+ * - track_shipment: Track a shipment
9
+ * - get_rates: Get shipping rates/quotes
10
+ * - list_cities: List available cities for shipping
11
+ * - create_pickup: Schedule a pickup
12
+ * - get_coverage: Check coverage for a location
13
+ * - cancel_shipment: Cancel a shipment
14
+ *
15
+ * Environment:
16
+ * COORDINADORA_API_KEY — API key
17
+ * COORDINADORA_NIT — Company NIT number
18
+ */
19
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,309 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Server for Coordinadora — major Colombian courier/logistics company.
4
+ *
5
+ * Tools:
6
+ * - create_shipment: Create a new shipment
7
+ * - get_shipment: Get shipment details
8
+ * - track_shipment: Track a shipment
9
+ * - get_rates: Get shipping rates/quotes
10
+ * - list_cities: List available cities for shipping
11
+ * - create_pickup: Schedule a pickup
12
+ * - get_coverage: Check coverage for a location
13
+ * - cancel_shipment: Cancel a shipment
14
+ *
15
+ * Environment:
16
+ * COORDINADORA_API_KEY — API key
17
+ * COORDINADORA_NIT — Company NIT number
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.COORDINADORA_API_KEY || "";
25
+ const NIT = process.env.COORDINADORA_NIT || "";
26
+ const BASE_URL = "https://api.coordinadora.com/v1";
27
+ async function coordinadoraRequest(method, path, body) {
28
+ const headers = {
29
+ "Content-Type": "application/json",
30
+ "Accept": "application/json",
31
+ "x-api-key": API_KEY,
32
+ "x-nit": NIT,
33
+ };
34
+ const res = await fetch(`${BASE_URL}${path}`, {
35
+ method,
36
+ headers,
37
+ body: body ? JSON.stringify(body) : undefined,
38
+ });
39
+ if (!res.ok) {
40
+ const err = await res.text();
41
+ throw new Error(`Coordinadora API ${res.status}: ${err}`);
42
+ }
43
+ return res.json();
44
+ }
45
+ const server = new Server({ name: "mcp-coordinadora", version: "0.1.0" }, { capabilities: { tools: {} } });
46
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
47
+ tools: [
48
+ {
49
+ name: "create_shipment",
50
+ description: "Create a new shipment",
51
+ inputSchema: {
52
+ type: "object",
53
+ properties: {
54
+ origin_city: { type: "string", description: "Origin city DANE code" },
55
+ destination_city: { type: "string", description: "Destination city DANE code" },
56
+ sender: {
57
+ type: "object",
58
+ description: "Sender information",
59
+ properties: {
60
+ name: { type: "string", description: "Sender name" },
61
+ phone: { type: "string", description: "Sender phone" },
62
+ address: { type: "string", description: "Sender address" },
63
+ nit: { type: "string", description: "Sender NIT/CC" },
64
+ },
65
+ required: ["name", "phone", "address"],
66
+ },
67
+ recipient: {
68
+ type: "object",
69
+ description: "Recipient information",
70
+ properties: {
71
+ name: { type: "string", description: "Recipient name" },
72
+ phone: { type: "string", description: "Recipient phone" },
73
+ address: { type: "string", description: "Recipient address" },
74
+ nit: { type: "string", description: "Recipient NIT/CC" },
75
+ },
76
+ required: ["name", "phone", "address"],
77
+ },
78
+ packages: {
79
+ type: "array",
80
+ description: "Packages to ship",
81
+ items: {
82
+ type: "object",
83
+ properties: {
84
+ weight: { type: "number", description: "Weight in kg" },
85
+ height: { type: "number", description: "Height in cm" },
86
+ width: { type: "number", description: "Width in cm" },
87
+ length: { type: "number", description: "Length in cm" },
88
+ declared_value: { type: "number", description: "Declared value in COP" },
89
+ content: { type: "string", description: "Package content description" },
90
+ },
91
+ required: ["weight"],
92
+ },
93
+ },
94
+ service_type: { type: "string", description: "Service type (standard, express, same_day)" },
95
+ payment_type: { type: "string", description: "Payment type (prepaid, collect, contra_entrega)" },
96
+ },
97
+ required: ["origin_city", "destination_city", "sender", "recipient", "packages"],
98
+ },
99
+ },
100
+ {
101
+ name: "get_shipment",
102
+ description: "Get shipment details by guide number",
103
+ inputSchema: {
104
+ type: "object",
105
+ properties: { guideNumber: { type: "string", description: "Guide number (numero de guia)" } },
106
+ required: ["guideNumber"],
107
+ },
108
+ },
109
+ {
110
+ name: "track_shipment",
111
+ description: "Track a shipment by guide number",
112
+ inputSchema: {
113
+ type: "object",
114
+ properties: { guideNumber: { type: "string", description: "Guide number" } },
115
+ required: ["guideNumber"],
116
+ },
117
+ },
118
+ {
119
+ name: "get_rates",
120
+ description: "Get shipping rates/quotes",
121
+ inputSchema: {
122
+ type: "object",
123
+ properties: {
124
+ origin_city: { type: "string", description: "Origin city DANE code" },
125
+ destination_city: { type: "string", description: "Destination city DANE code" },
126
+ weight: { type: "number", description: "Weight in kg" },
127
+ height: { type: "number", description: "Height in cm" },
128
+ width: { type: "number", description: "Width in cm" },
129
+ length: { type: "number", description: "Length in cm" },
130
+ declared_value: { type: "number", description: "Declared value in COP" },
131
+ },
132
+ required: ["origin_city", "destination_city", "weight"],
133
+ },
134
+ },
135
+ {
136
+ name: "list_cities",
137
+ description: "List available cities for shipping",
138
+ inputSchema: {
139
+ type: "object",
140
+ properties: {
141
+ department: { type: "string", description: "Filter by department name" },
142
+ search: { type: "string", description: "Search by city name" },
143
+ },
144
+ },
145
+ },
146
+ {
147
+ name: "create_pickup",
148
+ description: "Schedule a pickup at an address",
149
+ inputSchema: {
150
+ type: "object",
151
+ properties: {
152
+ address: { type: "string", description: "Pickup address" },
153
+ city: { type: "string", description: "City DANE code" },
154
+ contact_name: { type: "string", description: "Contact person name" },
155
+ contact_phone: { type: "string", description: "Contact phone" },
156
+ date: { type: "string", description: "Pickup date (YYYY-MM-DD)" },
157
+ time_from: { type: "string", description: "Pickup window start (HH:MM)" },
158
+ time_to: { type: "string", description: "Pickup window end (HH:MM)" },
159
+ packages_count: { type: "number", description: "Number of packages" },
160
+ total_weight: { type: "number", description: "Total weight in kg" },
161
+ },
162
+ required: ["address", "city", "contact_name", "contact_phone", "date"],
163
+ },
164
+ },
165
+ {
166
+ name: "get_coverage",
167
+ description: "Check if a location is within coverage area",
168
+ inputSchema: {
169
+ type: "object",
170
+ properties: {
171
+ city: { type: "string", description: "City DANE code or name" },
172
+ postal_code: { type: "string", description: "Postal code" },
173
+ },
174
+ required: ["city"],
175
+ },
176
+ },
177
+ {
178
+ name: "cancel_shipment",
179
+ description: "Cancel a shipment",
180
+ inputSchema: {
181
+ type: "object",
182
+ properties: {
183
+ guideNumber: { type: "string", description: "Guide number" },
184
+ reason: { type: "string", description: "Cancellation reason" },
185
+ },
186
+ required: ["guideNumber"],
187
+ },
188
+ },
189
+ ],
190
+ }));
191
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
192
+ const { name, arguments: args } = request.params;
193
+ try {
194
+ switch (name) {
195
+ case "create_shipment":
196
+ return { content: [{ type: "text", text: JSON.stringify(await coordinadoraRequest("POST", "/guias", {
197
+ ciudadOrigen: args?.origin_city,
198
+ ciudadDestino: args?.destination_city,
199
+ remitente: args?.sender,
200
+ destinatario: args?.recipient,
201
+ bultos: args?.packages,
202
+ tipoServicio: args?.service_type,
203
+ tipoPago: args?.payment_type,
204
+ }), null, 2) }] };
205
+ case "get_shipment":
206
+ return { content: [{ type: "text", text: JSON.stringify(await coordinadoraRequest("GET", `/guias/${args?.guideNumber}`), null, 2) }] };
207
+ case "track_shipment":
208
+ return { content: [{ type: "text", text: JSON.stringify(await coordinadoraRequest("GET", `/guias/${args?.guideNumber}/tracking`), null, 2) }] };
209
+ case "get_rates": {
210
+ const payload = {
211
+ ciudadOrigen: args?.origin_city,
212
+ ciudadDestino: args?.destination_city,
213
+ peso: args?.weight,
214
+ };
215
+ if (args?.height)
216
+ payload.alto = args.height;
217
+ if (args?.width)
218
+ payload.ancho = args.width;
219
+ if (args?.length)
220
+ payload.largo = args.length;
221
+ if (args?.declared_value)
222
+ payload.valorDeclarado = args.declared_value;
223
+ return { content: [{ type: "text", text: JSON.stringify(await coordinadoraRequest("POST", "/cotizaciones", payload), null, 2) }] };
224
+ }
225
+ case "list_cities": {
226
+ const params = new URLSearchParams();
227
+ if (args?.department)
228
+ params.set("departamento", args.department);
229
+ if (args?.search)
230
+ params.set("buscar", args.search);
231
+ return { content: [{ type: "text", text: JSON.stringify(await coordinadoraRequest("GET", `/ciudades?${params}`), null, 2) }] };
232
+ }
233
+ case "create_pickup":
234
+ return { content: [{ type: "text", text: JSON.stringify(await coordinadoraRequest("POST", "/recolecciones", {
235
+ direccion: args?.address,
236
+ ciudad: args?.city,
237
+ contactoNombre: args?.contact_name,
238
+ contactoTelefono: args?.contact_phone,
239
+ fecha: args?.date,
240
+ horaDesde: args?.time_from,
241
+ horaHasta: args?.time_to,
242
+ cantidadBultos: args?.packages_count,
243
+ pesoTotal: args?.total_weight,
244
+ }), null, 2) }] };
245
+ case "get_coverage": {
246
+ const params = new URLSearchParams();
247
+ params.set("ciudad", args?.city);
248
+ if (args?.postal_code)
249
+ params.set("codigoPostal", args.postal_code);
250
+ return { content: [{ type: "text", text: JSON.stringify(await coordinadoraRequest("GET", `/cobertura?${params}`), null, 2) }] };
251
+ }
252
+ case "cancel_shipment": {
253
+ const payload = {};
254
+ if (args?.reason)
255
+ payload.motivo = args.reason;
256
+ return { content: [{ type: "text", text: JSON.stringify(await coordinadoraRequest("DELETE", `/guias/${args?.guideNumber}`, payload), null, 2) }] };
257
+ }
258
+ default:
259
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
260
+ }
261
+ }
262
+ catch (err) {
263
+ return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
264
+ }
265
+ });
266
+ async function main() {
267
+ if (process.argv.includes("--http") || process.env.MCP_HTTP === "true") {
268
+ const { default: express } = await import("express");
269
+ const { randomUUID } = await import("node:crypto");
270
+ const app = express();
271
+ app.use(express.json());
272
+ const transports = new Map();
273
+ app.get("/health", (_req, res) => res.json({ status: "ok", sessions: transports.size }));
274
+ app.post("/mcp", async (req, res) => {
275
+ const sid = req.headers["mcp-session-id"];
276
+ if (sid && transports.has(sid)) {
277
+ await transports.get(sid).handleRequest(req, res, req.body);
278
+ return;
279
+ }
280
+ if (!sid && isInitializeRequest(req.body)) {
281
+ const t = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (id) => { transports.set(id, t); } });
282
+ t.onclose = () => { if (t.sessionId)
283
+ transports.delete(t.sessionId); };
284
+ const s = new Server({ name: "mcp-coordinadora", version: "0.1.0" }, { capabilities: { tools: {} } });
285
+ server._requestHandlers.forEach((v, k) => s._requestHandlers.set(k, v));
286
+ server._notificationHandlers?.forEach((v, k) => s._notificationHandlers.set(k, v));
287
+ await s.connect(t);
288
+ await t.handleRequest(req, res, req.body);
289
+ return;
290
+ }
291
+ res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request" }, id: null });
292
+ });
293
+ app.get("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"]; if (sid && transports.has(sid))
294
+ await transports.get(sid).handleRequest(req, res);
295
+ else
296
+ res.status(400).send("Invalid session"); });
297
+ app.delete("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"]; if (sid && transports.has(sid))
298
+ await transports.get(sid).handleRequest(req, res);
299
+ else
300
+ res.status(400).send("Invalid session"); });
301
+ const port = Number(process.env.MCP_PORT) || 3000;
302
+ app.listen(port, () => { console.error(`MCP HTTP server on http://localhost:${port}/mcp`); });
303
+ }
304
+ else {
305
+ const transport = new StdioServerTransport();
306
+ await server.connect(transport);
307
+ }
308
+ }
309
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@codespar/mcp-coordinadora",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Coordinadora — Colombian courier and logistics",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "mcp-coordinadora": "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": ["coordinadora", "courier", "logistics", "colombia", "shipping", "mcp"],
24
+ "mcpName": "io.github.codespar/mcp-coordinadora"
25
+ }