@dichvuright/mcp-hosting-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +120 -0
  2. package/dist/server.js +217 -0
  3. package/package.json +44 -0
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # DichVuRight Admin MCP Server
2
+
3
+ Kết nối AI Agent (Claude, Cursor, Antigravity...) vào hệ thống quản trị DichVuRight.
4
+
5
+ ## Kiến trúc bảo mật
6
+
7
+ ```
8
+ AI Agent
9
+ |
10
+ MCP Server (chỉ có: API_URL + API_KEY + API_SECRET)
11
+ |
12
+ POST /api/mcp/tools/call ← HMAC-SHA256 signed
13
+ |
14
+ Next.js API (verify HMAC + scope mcp:admin)
15
+ |
16
+ MongoDB / Redis ← MCP không bao giờ kết nối thẳng vào đây
17
+ ```
18
+
19
+ **MCP server không bao giờ biết:** JWT_SECRET, MongoDB URI, Redis password.
20
+
21
+ ## Cài đặt
22
+
23
+ ```bash
24
+ cd D:/nextjs/hosting/mcp
25
+ npm install
26
+ ```
27
+
28
+ ## Tạo API Key
29
+
30
+ 1. Vào **Admin → API Keys** (hoặc trang quản lý ApiClient)
31
+ 2. Tạo key mới với scope **`mcp:admin`**
32
+ 3. Copy `API Key` và `Secret`
33
+
34
+ ## Cấu hình
35
+
36
+ Điền vào `mcp/.env`:
37
+
38
+ ```env
39
+ MCP_API_URL=http://localhost:3000
40
+ MCP_API_KEY=mc_xxxxxxxxxxxx
41
+ MCP_API_SECRET=sk_xxxxxxxxxxxx
42
+ ```
43
+
44
+ Chỉ 3 biến. Không có secret nào của hệ thống ở đây.
45
+
46
+ ## Kết nối Antigravity / Cursor / Cline
47
+
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "dichvuright": {
52
+ "command": "npx",
53
+ "args": ["tsx", "D:/nextjs/hosting/mcp/server.ts"],
54
+ "cwd": "D:/nextjs/hosting/mcp"
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
60
+ ## Kết nối Claude Desktop
61
+
62
+ Mở `%APPDATA%\Claude\claude_desktop_config.json`:
63
+
64
+ ```json
65
+ {
66
+ "mcpServers": {
67
+ "dichvuright": {
68
+ "command": "npx",
69
+ "args": ["tsx", "D:/nextjs/hosting/mcp/server.ts"],
70
+ "cwd": "D:/nextjs/hosting/mcp"
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ ## Test thủ công
77
+
78
+ ```bash
79
+ cd D:/nextjs/hosting/mcp
80
+ npm run dev
81
+ # Output: [MCP] DichVuRight Admin MCP Server v2.0 started
82
+ # Output: [MCP] API endpoint: http://localhost:3000/api/mcp/tools/call
83
+ # Output: [MCP] Tools registered: 28
84
+ ```
85
+
86
+ ## Tools (28 tools)
87
+
88
+ | Group | Tools |
89
+ |-------|-------|
90
+ | 🏥 Health (5) | health_check_all, health_check_mongodb, health_check_redis, health_check_suppliers, get_stuck_tasks |
91
+ | 📋 Logs (5) | query_logs*, get_error_summary, detect_suspicious_ips*, get_critical_events, get_admin_activity_digest |
92
+ | 🔔 Alerts (3) | send_alert✏️, get_alert_history, silence_alert✏️ |
93
+ | 📊 Dashboard (5) | get_revenue_stats, get_user_stats, get_service_utilization, get_ticket_stats, get_payment_health |
94
+ | 👤 Users (6) | find_user, get_user_profile, get_user_transactions, get_user_security_events, get_user_services, flag_user_for_review✏️ |
95
+ | 🎫 Tickets (4) | list_open_tickets, get_ticket_summary, get_overdue_tickets, get_ticket_stats |
96
+
97
+ `*` Heavy query: giới hạn 10 req/min
98
+ `✏️` Write op: tạo audit log
99
+
100
+ ## Bảo mật
101
+
102
+ - **PII masking**: email, phone, name, IP tự động masked trước khi trả về AI
103
+ - **Không expose**: password hash, CCCD data, encrypted secrets
104
+ - **Rate limit**: 100 req/min (normal), 10 req/min (heavy queries)
105
+ - **Audit trail**: mọi tool call ghi vào MongoDB logs
106
+ - **Revoke ngay**: vào Admin → API Keys → Revoke nếu bị lộ key
107
+
108
+ ## Troubleshooting
109
+
110
+ **"MCP_API_KEY và MCP_API_SECRET là bắt buộc"**
111
+ → Kiểm tra file `mcp/.env`
112
+
113
+ **"Invalid API key" hoặc "Insufficient scope"**
114
+ → API key chưa có scope `mcp:admin`, hoặc key bị revoke
115
+
116
+ **"Request expired"**
117
+ → Đồng hồ máy lệch >5 phút, sync lại NTP
118
+
119
+ **Tools không xuất hiện trong IDE**
120
+ → Kiểm tra `cwd` trỏ đúng `D:/nextjs/hosting/mcp`, restart IDE
package/dist/server.js ADDED
@@ -0,0 +1,217 @@
1
+ import "dotenv/config";
2
+ import crypto from "crypto";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { z } from "zod";
6
+ const API_URL = process.env.MCP_API_URL ?? "http://localhost:3000";
7
+ const API_KEY = process.env.MCP_API_KEY ?? "";
8
+ const API_SECRET = process.env.MCP_API_SECRET ?? "";
9
+ if (!API_KEY || !API_SECRET) {
10
+ console.error("[MCP] ERROR: MCP_API_KEY và MCP_API_SECRET là bắt buộc");
11
+ console.error("[MCP] Tạo API key trong Admin → API Keys với scope 'mcp:admin'");
12
+ process.exit(1);
13
+ }
14
+ // ── HMAC call helper ───────────────────────────────────────────────────────
15
+ async function callApi(tool, args) {
16
+ const body = JSON.stringify({ tool, args });
17
+ const timestamp = String(Math.floor(Date.now() / 1000));
18
+ const requestId = crypto.randomUUID();
19
+ const path = "/api/mcp/tools/call";
20
+ const message = ["POST", path, body, timestamp, requestId].join("\n");
21
+ const signature = crypto.createHmac("sha256", API_SECRET).update(message).digest("hex");
22
+ const resp = await fetch(`${API_URL}${path}`, {
23
+ method: "POST",
24
+ headers: {
25
+ "Content-Type": "application/json",
26
+ "X-Api-Key": API_KEY,
27
+ "X-Timestamp": timestamp,
28
+ "X-Request-Id": requestId,
29
+ "X-Signature": signature,
30
+ },
31
+ body,
32
+ });
33
+ const json = (await resp.json());
34
+ if (!resp.ok || json.status === "error") {
35
+ throw new Error(json.message ?? `API error ${resp.status}`);
36
+ }
37
+ return json.data?.result ?? json.data;
38
+ }
39
+ function text(result) {
40
+ return [{ type: "text", text: JSON.stringify(result, null, 2) }];
41
+ }
42
+ // ── Server ─────────────────────────────────────────────────────────────────
43
+ const server = new McpServer({ name: "dichvuright-admin", version: "2.0.0" });
44
+ // ── Health ─────────────────────────────────────────────────────────────────
45
+ server.registerTool("health_check_all", {
46
+ description: "Kiểm tra tổng quan hệ thống: MongoDB, Redis, task bị kẹt, WHM servers, VPS suppliers.",
47
+ }, async () => ({ content: text(await callApi("health_check_all", {})) }));
48
+ server.registerTool("health_check_mongodb", {
49
+ description: "Kiểm tra MongoDB: latency, connection state, số task theo trạng thái.",
50
+ }, async () => ({ content: text(await callApi("health_check_mongodb", {})) }));
51
+ server.registerTool("health_check_redis", {
52
+ description: "Kiểm tra Redis: memory, connected clients, hit rate, eviction policy.",
53
+ }, async () => ({ content: text(await callApi("health_check_redis", {})) }));
54
+ server.registerTool("health_check_suppliers", {
55
+ description: "Số WHM servers và VPS suppliers theo trạng thái active/inactive.",
56
+ }, async () => ({ content: text(await callApi("health_check_suppliers", {})) }));
57
+ server.registerTool("get_stuck_tasks", {
58
+ description: "Tasks đang running (status=2) quá 30 phút — có thể bị stuck.",
59
+ }, async () => ({ content: text(await callApi("get_stuck_tasks", {})) }));
60
+ // ── Logs ───────────────────────────────────────────────────────────────────
61
+ server.registerTool("query_logs", {
62
+ description: "Query audit logs với filter. ⚠️ Heavy query (10 req/min).",
63
+ inputSchema: {
64
+ severity: z.enum(["info", "warn", "error", "critical"]).optional().describe("Lọc theo mức độ"),
65
+ module: z.string().optional().describe("Lọc theo module (auth, user, vps, ...)"),
66
+ hours: z.number().optional().describe("Bao nhiêu giờ gần đây (mặc định 24)"),
67
+ limit: z.number().optional().describe("Số kết quả tối đa (mặc định 50, tối đa 200)"),
68
+ },
69
+ }, async (args) => ({ content: text(await callApi("query_logs", args)) }));
70
+ server.registerTool("get_error_summary", {
71
+ description: "Group errors theo module/severity. Dùng để xem điểm yếu hệ thống.",
72
+ inputSchema: {
73
+ hours: z.number().optional().describe("Bao nhiêu giờ gần đây (mặc định 24)"),
74
+ },
75
+ }, async (args) => ({ content: text(await callApi("get_error_summary", args)) }));
76
+ server.registerTool("detect_suspicious_ips", {
77
+ description: "Phát hiện IP có nhiều failed auth — dấu hiệu brute force. ⚠️ Heavy query.",
78
+ inputSchema: {
79
+ threshold: z.number().optional().describe("Số lần thất bại tối thiểu (mặc định 5)"),
80
+ hours: z.number().optional().describe("Khoảng thời gian (mặc định 1 giờ)"),
81
+ },
82
+ }, async (args) => ({ content: text(await callApi("detect_suspicious_ips", args)) }));
83
+ server.registerTool("get_critical_events", {
84
+ description: "Lấy events có severity error/critical gần đây.",
85
+ inputSchema: {
86
+ hours: z.number().optional().describe("Bao nhiêu giờ gần đây (mặc định 6)"),
87
+ },
88
+ }, async (args) => ({ content: text(await callApi("get_critical_events", args)) }));
89
+ server.registerTool("get_admin_activity_digest", {
90
+ description: "Tóm tắt hoạt động của admin trong ngày theo module/action.",
91
+ inputSchema: {
92
+ date: z.string().optional().describe("Ngày cần xem YYYY-MM-DD (mặc định hôm nay)"),
93
+ },
94
+ }, async (args) => ({ content: text(await callApi("get_admin_activity_digest", args)) }));
95
+ // ── Alerts ─────────────────────────────────────────────────────────────────
96
+ server.registerTool("send_alert", {
97
+ description: "Gửi Telegram alert đến admin. Cooldown: warn=5m, error=1m, critical=không giới hạn. ✏️ WRITE.",
98
+ inputSchema: {
99
+ message: z.string().describe("Nội dung cảnh báo"),
100
+ severity: z.enum(["info", "warn", "error", "critical"]).describe("Mức độ nghiêm trọng"),
101
+ },
102
+ annotations: { destructiveHint: false },
103
+ }, async (args) => ({ content: text(await callApi("send_alert", args)) }));
104
+ server.registerTool("get_alert_history", {
105
+ description: "Lịch sử alerts đã gửi gần đây.",
106
+ inputSchema: {
107
+ hours: z.number().optional().describe("Bao nhiêu giờ gần đây (mặc định 24)"),
108
+ },
109
+ }, async (args) => ({ content: text(await callApi("get_alert_history", args)) }));
110
+ server.registerTool("silence_alert", {
111
+ description: "Tắt alert tạm thời. key='all' để tắt hết. ✏️ WRITE.",
112
+ inputSchema: {
113
+ key: z.string().describe("Alert key ('all' = tắt hết)"),
114
+ minutes: z.number().describe("Số phút silence (1-1440)"),
115
+ },
116
+ annotations: { destructiveHint: false },
117
+ }, async (args) => ({ content: text(await callApi("silence_alert", args)) }));
118
+ // ── Dashboard ──────────────────────────────────────────────────────────────
119
+ server.registerTool("get_revenue_stats", {
120
+ description: "Thống kê doanh thu theo ngày/tuần/tháng từ logbalance.",
121
+ inputSchema: {
122
+ period: z.enum(["day", "week", "month"]).describe("Kỳ thống kê"),
123
+ },
124
+ }, async (args) => ({ content: text(await callApi("get_revenue_stats", args)) }));
125
+ server.registerTool("get_user_stats", {
126
+ description: "Thống kê user: mới đăng ký, active, bị ban trong kỳ.",
127
+ inputSchema: {
128
+ period: z.enum(["day", "week", "month"]).describe("Kỳ thống kê"),
129
+ },
130
+ }, async (args) => ({ content: text(await callApi("get_user_stats", args)) }));
131
+ server.registerTool("get_service_utilization", {
132
+ description: "VPS + hosting đang chạy theo status.",
133
+ annotations: { readOnlyHint: true },
134
+ }, async () => ({ content: text(await callApi("get_service_utilization", {})) }));
135
+ server.registerTool("get_ticket_stats", {
136
+ description: "Thống kê tickets theo status, số quá hạn >48h.",
137
+ annotations: { readOnlyHint: true },
138
+ }, async () => ({ content: text(await callApi("get_ticket_stats", {})) }));
139
+ server.registerTool("get_payment_health", {
140
+ description: "Tỉ lệ thành công thanh toán theo loại (bank/card/momo).",
141
+ annotations: { readOnlyHint: true },
142
+ }, async () => ({ content: text(await callApi("get_payment_health", {})) }));
143
+ // ── Users ──────────────────────────────────────────────────────────────────
144
+ server.registerTool("find_user", {
145
+ description: "Tìm user theo username, email hoặc phone. Kết quả PII đã mask.",
146
+ inputSchema: {
147
+ query: z.string().describe("Từ khóa tìm kiếm (username / email / phone)"),
148
+ },
149
+ annotations: { readOnlyHint: true },
150
+ }, async (args) => ({ content: text(await callApi("find_user", args)) }));
151
+ server.registerTool("get_user_profile", {
152
+ description: "Thông tin user. Email/phone/name đã mask. KHÔNG có password hay CCCD.",
153
+ inputSchema: {
154
+ userId: z.string().describe("MongoDB ObjectId của user"),
155
+ },
156
+ annotations: { readOnlyHint: true },
157
+ }, async (args) => ({ content: text(await callApi("get_user_profile", args)) }));
158
+ server.registerTool("get_user_transactions", {
159
+ description: "Lịch sử giao dịch của user (nạp tiền, trừ tiền...).",
160
+ inputSchema: {
161
+ userId: z.string().describe("MongoDB ObjectId của user"),
162
+ limit: z.number().optional().describe("Số giao dịch (mặc định 20, tối đa 50)"),
163
+ },
164
+ annotations: { readOnlyHint: true },
165
+ }, async (args) => ({ content: text(await callApi("get_user_transactions", args)) }));
166
+ server.registerTool("get_user_security_events", {
167
+ description: "Lịch sử login/2FA/failed auth của user. IP đã mask.",
168
+ inputSchema: {
169
+ userId: z.string().describe("MongoDB ObjectId của user"),
170
+ hours: z.number().optional().describe("Bao nhiêu giờ gần đây (mặc định 48)"),
171
+ },
172
+ annotations: { readOnlyHint: true },
173
+ }, async (args) => ({ content: text(await callApi("get_user_security_events", args)) }));
174
+ server.registerTool("get_user_services", {
175
+ description: "VPS + hosting của user theo status.",
176
+ inputSchema: {
177
+ userId: z.string().describe("MongoDB ObjectId của user"),
178
+ },
179
+ annotations: { readOnlyHint: true },
180
+ }, async (args) => ({ content: text(await callApi("get_user_services", args)) }));
181
+ server.registerTool("flag_user_for_review", {
182
+ description: "Đánh dấu user cần review. Chỉ tạo audit log, KHÔNG sửa User document. ✏️ WRITE.",
183
+ inputSchema: {
184
+ userId: z.string().describe("MongoDB ObjectId của user"),
185
+ reason: z.string().describe("Lý do đánh dấu (tối đa 500 ký tự)"),
186
+ },
187
+ annotations: { destructiveHint: false },
188
+ }, async (args) => ({ content: text(await callApi("flag_user_for_review", args)) }));
189
+ // ── Tickets ────────────────────────────────────────────────────────────────
190
+ server.registerTool("list_open_tickets", {
191
+ description: "Tickets chưa đóng, sort theo priority (urgent trước).",
192
+ inputSchema: {
193
+ limit: z.number().optional().describe("Số kết quả (mặc định 20)"),
194
+ priority: z.enum(["urgent", "high", "medium", "low"]).optional().describe("Lọc theo priority"),
195
+ },
196
+ annotations: { readOnlyHint: true },
197
+ }, async (args) => ({ content: text(await callApi("list_open_tickets", args)) }));
198
+ server.registerTool("get_ticket_summary", {
199
+ description: "Chi tiết ticket + 5 tin nhắn gần nhất. Email trong nội dung đã mask.",
200
+ inputSchema: {
201
+ ticketId: z.string().describe("MongoDB ObjectId của ticket"),
202
+ },
203
+ annotations: { readOnlyHint: true },
204
+ }, async (args) => ({ content: text(await callApi("get_ticket_summary", args)) }));
205
+ server.registerTool("get_overdue_tickets", {
206
+ description: "Tickets open/pending >48h chưa được xử lý, sort theo cũ nhất.",
207
+ annotations: { readOnlyHint: true },
208
+ }, async () => ({ content: text(await callApi("get_overdue_tickets", {})) }));
209
+ server.registerTool("get_ticket_stats_summary", {
210
+ description: "Thống kê tổng quan tickets: theo status, số quá hạn, tổng.",
211
+ annotations: { readOnlyHint: true },
212
+ }, async () => ({ content: text(await callApi("get_ticket_stats", {})) }));
213
+ // ── Start ──────────────────────────────────────────────────────────────────
214
+ const transport = new StdioServerTransport();
215
+ await server.connect(transport);
216
+ console.error(`[MCP] DichVuRight Admin MCP Server v2.0 started`);
217
+ console.error(`[MCP] API: ${API_URL}/api/mcp/tools/call`);
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@dichvuright/mcp-hosting-server",
3
+ "version": "1.0.0",
4
+ "description": "DichVuRight Admin MCP Server — AI agent integration via HMAC API",
5
+ "type": "module",
6
+ "bin": {
7
+ "dichvuright-mcp": "./dist/server.js"
8
+ },
9
+ "main": "./dist/server.js",
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "dev": "tsx server.ts",
15
+ "build": "tsc",
16
+ "start": "node dist/server.js",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "publishConfig": {
20
+ "registry": "https://registry.npmjs.org",
21
+ "access": "public"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/dichvuright/hosting-dichvuright.git",
26
+ "directory": "mcp"
27
+ },
28
+ "keywords": ["mcp", "dichvuright", "admin", "ai-agent"],
29
+ "license": "UNLICENSED",
30
+ "private": false,
31
+ "dependencies": {
32
+ "@modelcontextprotocol/sdk": "^1.10.0",
33
+ "dotenv": "^16.0.0",
34
+ "zod": "^4.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^20.0.0",
38
+ "tsx": "^4.0.0",
39
+ "typescript": "^5.0.0"
40
+ },
41
+ "engines": {
42
+ "node": ">=18.0.0"
43
+ }
44
+ }