mcphosting-cli 0.1.0 → 0.1.2

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.
@@ -1,188 +0,0 @@
1
- import { readFile, writeFile, access } from 'fs/promises';
2
- import { join, dirname } from 'path';
3
- import { homedir } from 'os';
4
- import { mkdir } from 'fs/promises';
5
- import { ClientConfig, ClaudeConfig, CursorConfig, MCPServerEntry, SupportedClient } from '../types.js';
6
- import { Logger } from './logger.js';
7
-
8
- export class ClientManager {
9
- private static getClientPaths(): Record<SupportedClient, string> {
10
- const home = homedir();
11
- const platform = process.platform;
12
-
13
- const paths: Record<SupportedClient, string> = {
14
- claude: platform === 'win32'
15
- ? join(process.env.APPDATA || '', 'Claude', 'claude_desktop_config.json')
16
- : join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),
17
- cursor: join(home, '.cursor', 'mcp.json'),
18
- vscode: join(process.cwd(), '.vscode', 'mcp.json'),
19
- openclaw: join(home, '.openclaw', 'mcp.json'),
20
- chatgpt: '' // Web-based, no local config
21
- };
22
-
23
- return paths;
24
- }
25
-
26
- static async detectInstalledClients(): Promise<ClientConfig[]> {
27
- const paths = this.getClientPaths();
28
- const clients: ClientConfig[] = [];
29
-
30
- for (const [name, path] of Object.entries(paths)) {
31
- if (name === 'chatgpt') continue; // Skip web-based clients
32
-
33
- try {
34
- await access(path);
35
- clients.push({ name, configPath: path, exists: true });
36
- } catch {
37
- clients.push({ name, configPath: path, exists: false });
38
- }
39
- }
40
-
41
- return clients;
42
- }
43
-
44
- static async addToClient(
45
- client: SupportedClient,
46
- slug: string,
47
- url: string
48
- ): Promise<boolean> {
49
- if (client === 'chatgpt') {
50
- Logger.info('ChatGPT Setup Instructions:');
51
- console.log(`
52
- 1. Open ChatGPT in your browser
53
- 2. Go to Settings → Apps → Connect an app
54
- 3. Enter this URL: ${url}
55
- 4. Follow the prompts to authorize the MCP server
56
- `);
57
- return true;
58
- }
59
-
60
- const paths = this.getClientPaths();
61
- const configPath = paths[client];
62
-
63
- if (!configPath) {
64
- throw new Error(`Unsupported client: ${client}`);
65
- }
66
-
67
- const serverEntry: MCPServerEntry = {
68
- command: 'npx',
69
- args: ['-y', '@mcphosting/cli', 'proxy', url],
70
- env: {}
71
- };
72
-
73
- try {
74
- // Ensure directory exists
75
- await mkdir(dirname(configPath), { recursive: true });
76
-
77
- let config: ClaudeConfig | CursorConfig = { mcpServers: {} };
78
-
79
- // Read existing config if it exists
80
- try {
81
- const configContent = await readFile(configPath, 'utf-8');
82
- config = JSON.parse(configContent);
83
- if (!config.mcpServers) {
84
- config.mcpServers = {};
85
- }
86
- } catch {
87
- // File doesn't exist or is invalid, use default config
88
- }
89
-
90
- // Add the MCP server
91
- config.mcpServers[slug] = serverEntry;
92
-
93
- // Write back to file
94
- await writeFile(configPath, JSON.stringify(config, null, 2));
95
- return true;
96
- } catch (error) {
97
- Logger.error(`Failed to configure ${client}: ${error}`);
98
- return false;
99
- }
100
- }
101
-
102
- static async removeFromClient(
103
- client: SupportedClient,
104
- slug: string
105
- ): Promise<boolean> {
106
- if (client === 'chatgpt') {
107
- Logger.info('To remove from ChatGPT:');
108
- console.log(`
109
- 1. Open ChatGPT in your browser
110
- 2. Go to Settings → Apps
111
- 3. Find and disconnect the MCP server: ${slug}
112
- `);
113
- return true;
114
- }
115
-
116
- const paths = this.getClientPaths();
117
- const configPath = paths[client];
118
-
119
- if (!configPath) {
120
- throw new Error(`Unsupported client: ${client}`);
121
- }
122
-
123
- try {
124
- const configContent = await readFile(configPath, 'utf-8');
125
- const config: ClaudeConfig | CursorConfig = JSON.parse(configContent);
126
-
127
- if (config.mcpServers && config.mcpServers[slug]) {
128
- delete config.mcpServers[slug];
129
- await writeFile(configPath, JSON.stringify(config, null, 2));
130
- return true;
131
- }
132
-
133
- return false;
134
- } catch (error) {
135
- Logger.error(`Failed to remove from ${client}: ${error}`);
136
- return false;
137
- }
138
- }
139
-
140
- static async removeFromAllClients(slug: string): Promise<string[]> {
141
- const clients = await this.detectInstalledClients();
142
- const removed: string[] = [];
143
-
144
- for (const client of clients) {
145
- if (!client.exists) continue;
146
-
147
- try {
148
- const success = await this.removeFromClient(client.name as SupportedClient, slug);
149
- if (success) {
150
- removed.push(client.name);
151
- }
152
- } catch (error) {
153
- Logger.warning(`Failed to remove from ${client.name}: ${error}`);
154
- }
155
- }
156
-
157
- return removed;
158
- }
159
-
160
- static resolveUrl(urlOrSlug: string): string {
161
- // If it's already a full URL, return as-is
162
- if (urlOrSlug.startsWith('http://') || urlOrSlug.startsWith('https://')) {
163
- return urlOrSlug;
164
- }
165
-
166
- // Otherwise, assume it's a slug and construct the mcphost.dev URL
167
- return `https://${urlOrSlug}.mcphost.dev`;
168
- }
169
-
170
- static extractSlug(url: string): string {
171
- // Extract slug from URLs like https://myserver.mcphost.dev
172
- if (url.includes('.mcphost.dev')) {
173
- const match = url.match(/https?:\/\/([^.]+)\.mcphost\.dev/);
174
- if (match) {
175
- return match[1];
176
- }
177
- }
178
-
179
- // For other URLs, use the hostname or a hash of the URL
180
- try {
181
- const parsed = new URL(url);
182
- return parsed.hostname.replace(/\./g, '-');
183
- } catch {
184
- // If URL parsing fails, create a simple hash
185
- return url.replace(/[^a-zA-Z0-9]/g, '-').slice(0, 20);
186
- }
187
- }
188
- }
package/src/lib/config.ts DELETED
@@ -1,90 +0,0 @@
1
- import Conf from 'conf';
2
- import { MCPConfig, MCPConnection } from '../types.js';
3
-
4
- export class Config {
5
- private conf: Conf<MCPConfig>;
6
- private connectionsConf: Conf<{ connections: MCPConnection[] }>;
7
-
8
- constructor() {
9
- this.conf = new Conf<MCPConfig>({
10
- projectName: 'mcphosting',
11
- defaults: {}
12
- });
13
-
14
- this.connectionsConf = new Conf<{ connections: MCPConnection[] }>({
15
- projectName: 'mcphosting',
16
- configName: 'connections',
17
- defaults: { connections: [] }
18
- });
19
- }
20
-
21
- get token(): string | undefined {
22
- return this.conf.get('token');
23
- }
24
-
25
- set token(value: string | undefined) {
26
- if (value) {
27
- this.conf.set('token', value);
28
- } else {
29
- this.conf.delete('token');
30
- }
31
- }
32
-
33
- get user() {
34
- return this.conf.get('user');
35
- }
36
-
37
- set user(value: { email: string; org?: string } | undefined) {
38
- if (value) {
39
- this.conf.set('user', value);
40
- } else {
41
- this.conf.delete('user');
42
- }
43
- }
44
-
45
- get connections(): MCPConnection[] {
46
- return this.connectionsConf.get('connections', []);
47
- }
48
-
49
- addConnection(connection: Omit<MCPConnection, 'id' | 'addedAt'>): MCPConnection {
50
- const connections = this.connections;
51
- const newConnection: MCPConnection = {
52
- ...connection,
53
- id: Math.random().toString(36).substr(2, 9),
54
- addedAt: Date.now()
55
- };
56
-
57
- connections.push(newConnection);
58
- this.connectionsConf.set('connections', connections);
59
- return newConnection;
60
- }
61
-
62
- removeConnection(id: string): boolean {
63
- const connections = this.connections;
64
- const index = connections.findIndex(c => c.id === id || c.slug === id);
65
- if (index === -1) return false;
66
-
67
- connections.splice(index, 1);
68
- this.connectionsConf.set('connections', connections);
69
- return true;
70
- }
71
-
72
- findConnection(slugOrId: string): MCPConnection | undefined {
73
- return this.connections.find(c => c.id === slugOrId || c.slug === slugOrId);
74
- }
75
-
76
- updateConnection(id: string, updates: Partial<MCPConnection>): boolean {
77
- const connections = this.connections;
78
- const index = connections.findIndex(c => c.id === id);
79
- if (index === -1) return false;
80
-
81
- connections[index] = { ...connections[index], ...updates };
82
- this.connectionsConf.set('connections', connections);
83
- return true;
84
- }
85
-
86
- clear(): void {
87
- this.conf.clear();
88
- this.connectionsConf.clear();
89
- }
90
- }
package/src/lib/logger.ts DELETED
@@ -1,63 +0,0 @@
1
- import chalk from 'chalk';
2
- import ora, { Ora } from 'ora';
3
-
4
- export class Logger {
5
- static info(message: string): void {
6
- console.log(chalk.blue('ℹ'), message);
7
- }
8
-
9
- static success(message: string): void {
10
- console.log(chalk.green('✓'), message);
11
- }
12
-
13
- static warning(message: string): void {
14
- console.log(chalk.yellow('⚠'), message);
15
- }
16
-
17
- static error(message: string): void {
18
- console.error(chalk.red('✗'), message);
19
- }
20
-
21
- static dim(message: string): void {
22
- console.log(chalk.dim(message));
23
- }
24
-
25
- static bold(message: string): void {
26
- console.log(chalk.bold(message));
27
- }
28
-
29
- static spinner(text: string): Ora {
30
- return ora(text).start();
31
- }
32
-
33
- static table(data: Array<Record<string, any>>, headers?: string[]): void {
34
- if (data.length === 0) {
35
- Logger.dim('No data to display');
36
- return;
37
- }
38
-
39
- const keys = headers || Object.keys(data[0]);
40
- const maxWidths = keys.map(key =>
41
- Math.max(key.length, ...data.map(row => String(row[key] || '').length))
42
- );
43
-
44
- // Header
45
- const headerRow = keys.map((key, i) =>
46
- chalk.bold(key.padEnd(maxWidths[i]))
47
- ).join(' | ');
48
- console.log(headerRow);
49
- console.log(keys.map((_, i) => '-'.repeat(maxWidths[i])).join('-|-'));
50
-
51
- // Data rows
52
- data.forEach(row => {
53
- const dataRow = keys.map((key, i) =>
54
- String(row[key] || '').padEnd(maxWidths[i])
55
- ).join(' | ');
56
- console.log(dataRow);
57
- });
58
- }
59
-
60
- static json(data: any): void {
61
- console.log(JSON.stringify(data, null, 2));
62
- }
63
- }
package/src/types.ts DELETED
@@ -1,47 +0,0 @@
1
- export interface MCPConfig {
2
- token?: string;
3
- user?: {
4
- email: string;
5
- org?: string;
6
- };
7
- }
8
-
9
- export interface MCPConnection {
10
- id: string;
11
- slug: string;
12
- url: string;
13
- clients: string[];
14
- addedAt: number;
15
- }
16
-
17
- export interface ClientConfig {
18
- name: string;
19
- configPath: string;
20
- exists: boolean;
21
- }
22
-
23
- export interface MCPServer {
24
- slug: string;
25
- name: string;
26
- description: string;
27
- url?: string;
28
- tools: string[];
29
- installs: number;
30
- author: string;
31
- }
32
-
33
- export interface MCPServerEntry {
34
- command: string;
35
- args: string[];
36
- env: Record<string, string>;
37
- }
38
-
39
- export interface ClaudeConfig {
40
- mcpServers: Record<string, MCPServerEntry>;
41
- }
42
-
43
- export interface CursorConfig {
44
- mcpServers: Record<string, MCPServerEntry>;
45
- }
46
-
47
- export type SupportedClient = 'claude' | 'cursor' | 'vscode' | 'openclaw' | 'chatgpt';
package/tsconfig.json DELETED
@@ -1,26 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "node",
6
- "allowSyntheticDefaultImports": true,
7
- "esModuleInterop": true,
8
- "allowJs": true,
9
- "outDir": "./dist",
10
- "rootDir": "./src",
11
- "strict": true,
12
- "moduleDetection": "force",
13
- "skipLibCheck": true,
14
- "forceConsistentCasingInFileNames": true,
15
- "resolveJsonModule": true,
16
- "isolatedModules": true,
17
- "verbatimModuleSyntax": false
18
- },
19
- "include": [
20
- "src/**/*"
21
- ],
22
- "exclude": [
23
- "node_modules",
24
- "dist"
25
- ]
26
- }
package/tsup.config.ts DELETED
@@ -1,14 +0,0 @@
1
- import { defineConfig } from 'tsup';
2
-
3
- export default defineConfig({
4
- entry: ['src/index.ts'],
5
- format: ['esm', 'cjs'],
6
- dts: true,
7
- splitting: false,
8
- sourcemap: true,
9
- clean: true,
10
- shims: true,
11
- banner: {
12
- js: '#!/usr/bin/env node'
13
- }
14
- });