@suzko/mcp-server 0.1.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/CHANGELOG.md +32 -0
- package/GUIDE.md +952 -0
- package/LICENSE +21 -0
- package/README.md +171 -0
- package/dist/bin/mcp-server.d.ts +11 -0
- package/dist/bin/mcp-server.js +108 -0
- package/dist/src/auth.d.ts +14 -0
- package/dist/src/auth.js +140 -0
- package/dist/src/client.d.ts +18 -0
- package/dist/src/client.js +78 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.js +44 -0
- package/dist/src/prompts/index.d.ts +5 -0
- package/dist/src/prompts/index.js +87 -0
- package/dist/src/resources/index.d.ts +7 -0
- package/dist/src/resources/index.js +86 -0
- package/dist/src/security.d.ts +18 -0
- package/dist/src/security.js +108 -0
- package/dist/src/tools/account.d.ts +3 -0
- package/dist/src/tools/account.js +254 -0
- package/dist/src/tools/deploy.d.ts +3 -0
- package/dist/src/tools/deploy.js +468 -0
- package/dist/src/tools/dns.d.ts +3 -0
- package/dist/src/tools/dns.js +313 -0
- package/dist/src/tools/domains.d.ts +3 -0
- package/dist/src/tools/domains.js +499 -0
- package/dist/src/tools/server-admin.d.ts +3 -0
- package/dist/src/tools/server-admin.js +738 -0
- package/dist/src/tools/services.d.ts +3 -0
- package/dist/src/tools/services.js +181 -0
- package/dist/src/tools/support.d.ts +3 -0
- package/dist/src/tools/support.js +259 -0
- package/package.json +57 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerServicesTools(server, client) {
|
|
3
|
+
// 1. List services
|
|
4
|
+
server.tool("list_services", "List all WHMCS hosting/server services for the authenticated user, including status, product name, and domain.", {
|
|
5
|
+
page: z
|
|
6
|
+
.number()
|
|
7
|
+
.optional()
|
|
8
|
+
.describe("Page number (each page is 25 services, starting from 1)"),
|
|
9
|
+
}, async ({ page }) => {
|
|
10
|
+
const params = {};
|
|
11
|
+
if (page && page > 1)
|
|
12
|
+
params.limitstart = String((page - 1) * 25);
|
|
13
|
+
const res = await client.get("/api/services", params);
|
|
14
|
+
if (res.error) {
|
|
15
|
+
return {
|
|
16
|
+
content: [{ type: "text", text: `Error: ${res.error}` }],
|
|
17
|
+
isError: true,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const services = res.services ?? [];
|
|
21
|
+
if (services.length === 0) {
|
|
22
|
+
return {
|
|
23
|
+
content: [
|
|
24
|
+
{
|
|
25
|
+
type: "text",
|
|
26
|
+
text: "No services found. Visit https://www.suzko.com to browse available products.",
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const lines = services.map((s) => {
|
|
32
|
+
const domain = s.domain ? ` | ${s.domain}` : "";
|
|
33
|
+
const ip = s.dedicatedip ? ` | IP: ${s.dedicatedip}` : "";
|
|
34
|
+
const price = s.recurringamount ? ` | ${s.recurringamount}/${s.billingcycle}` : "";
|
|
35
|
+
const due = s.nextduedate ? ` | Next due: ${s.nextduedate}` : "";
|
|
36
|
+
return `• **${s.name}** (ID: ${s.id}) — ${s.status}${domain}${ip}${price}${due}`;
|
|
37
|
+
});
|
|
38
|
+
return {
|
|
39
|
+
content: [
|
|
40
|
+
{
|
|
41
|
+
type: "text",
|
|
42
|
+
text: `**Services (${services.length}${res.totalresults ? ` of ${res.totalresults}` : ""}):**\n\n${lines.join("\n")}`,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
// 2. Get service details
|
|
48
|
+
server.tool("get_service_details", "Get full details for a specific WHMCS service including configuration, billing, assigned IPs, and server info.", {
|
|
49
|
+
serviceId: z.number().describe("The service ID to look up"),
|
|
50
|
+
}, async ({ serviceId }) => {
|
|
51
|
+
const res = await client.get(`/api/services/${serviceId}`);
|
|
52
|
+
if (res.error || !res.service) {
|
|
53
|
+
return {
|
|
54
|
+
content: [
|
|
55
|
+
{
|
|
56
|
+
type: "text",
|
|
57
|
+
text: `Error: ${res.error || "Service not found"}`,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
isError: true,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const s = res.service;
|
|
64
|
+
const header = [
|
|
65
|
+
`**Service: ${s.name}** (ID: ${s.id})`,
|
|
66
|
+
`- **Status:** ${s.status}`,
|
|
67
|
+
s.domain ? `- **Domain:** ${s.domain}` : null,
|
|
68
|
+
s.groupname ? `- **Category:** ${s.groupname}` : null,
|
|
69
|
+
`- **Registered:** ${s.regdate}`,
|
|
70
|
+
`- **Billing:** ${s.recurringamount || s.firstpaymentamount || "N/A"} / ${s.billingcycle}`,
|
|
71
|
+
s.nextduedate ? `- **Next Due:** ${s.nextduedate}` : null,
|
|
72
|
+
].filter(Boolean);
|
|
73
|
+
const serverInfo = [
|
|
74
|
+
s.servername ? `- **Server:** ${s.servername}` : null,
|
|
75
|
+
s.serverip ? `- **Server IP:** ${s.serverip}` : null,
|
|
76
|
+
s.dedicatedip ? `- **Dedicated IP:** ${s.dedicatedip}` : null,
|
|
77
|
+
s.assignedips ? `- **Assigned IPs:** ${s.assignedips}` : null,
|
|
78
|
+
s.username ? `- **Username:** ${s.username}` : null,
|
|
79
|
+
].filter(Boolean);
|
|
80
|
+
const usage = [
|
|
81
|
+
s.diskusage && s.disklimit
|
|
82
|
+
? `- **Disk:** ${s.diskusage} / ${s.disklimit}`
|
|
83
|
+
: null,
|
|
84
|
+
s.bwusage && s.bwlimit
|
|
85
|
+
? `- **Bandwidth:** ${s.bwusage} / ${s.bwlimit}`
|
|
86
|
+
: null,
|
|
87
|
+
].filter(Boolean);
|
|
88
|
+
const configOptions = s.configoptions?.configoption ?? [];
|
|
89
|
+
const customFields = s.customfields?.customfield ?? [];
|
|
90
|
+
let sections = header.join("\n");
|
|
91
|
+
if (serverInfo.length > 0) {
|
|
92
|
+
sections += `\n\n**Server & Network:**\n${serverInfo.join("\n")}`;
|
|
93
|
+
}
|
|
94
|
+
if (usage.length > 0) {
|
|
95
|
+
sections += `\n\n**Usage:**\n${usage.join("\n")}`;
|
|
96
|
+
}
|
|
97
|
+
if (configOptions.length > 0) {
|
|
98
|
+
const coLines = configOptions.map((co) => ` • ${co.option}: ${co.value}`);
|
|
99
|
+
sections += `\n\n**Config Options (${configOptions.length}):**\n${coLines.join("\n")}`;
|
|
100
|
+
}
|
|
101
|
+
if (customFields.length > 0) {
|
|
102
|
+
const cfLines = customFields.map((cf) => ` • ${cf.name}: ${cf.value}`);
|
|
103
|
+
sections += `\n\n**Custom Fields (${customFields.length}):**\n${cfLines.join("\n")}`;
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
content: [{ type: "text", text: sections }],
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
// 3. Get SSO URL
|
|
110
|
+
server.tool("get_service_sso_url", "Get a single sign-on URL to access the control panel (Plesk, Pterodactyl, etc.) for a service. Opens directly into the panel without needing credentials.", {
|
|
111
|
+
serviceId: z.number().describe("The service ID to get an SSO link for"),
|
|
112
|
+
}, async ({ serviceId }) => {
|
|
113
|
+
const res = await client.post(`/api/services/${serviceId}/sso`);
|
|
114
|
+
if (res.error) {
|
|
115
|
+
return {
|
|
116
|
+
content: [
|
|
117
|
+
{
|
|
118
|
+
type: "text",
|
|
119
|
+
text: `Error: ${res.error}`,
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
isError: true,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const ssoUrl = res.redirect_url || res.url || res.ssoUrl;
|
|
126
|
+
if (!ssoUrl) {
|
|
127
|
+
return {
|
|
128
|
+
content: [
|
|
129
|
+
{
|
|
130
|
+
type: "text",
|
|
131
|
+
text: "SSO is not available for this service. The service may not have a control panel, or the panel doesn't support single sign-on.",
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
content: [
|
|
138
|
+
{
|
|
139
|
+
type: "text",
|
|
140
|
+
text: `**SSO Link for Service #${serviceId}:**\n\n${ssoUrl}\n\n⚠️ This link is temporary and single-use. Open it in your browser to access the control panel.`,
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
// 4. Upgrade service (redirect to dashboard)
|
|
146
|
+
server.tool("upgrade_service", "Get information about upgrading or downgrading a service. Upgrades are managed through the dashboard.", {
|
|
147
|
+
serviceId: z.number().describe("The service ID to upgrade"),
|
|
148
|
+
}, async ({ serviceId }) => {
|
|
149
|
+
// First verify the service exists
|
|
150
|
+
const res = await client.get(`/api/services/${serviceId}`);
|
|
151
|
+
if (res.error || !res.service) {
|
|
152
|
+
return {
|
|
153
|
+
content: [
|
|
154
|
+
{
|
|
155
|
+
type: "text",
|
|
156
|
+
text: `Error: ${res.error || "Service not found"}`,
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
isError: true,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const s = res.service;
|
|
163
|
+
return {
|
|
164
|
+
content: [
|
|
165
|
+
{
|
|
166
|
+
type: "text",
|
|
167
|
+
text: [
|
|
168
|
+
`**Upgrade/Downgrade: ${s.name}** (ID: ${s.id})`,
|
|
169
|
+
`- **Current Status:** ${s.status}`,
|
|
170
|
+
`- **Current Billing:** ${s.recurringamount || "N/A"} / ${s.billingcycle}`,
|
|
171
|
+
``,
|
|
172
|
+
`To manage upgrades and downgrades, visit the service management page:`,
|
|
173
|
+
`**https://www.suzko.com/dashboard/services/${serviceId}**`,
|
|
174
|
+
``,
|
|
175
|
+
`From there you can change your plan, modify configurable options, and review pricing differences.`,
|
|
176
|
+
].join("\n"),
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerSupportTools(server, client) {
|
|
3
|
+
// 1. List tickets
|
|
4
|
+
server.tool("list_tickets", "List support tickets for the authenticated user. Optionally filter by status (Open, Answered, Customer-Reply, Closed, On Hold).", {
|
|
5
|
+
status: z
|
|
6
|
+
.string()
|
|
7
|
+
.optional()
|
|
8
|
+
.describe("Filter by ticket status: Open, Answered, Customer-Reply, Closed, On Hold"),
|
|
9
|
+
page: z
|
|
10
|
+
.number()
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("Page number (each page is 25 tickets, starting from 1)"),
|
|
13
|
+
}, async ({ status, page }) => {
|
|
14
|
+
const params = {};
|
|
15
|
+
if (status)
|
|
16
|
+
params.status = status;
|
|
17
|
+
if (page && page > 1)
|
|
18
|
+
params.limitstart = String((page - 1) * 25);
|
|
19
|
+
const res = await client.get("/api/tickets", params);
|
|
20
|
+
if (res.error) {
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: "text", text: `Error: ${res.error}` }],
|
|
23
|
+
isError: true,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const tickets = res.tickets ?? [];
|
|
27
|
+
if (tickets.length === 0) {
|
|
28
|
+
return {
|
|
29
|
+
content: [
|
|
30
|
+
{
|
|
31
|
+
type: "text",
|
|
32
|
+
text: status
|
|
33
|
+
? `No tickets found with status "${status}".`
|
|
34
|
+
: "No support tickets found.",
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const lines = tickets.map((t) => {
|
|
40
|
+
const dept = t.deptname ? ` [${t.deptname}]` : "";
|
|
41
|
+
return `• **#${t.tid}** — ${t.subject}\n Status: ${t.status} | Priority: ${t.priority}${dept} | Last reply: ${t.lastreply}`;
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
content: [
|
|
45
|
+
{
|
|
46
|
+
type: "text",
|
|
47
|
+
text: `**Support Tickets (${tickets.length}${res.totalresults ? ` of ${res.totalresults}` : ""}):**\n\n${lines.join("\n\n")}`,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
// 2. Get ticket details
|
|
53
|
+
server.tool("get_ticket", "Get full details of a support ticket including all replies and messages.", {
|
|
54
|
+
ticketId: z.number().describe("The ticket ID (numeric ID, not the ticket mask/tid)"),
|
|
55
|
+
}, async ({ ticketId }) => {
|
|
56
|
+
const res = await client.get(`/api/tickets/${ticketId}`);
|
|
57
|
+
if (res.error || !res.ticket) {
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{
|
|
61
|
+
type: "text",
|
|
62
|
+
text: `Error: ${res.error || "Ticket not found"}`,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
isError: true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const t = res.ticket;
|
|
69
|
+
const header = [
|
|
70
|
+
`**Ticket #${t.tid}** — ${t.subject}`,
|
|
71
|
+
`- **Status:** ${t.status}`,
|
|
72
|
+
`- **Priority:** ${t.priority}`,
|
|
73
|
+
t.deptname ? `- **Department:** ${t.deptname}` : null,
|
|
74
|
+
`- **Opened:** ${t.date}`,
|
|
75
|
+
t.name ? `- **From:** ${t.name}` : null,
|
|
76
|
+
].filter(Boolean);
|
|
77
|
+
// Original message
|
|
78
|
+
const originalMsg = `\n\n**Original Message:**\n${stripHtml(t.message)}`;
|
|
79
|
+
// Replies
|
|
80
|
+
const replies = t.replies ?? [];
|
|
81
|
+
let repliesText = "";
|
|
82
|
+
if (replies.length > 0) {
|
|
83
|
+
const replyLines = replies.map((r, i) => {
|
|
84
|
+
const author = r.admin
|
|
85
|
+
? `Staff (${r.admin})`
|
|
86
|
+
: r.name || r.email || "You";
|
|
87
|
+
return `\n**Reply #${i + 1}** by ${author} — ${r.date}\n${stripHtml(r.message)}`;
|
|
88
|
+
});
|
|
89
|
+
repliesText = `\n\n**Replies (${replies.length}):**${replyLines.join("\n")}`;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
content: [
|
|
93
|
+
{
|
|
94
|
+
type: "text",
|
|
95
|
+
text: header.join("\n") + originalMsg + repliesText,
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
// 3. Open support ticket
|
|
101
|
+
server.tool("open_support_ticket", "Open a new support ticket. Requires a department ID, subject, and message. Use list_departments resource to find department IDs.", {
|
|
102
|
+
deptid: z
|
|
103
|
+
.number()
|
|
104
|
+
.describe("Department ID (use the support departments list to find valid IDs)"),
|
|
105
|
+
subject: z.string().describe("Ticket subject line"),
|
|
106
|
+
message: z.string().describe("Ticket message body"),
|
|
107
|
+
priority: z
|
|
108
|
+
.enum(["Low", "Medium", "High"])
|
|
109
|
+
.optional()
|
|
110
|
+
.describe("Ticket priority (default: Medium)"),
|
|
111
|
+
serviceId: z
|
|
112
|
+
.number()
|
|
113
|
+
.optional()
|
|
114
|
+
.describe("Related service ID (optional, links ticket to a service)"),
|
|
115
|
+
}, async ({ deptid, subject, message, priority, serviceId }) => {
|
|
116
|
+
const body = {
|
|
117
|
+
deptid,
|
|
118
|
+
subject,
|
|
119
|
+
message,
|
|
120
|
+
priority: priority || "Medium",
|
|
121
|
+
};
|
|
122
|
+
if (serviceId)
|
|
123
|
+
body.serviceid = serviceId;
|
|
124
|
+
const res = await client.post("/api/tickets", body);
|
|
125
|
+
if (res.error || !res.success) {
|
|
126
|
+
return {
|
|
127
|
+
content: [
|
|
128
|
+
{
|
|
129
|
+
type: "text",
|
|
130
|
+
text: `Error: ${res.error || "Failed to create ticket"}`,
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
isError: true,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
content: [
|
|
138
|
+
{
|
|
139
|
+
type: "text",
|
|
140
|
+
text: [
|
|
141
|
+
`**Ticket Created Successfully!**`,
|
|
142
|
+
`- **Ticket #${res.tid}** (ID: ${res.id})`,
|
|
143
|
+
`- **Subject:** ${subject}`,
|
|
144
|
+
`- **Priority:** ${priority || "Medium"}`,
|
|
145
|
+
``,
|
|
146
|
+
`View your ticket at: https://www.suzko.com/dashboard/tickets/${res.id}`,
|
|
147
|
+
].join("\n"),
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
// 4. Reply to ticket
|
|
153
|
+
server.tool("reply_to_ticket", "Add a reply to an existing support ticket.", {
|
|
154
|
+
ticketId: z.number().describe("The ticket ID to reply to"),
|
|
155
|
+
message: z.string().describe("Reply message body"),
|
|
156
|
+
}, async ({ ticketId, message }) => {
|
|
157
|
+
const res = await client.post(`/api/tickets/${ticketId}/reply`, { message });
|
|
158
|
+
if (res.error || !res.success) {
|
|
159
|
+
return {
|
|
160
|
+
content: [
|
|
161
|
+
{
|
|
162
|
+
type: "text",
|
|
163
|
+
text: `Error: ${res.error || "Failed to add reply"}`,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
isError: true,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
content: [
|
|
171
|
+
{
|
|
172
|
+
type: "text",
|
|
173
|
+
text: `**Reply added** to ticket #${ticketId}.\n\nView the ticket at: https://www.suzko.com/dashboard/tickets/${ticketId}`,
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
// 5. Close ticket
|
|
179
|
+
server.tool("close_ticket", "Close a support ticket by replying with a close request. Adds a closing message and updates the status.", {
|
|
180
|
+
ticketId: z.number().describe("The ticket ID to close"),
|
|
181
|
+
message: z
|
|
182
|
+
.string()
|
|
183
|
+
.optional()
|
|
184
|
+
.describe("Optional closing message (default: 'Closing this ticket. Thank you for your help.')"),
|
|
185
|
+
}, async ({ ticketId, message }) => {
|
|
186
|
+
// First check the ticket exists and get its current status
|
|
187
|
+
const ticketRes = await client.get(`/api/tickets/${ticketId}`);
|
|
188
|
+
if (ticketRes.error || !ticketRes.ticket) {
|
|
189
|
+
return {
|
|
190
|
+
content: [
|
|
191
|
+
{
|
|
192
|
+
type: "text",
|
|
193
|
+
text: `Error: ${ticketRes.error || "Ticket not found"}`,
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
isError: true,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const ticket = ticketRes.ticket;
|
|
200
|
+
if (ticket.status === "Closed") {
|
|
201
|
+
return {
|
|
202
|
+
content: [
|
|
203
|
+
{
|
|
204
|
+
type: "text",
|
|
205
|
+
text: `Ticket #${ticket.tid} is already closed.`,
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
// Add a closing reply
|
|
211
|
+
const closingMessage = message || "Closing this ticket. Thank you for your help.";
|
|
212
|
+
const replyRes = await client.post(`/api/tickets/${ticketId}/reply`, {
|
|
213
|
+
message: closingMessage,
|
|
214
|
+
});
|
|
215
|
+
if (replyRes.error || !replyRes.success) {
|
|
216
|
+
return {
|
|
217
|
+
content: [
|
|
218
|
+
{
|
|
219
|
+
type: "text",
|
|
220
|
+
text: `Error adding closing reply: ${replyRes.error || "Failed"}. You can close the ticket manually at https://www.suzko.com/dashboard/tickets/${ticketId}`,
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
isError: true,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
// Note: WHMCS client API doesn't allow clients to directly change ticket status.
|
|
227
|
+
// The reply has been added; the ticket will be marked as "Customer-Reply".
|
|
228
|
+
// Staff or auto-close rules will handle the actual close.
|
|
229
|
+
return {
|
|
230
|
+
content: [
|
|
231
|
+
{
|
|
232
|
+
type: "text",
|
|
233
|
+
text: [
|
|
234
|
+
`**Closing reply added** to ticket #${ticket.tid} (${ticket.subject}).`,
|
|
235
|
+
``,
|
|
236
|
+
`The ticket status is now "Customer-Reply". If you need it formally closed, a staff member will update the status, or it may auto-close after the inactivity period.`,
|
|
237
|
+
``,
|
|
238
|
+
`View at: https://www.suzko.com/dashboard/tickets/${ticketId}`,
|
|
239
|
+
].join("\n"),
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
};
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/** Strip basic HTML tags from WHMCS ticket messages for cleaner display */
|
|
246
|
+
function stripHtml(html) {
|
|
247
|
+
return html
|
|
248
|
+
.replace(/<br\s*\/?>/gi, "\n")
|
|
249
|
+
.replace(/<\/p>/gi, "\n")
|
|
250
|
+
.replace(/<[^>]*>/g, "")
|
|
251
|
+
.replace(/&/g, "&")
|
|
252
|
+
.replace(/</g, "<")
|
|
253
|
+
.replace(/>/g, ">")
|
|
254
|
+
.replace(/"/g, '"')
|
|
255
|
+
.replace(/'/g, "'")
|
|
256
|
+
.replace(/ /g, " ")
|
|
257
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
258
|
+
.trim();
|
|
259
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@suzko/mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Suzko MCP Server — AI-powered infrastructure management for VS Code, Claude, and Cursor",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"suzko-mcp": "dist/bin/mcp-server.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"start": "node dist/src/index.js",
|
|
14
|
+
"auth": "node dist/bin/mcp-server.js auth login",
|
|
15
|
+
"prepublishOnly": "tsc"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
19
|
+
"zod": "^3.24.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"typescript": "^5.7.0",
|
|
23
|
+
"@types/node": "^22.0.0"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18.0.0"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md",
|
|
31
|
+
"GUIDE.md",
|
|
32
|
+
"LICENSE",
|
|
33
|
+
"CHANGELOG.md"
|
|
34
|
+
],
|
|
35
|
+
"keywords": [
|
|
36
|
+
"mcp",
|
|
37
|
+
"ai",
|
|
38
|
+
"deploy",
|
|
39
|
+
"infrastructure",
|
|
40
|
+
"domains",
|
|
41
|
+
"hosting",
|
|
42
|
+
"suzko",
|
|
43
|
+
"model-context-protocol"
|
|
44
|
+
],
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://github.com/suzko-ops/mcp-server#readme",
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/suzko-ops/mcp-server.git"
|
|
53
|
+
},
|
|
54
|
+
"bugs": {
|
|
55
|
+
"url": "https://github.com/suzko-ops/mcp-server/issues"
|
|
56
|
+
}
|
|
57
|
+
}
|