monacloud-mcp 0.10.12 → 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 +3 -0
- package/dist/affiliate.d.ts +3 -0
- package/dist/affiliate.js +65 -0
- package/dist/monapay.js +2 -0
- package/dist/remote.js +38 -2
- package/dist/server.js +2 -0
- package/docs/ai-agent.md +6 -0
- package/docs/connectors-directory-submission.md +1 -1
- package/package.json +1 -1
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,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/monapay.js
CHANGED
|
@@ -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
|
@@ -7,6 +7,7 @@ import express from "express";
|
|
|
7
7
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
8
8
|
import { ProxyOAuthServerProvider } from "@modelcontextprotocol/sdk/server/auth/providers/proxyProvider.js";
|
|
9
9
|
import { mcpAuthRouter } from "@modelcontextprotocol/sdk/server/auth/router.js";
|
|
10
|
+
import { InvalidClientMetadataError } from "@modelcontextprotocol/sdk/server/auth/errors.js";
|
|
10
11
|
import { createServer as createMcpServer, PKG_VERSION } from "./server.js";
|
|
11
12
|
import { createAccessTokenVerifier } from "./remote-verify.js";
|
|
12
13
|
const SCOPES = ["openid", "profile", "email", "offline_access", "vibecloud-api", "billing-api"];
|
|
@@ -64,6 +65,24 @@ export async function createRemoteServer(options) {
|
|
|
64
65
|
});
|
|
65
66
|
const registrationUrl = `${issuer}/clients-registrations/openid-connect`;
|
|
66
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
|
+
}
|
|
67
86
|
const pendingAuth = new Map();
|
|
68
87
|
const issuedCodes = new Map();
|
|
69
88
|
const PENDING_TTL_MS = 10 * 60_000;
|
|
@@ -71,6 +90,16 @@ export async function createRemoteServer(options) {
|
|
|
71
90
|
// DCR: SDK router lo validate + rate-limit (express-rate-limit 1h/IP); registerClient đẩy sang Keycloak
|
|
72
91
|
// kèm Initial Access Token (Keycloak không mở đăng ký ẩn danh) và ép public client + PKCE.
|
|
73
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
|
+
}
|
|
74
103
|
const upstream = await fetchImpl(registrationUrl, {
|
|
75
104
|
method: "POST",
|
|
76
105
|
headers: {
|
|
@@ -80,8 +109,11 @@ export async function createRemoteServer(options) {
|
|
|
80
109
|
body: JSON.stringify({
|
|
81
110
|
...client,
|
|
82
111
|
client_id: undefined,
|
|
83
|
-
//
|
|
84
|
-
|
|
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],
|
|
85
117
|
grant_types: ["authorization_code", "refresh_token"],
|
|
86
118
|
token_endpoint_auth_method: "none",
|
|
87
119
|
scope: DCR_SCOPES.join(" "),
|
|
@@ -188,6 +220,10 @@ export async function createRemoteServer(options) {
|
|
|
188
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." });
|
|
189
221
|
return;
|
|
190
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
|
+
}
|
|
191
227
|
const target = new URL(pending.redirectUri);
|
|
192
228
|
if (typeof q.error === "string") {
|
|
193
229
|
target.searchParams.set("error", q.error);
|
package/dist/server.js
CHANGED
|
@@ -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:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
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
4
|
|
|
5
|
-
Điều kiện kỹ thuật đã đạt (
|
|
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
6
|
|
|
7
7
|
## 2. Connection
|
|
8
8
|
- Server URL: `https://mcp.monacloud.vn/mcp` · Transport: Streamable HTTP · Universal URL (một URL cho mọi người dùng).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "monacloud-mcp",
|
|
3
|
-
"version": "0.
|
|
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",
|