monacloud-mcp 0.10.10 → 0.10.11

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/base.js CHANGED
@@ -2,6 +2,7 @@ import { z } from 'zod';
2
2
  import { CloudError, runTool } from './errors.js';
3
3
  import { pollJob, TERMINAL_JOBS } from './jobs.js';
4
4
  import { unwrapData } from './http.js';
5
+ import { completeToolMetadata } from './monapay.js';
5
6
  const object = (value) => value && typeof value === 'object' ? value : {};
6
7
  const id = z.string().trim().min(1).max(255);
7
8
  const sandbox = z.boolean().optional().describe('Thử 0đ, chỉ ước tính và không tạo hạ tầng thật / Sandbox estimate only');
@@ -45,7 +46,10 @@ async function createBase(clients, body) {
45
46
  export function registerBaseTools(server, clients) {
46
47
  const register = (name, description, schema, handler) => {
47
48
  for (const toolName of [name, name.replace(/^cloud_/, 'vibecloud_')]) {
49
+ const metadata = completeToolMetadata(toolName, undefined, undefined);
48
50
  server.registerTool(toolName, {
51
+ title: metadata.title,
52
+ annotations: metadata.annotations,
49
53
  description: toolName === name ? `${description} ${baseDescription}` : `Alias tương thích của ${name}. / Compatibility alias. ${description} ${baseDescription}`,
50
54
  inputSchema: schema,
51
55
  }, (args) => runTool(() => handler(args)));
package/dist/compute.js CHANGED
@@ -4,6 +4,7 @@ import { unwrapData } from './http.js';
4
4
  import { finishAppJob } from './jobs.js';
5
5
  import { detectProject } from './local.js';
6
6
  import { localAppTools } from './local-app.js';
7
+ import { completeToolMetadata } from './monapay.js';
7
8
  export const APP_FLOW = 'AI làm 99%: cloud_app_detect(local_dir) offline → cloud_app_host_list, cloud_prices/cloud_packages/cloud_plan_list và cloud_balance → cloud_app_create(local_dir, sandbox=true) nếu chưa có app host để ước tính chi phí giờ/gói → hỏi human một lần duyệt chi phí nếu chưa được duyệt → cloud_app_create(local_dir, sandbox=false) → poll tới done/succeeded → kiểm và trả URL. Cần DB/Supabase? cloud_base_create (beta) — chung account/ví MONA Cloud, khớp app deploy. Sau khi app có URL, CHỦ ĐỘNG hỏi tên miền riêng: human đã có domain → cloud_app_domain_add (CNAME); chưa có → cloud_domain_search gợi 3–5 đuôi (.vn trước nếu khách Việt) kèm giá VND, hỏi xác nhận chính tả + duyệt tiền → cloud_domain_buy (đã login) hoặc cloud_domain_reserve (chưa login: QR + claim_url) → cloud_domain_attach trỏ DNS + SSL. Human đăng ký MONA Pass bằng device flow một lần; hết credit 20k thì AI gọi cloud_topup và in QR (qr_ascii) ngay trong terminal, human chỉ quét bằng app ngân hàng; AI làm các bước còn lại. App git cloud_app_create(repo_url) đã live; không gọi agent_deploy cho deploy dự án. / Detect locally, estimate, obtain cost approval once, upload and deploy, create an optional Base, return URL, then attach an optional domain.';
8
9
  const sandbox = z.boolean().optional().describe('Thử 0đ, không tạo hạ tầng thật / Sandbox, no charge');
9
10
  const id = z.string().trim().min(1).max(255);
@@ -49,7 +50,10 @@ export function registerComputeTools(server, clients) {
49
50
  // Every new compute tool keeps a mechanical legacy alias, using the same schema and handler.
50
51
  const register = (name, description, schema, handler) => {
51
52
  for (const toolName of [name, name.replace(/^cloud_/, 'vibecloud_')]) {
53
+ const metadata = completeToolMetadata(toolName, undefined, undefined);
52
54
  server.registerTool(toolName, {
55
+ title: metadata.title,
56
+ annotations: metadata.annotations,
53
57
  description: toolName === name ? description : `Alias tương thích của ${name}. / Compatibility alias. ${description}`,
54
58
  inputSchema: schema,
55
59
  }, (args) => runTool(() => handler(args)));
package/dist/domains.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { runTool } from './errors.js';
3
+ import { toolAnnotationsForName } from './monapay.js';
3
4
  const domainName = z.string().trim().toLowerCase()
4
5
  .min(3).max(253)
5
6
  .describe('Tên miền đầy đủ (vd: example.vn) hoặc chỉ tên chưa có đuôi (vd: example).');
@@ -24,6 +25,7 @@ const registrantSchema = z.object({
24
25
  }).strict();
25
26
  export function registerDomainTools(server, clients) {
26
27
  server.registerTool('cloud_domain_search', {
28
+ annotations: toolAnnotationsForName('cloud_domain_search'),
27
29
  title: 'Tìm kiếm + báo giá tên miền',
28
30
  description: 'Kiểm tra tên miền còn trống và xem giá mua (VND, đã VAT). KHÔNG cần đăng nhập MONA Pass — gọi được ngay cả khi người dùng chưa có tài khoản; dùng trước khi mua/giữ chỗ để biết available/price.',
29
31
  inputSchema: z.object({
@@ -49,11 +51,13 @@ export function registerDomainTools(server, clients) {
49
51
  return results;
50
52
  }));
51
53
  server.registerTool('cloud_domain_registrant_get', {
54
+ annotations: toolAnnotationsForName('cloud_domain_registrant_get'),
52
55
  title: 'Xem thông tin chủ thể đăng ký tên miền',
53
56
  description: 'Lấy thông tin chủ thể (registrant) đã lưu. Cần dữ liệu này để mua tên miền.',
54
57
  inputSchema: z.object({}).strict(),
55
58
  }, () => runTool(() => clients.vibecloud('/api/domains/registrant')));
56
59
  server.registerTool('cloud_domain_registrant_set', {
60
+ annotations: toolAnnotationsForName('cloud_domain_registrant_set'),
57
61
  title: 'Cập nhật thông tin chủ thể đăng ký tên miền',
58
62
  description: 'Lưu thông tin chủ thể để đăng ký tên miền. HỎI NGƯỜI DÙNG cung cấp NGAY TRONG PHIÊN: họ tên, email, điện thoại, địa chỉ. Tên miền .vn cá nhân cần thêm CCCD 12 số + ngày sinh (DD/MM/YYYY) + giới tính; .vn tổ chức cần org_name + tax_code (MST) + representative. Lưu 1 lần, tái dùng cho các domain sau. Không tự bịa dữ liệu — thiếu trường nào thì hỏi đúng trường đó.',
59
63
  inputSchema: registrantSchema,
@@ -62,6 +66,7 @@ export function registerDomainTools(server, clients) {
62
66
  body: args,
63
67
  })));
64
68
  server.registerTool('cloud_domain_buy', {
69
+ annotations: toolAnnotationsForName('cloud_domain_buy'),
65
70
  title: 'Mua tên miền',
66
71
  description: [
67
72
  'Mua tên miền và trừ ví VND — làm trọn trong phiên; người dùng chỉ quét QR và xác nhận, không phải đăng ký trước hay tự làm trên web. Trước khi gọi:',
@@ -93,6 +98,7 @@ export function registerDomainTools(server, clients) {
93
98
  }));
94
99
  // --- Guest → reserve → claim (spec monadomain §12): mua TRƯỚC khi có tài khoản ---------------
95
100
  server.registerTool('cloud_domain_reserve', {
101
+ annotations: toolAnnotationsForName('cloud_domain_reserve'),
96
102
  title: 'Giữ chỗ tên miền + QR trả tiền (không cần tài khoản)',
97
103
  description: [
98
104
  'Giữ chỗ tên miền 30 phút cho người dùng CHƯA có MONA Pass (hoặc chưa login trên máy này) — không trừ tiền, không đăng ký thật, chỉ khoá tên trong hệ MONA Cloud.',
@@ -119,6 +125,7 @@ export function registerDomainTools(server, clients) {
119
125
  ...(clients.sandboxEnabled(sandbox === true) ? { headers: { 'X-Vibecloud-Sandbox': '1' } } : {}),
120
126
  })));
121
127
  server.registerTool('cloud_domain_reserve_status', {
128
+ annotations: toolAnnotationsForName('cloud_domain_reserve_status'),
122
129
  title: 'Trạng thái giữ chỗ tên miền',
123
130
  description: 'Xem reservation đã nhận tiền chưa / đã claim chưa / còn hạn không. Guest truyền claim_token (hoặc guest_token) nhận từ cloud_domain_reserve; chủ reservation đã login thì không cần. Đọc next_step trong kết quả để biết bước kế.',
124
131
  inputSchema: z.object({
@@ -127,6 +134,7 @@ export function registerDomainTools(server, clients) {
127
134
  }).strict(),
128
135
  }, ({ reservation_id, claim_token }) => runTool(() => clients.vibecloudGuestOk(`/api/domains/reserve/${encodeURIComponent(reservation_id)}`, { query: claim_token ? { t: claim_token } : undefined, guestToken: claim_token })));
129
136
  server.registerTool('cloud_domain_claim', {
137
+ annotations: toolAnnotationsForName('cloud_domain_claim'),
130
138
  title: 'Nhận reservation về tài khoản + mua (claim = đăng ký = trả tiền)',
131
139
  description: [
132
140
  'Gắn reservation vào MONA Pass đang đăng nhập, kéo tiền đã chuyển (nếu có) về ví rồi MUA ngay. Cần đăng nhập (monacloud-mcp login) — lần đầu đăng nhập MONA Cloud tự tạo hồ sơ + ví, không có form đăng ký riêng.',
@@ -141,6 +149,7 @@ export function registerDomainTools(server, clients) {
141
149
  }).strict(),
142
150
  }, ({ reservation_id, ...body }) => runTool(() => clients.vibecloud(`/api/domains/reserve/${encodeURIComponent(reservation_id)}/claim`, { method: 'POST', body })));
143
151
  server.registerTool('cloud_domain_reserve_release', {
152
+ annotations: toolAnnotationsForName('cloud_domain_reserve_release'),
144
153
  title: 'Nhả chỗ tên miền đã giữ',
145
154
  description: 'Huỷ reservation chưa nhận tiền (người dùng đổi ý / chọn tên khác). Đã có tiền vào thì không huỷ được — dùng cloud_domain_claim.',
146
155
  inputSchema: z.object({
@@ -149,6 +158,7 @@ export function registerDomainTools(server, clients) {
149
158
  }).strict(),
150
159
  }, ({ reservation_id, claim_token }) => runTool(() => clients.vibecloudGuestOk(`/api/domains/reserve/${encodeURIComponent(reservation_id)}`, { method: 'DELETE', query: claim_token ? { t: claim_token } : undefined, guestToken: claim_token })));
151
160
  server.registerTool('cloud_domain_list', {
161
+ annotations: toolAnnotationsForName('cloud_domain_list'),
152
162
  title: 'Danh sách tên miền đã import/mua',
153
163
  description: 'Liệt kê tên miền đã đưa vào MONA Cloud (import + mua). sandbox=true → xem domain sandbox.',
154
164
  inputSchema: z.object({
@@ -159,6 +169,7 @@ export function registerDomainTools(server, clients) {
159
169
  return clients.vibecloud('/api/domains', { headers });
160
170
  }));
161
171
  server.registerTool('cloud_domain_verify_start', {
172
+ annotations: toolAnnotationsForName('cloud_domain_verify_start'),
162
173
  title: 'Bắt đầu nộp hồ sơ .vn',
163
174
  description: 'Sinh bản khai đã điền sẵn và link upload hồ sơ .vn. Dùng sau khi mua tên miền .vn (status=pending_verification).',
164
175
  inputSchema: z.object({
@@ -168,6 +179,7 @@ export function registerDomainTools(server, clients) {
168
179
  method: 'POST',
169
180
  })));
170
181
  server.registerTool('cloud_domain_verify_status', {
182
+ annotations: toolAnnotationsForName('cloud_domain_verify_status'),
171
183
  title: 'Trạng thái hồ sơ .vn',
172
184
  description: 'Kiểm tra trạng thái duyệt hồ sơ đăng ký .vn (profile_status từ MONA Host).',
173
185
  inputSchema: z.object({
@@ -175,6 +187,7 @@ export function registerDomainTools(server, clients) {
175
187
  }).strict(),
176
188
  }, ({ id }) => runTool(() => clients.vibecloud(`/api/domains/${encodeURIComponent(id)}/verify`)));
177
189
  server.registerTool('cloud_domain_health', {
190
+ annotations: toolAnnotationsForName('cloud_domain_health'),
178
191
  title: 'Kiểm tra sức khoẻ tên miền',
179
192
  description: 'Kiểm tra hạn đăng ký, trạng thái hồ sơ, NS, SSL và cảnh báo tên miền đã mua.',
180
193
  inputSchema: z.object({
@@ -182,6 +195,7 @@ export function registerDomainTools(server, clients) {
182
195
  }).strict(),
183
196
  }, ({ id }) => runTool(() => clients.vibecloud(`/api/domains/${encodeURIComponent(id)}/health`)));
184
197
  server.registerTool('cloud_domain_renew', {
198
+ annotations: toolAnnotationsForName('cloud_domain_renew'),
185
199
  title: 'Gia hạn tên miền (trừ ví VND)',
186
200
  description: [
187
201
  'Gia hạn tên miền đã mua qua MONA Cloud. LUÔN gọi dry_run=true trước để lấy price_vnd (đã VAT), HỎI người dùng duyệt số tiền, rồi gọi lại dry_run=false — lúc đó trừ ví VND và gia hạn thật ở registrar.',
@@ -194,6 +208,7 @@ export function registerDomainTools(server, clients) {
194
208
  }).strict(),
195
209
  }, ({ id, billing_cycle, dry_run }) => runTool(() => clients.guardedVibecloud(`/api/domains/${encodeURIComponent(id)}/renew`, { method: 'POST', body: { billing_cycle, dry_run: dry_run !== false } }, false)));
196
210
  server.registerTool('cloud_domain_wait', {
211
+ annotations: toolAnnotationsForName('cloud_domain_wait'),
197
212
  title: 'Chờ tên miền active',
198
213
  description: 'Long-poll cho đến khi tên miền chuyển sang active/failed (mặc định timeout=60s). Dùng sau cloud_domain_buy để chờ MONA Host xử lý.',
199
214
  inputSchema: z.object({
@@ -205,6 +220,7 @@ export function registerDomainTools(server, clients) {
205
220
  return clients.vibecloud(`/api/domains/${encodeURIComponent(id)}/wait${params}`);
206
221
  }));
207
222
  server.registerTool('cloud_domain_webhook_set', {
223
+ annotations: toolAnnotationsForName('cloud_domain_webhook_set'),
208
224
  title: 'Đăng ký webhook tên miền',
209
225
  description: 'Đăng ký URL nhận sự kiện domain.status_changed (ký HMAC). Mỗi user 1 webhook; gọi lại để cập nhật.',
210
226
  inputSchema: z.object({
@@ -216,6 +232,7 @@ export function registerDomainTools(server, clients) {
216
232
  body: { url, secret },
217
233
  })));
218
234
  server.registerTool('cloud_domain_attach', {
235
+ annotations: toolAnnotationsForName('cloud_domain_attach'),
219
236
  title: 'Gắn tên miền vào ứng dụng',
220
237
  description: [
221
238
  'Gắn tên miền vào app đã deploy. Tên miền .vn chưa active vẫn gọi được — attach đặt trước, server tự hoàn tất sau khi hồ sơ được duyệt và gửi webhook domain.status_changed.',
@@ -242,26 +259,31 @@ export function registerDomainTools(server, clients) {
242
259
  priority: z.number().int().min(0).max(65535).optional().describe('Ưu tiên (MX/SRV).'),
243
260
  };
244
261
  server.registerTool('cloud_domain_dns_list', {
262
+ annotations: toolAnnotationsForName('cloud_domain_dns_list'),
245
263
  title: 'Xem bản ghi DNS của tên miền',
246
264
  description: 'Liệt kê bản ghi DNS (A/CNAME/MX/TXT...) của tên miền đăng ký tại MONA Cloud. domain_id lấy từ cloud_domain_list.',
247
265
  inputSchema: z.object({ domain_id: objectId }).strict(),
248
266
  }, ({ domain_id }) => runTool(() => clients.vibecloud(`/api/domains/${encodeURIComponent(domain_id)}/records`)));
249
267
  server.registerTool('cloud_domain_dns_add', {
268
+ annotations: toolAnnotationsForName('cloud_domain_dns_add'),
250
269
  title: 'Thêm bản ghi DNS',
251
270
  description: 'Thêm 1 bản ghi DNS. Vd trỏ web: type=A, name=@, data=<IP>. Trỏ www: type=CNAME, name=www, data=<domain>. AI làm trọn.',
252
271
  inputSchema: z.object({ domain_id: objectId, ...recordSchema }).strict(),
253
272
  }, ({ domain_id, ...record }) => runTool(() => clients.vibecloud(`/api/domains/${encodeURIComponent(domain_id)}/records`, { method: 'POST', body: record })));
254
273
  server.registerTool('cloud_domain_dns_update', {
274
+ annotations: toolAnnotationsForName('cloud_domain_dns_update'),
255
275
  title: 'Sửa bản ghi DNS',
256
276
  description: 'Sửa 1 bản ghi DNS theo record_id (lấy từ cloud_domain_dns_list).',
257
277
  inputSchema: z.object({ domain_id: objectId, record_id: z.string().min(1).max(128), ...recordSchema }).strict(),
258
278
  }, ({ domain_id, record_id, ...record }) => runTool(() => clients.vibecloud(`/api/domains/${encodeURIComponent(domain_id)}/records/${encodeURIComponent(record_id)}`, { method: 'PUT', body: record })));
259
279
  server.registerTool('cloud_domain_dns_delete', {
280
+ annotations: toolAnnotationsForName('cloud_domain_dns_delete'),
260
281
  title: 'Xoá bản ghi DNS',
261
282
  description: 'Xoá 1 bản ghi DNS theo record_id.',
262
283
  inputSchema: z.object({ domain_id: objectId, record_id: z.string().min(1).max(128) }).strict(),
263
284
  }, ({ domain_id, record_id }) => runTool(() => clients.vibecloud(`/api/domains/${encodeURIComponent(domain_id)}/records/${encodeURIComponent(record_id)}`, { method: 'DELETE' })));
264
285
  server.registerTool('cloud_domain_ns_set', {
286
+ annotations: toolAnnotationsForName('cloud_domain_ns_set'),
265
287
  title: 'Đổi nameserver (NS) của tên miền',
266
288
  description: 'Đổi NS cho tên miền (≥2). Vd giữ DNS ở MONA: ns1.mona.host, ns2.mona.host. Hoặc chuyển sang Cloudflare/nhà khác. AI làm hoàn toàn.',
267
289
  inputSchema: z.object({
package/dist/mail.js CHANGED
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto';
2
2
  import { domainToASCII } from 'node:url';
3
3
  import { z } from 'zod';
4
4
  import { runTool } from './errors.js';
5
+ import { toolAnnotationsForName } from './monapay.js';
5
6
  const id = z.string().trim().min(1).max(255);
6
7
  const email = z.string().max(320).regex(/^[^\s@<>,;]+@[^\s@<>,;]+\.[^\s@<>,;]+$/, 'Cần địa chỉ email hợp lệ.');
7
8
  const mailbox = z.union([
@@ -32,21 +33,25 @@ function idempotencyHeaders(key) {
32
33
  }
33
34
  export function registerMailTools(server, clients, config) {
34
35
  server.registerTool('mail_account', {
36
+ annotations: toolAnnotationsForName('mail_account'),
35
37
  title: 'Tài khoản MONA Mail',
36
38
  description: `Khi bắt đầu tích hợp email, đọc tài khoản, quota và bước kế tiếp. / Use first to read account and quota at ${config.monamailApi}.`,
37
39
  inputSchema: empty,
38
40
  }, () => runTool(() => clients.mail('/v1/account')));
39
41
  server.registerTool('mail_plans', {
42
+ annotations: toolAnnotationsForName('mail_plans'),
40
43
  title: 'Gói MONA Mail',
41
44
  description: 'Khi chọn gói gửi mail, đọc giá và quota hiện hành. / Use to compare current email plans.',
42
45
  inputSchema: empty,
43
46
  }, () => runTool(() => clients.mail('/v1/plans')));
44
47
  server.registerTool('mail_plan_set', {
48
+ annotations: toolAnnotationsForName('mail_plan_set'),
45
49
  title: 'Đổi gói MONA Mail',
46
50
  description: 'Khi cần đổi quota, chọn gói; gói trả phí trừ ví VND, thiếu tiền gọi cloud_topup. / Use to change the email plan.',
47
51
  inputSchema: z.object({ plan: z.enum(['free', 'khoi-nghiep', 'kinh-doanh', 'doanh-nghiep']) }).strict(),
48
52
  }, ({ plan }) => runTool(() => clients.mail('/v1/account/plan', { method: 'PUT', body: { plan } })));
49
53
  server.registerTool('mail_send', {
54
+ annotations: toolAnnotationsForName('mail_send'),
50
55
  title: 'Gửi email giao dịch',
51
56
  description: 'Khi gửi OTP hoặc thông báo, dùng domain đã verify; onboarding@monamail.vn chỉ gửi tới email chủ. sandbox=true thử 0đ, không gửi ra Internet. / Use to send transactional email or test in sandbox.',
52
57
  inputSchema: z.object({
@@ -79,11 +84,13 @@ export function registerMailTools(server, clients, config) {
79
84
  return sandbox ? { ...result, sandbox: true } : result;
80
85
  }));
81
86
  server.registerTool('mail_status', {
87
+ annotations: toolAnnotationsForName('mail_status'),
82
88
  title: 'Trạng thái email',
83
89
  description: 'Khi cần xác nhận thư đã giao, đọc trạng thái, events và sandbox_preview. / Use to inspect an email after sending.',
84
90
  inputSchema: z.object({ email_id: id }).strict(),
85
91
  }, ({ email_id }) => runTool(() => clients.mail(`/v1/emails/${encodeURIComponent(email_id)}`)));
86
92
  server.registerTool('mail_list', {
93
+ annotations: toolAnnotationsForName('mail_list'),
87
94
  title: 'Danh sách email',
88
95
  description: 'Khi tra lịch sử gửi, lọc theo trạng thái, người nhận hoặc thời gian. / Use to search sent email history.',
89
96
  inputSchema: z.object({
@@ -94,6 +101,7 @@ export function registerMailTools(server, clients, config) {
94
101
  }).strict(),
95
102
  }, (query) => runTool(() => clients.mail('/v1/emails', { query })));
96
103
  server.registerTool('mail_domain_add', {
104
+ annotations: toolAnnotationsForName('mail_domain_add'),
97
105
  title: 'Thêm domain gửi email',
98
106
  description: 'Khi gửi bằng domain của app, thêm domain. Trả record DNS; nếu người dùng dùng Cloudflare có thể gọi mail_domain_cloudflare với token của họ (không lưu). / Use to register a sender domain and get DNS records.',
99
107
  inputSchema: z.object({
@@ -107,6 +115,7 @@ export function registerMailTools(server, clients, config) {
107
115
  instructions: 'Thêm các records vào DNS rồi gọi mail_domain_verify. Nếu dùng Cloudflare, gọi mail_domain_cloudflare với token người dùng cung cấp; token chỉ dùng một lần, không lưu. DKIM đúng là đủ để verify; SPF/DMARC là cảnh báo. Nếu đã có SPF, thêm include:_spf.monamail.vn vào record hiện có.',
108
116
  })));
109
117
  server.registerTool('mail_domain_verify', {
118
+ annotations: toolAnnotationsForName('mail_domain_verify'),
110
119
  title: 'Xác minh DNS domain',
111
120
  description: 'Khi đã thêm DNS, kiểm DKIM và trạng thái domain. / Use after adding DNS records to verify the sender domain.',
112
121
  inputSchema: z.object({ domain_id: id, ...idempotency }).strict(),
@@ -114,6 +123,7 @@ export function registerMailTools(server, clients, config) {
114
123
  method: 'POST', headers: idempotencyHeaders(idempotency_key),
115
124
  })));
116
125
  server.registerTool('mail_domain_cloudflare', {
126
+ annotations: toolAnnotationsForName('mail_domain_cloudflare'),
117
127
  title: 'Thêm DNS qua Cloudflare',
118
128
  description: 'Khi người dùng cung cấp token Cloudflare, thêm DNS rồi verify domain. Token dùng một lần, không lưu, không log. / Use a user-provided Cloudflare token to configure DNS and verify.',
119
129
  inputSchema: z.object({ domain_id: id, api_token: z.string().trim().min(1).max(4096), ...idempotency }).strict(),
@@ -121,11 +131,13 @@ export function registerMailTools(server, clients, config) {
121
131
  method: 'POST', headers: idempotencyHeaders(idempotency_key), body: { api_token },
122
132
  })));
123
133
  server.registerTool('mail_domains_list', {
134
+ annotations: toolAnnotationsForName('mail_domains_list'),
124
135
  title: 'Domain MONA Mail',
125
136
  description: 'Khi chọn địa chỉ gửi, xem domain và trạng thái xác minh. / Use to find verified sender domains.',
126
137
  inputSchema: empty,
127
138
  }, () => runTool(() => clients.mail('/v1/domains')));
128
139
  server.registerTool('mail_api_key_create', {
140
+ annotations: toolAnnotationsForName('mail_api_key_create'),
129
141
  title: 'Tạo API key MONA Mail',
130
142
  description: 'Khi tích hợp SDK vào app, tạo key live hoặc test. Key chỉ trả một lần; ghi vào .env của app dưới tên MONAMAIL_API_KEY, không cần in ra chat. / Use to create an app key; store the one-time secret in .env.',
131
143
  inputSchema: z.object({ name: id, mode: z.enum(['live', 'test']), ...idempotency }).strict(),
@@ -133,16 +145,19 @@ export function registerMailTools(server, clients, config) {
133
145
  method: 'POST', headers: idempotencyHeaders(idempotency_key), body,
134
146
  })));
135
147
  server.registerTool('mail_api_keys_list', {
148
+ annotations: toolAnnotationsForName('mail_api_keys_list'),
136
149
  title: 'Danh sách API key',
137
150
  description: 'Khi kiểm tra key của app, đọc prefix và trạng thái; không trả secret. / Use to inspect existing API key metadata.',
138
151
  inputSchema: empty,
139
152
  }, () => runTool(() => clients.mail('/v1/api-keys')));
140
153
  server.registerTool('mail_api_key_revoke', {
154
+ annotations: toolAnnotationsForName('mail_api_key_revoke'),
141
155
  title: 'Thu hồi API key',
142
156
  description: 'Khi key không còn dùng hoặc bị lộ, thu hồi bằng key_id. / Use to revoke an unused or compromised API key.',
143
157
  inputSchema: z.object({ key_id: id }).strict(),
144
158
  }, ({ key_id }) => runTool(() => clients.mail(`/v1/api-keys/${encodeURIComponent(key_id)}`, { method: 'DELETE' })));
145
159
  server.registerTool('mail_webhook_create', {
160
+ annotations: toolAnnotationsForName('mail_webhook_create'),
146
161
  title: 'Tạo webhook email',
147
162
  description: 'Khi app cần nhận sự kiện gửi hoặc bounce, đăng ký HTTPS webhook; lưu secret một lần vào .env, không log. / Use to subscribe an app to email events.',
148
163
  inputSchema: z.object({ url: httpsUrl, events: z.array(events).min(1).max(8), ...idempotency }).strict(),
@@ -150,11 +165,13 @@ export function registerMailTools(server, clients, config) {
150
165
  method: 'POST', headers: idempotencyHeaders(idempotency_key), body,
151
166
  })));
152
167
  server.registerTool('mail_webhooks_list', {
168
+ annotations: toolAnnotationsForName('mail_webhooks_list'),
153
169
  title: 'Danh sách webhook email',
154
170
  description: 'Khi kiểm tra cấu hình sự kiện của app, liệt kê webhook. / Use to inspect registered email webhooks.',
155
171
  inputSchema: empty,
156
172
  }, () => runTool(() => clients.mail('/v1/webhooks')));
157
173
  server.registerTool('mail_webhook_test', {
174
+ annotations: toolAnnotationsForName('mail_webhook_test'),
158
175
  title: 'Thử webhook email',
159
176
  description: 'Khi đã có endpoint, gửi mẫu email.delivered để kiểm tra HTTP response. / Use to test webhook delivery to an app.',
160
177
  inputSchema: z.object({ webhook_id: id, ...idempotency }).strict(),
@@ -162,16 +179,19 @@ export function registerMailTools(server, clients, config) {
162
179
  method: 'POST', headers: idempotencyHeaders(idempotency_key),
163
180
  })));
164
181
  server.registerTool('mail_suppressions_list', {
182
+ annotations: toolAnnotationsForName('mail_suppressions_list'),
165
183
  title: 'Địa chỉ ngừng gửi',
166
184
  description: 'Khi thư bị suppressed, xem địa chỉ và lý do ngừng gửi. / Use to diagnose suppressed recipients.',
167
185
  inputSchema: empty,
168
186
  }, () => runTool(() => clients.mail('/v1/suppressions')));
169
187
  server.registerTool('mail_suppression_remove', {
188
+ annotations: toolAnnotationsForName('mail_suppression_remove'),
170
189
  title: 'Gỡ suppression tài khoản',
171
190
  description: 'Khi đã xử lý nguyên nhân chặn, gỡ suppression của tài khoản; lớp toàn hệ không gỡ được. / Use to remove an account-level suppression.',
172
191
  inputSchema: z.object({ email }).strict(),
173
192
  }, ({ email }) => runTool(() => clients.mail(`/v1/suppressions/${encodeURIComponent(email)}`, { method: 'DELETE' })));
174
193
  server.registerTool('mail_template_create', {
194
+ annotations: toolAnnotationsForName('mail_template_create'),
175
195
  title: 'Tạo mẫu email',
176
196
  description: 'Khi app dùng lại nội dung mail, tạo template với biến {{ten_bien}}. / Use to create a reusable email template.',
177
197
  inputSchema: z.object({ name: id, subject, html: z.string().min(1), text: z.string().min(1).optional(), ...idempotency }).strict(),
@@ -179,12 +199,14 @@ export function registerMailTools(server, clients, config) {
179
199
  method: 'POST', headers: idempotencyHeaders(idempotency_key), body,
180
200
  })));
181
201
  server.registerTool('mail_stats', {
202
+ annotations: toolAnnotationsForName('mail_stats'),
182
203
  title: 'Thống kê gửi email',
183
204
  description: 'Khi đánh giá khả năng giao thư, đọc tỷ lệ delivered và bounce theo thời gian. / Use to review email delivery statistics.',
184
205
  inputSchema: z.object({ from: dateOrTimestamp.optional(), to: dateOrTimestamp.optional() }).strict().refine((value) => !value.from || !value.to || Date.parse(value.from) <= Date.parse(value.to), 'Thời điểm from phải trước hoặc bằng to.'),
185
206
  }, (query) => runTool(() => clients.mail('/v1/stats', { query })));
186
207
  // ── Hộp thư AI agent (nhận + đọc + trả lời) ─────────────────────────────
187
208
  server.registerTool('mail_inbox_create', {
209
+ annotations: toolAnnotationsForName('mail_inbox_create'),
188
210
  title: 'Tạo hộp thư agent',
189
211
  description: 'Khi cần một địa chỉ email cho agent trực (đọc thư, trả lời), tạo hộp. Bỏ domain để dùng miền agent.monamail.vn (nhận ngay); truyền domain đã verify để có địa chỉ brand. / Use to create an inbox an AI agent can read and reply from.',
190
212
  inputSchema: z.object({ agent_id: agentId, domain: domain.optional() }).strict(),
@@ -192,21 +214,25 @@ export function registerMailTools(server, clients, config) {
192
214
  method: 'POST', body: domain ? { agent_id, domain } : { agent_id },
193
215
  })));
194
216
  server.registerTool('mail_inbox_list', {
217
+ annotations: toolAnnotationsForName('mail_inbox_list'),
195
218
  title: 'Danh sách hộp agent',
196
219
  description: 'Khi cần biết agent đang trực những hộp nào, liệt kê hộp còn hoạt động. / Use to list active agent inboxes.',
197
220
  inputSchema: empty,
198
221
  }, () => runTool(() => clients.mail('/v1/inboxes')));
199
222
  server.registerTool('mail_inbox_get', {
223
+ annotations: toolAnnotationsForName('mail_inbox_get'),
200
224
  title: 'Xem hộp agent',
201
225
  description: 'Khi cần địa chỉ và trạng thái của một hộp, đọc chi tiết. / Use to read one inbox.',
202
226
  inputSchema: z.object({ inbox_id: id }).strict(),
203
227
  }, ({ inbox_id }) => runTool(() => clients.mail(`/v1/inboxes/${encodeURIComponent(inbox_id)}`)));
204
228
  server.registerTool('mail_inbox_delete', {
229
+ annotations: toolAnnotationsForName('mail_inbox_delete'),
205
230
  title: 'Xóa hộp agent',
206
231
  description: 'Khi hộp không còn dùng, xóa mềm; thư cũ còn đọc tới hết retention. / Use to soft-delete an inbox.',
207
232
  inputSchema: z.object({ inbox_id: id }).strict(),
208
233
  }, ({ inbox_id }) => runTool(() => clients.mail(`/v1/inboxes/${encodeURIComponent(inbox_id)}`, { method: 'DELETE' })));
209
234
  server.registerTool('mail_inbox_messages', {
235
+ annotations: toolAnnotationsForName('mail_inbox_messages'),
210
236
  title: 'Thư trong hộp agent',
211
237
  description: 'Khi trực hộp, liệt kê thư nhận; lọc seen=false để lấy thư chưa xử lý, phân trang bằng cursor. / Use to list messages an agent needs to handle.',
212
238
  inputSchema: z.object({
@@ -220,17 +246,20 @@ export function registerMailTools(server, clients, config) {
220
246
  query: { limit, cursor, since, ...(seen === undefined ? {} : { seen: String(seen) }) },
221
247
  })));
222
248
  server.registerTool('mail_inbox_message', {
249
+ annotations: toolAnnotationsForName('mail_inbox_message'),
223
250
  title: 'Đọc thư trong hộp',
224
251
  description: 'Khi cần nội dung đầy đủ, header và đính kèm của một thư, đọc chi tiết; lần đọc đầu đánh dấu đã xem. / Use to read a full inbox message before replying.',
225
252
  inputSchema: z.object({ inbox_id: id, message_id: id }).strict(),
226
253
  }, ({ inbox_id, message_id }) => runTool(() => clients.mail(`/v1/inboxes/${encodeURIComponent(inbox_id)}/messages/${encodeURIComponent(message_id)}`)));
227
254
  server.registerTool('mail_inbox_reply', {
255
+ annotations: toolAnnotationsForName('mail_inbox_reply'),
228
256
  title: 'Trả lời thư trong hộp',
229
257
  description: 'Khi agent trả lời khách, gửi đúng thread (In-Reply-To, References) từ địa chỉ hộp; cần text hoặc html. Việc khó rút lại (hứa giá, chuyển tiền) thì báo người, đừng tự gửi. / Use to reply to an inbox message in-thread.',
230
258
  inputSchema: z.object({ inbox_id: id, message_id: id, text: z.string().min(1).optional(), html: z.string().min(1).optional() })
231
259
  .strict().refine((value) => value.text || value.html, 'Cần text hoặc html để trả lời.'),
232
260
  }, ({ inbox_id, message_id, ...body }) => runTool(() => clients.mail(`/v1/inboxes/${encodeURIComponent(inbox_id)}/messages/${encodeURIComponent(message_id)}/reply`, { method: 'POST', body })));
233
261
  server.registerTool('mail_inbox_wait', {
262
+ annotations: toolAnnotationsForName('mail_inbox_wait'),
234
263
  title: 'Chờ thư trong hộp',
235
264
  description: 'Khi cần chờ thư tới (ví dụ mã OTP hoặc thư khớp mẫu), chờ tối đa timeout giây; có thư khớp trả full nội dung + extracted_code, hết giờ trả 204. Gọi lại nếu cần chờ lâu hơn. / Use to wait for an OTP or a matching message.',
236
265
  inputSchema: z.object({ inbox_id: id, match: inboxMatch, timeout: z.number().int().min(0).max(300).default(120) }).strict(),
@@ -238,6 +267,7 @@ export function registerMailTools(server, clients, config) {
238
267
  query: { match, timeout },
239
268
  })));
240
269
  server.registerTool('mail_inbox_update', {
270
+ annotations: toolAnnotationsForName('mail_inbox_update'),
241
271
  title: 'Cấu hình hộp agent',
242
272
  description: 'Khi cần đặt cách hộp xử lý thư: mode redirect (tự forward thư về email chủ) | store (chỉ lưu) | ai (để AI trực), địa chỉ forward_to, chat Telegram nhận báo, hoặc bật/tắt hộp. / Use to configure an inbox: redirect-forward email, Telegram chat, mode, active.',
243
273
  inputSchema: z.object({
@@ -249,6 +279,7 @@ export function registerMailTools(server, clients, config) {
249
279
  }).strict().refine((v) => Object.keys(v).length > 1, 'Cần ít nhất một trường để cập nhật.'),
250
280
  }, ({ inbox_id, ...body }) => runTool(() => clients.mail(`/v1/inboxes/${encodeURIComponent(inbox_id)}`, { method: 'PATCH', body })));
251
281
  server.registerTool('mail_inbox_batch', {
282
+ annotations: toolAnnotationsForName('mail_inbox_batch'),
252
283
  title: 'Tạo nhiều hộp agent',
253
284
  description: 'Khi cần tạo nhiều địa chỉ một lần (ví dụ mỗi nhân viên một hộp), tạo hàng loạt trong quota gói; lỗi từng hộp không hủy cả batch. / Use to create many inboxes at once.',
254
285
  inputSchema: z.object({
package/dist/monapay.d.ts CHANGED
@@ -10,6 +10,18 @@ type ImportedTool = {
10
10
  handler: (...args: unknown[]) => unknown;
11
11
  enabled: boolean;
12
12
  };
13
+ export type ToolAnnotations = {
14
+ readOnlyHint: boolean;
15
+ destructiveHint: boolean;
16
+ idempotentHint: boolean;
17
+ openWorldHint: boolean;
18
+ };
19
+ /** Mechanical metadata shared by native tools and tools imported from monapay-mcp. */
20
+ export declare function toolAnnotationsForName(name: string): ToolAnnotations;
21
+ export declare function completeToolMetadata(name: string, title: unknown, annotations: unknown): {
22
+ title: string;
23
+ annotations: ToolAnnotations;
24
+ };
13
25
  export declare function createMonaPayClient(config: Config, env?: NodeJS.ProcessEnv, fetchImpl?: typeof fetch): MonaPayClient;
14
26
  export declare function importedMonaPayTools(config: Config, env?: NodeJS.ProcessEnv, fetchImpl?: typeof fetch): Record<string, ImportedTool>;
15
27
  export declare function normalizeImportedResult(result: unknown): Promise<unknown>;
package/dist/monapay.js CHANGED
@@ -2,6 +2,47 @@ import { readFileSync } from 'node:fs';
2
2
  import { createServer as createMonaPayServer } from 'monapay-mcp';
3
3
  import { MonaPayClient } from 'monapay-mcp/dist/client.js';
4
4
  import { CloudError, errorResult } from './errors.js';
5
+ // cloud_subscription_update có thể huỷ gia hạn / dừng máy (cancel_action=stop) → xếp destructive để agent hỏi lại trước khi gọi.
6
+ const DESTRUCTIVE = /(?:^|_)(?:delete|remove|cancel|release|rotate|stop|destroy|reset|revoke|suspend)(?:_|$)|^cloud_subscription_update$/;
7
+ const READ_ONLY = /(?:^|_)(?:list|get|status|search|balance|ledger|health|whoami|usage|services|quote|detect|wait|logs|stats|templates|tlds|prices|packages)(?:_|$)/;
8
+ const IDEMPOTENT_WRITE = /(?:^|_)(?:set|update|attach)(?:_|$)/;
9
+ /** Mechanical metadata shared by native tools and tools imported from monapay-mcp. */
10
+ export function toolAnnotationsForName(name) {
11
+ const canonical = name.replace(/^vibecloud_/, 'cloud_');
12
+ const destructiveHint = DESTRUCTIVE.test(canonical);
13
+ const readOnlyHint = !destructiveHint && (READ_ONLY.test(canonical)
14
+ || canonical === 'cloud_open_console'
15
+ || canonical === 'mail_account'
16
+ || canonical === 'mail_plans'
17
+ || canonical === 'cloud_invoice_pdf'
18
+ || canonical === 'cloud_base_credentials'
19
+ || canonical === 'mail_inbox_message'
20
+ || canonical === 'mail_inbox_messages'
21
+ || canonical === 'monapay_me'
22
+ || canonical === 'monapay_verify_signature'
23
+ || canonical === 'monapay_generate_webhook_snippet'
24
+ || canonical === 'monapay_quickstart');
25
+ const idempotentHint = readOnlyHint
26
+ || (destructiveHint && !/(?:^|_)(?:rotate|reset)(?:_|$)/.test(canonical))
27
+ || (!destructiveHint && IDEMPOTENT_WRITE.test(canonical));
28
+ return {
29
+ readOnlyHint,
30
+ destructiveHint,
31
+ idempotentHint,
32
+ openWorldHint: canonical !== 'cloud_app_detect',
33
+ };
34
+ }
35
+ export function completeToolMetadata(name, title, annotations) {
36
+ const existing = annotations && typeof annotations === 'object'
37
+ ? annotations
38
+ : {};
39
+ return {
40
+ title: typeof title === 'string' && title.trim()
41
+ ? title
42
+ : `Công cụ MONA ${name.replace(/^(?:cloud|vibecloud|monapay|mail|agent)_/, '').replaceAll('_', ' ')}`,
43
+ annotations: { ...toolAnnotationsForName(name), ...existing },
44
+ };
45
+ }
5
46
  function linkedCredentials(config) {
6
47
  try {
7
48
  const parsed = JSON.parse(readFileSync(config.linksFile, 'utf8'));
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import { createServer as createHttpServer } from "node:http";
3
+ import { createRemoteServer } from "./remote.js";
4
+ const bind = process.env.MCP_REMOTE_BIND ?? "127.0.0.1";
5
+ const port = Number.parseInt(process.env.MCP_REMOTE_PORT ?? "8765", 10);
6
+ const publicUrl = process.env.MCP_REMOTE_PUBLIC_URL ?? "https://mcp.monacloud.vn";
7
+ const issuer = process.env.MONACLOUD_ISSUER ?? "https://pass.monacloud.vn/realms/mona";
8
+ const initialAccessToken = process.env.MONA_PASS_INITIAL_ACCESS_TOKEN;
9
+ if (!initialAccessToken)
10
+ throw new Error("MONA_PASS_INITIAL_ACCESS_TOKEN is required");
11
+ if (!Number.isInteger(port) || port < 1 || port > 65_535)
12
+ throw new Error("MCP_REMOTE_PORT is invalid");
13
+ const dataDir = process.env.MCP_REMOTE_DATA_DIR ?? "./data";
14
+ const remote = await createRemoteServer({ publicUrl, issuer, initialAccessToken, dataDir });
15
+ const httpServer = createHttpServer(remote.app);
16
+ httpServer.listen(port, bind, () => {
17
+ console.log(JSON.stringify({ event: "listening", bind, port }));
18
+ });
19
+ let stopping = false;
20
+ async function shutdown() {
21
+ if (stopping)
22
+ return;
23
+ stopping = true;
24
+ await remote.close();
25
+ await new Promise((resolve, reject) => {
26
+ httpServer.close((error) => error ? reject(error) : resolve());
27
+ });
28
+ }
29
+ process.on("SIGTERM", () => void shutdown());
30
+ process.on("SIGINT", () => void shutdown());
@@ -0,0 +1,17 @@
1
+ export interface VerifiedAccessToken {
2
+ token: string;
3
+ clientId: string;
4
+ scopes: string[];
5
+ expiresAt: number;
6
+ extra: {
7
+ sub: string;
8
+ };
9
+ }
10
+ export interface JwtVerifierOptions {
11
+ issuer: string;
12
+ fetch?: typeof globalThis.fetch;
13
+ jwksTtlMs?: number;
14
+ isRegisteredClient?: (clientId: string) => boolean | Promise<boolean>;
15
+ now?: () => number;
16
+ }
17
+ export declare function createAccessTokenVerifier(options: JwtVerifierOptions): (token: string) => Promise<VerifiedAccessToken>;
@@ -0,0 +1,62 @@
1
+ import { createPublicKey, verify as verifySignature } from "node:crypto";
2
+ function decodeJson(value) {
3
+ return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
4
+ }
5
+ function includesAccount(aud) {
6
+ return typeof aud === "string" ? aud === "account" : Array.isArray(aud) && aud.includes("account");
7
+ }
8
+ export function createAccessTokenVerifier(options) {
9
+ const issuer = options.issuer.replace(/\/$/, "");
10
+ const fetchImpl = options.fetch ?? globalThis.fetch;
11
+ const ttl = options.jwksTtlMs ?? 10 * 60_000;
12
+ const now = options.now ?? Date.now;
13
+ let cached;
14
+ async function getJwks() {
15
+ if (cached && cached.expires > now())
16
+ return cached.document;
17
+ const response = await fetchImpl(`${issuer}/protocol/openid-connect/certs`);
18
+ if (!response.ok)
19
+ throw new Error(`JWKS request failed: ${response.status}`);
20
+ const document = await response.json();
21
+ if (!Array.isArray(document.keys))
22
+ throw new Error("JWKS response has no keys");
23
+ cached = { expires: now() + ttl, document };
24
+ return document;
25
+ }
26
+ return async function verifyAccessToken(token) {
27
+ const parts = token.split(".");
28
+ if (parts.length !== 3)
29
+ throw new Error("Malformed JWT");
30
+ const header = decodeJson(parts[0]);
31
+ const claims = decodeJson(parts[1]);
32
+ if (header.alg !== "RS256" || !header.kid)
33
+ throw new Error("Unsupported JWT algorithm");
34
+ const jwks = await getJwks();
35
+ const jwk = jwks.keys?.find((key) => key.kid === header.kid && (!key.alg || key.alg === "RS256"));
36
+ if (!jwk)
37
+ throw new Error("JWT signing key not found");
38
+ const publicKey = createPublicKey({ key: jwk, format: "jwk" });
39
+ const valid = verifySignature("RSA-SHA256", Buffer.from(`${parts[0]}.${parts[1]}`), publicKey, Buffer.from(parts[2], "base64url"));
40
+ if (!valid)
41
+ throw new Error("Invalid JWT signature");
42
+ const currentSeconds = Math.floor(now() / 1000);
43
+ if (claims.iss !== issuer)
44
+ throw new Error("Invalid token issuer");
45
+ if (!claims.exp || claims.exp <= currentSeconds)
46
+ throw new Error("Expired access token");
47
+ if (!claims.sub || !claims.azp)
48
+ throw new Error("Missing token subject or authorized party");
49
+ const registered = options.isRegisteredClient
50
+ ? await options.isRegisteredClient(claims.azp)
51
+ : false;
52
+ if (!includesAccount(claims.aud) && !registered)
53
+ throw new Error("Invalid token audience");
54
+ return {
55
+ token,
56
+ clientId: claims.azp,
57
+ scopes: claims.scope?.split(/\s+/).filter(Boolean) ?? [],
58
+ expiresAt: claims.exp,
59
+ extra: { sub: claims.sub },
60
+ };
61
+ };
62
+ }
@@ -0,0 +1,21 @@
1
+ import express from "express";
2
+ import { createServer as createMcpServer } from "./server.js";
3
+ import { type VerifiedAccessToken } from "./remote-verify.js";
4
+ export interface RemoteServerOptions {
5
+ publicUrl: string;
6
+ issuer: string;
7
+ initialAccessToken: string;
8
+ /** Thư mục lưu client DCR (clients.json) để sống qua restart; bỏ trống = chỉ giữ trong RAM (test). */
9
+ dataDir?: string;
10
+ env?: NodeJS.ProcessEnv;
11
+ fetch?: typeof globalThis.fetch;
12
+ verifyAccessToken?: (token: string) => Promise<VerifiedAccessToken>;
13
+ createServer?: typeof createMcpServer;
14
+ now?: () => number;
15
+ }
16
+ export interface RemoteServer {
17
+ app: ReturnType<typeof express>;
18
+ close(): Promise<void>;
19
+ sessionCount(): number;
20
+ }
21
+ export declare function createRemoteServer(options: RemoteServerOptions): Promise<RemoteServer>;