@codespar/mcp-tienda-nube 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,53 @@
1
+ # MCP Tienda Nube
2
+
3
+ MCP server for **Tienda Nube** (Nuvemshop) — the leading LATAM e-commerce platform, Argentine-founded, equivalent to Shopify for Latin America.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Set your credentials
9
+ export TIENDANUBE_ACCESS_TOKEN="your-access-token"
10
+ export TIENDANUBE_STORE_ID="your-store-id"
11
+
12
+ # Run via stdio
13
+ npx tsx packages/argentina/tienda-nube/src/index.ts
14
+
15
+ # Run via HTTP
16
+ npx tsx packages/argentina/tienda-nube/src/index.ts --http
17
+ ```
18
+
19
+ ## Environment Variables
20
+
21
+ | Variable | Required | Description |
22
+ |----------|----------|-------------|
23
+ | `TIENDANUBE_ACCESS_TOKEN` | Yes | Access token from Tienda Nube |
24
+ | `TIENDANUBE_STORE_ID` | Yes | Store identifier |
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
+ | `list_products` | List products from the store |
33
+ | `get_product` | Get product details by ID |
34
+ | `create_product` | Create a new product |
35
+ | `update_product` | Update an existing product |
36
+ | `list_orders` | List orders |
37
+ | `get_order` | Get order details by ID |
38
+ | `list_customers` | List customers |
39
+ | `get_customer` | Get customer details by ID |
40
+ | `list_categories` | List product categories |
41
+ | `update_order_status` | Update order fulfillment/shipping status |
42
+
43
+ ## Auth
44
+
45
+ Uses **Bearer token** authentication. Obtain your access token via the Tienda Nube Partners OAuth flow. The store ID is included in the API base URL.
46
+
47
+ ## API Reference
48
+
49
+ - [Tienda Nube API Docs](https://tiendanube.github.io/api-documentation/)
50
+
51
+ ---
52
+
53
+ **Enterprise?** Contact us at [codespar.com](https://codespar.com) for dedicated support, custom integrations, and SLAs.
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Server for Tienda Nube — LATAM e-commerce platform (Nuvemshop).
4
+ *
5
+ * Tools:
6
+ * - list_products: List products
7
+ * - get_product: Get product by ID
8
+ * - create_product: Create a product
9
+ * - update_product: Update a product
10
+ * - list_orders: List orders
11
+ * - get_order: Get order by ID
12
+ * - list_customers: List customers
13
+ * - get_customer: Get customer by ID
14
+ * - list_categories: List product categories
15
+ * - update_order_status: Update order fulfillment status
16
+ *
17
+ * Environment:
18
+ * TIENDANUBE_ACCESS_TOKEN — Access token
19
+ * TIENDANUBE_STORE_ID — Store identifier
20
+ */
21
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,330 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Server for Tienda Nube — LATAM e-commerce platform (Nuvemshop).
4
+ *
5
+ * Tools:
6
+ * - list_products: List products
7
+ * - get_product: Get product by ID
8
+ * - create_product: Create a product
9
+ * - update_product: Update a product
10
+ * - list_orders: List orders
11
+ * - get_order: Get order by ID
12
+ * - list_customers: List customers
13
+ * - get_customer: Get customer by ID
14
+ * - list_categories: List product categories
15
+ * - update_order_status: Update order fulfillment status
16
+ *
17
+ * Environment:
18
+ * TIENDANUBE_ACCESS_TOKEN — Access token
19
+ * TIENDANUBE_STORE_ID — Store identifier
20
+ */
21
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
22
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
23
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
24
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
25
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
26
+ const ACCESS_TOKEN = process.env.TIENDANUBE_ACCESS_TOKEN || "";
27
+ const STORE_ID = process.env.TIENDANUBE_STORE_ID || "";
28
+ const BASE_URL = `https://api.tiendanube.com/v1/${STORE_ID}`;
29
+ async function tiendaNubeRequest(method, path, body) {
30
+ const headers = {
31
+ "Content-Type": "application/json",
32
+ "Accept": "application/json",
33
+ "Authentication": `bearer ${ACCESS_TOKEN}`,
34
+ "User-Agent": "MCP Tienda Nube Server/0.1.0",
35
+ };
36
+ const res = await fetch(`${BASE_URL}${path}`, {
37
+ method,
38
+ headers,
39
+ body: body ? JSON.stringify(body) : undefined,
40
+ });
41
+ if (!res.ok) {
42
+ const err = await res.text();
43
+ throw new Error(`Tienda Nube API ${res.status}: ${err}`);
44
+ }
45
+ return res.json();
46
+ }
47
+ const server = new Server({ name: "mcp-tienda-nube", version: "0.1.0" }, { capabilities: { tools: {} } });
48
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
49
+ tools: [
50
+ {
51
+ name: "list_products",
52
+ description: "List products from the store",
53
+ inputSchema: {
54
+ type: "object",
55
+ properties: {
56
+ page: { type: "number", description: "Page number" },
57
+ per_page: { type: "number", description: "Results per page (max 200)" },
58
+ since_id: { type: "number", description: "Filter products after this ID" },
59
+ created_at_min: { type: "string", description: "Min creation date (ISO 8601)" },
60
+ created_at_max: { type: "string", description: "Max creation date (ISO 8601)" },
61
+ },
62
+ },
63
+ },
64
+ {
65
+ name: "get_product",
66
+ description: "Get product details by ID",
67
+ inputSchema: {
68
+ type: "object",
69
+ properties: { productId: { type: "number", description: "Product ID" } },
70
+ required: ["productId"],
71
+ },
72
+ },
73
+ {
74
+ name: "create_product",
75
+ description: "Create a new product",
76
+ inputSchema: {
77
+ type: "object",
78
+ properties: {
79
+ name: { type: "object", description: "Product name by locale, e.g. {\"es\":\"Producto\"}", properties: { es: { type: "string" }, pt: { type: "string" }, en: { type: "string" } } },
80
+ variants: {
81
+ type: "array",
82
+ description: "Product variants",
83
+ items: {
84
+ type: "object",
85
+ properties: {
86
+ price: { type: "string", description: "Price" },
87
+ stock: { type: "number", description: "Stock quantity" },
88
+ sku: { type: "string", description: "SKU" },
89
+ weight: { type: "string", description: "Weight in kg" },
90
+ },
91
+ required: ["price"],
92
+ },
93
+ },
94
+ description: { type: "object", description: "Description by locale", properties: { es: { type: "string" }, pt: { type: "string" } } },
95
+ published: { type: "boolean", description: "Whether the product is published" },
96
+ categories: { type: "array", description: "Category IDs", items: { type: "number" } },
97
+ },
98
+ required: ["name", "variants"],
99
+ },
100
+ },
101
+ {
102
+ name: "update_product",
103
+ description: "Update an existing product",
104
+ inputSchema: {
105
+ type: "object",
106
+ properties: {
107
+ productId: { type: "number", description: "Product ID" },
108
+ name: { type: "object", description: "Product name by locale", properties: { es: { type: "string" }, pt: { type: "string" } } },
109
+ description: { type: "object", description: "Description by locale", properties: { es: { type: "string" }, pt: { type: "string" } } },
110
+ published: { type: "boolean", description: "Whether the product is published" },
111
+ },
112
+ required: ["productId"],
113
+ },
114
+ },
115
+ {
116
+ name: "list_orders",
117
+ description: "List orders",
118
+ inputSchema: {
119
+ type: "object",
120
+ properties: {
121
+ page: { type: "number", description: "Page number" },
122
+ per_page: { type: "number", description: "Results per page" },
123
+ status: { type: "string", description: "Order status (open, closed, cancelled)" },
124
+ payment_status: { type: "string", description: "Payment status (pending, paid, refunded)" },
125
+ shipping_status: { type: "string", description: "Shipping status (unpacked, shipped, delivered)" },
126
+ },
127
+ },
128
+ },
129
+ {
130
+ name: "get_order",
131
+ description: "Get order details by ID",
132
+ inputSchema: {
133
+ type: "object",
134
+ properties: { orderId: { type: "number", description: "Order ID" } },
135
+ required: ["orderId"],
136
+ },
137
+ },
138
+ {
139
+ name: "list_customers",
140
+ description: "List customers",
141
+ inputSchema: {
142
+ type: "object",
143
+ properties: {
144
+ page: { type: "number", description: "Page number" },
145
+ per_page: { type: "number", description: "Results per page" },
146
+ since_id: { type: "number", description: "Filter customers after this ID" },
147
+ q: { type: "string", description: "Search query (name, email)" },
148
+ },
149
+ },
150
+ },
151
+ {
152
+ name: "get_customer",
153
+ description: "Get customer details by ID",
154
+ inputSchema: {
155
+ type: "object",
156
+ properties: { customerId: { type: "number", description: "Customer ID" } },
157
+ required: ["customerId"],
158
+ },
159
+ },
160
+ {
161
+ name: "list_categories",
162
+ description: "List product categories",
163
+ inputSchema: {
164
+ type: "object",
165
+ properties: {
166
+ page: { type: "number", description: "Page number" },
167
+ per_page: { type: "number", description: "Results per page" },
168
+ },
169
+ },
170
+ },
171
+ {
172
+ name: "update_order_status",
173
+ description: "Update order fulfillment/shipping status",
174
+ inputSchema: {
175
+ type: "object",
176
+ properties: {
177
+ orderId: { type: "number", description: "Order ID" },
178
+ status: { type: "string", description: "New status (open, closed, cancelled)" },
179
+ shipping_status: { type: "string", description: "Shipping status (unpacked, shipped, delivered)" },
180
+ tracking_number: { type: "string", description: "Tracking number" },
181
+ tracking_url: { type: "string", description: "Tracking URL" },
182
+ shipping_carrier: { type: "string", description: "Shipping carrier name" },
183
+ },
184
+ required: ["orderId"],
185
+ },
186
+ },
187
+ ],
188
+ }));
189
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
190
+ const { name, arguments: args } = request.params;
191
+ try {
192
+ switch (name) {
193
+ case "list_products": {
194
+ const params = new URLSearchParams();
195
+ if (args?.page)
196
+ params.set("page", String(args.page));
197
+ if (args?.per_page)
198
+ params.set("per_page", String(args.per_page));
199
+ if (args?.since_id)
200
+ params.set("since_id", String(args.since_id));
201
+ if (args?.created_at_min)
202
+ params.set("created_at_min", args.created_at_min);
203
+ if (args?.created_at_max)
204
+ params.set("created_at_max", args.created_at_max);
205
+ return { content: [{ type: "text", text: JSON.stringify(await tiendaNubeRequest("GET", `/products?${params}`), null, 2) }] };
206
+ }
207
+ case "get_product":
208
+ return { content: [{ type: "text", text: JSON.stringify(await tiendaNubeRequest("GET", `/products/${args?.productId}`), null, 2) }] };
209
+ case "create_product":
210
+ return { content: [{ type: "text", text: JSON.stringify(await tiendaNubeRequest("POST", "/products", {
211
+ name: args?.name,
212
+ variants: args?.variants,
213
+ description: args?.description,
214
+ published: args?.published,
215
+ categories: args?.categories,
216
+ }), null, 2) }] };
217
+ case "update_product": {
218
+ const payload = {};
219
+ if (args?.name)
220
+ payload.name = args.name;
221
+ if (args?.description)
222
+ payload.description = args.description;
223
+ if (args?.published !== undefined)
224
+ payload.published = args.published;
225
+ return { content: [{ type: "text", text: JSON.stringify(await tiendaNubeRequest("PUT", `/products/${args?.productId}`, payload), null, 2) }] };
226
+ }
227
+ case "list_orders": {
228
+ const params = new URLSearchParams();
229
+ if (args?.page)
230
+ params.set("page", String(args.page));
231
+ if (args?.per_page)
232
+ params.set("per_page", String(args.per_page));
233
+ if (args?.status)
234
+ params.set("status", args.status);
235
+ if (args?.payment_status)
236
+ params.set("payment_status", args.payment_status);
237
+ if (args?.shipping_status)
238
+ params.set("shipping_status", args.shipping_status);
239
+ return { content: [{ type: "text", text: JSON.stringify(await tiendaNubeRequest("GET", `/orders?${params}`), null, 2) }] };
240
+ }
241
+ case "get_order":
242
+ return { content: [{ type: "text", text: JSON.stringify(await tiendaNubeRequest("GET", `/orders/${args?.orderId}`), null, 2) }] };
243
+ case "list_customers": {
244
+ const params = new URLSearchParams();
245
+ if (args?.page)
246
+ params.set("page", String(args.page));
247
+ if (args?.per_page)
248
+ params.set("per_page", String(args.per_page));
249
+ if (args?.since_id)
250
+ params.set("since_id", String(args.since_id));
251
+ if (args?.q)
252
+ params.set("q", args.q);
253
+ return { content: [{ type: "text", text: JSON.stringify(await tiendaNubeRequest("GET", `/customers?${params}`), null, 2) }] };
254
+ }
255
+ case "get_customer":
256
+ return { content: [{ type: "text", text: JSON.stringify(await tiendaNubeRequest("GET", `/customers/${args?.customerId}`), null, 2) }] };
257
+ case "list_categories": {
258
+ const params = new URLSearchParams();
259
+ if (args?.page)
260
+ params.set("page", String(args.page));
261
+ if (args?.per_page)
262
+ params.set("per_page", String(args.per_page));
263
+ return { content: [{ type: "text", text: JSON.stringify(await tiendaNubeRequest("GET", `/categories?${params}`), null, 2) }] };
264
+ }
265
+ case "update_order_status": {
266
+ const payload = {};
267
+ if (args?.status)
268
+ payload.status = args.status;
269
+ if (args?.shipping_status)
270
+ payload.shipping_status = args.shipping_status;
271
+ if (args?.tracking_number)
272
+ payload.tracking_number = args.tracking_number;
273
+ if (args?.tracking_url)
274
+ payload.tracking_url = args.tracking_url;
275
+ if (args?.shipping_carrier)
276
+ payload.shipping_carrier = args.shipping_carrier;
277
+ return { content: [{ type: "text", text: JSON.stringify(await tiendaNubeRequest("PUT", `/orders/${args?.orderId}`, payload), null, 2) }] };
278
+ }
279
+ default:
280
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
281
+ }
282
+ }
283
+ catch (err) {
284
+ return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
285
+ }
286
+ });
287
+ async function main() {
288
+ if (process.argv.includes("--http") || process.env.MCP_HTTP === "true") {
289
+ const { default: express } = await import("express");
290
+ const { randomUUID } = await import("node:crypto");
291
+ const app = express();
292
+ app.use(express.json());
293
+ const transports = new Map();
294
+ app.get("/health", (_req, res) => res.json({ status: "ok", sessions: transports.size }));
295
+ app.post("/mcp", async (req, res) => {
296
+ const sid = req.headers["mcp-session-id"];
297
+ if (sid && transports.has(sid)) {
298
+ await transports.get(sid).handleRequest(req, res, req.body);
299
+ return;
300
+ }
301
+ if (!sid && isInitializeRequest(req.body)) {
302
+ const t = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (id) => { transports.set(id, t); } });
303
+ t.onclose = () => { if (t.sessionId)
304
+ transports.delete(t.sessionId); };
305
+ const s = new Server({ name: "mcp-tienda-nube", version: "0.1.0" }, { capabilities: { tools: {} } });
306
+ server._requestHandlers.forEach((v, k) => s._requestHandlers.set(k, v));
307
+ server._notificationHandlers?.forEach((v, k) => s._notificationHandlers.set(k, v));
308
+ await s.connect(t);
309
+ await t.handleRequest(req, res, req.body);
310
+ return;
311
+ }
312
+ res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request" }, id: null });
313
+ });
314
+ app.get("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"]; if (sid && transports.has(sid))
315
+ await transports.get(sid).handleRequest(req, res);
316
+ else
317
+ res.status(400).send("Invalid session"); });
318
+ app.delete("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"]; if (sid && transports.has(sid))
319
+ await transports.get(sid).handleRequest(req, res);
320
+ else
321
+ res.status(400).send("Invalid session"); });
322
+ const port = Number(process.env.MCP_PORT) || 3000;
323
+ app.listen(port, () => { console.error(`MCP HTTP server on http://localhost:${port}/mcp`); });
324
+ }
325
+ else {
326
+ const transport = new StdioServerTransport();
327
+ await server.connect(transport);
328
+ }
329
+ }
330
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@codespar/mcp-tienda-nube",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Tienda Nube — LATAM e-commerce platform",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "mcp-tienda-nube": "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": ["tienda-nube", "nuvemshop", "ecommerce", "argentina", "latam", "mcp"],
24
+ "mcpName": "io.github.codespar/mcp-tienda-nube"
25
+ }