@keypro/cli 0.1.0 → 0.1.3
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/dist/index.js +211 -34
- package/dist/mcp-tools.d.ts +258 -0
- package/dist/mcp-tools.js +461 -0
- package/package.json +7 -1
package/dist/index.js
CHANGED
|
@@ -133,6 +133,15 @@ var KeyproClient = class {
|
|
|
133
133
|
orderKeys(id) {
|
|
134
134
|
return this.request("GET", `/api/v1/orders/${id}/keys`);
|
|
135
135
|
}
|
|
136
|
+
orderCancel(id) {
|
|
137
|
+
return this.request("POST", `/api/v1/orders/${id}/cancel`);
|
|
138
|
+
}
|
|
139
|
+
orderPaymentPreview(id, newMethod) {
|
|
140
|
+
return this.request("POST", `/api/v1/orders/${id}/payment/preview`, { newMethod });
|
|
141
|
+
}
|
|
142
|
+
orderChangePayment(id, body) {
|
|
143
|
+
return this.request("POST", `/api/v1/orders/${id}/payment`, body);
|
|
144
|
+
}
|
|
136
145
|
licenseKeys() {
|
|
137
146
|
return this.request("GET", "/api/v1/license-keys");
|
|
138
147
|
}
|
|
@@ -179,6 +188,9 @@ var KeyproClient = class {
|
|
|
179
188
|
if (type) qs.set("type", type);
|
|
180
189
|
return this.request("GET", `/api/v1/shipping/parcelshops?${qs}`);
|
|
181
190
|
}
|
|
191
|
+
exchangeRate() {
|
|
192
|
+
return this.request("GET", "/api/v1/exchange-rate");
|
|
193
|
+
}
|
|
182
194
|
};
|
|
183
195
|
function createClient(opts) {
|
|
184
196
|
return new KeyproClient(opts);
|
|
@@ -296,7 +308,13 @@ for gls_parcelshop also \`--parcelshop <ID>\`
|
|
|
296
308
|
## Queries
|
|
297
309
|
|
|
298
310
|
- \`keypro products search <query>\` / \`keypro products get <sku|id>\`
|
|
311
|
+
- \`keypro rate\` - current EUR/HUF rate the shop uses (net prices are stored in
|
|
312
|
+
EUR; HUF price = round(EUR * rate) to whole forint, EUR to 2 decimals)
|
|
299
313
|
- \`keypro order list [--status <status>]\` / \`keypro order get <id>\`
|
|
314
|
+
- \`keypro order cancel <id>\` - cancel an UNPAID order (bacs / stripe / cod;
|
|
315
|
+
NOT 8-day cheque or already-paid orders)
|
|
316
|
+
- \`keypro order change-payment <id> --payment <method> [--yes]\` - change an
|
|
317
|
+
unpaid order's payment method (preview first; wallet/stripe move money)
|
|
300
318
|
- \`keypro keys list [--order <id>]\` - delivered license keys
|
|
301
319
|
- \`keypro invoices list [--order <id>]\` / \`keypro invoices get <id>\`
|
|
302
320
|
(each invoice has a public \`downloadUrl\` PDF link)
|
|
@@ -309,7 +327,7 @@ for gls_parcelshop also \`--parcelshop <ID>\`
|
|
|
309
327
|
|
|
310
328
|
Register the CLI as a native MCP toolset (recommended for Claude Code):
|
|
311
329
|
|
|
312
|
-
claude mcp add keypro -- keypro mcp
|
|
330
|
+
claude mcp add keypro -- npx -y @keypro/cli mcp
|
|
313
331
|
|
|
314
332
|
Auth comes from KEYPRO_API_KEY / config; there is no login tool over MCP.
|
|
315
333
|
The keypro_order_create tool requires the confirmToken from
|
|
@@ -431,6 +449,8 @@ function eurFmt(value) {
|
|
|
431
449
|
// src/mcp.ts
|
|
432
450
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
433
451
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
452
|
+
|
|
453
|
+
// src/mcp-tools.ts
|
|
434
454
|
import { z } from "zod";
|
|
435
455
|
var addressShape = z.object({
|
|
436
456
|
firstName: z.string().optional(),
|
|
@@ -486,8 +506,7 @@ async function run(fn) {
|
|
|
486
506
|
return errorResult(err);
|
|
487
507
|
}
|
|
488
508
|
}
|
|
489
|
-
|
|
490
|
-
const server = new McpServer({ name: "keypro", version: "0.1.0" });
|
|
509
|
+
function registerKeyproTools(server, client) {
|
|
491
510
|
server.registerTool(
|
|
492
511
|
"keypro_whoami",
|
|
493
512
|
{
|
|
@@ -496,6 +515,14 @@ async function runMcpServer(client) {
|
|
|
496
515
|
},
|
|
497
516
|
() => run(() => client.me())
|
|
498
517
|
);
|
|
518
|
+
server.registerTool(
|
|
519
|
+
"keypro_exchange_rate",
|
|
520
|
+
{
|
|
521
|
+
description: "Current EUR->HUF exchange rate the shop uses and displays (ECB daily reference + 3% markup). Product net prices are stored in EUR; the HUF price = round(EUR * rate) to whole forint (EUR shown with 2 decimals). Call this to price products accurately in both currencies.",
|
|
522
|
+
inputSchema: {}
|
|
523
|
+
},
|
|
524
|
+
() => run(() => client.exchangeRate())
|
|
525
|
+
);
|
|
499
526
|
server.registerTool(
|
|
500
527
|
"keypro_products_search",
|
|
501
528
|
{
|
|
@@ -574,6 +601,44 @@ async function runMcpServer(client) {
|
|
|
574
601
|
},
|
|
575
602
|
(args) => run(() => client.orderKeys(args.orderId))
|
|
576
603
|
);
|
|
604
|
+
server.registerTool(
|
|
605
|
+
"keypro_order_cancel",
|
|
606
|
+
{
|
|
607
|
+
description: "Cancel an UNPAID order. Only orders awaiting payment can be cancelled: bacs (bank transfer / proforma), stripe (card checkout not completed), and cod (cash on delivery, still processing). 8-day-deferred (cheque) and already-paid orders CANNOT be cancelled. Idempotent.",
|
|
608
|
+
inputSchema: { orderId: z.number().int().positive() }
|
|
609
|
+
},
|
|
610
|
+
(args) => run(() => client.orderCancel(args.orderId))
|
|
611
|
+
);
|
|
612
|
+
server.registerTool(
|
|
613
|
+
"keypro_order_change_payment_preview",
|
|
614
|
+
{
|
|
615
|
+
description: "Preview changing an UNPAID order's payment method (only on-hold=bacs / pending=stripe orders qualify). Returns the recomputed totals for the new method (cheque adds +5%, cod adds a fixed fee; bacs/wallet/stripe add none) and a confirmToken. ALWAYS show the new totals to the user, then call keypro_order_change_payment.",
|
|
616
|
+
inputSchema: {
|
|
617
|
+
orderId: z.number().int().positive(),
|
|
618
|
+
newMethod: z.enum(["bacs", "cheque", "cod", "wallet", "stripe"])
|
|
619
|
+
}
|
|
620
|
+
},
|
|
621
|
+
(args) => run(() => client.orderPaymentPreview(args.orderId, args.newMethod))
|
|
622
|
+
);
|
|
623
|
+
server.registerTool(
|
|
624
|
+
"keypro_order_change_payment",
|
|
625
|
+
{
|
|
626
|
+
description: "Change an UNPAID order's payment method. Requires the confirmToken from keypro_order_change_payment_preview. wallet debits the KEP balance now and fulfils; cheque/cod add their fee and fulfil (final invoice + keys where due); bacs issues a proforma (awaits transfer); stripe charges the saved card or returns a payment link in payment.paymentUrl. Pass cardId (pm_...) to pick a specific card for stripe.",
|
|
627
|
+
inputSchema: {
|
|
628
|
+
orderId: z.number().int().positive(),
|
|
629
|
+
newMethod: z.enum(["bacs", "cheque", "cod", "wallet", "stripe"]),
|
|
630
|
+
confirmToken: z.string().describe("Token from keypro_order_change_payment_preview"),
|
|
631
|
+
cardId: z.string().optional().describe("Saved card id (pm_...) for stripe")
|
|
632
|
+
}
|
|
633
|
+
},
|
|
634
|
+
(args) => run(
|
|
635
|
+
() => client.orderChangePayment(args.orderId, {
|
|
636
|
+
newMethod: args.newMethod,
|
|
637
|
+
confirmToken: args.confirmToken,
|
|
638
|
+
cardId: args.cardId
|
|
639
|
+
})
|
|
640
|
+
)
|
|
641
|
+
);
|
|
577
642
|
server.registerTool(
|
|
578
643
|
"keypro_license_keys",
|
|
579
644
|
{
|
|
@@ -648,15 +713,72 @@ async function runMcpServer(client) {
|
|
|
648
713
|
},
|
|
649
714
|
(args) => run(() => client.parcelshopsSearch(args.q, args.type))
|
|
650
715
|
);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// src/mcp.ts
|
|
719
|
+
async function runMcpServer(client) {
|
|
720
|
+
const server = new McpServer({ name: "keypro", version: "0.1.3" });
|
|
721
|
+
registerKeyproTools(server, client);
|
|
651
722
|
const transport = new StdioServerTransport();
|
|
652
723
|
await server.connect(transport);
|
|
653
724
|
}
|
|
654
725
|
|
|
726
|
+
// src/prompt.ts
|
|
727
|
+
var ESC = String.fromCharCode(27);
|
|
728
|
+
var BRACKETED_PASTE = new RegExp(ESC + "\\[20[01]~", "g");
|
|
729
|
+
function consumeHiddenChunk(current, chunk) {
|
|
730
|
+
const s = chunk.replace(BRACKETED_PASTE, "");
|
|
731
|
+
let value = current;
|
|
732
|
+
for (const ch of s) {
|
|
733
|
+
const code = ch.charCodeAt(0);
|
|
734
|
+
if (code === 10 || code === 13 || code === 4) {
|
|
735
|
+
return { value, status: "submit" };
|
|
736
|
+
}
|
|
737
|
+
if (code === 3) {
|
|
738
|
+
return { value, status: "cancel" };
|
|
739
|
+
}
|
|
740
|
+
if (code === 127 || code === 8) {
|
|
741
|
+
value = value.slice(0, -1);
|
|
742
|
+
} else {
|
|
743
|
+
value += ch;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return { value, status: "typing" };
|
|
747
|
+
}
|
|
748
|
+
async function promptHidden(question) {
|
|
749
|
+
process.stdout.write(question);
|
|
750
|
+
return new Promise((resolve, reject) => {
|
|
751
|
+
const stdin = process.stdin;
|
|
752
|
+
let value = "";
|
|
753
|
+
const wasRaw = stdin.isRaw;
|
|
754
|
+
if (stdin.isTTY) stdin.setRawMode(true);
|
|
755
|
+
stdin.resume();
|
|
756
|
+
const stop = () => {
|
|
757
|
+
stdin.off("data", onData);
|
|
758
|
+
if (stdin.isTTY) stdin.setRawMode(wasRaw ?? false);
|
|
759
|
+
stdin.pause();
|
|
760
|
+
process.stdout.write("\n");
|
|
761
|
+
};
|
|
762
|
+
const onData = (chunk) => {
|
|
763
|
+
const res = consumeHiddenChunk(value, chunk.toString("utf8"));
|
|
764
|
+
value = res.value;
|
|
765
|
+
if (res.status === "submit") {
|
|
766
|
+
stop();
|
|
767
|
+
resolve(value);
|
|
768
|
+
} else if (res.status === "cancel") {
|
|
769
|
+
stop();
|
|
770
|
+
reject(new Error("Megszak\xEDtva."));
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
stdin.on("data", onData);
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
|
|
655
777
|
// src/index.ts
|
|
656
778
|
var program = new Command();
|
|
657
779
|
program.name("keypro").description(
|
|
658
780
|
"KeyPro.hu B2B licencshop CLI - rendeles, szamlak, term\xE9kkulcsok, profil. AI-agent utmutato: keypro agent-docs"
|
|
659
|
-
).version("0.1.
|
|
781
|
+
).version("0.1.3").option("--json", "gepi (JSON) kimenet a stdout-ra", false).option("--api-key <kulcs>", "API kulcs (felulirja az env/config erteket)").option("--api-base <url>", "API kiszolgalo cime (alap: eles bolt)").hook("preAction", (thisCommand) => {
|
|
660
782
|
setJsonMode(Boolean(thisCommand.optsWithGlobals().json));
|
|
661
783
|
});
|
|
662
784
|
function resolved(cmd) {
|
|
@@ -673,36 +795,6 @@ function clientFor(cmd, requireKey = true) {
|
|
|
673
795
|
}
|
|
674
796
|
return createClient({ apiBase: cfg.apiBase, apiKey: cfg.apiKey });
|
|
675
797
|
}
|
|
676
|
-
async function promptHidden(question) {
|
|
677
|
-
process.stdout.write(question);
|
|
678
|
-
return new Promise((resolve, reject) => {
|
|
679
|
-
const stdin = process.stdin;
|
|
680
|
-
let value = "";
|
|
681
|
-
const wasRaw = stdin.isRaw;
|
|
682
|
-
if (stdin.isTTY) stdin.setRawMode(true);
|
|
683
|
-
stdin.resume();
|
|
684
|
-
const onData = (chunk) => {
|
|
685
|
-
const ch = chunk.toString("utf8");
|
|
686
|
-
if (ch === "\n" || ch === "\r" || ch === "") {
|
|
687
|
-
stdin.off("data", onData);
|
|
688
|
-
if (stdin.isTTY) stdin.setRawMode(wasRaw ?? false);
|
|
689
|
-
stdin.pause();
|
|
690
|
-
process.stdout.write("\n");
|
|
691
|
-
resolve(value);
|
|
692
|
-
} else if (ch === "") {
|
|
693
|
-
stdin.off("data", onData);
|
|
694
|
-
if (stdin.isTTY) stdin.setRawMode(wasRaw ?? false);
|
|
695
|
-
process.stdout.write("\n");
|
|
696
|
-
reject(new Error("Megszak\xEDtva."));
|
|
697
|
-
} else if (ch === "\x7F" || ch === "\b") {
|
|
698
|
-
value = value.slice(0, -1);
|
|
699
|
-
} else {
|
|
700
|
-
value += ch;
|
|
701
|
-
}
|
|
702
|
-
};
|
|
703
|
-
stdin.on("data", onData);
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
798
|
async function promptText(question) {
|
|
707
799
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
708
800
|
try {
|
|
@@ -1154,6 +1246,75 @@ order.command("get <id>").description("rendeles reszletei").action(async (id, _o
|
|
|
1154
1246
|
fail(err);
|
|
1155
1247
|
}
|
|
1156
1248
|
});
|
|
1249
|
+
order.command("cancel <id>").description("fizetetlen rendeles visszamondasa (torlese)").action(async (id, _opts, cmd) => {
|
|
1250
|
+
try {
|
|
1251
|
+
const result = await clientFor(cmd).orderCancel(Number(id));
|
|
1252
|
+
output(result, () => {
|
|
1253
|
+
const o = result.order;
|
|
1254
|
+
printKV([
|
|
1255
|
+
["Rendel\xE9s", `#${o.number} (id: ${o.id})`],
|
|
1256
|
+
["\xC1llapot", String(o.statusLabel)]
|
|
1257
|
+
]);
|
|
1258
|
+
process.stdout.write(`
|
|
1259
|
+
${result.note}
|
|
1260
|
+
`);
|
|
1261
|
+
});
|
|
1262
|
+
} catch (err) {
|
|
1263
|
+
fail(err);
|
|
1264
|
+
}
|
|
1265
|
+
});
|
|
1266
|
+
order.command("change-payment <id>").description("fizetetlen rendeles fizetesi modjanak modositasa (elonezet; --yes nelkul nem valt)").requiredOption("--payment <mod>", "uj fizetesi mod: bacs|cheque|cod|wallet|stripe").option("--card <pm_id>", "mentett kartya id (stripe-hoz, opcionalis)").option("--yes", "a valtas tenyleges vegrehajtasa", false).action(
|
|
1267
|
+
async (id, opts, cmd) => {
|
|
1268
|
+
try {
|
|
1269
|
+
const client = clientFor(cmd);
|
|
1270
|
+
const preview = await client.orderPaymentPreview(Number(id), opts.payment);
|
|
1271
|
+
if (!opts.yes) {
|
|
1272
|
+
output(
|
|
1273
|
+
{ preview, hint: "Add hozz\xE1 a --yes kapcsol\xF3t a m\xF3dos\xEDt\xE1shoz." },
|
|
1274
|
+
() => {
|
|
1275
|
+
printKV([
|
|
1276
|
+
["Jelenlegi m\xF3d", preview.currentMethod],
|
|
1277
|
+
["\xDAj m\xF3d", preview.newMethod],
|
|
1278
|
+
["\xDAj nett\xF3", eurFmt(preview.newTotals.netTotalEur)],
|
|
1279
|
+
["\xDAj brutt\xF3", eurFmt(preview.newTotals.grossTotalEur)],
|
|
1280
|
+
["D\xEDj-v\xE1ltoz\xE1s (brutt\xF3)", eurFmt(preview.feeDeltaEur)],
|
|
1281
|
+
[
|
|
1282
|
+
"KEP fedezet",
|
|
1283
|
+
preview.wallet ? preview.wallet.sufficient ? "el\xE9g" : "NEM el\xE9g" : null
|
|
1284
|
+
]
|
|
1285
|
+
]);
|
|
1286
|
+
process.stdout.write(
|
|
1287
|
+
"\nEz csak el\u0151n\xE9zet. Add hozz\xE1 a --yes kapcsol\xF3t a m\xF3dos\xEDt\xE1shoz.\n"
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
);
|
|
1291
|
+
process.exit(1);
|
|
1292
|
+
}
|
|
1293
|
+
const result = await client.orderChangePayment(Number(id), {
|
|
1294
|
+
newMethod: opts.payment,
|
|
1295
|
+
confirmToken: preview.confirmToken,
|
|
1296
|
+
cardId: opts.card
|
|
1297
|
+
});
|
|
1298
|
+
output(result, () => {
|
|
1299
|
+
const o = result.order;
|
|
1300
|
+
const p = result.payment;
|
|
1301
|
+
printKV([
|
|
1302
|
+
["Rendel\xE9s", `#${o.number} (id: ${o.id})`],
|
|
1303
|
+
["\xC1llapot", String(o.statusLabel)],
|
|
1304
|
+
["Fizet\xE9si m\xF3d", String(o.paymentMethodLabel)],
|
|
1305
|
+
["Fizet\xE9s", String(p.note)],
|
|
1306
|
+
["Fizet\xE9si link", p.paymentUrl ? String(p.paymentUrl) : null],
|
|
1307
|
+
[
|
|
1308
|
+
"KEP egyenleg",
|
|
1309
|
+
p.walletBalanceAfterEur !== void 0 ? eurFmt(Number(p.walletBalanceAfterEur)) : null
|
|
1310
|
+
]
|
|
1311
|
+
]);
|
|
1312
|
+
});
|
|
1313
|
+
} catch (err) {
|
|
1314
|
+
fail(err);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
);
|
|
1157
1318
|
var keysCmd = program.command("keys").description("term\xE9kkulcsok");
|
|
1158
1319
|
keysCmd.command("list").description("kezbesitett term\xE9kkulcsok (osszes vagy egy rendelese)").option("--order <id>", "csak az adott rendeles kulcsai").action(async (opts, cmd) => {
|
|
1159
1320
|
try {
|
|
@@ -1369,6 +1530,22 @@ profile.command("set <mezo=ertek...>").description(
|
|
|
1369
1530
|
fail(err);
|
|
1370
1531
|
}
|
|
1371
1532
|
});
|
|
1533
|
+
program.command("rate").description("Aktu\xE1lis EUR/HUF \xE1rfolyam (amit a webshop haszn\xE1l \xE9s megjelen\xEDt)").action(async (_opts, cmd) => {
|
|
1534
|
+
try {
|
|
1535
|
+
const result = await clientFor(cmd).exchangeRate();
|
|
1536
|
+
output(result, () => {
|
|
1537
|
+
printKV([
|
|
1538
|
+
["EUR -> HUF", `${result.rate.toFixed(2)} Ft`],
|
|
1539
|
+
["ECB referencia (fel\xE1r n\xE9lk\xFCl)", `${result.referenceRate.toFixed(2)} Ft`],
|
|
1540
|
+
["Fel\xE1r", `${result.markupPct}%`],
|
|
1541
|
+
["1 HUF -> EUR", result.hufToEur.toFixed(6)],
|
|
1542
|
+
["Forr\xE1s", result.source]
|
|
1543
|
+
]);
|
|
1544
|
+
});
|
|
1545
|
+
} catch (err) {
|
|
1546
|
+
fail(err);
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1372
1549
|
var wallet = program.command("wallet").description("KEP egyenleg");
|
|
1373
1550
|
wallet.action(async (_opts, cmd) => {
|
|
1374
1551
|
try {
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Vekony HTTP kliens a KeyPro /api/v1 vegpontokhoz (nativ fetch).
|
|
5
|
+
* Minden uzleti logika a szerveren el; itt csak a boritek ({ ok, data |
|
|
6
|
+
* error }) kibontasa es a hibak tipusositasa tortenik. A parancsok ES az
|
|
7
|
+
* MCP szerver is ezt a klienst hasznalja.
|
|
8
|
+
*/
|
|
9
|
+
declare class KeyproApiError extends Error {
|
|
10
|
+
status: number;
|
|
11
|
+
code: string;
|
|
12
|
+
details?: unknown | undefined;
|
|
13
|
+
constructor(status: number, code: string, message: string, details?: unknown | undefined);
|
|
14
|
+
}
|
|
15
|
+
interface OrderItemInput {
|
|
16
|
+
sku?: string;
|
|
17
|
+
productId?: number;
|
|
18
|
+
qty: number;
|
|
19
|
+
}
|
|
20
|
+
interface AddressInput {
|
|
21
|
+
firstName?: string;
|
|
22
|
+
lastName?: string;
|
|
23
|
+
company?: string;
|
|
24
|
+
address1?: string;
|
|
25
|
+
address2?: string;
|
|
26
|
+
city?: string;
|
|
27
|
+
postcode?: string;
|
|
28
|
+
state?: string;
|
|
29
|
+
country?: string;
|
|
30
|
+
email?: string;
|
|
31
|
+
phone?: string;
|
|
32
|
+
}
|
|
33
|
+
interface OrderRequestInput {
|
|
34
|
+
items: OrderItemInput[];
|
|
35
|
+
paymentMethod: "bacs" | "cheque" | "cod" | "wallet" | "stripe";
|
|
36
|
+
shippingMethodId?: "gls_hd" | "gls_parcelshop" | "combine_free";
|
|
37
|
+
parcelshopId?: string;
|
|
38
|
+
couponCode?: string;
|
|
39
|
+
currency?: "EUR" | "HUF";
|
|
40
|
+
billing?: AddressInput;
|
|
41
|
+
shipping?: AddressInput;
|
|
42
|
+
taxNumber?: string;
|
|
43
|
+
cardId?: string;
|
|
44
|
+
}
|
|
45
|
+
interface KeyproClientOptions {
|
|
46
|
+
apiBase: string;
|
|
47
|
+
apiKey?: string | null;
|
|
48
|
+
}
|
|
49
|
+
declare class KeyproClient {
|
|
50
|
+
private readonly opts;
|
|
51
|
+
constructor(opts: KeyproClientOptions);
|
|
52
|
+
private request;
|
|
53
|
+
login(email: string, password: string, name?: string): Promise<{
|
|
54
|
+
token: string;
|
|
55
|
+
keyId: number;
|
|
56
|
+
prefix: string;
|
|
57
|
+
scopes: string[];
|
|
58
|
+
name: string;
|
|
59
|
+
}>;
|
|
60
|
+
me(): Promise<{
|
|
61
|
+
id: number;
|
|
62
|
+
email: string;
|
|
63
|
+
companyName: string | null;
|
|
64
|
+
firstName: string | null;
|
|
65
|
+
role: string;
|
|
66
|
+
walletBalanceEurNet: number;
|
|
67
|
+
key: {
|
|
68
|
+
id: number;
|
|
69
|
+
prefix: string;
|
|
70
|
+
name: string;
|
|
71
|
+
scopes: string[];
|
|
72
|
+
};
|
|
73
|
+
}>;
|
|
74
|
+
keysList(): Promise<{
|
|
75
|
+
keys: Array<Record<string, unknown>>;
|
|
76
|
+
}>;
|
|
77
|
+
keyRevoke(keyId: number): Promise<{
|
|
78
|
+
revoked: boolean;
|
|
79
|
+
keyId: number;
|
|
80
|
+
}>;
|
|
81
|
+
productsSearch(params: {
|
|
82
|
+
q?: string;
|
|
83
|
+
category?: string;
|
|
84
|
+
onSale?: boolean;
|
|
85
|
+
sort?: string;
|
|
86
|
+
limit?: number;
|
|
87
|
+
offset?: number;
|
|
88
|
+
}): Promise<{
|
|
89
|
+
total: number;
|
|
90
|
+
products: Array<Record<string, unknown>>;
|
|
91
|
+
}>;
|
|
92
|
+
productGet(key: string): Promise<Record<string, unknown>>;
|
|
93
|
+
orderPreview(req: OrderRequestInput): Promise<{
|
|
94
|
+
lines: Array<Record<string, unknown>>;
|
|
95
|
+
totals: Record<string, number>;
|
|
96
|
+
payment: Record<string, unknown>;
|
|
97
|
+
shipping: Record<string, unknown> | null;
|
|
98
|
+
coupon: Record<string, unknown> | null;
|
|
99
|
+
currency: string;
|
|
100
|
+
eurRate: number;
|
|
101
|
+
displayGrossTotal: number;
|
|
102
|
+
wallet: {
|
|
103
|
+
balanceEurNet: number;
|
|
104
|
+
sufficient: boolean;
|
|
105
|
+
} | null;
|
|
106
|
+
confirmToken: string;
|
|
107
|
+
confirmTokenExpiresAt: string;
|
|
108
|
+
}>;
|
|
109
|
+
orderCreate(req: OrderRequestInput & {
|
|
110
|
+
confirmToken: string;
|
|
111
|
+
}, idempotencyKey?: string): Promise<{
|
|
112
|
+
order: Record<string, unknown>;
|
|
113
|
+
invoices: Array<Record<string, unknown>>;
|
|
114
|
+
deliveredKeyCount: number;
|
|
115
|
+
payment: {
|
|
116
|
+
method: string;
|
|
117
|
+
charged: boolean;
|
|
118
|
+
paymentUrl?: string;
|
|
119
|
+
declineCode?: string | null;
|
|
120
|
+
walletBalanceAfterEur?: number;
|
|
121
|
+
note: string;
|
|
122
|
+
};
|
|
123
|
+
idempotentReplay: boolean;
|
|
124
|
+
}>;
|
|
125
|
+
ordersList(params: {
|
|
126
|
+
status?: string;
|
|
127
|
+
limit?: number;
|
|
128
|
+
offset?: number;
|
|
129
|
+
}): Promise<{
|
|
130
|
+
orders: Array<Record<string, unknown>>;
|
|
131
|
+
}>;
|
|
132
|
+
orderGet(id: number): Promise<{
|
|
133
|
+
order: Record<string, unknown>;
|
|
134
|
+
invoices: Array<Record<string, unknown>>;
|
|
135
|
+
paymentUrl: string | null;
|
|
136
|
+
}>;
|
|
137
|
+
orderKeys(id: number): Promise<{
|
|
138
|
+
orderId: number;
|
|
139
|
+
orderStatus: string;
|
|
140
|
+
keys: Array<{
|
|
141
|
+
productId: number;
|
|
142
|
+
productName: string;
|
|
143
|
+
keyValue: string;
|
|
144
|
+
deliveredAt: string | null;
|
|
145
|
+
}>;
|
|
146
|
+
licenses: Array<Record<string, unknown>>;
|
|
147
|
+
}>;
|
|
148
|
+
orderCancel(id: number): Promise<{
|
|
149
|
+
order: Record<string, unknown>;
|
|
150
|
+
invoices: Array<Record<string, unknown>>;
|
|
151
|
+
cancelled: boolean;
|
|
152
|
+
alreadyCancelled: boolean;
|
|
153
|
+
note: string;
|
|
154
|
+
}>;
|
|
155
|
+
orderPaymentPreview(id: number, newMethod: string): Promise<{
|
|
156
|
+
currentMethod: string;
|
|
157
|
+
newMethod: string;
|
|
158
|
+
newTotals: {
|
|
159
|
+
netTotalEur: number;
|
|
160
|
+
grossTotalEur: number;
|
|
161
|
+
};
|
|
162
|
+
feeDeltaEur: number;
|
|
163
|
+
fees: Array<{
|
|
164
|
+
label: string;
|
|
165
|
+
netEur: number;
|
|
166
|
+
}>;
|
|
167
|
+
confirmToken: string;
|
|
168
|
+
confirmTokenExpiresAt: string;
|
|
169
|
+
wallet: {
|
|
170
|
+
balanceEurNet: number;
|
|
171
|
+
sufficient: boolean;
|
|
172
|
+
} | null;
|
|
173
|
+
}>;
|
|
174
|
+
orderChangePayment(id: number, body: {
|
|
175
|
+
newMethod: string;
|
|
176
|
+
confirmToken: string;
|
|
177
|
+
cardId?: string;
|
|
178
|
+
}): Promise<{
|
|
179
|
+
order: Record<string, unknown>;
|
|
180
|
+
invoices: Array<Record<string, unknown>>;
|
|
181
|
+
payment: Record<string, unknown>;
|
|
182
|
+
}>;
|
|
183
|
+
licenseKeys(): Promise<{
|
|
184
|
+
products: Array<{
|
|
185
|
+
productId: number;
|
|
186
|
+
productName: string;
|
|
187
|
+
keys: Array<Record<string, unknown>>;
|
|
188
|
+
}>;
|
|
189
|
+
}>;
|
|
190
|
+
invoicesList(params: {
|
|
191
|
+
orderId?: number;
|
|
192
|
+
limit?: number;
|
|
193
|
+
offset?: number;
|
|
194
|
+
}): Promise<{
|
|
195
|
+
invoices: Array<Record<string, unknown>>;
|
|
196
|
+
}>;
|
|
197
|
+
invoiceGet(id: number): Promise<{
|
|
198
|
+
invoice: Record<string, unknown>;
|
|
199
|
+
}>;
|
|
200
|
+
profileGet(): Promise<{
|
|
201
|
+
profile: Record<string, unknown>;
|
|
202
|
+
}>;
|
|
203
|
+
profileUpdate(patch: Record<string, unknown>): Promise<{
|
|
204
|
+
updated: string[];
|
|
205
|
+
profile: Record<string, unknown>;
|
|
206
|
+
}>;
|
|
207
|
+
wallet(params?: {
|
|
208
|
+
limit?: number;
|
|
209
|
+
offset?: number;
|
|
210
|
+
}): Promise<{
|
|
211
|
+
balanceEurNet: number;
|
|
212
|
+
transactions: Array<Record<string, unknown>>;
|
|
213
|
+
}>;
|
|
214
|
+
cardsList(): Promise<{
|
|
215
|
+
stripeEnabled: boolean;
|
|
216
|
+
cards: Array<{
|
|
217
|
+
id: string;
|
|
218
|
+
brand: string;
|
|
219
|
+
last4: string;
|
|
220
|
+
expMonth: number;
|
|
221
|
+
expYear: number;
|
|
222
|
+
isDefault: boolean;
|
|
223
|
+
}>;
|
|
224
|
+
}>;
|
|
225
|
+
parcelshopsSearch(q: string, type?: string): Promise<{
|
|
226
|
+
truncated: boolean;
|
|
227
|
+
parcelshops: Array<Record<string, unknown>>;
|
|
228
|
+
}>;
|
|
229
|
+
exchangeRate(): Promise<{
|
|
230
|
+
base: string;
|
|
231
|
+
quote: string;
|
|
232
|
+
rate: number;
|
|
233
|
+
eurToHuf: number;
|
|
234
|
+
hufToEur: number;
|
|
235
|
+
referenceRate: number;
|
|
236
|
+
markupPct: number;
|
|
237
|
+
source: string;
|
|
238
|
+
rounding: {
|
|
239
|
+
HUF: number;
|
|
240
|
+
EUR: number;
|
|
241
|
+
};
|
|
242
|
+
note: string;
|
|
243
|
+
}>;
|
|
244
|
+
}
|
|
245
|
+
declare function createClient(opts: KeyproClientOptions): KeyproClient;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Megosztott MCP tool-registry: a KeyPro muveletek nativ MCP tool-kent, ugyanazon
|
|
249
|
+
* a KeyproClient-en keresztul. Ezt hasznalja a CLI stdio szerver (mcp.ts) ES a
|
|
250
|
+
* webes tavoli MCP route (src/app/mcp/route.ts) - igy a tool-definiciok nem
|
|
251
|
+
* csusznak szet. A McpServer csak tipuskent kell (type-only import), igy ez a
|
|
252
|
+
* modul nem huzza be a stdio transportot a webes buildbe.
|
|
253
|
+
*/
|
|
254
|
+
|
|
255
|
+
/** A KeyPro MCP tool-jait regisztralja a megadott McpServer-re. */
|
|
256
|
+
declare function registerKeyproTools(server: McpServer, client: KeyproClient): void;
|
|
257
|
+
|
|
258
|
+
export { KeyproApiError, KeyproClient, type KeyproClientOptions, createClient, registerKeyproTools };
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
// src/mcp-tools.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
// src/client.ts
|
|
5
|
+
var KeyproApiError = class extends Error {
|
|
6
|
+
constructor(status, code, message, details) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.details = details;
|
|
11
|
+
this.name = "KeyproApiError";
|
|
12
|
+
}
|
|
13
|
+
status;
|
|
14
|
+
code;
|
|
15
|
+
details;
|
|
16
|
+
};
|
|
17
|
+
var KeyproClient = class {
|
|
18
|
+
constructor(opts) {
|
|
19
|
+
this.opts = opts;
|
|
20
|
+
}
|
|
21
|
+
opts;
|
|
22
|
+
async request(method, path, body, extraHeaders) {
|
|
23
|
+
const headers = {
|
|
24
|
+
accept: "application/json",
|
|
25
|
+
...extraHeaders
|
|
26
|
+
};
|
|
27
|
+
if (this.opts.apiKey) headers.authorization = `Bearer ${this.opts.apiKey}`;
|
|
28
|
+
if (body !== void 0) headers["content-type"] = "application/json";
|
|
29
|
+
let response;
|
|
30
|
+
try {
|
|
31
|
+
response = await fetch(`${this.opts.apiBase}${path}`, {
|
|
32
|
+
method,
|
|
33
|
+
headers,
|
|
34
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
35
|
+
});
|
|
36
|
+
} catch (err) {
|
|
37
|
+
throw new KeyproApiError(
|
|
38
|
+
0,
|
|
39
|
+
"network_error",
|
|
40
|
+
`Nem siker\xFClt el\xE9rni a szervert (${this.opts.apiBase}): ${err instanceof Error ? err.message : String(err)}`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
let payload;
|
|
44
|
+
try {
|
|
45
|
+
payload = await response.json();
|
|
46
|
+
} catch {
|
|
47
|
+
throw new KeyproApiError(
|
|
48
|
+
response.status,
|
|
49
|
+
"invalid_response",
|
|
50
|
+
`A szerver nem JSON v\xE1laszt adott (HTTP ${response.status}). J\xF3 az api-base be\xE1ll\xEDt\xE1s?`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const envelope = payload;
|
|
54
|
+
if (envelope.ok === true && envelope.data !== void 0) {
|
|
55
|
+
return envelope.data;
|
|
56
|
+
}
|
|
57
|
+
const error = envelope.error ?? {};
|
|
58
|
+
throw new KeyproApiError(
|
|
59
|
+
response.status,
|
|
60
|
+
error.code ?? "unknown_error",
|
|
61
|
+
error.message ?? `Ismeretlen hiba (HTTP ${response.status}).`,
|
|
62
|
+
error.details
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
// --- Auth / kulcsok ---
|
|
66
|
+
login(email, password, name) {
|
|
67
|
+
return this.request("POST", "/api/v1/auth/login", { email, password, name });
|
|
68
|
+
}
|
|
69
|
+
me() {
|
|
70
|
+
return this.request("GET", "/api/v1/me");
|
|
71
|
+
}
|
|
72
|
+
keysList() {
|
|
73
|
+
return this.request(
|
|
74
|
+
"GET",
|
|
75
|
+
"/api/v1/keys"
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
keyRevoke(keyId) {
|
|
79
|
+
return this.request(
|
|
80
|
+
"DELETE",
|
|
81
|
+
`/api/v1/keys/${keyId}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
// --- Termekek ---
|
|
85
|
+
productsSearch(params) {
|
|
86
|
+
const qs = new URLSearchParams();
|
|
87
|
+
if (params.q) qs.set("q", params.q);
|
|
88
|
+
if (params.category) qs.set("category", params.category);
|
|
89
|
+
if (params.onSale) qs.set("on_sale", "true");
|
|
90
|
+
if (params.sort) qs.set("sort", params.sort);
|
|
91
|
+
if (params.limit) qs.set("limit", String(params.limit));
|
|
92
|
+
if (params.offset) qs.set("offset", String(params.offset));
|
|
93
|
+
const suffix = qs.size > 0 ? `?${qs}` : "";
|
|
94
|
+
return this.request("GET", `/api/v1/products${suffix}`);
|
|
95
|
+
}
|
|
96
|
+
productGet(key) {
|
|
97
|
+
return this.request(
|
|
98
|
+
"GET",
|
|
99
|
+
`/api/v1/products/${encodeURIComponent(key)}`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
// --- Rendelesek ---
|
|
103
|
+
orderPreview(req) {
|
|
104
|
+
return this.request("POST", "/api/v1/orders/preview", req);
|
|
105
|
+
}
|
|
106
|
+
orderCreate(req, idempotencyKey) {
|
|
107
|
+
return this.request(
|
|
108
|
+
"POST",
|
|
109
|
+
"/api/v1/orders",
|
|
110
|
+
req,
|
|
111
|
+
idempotencyKey ? { "idempotency-key": idempotencyKey } : void 0
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
ordersList(params) {
|
|
115
|
+
const qs = new URLSearchParams();
|
|
116
|
+
if (params.status) qs.set("status", params.status);
|
|
117
|
+
if (params.limit) qs.set("limit", String(params.limit));
|
|
118
|
+
if (params.offset) qs.set("offset", String(params.offset));
|
|
119
|
+
const suffix = qs.size > 0 ? `?${qs}` : "";
|
|
120
|
+
return this.request(
|
|
121
|
+
"GET",
|
|
122
|
+
`/api/v1/orders${suffix}`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
orderGet(id) {
|
|
126
|
+
return this.request("GET", `/api/v1/orders/${id}`);
|
|
127
|
+
}
|
|
128
|
+
orderKeys(id) {
|
|
129
|
+
return this.request("GET", `/api/v1/orders/${id}/keys`);
|
|
130
|
+
}
|
|
131
|
+
orderCancel(id) {
|
|
132
|
+
return this.request("POST", `/api/v1/orders/${id}/cancel`);
|
|
133
|
+
}
|
|
134
|
+
orderPaymentPreview(id, newMethod) {
|
|
135
|
+
return this.request("POST", `/api/v1/orders/${id}/payment/preview`, { newMethod });
|
|
136
|
+
}
|
|
137
|
+
orderChangePayment(id, body) {
|
|
138
|
+
return this.request("POST", `/api/v1/orders/${id}/payment`, body);
|
|
139
|
+
}
|
|
140
|
+
licenseKeys() {
|
|
141
|
+
return this.request("GET", "/api/v1/license-keys");
|
|
142
|
+
}
|
|
143
|
+
// --- Szamlak ---
|
|
144
|
+
invoicesList(params) {
|
|
145
|
+
const qs = new URLSearchParams();
|
|
146
|
+
if (params.orderId) qs.set("order_id", String(params.orderId));
|
|
147
|
+
if (params.limit) qs.set("limit", String(params.limit));
|
|
148
|
+
if (params.offset) qs.set("offset", String(params.offset));
|
|
149
|
+
const suffix = qs.size > 0 ? `?${qs}` : "";
|
|
150
|
+
return this.request(
|
|
151
|
+
"GET",
|
|
152
|
+
`/api/v1/invoices${suffix}`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
invoiceGet(id) {
|
|
156
|
+
return this.request(
|
|
157
|
+
"GET",
|
|
158
|
+
`/api/v1/invoices/${id}`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
// --- Profil / wallet / kartyak / csomagpontok ---
|
|
162
|
+
profileGet() {
|
|
163
|
+
return this.request(
|
|
164
|
+
"GET",
|
|
165
|
+
"/api/v1/profile"
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
profileUpdate(patch) {
|
|
169
|
+
return this.request("PATCH", "/api/v1/profile", patch);
|
|
170
|
+
}
|
|
171
|
+
wallet(params = {}) {
|
|
172
|
+
const qs = new URLSearchParams();
|
|
173
|
+
if (params.limit) qs.set("limit", String(params.limit));
|
|
174
|
+
if (params.offset) qs.set("offset", String(params.offset));
|
|
175
|
+
const suffix = qs.size > 0 ? `?${qs}` : "";
|
|
176
|
+
return this.request("GET", `/api/v1/wallet${suffix}`);
|
|
177
|
+
}
|
|
178
|
+
cardsList() {
|
|
179
|
+
return this.request("GET", "/api/v1/cards");
|
|
180
|
+
}
|
|
181
|
+
parcelshopsSearch(q, type) {
|
|
182
|
+
const qs = new URLSearchParams({ q });
|
|
183
|
+
if (type) qs.set("type", type);
|
|
184
|
+
return this.request("GET", `/api/v1/shipping/parcelshops?${qs}`);
|
|
185
|
+
}
|
|
186
|
+
exchangeRate() {
|
|
187
|
+
return this.request("GET", "/api/v1/exchange-rate");
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
function createClient(opts) {
|
|
191
|
+
return new KeyproClient(opts);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/mcp-tools.ts
|
|
195
|
+
var addressShape = z.object({
|
|
196
|
+
firstName: z.string().optional(),
|
|
197
|
+
lastName: z.string().optional(),
|
|
198
|
+
company: z.string().optional(),
|
|
199
|
+
address1: z.string().optional(),
|
|
200
|
+
address2: z.string().optional(),
|
|
201
|
+
city: z.string().optional(),
|
|
202
|
+
postcode: z.string().optional(),
|
|
203
|
+
state: z.string().optional(),
|
|
204
|
+
country: z.string().optional(),
|
|
205
|
+
email: z.string().optional(),
|
|
206
|
+
phone: z.string().optional()
|
|
207
|
+
}).optional();
|
|
208
|
+
var orderRequestShape = {
|
|
209
|
+
items: z.array(
|
|
210
|
+
z.object({
|
|
211
|
+
sku: z.string().optional().describe("Product SKU (either sku or productId)"),
|
|
212
|
+
productId: z.number().int().positive().optional(),
|
|
213
|
+
qty: z.number().int().min(1).max(999).default(1)
|
|
214
|
+
})
|
|
215
|
+
).min(1).describe("Order lines"),
|
|
216
|
+
paymentMethod: z.enum(["bacs", "cheque", "cod", "wallet", "stripe"]).describe(
|
|
217
|
+
"bacs=bank transfer (proforma first), cheque=8-day terms (+5%), cod=cash on delivery, wallet=KEP balance, stripe=saved card"
|
|
218
|
+
),
|
|
219
|
+
shippingMethodId: z.enum(["gls_hd", "gls_parcelshop", "combine_free"]).optional().describe("Required when the cart contains physical products"),
|
|
220
|
+
parcelshopId: z.string().optional().describe("GLS pickup point id (required for gls_parcelshop)"),
|
|
221
|
+
couponCode: z.string().optional(),
|
|
222
|
+
currency: z.enum(["EUR", "HUF"]).default("EUR"),
|
|
223
|
+
billing: addressShape.describe(
|
|
224
|
+
"Per-field billing address overrides (defaults come from the user profile)"
|
|
225
|
+
),
|
|
226
|
+
shipping: addressShape.describe(
|
|
227
|
+
"Separate shipping address (omit to ship to the billing address)"
|
|
228
|
+
),
|
|
229
|
+
taxNumber: z.string().optional(),
|
|
230
|
+
cardId: z.string().optional().describe("Saved card id (pm_...) for stripe payments; omit for default card")
|
|
231
|
+
};
|
|
232
|
+
function jsonResult(data) {
|
|
233
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
234
|
+
}
|
|
235
|
+
function errorResult(err) {
|
|
236
|
+
const payload = err instanceof KeyproApiError ? { code: err.code, message: err.message, details: err.details } : { code: "cli_error", message: err instanceof Error ? err.message : String(err) };
|
|
237
|
+
return {
|
|
238
|
+
content: [{ type: "text", text: JSON.stringify({ error: payload }) }],
|
|
239
|
+
isError: true
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
async function run(fn) {
|
|
243
|
+
try {
|
|
244
|
+
return jsonResult(await fn());
|
|
245
|
+
} catch (err) {
|
|
246
|
+
return errorResult(err);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function registerKeyproTools(server, client) {
|
|
250
|
+
server.registerTool(
|
|
251
|
+
"keypro_whoami",
|
|
252
|
+
{
|
|
253
|
+
description: "The authenticated KeyPro account (email, company, role, KEP wallet balance, API key scopes).",
|
|
254
|
+
inputSchema: {}
|
|
255
|
+
},
|
|
256
|
+
() => run(() => client.me())
|
|
257
|
+
);
|
|
258
|
+
server.registerTool(
|
|
259
|
+
"keypro_exchange_rate",
|
|
260
|
+
{
|
|
261
|
+
description: "Current EUR->HUF exchange rate the shop uses and displays (ECB daily reference + 3% markup). Product net prices are stored in EUR; the HUF price = round(EUR * rate) to whole forint (EUR shown with 2 decimals). Call this to price products accurately in both currencies.",
|
|
262
|
+
inputSchema: {}
|
|
263
|
+
},
|
|
264
|
+
() => run(() => client.exchangeRate())
|
|
265
|
+
);
|
|
266
|
+
server.registerTool(
|
|
267
|
+
"keypro_products_search",
|
|
268
|
+
{
|
|
269
|
+
description: "Search the KeyPro product catalog by name or SKU. Returns id, sku, name, net EUR price.",
|
|
270
|
+
inputSchema: {
|
|
271
|
+
q: z.string().optional().describe("Search text (name or SKU)"),
|
|
272
|
+
category: z.string().optional().describe("Category slug filter"),
|
|
273
|
+
onSale: z.boolean().optional().describe("Only discounted products"),
|
|
274
|
+
limit: z.number().int().min(1).max(100).optional()
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
(args) => run(
|
|
278
|
+
() => client.productsSearch({
|
|
279
|
+
q: args.q,
|
|
280
|
+
category: args.category,
|
|
281
|
+
onSale: args.onSale,
|
|
282
|
+
limit: args.limit
|
|
283
|
+
})
|
|
284
|
+
)
|
|
285
|
+
);
|
|
286
|
+
server.registerTool(
|
|
287
|
+
"keypro_product_get",
|
|
288
|
+
{
|
|
289
|
+
description: "Product details by id, slug or SKU, including the caller's effective unit price with discounts.",
|
|
290
|
+
inputSchema: { key: z.string().describe("Product id, slug or SKU") }
|
|
291
|
+
},
|
|
292
|
+
(args) => run(() => client.productGet(args.key))
|
|
293
|
+
);
|
|
294
|
+
server.registerTool(
|
|
295
|
+
"keypro_order_preview",
|
|
296
|
+
{
|
|
297
|
+
description: "Preview an order WITHOUT placing it: priced lines, fees, shipping, totals, and a confirmToken (valid 15 minutes). ALWAYS show the returned totals to the user and get their approval before calling keypro_order_create.",
|
|
298
|
+
inputSchema: orderRequestShape
|
|
299
|
+
},
|
|
300
|
+
(args) => run(() => client.orderPreview(args))
|
|
301
|
+
);
|
|
302
|
+
server.registerTool(
|
|
303
|
+
"keypro_order_create",
|
|
304
|
+
{
|
|
305
|
+
description: "Place an order. Requires the confirmToken from keypro_order_preview (same items, payment method and total). If the response contains payment.paymentUrl, give that link to the user to finish paying in a browser. Pass idempotencyKey to make retries safe.",
|
|
306
|
+
inputSchema: {
|
|
307
|
+
...orderRequestShape,
|
|
308
|
+
confirmToken: z.string().describe("Token from keypro_order_preview"),
|
|
309
|
+
idempotencyKey: z.string().optional().describe("Unique retry-dedup key; the same key never creates a second order")
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
(args) => run(() => {
|
|
313
|
+
const { idempotencyKey, ...request } = args;
|
|
314
|
+
return client.orderCreate(request, idempotencyKey);
|
|
315
|
+
})
|
|
316
|
+
);
|
|
317
|
+
server.registerTool(
|
|
318
|
+
"keypro_orders_list",
|
|
319
|
+
{
|
|
320
|
+
description: "List the user's orders (newest first), optional status filter.",
|
|
321
|
+
inputSchema: {
|
|
322
|
+
status: z.string().optional().describe("e.g. pending, processing, completed"),
|
|
323
|
+
limit: z.number().int().min(1).max(100).optional()
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
(args) => run(() => client.ordersList({ status: args.status, limit: args.limit }))
|
|
327
|
+
);
|
|
328
|
+
server.registerTool(
|
|
329
|
+
"keypro_order_get",
|
|
330
|
+
{
|
|
331
|
+
description: "Order details: items, totals, status, invoices, and a payment link if the order is awaiting card payment.",
|
|
332
|
+
inputSchema: { orderId: z.number().int().positive() }
|
|
333
|
+
},
|
|
334
|
+
(args) => run(() => client.orderGet(args.orderId))
|
|
335
|
+
);
|
|
336
|
+
server.registerTool(
|
|
337
|
+
"keypro_order_keys",
|
|
338
|
+
{
|
|
339
|
+
description: "License keys delivered for one order. COD orders release keys only after completion.",
|
|
340
|
+
inputSchema: { orderId: z.number().int().positive() }
|
|
341
|
+
},
|
|
342
|
+
(args) => run(() => client.orderKeys(args.orderId))
|
|
343
|
+
);
|
|
344
|
+
server.registerTool(
|
|
345
|
+
"keypro_order_cancel",
|
|
346
|
+
{
|
|
347
|
+
description: "Cancel an UNPAID order. Only orders awaiting payment can be cancelled: bacs (bank transfer / proforma), stripe (card checkout not completed), and cod (cash on delivery, still processing). 8-day-deferred (cheque) and already-paid orders CANNOT be cancelled. Idempotent.",
|
|
348
|
+
inputSchema: { orderId: z.number().int().positive() }
|
|
349
|
+
},
|
|
350
|
+
(args) => run(() => client.orderCancel(args.orderId))
|
|
351
|
+
);
|
|
352
|
+
server.registerTool(
|
|
353
|
+
"keypro_order_change_payment_preview",
|
|
354
|
+
{
|
|
355
|
+
description: "Preview changing an UNPAID order's payment method (only on-hold=bacs / pending=stripe orders qualify). Returns the recomputed totals for the new method (cheque adds +5%, cod adds a fixed fee; bacs/wallet/stripe add none) and a confirmToken. ALWAYS show the new totals to the user, then call keypro_order_change_payment.",
|
|
356
|
+
inputSchema: {
|
|
357
|
+
orderId: z.number().int().positive(),
|
|
358
|
+
newMethod: z.enum(["bacs", "cheque", "cod", "wallet", "stripe"])
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
(args) => run(() => client.orderPaymentPreview(args.orderId, args.newMethod))
|
|
362
|
+
);
|
|
363
|
+
server.registerTool(
|
|
364
|
+
"keypro_order_change_payment",
|
|
365
|
+
{
|
|
366
|
+
description: "Change an UNPAID order's payment method. Requires the confirmToken from keypro_order_change_payment_preview. wallet debits the KEP balance now and fulfils; cheque/cod add their fee and fulfil (final invoice + keys where due); bacs issues a proforma (awaits transfer); stripe charges the saved card or returns a payment link in payment.paymentUrl. Pass cardId (pm_...) to pick a specific card for stripe.",
|
|
367
|
+
inputSchema: {
|
|
368
|
+
orderId: z.number().int().positive(),
|
|
369
|
+
newMethod: z.enum(["bacs", "cheque", "cod", "wallet", "stripe"]),
|
|
370
|
+
confirmToken: z.string().describe("Token from keypro_order_change_payment_preview"),
|
|
371
|
+
cardId: z.string().optional().describe("Saved card id (pm_...) for stripe")
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
(args) => run(
|
|
375
|
+
() => client.orderChangePayment(args.orderId, {
|
|
376
|
+
newMethod: args.newMethod,
|
|
377
|
+
confirmToken: args.confirmToken,
|
|
378
|
+
cardId: args.cardId
|
|
379
|
+
})
|
|
380
|
+
)
|
|
381
|
+
);
|
|
382
|
+
server.registerTool(
|
|
383
|
+
"keypro_license_keys",
|
|
384
|
+
{
|
|
385
|
+
description: "All delivered license keys of the user, grouped by product.",
|
|
386
|
+
inputSchema: {}
|
|
387
|
+
},
|
|
388
|
+
() => run(() => client.licenseKeys())
|
|
389
|
+
);
|
|
390
|
+
server.registerTool(
|
|
391
|
+
"keypro_invoices_list",
|
|
392
|
+
{
|
|
393
|
+
description: "List invoices/proformas (each has a public downloadUrl PDF link). Optional order filter.",
|
|
394
|
+
inputSchema: {
|
|
395
|
+
orderId: z.number().int().positive().optional(),
|
|
396
|
+
limit: z.number().int().min(1).max(100).optional()
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
(args) => run(() => client.invoicesList({ orderId: args.orderId, limit: args.limit }))
|
|
400
|
+
);
|
|
401
|
+
server.registerTool(
|
|
402
|
+
"keypro_invoice_get",
|
|
403
|
+
{
|
|
404
|
+
description: "One invoice with totals and public downloadUrl.",
|
|
405
|
+
inputSchema: { invoiceId: z.number().int().positive() }
|
|
406
|
+
},
|
|
407
|
+
(args) => run(() => client.invoiceGet(args.invoiceId))
|
|
408
|
+
);
|
|
409
|
+
server.registerTool(
|
|
410
|
+
"keypro_profile_get",
|
|
411
|
+
{
|
|
412
|
+
description: "The account profile: contact data, billing and shipping address.",
|
|
413
|
+
inputSchema: {}
|
|
414
|
+
},
|
|
415
|
+
() => run(() => client.profileGet())
|
|
416
|
+
);
|
|
417
|
+
server.registerTool(
|
|
418
|
+
"keypro_profile_update",
|
|
419
|
+
{
|
|
420
|
+
description: "Update profile fields (partial). Allowed keys: firstName, phone, website, companyName, taxNumber, billingFirstName, billingLastName, billingCompany, billingAddress1, billingAddress2, billingCity, billingPostcode, billingState, billingCountry, billingEmail, billingPhone, and the same shipping* fields (no shippingEmail). Empty string clears a field.",
|
|
421
|
+
inputSchema: {
|
|
422
|
+
fields: z.record(z.string(), z.string()).describe("Field name -> new value map (flat API field names)")
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
(args) => run(() => client.profileUpdate(args.fields))
|
|
426
|
+
);
|
|
427
|
+
server.registerTool(
|
|
428
|
+
"keypro_wallet",
|
|
429
|
+
{
|
|
430
|
+
description: "KEP wallet balance (net EUR) and transaction history (topup/payment/refund/bonus with running balance).",
|
|
431
|
+
inputSchema: {
|
|
432
|
+
limit: z.number().int().min(1).max(100).optional().describe("History length")
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
(args) => run(() => client.wallet({ limit: args.limit }))
|
|
436
|
+
);
|
|
437
|
+
server.registerTool(
|
|
438
|
+
"keypro_cards_list",
|
|
439
|
+
{
|
|
440
|
+
description: "Saved bank cards (brand, last4, default flag). New cards can only be added on the website.",
|
|
441
|
+
inputSchema: {}
|
|
442
|
+
},
|
|
443
|
+
() => run(() => client.cardsList())
|
|
444
|
+
);
|
|
445
|
+
server.registerTool(
|
|
446
|
+
"keypro_parcelshops_search",
|
|
447
|
+
{
|
|
448
|
+
description: "Search GLS pickup points (city, zip prefix or name). Use the returned id as parcelshopId for gls_parcelshop shipping.",
|
|
449
|
+
inputSchema: {
|
|
450
|
+
q: z.string().describe("City, zip prefix or name"),
|
|
451
|
+
type: z.enum(["parcel-shop", "parcel-locker", "all"]).optional()
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
(args) => run(() => client.parcelshopsSearch(args.q, args.type))
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
export {
|
|
458
|
+
KeyproApiError,
|
|
459
|
+
createClient,
|
|
460
|
+
registerKeyproTools
|
|
461
|
+
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keypro/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "KeyPro.hu B2B szoftverlicenc webshop CLI: rendeles leadas, rendelesek/szamlak/termekkulcsok lekerdezese, profil kezeles. AI-ugynok (Claude Code, Codex) barat: --json kimenet es beepitett MCP szerver (keypro mcp).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"keypro": "dist/index.js"
|
|
9
9
|
},
|
|
10
|
+
"exports": {
|
|
11
|
+
"./mcp-tools": {
|
|
12
|
+
"types": "./dist/mcp-tools.d.ts",
|
|
13
|
+
"import": "./dist/mcp-tools.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
10
16
|
"files": [
|
|
11
17
|
"dist",
|
|
12
18
|
"AGENTS.md",
|