multi-app-mcp 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.
- package/README.md +106 -0
- package/index.js +59 -0
- package/package.json +20 -0
- package/plugins/quickreviewer/auth.js +478 -0
- package/plugins/quickreviewer/documents.js +476 -0
- package/plugins/quickreviewer/folders.js +453 -0
- package/plugins/quickreviewer/index.js +32 -0
- package/plugins/quickreviewer/session.js +30 -0
- package/plugins/quickreviewer/teams.js +280 -0
- package/plugins/quickreviewer/upload.js +234 -0
- package/plugins/quickreviewer/workflows.js +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Multi-App MCP Server
|
|
2
|
+
|
|
3
|
+
Ek single MCP Server jo multiple apps ke saath kaam karta hai.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
qr-mcp-server/
|
|
9
|
+
├── index.js ← MCP core (touch mat karo)
|
|
10
|
+
├── plugins/
|
|
11
|
+
│ └── quickreviewer.js ← Quickreviewer ke tools
|
|
12
|
+
├── .env.example ← Environment variables template
|
|
13
|
+
└── package.json
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Setup
|
|
17
|
+
|
|
18
|
+
### 1. Install
|
|
19
|
+
```bash
|
|
20
|
+
npm install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 2. .env banao
|
|
24
|
+
```bash
|
|
25
|
+
cp .env.example .env
|
|
26
|
+
# Phir .env mein apni API URL daalo
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 3. Claude Desktop Config
|
|
30
|
+
|
|
31
|
+
**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
32
|
+
**Mac:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"mcpServers": {
|
|
37
|
+
"my-apps": {
|
|
38
|
+
"command": "node",
|
|
39
|
+
"args": ["C:/full/path/to/qr-mcp-server/index.js"],
|
|
40
|
+
"env": {
|
|
41
|
+
"QR_API_URL": "https://your-quickreviewer-domain.com"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 4. Claude Desktop restart karo ✅
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Nayi App Add Karna
|
|
53
|
+
|
|
54
|
+
Sirf 3 steps:
|
|
55
|
+
|
|
56
|
+
**Step 1** — Naya plugin banao: `plugins/app2.js`
|
|
57
|
+
```js
|
|
58
|
+
const APP2_URL = process.env.APP2_API_URL;
|
|
59
|
+
|
|
60
|
+
export const tools = [
|
|
61
|
+
{
|
|
62
|
+
name: "app2_create_order",
|
|
63
|
+
description: "Create an order in App2",
|
|
64
|
+
inputSchema: {
|
|
65
|
+
type: "object",
|
|
66
|
+
properties: {
|
|
67
|
+
item: { type: "string", description: "Item name" },
|
|
68
|
+
},
|
|
69
|
+
required: ["item"],
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
export const handlers = {
|
|
75
|
+
app2_create_order: async ({ item }) => {
|
|
76
|
+
const res = await fetch(`${APP2_URL}/orders`, {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers: { "Content-Type": "application/json" },
|
|
79
|
+
body: JSON.stringify({ item }),
|
|
80
|
+
});
|
|
81
|
+
const data = await res.json();
|
|
82
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Step 2** — `index.js` mein import karo (sirf 2 lines):
|
|
88
|
+
```js
|
|
89
|
+
import { tools as app2Tools, handlers as app2Handlers } from "./plugins/app2.js";
|
|
90
|
+
|
|
91
|
+
const allTools = [...qrTools, ...app2Tools];
|
|
92
|
+
const allHandlers = { ...qrHandlers, ...app2Handlers };
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Step 3** — `.env` mein URL add karo:
|
|
96
|
+
```
|
|
97
|
+
APP2_API_URL=https://your-app2-domain.com
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Available Tools
|
|
103
|
+
|
|
104
|
+
| Tool | App | Description |
|
|
105
|
+
|------|-----|-------------|
|
|
106
|
+
| `qr_register_user` | Quickreviewer | Naya user register karo |
|
package/index.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import {
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
|
|
10
|
+
import { tools as qrTools, handlers as qrHandlers } from "./plugins/quickreviewer/index.js";
|
|
11
|
+
|
|
12
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
// PLUGIN REGISTRY
|
|
14
|
+
// Nayi app add karni ho to bas yahan 2 lines add karo:
|
|
15
|
+
// import { tools as app2Tools, handlers as app2Handlers } from "./plugins/app2.js";
|
|
16
|
+
// allTools push, allHandlers spread
|
|
17
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const allTools = [...qrTools];
|
|
20
|
+
const allHandlers = { ...qrHandlers };
|
|
21
|
+
|
|
22
|
+
// ─── MCP Server Setup ─────────────────────────────────────────────────────────
|
|
23
|
+
const server = new Server(
|
|
24
|
+
{ name: "multi-app-mcp", version: "1.0.0" },
|
|
25
|
+
{ capabilities: { tools: {} } }
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// Claude poochta hai: "kya kya kar sakte ho?"
|
|
29
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
30
|
+
tools: allTools,
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
// Claude ek tool call karta hai
|
|
34
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
35
|
+
const { name, arguments: args } = request.params;
|
|
36
|
+
|
|
37
|
+
const handler = allHandlers[name];
|
|
38
|
+
|
|
39
|
+
if (!handler) {
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: "text", text: `❌ Unknown tool: "${name}"` }],
|
|
42
|
+
isError: true,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
return await handler(args);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
return {
|
|
50
|
+
content: [{ type: "text", text: `❌ Error in tool "${name}": ${err.message}` }],
|
|
51
|
+
isError: true,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// ─── Start Server ─────────────────────────────────────────────────────────────
|
|
57
|
+
const transport = new StdioServerTransport();
|
|
58
|
+
await server.connect(transport);
|
|
59
|
+
console.error("✅ Multi-App MCP Server running...");
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "multi-app-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Single MCP Server for multiple apps",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"multi-app-mcp": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.js",
|
|
12
|
+
"plugins/"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"start": "node index.js"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// AUTH — Register, Login, Logout, Password tools
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
import { session, getAuthHeaders, requireLogin } from "./session.js";
|
|
6
|
+
|
|
7
|
+
const QR_API_URL = process.env.QR_API_URL || "https://app.quickreviewer.com/api";
|
|
8
|
+
|
|
9
|
+
const b64 = (str) => Buffer.from(str).toString("base64");
|
|
10
|
+
|
|
11
|
+
// ─── Tool Definitions ─────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export const tools = [
|
|
14
|
+
{
|
|
15
|
+
name: "qr_register_user",
|
|
16
|
+
description:
|
|
17
|
+
"Register a new user in Quickreviewer. " +
|
|
18
|
+
"Ask for userName, userEmail, and userPassword if not provided. " +
|
|
19
|
+
"promoCode is optional.",
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: {
|
|
23
|
+
userName: { type: "string", description: "Full name of the user" },
|
|
24
|
+
userEmail: { type: "string", description: "Email address of the user" },
|
|
25
|
+
userPassword: { type: "string", description: "Password (min 6 characters)" },
|
|
26
|
+
promoCode: { type: "string", description: "Optional promo code" },
|
|
27
|
+
},
|
|
28
|
+
required: ["userName", "userEmail", "userPassword"],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "qr_login",
|
|
33
|
+
description:
|
|
34
|
+
"Login to Quickreviewer with email and password. " +
|
|
35
|
+
"Saves the auth token in memory so other tools work automatically. " +
|
|
36
|
+
"Call this first before using any other QR tool.",
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: "object",
|
|
39
|
+
properties: {
|
|
40
|
+
userEmail: { type: "string", description: "Registered email address" },
|
|
41
|
+
userPassword: { type: "string", description: "Account password" },
|
|
42
|
+
},
|
|
43
|
+
required: ["userEmail", "userPassword"],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "qr_logout",
|
|
48
|
+
description: "Logout from Quickreviewer. Clears the saved auth token from memory.",
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: "object",
|
|
51
|
+
properties: {},
|
|
52
|
+
required: [],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "qr_forgot_password",
|
|
57
|
+
description:
|
|
58
|
+
"Send a password reset email to the given email address. " +
|
|
59
|
+
"User does not need to be logged in.",
|
|
60
|
+
inputSchema: {
|
|
61
|
+
type: "object",
|
|
62
|
+
properties: {
|
|
63
|
+
userEmail: { type: "string", description: "Email address to send reset link to" },
|
|
64
|
+
},
|
|
65
|
+
required: ["userEmail"],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "qr_resend_verification_code",
|
|
70
|
+
description:
|
|
71
|
+
"Resend the email verification code to the user's email. " +
|
|
72
|
+
"Useful if the user did not receive the code after registration.",
|
|
73
|
+
inputSchema: {
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: {
|
|
76
|
+
email: { type: "string", description: "Email address to resend the code to" },
|
|
77
|
+
},
|
|
78
|
+
required: ["email"],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "qr_validate_verification_code",
|
|
83
|
+
description:
|
|
84
|
+
"Validate the email verification code sent after registration. " +
|
|
85
|
+
"Provide the email and the code received.",
|
|
86
|
+
inputSchema: {
|
|
87
|
+
type: "object",
|
|
88
|
+
properties: {
|
|
89
|
+
email: { type: "string", description: "Email address of the user" },
|
|
90
|
+
code: { type: "string", description: "Verification code received via email" },
|
|
91
|
+
},
|
|
92
|
+
required: ["email", "code"],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "qr_change_password",
|
|
97
|
+
description:
|
|
98
|
+
"Change the password of the currently logged-in user. " +
|
|
99
|
+
"Requires the current password and the new password. " +
|
|
100
|
+
"User must be logged in first.",
|
|
101
|
+
inputSchema: {
|
|
102
|
+
type: "object",
|
|
103
|
+
properties: {
|
|
104
|
+
currentPassword: { type: "string", description: "Current account password" },
|
|
105
|
+
userPassword: { type: "string", description: "New password (min 6 characters)" },
|
|
106
|
+
},
|
|
107
|
+
required: ["currentPassword", "userPassword"],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "qr_get_storage",
|
|
112
|
+
description:
|
|
113
|
+
"Check storage usage of the logged-in user's Quickreviewer account. " +
|
|
114
|
+
"Returns used space, total space, and usage percentage. " +
|
|
115
|
+
"User must be logged in.",
|
|
116
|
+
inputSchema: {
|
|
117
|
+
type: "object",
|
|
118
|
+
properties: {},
|
|
119
|
+
required: [],
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "qr_send_feedback",
|
|
124
|
+
description:
|
|
125
|
+
"Send feedback or a support request to Quickreviewer. " +
|
|
126
|
+
"ALWAYS ask for BOTH fields before calling this tool: " +
|
|
127
|
+
"1) subject — a short title for the feedback, " +
|
|
128
|
+
"2) comment — the detailed message. " +
|
|
129
|
+
"Do NOT call this tool until both subject and comment are provided by the user. " +
|
|
130
|
+
"User must be logged in.",
|
|
131
|
+
inputSchema: {
|
|
132
|
+
type: "object",
|
|
133
|
+
properties: {
|
|
134
|
+
subject: { type: "string", description: "Subject of the feedback or support request" },
|
|
135
|
+
comment: { type: "string", description: "Detailed message or feedback" },
|
|
136
|
+
},
|
|
137
|
+
required: ["subject", "comment"],
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "qr_get_notifications",
|
|
142
|
+
description:
|
|
143
|
+
"Get notifications for the logged-in user from Quickreviewer. " +
|
|
144
|
+
"Returns 10 notifications per page, sorted newest first. " +
|
|
145
|
+
"To get all notifications keep fetching next pages until no more results. " +
|
|
146
|
+
"User must be logged in.",
|
|
147
|
+
inputSchema: {
|
|
148
|
+
type: "object",
|
|
149
|
+
properties: {
|
|
150
|
+
page: { type: "integer", description: "Page number (start from 1)" },
|
|
151
|
+
},
|
|
152
|
+
required: ["page"],
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
// ─── Handlers ─────────────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
export const handlers = {
|
|
160
|
+
|
|
161
|
+
qr_register_user: async ({ userName, userEmail, userPassword, promoCode }) => {
|
|
162
|
+
const body = { userName, userEmail, userPassword };
|
|
163
|
+
if (promoCode) body.promoCode = promoCode;
|
|
164
|
+
|
|
165
|
+
const res = await fetch(`${QR_API_URL}/signup`, {
|
|
166
|
+
method: "POST",
|
|
167
|
+
headers: getAuthHeaders(),
|
|
168
|
+
body: JSON.stringify(body),
|
|
169
|
+
});
|
|
170
|
+
const data = await res.json();
|
|
171
|
+
|
|
172
|
+
if (res.ok) {
|
|
173
|
+
return {
|
|
174
|
+
content: [{
|
|
175
|
+
type: "text",
|
|
176
|
+
text:
|
|
177
|
+
`✅ User registered successfully!\n\n` +
|
|
178
|
+
`Name: ${userName}\n` +
|
|
179
|
+
`Email: ${userEmail}\n\n` +
|
|
180
|
+
`API Response:\n${JSON.stringify(data, null, 2)}`,
|
|
181
|
+
}],
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
content: [{
|
|
186
|
+
type: "text",
|
|
187
|
+
text: `❌ Registration failed (HTTP ${res.status})\n\n${JSON.stringify(data, null, 2)}`,
|
|
188
|
+
}],
|
|
189
|
+
isError: true,
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
qr_login: async ({ userEmail, userPassword }) => {
|
|
194
|
+
const res = await fetch(`${QR_API_URL}/login`, {
|
|
195
|
+
method: "POST",
|
|
196
|
+
headers: getAuthHeaders(),
|
|
197
|
+
body: JSON.stringify({ userEmail, userPassword: b64(userPassword) }),
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const rawText = await res.text();
|
|
201
|
+
let data;
|
|
202
|
+
try {
|
|
203
|
+
data = JSON.parse(rawText);
|
|
204
|
+
} catch {
|
|
205
|
+
return {
|
|
206
|
+
content: [{
|
|
207
|
+
type: "text",
|
|
208
|
+
text:
|
|
209
|
+
`❌ Server returned non-JSON response (HTTP ${res.status})\n\n` +
|
|
210
|
+
`API URL: ${QR_API_URL}/login\n\n` +
|
|
211
|
+
`Raw Response:\n${rawText.slice(0, 500)}`,
|
|
212
|
+
}],
|
|
213
|
+
isError: true,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (res.ok) {
|
|
218
|
+
session.token = data.accessToken || data.token || null;
|
|
219
|
+
session.userId = data.userId || null;
|
|
220
|
+
session.userName = data.displayName || data.userName || null;
|
|
221
|
+
session.userEmail = userEmail;
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
content: [{
|
|
225
|
+
type: "text",
|
|
226
|
+
text:
|
|
227
|
+
`✅ Logged in successfully!\n\n` +
|
|
228
|
+
`Email: ${userEmail}\n` +
|
|
229
|
+
`Token saved — other tools are ready to use.\n\n` +
|
|
230
|
+
`API Response:\n${JSON.stringify(data, null, 2)}`,
|
|
231
|
+
}],
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
content: [{
|
|
236
|
+
type: "text",
|
|
237
|
+
text: `❌ Login failed (HTTP ${res.status})\n\n${JSON.stringify(data, null, 2)}`,
|
|
238
|
+
}],
|
|
239
|
+
isError: true,
|
|
240
|
+
};
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
qr_logout: async () => {
|
|
244
|
+
if (!session.token) {
|
|
245
|
+
return { content: [{ type: "text", text: "⚠️ You are not logged in." }] };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const res = await fetch(`${QR_API_URL}/logout`, {
|
|
249
|
+
method: "GET",
|
|
250
|
+
headers: getAuthHeaders(),
|
|
251
|
+
});
|
|
252
|
+
const data = await res.json().catch(() => ({}));
|
|
253
|
+
|
|
254
|
+
const loggedOutEmail = session.userEmail;
|
|
255
|
+
session.token = null;
|
|
256
|
+
session.userId = null;
|
|
257
|
+
session.userName = null;
|
|
258
|
+
session.userEmail = null;
|
|
259
|
+
|
|
260
|
+
if (res.ok) {
|
|
261
|
+
return {
|
|
262
|
+
content: [{ type: "text", text: `✅ Logged out successfully!\n\nEmail: ${loggedOutEmail}` }],
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
content: [{
|
|
267
|
+
type: "text",
|
|
268
|
+
text: `⚠️ API error but session cleared.\n\nHTTP ${res.status}: ${JSON.stringify(data, null, 2)}`,
|
|
269
|
+
}],
|
|
270
|
+
};
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
qr_forgot_password: async ({ userEmail }) => {
|
|
274
|
+
const res = await fetch(`${QR_API_URL}/forgot-password`, {
|
|
275
|
+
method: "POST",
|
|
276
|
+
headers: getAuthHeaders(),
|
|
277
|
+
body: JSON.stringify({ userEmail }),
|
|
278
|
+
});
|
|
279
|
+
const data = await res.json();
|
|
280
|
+
|
|
281
|
+
if (res.ok) {
|
|
282
|
+
return {
|
|
283
|
+
content: [{
|
|
284
|
+
type: "text",
|
|
285
|
+
text: `✅ Password reset email sent!\n\nEmail: ${userEmail}\nPlease check your inbox.`,
|
|
286
|
+
}],
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
content: [{
|
|
291
|
+
type: "text",
|
|
292
|
+
text: `❌ Failed (HTTP ${res.status})\n\n${JSON.stringify(data, null, 2)}`,
|
|
293
|
+
}],
|
|
294
|
+
isError: true,
|
|
295
|
+
};
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
qr_resend_verification_code: async ({ email }) => {
|
|
299
|
+
const res = await fetch(`${QR_API_URL}/resend-verification-code`, {
|
|
300
|
+
method: "POST",
|
|
301
|
+
headers: getAuthHeaders(),
|
|
302
|
+
body: JSON.stringify({ email }),
|
|
303
|
+
});
|
|
304
|
+
const data = await res.json();
|
|
305
|
+
|
|
306
|
+
if (res.ok) {
|
|
307
|
+
return {
|
|
308
|
+
content: [{ type: "text", text: `✅ Verification code resent!\n\nEmail: ${email}` }],
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
content: [{
|
|
313
|
+
type: "text",
|
|
314
|
+
text: `❌ Failed (HTTP ${res.status})\n\n${JSON.stringify(data, null, 2)}`,
|
|
315
|
+
}],
|
|
316
|
+
isError: true,
|
|
317
|
+
};
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
qr_validate_verification_code: async ({ email, code }) => {
|
|
321
|
+
const res = await fetch(`${QR_API_URL}/validate-verification-code`, {
|
|
322
|
+
method: "POST",
|
|
323
|
+
headers: getAuthHeaders(),
|
|
324
|
+
body: JSON.stringify({ email, code }),
|
|
325
|
+
});
|
|
326
|
+
const data = await res.json();
|
|
327
|
+
|
|
328
|
+
if (res.ok) {
|
|
329
|
+
return {
|
|
330
|
+
content: [{
|
|
331
|
+
type: "text",
|
|
332
|
+
text: `✅ Email verified successfully!\n\nEmail: ${email}\nYour account is now active.`,
|
|
333
|
+
}],
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
content: [{
|
|
338
|
+
type: "text",
|
|
339
|
+
text: `❌ Verification failed (HTTP ${res.status})\n\n${JSON.stringify(data, null, 2)}`,
|
|
340
|
+
}],
|
|
341
|
+
isError: true,
|
|
342
|
+
};
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
qr_change_password: async ({ currentPassword, userPassword }) => {
|
|
346
|
+
const authError = requireLogin();
|
|
347
|
+
if (authError) return authError;
|
|
348
|
+
|
|
349
|
+
if (!currentPassword || !userPassword) {
|
|
350
|
+
return {
|
|
351
|
+
content: [{
|
|
352
|
+
type: "text",
|
|
353
|
+
text:
|
|
354
|
+
`❌ Both fields are required to change password:\n\n` +
|
|
355
|
+
`- currentPassword: ${currentPassword ? "✅ provided" : "❌ missing"}\n` +
|
|
356
|
+
`- userPassword (new): ${userPassword ? "✅ provided" : "❌ missing"}\n\n` +
|
|
357
|
+
`Please provide both the current and new password.`,
|
|
358
|
+
}],
|
|
359
|
+
isError: true,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const res = await fetch(`${QR_API_URL}/change-password`, {
|
|
364
|
+
method: "POST",
|
|
365
|
+
headers: getAuthHeaders(),
|
|
366
|
+
body: JSON.stringify({ currentPassword: b64(currentPassword), userPassword: b64(userPassword) }),
|
|
367
|
+
});
|
|
368
|
+
const data = await res.json();
|
|
369
|
+
|
|
370
|
+
if (res.ok) {
|
|
371
|
+
return {
|
|
372
|
+
content: [{ type: "text", text: `✅ Password changed successfully!` }],
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
content: [{
|
|
377
|
+
type: "text",
|
|
378
|
+
text: `❌ Failed (HTTP ${res.status})\n\n${JSON.stringify(data, null, 2)}`,
|
|
379
|
+
}],
|
|
380
|
+
isError: true,
|
|
381
|
+
};
|
|
382
|
+
},
|
|
383
|
+
|
|
384
|
+
qr_send_feedback: async ({ subject, comment }) => {
|
|
385
|
+
const authError = requireLogin();
|
|
386
|
+
if (authError) return authError;
|
|
387
|
+
|
|
388
|
+
const res = await fetch(`${QR_API_URL}/feedback`, {
|
|
389
|
+
method: "POST",
|
|
390
|
+
headers: getAuthHeaders(),
|
|
391
|
+
body: JSON.stringify({ subject, comment }),
|
|
392
|
+
});
|
|
393
|
+
const data = await res.json().catch(() => ({}));
|
|
394
|
+
|
|
395
|
+
if (res.ok) {
|
|
396
|
+
return {
|
|
397
|
+
content: [{
|
|
398
|
+
type: "text",
|
|
399
|
+
text:
|
|
400
|
+
`✅ Feedback sent successfully!\n\n` +
|
|
401
|
+
`Subject: ${subject}\n` +
|
|
402
|
+
`Message: ${comment}`,
|
|
403
|
+
}],
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
content: [{
|
|
408
|
+
type: "text",
|
|
409
|
+
text: `❌ Failed (HTTP ${res.status})\n\n${JSON.stringify(data, null, 2)}`,
|
|
410
|
+
}],
|
|
411
|
+
isError: true,
|
|
412
|
+
};
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
qr_get_notifications: async ({ page = 1 }) => {
|
|
416
|
+
const authError = requireLogin();
|
|
417
|
+
if (authError) return authError;
|
|
418
|
+
|
|
419
|
+
const res = await fetch(`${QR_API_URL}/notification-list`, {
|
|
420
|
+
method: "POST",
|
|
421
|
+
headers: getAuthHeaders(),
|
|
422
|
+
body: JSON.stringify({ page }),
|
|
423
|
+
});
|
|
424
|
+
const data = await res.json();
|
|
425
|
+
|
|
426
|
+
if (res.ok) {
|
|
427
|
+
const rows = data.rows || data || [];
|
|
428
|
+
const count = Array.isArray(rows) ? rows.length : 0;
|
|
429
|
+
return {
|
|
430
|
+
content: [{
|
|
431
|
+
type: "text",
|
|
432
|
+
text:
|
|
433
|
+
`✅ Notifications fetched — Page ${page} (${count} results)\n\n` +
|
|
434
|
+
`${JSON.stringify(data, null, 2)}`,
|
|
435
|
+
}],
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
content: [{
|
|
440
|
+
type: "text",
|
|
441
|
+
text: `❌ Failed (HTTP ${res.status})\n\n${JSON.stringify(data, null, 2)}`,
|
|
442
|
+
}],
|
|
443
|
+
isError: true,
|
|
444
|
+
};
|
|
445
|
+
},
|
|
446
|
+
|
|
447
|
+
qr_get_storage: async () => {
|
|
448
|
+
const authError = requireLogin();
|
|
449
|
+
if (authError) return authError;
|
|
450
|
+
|
|
451
|
+
const res = await fetch(`${QR_API_URL}/storage`, {
|
|
452
|
+
method: "GET",
|
|
453
|
+
headers: getAuthHeaders(),
|
|
454
|
+
});
|
|
455
|
+
const data = await res.json();
|
|
456
|
+
|
|
457
|
+
if (res.ok) {
|
|
458
|
+
return {
|
|
459
|
+
content: [{
|
|
460
|
+
type: "text",
|
|
461
|
+
text:
|
|
462
|
+
`✅ Storage info fetched!\n\n` +
|
|
463
|
+
`Used: ${data.usedSpace}\n` +
|
|
464
|
+
`Total: ${data.totalSpace}\n` +
|
|
465
|
+
`Usage: ${data.usedPercentage}%`,
|
|
466
|
+
}],
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
return {
|
|
470
|
+
content: [{
|
|
471
|
+
type: "text",
|
|
472
|
+
text: `❌ Failed (HTTP ${res.status})\n\n${JSON.stringify(data, null, 2)}`,
|
|
473
|
+
}],
|
|
474
|
+
isError: true,
|
|
475
|
+
};
|
|
476
|
+
},
|
|
477
|
+
|
|
478
|
+
};
|