monacloud-mcp 0.10.11 → 0.11.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/README.md CHANGED
@@ -109,6 +109,9 @@ Tên tool dùng `snake_case` để giữ tương thích với prompt MONA Pay c
109
109
  | `cloud_budget_set` / `cloud_budget_get` | Đặt và đọc budget theo product/project/token |
110
110
  | `cloud_token_limit` | Đặt spend limit cho PAT/token |
111
111
  | `cloud_open_console` | Trả URL console chung |
112
+ | `cloud_affiliate_link` | Lấy mã, link giới thiệu, hạng và mức chia sẻ doanh thu; chỉ đăng sau khi người dùng duyệt và có disclosure |
113
+ | `cloud_affiliate_stats` | Đọc thống kê, số dư có thể nhận và tối đa 100 hoa hồng gần nhất |
114
+ | `cloud_affiliate_claim(code)` | Gắn mã giới thiệu vào tài khoản còn trong thời hạn nhận mã |
112
115
 
113
116
  ### MONA Pay
114
117
 
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { CloudClients } from './clients.js';
3
+ export declare function registerAffiliateTools(server: McpServer, clients: CloudClients): void;
@@ -0,0 +1,65 @@
1
+ import { z } from 'zod';
2
+ import { runTool } from './errors.js';
3
+ import { unwrapData } from './http.js';
4
+ import { completeToolMetadata } from './monapay.js';
5
+ const asObject = (value) => (value && typeof value === 'object' ? value : {});
6
+ const affiliate = (value) => asObject(unwrapData(value));
7
+ const LINK_NEXT_STEP = "Chèn link vào README/docs/bài viết của người dùng kèm câu công khai 'link giới thiệu, MONA Cloud chia sẻ doanh thu cho người giới thiệu'. Không tự đăng lên đâu nếu người dùng chưa duyệt.";
8
+ export function registerAffiliateTools(server, clients) {
9
+ const register = (name, description, schema, handler) => {
10
+ for (const toolName of [name, name.replace(/^cloud_/, 'vibecloud_')]) {
11
+ const metadata = completeToolMetadata(toolName, undefined, undefined);
12
+ server.registerTool(toolName, {
13
+ title: metadata.title,
14
+ annotations: metadata.annotations,
15
+ description: toolName === name
16
+ ? description
17
+ : `Alias tương thích của ${name}. / Compatibility alias. ${description}`,
18
+ inputSchema: schema,
19
+ }, (args) => runTool(() => handler(args)));
20
+ }
21
+ };
22
+ register('cloud_affiliate_link', 'Lấy link giới thiệu MONA Cloud và mức chia sẻ doanh thu để người dùng duyệt trước khi đăng. / Get the MONA Cloud referral link and revenue-share rates.', z.object({}).strict(), async () => {
23
+ const result = affiliate(await clients.vibecloud('/api/me/affiliate'));
24
+ return {
25
+ referral_code: result.referral_code,
26
+ link: result.link,
27
+ console_link: result.console_link,
28
+ tier: result.tier,
29
+ rates: result.rates,
30
+ next_step: LINK_NEXT_STEP,
31
+ };
32
+ });
33
+ register('cloud_affiliate_stats', 'Đọc thống kê, số dư có thể nhận và các hoa hồng gần nhất. / Read affiliate stats, payout availability and recent commissions.', z.object({
34
+ status: z.enum(['pending', 'approved', 'paid', 'reversed', 'flagged']).optional(),
35
+ limit: z.number().int().min(1).max(100).default(20),
36
+ }).strict(), async ({ status, limit }) => {
37
+ const [profileResponse, commissionResponse] = await Promise.all([
38
+ clients.vibecloud('/api/me/affiliate'),
39
+ clients.vibecloud('/api/me/affiliate/commissions', { query: { status, limit } }),
40
+ ]);
41
+ const profile = affiliate(profileResponse);
42
+ const commissionData = affiliate(commissionResponse);
43
+ return {
44
+ stats: profile.stats,
45
+ payout_available_vnd: profile.payout_available_vnd,
46
+ can_request_payout: profile.can_request_payout,
47
+ commissions: Array.isArray(commissionData.commissions) ? commissionData.commissions : [],
48
+ next_step: profile.can_request_payout
49
+ ? 'Có thể yêu cầu thanh toán trong MONA Cloud Console; kiểm tra thông tin nhận tiền trước khi gửi yêu cầu.'
50
+ : 'Theo dõi hoa hồng approved và ngưỡng thanh toán; chưa tự yêu cầu thanh toán thay người dùng.',
51
+ };
52
+ });
53
+ register('cloud_affiliate_claim', 'Gắn mã giới thiệu vào tài khoản hiện tại trong thời hạn cho phép. / Claim a referral code for the current account.', z.object({ code: z.string().trim().min(4).max(16) }).strict(), async ({ code }) => {
54
+ const result = affiliate(await clients.vibecloud('/api/me/referral/claim', {
55
+ method: 'POST',
56
+ body: { code },
57
+ }));
58
+ return {
59
+ ...result,
60
+ next_step: typeof result.next_step === 'string'
61
+ ? result.next_step
62
+ : 'Mã giới thiệu đã được ghi nhận; dùng cloud_affiliate_link nếu muốn lấy link của chính tài khoản này.',
63
+ };
64
+ });
65
+ }
package/dist/domains.js CHANGED
@@ -50,6 +50,71 @@ export function registerDomainTools(server, clients) {
50
50
  }
51
51
  return results;
52
52
  }));
53
+ server.registerTool('cloud_domain_tld_groups', {
54
+ annotations: toolAnnotationsForName('cloud_domain_tld_groups'),
55
+ title: 'Danh mục đuôi tên miền theo ngành',
56
+ description: 'Liệt kê các nhóm ngành của đuôi tên miền (Ăn uống, Công nghệ & AI, Quốc gia, Việt Nam...) kèm số lượng đuôi và khoảng giá VND mỗi nhóm. Gọi tool này TRƯỚC cloud_domain_tlds để biết slug ngành cần lọc. Không cần đăng nhập.',
57
+ inputSchema: z.object({}).strict(),
58
+ }, () => runTool(() => clients.vibecloudGuestOk('/api/domains/tlds/groups')));
59
+ server.registerTool('cloud_domain_tlds', {
60
+ annotations: toolAnnotationsForName('cloud_domain_tlds'),
61
+ title: 'Bảng giá đuôi tên miền',
62
+ description: 'Bảng giá đầy đủ các đuôi tên miền MONA Domain bán được, giá VND đã gồm VAT — giá năm đầu (price_vnd, đã tính khuyến mãi nếu registry đang chạy) và giá gia hạn mỗi năm (renew_vnd). Lọc theo ngành (group, lấy slug từ cloud_domain_tld_groups), theo từ khoá (q), hoặc theo ngân sách (max_price_vnd). Dùng khi người dùng hỏi "có những đuôi nào", "đuôi nào rẻ", "đuôi nào hợp ngành X". Đuôi .vn có needs_verification=true nghĩa là cần hồ sơ xác thực chủ thể. Không cần đăng nhập.',
63
+ inputSchema: z.object({
64
+ scope: z.enum(['popular', 'all']).optional()
65
+ .describe('popular = nhóm đuôi thông dụng (mặc định), all = toàn bộ danh sách.'),
66
+ group: z.string().max(40).optional()
67
+ .describe('Slug ngành từ cloud_domain_tld_groups (vd: "an-uong", "cong-nghe", "viet-nam").'),
68
+ q: z.string().max(40).optional().describe('Tìm trong tên đuôi và nhãn tiếng Việt (vd: "shop").'),
69
+ max_price_vnd: z.number().int().min(0).optional().describe('Chỉ lấy đuôi có giá năm đầu không quá số này.'),
70
+ sort: z.enum(['popular', 'price', 'tld']).optional().describe('Thứ tự sắp xếp. Mặc định popular.'),
71
+ limit: z.number().int().min(1).max(1000).optional().describe('Số dòng tối đa. Mặc định 200.'),
72
+ offset: z.number().int().min(0).optional().describe('Bỏ qua bao nhiêu dòng đầu, để lật trang.'),
73
+ }).strict(),
74
+ }, ({ scope, group, q, max_price_vnd, sort, limit, offset }) => runTool(() => {
75
+ const params = new URLSearchParams();
76
+ if (scope)
77
+ params.set('scope', scope);
78
+ if (group)
79
+ params.set('group', group);
80
+ if (q)
81
+ params.set('q', q);
82
+ if (max_price_vnd !== undefined)
83
+ params.set('max_price_vnd', String(max_price_vnd));
84
+ if (sort)
85
+ params.set('sort', sort);
86
+ if (limit !== undefined)
87
+ params.set('limit', String(limit));
88
+ if (offset !== undefined)
89
+ params.set('offset', String(offset));
90
+ const query = params.toString();
91
+ return clients.vibecloudGuestOk(`/api/domains/tlds${query ? `?${query}` : ''}`);
92
+ }));
93
+ server.registerTool('cloud_domain_suggest', {
94
+ annotations: toolAnnotationsForName('cloud_domain_suggest'),
95
+ title: 'Gợi ý tên miền còn trống',
96
+ description: 'Gợi ý những tên miền còn trống quanh một từ khoá, kèm giá VND đã gồm VAT. Từ khoá nhận tiếng Việt có dấu, tool tự bỏ dấu thành nhãn hợp lệ. Truyền industry (slug từ cloud_domain_tld_groups) để ưu tiên đuôi hợp ngành — vd quán cà phê sẽ ra .cafe và .coffee trước. Mọi kết quả đều đã tra trạng thái thật tại nhà đăng ký. Dùng khi tên người dùng muốn đã có người lấy, hoặc khi họ chưa nghĩ ra tên. Sau khi người dùng chọn thì gọi cloud_domain_buy (hoặc cloud_domain_reserve nếu chưa đăng nhập). Không cần đăng nhập.',
97
+ inputSchema: z.object({
98
+ keyword: z.string().min(1).max(63)
99
+ .describe('Tên muốn đặt hoặc từ khoá mô tả sản phẩm (vd: "Cà Phê Sạch", "myapp").'),
100
+ industry: z.string().max(40).optional()
101
+ .describe('Slug ngành từ cloud_domain_tld_groups để ưu tiên đuôi hợp ngành.'),
102
+ limit: z.number().int().min(1).max(30).optional().describe('Số gợi ý trả về. Mặc định 12.'),
103
+ max_price_vnd: z.number().int().min(0).optional().describe('Chặn trần giá năm đầu.'),
104
+ include_taken: z.boolean().optional().describe('true = giữ lại cả tên đã có người đăng ký.'),
105
+ }).strict(),
106
+ }, ({ keyword, industry, limit, max_price_vnd, include_taken }) => runTool(() => {
107
+ const params = new URLSearchParams({ keyword });
108
+ if (industry)
109
+ params.set('industry', industry);
110
+ if (limit !== undefined)
111
+ params.set('limit', String(limit));
112
+ if (max_price_vnd !== undefined)
113
+ params.set('max_price_vnd', String(max_price_vnd));
114
+ if (include_taken)
115
+ params.set('include_taken', 'true');
116
+ return clients.vibecloudGuestOk(`/api/domains/tlds/suggest?${params}`);
117
+ }));
53
118
  server.registerTool('cloud_domain_registrant_get', {
54
119
  annotations: toolAnnotationsForName('cloud_domain_registrant_get'),
55
120
  title: 'Xem thông tin chủ thể đăng ký tên miền',
package/dist/monapay.js CHANGED
@@ -4,7 +4,7 @@ import { MonaPayClient } from 'monapay-mcp/dist/client.js';
4
4
  import { CloudError, errorResult } from './errors.js';
5
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
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)(?:_|$)/;
7
+ const READ_ONLY = /(?:^|_)(?:list|get|status|search|balance|ledger|health|whoami|usage|services|quote|detect|wait|logs|stats|templates|tlds|prices|packages|suggest|groups)(?:_|$)/;
8
8
  const IDEMPOTENT_WRITE = /(?:^|_)(?:set|update|attach)(?:_|$)/;
9
9
  /** Mechanical metadata shared by native tools and tools imported from monapay-mcp. */
10
10
  export function toolAnnotationsForName(name) {
@@ -16,6 +16,8 @@ export function toolAnnotationsForName(name) {
16
16
  || canonical === 'mail_plans'
17
17
  || canonical === 'cloud_invoice_pdf'
18
18
  || canonical === 'cloud_base_credentials'
19
+ || canonical === 'cloud_affiliate_link'
20
+ || canonical === 'cloud_affiliate_stats'
19
21
  || canonical === 'mail_inbox_message'
20
22
  || canonical === 'mail_inbox_messages'
21
23
  || canonical === 'monapay_me'
package/dist/remote.js CHANGED
@@ -1,12 +1,14 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import { mkdirSync, mkdtempSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
3
4
  import { join } from "node:path";
4
5
  import cors from "cors";
5
6
  import express from "express";
6
7
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
7
8
  import { ProxyOAuthServerProvider } from "@modelcontextprotocol/sdk/server/auth/providers/proxyProvider.js";
8
9
  import { mcpAuthRouter } from "@modelcontextprotocol/sdk/server/auth/router.js";
9
- import { createServer as createMcpServer } from "./server.js";
10
+ import { InvalidClientMetadataError } from "@modelcontextprotocol/sdk/server/auth/errors.js";
11
+ import { createServer as createMcpServer, PKG_VERSION } from "./server.js";
10
12
  import { createAccessTokenVerifier } from "./remote-verify.js";
11
13
  const SCOPES = ["openid", "profile", "email", "offline_access", "vibecloud-api", "billing-api"];
12
14
  // Keycloak DCR chỉ nhận tên client scope thật (openid/profile/email là mặc định, gửi kèm sẽ bị policy "Allowed Client Scopes" từ chối).
@@ -63,6 +65,24 @@ export async function createRemoteServer(options) {
63
65
  });
64
66
  const registrationUrl = `${issuer}/clients-registrations/openid-connect`;
65
67
  const callbackUrl = `${publicUrl}/callback`;
68
+ // Nơi được phép nhận code sau khi đăng nhập. Client MCP thật chỉ nằm ở mấy chỗ này; ai khai redirect_uri
69
+ // ngoài danh sách thì proxy từ chối ngay từ bước đăng ký, khỏi cần người dùng tự nhận ra mình bị lừa.
70
+ const REDIRECT_ALLOW_HOSTS = (options.env?.MCP_REMOTE_REDIRECT_HOSTS || process.env.MCP_REMOTE_REDIRECT_HOSTS
71
+ || "claude.ai,claude.com,anthropic.com,server.smithery.ai,smithery.ai,connect.smithery.ai,cursor.com,cursor.sh,chatgpt.com,openai.com,localhost,127.0.0.1,[::1]")
72
+ .split(",").map((host) => host.trim().toLowerCase()).filter(Boolean);
73
+ function redirectAllowed(uri) {
74
+ let parsed;
75
+ try {
76
+ parsed = new URL(uri);
77
+ }
78
+ catch {
79
+ return false;
80
+ }
81
+ const host = parsed.hostname.toLowerCase();
82
+ if (parsed.protocol !== "https:" && host !== "localhost" && host !== "127.0.0.1" && host !== "[::1]")
83
+ return false;
84
+ return REDIRECT_ALLOW_HOSTS.some((allowed) => host === allowed || host.endsWith(`.${allowed}`));
85
+ }
66
86
  const pendingAuth = new Map();
67
87
  const issuedCodes = new Map();
68
88
  const PENDING_TTL_MS = 10 * 60_000;
@@ -70,6 +90,16 @@ export async function createRemoteServer(options) {
70
90
  // DCR: SDK router lo validate + rate-limit (express-rate-limit 1h/IP); registerClient đẩy sang Keycloak
71
91
  // kèm Initial Access Token (Keycloak không mở đăng ký ẩn danh) và ép public client + PKCE.
72
92
  async function registerClient(client) {
93
+ const wanted = client.redirect_uris || [];
94
+ const rejected = wanted.filter((uri) => !redirectAllowed(uri));
95
+ if (!wanted.length || rejected.length) {
96
+ // PHẢI là OAuthError thì SDK mới trả 400 kèm mã lỗi; `new Error` thường bị quy thành
97
+ // 500 server_error, khiến client thật tưởng máy chủ hỏng và cứ thử lại thay vì sửa redirect_uri.
98
+ const why = rejected.length
99
+ ? `redirect_uri không được phép: ${rejected.join(", ")}`
100
+ : "thiếu redirect_uris";
101
+ throw new InvalidClientMetadataError(`${why}. Chỉ nhận https thuộc ${REDIRECT_ALLOW_HOSTS.join(", ")} (localhost cho phép http).`);
102
+ }
73
103
  const upstream = await fetchImpl(registrationUrl, {
74
104
  method: "POST",
75
105
  headers: {
@@ -79,8 +109,11 @@ export async function createRemoteServer(options) {
79
109
  body: JSON.stringify({
80
110
  ...client,
81
111
  client_id: undefined,
82
- // Keycloak trả code về /callback của proxy (để không lộ tham số iss của Keycloak cho client) → phải đăng ký thêm URI này.
83
- redirect_uris: [...new Set([...client.redirect_uris, callbackUrl])],
112
+ // CHỈ callback của proxy. Không bao giờ đẩy redirect_uri do client tự khai lên Keycloak:
113
+ // proxy luôn nhận code ở /callback (authorize + exchange đều dùng callbackUrl), redirect_uri của client
114
+ // chỉ dùng ở hop cuối trong handler /callback. Đẩy lên Keycloak = mở đường cho kẻ lạ tự đăng ký client
115
+ // rồi lái code về máy nó (báo cáo bảo mật 21/09/2026).
116
+ redirect_uris: [callbackUrl],
84
117
  grant_types: ["authorization_code", "refresh_token"],
85
118
  token_endpoint_auth_method: "none",
86
119
  scope: DCR_SCOPES.join(" "),
@@ -187,6 +220,10 @@ export async function createRemoteServer(options) {
187
220
  res.status(400).json({ error: "invalid_request", error_description: "Phiên đăng nhập hết hạn hoặc không hợp lệ, hãy kết nối lại." });
188
221
  return;
189
222
  }
223
+ if (!redirectAllowed(pending.redirectUri)) {
224
+ res.status(400).json({ error: "invalid_request", error_description: "Địa chỉ nhận kết quả đăng nhập không nằm trong danh sách cho phép." });
225
+ return;
226
+ }
190
227
  const target = new URL(pending.redirectUri);
191
228
  if (typeof q.error === "string") {
192
229
  target.searchParams.set("error", q.error);
@@ -206,7 +243,7 @@ export async function createRemoteServer(options) {
206
243
  res.set("Location", target.toString()).status(302).end();
207
244
  });
208
245
  app.get("/healthz", (_req, res) => {
209
- res.json({ ok: true, version: process.env.npm_package_version ?? "0.10.10", sessions: sessions.size });
246
+ res.json({ ok: true, version: PKG_VERSION, sessions: sessions.size });
210
247
  });
211
248
  // RFC 9728 cho phép metadata theo path của resource (/.well-known/oauth-protected-resource/mcp) — Smithery/Claude thử URL này trước.
212
249
  app.get(["/.well-known/oauth-protected-resource", "/.well-known/oauth-protected-resource/mcp"], (_req, res) => {
@@ -308,7 +345,11 @@ export async function createRemoteServer(options) {
308
345
  sessionIdGenerator: randomUUID,
309
346
  onsessioninitialized: (id) => { pendingSessionId = id; },
310
347
  });
311
- const env = { ...process.env, ...options.env, MONACLOUD_TOKEN: auth.token };
348
+ // Mỗi người dùng (sub MONA Pass) một thư mục cấu hình riêng: monapay_link ghi links.json, tuyệt đối không dùng chung
349
+ // ~/.config/monacloud của tiến trình (nếu dùng chung, credential MONA Pay của người này lọt sang phiên người khác).
350
+ const userDir = join(options.dataDir ?? mkdtempSync(join(tmpdir(), "mcp-remote-")), "users", auth.extra.sub.replace(/[^a-zA-Z0-9_-]/g, "_"));
351
+ mkdirSync(userDir, { recursive: true, mode: 0o700 });
352
+ const env = { ...process.env, ...options.env, MONACLOUD_TOKEN: auth.token, MONACLOUD_CONFIG_DIR: userDir };
312
353
  const server = createServer({ env });
313
354
  await server.connect(transport);
314
355
  await transport.handleRequest(req, res, req.body);
package/dist/server.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { Config } from './config.js';
3
+ export declare const PKG_VERSION: string;
3
4
  export type ServerDependencies = {
4
5
  config?: Config;
5
6
  env?: NodeJS.ProcessEnv;
package/dist/server.js CHANGED
@@ -5,7 +5,7 @@ import { readConfig } from './config.js';
5
5
  import { AuthManager } from './auth.js';
6
6
  import { CloudClients } from './clients.js';
7
7
  // serverInfo.version đọc từ package.json (từng hardcode 0.8.1 → client báo sai bản)
8
- const PKG_VERSION = (() => {
8
+ export const PKG_VERSION = (() => {
9
9
  try {
10
10
  return String(createRequire(import.meta.url)('../package.json').version);
11
11
  }
@@ -22,6 +22,7 @@ import { APP_FLOW, createVps, registerComputeTools } from './compute.js';
22
22
  import { pollJob } from './jobs.js';
23
23
  import { registerBaseTools } from './base.js';
24
24
  import { registerDomainTools } from './domains.js';
25
+ import { registerAffiliateTools } from './affiliate.js';
25
26
  import { presentTopup } from './qr.js';
26
27
  const ENTITY = 'MONA Cloud là hệ công cụ cho vibecoder Việt Nam: một MONA Pass, một ví VND và một MCP để chạy app/VPS, thu tiền MONA Pay và dùng các sản phẩm MONA. Base beta thay Supabase bằng cloud_base_*, chung account/ví MONA Cloud và khớp app deploy. Dùng mail_* để gửi email giao dịch (MONA Mail). MONA Mail là dịch vụ gửi email giao dịch cho phần mềm và AI agent của người Việt: một API, trả VND qua ví MONA Cloud (nạp bằng VietQR), thuộc nhóm MONA Cloud của The MONA Group.';
27
28
  const SANDBOX_DESCRIPTION = 'sandbox=true: thử 0đ, không cần ví';
@@ -359,6 +360,7 @@ export function createServer(dependencies = {}) {
359
360
  registerBaseTools(server, clients);
360
361
  registerMailTools(server, clients, config);
361
362
  registerDomainTools(server, clients);
363
+ registerAffiliateTools(server, clients);
362
364
  server.registerTool('agent_templates_list', {
363
365
  annotations: toolAnnotationsForName('agent_templates_list'),
364
366
  title: 'Catalog MONA Agent',
package/docs/ai-agent.md CHANGED
@@ -25,6 +25,12 @@ Human chỉ cần đăng ký MONA Pass/device flow một lần, duyệt chi phí
25
25
  6. Không giao hàng chỉ dựa vào browser redirect. Chỉ xác nhận đơn sau webhook `CHECKOUT_PAID` hoặc đối soát server-side.
26
26
  7. Nếu tool trả `isError`, parse text content thành `{code,message,next_step,request_id?}` và làm theo `next_step`. Không retry vô hạn lệnh ghi.
27
27
 
28
+ ## Chia sẻ doanh thu
29
+
30
+ Khi người dùng hỏi cách kiếm tiền hoặc giới thiệu MONA Cloud, gọi `cloud_affiliate_link` để lấy đúng link, hạng và mức chia sẻ doanh thu hiện tại. Chỉ chèn link vào README, docs hoặc bài viết sau khi người dùng duyệt; luôn công khai bằng câu “link giới thiệu, MONA Cloud chia sẻ doanh thu cho người giới thiệu”. Không tự đăng nội dung ra bên ngoài.
31
+
32
+ `cloud_affiliate_stats` đọc thống kê và hoa hồng gần nhất. `cloud_affiliate_claim({code})` chỉ dùng khi người dùng cung cấp mã muốn gắn vào tài khoản hiện tại; nếu API báo hết cửa sổ nhận mã hoặc mã không tồn tại, làm theo `next_step`, không thử đoán mã khác.
33
+
28
34
  ## Xác thực
29
35
 
30
36
  MCP dùng OAuth 2.0 Device Authorization Grant với public client `monacloud-mcp`, scope:
@@ -0,0 +1,48 @@
1
+ # Nộp Claude Connectors Directory — bản điền sẵn (Mon dán vào portal)
2
+
3
+ Portal: https://claude.ai/admin-settings/directory/submissions/new — **cần tài khoản Claude Team/Enterprise, vai Owner**. Cá nhân không có mục này. Tài liệu: https://claude.com/docs/connectors/building/submission
4
+
5
+ Điều kiện kỹ thuật đã đạt (cập nhật 21/09/2026): server HTTPS `https://mcp.monacloud.vn/mcp` (Streamable HTTP) · OAuth 2.0 + PKCE + Dynamic Client Registration (`/.well-known/oauth-authorization-server`, `/register`) · 180 tool đều có `title` + `readOnlyHint`/`destructiveHint` (monacloud-mcp ≥ 0.10.12) · chính sách quyền riêng tư https://monacloud.vn/chinh-sach-quyen-rieng-tu (EN: https://monacloud.vn/en/privacy) · Smithery đã quét thành công 180 tool (thẻ tự cập nhật đủ 180 sau 24h, 3 prompt, 2 resource).
6
+
7
+ ## 2. Connection
8
+ - Server URL: `https://mcp.monacloud.vn/mcp` · Transport: Streamable HTTP · Universal URL (một URL cho mọi người dùng).
9
+
10
+ ## 4. Listing
11
+ - Name (≤100): `MONA Cloud`
12
+ - Tagline (≤55): `Domains .vn, deploy, Postgres, VietQR pay for Vietnam`
13
+ - Description (≤2000, tiếng Anh):
14
+ MONA Cloud gives Claude the tools Vietnamese builders need to ship a product end to end. Search and buy .vn and 370+ international domains priced in VND with VAT, reserve a name for 30 minutes and pay by VietQR from any Vietnamese banking app, then point DNS and SSL at your app. Deploy the app you are working on to MONA Cloud (a Vercel-style host billed in VND), create Postgres, Auth and Storage bases (a Supabase-style backend), send transactional email with MONA Mail, and accept bank-transfer payments with MONA Pay, all from one MONA Pass account and one VND wallet. When the wallet runs low, Claude shows a VietQR code; you scan it and continue. MONA Cloud is operated by The MONA Group (Vietnam, since 2016, 14,000+ projects). Domain search and quotes work before you sign in; buying, deploying and paying use your MONA Pass login.
15
+ - Categories: Developer Tools · Cloud & Infrastructure · Domains/Hosting · Payments (chọn 1–5 theo danh mục portal)
16
+ - Documentation URL: https://monacloud.vn/docs/mcp-va-sdk (thêm: https://monadomain.vn/docs, https://github.com/themonagroup/monacloud-mcp/blob/main/docs/remote.md)
17
+ - Privacy policy URL: https://monacloud.vn/en/privacy
18
+ - Support contact: info@themona.global · hotline 1900 636 648
19
+ - Icon: `~/mona-hub/brand/mona/monacloud/fav-monacloud.svg` (PNG 512: https://monadomain.vn/icon-512.png)
20
+ - Slug: `mona-cloud`
21
+
22
+ ## 5. Use cases
23
+ - Primary use cases: (1) buy and configure a .vn or international domain for the app being built, in VND; (2) deploy the current project and databases to MONA Cloud and manage them; (3) accept VietQR bank-transfer payments and send transactional email from the app.
24
+ - Prerequisites: a free MONA Pass account (created on first login, Google/GitHub/email). Paid actions need VND in the wallet; Claude shows a VietQR to top up. .vn domains require the registrant's identity per Vietnamese law (Claude fills the form, the user verifies once).
25
+ - Reads and writes data: both.
26
+
27
+ ## Example prompts (≥3, mỗi prompt đụng tool khác nhau)
28
+ 1. "Buy a domain for this app, prefer .vn, budget 800,000 VND." → cloud_domain_search → cloud_domain_reserve → cloud_domain_claim/cloud_domain_buy
29
+ 2. "Deploy this folder to MONA Cloud and give me the URL." → cloud_app_detect → cloud_app_create → cloud_app_status
30
+ 3. "Create a Postgres base for this project and put the connection string in .env." → cloud_base_create → cloud_base_credentials
31
+ 4. "Point mydomain.vn at the deployed app with SSL." → cloud_domain_attach → cloud_domain_dns_list
32
+ 5. "Check my wallet balance and top up 200,000 VND." → cloud_balance → cloud_topup → cloud_topup_status
33
+
34
+ ## 6. Company
35
+ - The MONA Group (Công ty TNHH MONA MEDIA), https://mona.media — contact: Vy Nguyễn Khánh Hùng, mon@themona.global
36
+
37
+ ## 7. Authentication
38
+ - OAuth 2.0 with **dynamic client registration** (RFC 7591) at `https://mcp.monacloud.vn/register`; authorization server metadata at `https://mcp.monacloud.vn/.well-known/oauth-authorization-server`; PKCE S256; refresh tokens (offline_access). Identity provider: MONA Pass (Keycloak) at pass.monacloud.vn.
39
+
40
+ ## 8. Data handling
41
+ - Underlying API is our own (api.monacloud.vn, billing.monacloud.vn, MONA Pass). No personal health data. No sponsored content. Domain registration data is passed to the registrar/VNNIC as required by law (see privacy policy).
42
+
43
+ ## 9. Test & launch
44
+ - Test account: tài khoản MONA Pass riêng cho reviewer — lấy trong `~/.config/mona/monapass-e2e.env` (user `mon+mcp-e2e@themona.global`). Trước khi nộp: nạp ví test ~200.000đ để reviewer chạy được tool có phí ở sandbox (`MONACLOUD_SANDBOX` không áp cho remote → nêu rõ tool nào tính tiền).
45
+ - Steps for reviewer: Add custom connector → URL above → sign in with the test account → run `cloud_whoami`, `cloud_balance`, `cloud_domain_search` (q="monatest", tlds="vn,com"), `cloud_domain_reserve` (creates a 30-minute hold, no charge), `cloud_app_detect` on any folder.
46
+ - Confirm: every tool exercised via `support/e2e-remote.mjs` + MCP Inspector (19/09/2026).
47
+
48
+ ## 10. Compliance — 7 xác nhận (đọc và tick trong portal).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "monacloud-mcp",
3
- "version": "0.10.11",
3
+ "version": "0.11.0",
4
4
  "description": "MCP server for MONA Cloud — deploy apps (Vercel alternative), Supabase-compatible Postgres/Auth/Storage bases (Supabase alternative), VND wallet, MONA Pay & Mail — for AI agents (Claude, Cursor, Codex) in Vietnam.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://monacloud.vn",