@nuno1026/bithumb-mcp 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/dist/index.js ADDED
@@ -0,0 +1,3567 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { parseArgs } from "util";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+
7
+ // ../core/dist/index.js
8
+ import { createHash, createHmac, randomUUID } from "crypto";
9
+ import fs from "fs";
10
+ import path from "path";
11
+ import os from "os";
12
+ import { existsSync } from "fs";
13
+ import { join } from "path";
14
+ import { homedir } from "os";
15
+ import { readFileSync, writeFileSync, mkdirSync, existsSync as existsSync2 } from "fs";
16
+ import { join as join2, dirname } from "path";
17
+ import { homedir as homedir2 } from "os";
18
+
19
+ // ../../node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/error.js
20
+ function getLineColFromPtr(string, ptr) {
21
+ let lines = string.slice(0, ptr).split(/\r\n|\n|\r/g);
22
+ return [lines.length, lines.pop().length + 1];
23
+ }
24
+ function makeCodeBlock(string, line, column) {
25
+ let lines = string.split(/\r\n|\n|\r/g);
26
+ let codeblock = "";
27
+ let numberLen = (Math.log10(line + 1) | 0) + 1;
28
+ for (let i = line - 1; i <= line + 1; i++) {
29
+ let l = lines[i - 1];
30
+ if (!l)
31
+ continue;
32
+ codeblock += i.toString().padEnd(numberLen, " ");
33
+ codeblock += ": ";
34
+ codeblock += l;
35
+ codeblock += "\n";
36
+ if (i === line) {
37
+ codeblock += " ".repeat(numberLen + column + 2);
38
+ codeblock += "^\n";
39
+ }
40
+ }
41
+ return codeblock;
42
+ }
43
+ var TomlError = class extends Error {
44
+ line;
45
+ column;
46
+ codeblock;
47
+ constructor(message, options) {
48
+ const [line, column] = getLineColFromPtr(options.toml, options.ptr);
49
+ const codeblock = makeCodeBlock(options.toml, line, column);
50
+ super(`Invalid TOML document: ${message}
51
+
52
+ ${codeblock}`, options);
53
+ this.line = line;
54
+ this.column = column;
55
+ this.codeblock = codeblock;
56
+ }
57
+ };
58
+
59
+ // ../../node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/util.js
60
+ function isEscaped(str, ptr) {
61
+ let i = 0;
62
+ while (str[ptr - ++i] === "\\")
63
+ ;
64
+ return --i && i % 2;
65
+ }
66
+ function indexOfNewline(str, start = 0, end = str.length) {
67
+ let idx = str.indexOf("\n", start);
68
+ if (str[idx - 1] === "\r")
69
+ idx--;
70
+ return idx <= end ? idx : -1;
71
+ }
72
+ function skipComment(str, ptr) {
73
+ for (let i = ptr; i < str.length; i++) {
74
+ let c = str[i];
75
+ if (c === "\n")
76
+ return i;
77
+ if (c === "\r" && str[i + 1] === "\n")
78
+ return i + 1;
79
+ if (c < " " && c !== " " || c === "\x7F") {
80
+ throw new TomlError("control characters are not allowed in comments", {
81
+ toml: str,
82
+ ptr
83
+ });
84
+ }
85
+ }
86
+ return str.length;
87
+ }
88
+ function skipVoid(str, ptr, banNewLines, banComments) {
89
+ let c;
90
+ while (1) {
91
+ while ((c = str[ptr]) === " " || c === " " || !banNewLines && (c === "\n" || c === "\r" && str[ptr + 1] === "\n"))
92
+ ptr++;
93
+ if (banComments || c !== "#")
94
+ break;
95
+ ptr = skipComment(str, ptr);
96
+ }
97
+ return ptr;
98
+ }
99
+ function skipUntil(str, ptr, sep, end, banNewLines = false) {
100
+ if (!end) {
101
+ ptr = indexOfNewline(str, ptr);
102
+ return ptr < 0 ? str.length : ptr;
103
+ }
104
+ for (let i = ptr; i < str.length; i++) {
105
+ let c = str[i];
106
+ if (c === "#") {
107
+ i = indexOfNewline(str, i);
108
+ } else if (c === sep) {
109
+ return i + 1;
110
+ } else if (c === end || banNewLines && (c === "\n" || c === "\r" && str[i + 1] === "\n")) {
111
+ return i;
112
+ }
113
+ }
114
+ throw new TomlError("cannot find end of structure", {
115
+ toml: str,
116
+ ptr
117
+ });
118
+ }
119
+ function getStringEnd(str, seek) {
120
+ let first = str[seek];
121
+ let target = first === str[seek + 1] && str[seek + 1] === str[seek + 2] ? str.slice(seek, seek + 3) : first;
122
+ seek += target.length - 1;
123
+ do
124
+ seek = str.indexOf(target, ++seek);
125
+ while (seek > -1 && first !== "'" && isEscaped(str, seek));
126
+ if (seek > -1) {
127
+ seek += target.length;
128
+ if (target.length > 1) {
129
+ if (str[seek] === first)
130
+ seek++;
131
+ if (str[seek] === first)
132
+ seek++;
133
+ }
134
+ }
135
+ return seek;
136
+ }
137
+
138
+ // ../../node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/date.js
139
+ var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i;
140
+ var TomlDate = class _TomlDate extends Date {
141
+ #hasDate = false;
142
+ #hasTime = false;
143
+ #offset = null;
144
+ constructor(date) {
145
+ let hasDate = true;
146
+ let hasTime = true;
147
+ let offset = "Z";
148
+ if (typeof date === "string") {
149
+ let match = date.match(DATE_TIME_RE);
150
+ if (match) {
151
+ if (!match[1]) {
152
+ hasDate = false;
153
+ date = `0000-01-01T${date}`;
154
+ }
155
+ hasTime = !!match[2];
156
+ hasTime && date[10] === " " && (date = date.replace(" ", "T"));
157
+ if (match[2] && +match[2] > 23) {
158
+ date = "";
159
+ } else {
160
+ offset = match[3] || null;
161
+ date = date.toUpperCase();
162
+ if (!offset && hasTime)
163
+ date += "Z";
164
+ }
165
+ } else {
166
+ date = "";
167
+ }
168
+ }
169
+ super(date);
170
+ if (!isNaN(this.getTime())) {
171
+ this.#hasDate = hasDate;
172
+ this.#hasTime = hasTime;
173
+ this.#offset = offset;
174
+ }
175
+ }
176
+ isDateTime() {
177
+ return this.#hasDate && this.#hasTime;
178
+ }
179
+ isLocal() {
180
+ return !this.#hasDate || !this.#hasTime || !this.#offset;
181
+ }
182
+ isDate() {
183
+ return this.#hasDate && !this.#hasTime;
184
+ }
185
+ isTime() {
186
+ return this.#hasTime && !this.#hasDate;
187
+ }
188
+ isValid() {
189
+ return this.#hasDate || this.#hasTime;
190
+ }
191
+ toISOString() {
192
+ let iso = super.toISOString();
193
+ if (this.isDate())
194
+ return iso.slice(0, 10);
195
+ if (this.isTime())
196
+ return iso.slice(11, 23);
197
+ if (this.#offset === null)
198
+ return iso.slice(0, -1);
199
+ if (this.#offset === "Z")
200
+ return iso;
201
+ let offset = +this.#offset.slice(1, 3) * 60 + +this.#offset.slice(4, 6);
202
+ offset = this.#offset[0] === "-" ? offset : -offset;
203
+ let offsetDate = new Date(this.getTime() - offset * 6e4);
204
+ return offsetDate.toISOString().slice(0, -1) + this.#offset;
205
+ }
206
+ static wrapAsOffsetDateTime(jsDate, offset = "Z") {
207
+ let date = new _TomlDate(jsDate);
208
+ date.#offset = offset;
209
+ return date;
210
+ }
211
+ static wrapAsLocalDateTime(jsDate) {
212
+ let date = new _TomlDate(jsDate);
213
+ date.#offset = null;
214
+ return date;
215
+ }
216
+ static wrapAsLocalDate(jsDate) {
217
+ let date = new _TomlDate(jsDate);
218
+ date.#hasTime = false;
219
+ date.#offset = null;
220
+ return date;
221
+ }
222
+ static wrapAsLocalTime(jsDate) {
223
+ let date = new _TomlDate(jsDate);
224
+ date.#hasDate = false;
225
+ date.#offset = null;
226
+ return date;
227
+ }
228
+ };
229
+
230
+ // ../../node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/primitive.js
231
+ var INT_REGEX = /^((0x[0-9a-fA-F](_?[0-9a-fA-F])*)|(([+-]|0[ob])?\d(_?\d)*))$/;
232
+ var FLOAT_REGEX = /^[+-]?\d(_?\d)*(\.\d(_?\d)*)?([eE][+-]?\d(_?\d)*)?$/;
233
+ var LEADING_ZERO = /^[+-]?0[0-9_]/;
234
+ var ESCAPE_REGEX = /^[0-9a-f]{2,8}$/i;
235
+ var ESC_MAP = {
236
+ b: "\b",
237
+ t: " ",
238
+ n: "\n",
239
+ f: "\f",
240
+ r: "\r",
241
+ e: "\x1B",
242
+ '"': '"',
243
+ "\\": "\\"
244
+ };
245
+ function parseString(str, ptr = 0, endPtr = str.length) {
246
+ let isLiteral = str[ptr] === "'";
247
+ let isMultiline = str[ptr++] === str[ptr] && str[ptr] === str[ptr + 1];
248
+ if (isMultiline) {
249
+ endPtr -= 2;
250
+ if (str[ptr += 2] === "\r")
251
+ ptr++;
252
+ if (str[ptr] === "\n")
253
+ ptr++;
254
+ }
255
+ let tmp = 0;
256
+ let isEscape;
257
+ let parsed = "";
258
+ let sliceStart = ptr;
259
+ while (ptr < endPtr - 1) {
260
+ let c = str[ptr++];
261
+ if (c === "\n" || c === "\r" && str[ptr] === "\n") {
262
+ if (!isMultiline) {
263
+ throw new TomlError("newlines are not allowed in strings", {
264
+ toml: str,
265
+ ptr: ptr - 1
266
+ });
267
+ }
268
+ } else if (c < " " && c !== " " || c === "\x7F") {
269
+ throw new TomlError("control characters are not allowed in strings", {
270
+ toml: str,
271
+ ptr: ptr - 1
272
+ });
273
+ }
274
+ if (isEscape) {
275
+ isEscape = false;
276
+ if (c === "x" || c === "u" || c === "U") {
277
+ let code = str.slice(ptr, ptr += c === "x" ? 2 : c === "u" ? 4 : 8);
278
+ if (!ESCAPE_REGEX.test(code)) {
279
+ throw new TomlError("invalid unicode escape", {
280
+ toml: str,
281
+ ptr: tmp
282
+ });
283
+ }
284
+ try {
285
+ parsed += String.fromCodePoint(parseInt(code, 16));
286
+ } catch {
287
+ throw new TomlError("invalid unicode escape", {
288
+ toml: str,
289
+ ptr: tmp
290
+ });
291
+ }
292
+ } else if (isMultiline && (c === "\n" || c === " " || c === " " || c === "\r")) {
293
+ ptr = skipVoid(str, ptr - 1, true);
294
+ if (str[ptr] !== "\n" && str[ptr] !== "\r") {
295
+ throw new TomlError("invalid escape: only line-ending whitespace may be escaped", {
296
+ toml: str,
297
+ ptr: tmp
298
+ });
299
+ }
300
+ ptr = skipVoid(str, ptr);
301
+ } else if (c in ESC_MAP) {
302
+ parsed += ESC_MAP[c];
303
+ } else {
304
+ throw new TomlError("unrecognized escape sequence", {
305
+ toml: str,
306
+ ptr: tmp
307
+ });
308
+ }
309
+ sliceStart = ptr;
310
+ } else if (!isLiteral && c === "\\") {
311
+ tmp = ptr - 1;
312
+ isEscape = true;
313
+ parsed += str.slice(sliceStart, tmp);
314
+ }
315
+ }
316
+ return parsed + str.slice(sliceStart, endPtr - 1);
317
+ }
318
+ function parseValue(value, toml, ptr, integersAsBigInt) {
319
+ if (value === "true")
320
+ return true;
321
+ if (value === "false")
322
+ return false;
323
+ if (value === "-inf")
324
+ return -Infinity;
325
+ if (value === "inf" || value === "+inf")
326
+ return Infinity;
327
+ if (value === "nan" || value === "+nan" || value === "-nan")
328
+ return NaN;
329
+ if (value === "-0")
330
+ return integersAsBigInt ? 0n : 0;
331
+ let isInt = INT_REGEX.test(value);
332
+ if (isInt || FLOAT_REGEX.test(value)) {
333
+ if (LEADING_ZERO.test(value)) {
334
+ throw new TomlError("leading zeroes are not allowed", {
335
+ toml,
336
+ ptr
337
+ });
338
+ }
339
+ value = value.replace(/_/g, "");
340
+ let numeric = +value;
341
+ if (isNaN(numeric)) {
342
+ throw new TomlError("invalid number", {
343
+ toml,
344
+ ptr
345
+ });
346
+ }
347
+ if (isInt) {
348
+ if ((isInt = !Number.isSafeInteger(numeric)) && !integersAsBigInt) {
349
+ throw new TomlError("integer value cannot be represented losslessly", {
350
+ toml,
351
+ ptr
352
+ });
353
+ }
354
+ if (isInt || integersAsBigInt === true)
355
+ numeric = BigInt(value);
356
+ }
357
+ return numeric;
358
+ }
359
+ const date = new TomlDate(value);
360
+ if (!date.isValid()) {
361
+ throw new TomlError("invalid value", {
362
+ toml,
363
+ ptr
364
+ });
365
+ }
366
+ return date;
367
+ }
368
+
369
+ // ../../node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/extract.js
370
+ function sliceAndTrimEndOf(str, startPtr, endPtr) {
371
+ let value = str.slice(startPtr, endPtr);
372
+ let commentIdx = value.indexOf("#");
373
+ if (commentIdx > -1) {
374
+ skipComment(str, commentIdx);
375
+ value = value.slice(0, commentIdx);
376
+ }
377
+ return [value.trimEnd(), commentIdx];
378
+ }
379
+ function extractValue(str, ptr, end, depth, integersAsBigInt) {
380
+ if (depth === 0) {
381
+ throw new TomlError("document contains excessively nested structures. aborting.", {
382
+ toml: str,
383
+ ptr
384
+ });
385
+ }
386
+ let c = str[ptr];
387
+ if (c === "[" || c === "{") {
388
+ let [value, endPtr2] = c === "[" ? parseArray(str, ptr, depth, integersAsBigInt) : parseInlineTable(str, ptr, depth, integersAsBigInt);
389
+ if (end) {
390
+ endPtr2 = skipVoid(str, endPtr2);
391
+ if (str[endPtr2] === ",")
392
+ endPtr2++;
393
+ else if (str[endPtr2] !== end) {
394
+ throw new TomlError("expected comma or end of structure", {
395
+ toml: str,
396
+ ptr: endPtr2
397
+ });
398
+ }
399
+ }
400
+ return [value, endPtr2];
401
+ }
402
+ let endPtr;
403
+ if (c === '"' || c === "'") {
404
+ endPtr = getStringEnd(str, ptr);
405
+ let parsed = parseString(str, ptr, endPtr);
406
+ if (end) {
407
+ endPtr = skipVoid(str, endPtr);
408
+ if (str[endPtr] && str[endPtr] !== "," && str[endPtr] !== end && str[endPtr] !== "\n" && str[endPtr] !== "\r") {
409
+ throw new TomlError("unexpected character encountered", {
410
+ toml: str,
411
+ ptr: endPtr
412
+ });
413
+ }
414
+ endPtr += +(str[endPtr] === ",");
415
+ }
416
+ return [parsed, endPtr];
417
+ }
418
+ endPtr = skipUntil(str, ptr, ",", end);
419
+ let slice = sliceAndTrimEndOf(str, ptr, endPtr - +(str[endPtr - 1] === ","));
420
+ if (!slice[0]) {
421
+ throw new TomlError("incomplete key-value declaration: no value specified", {
422
+ toml: str,
423
+ ptr
424
+ });
425
+ }
426
+ if (end && slice[1] > -1) {
427
+ endPtr = skipVoid(str, ptr + slice[1]);
428
+ endPtr += +(str[endPtr] === ",");
429
+ }
430
+ return [
431
+ parseValue(slice[0], str, ptr, integersAsBigInt),
432
+ endPtr
433
+ ];
434
+ }
435
+
436
+ // ../../node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/struct.js
437
+ var KEY_PART_RE = /^[a-zA-Z0-9-_]+[ \t]*$/;
438
+ function parseKey(str, ptr, end = "=") {
439
+ let dot = ptr - 1;
440
+ let parsed = [];
441
+ let endPtr = str.indexOf(end, ptr);
442
+ if (endPtr < 0) {
443
+ throw new TomlError("incomplete key-value: cannot find end of key", {
444
+ toml: str,
445
+ ptr
446
+ });
447
+ }
448
+ do {
449
+ let c = str[ptr = ++dot];
450
+ if (c !== " " && c !== " ") {
451
+ if (c === '"' || c === "'") {
452
+ if (c === str[ptr + 1] && c === str[ptr + 2]) {
453
+ throw new TomlError("multiline strings are not allowed in keys", {
454
+ toml: str,
455
+ ptr
456
+ });
457
+ }
458
+ let eos = getStringEnd(str, ptr);
459
+ if (eos < 0) {
460
+ throw new TomlError("unfinished string encountered", {
461
+ toml: str,
462
+ ptr
463
+ });
464
+ }
465
+ dot = str.indexOf(".", eos);
466
+ let strEnd = str.slice(eos, dot < 0 || dot > endPtr ? endPtr : dot);
467
+ let newLine = indexOfNewline(strEnd);
468
+ if (newLine > -1) {
469
+ throw new TomlError("newlines are not allowed in keys", {
470
+ toml: str,
471
+ ptr: ptr + dot + newLine
472
+ });
473
+ }
474
+ if (strEnd.trimStart()) {
475
+ throw new TomlError("found extra tokens after the string part", {
476
+ toml: str,
477
+ ptr: eos
478
+ });
479
+ }
480
+ if (endPtr < eos) {
481
+ endPtr = str.indexOf(end, eos);
482
+ if (endPtr < 0) {
483
+ throw new TomlError("incomplete key-value: cannot find end of key", {
484
+ toml: str,
485
+ ptr
486
+ });
487
+ }
488
+ }
489
+ parsed.push(parseString(str, ptr, eos));
490
+ } else {
491
+ dot = str.indexOf(".", ptr);
492
+ let part = str.slice(ptr, dot < 0 || dot > endPtr ? endPtr : dot);
493
+ if (!KEY_PART_RE.test(part)) {
494
+ throw new TomlError("only letter, numbers, dashes and underscores are allowed in keys", {
495
+ toml: str,
496
+ ptr
497
+ });
498
+ }
499
+ parsed.push(part.trimEnd());
500
+ }
501
+ }
502
+ } while (dot + 1 && dot < endPtr);
503
+ return [parsed, skipVoid(str, endPtr + 1, true, true)];
504
+ }
505
+ function parseInlineTable(str, ptr, depth, integersAsBigInt) {
506
+ let res = {};
507
+ let seen = /* @__PURE__ */ new Set();
508
+ let c;
509
+ ptr++;
510
+ while ((c = str[ptr++]) !== "}" && c) {
511
+ if (c === ",") {
512
+ throw new TomlError("expected value, found comma", {
513
+ toml: str,
514
+ ptr: ptr - 1
515
+ });
516
+ } else if (c === "#")
517
+ ptr = skipComment(str, ptr);
518
+ else if (c !== " " && c !== " " && c !== "\n" && c !== "\r") {
519
+ let k;
520
+ let t = res;
521
+ let hasOwn = false;
522
+ let [key, keyEndPtr] = parseKey(str, ptr - 1);
523
+ for (let i = 0; i < key.length; i++) {
524
+ if (i)
525
+ t = hasOwn ? t[k] : t[k] = {};
526
+ k = key[i];
527
+ if ((hasOwn = Object.hasOwn(t, k)) && (typeof t[k] !== "object" || seen.has(t[k]))) {
528
+ throw new TomlError("trying to redefine an already defined value", {
529
+ toml: str,
530
+ ptr
531
+ });
532
+ }
533
+ if (!hasOwn && k === "__proto__") {
534
+ Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
535
+ }
536
+ }
537
+ if (hasOwn) {
538
+ throw new TomlError("trying to redefine an already defined value", {
539
+ toml: str,
540
+ ptr
541
+ });
542
+ }
543
+ let [value, valueEndPtr] = extractValue(str, keyEndPtr, "}", depth - 1, integersAsBigInt);
544
+ seen.add(value);
545
+ t[k] = value;
546
+ ptr = valueEndPtr;
547
+ }
548
+ }
549
+ if (!c) {
550
+ throw new TomlError("unfinished table encountered", {
551
+ toml: str,
552
+ ptr
553
+ });
554
+ }
555
+ return [res, ptr];
556
+ }
557
+ function parseArray(str, ptr, depth, integersAsBigInt) {
558
+ let res = [];
559
+ let c;
560
+ ptr++;
561
+ while ((c = str[ptr++]) !== "]" && c) {
562
+ if (c === ",") {
563
+ throw new TomlError("expected value, found comma", {
564
+ toml: str,
565
+ ptr: ptr - 1
566
+ });
567
+ } else if (c === "#")
568
+ ptr = skipComment(str, ptr);
569
+ else if (c !== " " && c !== " " && c !== "\n" && c !== "\r") {
570
+ let e = extractValue(str, ptr - 1, "]", depth - 1, integersAsBigInt);
571
+ res.push(e[0]);
572
+ ptr = e[1];
573
+ }
574
+ }
575
+ if (!c) {
576
+ throw new TomlError("unfinished array encountered", {
577
+ toml: str,
578
+ ptr
579
+ });
580
+ }
581
+ return [res, ptr];
582
+ }
583
+
584
+ // ../../node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/parse.js
585
+ function peekTable(key, table, meta, type) {
586
+ let t = table;
587
+ let m = meta;
588
+ let k;
589
+ let hasOwn = false;
590
+ let state;
591
+ for (let i = 0; i < key.length; i++) {
592
+ if (i) {
593
+ t = hasOwn ? t[k] : t[k] = {};
594
+ m = (state = m[k]).c;
595
+ if (type === 0 && (state.t === 1 || state.t === 2)) {
596
+ return null;
597
+ }
598
+ if (state.t === 2) {
599
+ let l = t.length - 1;
600
+ t = t[l];
601
+ m = m[l].c;
602
+ }
603
+ }
604
+ k = key[i];
605
+ if ((hasOwn = Object.hasOwn(t, k)) && m[k]?.t === 0 && m[k]?.d) {
606
+ return null;
607
+ }
608
+ if (!hasOwn) {
609
+ if (k === "__proto__") {
610
+ Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
611
+ Object.defineProperty(m, k, { enumerable: true, configurable: true, writable: true });
612
+ }
613
+ m[k] = {
614
+ t: i < key.length - 1 && type === 2 ? 3 : type,
615
+ d: false,
616
+ i: 0,
617
+ c: {}
618
+ };
619
+ }
620
+ }
621
+ state = m[k];
622
+ if (state.t !== type && !(type === 1 && state.t === 3)) {
623
+ return null;
624
+ }
625
+ if (type === 2) {
626
+ if (!state.d) {
627
+ state.d = true;
628
+ t[k] = [];
629
+ }
630
+ t[k].push(t = {});
631
+ state.c[state.i++] = state = { t: 1, d: false, i: 0, c: {} };
632
+ }
633
+ if (state.d) {
634
+ return null;
635
+ }
636
+ state.d = true;
637
+ if (type === 1) {
638
+ t = hasOwn ? t[k] : t[k] = {};
639
+ } else if (type === 0 && hasOwn) {
640
+ return null;
641
+ }
642
+ return [k, t, state.c];
643
+ }
644
+ function parse(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
645
+ let res = {};
646
+ let meta = {};
647
+ let tbl = res;
648
+ let m = meta;
649
+ for (let ptr = skipVoid(toml, 0); ptr < toml.length; ) {
650
+ if (toml[ptr] === "[") {
651
+ let isTableArray = toml[++ptr] === "[";
652
+ let k = parseKey(toml, ptr += +isTableArray, "]");
653
+ if (isTableArray) {
654
+ if (toml[k[1] - 1] !== "]") {
655
+ throw new TomlError("expected end of table declaration", {
656
+ toml,
657
+ ptr: k[1] - 1
658
+ });
659
+ }
660
+ k[1]++;
661
+ }
662
+ let p = peekTable(
663
+ k[0],
664
+ res,
665
+ meta,
666
+ isTableArray ? 2 : 1
667
+ /* Type.EXPLICIT */
668
+ );
669
+ if (!p) {
670
+ throw new TomlError("trying to redefine an already defined table or value", {
671
+ toml,
672
+ ptr
673
+ });
674
+ }
675
+ m = p[2];
676
+ tbl = p[1];
677
+ ptr = k[1];
678
+ } else {
679
+ let k = parseKey(toml, ptr);
680
+ let p = peekTable(
681
+ k[0],
682
+ tbl,
683
+ m,
684
+ 0
685
+ /* Type.DOTTED */
686
+ );
687
+ if (!p) {
688
+ throw new TomlError("trying to redefine an already defined table or value", {
689
+ toml,
690
+ ptr
691
+ });
692
+ }
693
+ let v = extractValue(toml, k[1], void 0, maxDepth, integersAsBigInt);
694
+ p[1][p[0]] = v[0];
695
+ ptr = v[1];
696
+ }
697
+ ptr = skipVoid(toml, ptr, true);
698
+ if (toml[ptr] && toml[ptr] !== "\n" && toml[ptr] !== "\r") {
699
+ throw new TomlError("each key-value declaration must be followed by an end-of-line", {
700
+ toml,
701
+ ptr
702
+ });
703
+ }
704
+ ptr = skipVoid(toml, ptr);
705
+ }
706
+ return res;
707
+ }
708
+
709
+ // ../core/dist/index.js
710
+ import { mkdirSync as mkdirSync2, appendFileSync } from "fs";
711
+ import { homedir as homedir3 } from "os";
712
+ import { join as join3 } from "path";
713
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
714
+ import { homedir as homedir4 } from "os";
715
+ import { join as join4 } from "path";
716
+ import * as fs2 from "fs";
717
+ import * as path2 from "path";
718
+ import * as os2 from "os";
719
+ import { execFileSync } from "child_process";
720
+ function base64url(input) {
721
+ const buf = typeof input === "string" ? Buffer.from(input) : input;
722
+ return buf.toString("base64url");
723
+ }
724
+ function hashQuery(queryString) {
725
+ return createHash("sha512").update(queryString, "utf8").digest("hex");
726
+ }
727
+ function createJwt(payload, secretKey) {
728
+ const header = { alg: "HS256", typ: "JWT" };
729
+ const headerB64 = base64url(JSON.stringify(header));
730
+ const payloadB64 = base64url(JSON.stringify(payload));
731
+ const signature = createHmac("sha256", secretKey).update(`${headerB64}.${payloadB64}`).digest("base64url");
732
+ return `${headerB64}.${payloadB64}.${signature}`;
733
+ }
734
+ function signRequest(accessKey, secretKey, queryString) {
735
+ const payload = {
736
+ access_key: accessKey,
737
+ nonce: randomUUID(),
738
+ timestamp: Date.now()
739
+ };
740
+ if (queryString && queryString.length > 0) {
741
+ payload.query_hash = hashQuery(queryString);
742
+ payload.query_hash_alg = "SHA512";
743
+ }
744
+ return createJwt(payload, secretKey);
745
+ }
746
+ var BithumbMcpError = class extends Error {
747
+ type;
748
+ code;
749
+ suggestion;
750
+ endpoint;
751
+ constructor(type, message, options) {
752
+ super(message, options?.cause ? { cause: options.cause } : void 0);
753
+ this.name = type;
754
+ this.type = type;
755
+ this.code = options?.code;
756
+ this.suggestion = options?.suggestion;
757
+ this.endpoint = options?.endpoint;
758
+ }
759
+ };
760
+ var ConfigError = class extends BithumbMcpError {
761
+ constructor(message, suggestion) {
762
+ super("ConfigError", message, { suggestion });
763
+ }
764
+ };
765
+ var ValidationError = class extends BithumbMcpError {
766
+ constructor(message, suggestion) {
767
+ super("ValidationError", message, { suggestion });
768
+ }
769
+ };
770
+ var RateLimitError = class extends BithumbMcpError {
771
+ constructor(message, suggestion, endpoint) {
772
+ super("RateLimitError", message, { suggestion, endpoint });
773
+ }
774
+ };
775
+ var AuthenticationError = class extends BithumbMcpError {
776
+ constructor(message, suggestion, endpoint) {
777
+ super("AuthenticationError", message, { suggestion, endpoint });
778
+ }
779
+ };
780
+ var BithumbApiError = class extends BithumbMcpError {
781
+ constructor(message, options) {
782
+ super("BithumbApiError", message, options);
783
+ }
784
+ };
785
+ var NetworkError = class extends BithumbMcpError {
786
+ constructor(message, endpoint, cause) {
787
+ super("NetworkError", message, {
788
+ endpoint,
789
+ cause,
790
+ suggestion: "Check network connectivity and try again."
791
+ });
792
+ }
793
+ };
794
+ function toToolErrorPayload(error) {
795
+ if (error instanceof BithumbMcpError) {
796
+ return {
797
+ error: true,
798
+ type: error.type,
799
+ code: error.code,
800
+ message: error.message,
801
+ suggestion: error.suggestion,
802
+ endpoint: error.endpoint,
803
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
804
+ };
805
+ }
806
+ const message = error instanceof Error ? error.message : "An unexpected error occurred.";
807
+ return {
808
+ error: true,
809
+ type: "InternalError",
810
+ message,
811
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
812
+ };
813
+ }
814
+ var RateLimiter = class {
815
+ buckets = /* @__PURE__ */ new Map();
816
+ cleanupMs;
817
+ verbose;
818
+ lastCleanup;
819
+ constructor(cleanupMs = 3e4, verbose = false) {
820
+ this.cleanupMs = cleanupMs;
821
+ this.verbose = verbose;
822
+ this.lastCleanup = Date.now();
823
+ }
824
+ async consume(config, amount = 1) {
825
+ this.maybeCleanup();
826
+ const bucket = this.getBucket(config);
827
+ this.refill(bucket);
828
+ if (bucket.tokens >= amount) {
829
+ bucket.tokens -= amount;
830
+ return;
831
+ }
832
+ const deficit = amount - bucket.tokens;
833
+ const waitMs = Math.ceil(deficit / bucket.refillPerSecond * 1e3);
834
+ if (waitMs > 1e4) {
835
+ throw new RateLimitError(
836
+ `Rate limit exceeded for "${config.key}". Need ${deficit.toFixed(1)} tokens, wait ~${waitMs}ms.`,
837
+ "Reduce request frequency or wait."
838
+ );
839
+ }
840
+ if (this.verbose) {
841
+ process.stderr.write(
842
+ `[rate-limiter] ${config.key}: waiting ${waitMs}ms for ${deficit.toFixed(1)} tokens
843
+ `
844
+ );
845
+ }
846
+ await new Promise((resolve) => setTimeout(resolve, waitMs));
847
+ this.refill(bucket);
848
+ if (bucket.tokens >= amount) {
849
+ bucket.tokens -= amount;
850
+ return;
851
+ }
852
+ throw new RateLimitError(
853
+ `Rate limit exceeded for "${config.key}" after waiting.`,
854
+ "Reduce request frequency."
855
+ );
856
+ }
857
+ getBucket(config) {
858
+ let bucket = this.buckets.get(config.key);
859
+ if (!bucket) {
860
+ bucket = {
861
+ tokens: config.capacity,
862
+ capacity: config.capacity,
863
+ refillPerSecond: config.refillPerSecond,
864
+ lastRefill: Date.now()
865
+ };
866
+ this.buckets.set(config.key, bucket);
867
+ }
868
+ return bucket;
869
+ }
870
+ refill(bucket) {
871
+ const now = Date.now();
872
+ const elapsed = (now - bucket.lastRefill) / 1e3;
873
+ if (elapsed <= 0) return;
874
+ bucket.tokens = Math.min(
875
+ bucket.capacity,
876
+ bucket.tokens + elapsed * bucket.refillPerSecond
877
+ );
878
+ bucket.lastRefill = now;
879
+ }
880
+ maybeCleanup() {
881
+ const now = Date.now();
882
+ if (now - this.lastCleanup < this.cleanupMs) return;
883
+ this.lastCleanup = now;
884
+ for (const [key, bucket] of this.buckets) {
885
+ const idle = (now - bucket.lastRefill) / 1e3;
886
+ if (idle > 60 && bucket.tokens >= bucket.capacity) {
887
+ this.buckets.delete(key);
888
+ }
889
+ }
890
+ }
891
+ };
892
+ function isDefined(value) {
893
+ return value !== void 0 && value !== null;
894
+ }
895
+ function buildQueryString(query) {
896
+ if (!query) return "";
897
+ const entries = Object.entries(query).filter(([, v]) => isDefined(v));
898
+ if (entries.length === 0) return "";
899
+ const parts = [];
900
+ for (const [key, value] of entries) {
901
+ if (Array.isArray(value)) {
902
+ for (let i = 0; i < value.length; i++) {
903
+ const item = value[i];
904
+ if (item && typeof item === "object" && !Array.isArray(item)) {
905
+ for (const [subKey, subVal] of Object.entries(item)) {
906
+ parts.push(
907
+ `${encodeURIComponent(key)}[${i}][${encodeURIComponent(subKey)}]=${encodeURIComponent(String(subVal))}`
908
+ );
909
+ }
910
+ } else {
911
+ parts.push(
912
+ `${encodeURIComponent(key)}[]=${encodeURIComponent(String(item))}`
913
+ );
914
+ }
915
+ }
916
+ } else {
917
+ parts.push(
918
+ `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`
919
+ );
920
+ }
921
+ }
922
+ return parts.join("&");
923
+ }
924
+ function buildHashString(query, body) {
925
+ const source = body ?? query;
926
+ if (!source) return "";
927
+ const entries = Object.entries(source).filter(([, v]) => isDefined(v));
928
+ if (entries.length === 0) return "";
929
+ const parts = [];
930
+ for (const [key, value] of entries) {
931
+ if (Array.isArray(value)) {
932
+ for (let i = 0; i < value.length; i++) {
933
+ const item = value[i];
934
+ if (item && typeof item === "object" && !Array.isArray(item)) {
935
+ for (const [subKey, subVal] of Object.entries(item)) {
936
+ parts.push(`${key}[${i}][${subKey}]=${String(subVal)}`);
937
+ }
938
+ } else {
939
+ parts.push(`${key}[]=${String(item)}`);
940
+ }
941
+ }
942
+ } else {
943
+ parts.push(`${key}=${String(value)}`);
944
+ }
945
+ }
946
+ return parts.join("&");
947
+ }
948
+ function vlog(message) {
949
+ process.stderr.write(`[verbose] ${message}
950
+ `);
951
+ }
952
+ var BithumbRestClient = class {
953
+ config;
954
+ rateLimiter;
955
+ constructor(config) {
956
+ this.config = config;
957
+ this.rateLimiter = new RateLimiter(3e4, config.verbose);
958
+ }
959
+ async publicGet(path3, query, rateLimit) {
960
+ return this.request({
961
+ method: "GET",
962
+ path: path3,
963
+ auth: "public",
964
+ query,
965
+ rateLimit
966
+ });
967
+ }
968
+ async privateGet(path3, query, rateLimit) {
969
+ return this.request({
970
+ method: "GET",
971
+ path: path3,
972
+ auth: "private",
973
+ query,
974
+ rateLimit
975
+ });
976
+ }
977
+ async privatePost(path3, body, rateLimit) {
978
+ return this.request({
979
+ method: "POST",
980
+ path: path3,
981
+ auth: "private",
982
+ body,
983
+ rateLimit
984
+ });
985
+ }
986
+ async privateDelete(path3, query, rateLimit) {
987
+ return this.request({
988
+ method: "DELETE",
989
+ path: path3,
990
+ auth: "private",
991
+ query,
992
+ rateLimit
993
+ });
994
+ }
995
+ // ─── Core request ────────────────────────────────────────────────
996
+ async request(reqConfig) {
997
+ const queryString = buildQueryString(reqConfig.query);
998
+ const requestPath = queryString.length > 0 ? `${reqConfig.path}?${queryString}` : reqConfig.path;
999
+ const url = `${this.config.baseUrl}${requestPath}`;
1000
+ if (this.config.verbose) vlog(`\u2192 ${reqConfig.method} ${url}`);
1001
+ if (reqConfig.rateLimit) await this.rateLimiter.consume(reqConfig.rateLimit);
1002
+ const headers = {
1003
+ "Content-Type": "application/json",
1004
+ Accept: "application/json"
1005
+ };
1006
+ if (reqConfig.auth === "private") {
1007
+ if (!this.config.hasAuth || !this.config.accessKey || !this.config.secretKey) {
1008
+ throw new ConfigError(
1009
+ "Private endpoint requires API credentials.",
1010
+ "Set BITHUMB_ACCESS_KEY and BITHUMB_SECRET_KEY."
1011
+ );
1012
+ }
1013
+ const hashStr = buildHashString(reqConfig.query, reqConfig.body);
1014
+ const token = signRequest(
1015
+ this.config.accessKey,
1016
+ this.config.secretKey,
1017
+ hashStr
1018
+ );
1019
+ headers["Authorization"] = `Bearer ${token}`;
1020
+ }
1021
+ const t0 = Date.now();
1022
+ let response;
1023
+ try {
1024
+ const fetchOptions = {
1025
+ method: reqConfig.method,
1026
+ headers,
1027
+ signal: AbortSignal.timeout(this.config.timeoutMs)
1028
+ };
1029
+ if (reqConfig.body && reqConfig.method === "POST") {
1030
+ fetchOptions.body = JSON.stringify(reqConfig.body);
1031
+ }
1032
+ response = await fetch(url, fetchOptions);
1033
+ } catch (error) {
1034
+ if (this.config.verbose) {
1035
+ vlog(`\u2717 NetworkError after ${Date.now() - t0}ms`);
1036
+ }
1037
+ throw new NetworkError(
1038
+ `Failed to call ${reqConfig.method} ${reqConfig.path}.`,
1039
+ `${reqConfig.method} ${reqConfig.path}`,
1040
+ error
1041
+ );
1042
+ }
1043
+ const rawText = await response.text();
1044
+ const elapsed = Date.now() - t0;
1045
+ if (this.config.verbose) {
1046
+ vlog(`\u2190 ${response.status} | ${rawText.length}B | ${elapsed}ms`);
1047
+ }
1048
+ let parsed;
1049
+ try {
1050
+ parsed = rawText ? JSON.parse(rawText) : null;
1051
+ } catch {
1052
+ throw new NetworkError(
1053
+ `Non-JSON response from ${reqConfig.method} ${reqConfig.path}.`,
1054
+ `${reqConfig.method} ${reqConfig.path}`
1055
+ );
1056
+ }
1057
+ if (parsed && typeof parsed === "object" && "error" in parsed) {
1058
+ const errResp = parsed;
1059
+ const errName = errResp.error?.name ?? "unknown";
1060
+ const errMsg = errResp.error?.message ?? "Unknown error";
1061
+ const endpoint = `${reqConfig.method} ${reqConfig.path}`;
1062
+ if (response.status === 401 || errName === "unauthorized") {
1063
+ throw new AuthenticationError(
1064
+ errMsg,
1065
+ "Check BITHUMB_ACCESS_KEY and BITHUMB_SECRET_KEY.",
1066
+ endpoint
1067
+ );
1068
+ }
1069
+ if (response.status === 429) {
1070
+ throw new RateLimitError(
1071
+ errMsg,
1072
+ "Reduce request frequency.",
1073
+ endpoint
1074
+ );
1075
+ }
1076
+ throw new BithumbApiError(errMsg, { code: errName, endpoint });
1077
+ }
1078
+ if (!response.ok) {
1079
+ throw new BithumbApiError(`HTTP ${response.status}`, {
1080
+ code: String(response.status),
1081
+ endpoint: `${reqConfig.method} ${reqConfig.path}`
1082
+ });
1083
+ }
1084
+ return {
1085
+ endpoint: `${reqConfig.method} ${reqConfig.path}`,
1086
+ requestTime: (/* @__PURE__ */ new Date()).toISOString(),
1087
+ data: parsed
1088
+ };
1089
+ }
1090
+ };
1091
+ function asRecord(args) {
1092
+ if (args && typeof args === "object" && !Array.isArray(args)) {
1093
+ return args;
1094
+ }
1095
+ return {};
1096
+ }
1097
+ function readString(args, key) {
1098
+ const v = args[key];
1099
+ if (v === void 0 || v === null || v === "") return void 0;
1100
+ return String(v);
1101
+ }
1102
+ function readNumber(args, key) {
1103
+ const v = args[key];
1104
+ if (v === void 0 || v === null || v === "") return void 0;
1105
+ const n = Number(v);
1106
+ if (!Number.isFinite(n)) return void 0;
1107
+ return n;
1108
+ }
1109
+ function readBoolean(args, key) {
1110
+ const v = args[key];
1111
+ if (v === void 0 || v === null) return void 0;
1112
+ if (typeof v === "boolean") return v;
1113
+ if (v === "true" || v === "1") return true;
1114
+ if (v === "false" || v === "0") return false;
1115
+ return void 0;
1116
+ }
1117
+ function readStringArray(args, key) {
1118
+ const v = args[key];
1119
+ if (v === void 0 || v === null) return void 0;
1120
+ if (Array.isArray(v)) return v.map(String);
1121
+ if (typeof v === "string") {
1122
+ return v.split(",").map((s) => s.trim()).filter(Boolean);
1123
+ }
1124
+ return void 0;
1125
+ }
1126
+ function requireString(args, key) {
1127
+ const v = readString(args, key);
1128
+ if (!v) {
1129
+ throw new ValidationError(
1130
+ `Missing required parameter "${key}".`,
1131
+ `Provide a non-empty "${key}" string.`
1132
+ );
1133
+ }
1134
+ return v;
1135
+ }
1136
+ function compactObject(obj) {
1137
+ const result = {};
1138
+ for (const [key, value] of Object.entries(obj)) {
1139
+ if (value !== void 0 && value !== null) {
1140
+ result[key] = value;
1141
+ }
1142
+ }
1143
+ return result;
1144
+ }
1145
+ function normalizeResponse(data) {
1146
+ if (data === null || data === void 0) return {};
1147
+ return data;
1148
+ }
1149
+ function publicRateLimit(key, rps = 150) {
1150
+ return { key: `public:${key}`, capacity: rps, refillPerSecond: rps };
1151
+ }
1152
+ function privateRateLimit(key, rps = 140) {
1153
+ return { key: `private:${key}`, capacity: rps, refillPerSecond: rps };
1154
+ }
1155
+ function orderRateLimit(key, rps = 10) {
1156
+ return { key: `order:${key}`, capacity: rps, refillPerSecond: rps };
1157
+ }
1158
+ function registerMarketTools() {
1159
+ return [
1160
+ // ── 1. market_get_markets ──────────────────────────────────────
1161
+ {
1162
+ name: "market_get_markets",
1163
+ module: "market",
1164
+ description: "\uBE57\uC378 \uAC70\uB798 \uAC00\uB2A5\uD55C \uB9C8\uCF13(\uAC70\uB798 \uD398\uC5B4) \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get all available trading pairs on Bithumb.",
1165
+ isWrite: false,
1166
+ inputSchema: {
1167
+ type: "object",
1168
+ properties: {
1169
+ isDetails: {
1170
+ type: "boolean",
1171
+ description: "Include detailed market info"
1172
+ }
1173
+ },
1174
+ required: []
1175
+ },
1176
+ handler: async (rawArgs, context) => {
1177
+ const args = asRecord(rawArgs);
1178
+ const response = await context.client.publicGet(
1179
+ "/v1/market/all",
1180
+ compactObject({ isDetails: readBoolean(args, "isDetails") }),
1181
+ publicRateLimit("market_get_markets")
1182
+ );
1183
+ return normalizeResponse(response);
1184
+ }
1185
+ },
1186
+ // ── 2. market_get_ticker ───────────────────────────────────────
1187
+ {
1188
+ name: "market_get_ticker",
1189
+ module: "market",
1190
+ description: "\uD604\uC7AC\uAC00(Ticker) \uC815\uBCF4\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get current price ticker for specified markets.",
1191
+ isWrite: false,
1192
+ inputSchema: {
1193
+ type: "object",
1194
+ properties: {
1195
+ markets: {
1196
+ type: "string",
1197
+ description: "Comma-separated market codes, e.g. KRW-BTC,KRW-ETH"
1198
+ }
1199
+ },
1200
+ required: ["markets"]
1201
+ },
1202
+ handler: async (rawArgs, context) => {
1203
+ const args = asRecord(rawArgs);
1204
+ const response = await context.client.publicGet(
1205
+ "/v1/ticker",
1206
+ compactObject({ markets: requireString(args, "markets") }),
1207
+ publicRateLimit("market_get_ticker")
1208
+ );
1209
+ return normalizeResponse(response);
1210
+ }
1211
+ },
1212
+ // ── 3. market_get_orderbook ────────────────────────────────────
1213
+ {
1214
+ name: "market_get_orderbook",
1215
+ module: "market",
1216
+ description: "\uD638\uAC00(Orderbook) \uC815\uBCF4\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get orderbook (bids/asks) for specified markets.",
1217
+ isWrite: false,
1218
+ inputSchema: {
1219
+ type: "object",
1220
+ properties: {
1221
+ markets: {
1222
+ type: "string",
1223
+ description: "Comma-separated market codes, e.g. KRW-BTC,KRW-ETH"
1224
+ }
1225
+ },
1226
+ required: ["markets"]
1227
+ },
1228
+ handler: async (rawArgs, context) => {
1229
+ const args = asRecord(rawArgs);
1230
+ const response = await context.client.publicGet(
1231
+ "/v1/orderbook",
1232
+ compactObject({ markets: requireString(args, "markets") }),
1233
+ publicRateLimit("market_get_orderbook")
1234
+ );
1235
+ return normalizeResponse(response);
1236
+ }
1237
+ },
1238
+ // ── 4. market_get_trades ───────────────────────────────────────
1239
+ {
1240
+ name: "market_get_trades",
1241
+ module: "market",
1242
+ description: "\uCD5C\uADFC \uCCB4\uACB0 \uB0B4\uC5ED\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get recent trades for a market.",
1243
+ isWrite: false,
1244
+ inputSchema: {
1245
+ type: "object",
1246
+ properties: {
1247
+ market: {
1248
+ type: "string",
1249
+ description: "Market code, e.g. KRW-BTC"
1250
+ },
1251
+ to: {
1252
+ type: "string",
1253
+ description: "Return trades before this timestamp (exclusive)"
1254
+ },
1255
+ count: {
1256
+ type: "number",
1257
+ description: "Number of trades to return (1-500)"
1258
+ },
1259
+ cursor: {
1260
+ type: "string",
1261
+ description: "Pagination cursor from previous response"
1262
+ },
1263
+ daysAgo: {
1264
+ type: "number",
1265
+ description: "Filter trades from N days ago (1-7)"
1266
+ }
1267
+ },
1268
+ required: ["market"]
1269
+ },
1270
+ handler: async (rawArgs, context) => {
1271
+ const args = asRecord(rawArgs);
1272
+ const response = await context.client.publicGet(
1273
+ "/v1/trades/ticks",
1274
+ compactObject({
1275
+ market: requireString(args, "market"),
1276
+ to: readString(args, "to"),
1277
+ count: readNumber(args, "count"),
1278
+ cursor: readString(args, "cursor"),
1279
+ daysAgo: readNumber(args, "daysAgo")
1280
+ }),
1281
+ publicRateLimit("market_get_trades")
1282
+ );
1283
+ return normalizeResponse(response);
1284
+ }
1285
+ },
1286
+ // ── 5. market_get_candles_minutes ──────────────────────────────
1287
+ {
1288
+ name: "market_get_candles_minutes",
1289
+ module: "market",
1290
+ description: "\uBD84(minute) \uCE94\uB4E4\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get minute candles (OHLCV).",
1291
+ isWrite: false,
1292
+ inputSchema: {
1293
+ type: "object",
1294
+ properties: {
1295
+ unit: {
1296
+ type: "number",
1297
+ enum: [1, 3, 5, 10, 15, 30, 60, 240],
1298
+ description: "Candle unit in minutes"
1299
+ },
1300
+ market: {
1301
+ type: "string",
1302
+ description: "Market code, e.g. KRW-BTC"
1303
+ },
1304
+ to: {
1305
+ type: "string",
1306
+ description: "Return candles before this timestamp"
1307
+ },
1308
+ count: {
1309
+ type: "number",
1310
+ description: "Number of candles to return (max 200)"
1311
+ }
1312
+ },
1313
+ required: ["unit", "market"]
1314
+ },
1315
+ handler: async (rawArgs, context) => {
1316
+ const args = asRecord(rawArgs);
1317
+ const unit = readNumber(args, "unit");
1318
+ if (!unit) throw new Error('Missing required parameter "unit".');
1319
+ const response = await context.client.publicGet(
1320
+ `/v1/candles/minutes/${unit}`,
1321
+ compactObject({
1322
+ market: requireString(args, "market"),
1323
+ to: readString(args, "to"),
1324
+ count: readNumber(args, "count")
1325
+ }),
1326
+ publicRateLimit("market_get_candles_minutes")
1327
+ );
1328
+ return normalizeResponse(response);
1329
+ }
1330
+ },
1331
+ // ── 6. market_get_candles_days ─────────────────────────────────
1332
+ {
1333
+ name: "market_get_candles_days",
1334
+ module: "market",
1335
+ description: "\uC77C(day) \uCE94\uB4E4\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get daily candles.",
1336
+ isWrite: false,
1337
+ inputSchema: {
1338
+ type: "object",
1339
+ properties: {
1340
+ market: {
1341
+ type: "string",
1342
+ description: "Market code, e.g. KRW-BTC"
1343
+ },
1344
+ to: {
1345
+ type: "string",
1346
+ description: "Return candles before this timestamp"
1347
+ },
1348
+ count: {
1349
+ type: "number",
1350
+ description: "Number of candles to return (max 200)"
1351
+ },
1352
+ convertingPriceUnit: {
1353
+ type: "string",
1354
+ description: "Price unit for conversion"
1355
+ }
1356
+ },
1357
+ required: ["market"]
1358
+ },
1359
+ handler: async (rawArgs, context) => {
1360
+ const args = asRecord(rawArgs);
1361
+ const response = await context.client.publicGet(
1362
+ "/v1/candles/days",
1363
+ compactObject({
1364
+ market: requireString(args, "market"),
1365
+ to: readString(args, "to"),
1366
+ count: readNumber(args, "count"),
1367
+ convertingPriceUnit: readString(args, "convertingPriceUnit")
1368
+ }),
1369
+ publicRateLimit("market_get_candles_days")
1370
+ );
1371
+ return normalizeResponse(response);
1372
+ }
1373
+ },
1374
+ // ── 7. market_get_candles_weeks ────────────────────────────────
1375
+ {
1376
+ name: "market_get_candles_weeks",
1377
+ module: "market",
1378
+ description: "\uC8FC(week) \uCE94\uB4E4\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get weekly candles.",
1379
+ isWrite: false,
1380
+ inputSchema: {
1381
+ type: "object",
1382
+ properties: {
1383
+ market: {
1384
+ type: "string",
1385
+ description: "Market code, e.g. KRW-BTC"
1386
+ },
1387
+ to: {
1388
+ type: "string",
1389
+ description: "Return candles before this timestamp"
1390
+ },
1391
+ count: {
1392
+ type: "number",
1393
+ description: "Number of candles to return (max 200)"
1394
+ }
1395
+ },
1396
+ required: ["market"]
1397
+ },
1398
+ handler: async (rawArgs, context) => {
1399
+ const args = asRecord(rawArgs);
1400
+ const response = await context.client.publicGet(
1401
+ "/v1/candles/weeks",
1402
+ compactObject({
1403
+ market: requireString(args, "market"),
1404
+ to: readString(args, "to"),
1405
+ count: readNumber(args, "count")
1406
+ }),
1407
+ publicRateLimit("market_get_candles_weeks")
1408
+ );
1409
+ return normalizeResponse(response);
1410
+ }
1411
+ },
1412
+ // ── 8. market_get_candles_months ───────────────────────────────
1413
+ {
1414
+ name: "market_get_candles_months",
1415
+ module: "market",
1416
+ description: "\uC6D4(month) \uCE94\uB4E4\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get monthly candles.",
1417
+ isWrite: false,
1418
+ inputSchema: {
1419
+ type: "object",
1420
+ properties: {
1421
+ market: {
1422
+ type: "string",
1423
+ description: "Market code, e.g. KRW-BTC"
1424
+ },
1425
+ to: {
1426
+ type: "string",
1427
+ description: "Return candles before this timestamp"
1428
+ },
1429
+ count: {
1430
+ type: "number",
1431
+ description: "Number of candles to return (max 200)"
1432
+ }
1433
+ },
1434
+ required: ["market"]
1435
+ },
1436
+ handler: async (rawArgs, context) => {
1437
+ const args = asRecord(rawArgs);
1438
+ const response = await context.client.publicGet(
1439
+ "/v1/candles/months",
1440
+ compactObject({
1441
+ market: requireString(args, "market"),
1442
+ to: readString(args, "to"),
1443
+ count: readNumber(args, "count")
1444
+ }),
1445
+ publicRateLimit("market_get_candles_months")
1446
+ );
1447
+ return normalizeResponse(response);
1448
+ }
1449
+ },
1450
+ // ── 9. market_get_warnings ─────────────────────────────────────
1451
+ {
1452
+ name: "market_get_warnings",
1453
+ module: "market",
1454
+ description: "\uD22C\uC790\uACBD\uBCF4 \uB9C8\uCF13 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get virtual asset warning market list.",
1455
+ isWrite: false,
1456
+ inputSchema: {
1457
+ type: "object",
1458
+ properties: {},
1459
+ required: []
1460
+ },
1461
+ handler: async (_rawArgs, context) => {
1462
+ const response = await context.client.publicGet(
1463
+ "/v1/market/virtual_asset_warning",
1464
+ {},
1465
+ publicRateLimit("market_get_warnings")
1466
+ );
1467
+ return normalizeResponse(response);
1468
+ }
1469
+ },
1470
+ // ── 10. market_get_notices ─────────────────────────────────────
1471
+ {
1472
+ name: "market_get_notices",
1473
+ module: "market",
1474
+ description: "\uACF5\uC9C0\uC0AC\uD56D \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get notice list.",
1475
+ isWrite: false,
1476
+ inputSchema: {
1477
+ type: "object",
1478
+ properties: {
1479
+ count: {
1480
+ type: "number",
1481
+ description: "Number of notices to return (min 1, max 20, default 5)"
1482
+ }
1483
+ },
1484
+ required: []
1485
+ },
1486
+ handler: async (rawArgs, context) => {
1487
+ const args = asRecord(rawArgs);
1488
+ const count = readNumber(args, "count");
1489
+ const response = await context.client.publicGet(
1490
+ "/v1/notices",
1491
+ compactObject({ count }),
1492
+ publicRateLimit("market_get_notices")
1493
+ );
1494
+ return normalizeResponse(response);
1495
+ }
1496
+ },
1497
+ // ── 11. market_get_fee_inout ───────────────────────────────────
1498
+ {
1499
+ name: "market_get_fee_inout",
1500
+ module: "market",
1501
+ description: "\uC785\uCD9C\uAE08 \uC218\uC218\uB8CC\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get deposit/withdrawal fee info for a currency.",
1502
+ isWrite: false,
1503
+ inputSchema: {
1504
+ type: "object",
1505
+ properties: {
1506
+ currency: {
1507
+ type: "string",
1508
+ description: 'Currency symbol (use "ALL" to retrieve all currencies), e.g. BTC, ETH'
1509
+ }
1510
+ },
1511
+ required: ["currency"]
1512
+ },
1513
+ handler: async (rawArgs, context) => {
1514
+ const args = asRecord(rawArgs);
1515
+ const currency = requireString(args, "currency");
1516
+ const response = await context.client.publicGet(
1517
+ `/v2/fee/inout/${currency}`,
1518
+ {},
1519
+ publicRateLimit("market_get_fee_inout")
1520
+ );
1521
+ return normalizeResponse(response);
1522
+ }
1523
+ }
1524
+ ];
1525
+ }
1526
+ function registerAccountTools() {
1527
+ return [
1528
+ // ── 1. account_get_balance ─────────────────────────────────────
1529
+ {
1530
+ name: "account_get_balance",
1531
+ module: "account",
1532
+ description: "\uC804\uCCB4 \uACC4\uC88C \uC794\uACE0\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get all account balances.",
1533
+ isWrite: false,
1534
+ inputSchema: {
1535
+ type: "object",
1536
+ properties: {},
1537
+ required: []
1538
+ },
1539
+ handler: async (_rawArgs, context) => {
1540
+ const response = await context.client.privateGet(
1541
+ "/v1/accounts",
1542
+ void 0,
1543
+ privateRateLimit("account_get_balance")
1544
+ );
1545
+ return normalizeResponse(response);
1546
+ }
1547
+ },
1548
+ // ── 2. account_get_order_chance ────────────────────────────────
1549
+ {
1550
+ name: "account_get_order_chance",
1551
+ module: "account",
1552
+ description: "\uC8FC\uBB38 \uAC00\uB2A5 \uC815\uBCF4\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get order chance info (available balance, fees, limits) for a market.",
1553
+ isWrite: false,
1554
+ inputSchema: {
1555
+ type: "object",
1556
+ properties: {
1557
+ market: {
1558
+ type: "string",
1559
+ description: "Market code, e.g. KRW-BTC"
1560
+ }
1561
+ },
1562
+ required: ["market"]
1563
+ },
1564
+ handler: async (rawArgs, context) => {
1565
+ const args = asRecord(rawArgs);
1566
+ const response = await context.client.privateGet(
1567
+ "/v1/orders/chance",
1568
+ compactObject({ market: requireString(args, "market") }),
1569
+ privateRateLimit("account_get_order_chance")
1570
+ );
1571
+ return normalizeResponse(response);
1572
+ }
1573
+ },
1574
+ // ── 3. account_get_wallet_status ───────────────────────────────
1575
+ {
1576
+ name: "account_get_wallet_status",
1577
+ module: "account",
1578
+ description: "\uC785\uCD9C\uAE08 \uD604\uD669\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get wallet status (block status, deposit/withdrawal availability).",
1579
+ isWrite: false,
1580
+ inputSchema: {
1581
+ type: "object",
1582
+ properties: {},
1583
+ required: []
1584
+ },
1585
+ handler: async (_rawArgs, context) => {
1586
+ const response = await context.client.privateGet(
1587
+ "/v1/status/wallet",
1588
+ {},
1589
+ privateRateLimit("account_get_wallet_status")
1590
+ );
1591
+ return normalizeResponse(response);
1592
+ }
1593
+ },
1594
+ // ── 4. account_get_api_keys ────────────────────────────────────
1595
+ {
1596
+ name: "account_get_api_keys",
1597
+ module: "account",
1598
+ description: "API \uD0A4 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get list of API keys and expiration dates.",
1599
+ isWrite: false,
1600
+ inputSchema: {
1601
+ type: "object",
1602
+ properties: {},
1603
+ required: []
1604
+ },
1605
+ handler: async (_rawArgs, context) => {
1606
+ const response = await context.client.privateGet(
1607
+ "/v1/api_keys",
1608
+ {},
1609
+ privateRateLimit("account_get_api_keys")
1610
+ );
1611
+ return normalizeResponse(response);
1612
+ }
1613
+ }
1614
+ ];
1615
+ }
1616
+ function registerTradeTools() {
1617
+ return [
1618
+ // ── 1. trade_get_order ─────────────────────────────────────────
1619
+ {
1620
+ name: "trade_get_order",
1621
+ module: "trade",
1622
+ description: "\uAC1C\uBCC4 \uC8FC\uBB38\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get a specific order by uuid or client_order_id.",
1623
+ isWrite: false,
1624
+ inputSchema: {
1625
+ type: "object",
1626
+ properties: {
1627
+ uuid: {
1628
+ type: "string",
1629
+ description: "Order UUID"
1630
+ },
1631
+ client_order_id: {
1632
+ type: "string",
1633
+ description: "Client-assigned order ID"
1634
+ }
1635
+ },
1636
+ required: []
1637
+ },
1638
+ handler: async (rawArgs, context) => {
1639
+ const args = asRecord(rawArgs);
1640
+ const response = await context.client.privateGet(
1641
+ "/v1/order",
1642
+ compactObject({
1643
+ uuid: readString(args, "uuid"),
1644
+ client_order_id: readString(args, "client_order_id")
1645
+ }),
1646
+ privateRateLimit("trade_get_order")
1647
+ );
1648
+ return normalizeResponse(response);
1649
+ }
1650
+ },
1651
+ // ── 2. trade_get_orders ────────────────────────────────────────
1652
+ {
1653
+ name: "trade_get_orders",
1654
+ module: "trade",
1655
+ description: "\uC8FC\uBB38 \uB9AC\uC2A4\uD2B8\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get list of orders with filters.",
1656
+ isWrite: false,
1657
+ inputSchema: {
1658
+ type: "object",
1659
+ properties: {
1660
+ market: {
1661
+ type: "string",
1662
+ description: "Market code, e.g. KRW-BTC"
1663
+ },
1664
+ state: {
1665
+ type: "string",
1666
+ enum: ["wait", "watch", "done", "cancel"],
1667
+ description: "Order state filter"
1668
+ },
1669
+ states: {
1670
+ type: "array",
1671
+ items: { type: "string" },
1672
+ description: "Multiple order state filters"
1673
+ },
1674
+ uuids: {
1675
+ type: "array",
1676
+ items: { type: "string" },
1677
+ description: "Filter by order UUIDs"
1678
+ },
1679
+ client_order_ids: {
1680
+ type: "array",
1681
+ items: { type: "string" },
1682
+ description: "Filter by client order IDs"
1683
+ },
1684
+ page: {
1685
+ type: "number",
1686
+ description: "Page number"
1687
+ },
1688
+ limit: {
1689
+ type: "number",
1690
+ description: "Number of results per page"
1691
+ },
1692
+ order_by: {
1693
+ type: "string",
1694
+ description: "Sort order"
1695
+ }
1696
+ },
1697
+ required: []
1698
+ },
1699
+ handler: async (rawArgs, context) => {
1700
+ const args = asRecord(rawArgs);
1701
+ const response = await context.client.privateGet(
1702
+ "/v1/orders",
1703
+ compactObject({
1704
+ market: readString(args, "market"),
1705
+ state: readString(args, "state"),
1706
+ states: readStringArray(args, "states"),
1707
+ uuids: readStringArray(args, "uuids"),
1708
+ client_order_ids: readStringArray(args, "client_order_ids"),
1709
+ page: readNumber(args, "page"),
1710
+ limit: readNumber(args, "limit"),
1711
+ order_by: readString(args, "order_by")
1712
+ }),
1713
+ privateRateLimit("trade_get_orders")
1714
+ );
1715
+ return normalizeResponse(response);
1716
+ }
1717
+ },
1718
+ // ── 3. trade_place_order ───────────────────────────────────────
1719
+ {
1720
+ name: "trade_place_order",
1721
+ module: "trade",
1722
+ description: '\uC8FC\uBB38\uC744 \uC0DD\uC131\uD569\uB2C8\uB2E4. Place a new spot order on Bithumb. Use when: user asks to buy/sell a single market at a specific price or quantity. Do NOT use: for multiple orders at once (use trade_batch_place); for time-weighted execution (use twap_place). Field: order_type \u2208 {limit, price, market}. limit: price+volume required. price(\uC2DC\uC7A5\uAC00 \uB9E4\uC218): only price (total KRW). market(\uC2DC\uC7A5\uAC00 \uB9E4\uB3C4): only volume (coin qty). CLI flag: --order-type (alias: --ord-type, deprecated). Response field `ord_type` is preserved as Bithumb upstream. Example call: {"market":"KRW-BTC","side":"bid","order_type":"limit","price":"50000000","volume":"0.0001"}',
1723
+ isWrite: true,
1724
+ inputSchema: {
1725
+ type: "object",
1726
+ properties: {
1727
+ market: {
1728
+ type: "string",
1729
+ description: "Market code, e.g. KRW-BTC"
1730
+ },
1731
+ side: {
1732
+ type: "string",
1733
+ enum: ["bid", "ask"],
1734
+ description: "Order side: bid (buy) or ask (sell)"
1735
+ },
1736
+ order_type: {
1737
+ type: "string",
1738
+ enum: ["limit", "price", "market"],
1739
+ description: "Order type: limit, price (market buy), market (market sell). CLI flag: --order-type (alias: --ord-type, deprecated). Response field is `ord_type` (upstream Bithumb), preserved as-is."
1740
+ },
1741
+ price: {
1742
+ type: "string",
1743
+ description: "Order price (required for limit and price orders)"
1744
+ },
1745
+ volume: {
1746
+ type: "string",
1747
+ description: "Order volume (required for limit and market orders)"
1748
+ },
1749
+ client_order_id: {
1750
+ type: "string",
1751
+ description: "Client-assigned order ID for idempotency"
1752
+ }
1753
+ },
1754
+ required: ["market", "side", "order_type"]
1755
+ },
1756
+ handler: async (rawArgs, context) => {
1757
+ const args = asRecord(rawArgs);
1758
+ const response = await context.client.privatePost(
1759
+ "/v2/orders",
1760
+ compactObject({
1761
+ market: requireString(args, "market"),
1762
+ side: requireString(args, "side"),
1763
+ order_type: requireString(args, "order_type"),
1764
+ price: readString(args, "price"),
1765
+ volume: readString(args, "volume"),
1766
+ client_order_id: readString(args, "client_order_id")
1767
+ }),
1768
+ orderRateLimit("trade_place_order")
1769
+ );
1770
+ return normalizeResponse(response);
1771
+ }
1772
+ },
1773
+ // ── 4. trade_cancel_order ──────────────────────────────────────
1774
+ {
1775
+ name: "trade_cancel_order",
1776
+ module: "trade",
1777
+ description: "\uC8FC\uBB38\uC744 \uCDE8\uC18C\uD569\uB2C8\uB2E4. Cancel an order by order_id or client_order_id.",
1778
+ isWrite: true,
1779
+ inputSchema: {
1780
+ type: "object",
1781
+ properties: {
1782
+ order_id: {
1783
+ type: "string",
1784
+ description: "Order UUID to cancel"
1785
+ },
1786
+ client_order_id: {
1787
+ type: "string",
1788
+ description: "Client-assigned order ID to cancel"
1789
+ }
1790
+ },
1791
+ required: []
1792
+ },
1793
+ handler: async (rawArgs, context) => {
1794
+ const args = asRecord(rawArgs);
1795
+ const response = await context.client.privateDelete(
1796
+ "/v2/order",
1797
+ compactObject({
1798
+ order_id: readString(args, "order_id"),
1799
+ client_order_id: readString(args, "client_order_id")
1800
+ }),
1801
+ orderRateLimit("trade_cancel_order")
1802
+ );
1803
+ return normalizeResponse(response);
1804
+ }
1805
+ },
1806
+ // ── 5. trade_batch_place ───────────────────────────────────────
1807
+ {
1808
+ name: "trade_batch_place",
1809
+ module: "trade",
1810
+ description: '\uB2E4\uAC74 \uC8FC\uBB38\uC744 \uC694\uCCAD\uD569\uB2C8\uB2E4. Place multiple orders in a single batch (max 20). Use when: user asks for multiple orders at once (e.g., grid orders, simultaneous BTC+ETH limit buys). Do NOT use: for a single order (use trade_place_order); for time-sliced execution (use twap_place). Each order item field: `order_type` (canonical). Legacy `ord_type` is auto-normalized to `order_type` by the CLI; MCP callers should always send `order_type`. Partial-failure semantics: each item may succeed or fail independently. Do NOT auto-retry failed items; surface them to the user. Example call: {"batch_orders":[{"market":"KRW-BTC","side":"bid","order_type":"limit","price":"50000000","volume":"0.0001"},{"market":"KRW-ETH","side":"bid","order_type":"limit","price":"4500000","volume":"0.001"}]}',
1811
+ isWrite: true,
1812
+ inputSchema: {
1813
+ type: "object",
1814
+ properties: {
1815
+ batch_orders: {
1816
+ type: "array",
1817
+ items: {
1818
+ type: "object",
1819
+ properties: {
1820
+ market: { type: "string", description: "Market code, e.g. KRW-BTC" },
1821
+ side: { type: "string", enum: ["bid", "ask"], description: "Order side" },
1822
+ order_type: { type: "string", enum: ["limit", "price", "market"], description: "Order type" },
1823
+ price: { type: "string", description: "Order price" },
1824
+ volume: { type: "string", description: "Order volume" },
1825
+ client_order_id: { type: "string", description: "Client-assigned order ID" }
1826
+ },
1827
+ required: ["market", "side", "order_type"]
1828
+ },
1829
+ description: "Array of order objects (max 20)"
1830
+ }
1831
+ },
1832
+ required: ["batch_orders"]
1833
+ },
1834
+ handler: async (rawArgs, context) => {
1835
+ const args = asRecord(rawArgs);
1836
+ const batchOrders = args.batch_orders;
1837
+ if (!Array.isArray(batchOrders) || batchOrders.length === 0) {
1838
+ throw new ValidationError(
1839
+ 'Missing required parameter "batch_orders".',
1840
+ "Provide an array of order objects."
1841
+ );
1842
+ }
1843
+ const response = await context.client.privatePost(
1844
+ "/v2/orders/batch",
1845
+ { batch_orders: batchOrders },
1846
+ orderRateLimit("trade_batch_place")
1847
+ );
1848
+ return normalizeResponse(response);
1849
+ }
1850
+ },
1851
+ // ── 6. trade_batch_cancel ──────────────────────────────────────
1852
+ {
1853
+ name: "trade_batch_cancel",
1854
+ module: "trade",
1855
+ description: "\uB2E4\uAC74 \uC8FC\uBB38\uC744 \uCDE8\uC18C\uD569\uB2C8\uB2E4. Cancel multiple orders (max 30). Provide order_ids or client_order_ids.",
1856
+ isWrite: true,
1857
+ inputSchema: {
1858
+ type: "object",
1859
+ properties: {
1860
+ order_ids: {
1861
+ type: "array",
1862
+ items: { type: "string" },
1863
+ description: "List of order UUIDs to cancel (max 30)"
1864
+ },
1865
+ client_order_ids: {
1866
+ type: "array",
1867
+ items: { type: "string" },
1868
+ description: "List of client-assigned order IDs to cancel (max 30)"
1869
+ }
1870
+ },
1871
+ required: []
1872
+ },
1873
+ handler: async (rawArgs, context) => {
1874
+ const args = asRecord(rawArgs);
1875
+ const response = await context.client.privatePost(
1876
+ "/v2/orders/cancel",
1877
+ compactObject({
1878
+ order_ids: readStringArray(args, "order_ids"),
1879
+ client_order_ids: readStringArray(args, "client_order_ids")
1880
+ }),
1881
+ orderRateLimit("trade_batch_cancel")
1882
+ );
1883
+ return normalizeResponse(response);
1884
+ }
1885
+ }
1886
+ ];
1887
+ }
1888
+ var DEFAULT_LOG_DIR = path.join(os.homedir(), ".bithumb", "logs");
1889
+ function getLogPaths(logDir, days = 7) {
1890
+ const paths = [];
1891
+ const now = /* @__PURE__ */ new Date();
1892
+ for (let i = 0; i < days; i++) {
1893
+ const d = new Date(now);
1894
+ d.setDate(d.getDate() - i);
1895
+ const yyyy = d.getFullYear();
1896
+ const mm = String(d.getMonth() + 1).padStart(2, "0");
1897
+ const dd = String(d.getDate()).padStart(2, "0");
1898
+ paths.push(path.join(logDir, `trade-${yyyy}-${mm}-${dd}.log`));
1899
+ }
1900
+ return paths;
1901
+ }
1902
+ function readEntries(logDir) {
1903
+ const filePaths = getLogPaths(logDir);
1904
+ const entries = [];
1905
+ for (const filePath of filePaths) {
1906
+ if (!fs.existsSync(filePath)) continue;
1907
+ let content;
1908
+ try {
1909
+ content = fs.readFileSync(filePath, "utf8");
1910
+ } catch {
1911
+ continue;
1912
+ }
1913
+ for (const line of content.split("\n")) {
1914
+ const trimmed = line.trim();
1915
+ if (!trimmed) continue;
1916
+ try {
1917
+ const entry = JSON.parse(trimmed);
1918
+ entries.push(entry);
1919
+ } catch {
1920
+ }
1921
+ }
1922
+ }
1923
+ return entries;
1924
+ }
1925
+ function extractTool(entry) {
1926
+ if (entry.tool) return entry.tool;
1927
+ if (typeof entry.message === "string" && entry.message.startsWith("tool:")) {
1928
+ return entry.message.slice("tool:".length);
1929
+ }
1930
+ return void 0;
1931
+ }
1932
+ function registerAuditTools(logDir) {
1933
+ const resolvedLogDir = logDir ?? DEFAULT_LOG_DIR;
1934
+ return [
1935
+ // ── trade_get_history ──────────────────────────────────────────
1936
+ {
1937
+ name: "trade_get_history",
1938
+ module: "account",
1939
+ description: "\uB85C\uCEEC \uB85C\uADF8\uC5D0\uC11C \uAC70\uB798 \uC774\uB825\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Read local audit logs filtered by tool, level, and time.",
1940
+ isWrite: false,
1941
+ inputSchema: {
1942
+ type: "object",
1943
+ properties: {
1944
+ limit: {
1945
+ type: "number",
1946
+ description: "Maximum number of entries to return (default 20)."
1947
+ },
1948
+ tool: {
1949
+ type: "string",
1950
+ description: "Filter by tool name."
1951
+ },
1952
+ level: {
1953
+ type: "string",
1954
+ enum: ["INFO", "WARN", "ERROR", "DEBUG"],
1955
+ description: "Filter by log level (case-insensitive)."
1956
+ },
1957
+ since: {
1958
+ type: "string",
1959
+ description: "ISO 8601 timestamp; return entries at or after this time."
1960
+ }
1961
+ },
1962
+ required: []
1963
+ },
1964
+ handler: async (rawArgs, _context) => {
1965
+ const args = asRecord(rawArgs);
1966
+ const limit = readNumber(args, "limit") ?? 20;
1967
+ const toolFilter = readString(args, "tool");
1968
+ const levelFilter = readString(args, "level")?.toLowerCase();
1969
+ const sinceStr = readString(args, "since");
1970
+ const sinceMs = sinceStr ? new Date(sinceStr).getTime() : void 0;
1971
+ const entries = readEntries(resolvedLogDir);
1972
+ const filtered = entries.filter((entry) => {
1973
+ if (toolFilter) {
1974
+ const t = extractTool(entry);
1975
+ if (!t || !t.includes(toolFilter)) return false;
1976
+ }
1977
+ if (levelFilter && entry.level !== levelFilter) return false;
1978
+ if (sinceMs !== void 0) {
1979
+ const entryMs = new Date(entry.ts).getTime();
1980
+ if (Number.isNaN(entryMs) || entryMs < sinceMs) return false;
1981
+ }
1982
+ return true;
1983
+ });
1984
+ filtered.sort((a, b) => {
1985
+ const ta = new Date(a.ts).getTime();
1986
+ const tb = new Date(b.ts).getTime();
1987
+ return tb - ta;
1988
+ });
1989
+ return {
1990
+ endpoint: "local:audit-log",
1991
+ requestTime: (/* @__PURE__ */ new Date()).toISOString(),
1992
+ data: filtered.slice(0, limit)
1993
+ };
1994
+ }
1995
+ }
1996
+ ];
1997
+ }
1998
+ var BITHUMB_API_BASE_URL = "https://api.bithumb.com";
1999
+ var MODULES = ["market", "account", "trade", "twap", "withdraw", "deposit"];
2000
+ var DEFAULT_MODULES = ["market", "account", "trade", "twap", "withdraw", "deposit"];
2001
+ async function checkApiReachability(baseUrl) {
2002
+ try {
2003
+ const controller = new AbortController();
2004
+ const timeout = setTimeout(() => controller.abort(), 5e3);
2005
+ const res = await fetch(`${baseUrl}/v1/market/all`, {
2006
+ signal: controller.signal
2007
+ });
2008
+ clearTimeout(timeout);
2009
+ return {
2010
+ name: "API Reachability",
2011
+ status: res.ok ? "pass" : "fail",
2012
+ message: res.ok ? `${baseUrl} reachable (HTTP ${res.status})` : `${baseUrl} returned HTTP ${res.status}`
2013
+ };
2014
+ } catch (err) {
2015
+ return {
2016
+ name: "API Reachability",
2017
+ status: "fail",
2018
+ message: `Cannot reach ${baseUrl}: ${err instanceof Error ? err.message : String(err)}`
2019
+ };
2020
+ }
2021
+ }
2022
+ function checkAuthentication() {
2023
+ const accessKey = process.env.BITHUMB_ACCESS_KEY?.trim();
2024
+ const secretKey = process.env.BITHUMB_SECRET_KEY?.trim();
2025
+ if (accessKey && secretKey) {
2026
+ return { name: "Authentication", status: "pass", message: "API keys configured via environment variables" };
2027
+ }
2028
+ const tomlPath = join(homedir(), ".bithumb", "config.toml");
2029
+ if (existsSync(tomlPath)) {
2030
+ return { name: "Authentication", status: "pass", message: "config.toml found (credentials may be in profile)" };
2031
+ }
2032
+ if (accessKey || secretKey) {
2033
+ return { name: "Authentication", status: "fail", message: "Partial credentials: set both BITHUMB_ACCESS_KEY and BITHUMB_SECRET_KEY" };
2034
+ }
2035
+ return { name: "Authentication", status: "fail", message: "No credentials found (set env vars or create ~/.bithumb/config.toml)" };
2036
+ }
2037
+ function checkTomlConfig() {
2038
+ const tomlPath = join(homedir(), ".bithumb", "config.toml");
2039
+ if (existsSync(tomlPath)) {
2040
+ return { name: "TOML Config", status: "pass", message: `Found ${tomlPath}` };
2041
+ }
2042
+ return { name: "TOML Config", status: "fail", message: `Not found: ${tomlPath} (optional \u2014 use 'setup' command to create)` };
2043
+ }
2044
+ function checkModules(enabledModules) {
2045
+ return {
2046
+ name: "Enabled Modules",
2047
+ status: "pass",
2048
+ message: `Active: ${enabledModules.join(", ")}`
2049
+ };
2050
+ }
2051
+ var ALL_MODULES = ["market", "account", "trade", "twap", "withdraw", "deposit"];
2052
+ function registerDiagnoseTools() {
2053
+ return [
2054
+ {
2055
+ name: "system_get_capabilities",
2056
+ module: "account",
2057
+ description: "Return server capabilities and module availability for agent planning.",
2058
+ isWrite: false,
2059
+ inputSchema: {
2060
+ type: "object",
2061
+ properties: {},
2062
+ additionalProperties: false
2063
+ },
2064
+ handler: async (_args, context) => {
2065
+ const enabledModules = new Set(context.config.modules);
2066
+ const moduleAvailability = {};
2067
+ for (const mod of ALL_MODULES) {
2068
+ if (!enabledModules.has(mod)) {
2069
+ moduleAvailability[mod] = { status: "disabled", reasonCode: "MODULE_FILTERED" };
2070
+ } else if (mod !== "market" && !context.config.hasAuth) {
2071
+ moduleAvailability[mod] = { status: "requires_auth", reasonCode: "AUTH_MISSING" };
2072
+ } else {
2073
+ moduleAvailability[mod] = { status: "enabled" };
2074
+ }
2075
+ }
2076
+ return {
2077
+ endpoint: "local:capabilities",
2078
+ requestTime: (/* @__PURE__ */ new Date()).toISOString(),
2079
+ data: {
2080
+ readOnly: context.config.readOnly,
2081
+ hasAuth: context.config.hasAuth,
2082
+ moduleAvailability
2083
+ }
2084
+ };
2085
+ }
2086
+ },
2087
+ {
2088
+ name: "system_diagnose",
2089
+ module: "account",
2090
+ description: "Run diagnostic checks on the Bithumb Trade Kit configuration. Checks API reachability, authentication, TOML config, and module status.",
2091
+ isWrite: false,
2092
+ inputSchema: {
2093
+ type: "object",
2094
+ properties: {}
2095
+ },
2096
+ handler: async (_args, context) => {
2097
+ const baseUrl = context?.config?.baseUrl ?? BITHUMB_API_BASE_URL;
2098
+ const modules = context?.config?.modules ?? [];
2099
+ const checks = [];
2100
+ checks.push(await checkApiReachability(baseUrl));
2101
+ checks.push(checkAuthentication());
2102
+ checks.push(checkTomlConfig());
2103
+ checks.push(checkModules(modules));
2104
+ const passed = checks.filter((c) => c.status === "pass").length;
2105
+ const total = checks.length;
2106
+ return {
2107
+ endpoint: "local:diagnose",
2108
+ requestTime: (/* @__PURE__ */ new Date()).toISOString(),
2109
+ data: {
2110
+ summary: `${passed}/${total} checks passed`,
2111
+ checks
2112
+ }
2113
+ };
2114
+ }
2115
+ }
2116
+ ];
2117
+ }
2118
+ function registerTwapTools() {
2119
+ return [
2120
+ // ── 6. twap_place_order ────────────────────────────────────────
2121
+ {
2122
+ name: "twap_place_order",
2123
+ module: "twap",
2124
+ description: "TWAP \uC8FC\uBB38\uC744 \uC694\uCCAD\uD569\uB2C8\uB2E4. Place a TWAP (Time-Weighted Average Price) order.",
2125
+ isWrite: true,
2126
+ inputSchema: {
2127
+ type: "object",
2128
+ properties: {
2129
+ market: {
2130
+ type: "string",
2131
+ description: "\uAC70\uB798 \uB300\uC0C1 \uD398\uC5B4\uC758 \uACE0\uC720 \uC2EC\uBCFC (\uC608\uC2DC: KRW-BTC)"
2132
+ },
2133
+ side: {
2134
+ type: "string",
2135
+ enum: ["bid", "ask"],
2136
+ description: "\uC8FC\uBB38 \uC885\uB958: bid (\uB9E4\uC218), ask (\uB9E4\uB3C4)"
2137
+ },
2138
+ duration: {
2139
+ type: "string",
2140
+ description: "\uC8FC\uBB38 \uC2DC\uAC04 - TWAP \uC8FC\uBB38\uC774 \uC9C4\uD589\uB418\uB294 \uC2DC\uAC04(\uCD08). min 300, max 43200"
2141
+ },
2142
+ frequency: {
2143
+ type: "string",
2144
+ enum: ["5", "15", "20", "30", "60", "120"],
2145
+ description: "\uC8FC\uBB38 \uAC04\uACA9(\uCD08)"
2146
+ },
2147
+ volume: {
2148
+ type: "string",
2149
+ description: "\uC8FC\uBB38 \uC218\uB7C9 (\uB9E4\uB3C4 \uC2DC \uD544\uC218)"
2150
+ },
2151
+ price: {
2152
+ type: "string",
2153
+ description: "\uC8FC\uBB38 \uAC00\uACA9 (\uB9E4\uC218 \uC2DC \uD544\uC218)"
2154
+ }
2155
+ },
2156
+ required: ["market", "side", "duration", "frequency"]
2157
+ },
2158
+ handler: async (rawArgs, context) => {
2159
+ const args = asRecord(rawArgs);
2160
+ const response = await context.client.privatePost(
2161
+ "/v1/twap",
2162
+ compactObject({
2163
+ market: requireString(args, "market"),
2164
+ side: requireString(args, "side"),
2165
+ duration: requireString(args, "duration"),
2166
+ frequency: requireString(args, "frequency"),
2167
+ volume: readString(args, "volume"),
2168
+ price: readString(args, "price")
2169
+ }),
2170
+ orderRateLimit("twap_place_order")
2171
+ );
2172
+ return normalizeResponse(response);
2173
+ }
2174
+ },
2175
+ // ── 7. twap_get_orders ─────────────────────────────────────────
2176
+ {
2177
+ name: "twap_get_orders",
2178
+ module: "twap",
2179
+ description: "TWAP \uC8FC\uBB38 \uB0B4\uC5ED\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get TWAP order history.",
2180
+ isWrite: false,
2181
+ inputSchema: {
2182
+ type: "object",
2183
+ properties: {
2184
+ market: {
2185
+ type: "string",
2186
+ description: "\uAC70\uB798 \uB300\uC0C1 \uD398\uC5B4\uC758 \uACE0\uC720 \uC2EC\uBCFC (\uC608\uC2DC: KRW-BTC)"
2187
+ },
2188
+ uuids: {
2189
+ type: "array",
2190
+ items: { type: "string" },
2191
+ description: "TWAP \uC8FC\uBB38 ID \uBAA9\uB85D"
2192
+ },
2193
+ state: {
2194
+ type: "string",
2195
+ enum: ["progress", "done", "cancel"],
2196
+ description: "\uC8FC\uBB38 \uC0C1\uD0DC: progress (\uC9C4\uD589\uC911, default), done (\uC644\uB8CC), cancel (\uCDE8\uC18C)"
2197
+ },
2198
+ next_key: {
2199
+ type: "string",
2200
+ description: "\uB2E4\uC74C \uD398\uC774\uC9C0 \uC870\uD68C\uB97C \uC704\uD55C \uCEE4\uC11C \uAC12"
2201
+ },
2202
+ limit: {
2203
+ type: "number",
2204
+ description: "\uAC1C\uC218 \uC81C\uD55C (max 100)"
2205
+ },
2206
+ order_by: {
2207
+ type: "string",
2208
+ enum: ["asc", "desc"],
2209
+ description: "\uC870\uD68C \uACB0\uACFC \uC815\uB82C \uBC29\uC2DD: asc (\uC624\uB984\uCC28\uC21C), desc (\uB0B4\uB9BC\uCC28\uC21C, default)"
2210
+ }
2211
+ },
2212
+ required: []
2213
+ },
2214
+ handler: async (rawArgs, context) => {
2215
+ const args = asRecord(rawArgs);
2216
+ const response = await context.client.privateGet(
2217
+ "/v1/twap",
2218
+ compactObject({
2219
+ market: readString(args, "market"),
2220
+ uuids: readStringArray(args, "uuids"),
2221
+ state: readString(args, "state"),
2222
+ next_key: readString(args, "next_key"),
2223
+ limit: readNumber(args, "limit"),
2224
+ order_by: readString(args, "order_by")
2225
+ }),
2226
+ privateRateLimit("twap_get_orders")
2227
+ );
2228
+ return normalizeResponse(response);
2229
+ }
2230
+ },
2231
+ // ── 8. twap_cancel_order ───────────────────────────────────────
2232
+ {
2233
+ name: "twap_cancel_order",
2234
+ module: "twap",
2235
+ description: "TWAP \uC8FC\uBB38\uC744 \uCDE8\uC18C\uD569\uB2C8\uB2E4. Cancel a TWAP order.",
2236
+ isWrite: true,
2237
+ inputSchema: {
2238
+ type: "object",
2239
+ properties: {
2240
+ algo_order_id: {
2241
+ type: "string",
2242
+ description: "\uCDE8\uC18C\uD560 TWAP \uC8FC\uBB38 ID"
2243
+ }
2244
+ },
2245
+ required: ["algo_order_id"]
2246
+ },
2247
+ handler: async (rawArgs, context) => {
2248
+ const args = asRecord(rawArgs);
2249
+ const algo_order_id = requireString(args, "algo_order_id");
2250
+ const response = await context.client.privateDelete(
2251
+ "/v1/twap",
2252
+ { algo_order_id },
2253
+ orderRateLimit("twap_cancel_order")
2254
+ );
2255
+ return normalizeResponse(response);
2256
+ }
2257
+ }
2258
+ ];
2259
+ }
2260
+ function registerWithdrawTools() {
2261
+ return [
2262
+ // ── 9. withdraw_get_chance ───────────────────────────────────────
2263
+ {
2264
+ name: "withdraw_get_chance",
2265
+ module: "withdraw",
2266
+ description: "\uCD9C\uAE08 \uAC00\uB2A5 \uC815\uBCF4\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get withdrawal chance info (available balance, fees).",
2267
+ isWrite: false,
2268
+ inputSchema: {
2269
+ type: "object",
2270
+ properties: {
2271
+ currency: {
2272
+ type: "string",
2273
+ description: "Currency symbol, e.g. BTC"
2274
+ },
2275
+ net_type: {
2276
+ type: "string",
2277
+ description: "Withdrawal network, e.g. BTC, DASH"
2278
+ }
2279
+ },
2280
+ required: ["currency", "net_type"]
2281
+ },
2282
+ handler: async (rawArgs, context) => {
2283
+ const args = asRecord(rawArgs);
2284
+ const response = await context.client.privateGet(
2285
+ "/v1/withdraws/chance",
2286
+ {
2287
+ currency: requireString(args, "currency"),
2288
+ net_type: requireString(args, "net_type")
2289
+ },
2290
+ privateRateLimit("withdraw_get_chance")
2291
+ );
2292
+ return normalizeResponse(response);
2293
+ }
2294
+ },
2295
+ // ── 10. withdraw_get ─────────────────────────────────────────────
2296
+ {
2297
+ name: "withdraw_get",
2298
+ module: "withdraw",
2299
+ description: "\uAC1C\uBCC4 \uCD9C\uAE08\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get a specific withdrawal by currency.",
2300
+ isWrite: false,
2301
+ inputSchema: {
2302
+ type: "object",
2303
+ properties: {
2304
+ currency: {
2305
+ type: "string",
2306
+ description: "Currency symbol, e.g. BTC"
2307
+ },
2308
+ uuid: {
2309
+ type: "string",
2310
+ description: "Withdrawal unique ID"
2311
+ },
2312
+ txid: {
2313
+ type: "string",
2314
+ description: "Withdrawal transaction ID"
2315
+ }
2316
+ },
2317
+ required: ["currency"]
2318
+ },
2319
+ handler: async (rawArgs, context) => {
2320
+ const args = asRecord(rawArgs);
2321
+ const response = await context.client.privateGet(
2322
+ "/v1/withdraw",
2323
+ compactObject({
2324
+ currency: requireString(args, "currency"),
2325
+ uuid: readString(args, "uuid"),
2326
+ txid: readString(args, "txid")
2327
+ }),
2328
+ privateRateLimit("withdraw_get")
2329
+ );
2330
+ return normalizeResponse(response);
2331
+ }
2332
+ },
2333
+ // ── 11. withdraw_get_list ────────────────────────────────────────
2334
+ {
2335
+ name: "withdraw_get_list",
2336
+ module: "withdraw",
2337
+ description: "\uCD9C\uAE08 \uB9AC\uC2A4\uD2B8\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get list of coin withdrawals.",
2338
+ isWrite: false,
2339
+ inputSchema: {
2340
+ type: "object",
2341
+ properties: {
2342
+ currency: {
2343
+ type: "string",
2344
+ description: "Currency symbol, e.g. BTC"
2345
+ },
2346
+ state: {
2347
+ type: "string",
2348
+ enum: ["PROCESSING", "DONE", "CANCELED"],
2349
+ description: "Withdrawal state filter"
2350
+ },
2351
+ uuids: {
2352
+ type: "array",
2353
+ items: { type: "string" },
2354
+ description: "Filter by withdrawal UUIDs"
2355
+ },
2356
+ txids: {
2357
+ type: "array",
2358
+ items: { type: "string" },
2359
+ description: "Filter by transaction IDs"
2360
+ },
2361
+ limit: {
2362
+ type: "number",
2363
+ description: "Number of results per page (max 100)"
2364
+ },
2365
+ page: {
2366
+ type: "number",
2367
+ description: "Page number"
2368
+ },
2369
+ order_by: {
2370
+ type: "string",
2371
+ enum: ["asc", "desc"],
2372
+ description: "Sort order (default: desc)"
2373
+ }
2374
+ },
2375
+ required: []
2376
+ },
2377
+ handler: async (rawArgs, context) => {
2378
+ const args = asRecord(rawArgs);
2379
+ const response = await context.client.privateGet(
2380
+ "/v1/withdraws",
2381
+ compactObject({
2382
+ currency: readString(args, "currency"),
2383
+ state: readString(args, "state"),
2384
+ uuids: readStringArray(args, "uuids"),
2385
+ txids: readStringArray(args, "txids"),
2386
+ limit: readNumber(args, "limit"),
2387
+ page: readNumber(args, "page"),
2388
+ order_by: readString(args, "order_by")
2389
+ }),
2390
+ privateRateLimit("withdraw_get_list")
2391
+ );
2392
+ return normalizeResponse(response);
2393
+ }
2394
+ },
2395
+ // ── 12. withdraw_get_list_krw ────────────────────────────────────
2396
+ {
2397
+ name: "withdraw_get_list_krw",
2398
+ module: "withdraw",
2399
+ description: "\uC6D0\uD654 \uCD9C\uAE08 \uB9AC\uC2A4\uD2B8\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get list of KRW withdrawals.",
2400
+ isWrite: false,
2401
+ inputSchema: {
2402
+ type: "object",
2403
+ properties: {
2404
+ state: {
2405
+ type: "string",
2406
+ enum: ["PROCESSING", "DONE", "CANCELED"],
2407
+ description: "Withdrawal state filter"
2408
+ },
2409
+ uuids: {
2410
+ type: "array",
2411
+ items: { type: "string" },
2412
+ description: "Filter by withdrawal UUIDs"
2413
+ },
2414
+ txids: {
2415
+ type: "array",
2416
+ items: { type: "string" },
2417
+ description: "Filter by transaction IDs"
2418
+ },
2419
+ limit: {
2420
+ type: "number",
2421
+ description: "Number of results per page (max 100)"
2422
+ },
2423
+ page: {
2424
+ type: "number",
2425
+ description: "Page number"
2426
+ },
2427
+ order_by: {
2428
+ type: "string",
2429
+ enum: ["asc", "desc"],
2430
+ description: "Sort order (default: desc)"
2431
+ }
2432
+ },
2433
+ required: []
2434
+ },
2435
+ handler: async (rawArgs, context) => {
2436
+ const args = asRecord(rawArgs);
2437
+ const response = await context.client.privateGet(
2438
+ "/v1/withdraws/krw",
2439
+ compactObject({
2440
+ state: readString(args, "state"),
2441
+ uuids: readStringArray(args, "uuids"),
2442
+ txids: readStringArray(args, "txids"),
2443
+ limit: readNumber(args, "limit"),
2444
+ page: readNumber(args, "page"),
2445
+ order_by: readString(args, "order_by")
2446
+ }),
2447
+ privateRateLimit("withdraw_get_list_krw")
2448
+ );
2449
+ return normalizeResponse(response);
2450
+ }
2451
+ },
2452
+ // ── 13. withdraw_coin ────────────────────────────────────────────
2453
+ {
2454
+ name: "withdraw_coin",
2455
+ module: "withdraw",
2456
+ description: "\uC2E4\uC81C \uAC00\uC0C1 \uC790\uC0B0\uC744 \uCD9C\uAE08\uD569\uB2C8\uB2E4. \uC774 \uC791\uC5C5\uC740 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. Withdraw cryptocurrency. This action is IRREVERSIBLE. Use when: user has explicitly approved a withdrawal AND the full pre-flight checklist has run (account_get_balance, account_get_wallet_status, withdraw_get_chance, withdraw_get_addresses, market_get_fee_inout). Do NOT use: without prior `withdraw_get_chance` to confirm net_type/min/fee; without `withdraw_get_addresses` to confirm destination is in the allow-list; without explicit user confirmation of the full destination string. Multi-network coins (USDT/USDC/XRP): always run `withdraw_get_chance` first to discover supported `net_type`. Wrong net_type = permanent loss. secondary_address (memo/tag): mandatory for XRP/EOS/ATOM-class coins. Missing it = lost funds.",
2457
+ isWrite: true,
2458
+ inputSchema: {
2459
+ type: "object",
2460
+ properties: {
2461
+ currency: {
2462
+ type: "string",
2463
+ description: "Currency symbol, e.g. BTC"
2464
+ },
2465
+ net_type: {
2466
+ type: "string",
2467
+ description: "Withdrawal network, e.g. BTC, DASH"
2468
+ },
2469
+ amount: {
2470
+ type: "string",
2471
+ description: "Withdrawal amount"
2472
+ },
2473
+ address: {
2474
+ type: "string",
2475
+ description: "Registered withdrawal address"
2476
+ },
2477
+ secondary_address: {
2478
+ type: "string",
2479
+ description: "Secondary address (for certain assets)"
2480
+ },
2481
+ exchange_name: {
2482
+ type: "string",
2483
+ description: "Exchange name (English)"
2484
+ },
2485
+ receiver_type: {
2486
+ type: "string",
2487
+ enum: ["personal", "corporation"],
2488
+ description: "Receiver type: personal or corporation"
2489
+ },
2490
+ receiver_ko_name: {
2491
+ type: "string",
2492
+ description: "Receiver Korean name"
2493
+ },
2494
+ receiver_en_name: {
2495
+ type: "string",
2496
+ description: "Receiver English name"
2497
+ },
2498
+ receiver_corp_ko_name: {
2499
+ type: "string",
2500
+ description: "Corporation Korean name (required if corporation)"
2501
+ },
2502
+ receiver_corp_en_name: {
2503
+ type: "string",
2504
+ description: "Corporation English name (required if corporation)"
2505
+ }
2506
+ },
2507
+ required: ["currency", "net_type", "amount", "address"]
2508
+ },
2509
+ handler: async (rawArgs, context) => {
2510
+ const args = asRecord(rawArgs);
2511
+ const response = await context.client.privatePost(
2512
+ "/v1/withdraws/coin",
2513
+ compactObject({
2514
+ currency: requireString(args, "currency"),
2515
+ net_type: requireString(args, "net_type"),
2516
+ amount: requireString(args, "amount"),
2517
+ address: requireString(args, "address"),
2518
+ secondary_address: readString(args, "secondary_address"),
2519
+ exchange_name: readString(args, "exchange_name"),
2520
+ receiver_type: readString(args, "receiver_type"),
2521
+ receiver_ko_name: readString(args, "receiver_ko_name"),
2522
+ receiver_en_name: readString(args, "receiver_en_name"),
2523
+ receiver_corp_ko_name: readString(args, "receiver_corp_ko_name"),
2524
+ receiver_corp_en_name: readString(args, "receiver_corp_en_name")
2525
+ }),
2526
+ orderRateLimit("withdraw_coin")
2527
+ );
2528
+ return normalizeResponse(response);
2529
+ }
2530
+ },
2531
+ // ── 14. withdraw_krw ─────────────────────────────────────────────
2532
+ {
2533
+ name: "withdraw_krw",
2534
+ module: "withdraw",
2535
+ description: "\uB4F1\uB85D\uB41C \uACC4\uC88C\uB85C \uC6D0\uD654\uB97C \uCD9C\uAE08\uD569\uB2C8\uB2E4. 2\uCC28 \uC778\uC99D(\uCE74\uCE74\uC624)\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. Withdraw KRW to registered bank account. Requires 2FA (Kakao).",
2536
+ isWrite: true,
2537
+ inputSchema: {
2538
+ type: "object",
2539
+ properties: {
2540
+ amount: {
2541
+ type: "string",
2542
+ description: "Withdrawal amount in KRW"
2543
+ },
2544
+ two_factor_type: {
2545
+ type: "string",
2546
+ description: "2FA method (e.g. kakao)"
2547
+ }
2548
+ },
2549
+ required: ["amount", "two_factor_type"]
2550
+ },
2551
+ handler: async (rawArgs, context) => {
2552
+ const args = asRecord(rawArgs);
2553
+ const response = await context.client.privatePost(
2554
+ "/v1/withdraws/krw",
2555
+ {
2556
+ amount: requireString(args, "amount"),
2557
+ two_factor_type: requireString(args, "two_factor_type")
2558
+ },
2559
+ orderRateLimit("withdraw_krw")
2560
+ );
2561
+ return normalizeResponse(response);
2562
+ }
2563
+ },
2564
+ // ── 15. withdraw_cancel_coin ─────────────────────────────────────
2565
+ {
2566
+ name: "withdraw_cancel_coin",
2567
+ module: "withdraw",
2568
+ description: "\uAC00\uC0C1 \uC790\uC0B0 \uCD9C\uAE08\uC744 \uCDE8\uC18C\uD569\uB2C8\uB2E4. Cancel a cryptocurrency withdrawal.",
2569
+ isWrite: true,
2570
+ inputSchema: {
2571
+ type: "object",
2572
+ properties: {
2573
+ withdrawal_id: {
2574
+ type: "string",
2575
+ description: "Withdrawal unique ID to cancel"
2576
+ }
2577
+ },
2578
+ required: ["withdrawal_id"]
2579
+ },
2580
+ handler: async (rawArgs, context) => {
2581
+ const args = asRecord(rawArgs);
2582
+ const response = await context.client.privateDelete(
2583
+ "/v1/withdraws/coin",
2584
+ {
2585
+ withdrawal_id: requireString(args, "withdrawal_id")
2586
+ },
2587
+ orderRateLimit("withdraw_cancel_coin")
2588
+ );
2589
+ return normalizeResponse(response);
2590
+ }
2591
+ },
2592
+ // ── 16. withdraw_get_addresses ───────────────────────────────────
2593
+ {
2594
+ name: "withdraw_get_addresses",
2595
+ module: "withdraw",
2596
+ description: "\uCD9C\uAE08 \uD5C8\uC6A9 \uC8FC\uC18C \uB9AC\uC2A4\uD2B8\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get list of allowed withdrawal addresses.",
2597
+ isWrite: false,
2598
+ inputSchema: {
2599
+ type: "object",
2600
+ properties: {},
2601
+ required: []
2602
+ },
2603
+ handler: async (_rawArgs, context) => {
2604
+ const response = await context.client.privateGet(
2605
+ "/v1/withdraws/coin_addresses",
2606
+ {},
2607
+ privateRateLimit("withdraw_get_addresses")
2608
+ );
2609
+ return normalizeResponse(response);
2610
+ }
2611
+ }
2612
+ ];
2613
+ }
2614
+ function registerDepositTools() {
2615
+ return [
2616
+ // ── 17. deposit_get ───────────────────────────────────────────
2617
+ {
2618
+ name: "deposit_get",
2619
+ module: "deposit",
2620
+ description: '\uAC1C\uBCC4 \uC785\uAE08\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get a single deposit by currency + uuid (or single txid). Use when: user has a specific deposit UUID and wants the full record. Do NOT use: to search by transaction id list \u2014 use `deposit_get_list` with `txids` (array). The CLI blocks `deposit get --txids` and redirects to `deposit list --txids`. Example call: {"currency":"BTC","uuid":"12345678-...."}',
2621
+ isWrite: false,
2622
+ inputSchema: {
2623
+ type: "object",
2624
+ properties: {
2625
+ currency: {
2626
+ type: "string",
2627
+ description: "Currency symbol, e.g. BTC"
2628
+ },
2629
+ uuid: {
2630
+ type: "string",
2631
+ description: "Deposit UUID"
2632
+ },
2633
+ txid: {
2634
+ type: "string",
2635
+ description: "Deposit TXID"
2636
+ }
2637
+ },
2638
+ required: ["currency"]
2639
+ },
2640
+ handler: async (rawArgs, context) => {
2641
+ const args = asRecord(rawArgs);
2642
+ const response = await context.client.privateGet(
2643
+ "/v1/deposit",
2644
+ compactObject({
2645
+ currency: requireString(args, "currency"),
2646
+ uuid: readString(args, "uuid"),
2647
+ txid: readString(args, "txid")
2648
+ }),
2649
+ privateRateLimit("deposit_get")
2650
+ );
2651
+ return normalizeResponse(response);
2652
+ }
2653
+ },
2654
+ // ── 18. deposit_get_list ──────────────────────────────────────
2655
+ {
2656
+ name: "deposit_get_list",
2657
+ module: "deposit",
2658
+ description: `\uC785\uAE08 \uB9AC\uC2A4\uD2B8\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get list of coin deposits with optional filters. Use when: user asks 'find this txid' / 'recent deposits' / 'list deposits in PROCESSING state'. Do NOT use: when you already have a deposit UUID and want a single record (use \`deposit_get\`). Example call (txid lookup): {"txids":["0xabc123..."]}`,
2659
+ isWrite: false,
2660
+ inputSchema: {
2661
+ type: "object",
2662
+ properties: {
2663
+ currency: {
2664
+ type: "string",
2665
+ description: "Currency symbol, e.g. BTC"
2666
+ },
2667
+ state: {
2668
+ type: "string",
2669
+ description: "Deposit state filter"
2670
+ },
2671
+ uuids: {
2672
+ type: "array",
2673
+ items: { type: "string" },
2674
+ description: "Filter by deposit UUIDs"
2675
+ },
2676
+ txids: {
2677
+ type: "array",
2678
+ items: { type: "string" },
2679
+ description: "Filter by deposit TXIDs"
2680
+ },
2681
+ limit: {
2682
+ type: "number",
2683
+ description: "Number of results (max 100)"
2684
+ },
2685
+ page: {
2686
+ type: "number",
2687
+ description: "Page number (default 1)"
2688
+ },
2689
+ order_by: {
2690
+ type: "string",
2691
+ description: "Sort order: asc or desc (default desc)"
2692
+ }
2693
+ },
2694
+ required: []
2695
+ },
2696
+ handler: async (rawArgs, context) => {
2697
+ const args = asRecord(rawArgs);
2698
+ const response = await context.client.privateGet(
2699
+ "/v1/deposits",
2700
+ compactObject({
2701
+ currency: readString(args, "currency"),
2702
+ state: readString(args, "state"),
2703
+ uuids: readStringArray(args, "uuids"),
2704
+ txids: readStringArray(args, "txids"),
2705
+ limit: readNumber(args, "limit"),
2706
+ page: readNumber(args, "page"),
2707
+ order_by: readString(args, "order_by")
2708
+ }),
2709
+ privateRateLimit("deposit_get_list")
2710
+ );
2711
+ return normalizeResponse(response);
2712
+ }
2713
+ },
2714
+ // ── 19. deposit_get_list_krw ──────────────────────────────────
2715
+ {
2716
+ name: "deposit_get_list_krw",
2717
+ module: "deposit",
2718
+ description: "\uC6D0\uD654 \uC785\uAE08 \uB9AC\uC2A4\uD2B8\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get list of KRW deposits.",
2719
+ isWrite: false,
2720
+ inputSchema: {
2721
+ type: "object",
2722
+ properties: {
2723
+ state: {
2724
+ type: "string",
2725
+ description: "Deposit state: PROCESSING, ACCEPTED, CANCELED"
2726
+ },
2727
+ uuids: {
2728
+ type: "array",
2729
+ items: { type: "string" },
2730
+ description: "Filter by deposit UUIDs"
2731
+ },
2732
+ txids: {
2733
+ type: "array",
2734
+ items: { type: "string" },
2735
+ description: "Filter by deposit TXIDs"
2736
+ },
2737
+ limit: {
2738
+ type: "number",
2739
+ description: "Number of results (max 100)"
2740
+ },
2741
+ page: {
2742
+ type: "number",
2743
+ description: "Page number (default 1)"
2744
+ },
2745
+ order_by: {
2746
+ type: "string",
2747
+ description: "Sort order: asc or desc (default desc)"
2748
+ }
2749
+ },
2750
+ required: []
2751
+ },
2752
+ handler: async (rawArgs, context) => {
2753
+ const args = asRecord(rawArgs);
2754
+ const response = await context.client.privateGet(
2755
+ "/v1/deposits/krw",
2756
+ compactObject({
2757
+ state: readString(args, "state"),
2758
+ uuids: readStringArray(args, "uuids"),
2759
+ txids: readStringArray(args, "txids"),
2760
+ limit: readNumber(args, "limit"),
2761
+ page: readNumber(args, "page"),
2762
+ order_by: readString(args, "order_by")
2763
+ }),
2764
+ privateRateLimit("deposit_get_list_krw")
2765
+ );
2766
+ return normalizeResponse(response);
2767
+ }
2768
+ },
2769
+ // ── 20. deposit_krw ──────────────────────────────────────────
2770
+ {
2771
+ name: "deposit_krw",
2772
+ module: "deposit",
2773
+ description: "\uC6D0\uD654 \uC785\uAE08\uC744 \uC694\uCCAD\uD569\uB2C8\uB2E4. 2\uCC28 \uC778\uC99D(\uCE74\uCE74\uC624)\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. Request KRW deposit. Requires 2FA (Kakao).",
2774
+ isWrite: true,
2775
+ inputSchema: {
2776
+ type: "object",
2777
+ properties: {
2778
+ amount: {
2779
+ type: "string",
2780
+ description: "Deposit amount in KRW"
2781
+ },
2782
+ two_factor_type: {
2783
+ type: "string",
2784
+ description: "2FA method, e.g. kakao"
2785
+ }
2786
+ },
2787
+ required: ["amount", "two_factor_type"]
2788
+ },
2789
+ handler: async (rawArgs, context) => {
2790
+ const args = asRecord(rawArgs);
2791
+ const response = await context.client.privatePost(
2792
+ "/v1/deposits/krw",
2793
+ {
2794
+ amount: requireString(args, "amount"),
2795
+ two_factor_type: requireString(args, "two_factor_type")
2796
+ },
2797
+ privateRateLimit("deposit_krw")
2798
+ );
2799
+ return normalizeResponse(response);
2800
+ }
2801
+ },
2802
+ // ── 21. deposit_generate_address ─────────────────────────────
2803
+ {
2804
+ name: "deposit_generate_address",
2805
+ module: "deposit",
2806
+ description: "\uC785\uAE08 \uC8FC\uC18C\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4. Generate a new deposit address.",
2807
+ isWrite: true,
2808
+ inputSchema: {
2809
+ type: "object",
2810
+ properties: {
2811
+ currency: {
2812
+ type: "string",
2813
+ description: "Currency symbol, e.g. BTC"
2814
+ },
2815
+ net_type: {
2816
+ type: "string",
2817
+ description: "Network type, e.g. BTC, ETH"
2818
+ }
2819
+ },
2820
+ required: ["currency", "net_type"]
2821
+ },
2822
+ handler: async (rawArgs, context) => {
2823
+ const args = asRecord(rawArgs);
2824
+ const response = await context.client.privatePost(
2825
+ "/v1/deposits/generate_coin_address",
2826
+ {
2827
+ currency: requireString(args, "currency"),
2828
+ net_type: requireString(args, "net_type")
2829
+ },
2830
+ privateRateLimit("deposit_generate_address")
2831
+ );
2832
+ return normalizeResponse(response);
2833
+ }
2834
+ },
2835
+ // ── 22. deposit_get_addresses ────────────────────────────────
2836
+ {
2837
+ name: "deposit_get_addresses",
2838
+ module: "deposit",
2839
+ description: "\uC804\uCCB4 \uC785\uAE08 \uC8FC\uC18C\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get all deposit addresses.",
2840
+ isWrite: false,
2841
+ inputSchema: {
2842
+ type: "object",
2843
+ properties: {},
2844
+ required: []
2845
+ },
2846
+ handler: async (_rawArgs, context) => {
2847
+ const response = await context.client.privateGet(
2848
+ "/v1/deposits/coin_addresses",
2849
+ {},
2850
+ privateRateLimit("deposit_get_addresses")
2851
+ );
2852
+ return normalizeResponse(response);
2853
+ }
2854
+ },
2855
+ // ── 23. deposit_get_address ──────────────────────────────────
2856
+ {
2857
+ name: "deposit_get_address",
2858
+ module: "deposit",
2859
+ description: "\uAC1C\uBCC4 \uC785\uAE08 \uC8FC\uC18C\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get deposit address for a specific currency and network.",
2860
+ isWrite: false,
2861
+ inputSchema: {
2862
+ type: "object",
2863
+ properties: {
2864
+ currency: {
2865
+ type: "string",
2866
+ description: "Currency symbol, e.g. BTC"
2867
+ },
2868
+ net_type: {
2869
+ type: "string",
2870
+ description: "Network type, e.g. BTC, ETH"
2871
+ }
2872
+ },
2873
+ required: ["currency", "net_type"]
2874
+ },
2875
+ handler: async (rawArgs, context) => {
2876
+ const args = asRecord(rawArgs);
2877
+ const response = await context.client.privateGet(
2878
+ "/v1/deposits/coin_address",
2879
+ {
2880
+ currency: requireString(args, "currency"),
2881
+ net_type: requireString(args, "net_type")
2882
+ },
2883
+ privateRateLimit("deposit_get_address")
2884
+ );
2885
+ return normalizeResponse(response);
2886
+ }
2887
+ }
2888
+ ];
2889
+ }
2890
+ function allToolSpecs() {
2891
+ return [
2892
+ ...registerMarketTools(),
2893
+ ...registerAccountTools(),
2894
+ ...registerTradeTools(),
2895
+ ...registerAuditTools(),
2896
+ ...registerDiagnoseTools(),
2897
+ ...registerTwapTools(),
2898
+ ...registerWithdrawTools(),
2899
+ ...registerDepositTools()
2900
+ ];
2901
+ }
2902
+ function buildTools(config) {
2903
+ const enabledModules = new Set(config.modules);
2904
+ const tools = allToolSpecs().filter((t) => enabledModules.has(t.module));
2905
+ return config.readOnly ? tools.filter((t) => !t.isWrite) : tools;
2906
+ }
2907
+ function toMcpTool(tool) {
2908
+ return {
2909
+ name: tool.name,
2910
+ description: tool.description,
2911
+ inputSchema: tool.inputSchema,
2912
+ annotations: {
2913
+ readOnlyHint: !tool.isWrite,
2914
+ destructiveHint: tool.isWrite,
2915
+ idempotentHint: !tool.isWrite,
2916
+ openWorldHint: true
2917
+ }
2918
+ };
2919
+ }
2920
+ function configFilePath() {
2921
+ return join2(homedir2(), ".bithumb", "config.toml");
2922
+ }
2923
+ function readFullConfig() {
2924
+ const path3 = configFilePath();
2925
+ if (!existsSync2(path3)) return { profiles: {} };
2926
+ const raw = readFileSync(path3, "utf-8");
2927
+ try {
2928
+ return parse(raw);
2929
+ } catch (err) {
2930
+ throw new ConfigError(
2931
+ `Failed to parse ${path3}: ${err instanceof Error ? err.message : String(err)}`,
2932
+ "Check TOML syntax in your config file, or delete and re-create it."
2933
+ );
2934
+ }
2935
+ }
2936
+ function readTomlProfile(profileName) {
2937
+ const config = readFullConfig();
2938
+ const name = profileName ?? config.default_profile ?? "default";
2939
+ return config.profiles?.[name] ?? {};
2940
+ }
2941
+ function loadConfig(options) {
2942
+ const toml = readTomlProfile(options?.profile);
2943
+ const accessKey = process.env.BITHUMB_ACCESS_KEY?.trim() ?? toml.access_key;
2944
+ const secretKey = process.env.BITHUMB_SECRET_KEY?.trim() ?? toml.secret_key;
2945
+ const hasAuth = Boolean(accessKey && secretKey);
2946
+ const partialAuth = Boolean(accessKey) || Boolean(secretKey);
2947
+ if (partialAuth && !hasAuth) {
2948
+ throw new ConfigError(
2949
+ "Partial API credentials.",
2950
+ "Set both BITHUMB_ACCESS_KEY and BITHUMB_SECRET_KEY (env vars or config.toml profile)."
2951
+ );
2952
+ }
2953
+ const baseUrl = (process.env.BITHUMB_API_BASE_URL?.trim() ?? toml.base_url ?? BITHUMB_API_BASE_URL).replace(/\/+$/, "");
2954
+ const rawTimeout = process.env.BITHUMB_TIMEOUT_MS ? Number(process.env.BITHUMB_TIMEOUT_MS) : toml.timeout_ms ?? 15e3;
2955
+ if (!Number.isFinite(rawTimeout) || rawTimeout <= 0) {
2956
+ throw new ConfigError(
2957
+ "Invalid timeout.",
2958
+ "BITHUMB_TIMEOUT_MS must be a positive integer."
2959
+ );
2960
+ }
2961
+ let modules = [...DEFAULT_MODULES];
2962
+ if (options?.modules) {
2963
+ const requested = options.modules.split(",").map((s) => s.trim()).filter(Boolean);
2964
+ if (requested.length > 0) {
2965
+ let isAll = false;
2966
+ for (const m of requested) {
2967
+ if (m === "all") {
2968
+ modules = [...MODULES];
2969
+ isAll = true;
2970
+ break;
2971
+ }
2972
+ if (!MODULES.includes(m)) {
2973
+ throw new ConfigError(
2974
+ `Unknown module "${m}".`,
2975
+ `Use: ${MODULES.join(", ")} or "all".`
2976
+ );
2977
+ }
2978
+ }
2979
+ if (!isAll) modules = requested;
2980
+ }
2981
+ }
2982
+ return {
2983
+ accessKey,
2984
+ secretKey,
2985
+ hasAuth,
2986
+ baseUrl,
2987
+ timeoutMs: Math.floor(rawTimeout),
2988
+ modules,
2989
+ readOnly: options?.readOnly ?? false,
2990
+ verbose: options?.verbose ?? false
2991
+ };
2992
+ }
2993
+ var LEVEL_ORDER = {
2994
+ debug: 0,
2995
+ info: 1,
2996
+ warn: 2,
2997
+ error: 3
2998
+ };
2999
+ var SENSITIVE_KEY_PATTERN = /accessKey|secretKey|password|secret|token|jwt/i;
3000
+ function redactSensitive(obj) {
3001
+ const result = {};
3002
+ for (const [key, value] of Object.entries(obj)) {
3003
+ if (SENSITIVE_KEY_PATTERN.test(key)) {
3004
+ result[key] = "***REDACTED***";
3005
+ } else if (value && typeof value === "object" && !Array.isArray(value)) {
3006
+ result[key] = redactSensitive(value);
3007
+ } else {
3008
+ result[key] = value;
3009
+ }
3010
+ }
3011
+ return result;
3012
+ }
3013
+ function todayDateString() {
3014
+ const d = /* @__PURE__ */ new Date();
3015
+ const yyyy = d.getFullYear();
3016
+ const mm = String(d.getMonth() + 1).padStart(2, "0");
3017
+ const dd = String(d.getDate()).padStart(2, "0");
3018
+ return `${yyyy}-${mm}-${dd}`;
3019
+ }
3020
+ var TradeLogger = class {
3021
+ logDir;
3022
+ minLevel;
3023
+ verbose;
3024
+ constructor(minLevelOrOptions) {
3025
+ if (typeof minLevelOrOptions === "string") {
3026
+ this.logDir = join3(homedir3(), ".bithumb", "logs");
3027
+ this.minLevel = minLevelOrOptions;
3028
+ this.verbose = false;
3029
+ } else {
3030
+ this.logDir = minLevelOrOptions?.logDir ?? join3(homedir3(), ".bithumb", "logs");
3031
+ this.minLevel = minLevelOrOptions?.minLevel ?? "info";
3032
+ this.verbose = minLevelOrOptions?.verbose ?? false;
3033
+ }
3034
+ try {
3035
+ mkdirSync2(this.logDir, { recursive: true });
3036
+ } catch {
3037
+ }
3038
+ }
3039
+ debug(message, meta) {
3040
+ this.log("debug", message, meta);
3041
+ }
3042
+ info(message, meta) {
3043
+ this.log("info", message, meta);
3044
+ }
3045
+ warn(message, meta) {
3046
+ this.log("warn", message, meta);
3047
+ }
3048
+ error(message, meta) {
3049
+ this.log("error", message, meta);
3050
+ }
3051
+ /** Log a tool invocation result (used by MCP server). */
3052
+ logTool(level, toolName, args, result, elapsedMs) {
3053
+ this.log(level, `tool:${toolName}`, {
3054
+ args,
3055
+ result,
3056
+ elapsedMs
3057
+ });
3058
+ }
3059
+ log(level, message, meta) {
3060
+ if (LEVEL_ORDER[level] < LEVEL_ORDER[this.minLevel]) return;
3061
+ const entry = {
3062
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
3063
+ level,
3064
+ message,
3065
+ ...meta ? redactSensitive(meta) : {}
3066
+ };
3067
+ const line = JSON.stringify(entry);
3068
+ if (this.verbose || level === "error") {
3069
+ process.stderr.write(`[${level}] ${message}
3070
+ `);
3071
+ }
3072
+ try {
3073
+ const filePath = join3(this.logDir, `trade-${todayDateString()}.log`);
3074
+ appendFileSync(filePath, line + "\n", "utf8");
3075
+ } catch {
3076
+ }
3077
+ }
3078
+ };
3079
+ var CACHE_DIR = join4(homedir4(), ".bithumb");
3080
+ var CACHE_FILE = join4(CACHE_DIR, "update-check.json");
3081
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
3082
+ function isNewerVersion(current, latest) {
3083
+ const parse2 = (v) => v.replace(/^v/, "").split(".").map((n) => parseInt(n, 10) || 0);
3084
+ const cur = parse2(current);
3085
+ const lat = parse2(latest);
3086
+ for (let i = 0; i < Math.max(cur.length, lat.length); i++) {
3087
+ const c = cur[i] ?? 0;
3088
+ const l = lat[i] ?? 0;
3089
+ if (l > c) return true;
3090
+ if (l < c) return false;
3091
+ }
3092
+ return false;
3093
+ }
3094
+ async function fetchDistTags(packageName) {
3095
+ try {
3096
+ const url = `https://registry.npmjs.org/-/package/${encodeURIComponent(packageName)}/dist-tags`;
3097
+ const res = await fetch(url, { signal: AbortSignal.timeout(5e3) });
3098
+ if (!res.ok) return null;
3099
+ return await res.json();
3100
+ } catch {
3101
+ return null;
3102
+ }
3103
+ }
3104
+ async function fetchLatestVersion(packageName) {
3105
+ const tags = await fetchDistTags(packageName);
3106
+ return tags?.latest ?? null;
3107
+ }
3108
+ function readCache() {
3109
+ try {
3110
+ if (!existsSync3(CACHE_FILE)) return null;
3111
+ const raw = readFileSync2(CACHE_FILE, "utf8");
3112
+ return JSON.parse(raw);
3113
+ } catch {
3114
+ return null;
3115
+ }
3116
+ }
3117
+ function writeCache(data) {
3118
+ try {
3119
+ if (!existsSync3(CACHE_DIR)) {
3120
+ mkdirSync3(CACHE_DIR, { recursive: true });
3121
+ }
3122
+ writeFileSync2(CACHE_FILE, JSON.stringify(data), "utf8");
3123
+ } catch {
3124
+ }
3125
+ }
3126
+ function isCacheStale(cache) {
3127
+ return Date.now() - cache.checkedAt > CACHE_TTL_MS;
3128
+ }
3129
+ function checkForUpdates(packageName, currentVersion) {
3130
+ try {
3131
+ const cache = readCache();
3132
+ if (cache && !isCacheStale(cache)) {
3133
+ if (isNewerVersion(currentVersion, cache.latestVersion)) {
3134
+ process.stderr.write(
3135
+ `
3136
+ Update available: ${currentVersion} \u2192 ${cache.latestVersion}
3137
+ Run: npm install -g ${packageName}
3138
+
3139
+ `
3140
+ );
3141
+ }
3142
+ return;
3143
+ }
3144
+ setImmediate(() => {
3145
+ fetchLatestVersion(packageName).then((latest) => {
3146
+ if (latest) {
3147
+ writeCache({ latestVersion: latest, checkedAt: Date.now() });
3148
+ if (isNewerVersion(currentVersion, latest)) {
3149
+ process.stderr.write(
3150
+ `
3151
+ Update available: ${currentVersion} \u2192 ${latest}
3152
+ Run: npm install -g ${packageName}
3153
+
3154
+ `
3155
+ );
3156
+ }
3157
+ }
3158
+ }).catch(() => {
3159
+ });
3160
+ });
3161
+ } catch {
3162
+ }
3163
+ }
3164
+ var CLIENT_NAMES = {
3165
+ "claude-desktop": "Claude Desktop",
3166
+ cursor: "Cursor",
3167
+ windsurf: "Windsurf",
3168
+ vscode: "VS Code",
3169
+ "claude-code": "Claude Code CLI"
3170
+ };
3171
+ var SUPPORTED_CLIENTS = Object.keys(CLIENT_NAMES);
3172
+ function appData() {
3173
+ return process.env.APPDATA ?? path2.join(os2.homedir(), "AppData", "Roaming");
3174
+ }
3175
+ var CLAUDE_CONFIG_FILE = "claude_desktop_config.json";
3176
+ function findMsStoreClaudePath() {
3177
+ const localAppData = process.env.LOCALAPPDATA ?? path2.join(os2.homedir(), "AppData", "Local");
3178
+ const packagesDir = path2.join(localAppData, "Packages");
3179
+ try {
3180
+ const entries = fs2.readdirSync(packagesDir);
3181
+ const claudePkg = entries.find((e) => e.startsWith("Claude_"));
3182
+ if (claudePkg) {
3183
+ const configPath = path2.join(
3184
+ packagesDir,
3185
+ claudePkg,
3186
+ "LocalCache",
3187
+ "Roaming",
3188
+ "Claude",
3189
+ CLAUDE_CONFIG_FILE
3190
+ );
3191
+ if (fs2.existsSync(configPath) || fs2.existsSync(path2.dirname(configPath))) {
3192
+ return configPath;
3193
+ }
3194
+ }
3195
+ } catch {
3196
+ }
3197
+ return null;
3198
+ }
3199
+ function getConfigPath(client) {
3200
+ const home = os2.homedir();
3201
+ const platform = process.platform;
3202
+ switch (client) {
3203
+ case "claude-desktop":
3204
+ if (platform === "win32") {
3205
+ return findMsStoreClaudePath() ?? path2.join(appData(), "Claude", CLAUDE_CONFIG_FILE);
3206
+ }
3207
+ if (platform === "darwin") {
3208
+ return path2.join(home, "Library", "Application Support", "Claude", CLAUDE_CONFIG_FILE);
3209
+ }
3210
+ return path2.join(process.env.XDG_CONFIG_HOME ?? path2.join(home, ".config"), "Claude", CLAUDE_CONFIG_FILE);
3211
+ case "cursor":
3212
+ return path2.join(home, ".cursor", "mcp.json");
3213
+ case "windsurf":
3214
+ return path2.join(home, ".codeium", "windsurf", "mcp_config.json");
3215
+ case "vscode":
3216
+ return path2.join(process.cwd(), ".mcp.json");
3217
+ case "claude-code":
3218
+ return null;
3219
+ }
3220
+ }
3221
+ var NPX_PACKAGE = "@bithumb-tradekit/mcp";
3222
+ function buildEntry(client, args) {
3223
+ if (client === "vscode") {
3224
+ return { type: "stdio", command: "bithumb-trade-mcp", args };
3225
+ }
3226
+ return { command: "npx", args: ["-y", NPX_PACKAGE, ...args] };
3227
+ }
3228
+ function buildArgs(options) {
3229
+ const args = [];
3230
+ if (options.profile) args.push("--profile", options.profile);
3231
+ args.push("--modules", options.modules ?? "all");
3232
+ return args;
3233
+ }
3234
+ function mergeJsonConfig(configPath, serverName, entry) {
3235
+ const dir = path2.dirname(configPath);
3236
+ if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
3237
+ let data = {};
3238
+ if (fs2.existsSync(configPath)) {
3239
+ const raw = fs2.readFileSync(configPath, "utf-8");
3240
+ try {
3241
+ data = JSON.parse(raw);
3242
+ } catch {
3243
+ throw new Error(`Failed to parse existing config at ${configPath}`);
3244
+ }
3245
+ const backupPath = configPath + ".bak";
3246
+ fs2.copyFileSync(configPath, backupPath);
3247
+ process.stdout.write(` Backup \u2192 ${backupPath}
3248
+ `);
3249
+ }
3250
+ if (typeof data.mcpServers !== "object" || data.mcpServers === null) {
3251
+ data.mcpServers = {};
3252
+ }
3253
+ data.mcpServers[serverName] = entry;
3254
+ fs2.writeFileSync(configPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
3255
+ }
3256
+ function printSetupUsage() {
3257
+ process.stdout.write(
3258
+ `Usage: bithumb-trade-mcp setup --client <client> [--profile <name>] [--modules <list>]
3259
+
3260
+ Clients:
3261
+ ` + SUPPORTED_CLIENTS.map((id) => ` ${id.padEnd(16)} ${CLIENT_NAMES[id]}`).join("\n") + `
3262
+
3263
+ Options:
3264
+ --profile <name> Profile from ${configFilePath()} (default: uses default_profile)
3265
+ --modules <list> Comma-separated modules or "all" (default: all)
3266
+ `
3267
+ );
3268
+ }
3269
+ function runSetup(options) {
3270
+ const { client } = options;
3271
+ const name = CLIENT_NAMES[client];
3272
+ const args = buildArgs(options);
3273
+ const serverName = options.profile ? `bithumb-trade-mcp-${options.profile}` : "bithumb-trade-mcp";
3274
+ if (client === "claude-code") {
3275
+ const claudeArgs = [
3276
+ "mcp",
3277
+ "add",
3278
+ "--transport",
3279
+ "stdio",
3280
+ serverName,
3281
+ "--",
3282
+ "bithumb-trade-mcp",
3283
+ ...args
3284
+ ];
3285
+ process.stdout.write(`Running: claude ${claudeArgs.join(" ")}
3286
+ `);
3287
+ execFileSync("claude", claudeArgs, { stdio: "inherit" });
3288
+ process.stdout.write(`\u2713 Configured ${name}
3289
+ `);
3290
+ return;
3291
+ }
3292
+ const configPath = getConfigPath(client);
3293
+ if (!configPath) {
3294
+ throw new Error(`${name} is not supported on this platform`);
3295
+ }
3296
+ const entry = buildEntry(client, args);
3297
+ mergeJsonConfig(configPath, serverName, entry);
3298
+ process.stdout.write(
3299
+ `\u2713 Configured ${name}
3300
+ ${configPath}
3301
+ Server args: ${args.join(" ")}
3302
+ `
3303
+ );
3304
+ if (client !== "vscode") {
3305
+ process.stdout.write(` Restart ${name} to apply changes.
3306
+ `);
3307
+ }
3308
+ }
3309
+
3310
+ // src/constants.ts
3311
+ import { createRequire } from "module";
3312
+ var _require = createRequire(import.meta.url);
3313
+ var pkg = _require("../package.json");
3314
+ var SERVER_NAME = "bithumb-trade-mcp";
3315
+ var SERVER_VERSION = pkg.version;
3316
+
3317
+ // src/server.ts
3318
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3319
+ import {
3320
+ CallToolRequestSchema,
3321
+ ListToolsRequestSchema
3322
+ } from "@modelcontextprotocol/sdk/types.js";
3323
+ var SYSTEM_CAPABILITIES_TOOL_NAME = "system_get_capabilities";
3324
+ var SYSTEM_CAPABILITIES_TOOL = {
3325
+ name: SYSTEM_CAPABILITIES_TOOL_NAME,
3326
+ description: "Return server capabilities and module availability for agent planning. Use when: user asks '\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uBE57\uC378 \uAE30\uB2A5 \uC54C\uB824\uC918' / 'what can you do?' / 'what features are available?'; or when an agent needs to plan a multi-step workflow and must know which modules are enabled. Do NOT use: as a substitute for `system_diagnose` (which checks live connectivity and auth). CLI: `bithumb-trade system capabilities` (alias: `bithumb-trade account capabilities`). Returns: server name/version, readOnly flag, hasAuth flag, and per-module status (enabled / disabled / requires_auth).",
3327
+ inputSchema: { type: "object", additionalProperties: false },
3328
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
3329
+ };
3330
+ function buildCapabilitySnapshot(config) {
3331
+ const enabledModules = new Set(config.modules);
3332
+ const moduleAvailability = {};
3333
+ for (const moduleId of MODULES) {
3334
+ if (!enabledModules.has(moduleId)) {
3335
+ moduleAvailability[moduleId] = { status: "disabled", reasonCode: "MODULE_FILTERED" };
3336
+ continue;
3337
+ }
3338
+ if (moduleId === "market") {
3339
+ moduleAvailability[moduleId] = { status: "enabled" };
3340
+ continue;
3341
+ }
3342
+ if (!config.hasAuth) {
3343
+ moduleAvailability[moduleId] = { status: "requires_auth", reasonCode: "AUTH_MISSING" };
3344
+ continue;
3345
+ }
3346
+ moduleAvailability[moduleId] = { status: "enabled" };
3347
+ }
3348
+ return { readOnly: config.readOnly, hasAuth: config.hasAuth, moduleAvailability };
3349
+ }
3350
+ function successResult(toolName, data, snapshot) {
3351
+ const payload = {
3352
+ tool: toolName,
3353
+ ok: true,
3354
+ data,
3355
+ capabilities: snapshot,
3356
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3357
+ };
3358
+ return {
3359
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
3360
+ structuredContent: payload
3361
+ };
3362
+ }
3363
+ function errorResult(toolName, error, snapshot) {
3364
+ const payload = toToolErrorPayload(error);
3365
+ const structured = {
3366
+ tool: toolName,
3367
+ ...payload,
3368
+ serverVersion: SERVER_VERSION,
3369
+ capabilities: snapshot
3370
+ };
3371
+ return {
3372
+ isError: true,
3373
+ content: [{ type: "text", text: JSON.stringify(structured, null, 2) }],
3374
+ structuredContent: structured
3375
+ };
3376
+ }
3377
+ function unknownToolResult(toolName, snapshot) {
3378
+ return errorResult(
3379
+ toolName,
3380
+ new BithumbApiError(`Tool "${toolName}" is not available.`, { code: "TOOL_NOT_AVAILABLE" }),
3381
+ snapshot
3382
+ );
3383
+ }
3384
+ function createServer(config, logger) {
3385
+ const client = new BithumbRestClient(config);
3386
+ const tools = buildTools(config);
3387
+ const toolMap = new Map(tools.map((t) => [t.name, t]));
3388
+ const server = new Server(
3389
+ { name: SERVER_NAME, version: SERVER_VERSION },
3390
+ { capabilities: { tools: {} } }
3391
+ );
3392
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
3393
+ tools: [...tools.map(toMcpTool), SYSTEM_CAPABILITIES_TOOL]
3394
+ }));
3395
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3396
+ const toolName = request.params.name;
3397
+ if (toolName === SYSTEM_CAPABILITIES_TOOL_NAME) {
3398
+ const snapshot = buildCapabilitySnapshot(config);
3399
+ return successResult(toolName, { server: { name: SERVER_NAME, version: SERVER_VERSION }, capabilities: snapshot }, snapshot);
3400
+ }
3401
+ const tool = toolMap.get(toolName);
3402
+ if (!tool) return unknownToolResult(toolName, buildCapabilitySnapshot(config));
3403
+ const startTime = Date.now();
3404
+ try {
3405
+ const response = await tool.handler(request.params.arguments ?? {}, { config, client });
3406
+ logger?.logTool("info", toolName, request.params.arguments ?? {}, response, Date.now() - startTime);
3407
+ return successResult(toolName, response, buildCapabilitySnapshot(config));
3408
+ } catch (error) {
3409
+ const level = error instanceof BithumbApiError ? "warn" : "error";
3410
+ logger?.logTool(level, toolName, request.params.arguments ?? {}, error, Date.now() - startTime);
3411
+ return errorResult(toolName, error, buildCapabilitySnapshot(config));
3412
+ }
3413
+ });
3414
+ return server;
3415
+ }
3416
+
3417
+ // src/index.ts
3418
+ function printHelp() {
3419
+ const help = `
3420
+ Usage: bithumb-trade-mcp [options]
3421
+
3422
+ Options:
3423
+ --modules <list> Comma-separated modules: market, account, trade, twap, withdraw, deposit, or "all"
3424
+ Default: market,account,trade,twap,withdraw,deposit (all modules)
3425
+ --read-only Expose only read/query tools (disable order placement/cancellation)
3426
+ --no-log Disable audit logging
3427
+ --log-level <level> Log level: error, warn, info, debug (default: info)
3428
+ --verbose Enable verbose request logging to stderr
3429
+ --profile <name> Config profile from ~/.bithumb/config.toml (default: uses default_profile)
3430
+ --help Show this help message
3431
+ --version Show version
3432
+
3433
+ Credentials (environment variables):
3434
+ BITHUMB_ACCESS_KEY Bithumb API access key
3435
+ BITHUMB_SECRET_KEY Bithumb API secret key
3436
+
3437
+ Other Environment Variables:
3438
+ BITHUMB_API_BASE_URL Optional API base URL override (default: https://api.bithumb.com)
3439
+ BITHUMB_TIMEOUT_MS Request timeout in milliseconds (default: 15000)
3440
+ `;
3441
+ process.stdout.write(help);
3442
+ }
3443
+ function parseCli() {
3444
+ const parsed = parseArgs({
3445
+ options: {
3446
+ modules: { type: "string" },
3447
+ "read-only": { type: "boolean", default: false },
3448
+ "no-log": { type: "boolean", default: false },
3449
+ "log-level": { type: "string", default: "info" },
3450
+ verbose: { type: "boolean", default: false },
3451
+ profile: { type: "string" },
3452
+ help: { type: "boolean", default: false },
3453
+ version: { type: "boolean", default: false }
3454
+ },
3455
+ allowPositionals: false
3456
+ });
3457
+ return {
3458
+ modules: parsed.values.modules,
3459
+ readOnly: parsed.values["read-only"] ?? false,
3460
+ noLog: parsed.values["no-log"] ?? false,
3461
+ logLevel: parsed.values["log-level"] ?? "info",
3462
+ verbose: parsed.values.verbose ?? false,
3463
+ profile: parsed.values.profile,
3464
+ help: parsed.values.help ?? false,
3465
+ version: parsed.values.version ?? false
3466
+ };
3467
+ }
3468
+ async function main() {
3469
+ if (process.argv[2] === "setup") {
3470
+ const setupArgs = parseArgs({
3471
+ args: process.argv.slice(3),
3472
+ options: {
3473
+ client: { type: "string" },
3474
+ profile: { type: "string" },
3475
+ modules: { type: "string" },
3476
+ help: { type: "boolean", default: false }
3477
+ },
3478
+ allowPositionals: false
3479
+ });
3480
+ if (setupArgs.values.help || !setupArgs.values.client) {
3481
+ printSetupUsage();
3482
+ return;
3483
+ }
3484
+ if (!SUPPORTED_CLIENTS.includes(setupArgs.values.client)) {
3485
+ process.stderr.write(`Unknown client "${setupArgs.values.client}". Use: ${SUPPORTED_CLIENTS.join(", ")}
3486
+ `);
3487
+ process.exitCode = 1;
3488
+ return;
3489
+ }
3490
+ runSetup({
3491
+ client: setupArgs.values.client,
3492
+ profile: setupArgs.values.profile,
3493
+ modules: setupArgs.values.modules
3494
+ });
3495
+ return;
3496
+ }
3497
+ const cli = parseCli();
3498
+ if (cli.help) {
3499
+ printHelp();
3500
+ return;
3501
+ }
3502
+ if (cli.version) {
3503
+ process.stdout.write(`${SERVER_VERSION}
3504
+ `);
3505
+ return;
3506
+ }
3507
+ checkForUpdates("@bithumb-tradekit/mcp", SERVER_VERSION);
3508
+ const config = loadConfig({
3509
+ modules: cli.modules,
3510
+ readOnly: cli.readOnly,
3511
+ verbose: cli.verbose,
3512
+ profile: cli.profile
3513
+ });
3514
+ const logger = cli.noLog ? void 0 : new TradeLogger(cli.logLevel);
3515
+ const server = createServer(config, logger);
3516
+ const transport = new StdioServerTransport();
3517
+ await server.connect(transport);
3518
+ }
3519
+ main().catch((error) => {
3520
+ const payload = toToolErrorPayload(error);
3521
+ process.stderr.write(`${JSON.stringify(payload, null, 2)}
3522
+ `);
3523
+ process.exitCode = 1;
3524
+ });
3525
+ export {
3526
+ main
3527
+ };
3528
+ /*! Bundled license information:
3529
+
3530
+ smol-toml/dist/error.js:
3531
+ smol-toml/dist/util.js:
3532
+ smol-toml/dist/date.js:
3533
+ smol-toml/dist/primitive.js:
3534
+ smol-toml/dist/extract.js:
3535
+ smol-toml/dist/struct.js:
3536
+ smol-toml/dist/parse.js:
3537
+ smol-toml/dist/stringify.js:
3538
+ smol-toml/dist/index.js:
3539
+ (*!
3540
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
3541
+ * SPDX-License-Identifier: BSD-3-Clause
3542
+ *
3543
+ * Redistribution and use in source and binary forms, with or without
3544
+ * modification, are permitted provided that the following conditions are met:
3545
+ *
3546
+ * 1. Redistributions of source code must retain the above copyright notice, this
3547
+ * list of conditions and the following disclaimer.
3548
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
3549
+ * this list of conditions and the following disclaimer in the
3550
+ * documentation and/or other materials provided with the distribution.
3551
+ * 3. Neither the name of the copyright holder nor the names of its contributors
3552
+ * may be used to endorse or promote products derived from this software without
3553
+ * specific prior written permission.
3554
+ *
3555
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
3556
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3557
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3558
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
3559
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
3560
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
3561
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
3562
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
3563
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3564
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3565
+ *)
3566
+ */
3567
+ //# sourceMappingURL=index.js.map