gm-mcp 1.0.7

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.
Files changed (48) hide show
  1. package/README.md +60 -0
  2. package/dist/env.d.ts +36 -0
  3. package/dist/env.js +49 -0
  4. package/dist/helpers/currency.d.ts +1 -0
  5. package/dist/helpers/currency.js +43 -0
  6. package/dist/helpers/extract-date.d.ts +5 -0
  7. package/dist/helpers/extract-date.js +13 -0
  8. package/dist/helpers/extract-month.d.ts +4 -0
  9. package/dist/helpers/extract-month.js +17 -0
  10. package/dist/helpers/index.d.ts +3 -0
  11. package/dist/helpers/index.js +19 -0
  12. package/dist/index.d.ts +3 -0
  13. package/dist/index.js +83 -0
  14. package/dist/services/gimo.service.d.ts +3 -0
  15. package/dist/services/gimo.service.js +48 -0
  16. package/dist/services/lark.service.d.ts +109 -0
  17. package/dist/services/lark.service.js +162 -0
  18. package/dist/services/lending.service.d.ts +19 -0
  19. package/dist/services/lending.service.js +96 -0
  20. package/dist/services/types/gimo.type.d.ts +177 -0
  21. package/dist/services/types/gimo.type.js +2 -0
  22. package/dist/services/types/index.d.ts +2 -0
  23. package/dist/services/types/index.js +18 -0
  24. package/dist/services/types/lending.type.d.ts +168 -0
  25. package/dist/services/types/lending.type.js +21 -0
  26. package/dist/test.d.ts +1 -0
  27. package/dist/test.js +39 -0
  28. package/dist/tools/index.d.ts +1 -0
  29. package/dist/tools/index.js +17 -0
  30. package/dist/tools/statistic-x3.d.ts +26 -0
  31. package/dist/tools/statistic-x3.js +337 -0
  32. package/package.json +35 -0
  33. package/src/env.ts +45 -0
  34. package/src/helpers/currency.ts +8 -0
  35. package/src/helpers/extract-date.ts +10 -0
  36. package/src/helpers/extract-month.ts +14 -0
  37. package/src/helpers/index.ts +3 -0
  38. package/src/index.ts +104 -0
  39. package/src/services/gimo.service.ts +31 -0
  40. package/src/services/lark.service.ts +122 -0
  41. package/src/services/lending.service.ts +107 -0
  42. package/src/services/types/gimo.type.ts +184 -0
  43. package/src/services/types/index.ts +2 -0
  44. package/src/services/types/lending.type.ts +181 -0
  45. package/src/test.ts +38 -0
  46. package/src/tools/index.ts +1 -0
  47. package/src/tools/statistic-x3.ts +378 -0
  48. package/tsconfig.json +115 -0
@@ -0,0 +1,337 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.statisticX3TodayTool = statisticX3TodayTool;
13
+ exports.statisticX3YesterdayTool = statisticX3YesterdayTool;
14
+ exports.statisticX3WeekTool = statisticX3WeekTool;
15
+ exports.statisticX3Tool = statisticX3Tool;
16
+ exports.statisticX3RangeTool = statisticX3RangeTool;
17
+ exports.statisticX3MonthTool = statisticX3MonthTool;
18
+ exports.statisticX3ThisMonthTool = statisticX3ThisMonthTool;
19
+ const lending_service_1 = require("../services/lending.service");
20
+ const helpers_1 = require("../helpers");
21
+ const lark_service_1 = require("../services/lark.service");
22
+ const date_fns_1 = require("date-fns");
23
+ const date_fns_tz_1 = require("date-fns-tz");
24
+ const VN_TIME_ZONE = "Asia/Ho_Chi_Minh";
25
+ function statisticX3TodayTool(options) {
26
+ return __awaiter(this, void 0, void 0, function* () {
27
+ const { sendMessageToLark } = options;
28
+ const startDate = (0, date_fns_tz_1.zonedTimeToUtc)((0, date_fns_1.startOfDay)((0, date_fns_tz_1.utcToZonedTime)(Date.now(), VN_TIME_ZONE)), VN_TIME_ZONE);
29
+ const endDate = (0, date_fns_tz_1.zonedTimeToUtc)((0, date_fns_1.endOfDay)((0, date_fns_tz_1.utcToZonedTime)(Date.now(), VN_TIME_ZONE)), VN_TIME_ZONE);
30
+ try {
31
+ const { message, statisticData } = yield _statisticX3({
32
+ created_from: startDate.toISOString(),
33
+ created_to: endDate.toISOString(),
34
+ });
35
+ if (sendMessageToLark) {
36
+ yield (0, lark_service_1.sendMessageToX3Group)({
37
+ text: `Tôi là trợ lý Gimo Ai Bot. Xin phép được báo cáo Lương x3 hôm nay ngày ${(0, date_fns_tz_1.formatInTimeZone)(Date.now(), VN_TIME_ZONE, "dd/MM/yyyy")} như sau`,
38
+ });
39
+ yield (0, lark_service_1.sendStatisticX3Message)({
40
+ date: (0, date_fns_tz_1.formatInTimeZone)(new Date(startDate), VN_TIME_ZONE, "dd/MM/yyyy"),
41
+ content: message,
42
+ });
43
+ }
44
+ return { content: [{ type: "text", text: JSON.stringify(statisticData) }] };
45
+ }
46
+ catch (error) {
47
+ return {
48
+ content: [
49
+ {
50
+ type: "text",
51
+ text: "Oh no,có rồi, thử lại nhé " + error,
52
+ },
53
+ ],
54
+ };
55
+ }
56
+ });
57
+ }
58
+ function statisticX3YesterdayTool(options) {
59
+ return __awaiter(this, void 0, void 0, function* () {
60
+ const { sendMessageToLark } = options;
61
+ const yesterday = (0, date_fns_1.subDays)((0, date_fns_tz_1.utcToZonedTime)(Date.now(), VN_TIME_ZONE), 1);
62
+ const startDate = (0, date_fns_tz_1.zonedTimeToUtc)((0, date_fns_1.startOfDay)(yesterday), VN_TIME_ZONE);
63
+ const endDate = (0, date_fns_tz_1.zonedTimeToUtc)((0, date_fns_1.endOfDay)(yesterday), VN_TIME_ZONE);
64
+ console.log({ yesterday }, { startDate }, { endDate });
65
+ // const startDate = zonedTimeToUtc(startOfDay(subDays(utcToZonedTime(Date.now(), 1), VN_TIME_ZONE))), VN_TIME_ZONE);
66
+ // const endDate = zonedTimeToUtc(endOfDay(utcToZonedTime(Date.now(), VN_TIME_ZONE)), VN_TIME_ZONE);
67
+ try {
68
+ const { message, statisticData } = yield _statisticX3({
69
+ created_from: startDate.toISOString(),
70
+ created_to: endDate.toISOString(),
71
+ });
72
+ if (sendMessageToLark) {
73
+ yield (0, lark_service_1.sendMessageToX3Group)({
74
+ text: `Tôi là trợ lý Gimo Ai Bot. Xin phép được báo cáo Lương x3 hôm qua ngày ${(0, date_fns_tz_1.formatInTimeZone)(startDate, VN_TIME_ZONE, "dd/MM/yyyy")} như sau`,
75
+ });
76
+ yield (0, lark_service_1.sendStatisticX3Message)({
77
+ date: (0, date_fns_tz_1.formatInTimeZone)(startDate, VN_TIME_ZONE, "dd/MM/yyyy"),
78
+ content: message,
79
+ });
80
+ }
81
+ return { content: [{ type: "text", text: JSON.stringify(statisticData) }] };
82
+ }
83
+ catch (error) {
84
+ return {
85
+ content: [
86
+ {
87
+ type: "text",
88
+ text: "Oh no,có rồi, thử lại nhé " + error,
89
+ },
90
+ ],
91
+ };
92
+ }
93
+ });
94
+ }
95
+ function statisticX3WeekTool(options) {
96
+ return __awaiter(this, void 0, void 0, function* () {
97
+ const { sendMessageToLark } = options;
98
+ const startDate = (0, date_fns_tz_1.zonedTimeToUtc)((0, date_fns_1.startOfWeek)((0, date_fns_tz_1.utcToZonedTime)(Date.now(), VN_TIME_ZONE), { weekStartsOn: 1 }), VN_TIME_ZONE);
99
+ const endDate = (0, date_fns_tz_1.zonedTimeToUtc)((0, date_fns_1.endOfWeek)((0, date_fns_tz_1.utcToZonedTime)(Date.now(), VN_TIME_ZONE), { weekStartsOn: 1 }), VN_TIME_ZONE);
100
+ try {
101
+ const { message, statisticData } = yield _statisticX3({
102
+ created_from: startDate.toISOString(),
103
+ created_to: endDate.toISOString(),
104
+ });
105
+ const botText = `Tôi là trợ lý Gimo Ai Bot. Xin phép được báo cáo Lương x3 tuần này tính đến thời điểm ${(0, date_fns_tz_1.formatInTimeZone)(Date.now(), VN_TIME_ZONE, "HH:mm dd/MM/yyyy")} như sau`;
106
+ console.log(botText);
107
+ if (sendMessageToLark) {
108
+ yield (0, lark_service_1.sendMessageToX3Group)({
109
+ text: `Tôi là trợ lý Gimo Ai Bot. Xin phép được báo cáo Lương x3 tuần này tính đến thời điểm ${(0, date_fns_tz_1.formatInTimeZone)(Date.now(), VN_TIME_ZONE, "HH:mm dd/MM/yyyy")} như sau`,
110
+ });
111
+ yield (0, lark_service_1.sendStatisticX3Message)({
112
+ date: (0, date_fns_tz_1.formatInTimeZone)(new Date(startDate), VN_TIME_ZONE, "dd/MM/yyyy"),
113
+ content: message,
114
+ });
115
+ }
116
+ return { content: [{ type: "text", text: JSON.stringify(statisticData) }] };
117
+ }
118
+ catch (error) {
119
+ return {
120
+ content: [
121
+ {
122
+ type: "text",
123
+ text: "Oh no,có rồi, thử lại nhé " + error,
124
+ },
125
+ ],
126
+ };
127
+ }
128
+ });
129
+ }
130
+ function statisticX3Tool(options) {
131
+ return __awaiter(this, void 0, void 0, function* () {
132
+ try {
133
+ const { date, sendMessageToLark } = options;
134
+ const dateParts = (0, helpers_1.extractDate)(date);
135
+ if (!dateParts) {
136
+ return { content: [{ type: "text", text: `${date} is invalid date` }] };
137
+ }
138
+ const { year, month, day } = dateParts;
139
+ const queryDate = Date.UTC(year, month, day, 0, 0, 0, 0);
140
+ if (!(0, date_fns_1.isValid)(queryDate)) {
141
+ return { content: [{ type: "text", text: `${date} is invalid date` }] };
142
+ }
143
+ const startDate = (0, date_fns_tz_1.zonedTimeToUtc)((0, date_fns_1.startOfDay)(queryDate), VN_TIME_ZONE);
144
+ const endDate = (0, date_fns_tz_1.zonedTimeToUtc)((0, date_fns_1.endOfDay)(queryDate), "Asia/Ho_Chi_Minh");
145
+ const { message, statisticData } = yield _statisticX3({
146
+ created_from: startDate.toISOString(),
147
+ created_to: endDate.toISOString(),
148
+ });
149
+ if (sendMessageToLark) {
150
+ yield (0, lark_service_1.sendMessageToX3Group)({
151
+ text: `Tôi là trợ lý Gimo Ai Bot. Xin phép được báo cáo Lương x3 ngày ${(0, date_fns_tz_1.formatInTimeZone)(startDate, VN_TIME_ZONE, "dd/MM/yyyy")} như sau`,
152
+ });
153
+ yield (0, lark_service_1.sendStatisticX3Message)({
154
+ date: (0, date_fns_tz_1.formatInTimeZone)(new Date(startDate), VN_TIME_ZONE, "dd/MM/yyyy"),
155
+ content: message,
156
+ });
157
+ }
158
+ return { content: [{ type: "text", text: JSON.stringify(statisticData) }] };
159
+ }
160
+ catch (error) {
161
+ return {
162
+ content: [
163
+ {
164
+ type: "text",
165
+ text: "Oh no,có rồi, thử lại nhé " + error,
166
+ },
167
+ ],
168
+ };
169
+ }
170
+ });
171
+ }
172
+ function statisticX3RangeTool(options) {
173
+ return __awaiter(this, void 0, void 0, function* () {
174
+ try {
175
+ const { startDateStr, endDateStr, sendMessageToLark } = options;
176
+ const startDateParts = (0, helpers_1.extractDate)(startDateStr);
177
+ const endDateParts = (0, helpers_1.extractDate)(endDateStr);
178
+ if (!startDateParts || !endDateParts) {
179
+ return { content: [{ type: "text", text: `date range is invalid date` }] };
180
+ }
181
+ const _startDate = Date.UTC(startDateParts.year, startDateParts.month, startDateParts.day, 0, 0, 0, 0);
182
+ const _endDate = Date.UTC(endDateParts.year, endDateParts.month, endDateParts.day, 0, 0, 0, 0);
183
+ if (!(0, date_fns_1.isValid)(_startDate) || !(0, date_fns_1.isValid)(_endDate)) {
184
+ return { content: [{ type: "text", text: `date range is invalid date` }] };
185
+ }
186
+ const { message, statisticData } = yield _statisticX3({
187
+ created_from: (0, date_fns_tz_1.zonedTimeToUtc)((0, date_fns_1.startOfDay)(_startDate), VN_TIME_ZONE).toISOString(),
188
+ created_to: (0, date_fns_tz_1.zonedTimeToUtc)((0, date_fns_1.endOfDay)(_endDate), VN_TIME_ZONE).toISOString(),
189
+ });
190
+ if (sendMessageToLark) {
191
+ yield (0, lark_service_1.sendMessageToX3Group)({
192
+ text: `Tôi là trợ lý Gimo Ai Bot. Xin phép được báo cáo Lương x3 từ ngày ${(0, date_fns_tz_1.formatInTimeZone)(_startDate, VN_TIME_ZONE, "dd/MM/yyyy")} đến ngày ${(0, date_fns_tz_1.formatInTimeZone)(_endDate, VN_TIME_ZONE, "dd/MM/yyyy")} như sau`,
193
+ });
194
+ yield (0, lark_service_1.sendStatisticX3Message)({
195
+ date: (0, date_fns_tz_1.formatInTimeZone)(_startDate, VN_TIME_ZONE, "dd/MM/yyyy"),
196
+ content: message,
197
+ });
198
+ }
199
+ return { content: [{ type: "text", text: JSON.stringify(statisticData) }] };
200
+ }
201
+ catch (error) {
202
+ return {
203
+ content: [
204
+ {
205
+ type: "text",
206
+ text: "Oh no,có rồi, thử lại nhé " + error,
207
+ },
208
+ ],
209
+ };
210
+ }
211
+ });
212
+ }
213
+ function statisticX3MonthTool(options) {
214
+ return __awaiter(this, void 0, void 0, function* () {
215
+ // return {
216
+ // content: [{ type: "text", text: JSON.stringify(extractMonth(options.month)) }],
217
+ // };
218
+ const monthData = (0, helpers_1.extractMonth)(options.month);
219
+ if (!monthData) {
220
+ return {
221
+ content: [{ type: "text", text: "Month invalid" }],
222
+ };
223
+ }
224
+ const { sendMessageToLark } = options;
225
+ const { month, year } = monthData;
226
+ const nowInVnzone = (0, date_fns_tz_1.utcToZonedTime)(Date.now(), VN_TIME_ZONE);
227
+ const startDate = (0, date_fns_tz_1.zonedTimeToUtc)((0, date_fns_1.startOfMonth)(new Date(year || nowInVnzone.getFullYear(), month - 1, 1, 0, 0, 0, 0)), VN_TIME_ZONE);
228
+ const endDate = (0, date_fns_tz_1.zonedTimeToUtc)((0, date_fns_1.endOfMonth)(new Date(year || nowInVnzone.getFullYear(), month - 1, 1, 0, 0, 0, 0)), VN_TIME_ZONE);
229
+ try {
230
+ const { message, statisticData } = yield _statisticX3({
231
+ created_from: startDate.toISOString(),
232
+ created_to: endDate.toISOString(),
233
+ });
234
+ if (sendMessageToLark) {
235
+ yield (0, lark_service_1.sendMessageToX3Group)({
236
+ text: `Tôi là trợ lý Gimo Ai Bot. Xin phép được báo cáo Lương x3 tháng ${(0, date_fns_tz_1.formatInTimeZone)(startDate, VN_TIME_ZONE, "MM/yyyy")} như sau`,
237
+ });
238
+ yield (0, lark_service_1.sendStatisticX3Message)({
239
+ date: (0, date_fns_tz_1.formatInTimeZone)(new Date(startDate), VN_TIME_ZONE, "dd/MM/yyyy"),
240
+ content: message,
241
+ });
242
+ }
243
+ return {
244
+ content: [
245
+ {
246
+ type: "text",
247
+ text: JSON.stringify(Object.assign(Object.assign({}, statisticData), { startDate: startDate.toISOString(), endDate: endDate.toISOString() })),
248
+ },
249
+ ],
250
+ };
251
+ }
252
+ catch (error) {
253
+ return {
254
+ content: [
255
+ {
256
+ type: "text",
257
+ text: "Oh no,có rồi, thử lại nhé " + error,
258
+ },
259
+ ],
260
+ };
261
+ }
262
+ });
263
+ }
264
+ function statisticX3ThisMonthTool(options) {
265
+ return __awaiter(this, void 0, void 0, function* () {
266
+ // return {
267
+ // content: [{ type: "text", text: JSON.stringify(extractMonth(options.month)) }],
268
+ // };
269
+ const { sendMessageToLark } = options;
270
+ const nowInVnzone = (0, date_fns_tz_1.utcToZonedTime)(Date.now(), VN_TIME_ZONE);
271
+ const startDate = (0, date_fns_tz_1.zonedTimeToUtc)((0, date_fns_1.startOfMonth)(nowInVnzone), VN_TIME_ZONE);
272
+ const endDate = (0, date_fns_tz_1.zonedTimeToUtc)((0, date_fns_1.endOfMonth)(nowInVnzone), VN_TIME_ZONE);
273
+ try {
274
+ const { message, statisticData } = yield _statisticX3({
275
+ created_from: startDate.toISOString(),
276
+ created_to: endDate.toISOString(),
277
+ });
278
+ if (sendMessageToLark) {
279
+ yield (0, lark_service_1.sendMessageToX3Group)({
280
+ text: `Tôi là trợ lý Gimo Ai Bot. Xin phép được báo cáo Lương x3 tháng này ${(0, date_fns_tz_1.formatInTimeZone)(Date.now(), VN_TIME_ZONE, "MM/yyyy")} như sau`,
281
+ });
282
+ yield (0, lark_service_1.sendStatisticX3Message)({
283
+ date: (0, date_fns_tz_1.formatInTimeZone)(new Date(startDate), VN_TIME_ZONE, "dd/MM/yyyy"),
284
+ content: message,
285
+ });
286
+ }
287
+ return {
288
+ content: [
289
+ {
290
+ type: "text",
291
+ text: JSON.stringify(Object.assign(Object.assign({}, statisticData), { startDate: startDate.toISOString(), endDate: endDate.toISOString() })),
292
+ },
293
+ ],
294
+ };
295
+ }
296
+ catch (error) {
297
+ return {
298
+ content: [
299
+ {
300
+ type: "text",
301
+ text: "Oh no,có rồi, thử lại nhé " + error,
302
+ },
303
+ ],
304
+ };
305
+ }
306
+ });
307
+ }
308
+ function _statisticX3(p) {
309
+ return __awaiter(this, void 0, void 0, function* () {
310
+ var _a, _b, _c;
311
+ const { created_from, created_to } = p;
312
+ const res = yield (0, lending_service_1.statisticX3)({
313
+ created_to,
314
+ created_from,
315
+ });
316
+ const messageContent = [
317
+ `Số đơn đăng ký: ${res.total || "0"}`,
318
+ `<br>`,
319
+ `Số đơn chờ duyệt: ${res.total_appraisal || "0"}`,
320
+ `<br>`,
321
+ `Số đơn được giải ngân: ${res.total_dibursed || "0"}`,
322
+ `<hr>`,
323
+ `Số đơn bị từ chối: ${res.total_rejected || "0"}`,
324
+ `<br>`,
325
+ `Số đơn bị đóng: ${res.total_closed || "0"}`,
326
+ `<hr>`,
327
+ // `Tỷ lê đơn đơn được giải ngân/được duyệt: ${distributedOverApproval}%`,
328
+ // `<hr>`,
329
+ `Tổng đã giải ngân: ${(0, helpers_1.formatCurrency)((_a = res.loan_statistic) === null || _a === void 0 ? void 0 : _a.total_disbursement_amount) || "0"}`,
330
+ `<br>`,
331
+ `Tổng phải trả: ${(0, helpers_1.formatCurrency)((_b = res.loan_statistic) === null || _b === void 0 ? void 0 : _b.total_payment_amount) || "0"}`,
332
+ `<br>`,
333
+ `Tổng lãi phải trả: ${(0, helpers_1.formatCurrency)((_c = res.loan_statistic) === null || _c === void 0 ? void 0 : _c.total_interest_amount) || "0"}`,
334
+ ];
335
+ return { message: messageContent.join(""), statisticData: res };
336
+ });
337
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "gm-mcp",
3
+ "version": "1.0.7",
4
+ "description": "Mcp server",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1",
8
+ "build": "tsc",
9
+ "start": "npx ts-node src/index.ts",
10
+ "start:dev": "dotenvx run -f .env.dev -- npx ts-node src/index.ts",
11
+ "start:prod": "dotenvx run -f .env.prod -- npx ts-node src/index.ts",
12
+ "test:dev": "dotenvx run -f .env.dev -- npx ts-node src/test.ts",
13
+ "test:prod": "dotenvx run -f .env.prod -- npx ts-node src/test.ts",
14
+ "publish-pkg": "npm run build && npm publish"
15
+ },
16
+ "bin": {
17
+ "gimo-mcp": "dist/index.js"
18
+ },
19
+ "author": "danbn",
20
+ "license": "ISC",
21
+ "devDependencies": {
22
+ "@types/currency-formatter": "^1.5.4",
23
+ "typescript": "^5.8.3"
24
+ },
25
+ "dependencies": {
26
+ "@dotenvx/dotenvx": "^1.44.2",
27
+ "@larksuiteoapi/node-sdk": "^1.29.0",
28
+ "@modelcontextprotocol/sdk": "^1.8.0",
29
+ "currency-formatter": "^1.5.9",
30
+ "date-fns": "^2.30.0",
31
+ "date-fns-tz": "^2.0.1",
32
+ "gimo-mcp": "^1.0.3",
33
+ "zod": "^3.25.67"
34
+ }
35
+ }
package/src/env.ts ADDED
@@ -0,0 +1,45 @@
1
+ import z from "zod";
2
+
3
+ const envSchema = z.object({
4
+ DASH_URL: z.string({ description: "Dash url for login" }),
5
+ PASSWORD: z.string({ description: "User password" }),
6
+ USER_NAME: z.string({ description: "User name" }),
7
+ LENDING_URL: z.string({ description: "Lending url" }),
8
+ LARK_APP_ID: z.string({ description: `Lark's Bot app id` }),
9
+ LARK_APP_SECRET: z.string({ description: `Lark's Bot app secret` }),
10
+ LARK_X3_GROUP_ID: z.string({ description: `Id of lark's group x3` }),
11
+ LARK_X3_TEMPLATE_ID: z.string({ description: `Id of lark's card template x3` }),
12
+ SEND_MESSAGE_TO_LARK: z.boolean({ description: "Send message to lark or not", coerce: true }),
13
+ });
14
+ export type GimoMcpEnv = z.infer<typeof envSchema>;
15
+ export function ensureEnvVariables() {
16
+ const { success, error } = envSchema.safeParse(process.env);
17
+ if (!success) {
18
+ console.error("There is an error with the server environment variables", error.issues);
19
+ throw new Error("ENV_VARIABLES_INVALID");
20
+ }
21
+ return true;
22
+ }
23
+
24
+ export function getEnv(): GimoMcpEnv {
25
+ const DASH_URL = process.env["DASH_URL"] as string;
26
+ const PASSWORD = process.env["PASSWORD"] as string;
27
+ const USER_NAME = process.env["USER_NAME"] as string;
28
+ const LENDING_URL = process.env["LENDING_URL"] as string;
29
+ const LARK_APP_ID = process.env["LARK_APP_ID"] as string;
30
+ const LARK_APP_SECRET = process.env["LARK_APP_SECRET"] as string;
31
+ const LARK_X3_GROUP_ID = process.env["LARK_X3_GROUP_ID"] as string;
32
+ const LARK_X3_TEMPLATE_ID = process.env["LARK_X3_TEMPLATE_ID"] as string;
33
+ const SEND_MESSAGE_TO_LARK = Boolean(process.env["SEND_MESSAGE_TO_LARK"]);
34
+ return {
35
+ DASH_URL,
36
+ PASSWORD,
37
+ USER_NAME,
38
+ LENDING_URL,
39
+ LARK_APP_ID,
40
+ LARK_APP_SECRET,
41
+ LARK_X3_GROUP_ID,
42
+ LARK_X3_TEMPLATE_ID,
43
+ SEND_MESSAGE_TO_LARK,
44
+ };
45
+ }
@@ -0,0 +1,8 @@
1
+ import * as currencyFormatter from 'currency-formatter';
2
+
3
+ export function formatCurrency(val?: any) {
4
+ if (typeof val === 'undefined' || val === null || val === '') {
5
+ return null;
6
+ }
7
+ return currencyFormatter.format(val, { code: 'VND' });
8
+ }
@@ -0,0 +1,10 @@
1
+ export function extractDate(date: string) {
2
+ if (date.split("-").length === 3 || date.split("/").length === 3) {
3
+ const parts = date.split("/").length === 3 ? date.split("/") : date.split("-");
4
+ const day = parseInt(parts[0], 10);
5
+ const month = parseInt(parts[1], 10) - 1; // Month is 0-indexed in JavaScript
6
+ const year = parseInt(parts[2], 10);
7
+ return { day, month, year };
8
+ }
9
+ return null;
10
+ }
@@ -0,0 +1,14 @@
1
+ export function extractMonth(monthStr: string): { month: number; year?: number } | null {
2
+ if (monthStr.split("/").length === 2) {
3
+ const [month, year] = monthStr.split("/");
4
+ return { month: parseInt(month, 10), year: parseInt(year, 10) };
5
+ }
6
+ if (monthStr.split("-").length === 2) {
7
+ const [month, year] = monthStr.split("-");
8
+ return { month: parseInt(month, 10), year: parseInt(year, 10) };
9
+ }
10
+ if (!Number.isNaN(parseInt(monthStr, 10))) {
11
+ return { month: parseInt(monthStr, 10) };
12
+ }
13
+ return null;
14
+ }
@@ -0,0 +1,3 @@
1
+ export * from './currency';
2
+ export * from './extract-date';
3
+ export * from './extract-month';
package/src/index.ts ADDED
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import {
5
+ statisticX3MonthTool,
6
+ statisticX3RangeTool,
7
+ statisticX3ThisMonthTool,
8
+ statisticX3TodayTool,
9
+ statisticX3Tool,
10
+ statisticX3WeekTool,
11
+ statisticX3YesterdayTool,
12
+ } from "./tools";
13
+ import z from "zod";
14
+ import { ensureEnvVariables, getEnv } from "./env";
15
+
16
+ export const server = new McpServer(
17
+ {
18
+ name: "mcp-server",
19
+ version: "1.0.0",
20
+ },
21
+ {
22
+ capabilities: {
23
+ tools: {},
24
+ },
25
+ }
26
+ );
27
+
28
+ server.tool("statistic_salary_x3_today", "Get statistic salary x3 today", {}, async (args) => {
29
+ return statisticX3TodayTool({ sendMessageToLark: getEnv().SEND_MESSAGE_TO_LARK });
30
+ });
31
+
32
+ server.tool("statistic_salary_x3_yesterday", "Get statistic salary x3 yesterday", {}, async (args) => {
33
+ return statisticX3YesterdayTool({ sendMessageToLark: getEnv().SEND_MESSAGE_TO_LARK });
34
+ });
35
+
36
+ server.tool("statistic_salary_x3_this_week", "Get statistic salary x3 this week", {}, async (args) => {
37
+ return statisticX3WeekTool({ sendMessageToLark: getEnv().SEND_MESSAGE_TO_LARK });
38
+ });
39
+
40
+ server.tool(
41
+ "statistic_salary_x3_month",
42
+ `Get statistic salary x3 with month (include), month must be represented with format MM/yyyy, if year is not provided, ask user again "what is year you are mentioning ?"`,
43
+ {
44
+ month: z.string().describe("Month to statistic, month must be represented with format MM/yyyy"),
45
+ },
46
+ async (args) => {
47
+ return statisticX3MonthTool({ sendMessageToLark: getEnv().SEND_MESSAGE_TO_LARK, month: args.month });
48
+ }
49
+ );
50
+
51
+ server.tool("statistic_salary_x3_this_month", `Get statistic salary x3 this month`, {}, async (args) => {
52
+ return statisticX3ThisMonthTool({ sendMessageToLark: getEnv().SEND_MESSAGE_TO_LARK });
53
+ });
54
+
55
+ server.tool(
56
+ "statistic_salary_x3",
57
+ "Get statistic salary x3 with specific date,date must be formated in dd/mm/yyy or dd-mm-yyyy",
58
+ {
59
+ date: z.string({
60
+ description: "Date to statistic with format dd/mm/yyy or dd-mm-yyyy",
61
+ }),
62
+ },
63
+ async (args) => {
64
+ return statisticX3Tool({
65
+ date: args.date,
66
+ sendMessageToLark: getEnv().SEND_MESSAGE_TO_LARK,
67
+ });
68
+ }
69
+ );
70
+
71
+ server.tool(
72
+ "statistic_salary_x3_in_range",
73
+ "Get statistic salary x3 with range of date,date must be formated in dd/mm/yyy or dd-mm-yyyy",
74
+ {
75
+ start_date: z.string({
76
+ description: "Start date to statistic with format dd/mm/yyy or dd-mm-yyyy",
77
+ }),
78
+ end_date: z.string({
79
+ description: "End date to statistic with format dd/mm/yyy or dd-mm-yyyy",
80
+ }),
81
+ },
82
+ async (args) => {
83
+ return statisticX3RangeTool({
84
+ startDateStr: args.start_date,
85
+ endDateStr: args.end_date,
86
+ sendMessageToLark: getEnv().SEND_MESSAGE_TO_LARK,
87
+ });
88
+ }
89
+ );
90
+
91
+ function bootstap() {
92
+ ensureEnvVariables();
93
+ const transport: StdioServerTransport = new StdioServerTransport();
94
+ server
95
+ .connect(transport)
96
+ .then((res) => {
97
+ console.log("Server connected and ready!");
98
+ })
99
+ .catch((e) => {
100
+ console.log("Error when start server", e);
101
+ });
102
+ }
103
+
104
+ bootstap();
@@ -0,0 +1,31 @@
1
+ import axios from 'axios';
2
+ import { Gimo } from './types';
3
+ import { getEnv } from '../env';
4
+
5
+ export async function getAccessToken() {
6
+ const url = `${getEnv().DASH_URL}/api/v1/auth/admin/login`;
7
+ return axios
8
+ .post<Gimo.LoginRes>(url, {
9
+ password: getEnv().PASSWORD,
10
+ username: getEnv().USER_NAME,
11
+ })
12
+ .then((res) => res.data.access_token);
13
+ }
14
+
15
+ export async function findCustomerEWAByPhonenumber(phoneNumber: string) {
16
+ const customerApiUrl = `${getEnv().DASH_URL}/api/v1/admin/customers/ewa/pagination`;
17
+ const access_token = await getAccessToken();
18
+ // Find inlist
19
+ const customers = await axios
20
+ .get<{ content: Gimo.CustomerEWA[] }>(customerApiUrl, {
21
+ params: { phone_number: phoneNumber },
22
+ headers: {
23
+ Authorization: 'Bearer ' + access_token,
24
+ },
25
+ })
26
+ .then((res) => res.data)
27
+ .then((res) => res.content);
28
+ // Select first customer
29
+ const customer = customers?.[0];
30
+ return customer || null;
31
+ }