shoptet-mcp 1.0.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,179 @@
1
+ # Shoptet MCP Server
2
+
3
+ [![npm version](https://img.shields.io/npm/v/shoptet-mcp.svg)](https://www.npmjs.com/package/shoptet-mcp)
4
+
5
+ MCP (Model Context Protocol) server for the [Shoptet](https://www.shoptet.cz/) e-commerce platform API. Provides 42 tools for managing orders, products, customers, documents, webhooks, and more — directly from AI assistants like Claude.
6
+
7
+ ## Features
8
+
9
+ - **Orders** — list, create, update status, get PDF, history, remarks, change tracking
10
+ - **Products** — list, detail, stock levels, brands, categories, change tracking
11
+ - **Customers** — list and detail with filtering
12
+ - **Documents** — invoices, proforma invoices, credit notes, delivery notes, proof of payments
13
+ - **Webhooks** — create, list, delete webhook subscriptions
14
+ - **Eshop info** — basic shop metadata
15
+ - **AI Prompts** — built-in prompts for order analysis, low stock check, daily summary, customer lookup
16
+ - **Resources** — quick access to eshop info and order statuses
17
+
18
+ ## Prerequisites
19
+
20
+ - Node.js 18+
21
+ - Shoptet API token ([how to get one](https://www.shoptet.cz/api/))
22
+
23
+ ## Setup
24
+
25
+ No installation needed — just configure your AI client with `npx`:
26
+
27
+ ### Claude Code (CLI)
28
+
29
+ ```bash
30
+ claude mcp add shoptet -e SHOPTET_API_TOKEN=your-token -- npx shoptet-mcp
31
+ ```
32
+
33
+ Or add to your project's `.mcp.json`:
34
+
35
+ ```json
36
+ {
37
+ "mcpServers": {
38
+ "shoptet": {
39
+ "command": "npx",
40
+ "args": ["shoptet-mcp"],
41
+ "env": {
42
+ "SHOPTET_API_TOKEN": "your-token"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ### Claude Desktop
50
+
51
+ Open **Settings → Developer → Edit Config** and add to `claude_desktop_config.json`:
52
+
53
+ ```json
54
+ {
55
+ "mcpServers": {
56
+ "shoptet": {
57
+ "command": "npx",
58
+ "args": ["shoptet-mcp"],
59
+ "env": {
60
+ "SHOPTET_API_TOKEN": "your-token"
61
+ }
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ ### VS Code (Copilot / Claude extension)
68
+
69
+ Add to `.vscode/mcp.json` in your workspace:
70
+
71
+ ```json
72
+ {
73
+ "servers": {
74
+ "shoptet": {
75
+ "command": "npx",
76
+ "args": ["shoptet-mcp"],
77
+ "env": {
78
+ "SHOPTET_API_TOKEN": "your-token"
79
+ }
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
85
+ ### JetBrains IDEs
86
+
87
+ Go to **Settings → Tools → AI Assistant → MCP Servers → Add**, then configure:
88
+
89
+ - **Name:** `shoptet`
90
+ - **Command:** `npx`
91
+ - **Args:** `shoptet-mcp`
92
+ - **Environment:** `SHOPTET_API_TOKEN=your-token`
93
+
94
+ ### Cursor
95
+
96
+ Add to `.cursor/mcp.json`:
97
+
98
+ ```json
99
+ {
100
+ "mcpServers": {
101
+ "shoptet": {
102
+ "command": "npx",
103
+ "args": ["shoptet-mcp"],
104
+ "env": {
105
+ "SHOPTET_API_TOKEN": "your-token"
106
+ }
107
+ }
108
+ }
109
+ }
110
+ ```
111
+
112
+ ### Windsurf
113
+
114
+ Add to `~/.codeium/windsurf/mcp_config.json`:
115
+
116
+ ```json
117
+ {
118
+ "mcpServers": {
119
+ "shoptet": {
120
+ "command": "npx",
121
+ "args": ["shoptet-mcp"],
122
+ "env": {
123
+ "SHOPTET_API_TOKEN": "your-token"
124
+ }
125
+ }
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### Cline (VS Code extension)
131
+
132
+ Open MCP settings in Cline and add:
133
+
134
+ ```json
135
+ {
136
+ "mcpServers": {
137
+ "shoptet": {
138
+ "command": "npx",
139
+ "args": ["shoptet-mcp"],
140
+ "env": {
141
+ "SHOPTET_API_TOKEN": "your-token"
142
+ }
143
+ }
144
+ }
145
+ }
146
+ ```
147
+
148
+ ## Available Tools
149
+
150
+ | Category | Tools |
151
+ |----------|-------|
152
+ | **Orders** | `list_orders`, `get_order`, `create_order`, `update_order_status`, `get_order_statuses`, `get_order_pdf`, `get_order_history`, `add_order_remark`, `get_order_changes` |
153
+ | **Products** | `list_products`, `get_product`, `get_product_changes`, `get_stock`, `list_brands`, `create_brand`, `list_categories` |
154
+ | **Customers** | `list_customers`, `get_customer` |
155
+ | **Documents** | `list_invoices`, `get_invoice`, `create_invoice_from_order`, `get_invoice_pdf`, `list_proforma_invoices`, `get_proforma_invoice`, `list_credit_notes`, `get_credit_note`, `create_credit_note_from_invoice`, `list_delivery_notes`, `create_delivery_note`, `list_proof_payments`, `get_proof_payment` |
156
+ | **Webhooks** | `list_webhooks`, `create_webhook`, `delete_webhook` |
157
+ | **Eshop** | `get_eshop_info` |
158
+
159
+ ## Development
160
+
161
+ ```bash
162
+ git clone https://github.com/tomaskalina/shoptet-mcp.git
163
+ cd shoptet-mcp
164
+ npm install
165
+ npm run build
166
+ ```
167
+
168
+ ```bash
169
+ npm run dev # Watch mode (TypeScript compilation)
170
+ npm run test # Run tests
171
+ npm run test:watch # Watch mode tests
172
+ npm run lint # Lint
173
+ npm run typecheck # Type check
174
+ npm run check # All checks (typecheck + lint + test)
175
+ ```
176
+
177
+ ## License
178
+
179
+ MIT
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,72 @@
1
+ import { describe, it, expect } from "vitest";
2
+ // Must set env before importing client
3
+ process.env.SHOPTET_API_TOKEN = "test-token";
4
+ import { safePath, buildQuery, text, errorResult, safeCall } from "../client.js";
5
+ describe("safePath", () => {
6
+ it("encodes a simple string", () => {
7
+ expect(safePath("abc123")).toBe("abc123");
8
+ });
9
+ it("encodes special characters", () => {
10
+ expect(safePath("hello world")).toBe("hello%20world");
11
+ });
12
+ it("rejects path traversal", () => {
13
+ expect(() => safePath("../etc")).toThrow("Invalid path segment");
14
+ });
15
+ it("rejects slashes", () => {
16
+ expect(() => safePath("foo/bar")).toThrow("Invalid path segment");
17
+ });
18
+ it("rejects query strings", () => {
19
+ expect(() => safePath("foo?bar=1")).toThrow("Invalid path segment");
20
+ });
21
+ it("rejects fragments", () => {
22
+ expect(() => safePath("foo#bar")).toThrow("Invalid path segment");
23
+ });
24
+ it("handles numbers", () => {
25
+ expect(safePath(42)).toBe("42");
26
+ });
27
+ });
28
+ describe("buildQuery", () => {
29
+ it("returns empty string for no params", () => {
30
+ expect(buildQuery({})).toBe("");
31
+ });
32
+ it("builds query string from params", () => {
33
+ expect(buildQuery({ page: 1, itemsPerPage: 10 })).toBe("?page=1&itemsPerPage=10");
34
+ });
35
+ it("skips undefined values", () => {
36
+ expect(buildQuery({ page: 1, status: undefined })).toBe("?page=1");
37
+ });
38
+ it("handles string values", () => {
39
+ expect(buildQuery({ email: "test@example.com" })).toBe("?email=test%40example.com");
40
+ });
41
+ });
42
+ describe("text", () => {
43
+ it("wraps data as JSON text content", () => {
44
+ const result = text({ foo: "bar" });
45
+ expect(result.content).toHaveLength(1);
46
+ const item = result.content[0];
47
+ expect(item.type).toBe("text");
48
+ expect("text" in item && JSON.parse(item.text)).toEqual({ foo: "bar" });
49
+ });
50
+ });
51
+ describe("errorResult", () => {
52
+ it("returns error content with isError flag", () => {
53
+ const result = errorResult(new Error("test error"));
54
+ expect(result.isError).toBe(true);
55
+ const item = result.content[0];
56
+ expect("text" in item && item.text).toContain("test error");
57
+ });
58
+ });
59
+ describe("safeCall", () => {
60
+ it("returns result on success", async () => {
61
+ const result = await safeCall(async () => text({ ok: true }));
62
+ expect(result.isError).toBeUndefined();
63
+ });
64
+ it("catches errors and returns error result", async () => {
65
+ const result = await safeCall(async () => {
66
+ throw new Error("boom");
67
+ });
68
+ expect(result.isError).toBe(true);
69
+ const item = result.content[0];
70
+ expect("text" in item && item.text).toContain("boom");
71
+ });
72
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,60 @@
1
+ import { describe, it, expect, vi, beforeAll } from "vitest";
2
+ // Set env before any imports
3
+ process.env.SHOPTET_API_TOKEN = "test-token";
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { registerOrderTools } from "../tools/orders.js";
6
+ import { registerProductTools } from "../tools/products.js";
7
+ import { registerCustomerTools } from "../tools/customers.js";
8
+ import { registerDocumentTools } from "../tools/documents.js";
9
+ import { registerWebhookTools } from "../tools/webhooks.js";
10
+ import { registerEshopTools } from "../tools/eshop.js";
11
+ import { registerResources } from "../resources.js";
12
+ import { registerPrompts } from "../prompts.js";
13
+ describe("MCP Server registration", () => {
14
+ let server;
15
+ beforeAll(() => {
16
+ server = new McpServer({ name: "shoptet-test", version: "1.0.0" });
17
+ });
18
+ it("registers order tools without error", () => {
19
+ expect(() => registerOrderTools(server)).not.toThrow();
20
+ });
21
+ it("registers product tools without error", () => {
22
+ expect(() => registerProductTools(server)).not.toThrow();
23
+ });
24
+ it("registers customer tools without error", () => {
25
+ expect(() => registerCustomerTools(server)).not.toThrow();
26
+ });
27
+ it("registers document tools without error", () => {
28
+ expect(() => registerDocumentTools(server)).not.toThrow();
29
+ });
30
+ it("registers webhook tools without error", () => {
31
+ expect(() => registerWebhookTools(server)).not.toThrow();
32
+ });
33
+ it("registers eshop tools without error", () => {
34
+ expect(() => registerEshopTools(server)).not.toThrow();
35
+ });
36
+ it("registers resources without error", () => {
37
+ expect(() => registerResources(server)).not.toThrow();
38
+ });
39
+ it("registers prompts without error", () => {
40
+ expect(() => registerPrompts(server)).not.toThrow();
41
+ });
42
+ });
43
+ describe("API error handling", () => {
44
+ it("shoptet() throws on non-OK response", async () => {
45
+ const originalFetch = globalThis.fetch;
46
+ globalThis.fetch = vi.fn().mockResolvedValue({
47
+ ok: false,
48
+ status: 401,
49
+ text: async () => "Unauthorized",
50
+ headers: new Headers(),
51
+ });
52
+ try {
53
+ const { shoptet } = await import("../client.js");
54
+ await expect(shoptet("/api/eshop")).rejects.toThrow("401");
55
+ }
56
+ finally {
57
+ globalThis.fetch = originalFetch;
58
+ }
59
+ });
60
+ });
@@ -0,0 +1,12 @@
1
+ import { z } from "zod";
2
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ export declare function safePath(segment: string | number): string;
4
+ export declare function shoptet(path: string, method?: string, body?: unknown): Promise<unknown>;
5
+ export declare const pagingSchema: {
6
+ page: z.ZodOptional<z.ZodNumber>;
7
+ itemsPerPage: z.ZodOptional<z.ZodNumber>;
8
+ };
9
+ export declare function buildQuery(params: Record<string, string | number | undefined>): string;
10
+ export declare function text(data: unknown): CallToolResult;
11
+ export declare function errorResult(err: unknown): CallToolResult;
12
+ export declare function safeCall(fn: () => Promise<CallToolResult>): Promise<CallToolResult>;
package/dist/client.js ADDED
@@ -0,0 +1,65 @@
1
+ import { z } from "zod";
2
+ const API_BASE = "https://api.myshoptet.com";
3
+ function getToken() {
4
+ const t = process.env.SHOPTET_API_TOKEN;
5
+ if (!t)
6
+ throw new Error("SHOPTET_API_TOKEN environment variable is required");
7
+ return t;
8
+ }
9
+ export function safePath(segment) {
10
+ const s = String(segment);
11
+ if (s.includes("/") || s.includes("?") || s.includes("#") || s.includes("..")) {
12
+ throw new Error(`Invalid path segment: ${s}`);
13
+ }
14
+ return encodeURIComponent(s);
15
+ }
16
+ export async function shoptet(path, method = "GET", body) {
17
+ const res = await fetch(`${API_BASE}${path}`, {
18
+ method,
19
+ headers: {
20
+ "Shoptet-Access-Token": getToken(),
21
+ ...(body ? { "Content-Type": "application/json" } : {}),
22
+ },
23
+ ...(body ? { body: JSON.stringify(body) } : {}),
24
+ });
25
+ if (res.status === 429) {
26
+ const retryAfter = res.headers.get("Retry-After");
27
+ await new Promise((r) => setTimeout(r, (Number(retryAfter) || 2) * 1000));
28
+ return shoptet(path, method, body);
29
+ }
30
+ if (!res.ok) {
31
+ const errText = await res.text();
32
+ throw new Error(`Shoptet API ${res.status}: ${errText}`);
33
+ }
34
+ return res.json();
35
+ }
36
+ export const pagingSchema = {
37
+ page: z.number().optional().describe("Page number (starts at 1)"),
38
+ itemsPerPage: z.number().optional().describe("Items per page (max 100)"),
39
+ };
40
+ export function buildQuery(params) {
41
+ const query = new URLSearchParams();
42
+ for (const [key, value] of Object.entries(params)) {
43
+ if (value !== undefined)
44
+ query.set(key, String(value));
45
+ }
46
+ const qs = query.toString();
47
+ return qs ? `?${qs}` : "";
48
+ }
49
+ export function text(data) {
50
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
51
+ }
52
+ export function errorResult(err) {
53
+ return {
54
+ content: [{ type: "text", text: String(err) }],
55
+ isError: true,
56
+ };
57
+ }
58
+ export async function safeCall(fn) {
59
+ try {
60
+ return await fn();
61
+ }
62
+ catch (err) {
63
+ return errorResult(err);
64
+ }
65
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { registerOrderTools } from "./tools/orders.js";
5
+ import { registerProductTools } from "./tools/products.js";
6
+ import { registerCustomerTools } from "./tools/customers.js";
7
+ import { registerDocumentTools } from "./tools/documents.js";
8
+ import { registerWebhookTools } from "./tools/webhooks.js";
9
+ import { registerEshopTools } from "./tools/eshop.js";
10
+ import { registerResources } from "./resources.js";
11
+ import { registerPrompts } from "./prompts.js";
12
+ const server = new McpServer({
13
+ name: "shoptet",
14
+ version: "1.0.0",
15
+ });
16
+ // Tools
17
+ registerOrderTools(server);
18
+ registerProductTools(server);
19
+ registerCustomerTools(server);
20
+ registerDocumentTools(server);
21
+ registerWebhookTools(server);
22
+ registerEshopTools(server);
23
+ // Resources & Prompts
24
+ registerResources(server);
25
+ registerPrompts(server);
26
+ const transport = new StdioServerTransport();
27
+ server.connect(transport).catch((err) => {
28
+ console.error("Fatal error:", err);
29
+ process.exit(1);
30
+ });
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerPrompts(server: McpServer): void;
@@ -0,0 +1,54 @@
1
+ import { z } from "zod";
2
+ export function registerPrompts(server) {
3
+ server.prompt("analyze-orders", "Analyze recent orders — summarize totals, statuses, and trends", { days: z.string().optional().describe("Number of days to look back (default: 7)") }, async ({ days }) => {
4
+ const d = Number(days) || 7;
5
+ const from = new Date(Date.now() - d * 86400000).toISOString().slice(0, 16).replace("T", " ");
6
+ return {
7
+ messages: [
8
+ {
9
+ role: "user",
10
+ content: {
11
+ type: "text",
12
+ text: `Use the list_orders tool with creationTimeFrom="${from}" to fetch recent orders. Then analyze them:\n\n1. Total number of orders and total revenue\n2. Breakdown by order status\n3. Top customers by order value\n4. Any notable trends or anomalies\n\nPresent the results in a clear summary with tables where appropriate.`,
13
+ },
14
+ },
15
+ ],
16
+ };
17
+ });
18
+ server.prompt("low-stock-check", "Check for products with low inventory levels", {}, async () => ({
19
+ messages: [
20
+ {
21
+ role: "user",
22
+ content: {
23
+ type: "text",
24
+ text: "Use list_products to get all products, then check stock levels for each using get_stock. Identify products with low or zero inventory. Present a table of products sorted by stock level (lowest first) including product name, current stock, and any variants.",
25
+ },
26
+ },
27
+ ],
28
+ }));
29
+ server.prompt("daily-summary", "Generate a daily business summary for the eshop", {}, async () => {
30
+ const today = new Date().toISOString().slice(0, 10);
31
+ return {
32
+ messages: [
33
+ {
34
+ role: "user",
35
+ content: {
36
+ type: "text",
37
+ text: `Generate a daily business summary for ${today}:\n\n1. Use get_eshop_info to get the shop name\n2. Use list_orders with creationTimeFrom="${today} 00:00" to get today's orders\n3. Use get_order_changes to see recent activity\n4. Summarize: new orders count, revenue, status distribution\n\nFormat as a concise daily report.`,
38
+ },
39
+ },
40
+ ],
41
+ };
42
+ });
43
+ server.prompt("customer-lookup", "Look up a customer and their order history", { email: z.string().describe("Customer email address") }, async ({ email }) => ({
44
+ messages: [
45
+ {
46
+ role: "user",
47
+ content: {
48
+ type: "text",
49
+ text: `Look up the customer with email "${email}":\n\n1. Use list_customers with email filter to find them\n2. Use get_customer to get their full profile\n3. Use list_orders to find their orders\n4. Summarize: customer info, total orders, total spent, most recent order`,
50
+ },
51
+ },
52
+ ],
53
+ }));
54
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerResources(server: McpServer): void;
@@ -0,0 +1,21 @@
1
+ import { shoptet } from "./client.js";
2
+ export function registerResources(server) {
3
+ server.resource("eshop-info", "shoptet://eshop/info", { description: "Basic eshop information: name, URL, contact, currency, settings" }, async () => ({
4
+ contents: [
5
+ {
6
+ uri: "shoptet://eshop/info",
7
+ mimeType: "application/json",
8
+ text: JSON.stringify(await shoptet("/api/eshop"), null, 2),
9
+ },
10
+ ],
11
+ }));
12
+ server.resource("order-statuses", "shoptet://orders/statuses", { description: "All available order statuses with IDs and names" }, async () => ({
13
+ contents: [
14
+ {
15
+ uri: "shoptet://orders/statuses",
16
+ mimeType: "application/json",
17
+ text: JSON.stringify(await shoptet("/api/orders/statuses"), null, 2),
18
+ },
19
+ ],
20
+ }));
21
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerCustomerTools(server: McpServer): void;
@@ -0,0 +1,20 @@
1
+ import { z } from "zod";
2
+ import { shoptet, buildQuery, text, safePath, pagingSchema, safeCall } from "../client.js";
3
+ export function registerCustomerTools(server) {
4
+ server.registerTool("list_customers", {
5
+ title: "List Customers",
6
+ description: "List registered customers with optional filtering by email or creation date.",
7
+ inputSchema: {
8
+ ...pagingSchema,
9
+ email: z.string().optional().describe("Filter by email address"),
10
+ creationTimeFrom: z.string().optional().describe("Created from date (YYYY-MM-DD)"),
11
+ },
12
+ annotations: { readOnlyHint: true },
13
+ }, (params) => safeCall(async () => text(await shoptet(`/api/customers${buildQuery(params)}`))));
14
+ server.registerTool("get_customer", {
15
+ title: "Get Customer Detail",
16
+ description: "Get full customer profile including contact info, addresses, and account details.",
17
+ inputSchema: { guid: z.string().describe("Customer GUID") },
18
+ annotations: { readOnlyHint: true },
19
+ }, ({ guid }) => safeCall(async () => text(await shoptet(`/api/customers/${safePath(guid)}`))));
20
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerDocumentTools(server: McpServer): void;
@@ -0,0 +1,91 @@
1
+ import { z } from "zod";
2
+ import { shoptet, buildQuery, text, safePath, pagingSchema, safeCall } from "../client.js";
3
+ export function registerDocumentTools(server) {
4
+ // ── Invoices ──────────────────────────────────────────
5
+ server.registerTool("list_invoices", {
6
+ title: "List Invoices",
7
+ description: "List invoices with optional date filtering. Returns invoice summaries with codes, amounts, and dates.",
8
+ inputSchema: {
9
+ ...pagingSchema,
10
+ creationTimeFrom: z.string().optional().describe("Created from date (YYYY-MM-DD)"),
11
+ creationTimeTo: z.string().optional().describe("Created to date (YYYY-MM-DD)"),
12
+ },
13
+ annotations: { readOnlyHint: true },
14
+ }, (params) => safeCall(async () => text(await shoptet(`/api/invoices${buildQuery(params)}`))));
15
+ server.registerTool("get_invoice", {
16
+ title: "Get Invoice Detail",
17
+ description: "Get full invoice detail including items, amounts, tax, and customer billing info.",
18
+ inputSchema: { code: z.string().describe("Invoice code") },
19
+ annotations: { readOnlyHint: true },
20
+ }, ({ code }) => safeCall(async () => text(await shoptet(`/api/invoices/${safePath(code)}`))));
21
+ server.registerTool("create_invoice_from_order", {
22
+ title: "Create Invoice from Order",
23
+ description: "Generate an invoice from an existing order.",
24
+ inputSchema: { orderCode: z.string().describe("Order code") },
25
+ annotations: { readOnlyHint: false, destructiveHint: false },
26
+ }, ({ orderCode }) => safeCall(async () => text(await shoptet(`/api/orders/${safePath(orderCode)}/invoice`, "POST"))));
27
+ server.registerTool("get_invoice_pdf", {
28
+ title: "Get Invoice PDF",
29
+ description: "Get invoice document as PDF.",
30
+ inputSchema: { code: z.string().describe("Invoice code") },
31
+ annotations: { readOnlyHint: true },
32
+ }, ({ code }) => safeCall(async () => text(await shoptet(`/api/invoices/${safePath(code)}/pdf`))));
33
+ // ── Proforma Invoices ─────────────────────────────────
34
+ server.registerTool("list_proforma_invoices", {
35
+ title: "List Proforma Invoices",
36
+ description: "List proforma (advance) invoices.",
37
+ inputSchema: pagingSchema,
38
+ annotations: { readOnlyHint: true },
39
+ }, (params) => safeCall(async () => text(await shoptet(`/api/proforma-invoices${buildQuery(params)}`))));
40
+ server.registerTool("get_proforma_invoice", {
41
+ title: "Get Proforma Invoice Detail",
42
+ description: "Get full proforma invoice detail.",
43
+ inputSchema: { code: z.string().describe("Proforma invoice code") },
44
+ annotations: { readOnlyHint: true },
45
+ }, ({ code }) => safeCall(async () => text(await shoptet(`/api/proforma-invoices/${safePath(code)}`))));
46
+ // ── Credit Notes ──────────────────────────────────────
47
+ server.registerTool("list_credit_notes", {
48
+ title: "List Credit Notes",
49
+ description: "List credit notes (refund documents).",
50
+ inputSchema: pagingSchema,
51
+ annotations: { readOnlyHint: true },
52
+ }, (params) => safeCall(async () => text(await shoptet(`/api/credit-notes${buildQuery(params)}`))));
53
+ server.registerTool("get_credit_note", {
54
+ title: "Get Credit Note Detail",
55
+ description: "Get full credit note detail including refunded items and amounts.",
56
+ inputSchema: { code: z.string().describe("Credit note code") },
57
+ annotations: { readOnlyHint: true },
58
+ }, ({ code }) => safeCall(async () => text(await shoptet(`/api/credit-notes/${safePath(code)}`))));
59
+ server.registerTool("create_credit_note_from_invoice", {
60
+ title: "Create Credit Note from Invoice",
61
+ description: "Create a credit note (refund) from an existing invoice.",
62
+ inputSchema: { invoiceCode: z.string().describe("Invoice code") },
63
+ annotations: { readOnlyHint: false, destructiveHint: false },
64
+ }, ({ invoiceCode }) => safeCall(async () => text(await shoptet(`/api/invoices/${safePath(invoiceCode)}/credit-note`, "POST"))));
65
+ // ── Delivery Notes ────────────────────────────────────
66
+ server.registerTool("list_delivery_notes", {
67
+ title: "List Delivery Notes",
68
+ description: "List delivery notes (shipping documents).",
69
+ inputSchema: pagingSchema,
70
+ annotations: { readOnlyHint: true },
71
+ }, (params) => safeCall(async () => text(await shoptet(`/api/delivery-notes${buildQuery(params)}`))));
72
+ server.registerTool("create_delivery_note", {
73
+ title: "Create Delivery Note from Order",
74
+ description: "Generate a delivery note from an existing order.",
75
+ inputSchema: { orderCode: z.string().describe("Order code") },
76
+ annotations: { readOnlyHint: false, destructiveHint: false },
77
+ }, ({ orderCode }) => safeCall(async () => text(await shoptet(`/api/orders/${safePath(orderCode)}/delivery-notes`, "POST"))));
78
+ // ── Proof of Payments ─────────────────────────────────
79
+ server.registerTool("list_proof_payments", {
80
+ title: "List Proof of Payments",
81
+ description: "List proof of payment documents.",
82
+ inputSchema: pagingSchema,
83
+ annotations: { readOnlyHint: true },
84
+ }, (params) => safeCall(async () => text(await shoptet(`/api/proof-payments${buildQuery(params)}`))));
85
+ server.registerTool("get_proof_payment", {
86
+ title: "Get Proof of Payment Detail",
87
+ description: "Get full proof of payment detail.",
88
+ inputSchema: { code: z.string().describe("Proof of payment code") },
89
+ annotations: { readOnlyHint: true },
90
+ }, ({ code }) => safeCall(async () => text(await shoptet(`/api/proof-payments/${safePath(code)}`))));
91
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerEshopTools(server: McpServer): void;
@@ -0,0 +1,8 @@
1
+ import { shoptet, text, safeCall } from "../client.js";
2
+ export function registerEshopTools(server) {
3
+ server.registerTool("get_eshop_info", {
4
+ title: "Get Eshop Info",
5
+ description: "Get basic information about the eshop: name, URL, contact info, currency, and settings.",
6
+ annotations: { readOnlyHint: true },
7
+ }, () => safeCall(async () => text(await shoptet("/api/eshop"))));
8
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerOrderTools(server: McpServer): void;
@@ -0,0 +1,71 @@
1
+ import { z } from "zod";
2
+ import { shoptet, buildQuery, text, safePath, pagingSchema, safeCall } from "../client.js";
3
+ export function registerOrderTools(server) {
4
+ server.registerTool("list_orders", {
5
+ title: "List Orders",
6
+ description: "List orders with optional filtering. Returns paginated results with order summaries including code, status, customer, and totals.",
7
+ inputSchema: {
8
+ ...pagingSchema,
9
+ statusId: z.number().optional().describe("Filter by status ID (use get_order_statuses to see available)"),
10
+ creationTimeFrom: z.string().optional().describe("Filter from date (YYYY-MM-DD HH:MM)"),
11
+ creationTimeTo: z.string().optional().describe("Filter to date (YYYY-MM-DD HH:MM)"),
12
+ code: z.string().optional().describe("Filter by order code"),
13
+ },
14
+ annotations: { readOnlyHint: true },
15
+ }, (params) => safeCall(async () => text(await shoptet(`/api/orders${buildQuery(params)}`))));
16
+ server.registerTool("get_order", {
17
+ title: "Get Order Detail",
18
+ description: "Get full order detail including items, shipping, payment, and customer info.",
19
+ inputSchema: { code: z.string().describe("Order code (e.g. '2024000123')") },
20
+ annotations: { readOnlyHint: true },
21
+ }, ({ code }) => safeCall(async () => text(await shoptet(`/api/orders/${safePath(code)}`))));
22
+ server.registerTool("create_order", {
23
+ title: "Create Order",
24
+ description: "Create a new order. Requires at minimum customer info and order items.",
25
+ inputSchema: { order: z.record(z.unknown()).describe("Order data object") },
26
+ annotations: { readOnlyHint: false, destructiveHint: false },
27
+ }, ({ order }) => safeCall(async () => text(await shoptet("/api/orders", "POST", order))));
28
+ server.registerTool("update_order_status", {
29
+ title: "Update Order Status",
30
+ description: "Change the status of an order. Use get_order_statuses to see available status IDs.",
31
+ inputSchema: {
32
+ code: z.string().describe("Order code"),
33
+ statusId: z.number().describe("New status ID"),
34
+ },
35
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
36
+ }, ({ code, statusId }) => safeCall(async () => text(await shoptet(`/api/orders/${safePath(code)}/status`, "PATCH", { statusId }))));
37
+ server.registerTool("get_order_statuses", {
38
+ title: "List Order Statuses",
39
+ description: "List all available order statuses with their IDs and names. Useful for filtering orders or updating order status.",
40
+ annotations: { readOnlyHint: true },
41
+ }, () => safeCall(async () => text(await shoptet("/api/orders/statuses"))));
42
+ server.registerTool("get_order_pdf", {
43
+ title: "Get Order PDF",
44
+ description: "Get order document as PDF. Returns a download URL or base64-encoded content.",
45
+ inputSchema: { code: z.string().describe("Order code") },
46
+ annotations: { readOnlyHint: true },
47
+ }, ({ code }) => safeCall(async () => text(await shoptet(`/api/orders/${safePath(code)}/pdf`))));
48
+ server.registerTool("get_order_history", {
49
+ title: "Get Order History",
50
+ description: "Get order history log and user remarks. Shows status changes, edits, and internal notes.",
51
+ inputSchema: { code: z.string().describe("Order code") },
52
+ annotations: { readOnlyHint: true },
53
+ }, ({ code }) => safeCall(async () => text(await shoptet(`/api/orders/${safePath(code)}/history`))));
54
+ server.registerTool("add_order_remark", {
55
+ title: "Add Order Remark",
56
+ description: "Add an internal remark/note to an order. Visible only in admin, not to customers.",
57
+ inputSchema: {
58
+ code: z.string().describe("Order code"),
59
+ remark: z.string().describe("Remark text"),
60
+ },
61
+ annotations: { readOnlyHint: false, destructiveHint: false },
62
+ }, ({ code, remark }) => safeCall(async () => text(await shoptet(`/api/orders/${safePath(code)}/history`, "POST", { remark }))));
63
+ server.registerTool("get_order_changes", {
64
+ title: "Get Order Changes",
65
+ description: "Get list of recently changed orders. Useful for syncing or monitoring order updates.",
66
+ inputSchema: {
67
+ lastChangeFrom: z.string().optional().describe("Changes from date (YYYY-MM-DD HH:MM)"),
68
+ },
69
+ annotations: { readOnlyHint: true },
70
+ }, (params) => safeCall(async () => text(await shoptet(`/api/orders/changes${buildQuery(params)}`))));
71
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerProductTools(server: McpServer): void;
@@ -0,0 +1,53 @@
1
+ import { z } from "zod";
2
+ import { shoptet, buildQuery, text, safePath, pagingSchema, safeCall } from "../client.js";
3
+ export function registerProductTools(server) {
4
+ server.registerTool("list_products", {
5
+ title: "List Products",
6
+ description: "List products with optional filtering by category, brand, or visibility. Returns paginated product summaries.",
7
+ inputSchema: {
8
+ ...pagingSchema,
9
+ categoryGuid: z.string().optional().describe("Filter by category GUID"),
10
+ brandCode: z.string().optional().describe("Filter by brand code"),
11
+ visibility: z.string().optional().describe("Filter by visibility (visible, hidden, etc.)"),
12
+ creationTimeFrom: z.string().optional().describe("Created from date (YYYY-MM-DD)"),
13
+ creationTimeTo: z.string().optional().describe("Created to date (YYYY-MM-DD)"),
14
+ },
15
+ annotations: { readOnlyHint: true },
16
+ }, (params) => safeCall(async () => text(await shoptet(`/api/products${buildQuery(params)}`))));
17
+ server.registerTool("get_product", {
18
+ title: "Get Product Detail",
19
+ description: "Get full product detail including variants, pricing, images, and descriptions.",
20
+ inputSchema: { guid: z.string().describe("Product GUID") },
21
+ annotations: { readOnlyHint: true },
22
+ }, ({ guid }) => safeCall(async () => text(await shoptet(`/api/products/${safePath(guid)}`))));
23
+ server.registerTool("get_product_changes", {
24
+ title: "Get Product Changes",
25
+ description: "Get list of recently changed products. Useful for syncing product catalog.",
26
+ inputSchema: { lastChangeFrom: z.string().optional().describe("Changes from date (YYYY-MM-DD)") },
27
+ annotations: { readOnlyHint: true },
28
+ }, (params) => safeCall(async () => text(await shoptet(`/api/products/changes${buildQuery(params)}`))));
29
+ server.registerTool("get_stock", {
30
+ title: "Get Product Stock",
31
+ description: "Get inventory/stock levels for a product across all warehouses.",
32
+ inputSchema: { guid: z.string().describe("Product GUID") },
33
+ annotations: { readOnlyHint: true },
34
+ }, ({ guid }) => safeCall(async () => text(await shoptet(`/api/products/${safePath(guid)}/stock`))));
35
+ server.registerTool("list_brands", {
36
+ title: "List Brands",
37
+ description: "List all product brands in the eshop.",
38
+ inputSchema: pagingSchema,
39
+ annotations: { readOnlyHint: true },
40
+ }, (params) => safeCall(async () => text(await shoptet(`/api/brands${buildQuery(params)}`))));
41
+ server.registerTool("create_brand", {
42
+ title: "Create Brand",
43
+ description: "Create a new product brand.",
44
+ inputSchema: { brand: z.record(z.unknown()).describe("Brand data object") },
45
+ annotations: { readOnlyHint: false, destructiveHint: false },
46
+ }, ({ brand }) => safeCall(async () => text(await shoptet("/api/brands", "POST", brand))));
47
+ server.registerTool("list_categories", {
48
+ title: "List Categories",
49
+ description: "List product categories with their hierarchy structure.",
50
+ inputSchema: pagingSchema,
51
+ annotations: { readOnlyHint: true },
52
+ }, (params) => safeCall(async () => text(await shoptet(`/api/categories${buildQuery(params)}`))));
53
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerWebhookTools(server: McpServer): void;
@@ -0,0 +1,24 @@
1
+ import { z } from "zod";
2
+ import { shoptet, text, safePath, safeCall } from "../client.js";
3
+ export function registerWebhookTools(server) {
4
+ server.registerTool("list_webhooks", {
5
+ title: "List Webhooks",
6
+ description: "List all registered webhooks and their event subscriptions.",
7
+ annotations: { readOnlyHint: true },
8
+ }, () => safeCall(async () => text(await shoptet("/api/webhooks"))));
9
+ server.registerTool("create_webhook", {
10
+ title: "Create Webhook",
11
+ description: "Register a new webhook to receive real-time notifications. Events: order:create, order:update, product:create, product:update, etc.",
12
+ inputSchema: {
13
+ event: z.string().describe("Event type (e.g. order:create, order:update, product:create)"),
14
+ url: z.string().describe("Callback URL that will receive POST notifications"),
15
+ },
16
+ annotations: { readOnlyHint: false, destructiveHint: false },
17
+ }, ({ event, url }) => safeCall(async () => text(await shoptet("/api/webhooks", "POST", { event, url }))));
18
+ server.registerTool("delete_webhook", {
19
+ title: "Delete Webhook",
20
+ description: "Remove a registered webhook by its ID.",
21
+ inputSchema: { id: z.number().describe("Webhook ID") },
22
+ annotations: { readOnlyHint: false, destructiveHint: true },
23
+ }, ({ id }) => safeCall(async () => text(await shoptet(`/api/webhooks/${safePath(id)}`, "DELETE"))));
24
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "shoptet-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for Shoptet e-commerce platform API — manage orders, products, customers, documents, and webhooks from AI assistants",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "shoptet-mcp": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "keywords": [
14
+ "mcp",
15
+ "shoptet",
16
+ "ecommerce",
17
+ "model-context-protocol",
18
+ "ai",
19
+ "claude"
20
+ ],
21
+ "author": "Tonda Kalina",
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/tomaskalina/shoptet-mcp.git"
26
+ },
27
+ "homepage": "https://github.com/tomaskalina/shoptet-mcp#readme",
28
+ "scripts": {
29
+ "build": "tsc",
30
+ "start": "node dist/index.js",
31
+ "dev": "tsc --watch",
32
+ "test": "vitest run",
33
+ "test:watch": "vitest",
34
+ "lint": "eslint src/",
35
+ "typecheck": "tsc --noEmit",
36
+ "check": "npm run typecheck && npm run lint && npm run test",
37
+ "prepublishOnly": "npm run check && npm run build"
38
+ },
39
+ "dependencies": {
40
+ "@modelcontextprotocol/sdk": "^1.12.1",
41
+ "zod": "^3.24.4"
42
+ },
43
+ "devDependencies": {
44
+ "@eslint/js": "^10.0.1",
45
+ "@types/node": "^22.15.3",
46
+ "eslint": "^10.1.0",
47
+ "typescript": "^5.8.3",
48
+ "typescript-eslint": "^8.58.0",
49
+ "vitest": "^4.1.2"
50
+ },
51
+ "packageManager": "yarn@3.8.1+sha512.8cfec856814c797ccb480703ca5270824327fac5abce240835e2699e01732229fd22bbeb1bb87047a0069f7698be9b2e3d9a926e6046e851faa9908fdacdeacf"
52
+ }