mcphosting-cli 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/CONTRIBUTING.md +156 -0
- package/LICENSE +21 -0
- package/README.md +260 -0
- package/dist/index.cjs +1182 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1159 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
- package/src/commands/auth.ts +186 -0
- package/src/commands/connect.ts +172 -0
- package/src/commands/import.ts +213 -0
- package/src/commands/proxy.ts +144 -0
- package/src/commands/search.ts +135 -0
- package/src/commands/servers.ts +131 -0
- package/src/index.ts +130 -0
- package/src/lib/api.ts +137 -0
- package/src/lib/clients.ts +188 -0
- package/src/lib/config.ts +90 -0
- package/src/lib/logger.ts +63 -0
- package/src/types.ts +47 -0
- package/tsconfig.json +26 -0
- package/tsup.config.ts +14 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command as Command7 } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/auth.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import { createServer } from "http";
|
|
9
|
+
import open from "open";
|
|
10
|
+
|
|
11
|
+
// src/lib/config.ts
|
|
12
|
+
import Conf from "conf";
|
|
13
|
+
var Config = class {
|
|
14
|
+
conf;
|
|
15
|
+
connectionsConf;
|
|
16
|
+
constructor() {
|
|
17
|
+
this.conf = new Conf({
|
|
18
|
+
projectName: "mcphosting",
|
|
19
|
+
defaults: {}
|
|
20
|
+
});
|
|
21
|
+
this.connectionsConf = new Conf({
|
|
22
|
+
projectName: "mcphosting",
|
|
23
|
+
configName: "connections",
|
|
24
|
+
defaults: { connections: [] }
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
get token() {
|
|
28
|
+
return this.conf.get("token");
|
|
29
|
+
}
|
|
30
|
+
set token(value) {
|
|
31
|
+
if (value) {
|
|
32
|
+
this.conf.set("token", value);
|
|
33
|
+
} else {
|
|
34
|
+
this.conf.delete("token");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
get user() {
|
|
38
|
+
return this.conf.get("user");
|
|
39
|
+
}
|
|
40
|
+
set user(value) {
|
|
41
|
+
if (value) {
|
|
42
|
+
this.conf.set("user", value);
|
|
43
|
+
} else {
|
|
44
|
+
this.conf.delete("user");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
get connections() {
|
|
48
|
+
return this.connectionsConf.get("connections", []);
|
|
49
|
+
}
|
|
50
|
+
addConnection(connection) {
|
|
51
|
+
const connections = this.connections;
|
|
52
|
+
const newConnection = {
|
|
53
|
+
...connection,
|
|
54
|
+
id: Math.random().toString(36).substr(2, 9),
|
|
55
|
+
addedAt: Date.now()
|
|
56
|
+
};
|
|
57
|
+
connections.push(newConnection);
|
|
58
|
+
this.connectionsConf.set("connections", connections);
|
|
59
|
+
return newConnection;
|
|
60
|
+
}
|
|
61
|
+
removeConnection(id) {
|
|
62
|
+
const connections = this.connections;
|
|
63
|
+
const index = connections.findIndex((c) => c.id === id || c.slug === id);
|
|
64
|
+
if (index === -1) return false;
|
|
65
|
+
connections.splice(index, 1);
|
|
66
|
+
this.connectionsConf.set("connections", connections);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
findConnection(slugOrId) {
|
|
70
|
+
return this.connections.find((c) => c.id === slugOrId || c.slug === slugOrId);
|
|
71
|
+
}
|
|
72
|
+
updateConnection(id, updates) {
|
|
73
|
+
const connections = this.connections;
|
|
74
|
+
const index = connections.findIndex((c) => c.id === id);
|
|
75
|
+
if (index === -1) return false;
|
|
76
|
+
connections[index] = { ...connections[index], ...updates };
|
|
77
|
+
this.connectionsConf.set("connections", connections);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
clear() {
|
|
81
|
+
this.conf.clear();
|
|
82
|
+
this.connectionsConf.clear();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// src/lib/api.ts
|
|
87
|
+
var MCPHostingAPI = class {
|
|
88
|
+
baseUrl = "https://mcphosting.com/api";
|
|
89
|
+
token;
|
|
90
|
+
constructor(token) {
|
|
91
|
+
this.token = token;
|
|
92
|
+
}
|
|
93
|
+
async request(endpoint, options = {}) {
|
|
94
|
+
throw new Error(`API not available - using static fallback`);
|
|
95
|
+
}
|
|
96
|
+
async searchMCPs(query) {
|
|
97
|
+
try {
|
|
98
|
+
const results = await this.request(`/marketplace/search?q=${encodeURIComponent(query)}`);
|
|
99
|
+
return results.mcps || [];
|
|
100
|
+
} catch (error) {
|
|
101
|
+
const staticMCPs = this.getStaticMCPs();
|
|
102
|
+
return staticMCPs.filter(
|
|
103
|
+
(mcp) => mcp.name.toLowerCase().includes(query.toLowerCase()) || mcp.description.toLowerCase().includes(query.toLowerCase()) || mcp.tools.some((tool) => tool.toLowerCase().includes(query.toLowerCase()))
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async getMCPInfo(slug) {
|
|
108
|
+
try {
|
|
109
|
+
const result = await this.request(`/marketplace/mcp/${slug}`);
|
|
110
|
+
return result.mcp || null;
|
|
111
|
+
} catch {
|
|
112
|
+
const staticMCPs = this.getStaticMCPs();
|
|
113
|
+
return staticMCPs.find((mcp) => mcp.slug === slug) || null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async whoami() {
|
|
117
|
+
if (!this.token) return null;
|
|
118
|
+
try {
|
|
119
|
+
const result = await this.request("/auth/whoami");
|
|
120
|
+
return result.user;
|
|
121
|
+
} catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
getStaticMCPs() {
|
|
126
|
+
return [
|
|
127
|
+
{
|
|
128
|
+
slug: "github",
|
|
129
|
+
name: "GitHub MCP",
|
|
130
|
+
description: "Access GitHub repositories, issues, and pull requests",
|
|
131
|
+
url: "https://github.mcphost.dev",
|
|
132
|
+
tools: ["read_file", "list_repos", "create_issue", "list_issues"],
|
|
133
|
+
installs: 15420,
|
|
134
|
+
author: "MCPHosting"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
slug: "slack",
|
|
138
|
+
name: "Slack MCP",
|
|
139
|
+
description: "Send messages and manage Slack workspaces",
|
|
140
|
+
url: "https://slack.mcphost.dev",
|
|
141
|
+
tools: ["send_message", "list_channels", "get_history"],
|
|
142
|
+
installs: 8930,
|
|
143
|
+
author: "MCPHosting"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
slug: "notion",
|
|
147
|
+
name: "Notion MCP",
|
|
148
|
+
description: "Read and write Notion pages and databases",
|
|
149
|
+
url: "https://notion.mcphost.dev",
|
|
150
|
+
tools: ["read_page", "create_page", "query_database"],
|
|
151
|
+
installs: 12450,
|
|
152
|
+
author: "MCPHosting"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
slug: "stripe",
|
|
156
|
+
name: "Stripe MCP",
|
|
157
|
+
description: "Access Stripe payment and customer data",
|
|
158
|
+
url: "https://stripe.mcphost.dev",
|
|
159
|
+
tools: ["get_customer", "list_payments", "create_invoice"],
|
|
160
|
+
installs: 6720,
|
|
161
|
+
author: "MCPHosting"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
slug: "postgres",
|
|
165
|
+
name: "PostgreSQL MCP",
|
|
166
|
+
description: "Query PostgreSQL databases safely",
|
|
167
|
+
url: "https://postgres.mcphost.dev",
|
|
168
|
+
tools: ["query", "describe_table", "list_tables"],
|
|
169
|
+
installs: 9180,
|
|
170
|
+
author: "MCPHosting"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
slug: "filesystem",
|
|
174
|
+
name: "Filesystem MCP",
|
|
175
|
+
description: "Read and write files securely",
|
|
176
|
+
url: "https://filesystem.mcphost.dev",
|
|
177
|
+
tools: ["read_file", "write_file", "list_directory"],
|
|
178
|
+
installs: 18950,
|
|
179
|
+
author: "MCPHosting"
|
|
180
|
+
}
|
|
181
|
+
];
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// src/lib/logger.ts
|
|
186
|
+
import chalk from "chalk";
|
|
187
|
+
import ora from "ora";
|
|
188
|
+
var Logger = class _Logger {
|
|
189
|
+
static info(message) {
|
|
190
|
+
console.log(chalk.blue("\u2139"), message);
|
|
191
|
+
}
|
|
192
|
+
static success(message) {
|
|
193
|
+
console.log(chalk.green("\u2713"), message);
|
|
194
|
+
}
|
|
195
|
+
static warning(message) {
|
|
196
|
+
console.log(chalk.yellow("\u26A0"), message);
|
|
197
|
+
}
|
|
198
|
+
static error(message) {
|
|
199
|
+
console.error(chalk.red("\u2717"), message);
|
|
200
|
+
}
|
|
201
|
+
static dim(message) {
|
|
202
|
+
console.log(chalk.dim(message));
|
|
203
|
+
}
|
|
204
|
+
static bold(message) {
|
|
205
|
+
console.log(chalk.bold(message));
|
|
206
|
+
}
|
|
207
|
+
static spinner(text) {
|
|
208
|
+
return ora(text).start();
|
|
209
|
+
}
|
|
210
|
+
static table(data, headers) {
|
|
211
|
+
if (data.length === 0) {
|
|
212
|
+
_Logger.dim("No data to display");
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const keys = headers || Object.keys(data[0]);
|
|
216
|
+
const maxWidths = keys.map(
|
|
217
|
+
(key) => Math.max(key.length, ...data.map((row) => String(row[key] || "").length))
|
|
218
|
+
);
|
|
219
|
+
const headerRow = keys.map(
|
|
220
|
+
(key, i) => chalk.bold(key.padEnd(maxWidths[i]))
|
|
221
|
+
).join(" | ");
|
|
222
|
+
console.log(headerRow);
|
|
223
|
+
console.log(keys.map((_, i) => "-".repeat(maxWidths[i])).join("-|-"));
|
|
224
|
+
data.forEach((row) => {
|
|
225
|
+
const dataRow = keys.map(
|
|
226
|
+
(key, i) => String(row[key] || "").padEnd(maxWidths[i])
|
|
227
|
+
).join(" | ");
|
|
228
|
+
console.log(dataRow);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
static json(data) {
|
|
232
|
+
console.log(JSON.stringify(data, null, 2));
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// src/commands/auth.ts
|
|
237
|
+
function createAuthCommands() {
|
|
238
|
+
const auth = new Command("auth");
|
|
239
|
+
auth.command("login").description("Authenticate with MCPHosting").option("--token <token>", "Provide API token directly").action(async (options) => {
|
|
240
|
+
const config = new Config();
|
|
241
|
+
if (options.token) {
|
|
242
|
+
config.token = options.token;
|
|
243
|
+
const api = new MCPHostingAPI(options.token);
|
|
244
|
+
const user = await api.whoami();
|
|
245
|
+
if (user) {
|
|
246
|
+
config.user = user;
|
|
247
|
+
Logger.success(`Logged in as ${user.email}${user.org ? ` (${user.org})` : ""}`);
|
|
248
|
+
} else {
|
|
249
|
+
Logger.warning("Token saved, but could not verify user info");
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const spinner = Logger.spinner("Starting login flow...");
|
|
254
|
+
let server;
|
|
255
|
+
let resolved = false;
|
|
256
|
+
try {
|
|
257
|
+
const authPromise = new Promise((resolve, reject) => {
|
|
258
|
+
server = createServer((req, res) => {
|
|
259
|
+
if (req.url?.startsWith("/callback")) {
|
|
260
|
+
const url = new URL(req.url, "http://localhost");
|
|
261
|
+
const token2 = url.searchParams.get("token");
|
|
262
|
+
if (token2) {
|
|
263
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
264
|
+
res.end(`
|
|
265
|
+
<html>
|
|
266
|
+
<body style="font-family: system-ui; text-align: center; padding: 50px;">
|
|
267
|
+
<h2>\u2705 Login Successful!</h2>
|
|
268
|
+
<p>You can now close this window and return to your terminal.</p>
|
|
269
|
+
</body>
|
|
270
|
+
</html>
|
|
271
|
+
`);
|
|
272
|
+
resolve(token2);
|
|
273
|
+
} else {
|
|
274
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
275
|
+
res.end(`
|
|
276
|
+
<html>
|
|
277
|
+
<body style="font-family: system-ui; text-align: center; padding: 50px;">
|
|
278
|
+
<h2>\u274C Login Failed</h2>
|
|
279
|
+
<p>No token received. Please try again.</p>
|
|
280
|
+
</body>
|
|
281
|
+
</html>
|
|
282
|
+
`);
|
|
283
|
+
reject(new Error("No token received"));
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
res.writeHead(404);
|
|
287
|
+
res.end("Not found");
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
server.listen(0, () => {
|
|
291
|
+
const port = server.address().port;
|
|
292
|
+
const callbackUrl = `http://localhost:${port}/callback`;
|
|
293
|
+
const authUrl = `https://mcphosting.com/cli/auth?callback=${encodeURIComponent(callbackUrl)}`;
|
|
294
|
+
setTimeout(() => {
|
|
295
|
+
if (!resolved) {
|
|
296
|
+
reject(new Error("Login timeout - please try again"));
|
|
297
|
+
}
|
|
298
|
+
}, 12e4);
|
|
299
|
+
open(authUrl).catch(() => {
|
|
300
|
+
Logger.warning(`Could not open browser automatically. Please visit: ${authUrl}`);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
const token = await authPromise;
|
|
305
|
+
resolved = true;
|
|
306
|
+
spinner.succeed("Login successful!");
|
|
307
|
+
config.token = token;
|
|
308
|
+
const api = new MCPHostingAPI(token);
|
|
309
|
+
const user = await api.whoami();
|
|
310
|
+
if (user) {
|
|
311
|
+
config.user = user;
|
|
312
|
+
Logger.success(`Logged in as ${user.email}${user.org ? ` (${user.org})` : ""}`);
|
|
313
|
+
}
|
|
314
|
+
} catch (error) {
|
|
315
|
+
spinner.fail("Login failed");
|
|
316
|
+
Logger.error(`Login error: ${error}`);
|
|
317
|
+
process.exit(1);
|
|
318
|
+
} finally {
|
|
319
|
+
if (server) {
|
|
320
|
+
server.close();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
auth.command("logout").description("Remove stored authentication").action(() => {
|
|
325
|
+
const config = new Config();
|
|
326
|
+
config.token = void 0;
|
|
327
|
+
config.user = void 0;
|
|
328
|
+
Logger.success("Logged out successfully");
|
|
329
|
+
});
|
|
330
|
+
auth.command("whoami").description("Show current user information").action(async () => {
|
|
331
|
+
const config = new Config();
|
|
332
|
+
const token = config.token;
|
|
333
|
+
if (!token) {
|
|
334
|
+
Logger.warning("Not logged in. Use `mcphost auth login` to authenticate.");
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const user = config.user;
|
|
338
|
+
if (user) {
|
|
339
|
+
Logger.info(`Logged in as: ${user.email}${user.org ? ` (${user.org})` : ""}`);
|
|
340
|
+
} else {
|
|
341
|
+
Logger.warning("User info not available. Try logging in again.");
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
return auth;
|
|
345
|
+
}
|
|
346
|
+
function createLegacyAuthCommands() {
|
|
347
|
+
const config = new Config();
|
|
348
|
+
const login2 = new Command("login").description("Authenticate with MCPHosting").option("--token <token>", "Provide API token directly").action(async (options) => {
|
|
349
|
+
const authCmd = createAuthCommands();
|
|
350
|
+
const loginCmd = authCmd.commands.find((cmd) => cmd.name() === "login");
|
|
351
|
+
if (loginCmd) {
|
|
352
|
+
await loginCmd.action(options);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
const logout2 = new Command("logout").description("Remove stored authentication").action(() => {
|
|
356
|
+
config.token = void 0;
|
|
357
|
+
config.user = void 0;
|
|
358
|
+
Logger.success("Logged out successfully");
|
|
359
|
+
});
|
|
360
|
+
const whoami2 = new Command("whoami").description("Show current user information").action(async () => {
|
|
361
|
+
const authCmd = createAuthCommands();
|
|
362
|
+
const whoamiCmd = authCmd.commands.find((cmd) => cmd.name() === "whoami");
|
|
363
|
+
if (whoamiCmd) {
|
|
364
|
+
await whoamiCmd.action();
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
return [login2, logout2, whoami2];
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// src/commands/connect.ts
|
|
371
|
+
import { Command as Command2 } from "commander";
|
|
372
|
+
|
|
373
|
+
// src/lib/clients.ts
|
|
374
|
+
import { readFile, writeFile, access } from "fs/promises";
|
|
375
|
+
import { join, dirname } from "path";
|
|
376
|
+
import { homedir } from "os";
|
|
377
|
+
import { mkdir } from "fs/promises";
|
|
378
|
+
var ClientManager = class {
|
|
379
|
+
static getClientPaths() {
|
|
380
|
+
const home = homedir();
|
|
381
|
+
const platform = process.platform;
|
|
382
|
+
const paths = {
|
|
383
|
+
claude: platform === "win32" ? join(process.env.APPDATA || "", "Claude", "claude_desktop_config.json") : join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
|
|
384
|
+
cursor: join(home, ".cursor", "mcp.json"),
|
|
385
|
+
vscode: join(process.cwd(), ".vscode", "mcp.json"),
|
|
386
|
+
openclaw: join(home, ".openclaw", "mcp.json"),
|
|
387
|
+
chatgpt: ""
|
|
388
|
+
// Web-based, no local config
|
|
389
|
+
};
|
|
390
|
+
return paths;
|
|
391
|
+
}
|
|
392
|
+
static async detectInstalledClients() {
|
|
393
|
+
const paths = this.getClientPaths();
|
|
394
|
+
const clients = [];
|
|
395
|
+
for (const [name, path] of Object.entries(paths)) {
|
|
396
|
+
if (name === "chatgpt") continue;
|
|
397
|
+
try {
|
|
398
|
+
await access(path);
|
|
399
|
+
clients.push({ name, configPath: path, exists: true });
|
|
400
|
+
} catch {
|
|
401
|
+
clients.push({ name, configPath: path, exists: false });
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return clients;
|
|
405
|
+
}
|
|
406
|
+
static async addToClient(client, slug, url) {
|
|
407
|
+
if (client === "chatgpt") {
|
|
408
|
+
Logger.info("ChatGPT Setup Instructions:");
|
|
409
|
+
console.log(`
|
|
410
|
+
1. Open ChatGPT in your browser
|
|
411
|
+
2. Go to Settings \u2192 Apps \u2192 Connect an app
|
|
412
|
+
3. Enter this URL: ${url}
|
|
413
|
+
4. Follow the prompts to authorize the MCP server
|
|
414
|
+
`);
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
const paths = this.getClientPaths();
|
|
418
|
+
const configPath = paths[client];
|
|
419
|
+
if (!configPath) {
|
|
420
|
+
throw new Error(`Unsupported client: ${client}`);
|
|
421
|
+
}
|
|
422
|
+
const serverEntry = {
|
|
423
|
+
command: "npx",
|
|
424
|
+
args: ["-y", "@mcphosting/cli", "proxy", url],
|
|
425
|
+
env: {}
|
|
426
|
+
};
|
|
427
|
+
try {
|
|
428
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
429
|
+
let config = { mcpServers: {} };
|
|
430
|
+
try {
|
|
431
|
+
const configContent = await readFile(configPath, "utf-8");
|
|
432
|
+
config = JSON.parse(configContent);
|
|
433
|
+
if (!config.mcpServers) {
|
|
434
|
+
config.mcpServers = {};
|
|
435
|
+
}
|
|
436
|
+
} catch {
|
|
437
|
+
}
|
|
438
|
+
config.mcpServers[slug] = serverEntry;
|
|
439
|
+
await writeFile(configPath, JSON.stringify(config, null, 2));
|
|
440
|
+
return true;
|
|
441
|
+
} catch (error) {
|
|
442
|
+
Logger.error(`Failed to configure ${client}: ${error}`);
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
static async removeFromClient(client, slug) {
|
|
447
|
+
if (client === "chatgpt") {
|
|
448
|
+
Logger.info("To remove from ChatGPT:");
|
|
449
|
+
console.log(`
|
|
450
|
+
1. Open ChatGPT in your browser
|
|
451
|
+
2. Go to Settings \u2192 Apps
|
|
452
|
+
3. Find and disconnect the MCP server: ${slug}
|
|
453
|
+
`);
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
const paths = this.getClientPaths();
|
|
457
|
+
const configPath = paths[client];
|
|
458
|
+
if (!configPath) {
|
|
459
|
+
throw new Error(`Unsupported client: ${client}`);
|
|
460
|
+
}
|
|
461
|
+
try {
|
|
462
|
+
const configContent = await readFile(configPath, "utf-8");
|
|
463
|
+
const config = JSON.parse(configContent);
|
|
464
|
+
if (config.mcpServers && config.mcpServers[slug]) {
|
|
465
|
+
delete config.mcpServers[slug];
|
|
466
|
+
await writeFile(configPath, JSON.stringify(config, null, 2));
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
return false;
|
|
470
|
+
} catch (error) {
|
|
471
|
+
Logger.error(`Failed to remove from ${client}: ${error}`);
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
static async removeFromAllClients(slug) {
|
|
476
|
+
const clients = await this.detectInstalledClients();
|
|
477
|
+
const removed = [];
|
|
478
|
+
for (const client of clients) {
|
|
479
|
+
if (!client.exists) continue;
|
|
480
|
+
try {
|
|
481
|
+
const success = await this.removeFromClient(client.name, slug);
|
|
482
|
+
if (success) {
|
|
483
|
+
removed.push(client.name);
|
|
484
|
+
}
|
|
485
|
+
} catch (error) {
|
|
486
|
+
Logger.warning(`Failed to remove from ${client.name}: ${error}`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return removed;
|
|
490
|
+
}
|
|
491
|
+
static resolveUrl(urlOrSlug) {
|
|
492
|
+
if (urlOrSlug.startsWith("http://") || urlOrSlug.startsWith("https://")) {
|
|
493
|
+
return urlOrSlug;
|
|
494
|
+
}
|
|
495
|
+
return `https://${urlOrSlug}.mcphost.dev`;
|
|
496
|
+
}
|
|
497
|
+
static extractSlug(url) {
|
|
498
|
+
if (url.includes(".mcphost.dev")) {
|
|
499
|
+
const match = url.match(/https?:\/\/([^.]+)\.mcphost\.dev/);
|
|
500
|
+
if (match) {
|
|
501
|
+
return match[1];
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
const parsed = new URL(url);
|
|
506
|
+
return parsed.hostname.replace(/\./g, "-");
|
|
507
|
+
} catch {
|
|
508
|
+
return url.replace(/[^a-zA-Z0-9]/g, "-").slice(0, 20);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
// src/commands/connect.ts
|
|
514
|
+
import chalk2 from "chalk";
|
|
515
|
+
function createConnectCommands() {
|
|
516
|
+
const connect = new Command2("connect").description("Connect an MCP server to AI clients").argument("<url-or-slug>", "MCP server URL or slug").option("--client <client>", "Target specific client (claude, cursor, vscode, openclaw, chatgpt)").option("--name <name>", "Custom name for the connection").action(async (urlOrSlug, options) => {
|
|
517
|
+
const config = new Config();
|
|
518
|
+
const spinner = Logger.spinner("Setting up MCP connection...");
|
|
519
|
+
try {
|
|
520
|
+
const url = ClientManager.resolveUrl(urlOrSlug);
|
|
521
|
+
const slug = ClientManager.extractSlug(url);
|
|
522
|
+
const name = options.name || slug;
|
|
523
|
+
const existing = config.findConnection(slug);
|
|
524
|
+
if (existing) {
|
|
525
|
+
spinner.warn(`Already connected to ${slug}`);
|
|
526
|
+
Logger.info(`Use \`mcphost disconnect ${slug}\` to remove first`);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
const clients = [];
|
|
530
|
+
if (options.client) {
|
|
531
|
+
const clientName = options.client;
|
|
532
|
+
const success = await ClientManager.addToClient(clientName, slug, url);
|
|
533
|
+
if (success) {
|
|
534
|
+
clients.push(clientName);
|
|
535
|
+
spinner.succeed(`Connected to ${clientName}`);
|
|
536
|
+
} else {
|
|
537
|
+
spinner.fail(`Failed to connect to ${clientName}`);
|
|
538
|
+
process.exit(1);
|
|
539
|
+
}
|
|
540
|
+
} else {
|
|
541
|
+
const detectedClients = await ClientManager.detectInstalledClients();
|
|
542
|
+
const availableClients = detectedClients.filter((c) => c.exists);
|
|
543
|
+
if (availableClients.length === 0) {
|
|
544
|
+
spinner.warn("No supported clients found");
|
|
545
|
+
Logger.info("Supported clients: Claude Desktop, Cursor, VS Code, OpenClaw");
|
|
546
|
+
Logger.info("Install a client and try again, or use --client chatgpt for web setup");
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
spinner.text = `Configuring ${availableClients.length} client${availableClients.length > 1 ? "s" : ""}...`;
|
|
550
|
+
for (const client of availableClients) {
|
|
551
|
+
try {
|
|
552
|
+
const success = await ClientManager.addToClient(
|
|
553
|
+
client.name,
|
|
554
|
+
slug,
|
|
555
|
+
url
|
|
556
|
+
);
|
|
557
|
+
if (success) {
|
|
558
|
+
clients.push(client.name);
|
|
559
|
+
}
|
|
560
|
+
} catch (error) {
|
|
561
|
+
Logger.warning(`Failed to configure ${client.name}: ${error}`);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
spinner.succeed(`Connected to ${clients.length} client${clients.length > 1 ? "s" : ""}`);
|
|
565
|
+
}
|
|
566
|
+
config.addConnection({
|
|
567
|
+
slug,
|
|
568
|
+
url,
|
|
569
|
+
clients
|
|
570
|
+
});
|
|
571
|
+
Logger.success(`\u{1F517} ${name} connected successfully!`);
|
|
572
|
+
Logger.dim(` URL: ${url}`);
|
|
573
|
+
Logger.dim(` Clients: ${clients.join(", ")}`);
|
|
574
|
+
console.log("\n" + chalk2.green("\u{1F389} Connected! Share with your team:"));
|
|
575
|
+
console.log(chalk2.cyan(`npx @mcphosting/cli connect ${urlOrSlug}`));
|
|
576
|
+
console.log("\n" + chalk2.yellow("\u2B50 Star us: ") + chalk2.blue("https://github.com/gorlomi-enzo/mcphosting-cli"));
|
|
577
|
+
console.log("");
|
|
578
|
+
} catch (error) {
|
|
579
|
+
spinner.fail("Connection failed");
|
|
580
|
+
Logger.error(`Error: ${error}`);
|
|
581
|
+
process.exit(1);
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
return connect;
|
|
585
|
+
}
|
|
586
|
+
function createDisconnectCommand() {
|
|
587
|
+
return new Command2("disconnect").description("Disconnect an MCP server").argument("<slug-or-id>", "MCP server slug or connection ID").action(async (slugOrId) => {
|
|
588
|
+
const config = new Config();
|
|
589
|
+
const connection = config.findConnection(slugOrId);
|
|
590
|
+
if (!connection) {
|
|
591
|
+
Logger.error(`Connection not found: ${slugOrId}`);
|
|
592
|
+
Logger.info("Use `mcphost list` to see active connections");
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
const spinner = Logger.spinner(`Disconnecting ${connection.slug}...`);
|
|
596
|
+
try {
|
|
597
|
+
const removedFrom = await ClientManager.removeFromAllClients(connection.slug);
|
|
598
|
+
config.removeConnection(connection.id);
|
|
599
|
+
spinner.succeed(`Disconnected ${connection.slug}`);
|
|
600
|
+
if (removedFrom.length > 0) {
|
|
601
|
+
Logger.info(`Removed from: ${removedFrom.join(", ")}`);
|
|
602
|
+
}
|
|
603
|
+
} catch (error) {
|
|
604
|
+
spinner.fail("Disconnect failed");
|
|
605
|
+
Logger.error(`Error: ${error}`);
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
function createListCommand() {
|
|
610
|
+
return new Command2("list").description("List connected MCP servers").option("--json", "Output as JSON").action(async (options) => {
|
|
611
|
+
const config = new Config();
|
|
612
|
+
const connections = config.connections;
|
|
613
|
+
if (connections.length === 0) {
|
|
614
|
+
Logger.info("No MCP connections found");
|
|
615
|
+
Logger.dim("Use `mcphost connect <url>` to add one");
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (options.json) {
|
|
619
|
+
Logger.json(connections);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
Logger.bold(`\u{1F4CB} Connected MCP Servers (${connections.length})`);
|
|
623
|
+
console.log("");
|
|
624
|
+
const tableData = connections.map((conn) => ({
|
|
625
|
+
Slug: conn.slug,
|
|
626
|
+
URL: conn.url.length > 50 ? conn.url.slice(0, 47) + "..." : conn.url,
|
|
627
|
+
Clients: conn.clients.join(", "),
|
|
628
|
+
Added: new Date(conn.addedAt).toLocaleDateString()
|
|
629
|
+
}));
|
|
630
|
+
Logger.table(tableData);
|
|
631
|
+
console.log("");
|
|
632
|
+
Logger.dim("Use `mcphost disconnect <slug>` to remove a connection");
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// src/commands/import.ts
|
|
637
|
+
import { Command as Command3 } from "commander";
|
|
638
|
+
import { readFile as readFile2, access as access2 } from "fs/promises";
|
|
639
|
+
import { join as join2 } from "path";
|
|
640
|
+
import { homedir as homedir2 } from "os";
|
|
641
|
+
import chalk3 from "chalk";
|
|
642
|
+
function createImportCommand() {
|
|
643
|
+
return new Command3("import").description("Import MCP connections from other tools").option("--from <tool>", "Import from: smithery", "smithery").option("--dry-run", "Show what would be imported without actually importing").option("--config-path <path>", "Custom config file path").action(async (options) => {
|
|
644
|
+
if (options.from !== "smithery") {
|
|
645
|
+
Logger.error("Only Smithery import is currently supported");
|
|
646
|
+
Logger.info("Usage: mcphost import --from smithery");
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
await importFromSmitery(options);
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
async function importFromSmitery(options) {
|
|
653
|
+
const config = new Config();
|
|
654
|
+
const spinner = Logger.spinner("Looking for Smithery config...");
|
|
655
|
+
try {
|
|
656
|
+
const smitheryPaths = [
|
|
657
|
+
options.configPath,
|
|
658
|
+
join2(homedir2(), ".smithery", "config.json"),
|
|
659
|
+
join2(homedir2(), ".config", "smithery", "config.json"),
|
|
660
|
+
join2(homedir2(), "Library", "Application Support", "smithery", "config.json"),
|
|
661
|
+
join2(process.cwd(), "smithery.json"),
|
|
662
|
+
join2(process.cwd(), ".smithery.json")
|
|
663
|
+
].filter(Boolean);
|
|
664
|
+
let smitheryConfigPath = null;
|
|
665
|
+
for (const path of smitheryPaths) {
|
|
666
|
+
try {
|
|
667
|
+
await access2(path);
|
|
668
|
+
smitheryConfigPath = path;
|
|
669
|
+
break;
|
|
670
|
+
} catch {
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (!smitheryConfigPath) {
|
|
674
|
+
spinner.fail("Smithery config not found");
|
|
675
|
+
Logger.warning("Searched locations:");
|
|
676
|
+
smitheryPaths.forEach((path) => Logger.dim(` ${path}`));
|
|
677
|
+
Logger.info("\nIf Smithery is installed, you can specify the config path:");
|
|
678
|
+
Logger.info(" mcphost import --from smithery --config-path /path/to/smithery/config.json");
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
spinner.succeed(`Found Smithery config: ${smitheryConfigPath}`);
|
|
682
|
+
const loadSpinner = Logger.spinner("Reading Smithery config...");
|
|
683
|
+
const configContent = await readFile2(smitheryConfigPath, "utf-8");
|
|
684
|
+
const smitheryConfig = JSON.parse(configContent);
|
|
685
|
+
const servers = {
|
|
686
|
+
...smitheryConfig.servers,
|
|
687
|
+
...smitheryConfig.mcpServers
|
|
688
|
+
};
|
|
689
|
+
if (!servers || Object.keys(servers).length === 0) {
|
|
690
|
+
loadSpinner.warn("No MCP servers found in Smithery config");
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
const serverEntries = Object.entries(servers);
|
|
694
|
+
loadSpinner.succeed(`Found ${serverEntries.length} MCP server(s) in Smithery config`);
|
|
695
|
+
if (options.dryRun) {
|
|
696
|
+
console.log("\n" + chalk3.bold("\u{1F50D} Preview: Would import the following MCP servers:"));
|
|
697
|
+
console.log("");
|
|
698
|
+
serverEntries.forEach(([slug, serverConfig]) => {
|
|
699
|
+
const url = serverConfig.url || `https://${slug}.mcphost.dev`;
|
|
700
|
+
console.log(`\u2022 ${chalk3.cyan(slug)}`);
|
|
701
|
+
console.log(` URL: ${chalk3.dim(url)}`);
|
|
702
|
+
if (serverConfig.command) {
|
|
703
|
+
console.log(` Command: ${chalk3.dim(serverConfig.command + " " + (serverConfig.args?.join(" ") || ""))}`);
|
|
704
|
+
}
|
|
705
|
+
console.log("");
|
|
706
|
+
});
|
|
707
|
+
Logger.info("Run without --dry-run to perform the import");
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
const importSpinner = Logger.spinner("Importing MCP servers...");
|
|
711
|
+
let imported = 0;
|
|
712
|
+
let skipped = 0;
|
|
713
|
+
for (const [slug, serverConfig] of serverEntries) {
|
|
714
|
+
try {
|
|
715
|
+
const existing = config.findConnection(slug);
|
|
716
|
+
if (existing) {
|
|
717
|
+
Logger.warning(`Skipping ${slug} - already connected`);
|
|
718
|
+
skipped++;
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
let url = serverConfig.url;
|
|
722
|
+
if (!url) {
|
|
723
|
+
if (serverConfig.command === "npx" && serverConfig.args?.[0] === "@mcphosting/cli") {
|
|
724
|
+
url = serverConfig.args[2];
|
|
725
|
+
} else {
|
|
726
|
+
url = `https://${slug}.mcphost.dev`;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
if (!url) {
|
|
730
|
+
Logger.warning(`Skipping ${slug} - no URL found`);
|
|
731
|
+
skipped++;
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
const detectedClients = await ClientManager.detectInstalledClients();
|
|
735
|
+
const availableClients = detectedClients.filter((c) => c.exists);
|
|
736
|
+
const connectedClients = [];
|
|
737
|
+
for (const client of availableClients) {
|
|
738
|
+
try {
|
|
739
|
+
const success = await ClientManager.addToClient(
|
|
740
|
+
client.name,
|
|
741
|
+
slug,
|
|
742
|
+
url
|
|
743
|
+
);
|
|
744
|
+
if (success) {
|
|
745
|
+
connectedClients.push(client.name);
|
|
746
|
+
}
|
|
747
|
+
} catch (error) {
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
config.addConnection({
|
|
751
|
+
slug,
|
|
752
|
+
url,
|
|
753
|
+
clients: connectedClients
|
|
754
|
+
});
|
|
755
|
+
imported++;
|
|
756
|
+
} catch (error) {
|
|
757
|
+
Logger.warning(`Failed to import ${slug}: ${error}`);
|
|
758
|
+
skipped++;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
importSpinner.succeed("Import completed");
|
|
762
|
+
Logger.success(`\u2705 Imported ${imported} MCP server(s)`);
|
|
763
|
+
if (skipped > 0) {
|
|
764
|
+
Logger.warning(`\u26A0\uFE0F Skipped ${skipped} server(s)`);
|
|
765
|
+
}
|
|
766
|
+
console.log("");
|
|
767
|
+
Logger.info("Use `mcphost list` to see your imported connections");
|
|
768
|
+
console.log("\n" + chalk3.green("\u{1F389} Welcome to MCPHosting! Share with your team:"));
|
|
769
|
+
console.log(chalk3.cyan("npx @mcphosting/cli import --from smithery"));
|
|
770
|
+
console.log("\n" + chalk3.yellow("\u2B50 Star us: ") + chalk3.blue("https://github.com/gorlomi-enzo/mcphosting-cli"));
|
|
771
|
+
console.log("");
|
|
772
|
+
} catch (error) {
|
|
773
|
+
spinner.fail("Import failed");
|
|
774
|
+
Logger.error(`Error: ${error}`);
|
|
775
|
+
if (error instanceof SyntaxError) {
|
|
776
|
+
Logger.warning("Invalid JSON in Smithery config file");
|
|
777
|
+
}
|
|
778
|
+
process.exit(1);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// src/commands/proxy.ts
|
|
783
|
+
import { Command as Command4 } from "commander";
|
|
784
|
+
function createProxyCommand() {
|
|
785
|
+
return new Command4("proxy").description("Start a local STDIO MCP proxy to a remote server").argument("<url>", "Remote MCP server URL").option("--quiet", "Suppress startup messages").action(async (url, options) => {
|
|
786
|
+
if (!options.quiet) {
|
|
787
|
+
console.error("\u{1F517} Proxying via mcphosting.com");
|
|
788
|
+
console.error(`\u{1F4E1} Remote server: ${url}`);
|
|
789
|
+
}
|
|
790
|
+
try {
|
|
791
|
+
await startProxy(url, options.quiet);
|
|
792
|
+
} catch (error) {
|
|
793
|
+
if (!options.quiet) {
|
|
794
|
+
console.error(`\u274C Proxy error: ${error}`);
|
|
795
|
+
}
|
|
796
|
+
process.exit(1);
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
async function startProxy(url, quiet = false) {
|
|
801
|
+
let buffer = "";
|
|
802
|
+
process.stdin.setEncoding("utf8");
|
|
803
|
+
process.stdin.on("data", async (chunk) => {
|
|
804
|
+
buffer += chunk;
|
|
805
|
+
const lines = buffer.split("\n");
|
|
806
|
+
buffer = lines.pop() || "";
|
|
807
|
+
for (const line of lines) {
|
|
808
|
+
if (line.trim()) {
|
|
809
|
+
try {
|
|
810
|
+
await forwardMessage(url, line, quiet);
|
|
811
|
+
} catch (error) {
|
|
812
|
+
if (!quiet) {
|
|
813
|
+
console.error(`Proxy error: ${error}`);
|
|
814
|
+
}
|
|
815
|
+
const errorResponse = {
|
|
816
|
+
jsonrpc: "2.0",
|
|
817
|
+
id: null,
|
|
818
|
+
error: {
|
|
819
|
+
code: -32603,
|
|
820
|
+
message: "Proxy error",
|
|
821
|
+
data: error.toString()
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
process.stdout.write(JSON.stringify(errorResponse) + "\n");
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
process.stdin.on("end", () => {
|
|
830
|
+
if (!quiet) {
|
|
831
|
+
console.error("\u{1F4F4} Proxy connection closed");
|
|
832
|
+
}
|
|
833
|
+
process.exit(0);
|
|
834
|
+
});
|
|
835
|
+
process.stdin.on("error", (error) => {
|
|
836
|
+
if (!quiet) {
|
|
837
|
+
console.error(`Stdin error: ${error}`);
|
|
838
|
+
}
|
|
839
|
+
process.exit(1);
|
|
840
|
+
});
|
|
841
|
+
await new Promise(() => {
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
async function forwardMessage(url, message, quiet) {
|
|
845
|
+
try {
|
|
846
|
+
const jsonrpcMessage = JSON.parse(message);
|
|
847
|
+
const response = await fetch(url, {
|
|
848
|
+
method: "POST",
|
|
849
|
+
headers: {
|
|
850
|
+
"Content-Type": "application/json",
|
|
851
|
+
"Accept": "application/json",
|
|
852
|
+
"User-Agent": "@mcphosting/cli-proxy"
|
|
853
|
+
},
|
|
854
|
+
body: message
|
|
855
|
+
});
|
|
856
|
+
if (!response.ok) {
|
|
857
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
858
|
+
}
|
|
859
|
+
const responseData = await response.text();
|
|
860
|
+
process.stdout.write(responseData);
|
|
861
|
+
if (!responseData.endsWith("\n")) {
|
|
862
|
+
process.stdout.write("\n");
|
|
863
|
+
}
|
|
864
|
+
} catch (error) {
|
|
865
|
+
if (error instanceof SyntaxError) {
|
|
866
|
+
const errorResponse = {
|
|
867
|
+
jsonrpc: "2.0",
|
|
868
|
+
id: null,
|
|
869
|
+
error: {
|
|
870
|
+
code: -32700,
|
|
871
|
+
message: "Parse error",
|
|
872
|
+
data: "Invalid JSON-RPC message"
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
process.stdout.write(JSON.stringify(errorResponse) + "\n");
|
|
876
|
+
} else {
|
|
877
|
+
throw error;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// src/commands/search.ts
|
|
883
|
+
import { Command as Command5 } from "commander";
|
|
884
|
+
import chalk4 from "chalk";
|
|
885
|
+
function createSearchCommand() {
|
|
886
|
+
return new Command5("search").description("Search MCP servers in the marketplace").argument("<query>", "Search query").option("--limit <number>", "Maximum number of results", "10").option("--json", "Output as JSON").action(async (query, options) => {
|
|
887
|
+
const config = new Config();
|
|
888
|
+
const api = new MCPHostingAPI(config.token);
|
|
889
|
+
const spinner = Logger.spinner(`Searching for "${query}"...`);
|
|
890
|
+
try {
|
|
891
|
+
const results = await api.searchMCPs(query);
|
|
892
|
+
spinner.succeed(`Found ${results.length} MCP server(s)`);
|
|
893
|
+
if (results.length === 0) {
|
|
894
|
+
Logger.info("No MCP servers found matching your query");
|
|
895
|
+
Logger.dim("Try searching for: github, slack, notion, stripe, postgres, filesystem");
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
const limit = parseInt(options.limit);
|
|
899
|
+
const limitedResults = results.slice(0, limit);
|
|
900
|
+
if (options.json) {
|
|
901
|
+
Logger.json(limitedResults);
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
console.log("");
|
|
905
|
+
Logger.bold(`\u{1F50D} MCP Marketplace Search Results`);
|
|
906
|
+
console.log("");
|
|
907
|
+
limitedResults.forEach((server, index) => {
|
|
908
|
+
console.log(chalk4.cyan(`${index + 1}. ${server.name}`));
|
|
909
|
+
console.log(` ${chalk4.dim(server.description)}`);
|
|
910
|
+
console.log(` ${chalk4.yellow("Slug:")} ${server.slug} | ${chalk4.yellow("Installs:")} ${server.installs.toLocaleString()} | ${chalk4.yellow("Author:")} ${server.author}`);
|
|
911
|
+
console.log(` ${chalk4.green("Tools:")} ${server.tools.join(", ")}`);
|
|
912
|
+
if (server.url) {
|
|
913
|
+
console.log(` ${chalk4.blue("Connect:")} ${chalk4.dim(`mcphost connect ${server.slug}`)}`);
|
|
914
|
+
}
|
|
915
|
+
console.log("");
|
|
916
|
+
});
|
|
917
|
+
if (results.length > limit) {
|
|
918
|
+
Logger.dim(`Showing ${limit} of ${results.length} results. Use --limit to see more.`);
|
|
919
|
+
}
|
|
920
|
+
Logger.info("Use `mcphost info <slug>` for detailed information");
|
|
921
|
+
Logger.info("Use `mcphost connect <slug>` to install");
|
|
922
|
+
} catch (error) {
|
|
923
|
+
spinner.fail("Search failed");
|
|
924
|
+
Logger.error(`Error: ${error}`);
|
|
925
|
+
process.exit(1);
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
function createInfoCommand() {
|
|
930
|
+
return new Command5("info").description("Show detailed information about an MCP server").argument("<slug>", "MCP server slug").option("--json", "Output as JSON").action(async (slug, options) => {
|
|
931
|
+
const config = new Config();
|
|
932
|
+
const api = new MCPHostingAPI(config.token);
|
|
933
|
+
const spinner = Logger.spinner(`Getting info for ${slug}...`);
|
|
934
|
+
try {
|
|
935
|
+
const server = await api.getMCPInfo(slug);
|
|
936
|
+
if (!server) {
|
|
937
|
+
spinner.fail(`MCP server not found: ${slug}`);
|
|
938
|
+
Logger.info("Use `mcphost search <query>` to find servers");
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
spinner.succeed("Found server info");
|
|
942
|
+
if (options.json) {
|
|
943
|
+
Logger.json(server);
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
console.log("");
|
|
947
|
+
console.log(chalk4.bold.cyan(`\u{1F4E6} ${server.name}`));
|
|
948
|
+
console.log(chalk4.dim(server.description));
|
|
949
|
+
console.log("");
|
|
950
|
+
const infoTable = [
|
|
951
|
+
{ Property: "Slug", Value: server.slug },
|
|
952
|
+
{ Property: "Author", Value: server.author },
|
|
953
|
+
{ Property: "Installs", Value: server.installs.toLocaleString() },
|
|
954
|
+
{ Property: "Tools", Value: server.tools.length.toString() }
|
|
955
|
+
];
|
|
956
|
+
if (server.url) {
|
|
957
|
+
infoTable.push({ Property: "URL", Value: server.url });
|
|
958
|
+
}
|
|
959
|
+
Logger.table(infoTable);
|
|
960
|
+
console.log("");
|
|
961
|
+
console.log(chalk4.yellow("\u{1F527} Available Tools:"));
|
|
962
|
+
server.tools.forEach((tool) => {
|
|
963
|
+
console.log(` \u2022 ${tool}`);
|
|
964
|
+
});
|
|
965
|
+
console.log("");
|
|
966
|
+
console.log(chalk4.green("\u{1F4A1} Quick Start:"));
|
|
967
|
+
console.log(chalk4.dim(` mcphost connect ${server.slug}`));
|
|
968
|
+
const connection = config.findConnection(slug);
|
|
969
|
+
if (connection) {
|
|
970
|
+
console.log("");
|
|
971
|
+
console.log(chalk4.blue("\u2139\uFE0F Already connected to:"), connection.clients.join(", "));
|
|
972
|
+
}
|
|
973
|
+
} catch (error) {
|
|
974
|
+
spinner.fail("Failed to get server info");
|
|
975
|
+
Logger.error(`Error: ${error}`);
|
|
976
|
+
process.exit(1);
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// src/commands/servers.ts
|
|
982
|
+
import { Command as Command6 } from "commander";
|
|
983
|
+
function createServersCommand() {
|
|
984
|
+
const servers = new Command6("servers");
|
|
985
|
+
servers.description("Manage your hosted MCP servers");
|
|
986
|
+
servers.command("list").description("List your MCP servers").action(async () => {
|
|
987
|
+
const config = new Config();
|
|
988
|
+
if (!config.token) {
|
|
989
|
+
Logger.warning("Authentication required");
|
|
990
|
+
Logger.info("Use `mcphost login` to authenticate with MCPHosting");
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
Logger.info("\u{1F6A7} Server management coming soon!");
|
|
994
|
+
console.log("");
|
|
995
|
+
console.log("This feature will let you:");
|
|
996
|
+
console.log("\u2022 List your hosted MCP servers");
|
|
997
|
+
console.log("\u2022 View usage analytics");
|
|
998
|
+
console.log("\u2022 Manage API keys");
|
|
999
|
+
console.log("\u2022 Publish to marketplace");
|
|
1000
|
+
console.log("");
|
|
1001
|
+
Logger.info("Visit https://mcphosting.com to manage servers in the web dashboard");
|
|
1002
|
+
});
|
|
1003
|
+
return servers;
|
|
1004
|
+
}
|
|
1005
|
+
function createKeysCommand() {
|
|
1006
|
+
const keys = new Command6("keys");
|
|
1007
|
+
keys.description("Manage API keys for your MCP servers");
|
|
1008
|
+
keys.command("list").argument("[server-id]", "Server ID").description("List API keys").action(async (serverId) => {
|
|
1009
|
+
const config = new Config();
|
|
1010
|
+
if (!config.token) {
|
|
1011
|
+
Logger.warning("Authentication required");
|
|
1012
|
+
Logger.info("Use `mcphost login` to authenticate");
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
Logger.info("\u{1F6A7} API key management coming soon!");
|
|
1016
|
+
Logger.info("Visit https://mcphosting.com to manage keys in the web dashboard");
|
|
1017
|
+
});
|
|
1018
|
+
keys.command("create").argument("<server-id>", "Server ID").option("--name <name>", "Key name", "CLI Key").description("Create a new API key").action(async (serverId, options) => {
|
|
1019
|
+
const config = new Config();
|
|
1020
|
+
if (!config.token) {
|
|
1021
|
+
Logger.warning("Authentication required");
|
|
1022
|
+
Logger.info("Use `mcphost login` to authenticate");
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
Logger.info("\u{1F6A7} API key creation coming soon!");
|
|
1026
|
+
Logger.info("Visit https://mcphosting.com to create keys in the web dashboard");
|
|
1027
|
+
});
|
|
1028
|
+
keys.command("revoke").argument("<key-id>", "API key ID").description("Revoke an API key").action(async (keyId) => {
|
|
1029
|
+
const config = new Config();
|
|
1030
|
+
if (!config.token) {
|
|
1031
|
+
Logger.warning("Authentication required");
|
|
1032
|
+
Logger.info("Use `mcphost login` to authenticate");
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
Logger.info("\u{1F6A7} API key revocation coming soon!");
|
|
1036
|
+
Logger.info("Visit https://mcphosting.com to revoke keys in the web dashboard");
|
|
1037
|
+
});
|
|
1038
|
+
return keys;
|
|
1039
|
+
}
|
|
1040
|
+
function createPublishCommand() {
|
|
1041
|
+
return new Command6("publish").description("Publish MCP server to marketplace").argument("[server-id]", "Server ID to publish").option("--private", "Keep server private").option("--description <desc>", "Server description").option("--tags <tags>", "Comma-separated tags").action(async (serverId, options = {}) => {
|
|
1042
|
+
const config = new Config();
|
|
1043
|
+
if (!config.token) {
|
|
1044
|
+
Logger.warning("Authentication required");
|
|
1045
|
+
Logger.info("Use `mcphost login` to authenticate");
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
Logger.info("\u{1F6A7} Marketplace publishing coming soon!");
|
|
1049
|
+
console.log("");
|
|
1050
|
+
console.log("This feature will let you:");
|
|
1051
|
+
console.log("\u2022 Publish your MCP servers to the marketplace");
|
|
1052
|
+
console.log("\u2022 Set descriptions and tags");
|
|
1053
|
+
console.log("\u2022 Configure public/private visibility");
|
|
1054
|
+
console.log("\u2022 Track usage analytics");
|
|
1055
|
+
console.log("");
|
|
1056
|
+
Logger.info("Visit https://mcphosting.com to publish servers via the web dashboard");
|
|
1057
|
+
if (serverId) {
|
|
1058
|
+
Logger.dim(`Would publish server: ${serverId}`);
|
|
1059
|
+
}
|
|
1060
|
+
if (options.description) {
|
|
1061
|
+
Logger.dim(`Description: ${options.description}`);
|
|
1062
|
+
}
|
|
1063
|
+
if (options.tags) {
|
|
1064
|
+
Logger.dim(`Tags: ${options.tags}`);
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// src/index.ts
|
|
1070
|
+
import chalk5 from "chalk";
|
|
1071
|
+
var program = new Command7();
|
|
1072
|
+
program.name("mcphost").description("Connect AI agents to MCP servers. Browse, install, and manage Model Context Protocol servers.").version("0.1.0").configureOutput({
|
|
1073
|
+
outputError: (str, write) => {
|
|
1074
|
+
write(chalk5.red(str));
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
program.addCommand(createConnectCommands());
|
|
1078
|
+
program.addCommand(createDisconnectCommand());
|
|
1079
|
+
program.addCommand(createListCommand());
|
|
1080
|
+
program.addCommand(createImportCommand());
|
|
1081
|
+
program.addCommand(createProxyCommand());
|
|
1082
|
+
program.addCommand(createSearchCommand());
|
|
1083
|
+
program.addCommand(createInfoCommand());
|
|
1084
|
+
program.addCommand(createServersCommand());
|
|
1085
|
+
program.addCommand(createKeysCommand());
|
|
1086
|
+
program.addCommand(createPublishCommand());
|
|
1087
|
+
program.addCommand(createAuthCommands());
|
|
1088
|
+
var [login, logout, whoami] = createLegacyAuthCommands();
|
|
1089
|
+
program.addCommand(login);
|
|
1090
|
+
program.addCommand(logout);
|
|
1091
|
+
program.addCommand(whoami);
|
|
1092
|
+
program.configureHelp({
|
|
1093
|
+
subcommandTerm: (cmd) => chalk5.cyan(cmd.name()),
|
|
1094
|
+
commandUsage: (cmd) => {
|
|
1095
|
+
const usage = cmd.usage();
|
|
1096
|
+
return chalk5.yellow(usage);
|
|
1097
|
+
},
|
|
1098
|
+
commandDescription: (cmd) => {
|
|
1099
|
+
return chalk5.dim(cmd.description());
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
program.addHelpText("after", `
|
|
1103
|
+
${chalk5.bold("Examples:")}
|
|
1104
|
+
${chalk5.cyan("mcphost connect github")} ${chalk5.dim("Connect to GitHub MCP server")}
|
|
1105
|
+
${chalk5.cyan("mcphost connect https://my-mcp.example.com")} ${chalk5.dim("Connect to custom MCP server")}
|
|
1106
|
+
${chalk5.cyan("mcphost connect slack --client claude")} ${chalk5.dim("Connect Slack MCP only to Claude")}
|
|
1107
|
+
${chalk5.cyan("mcphost list")} ${chalk5.dim("List all connected MCP servers")}
|
|
1108
|
+
${chalk5.cyan("mcphost search github")} ${chalk5.dim("Search marketplace for MCP servers")}
|
|
1109
|
+
${chalk5.cyan("mcphost info notion")} ${chalk5.dim("Get details about Notion MCP")}
|
|
1110
|
+
${chalk5.cyan("mcphost import --from smithery")} ${chalk5.dim("Import connections from Smithery")}
|
|
1111
|
+
${chalk5.cyan("mcphost disconnect github")} ${chalk5.dim("Remove GitHub MCP connection")}
|
|
1112
|
+
|
|
1113
|
+
${chalk5.bold("Supported AI Clients:")}
|
|
1114
|
+
\u2022 ${chalk5.green("Claude Desktop")} - Auto-configured via claude_desktop_config.json
|
|
1115
|
+
\u2022 ${chalk5.green("Cursor")} - Auto-configured via .cursor/mcp.json
|
|
1116
|
+
\u2022 ${chalk5.green("VS Code")} - Configured via .vscode/mcp.json
|
|
1117
|
+
\u2022 ${chalk5.green("OpenClaw")} - Auto-configured via ~/.openclaw/mcp.json
|
|
1118
|
+
\u2022 ${chalk5.green("ChatGPT")} - Manual setup with web instructions
|
|
1119
|
+
|
|
1120
|
+
${chalk5.bold("Get Started:")}
|
|
1121
|
+
${chalk5.dim("1.")} ${chalk5.cyan("mcphost search <topic>")} ${chalk5.dim("Find MCP servers")}
|
|
1122
|
+
${chalk5.dim("2.")} ${chalk5.cyan("mcphost connect <slug>")} ${chalk5.dim("Connect to your AI clients")}
|
|
1123
|
+
${chalk5.dim("3.")} ${chalk5.cyan("mcphost list")} ${chalk5.dim("View your connections")}
|
|
1124
|
+
|
|
1125
|
+
${chalk5.yellow("\u2B50 Star us:")} ${chalk5.blue("https://github.com/gorlomi-enzo/mcphosting-cli")}
|
|
1126
|
+
${chalk5.yellow("\u{1F4DA} Docs:")} ${chalk5.blue("https://mcphosting.com/docs")}
|
|
1127
|
+
`);
|
|
1128
|
+
process.on("uncaughtException", (error) => {
|
|
1129
|
+
Logger.error(`Uncaught error: ${error.message}`);
|
|
1130
|
+
if (process.env.DEBUG) {
|
|
1131
|
+
console.error(error.stack);
|
|
1132
|
+
}
|
|
1133
|
+
process.exit(1);
|
|
1134
|
+
});
|
|
1135
|
+
process.on("unhandledRejection", (reason) => {
|
|
1136
|
+
Logger.error(`Unhandled rejection: ${reason}`);
|
|
1137
|
+
if (process.env.DEBUG) {
|
|
1138
|
+
console.error(reason);
|
|
1139
|
+
}
|
|
1140
|
+
process.exit(1);
|
|
1141
|
+
});
|
|
1142
|
+
process.on("SIGINT", () => {
|
|
1143
|
+
console.log("\n");
|
|
1144
|
+
Logger.info("Goodbye! \u{1F44B}");
|
|
1145
|
+
process.exit(0);
|
|
1146
|
+
});
|
|
1147
|
+
async function main() {
|
|
1148
|
+
try {
|
|
1149
|
+
await program.parseAsync();
|
|
1150
|
+
} catch (error) {
|
|
1151
|
+
Logger.error(`Command failed: ${error}`);
|
|
1152
|
+
if (process.env.DEBUG) {
|
|
1153
|
+
console.error(error);
|
|
1154
|
+
}
|
|
1155
|
+
process.exit(1);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
main();
|
|
1159
|
+
//# sourceMappingURL=index.js.map
|