@evolvedqube/gmcp 0.1.0-alpha.5

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 ADDED
@@ -0,0 +1,245 @@
1
+ # gmcp - Global MCP Config Manager
2
+
3
+ <p align="center">
4
+ <img src="https://img.shields.io/badge/Tested%20on-macOS-2e7d32?style=for-the-badge&logo=apple&logoColor=white" alt="Tested on macOS"/>
5
+ <img src="https://img.shields.io/badge/Windows%20support-coming%20soon-bdbdbd?style=for-the-badge&logo=windows&logoColor=white" alt="Windows support coming soon"/>
6
+ </p>
7
+
8
+ Manage MCP (Model Context Protocol) server configurations across Claude Code, Copilot CLI, and Windsurf from a single command-line interface. Stop manually editing config files for each platform.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install -g gmcp
14
+ ```
15
+
16
+ ## Quick Start
17
+
18
+ ```bash
19
+ # First time setup - select your platforms
20
+ gmcp init
21
+
22
+ # Browse available servers
23
+ gmcp market
24
+
25
+ # Add a server to all configured platforms
26
+ gmcp add fetch
27
+
28
+ # See what's installed
29
+ gmcp list
30
+
31
+ # Remove a server
32
+ gmcp remove fetch
33
+
34
+ # Update gmcp
35
+ gmcp update
36
+ ```
37
+
38
+ ## Supported Platforms
39
+
40
+ - **Claude Code** (`~/.claude.json`)
41
+ - **Copilot CLI** (`~/.copilot/mcp-config.json`)
42
+ - **Windsurf** (`~/.codeium/windsurf/mcp_config.json`)
43
+
44
+ ## All Commands
45
+
46
+ ### `gmcp init`
47
+
48
+ Interactive setup to select which platforms you want to manage.
49
+
50
+ ```bash
51
+ gmcp init
52
+ ```
53
+
54
+ ### `gmcp platforms`
55
+
56
+ Display configured platforms and their detection status.
57
+
58
+ ```bash
59
+ gmcp platforms
60
+ ```
61
+
62
+ ### `gmcp market`
63
+
64
+ Browse all available MCP servers from the registry. Results paginate in groups of 20.
65
+
66
+ ```bash
67
+ gmcp market
68
+ ```
69
+
70
+ ### `gmcp search <query>`
71
+
72
+ Search for a server by name.
73
+
74
+ ```bash
75
+ gmcp search github
76
+ gmcp search filesystem
77
+ gmcp search database
78
+ ```
79
+
80
+ ### `gmcp add <name>`
81
+
82
+ Add a server to your configured platforms.
83
+
84
+ ```bash
85
+ # Add to all configured platforms
86
+ gmcp add fetch
87
+
88
+ # Add to specific platforms only
89
+ gmcp add github --only claude-code,windsurf
90
+ ```
91
+
92
+ If a server offers multiple runtimes (npm or Docker), you'll be prompted to choose:
93
+
94
+ ```
95
+ This server offers multiple runtimes:
96
+ 1. npm (via npx)
97
+ 2. Docker (via OCI)
98
+
99
+ Select runtime: _
100
+ ```
101
+
102
+ ### `gmcp add --manual`
103
+
104
+ Add a custom server that's not in the registry. You'll be prompted for the server details.
105
+
106
+ ```bash
107
+ gmcp add --manual
108
+ ```
109
+
110
+ Interactive flow:
111
+
112
+ ```
113
+ Server name: my-custom-server
114
+ Command: npx
115
+ Arguments: @myorg/custom-server
116
+ ```
117
+
118
+ This adds your custom server to all configured platforms. To target specific platforms:
119
+
120
+ ```bash
121
+ gmcp add --manual --only claude-code,windsurf
122
+ ```
123
+
124
+ **Use cases:**
125
+ - Internal company MCP servers
126
+ - Local development servers
127
+ - Servers from private registries
128
+ - Experimental or forked servers
129
+
130
+ ### `gmcp remove <name>`
131
+
132
+ Remove a server from your platforms (with confirmation).
133
+
134
+ ```bash
135
+ # Remove from all platforms
136
+ gmcp remove fetch
137
+
138
+ # Remove from specific platforms only
139
+ gmcp remove github --only claude-code
140
+ ```
141
+
142
+ ### `gmcp list`
143
+
144
+ View all installed servers and their status on each platform.
145
+
146
+ ```bash
147
+ gmcp list
148
+ ```
149
+
150
+ Output example:
151
+
152
+ ```
153
+ MCP Servers:
154
+
155
+ Server Claude Code Copilot CLI Windsurf
156
+ ─────────────────────────────────────────────────
157
+ fetch active active —
158
+ github active — active
159
+ filesystem disabled active active
160
+ ```
161
+
162
+ ### `gmcp sync`
163
+
164
+ Sync servers across platforms. Shows servers installed on some platforms but missing on others, then lets you selectively add them everywhere. Safe to run multiple times.
165
+
166
+ ```bash
167
+ gmcp sync
168
+ ```
169
+
170
+ ### `gmcp enable <name>` / `gmcp disable <name>`
171
+
172
+ Enable or disable a server on your platforms.
173
+
174
+ ```bash
175
+ gmcp enable fetch
176
+ gmcp disable github
177
+ ```
178
+
179
+ **Note:** Only Windsurf fully supports the disable flag. Other platforms will ignore it.
180
+
181
+ ### `gmcp update`
182
+
183
+ Update gmcp to the latest version.
184
+
185
+ ```bash
186
+ gmcp update
187
+ ```
188
+
189
+ ## Important: Server Names
190
+
191
+ Server names in the registry are automatically cleaned up for cross-platform compatibility:
192
+
193
+ - `io.github.upstash/context7` → `context7`
194
+ - `ai.smithery/example-server` → `example-server`
195
+ - `server.name` → `server-name`
196
+
197
+ Always use the cleaned name when removing or managing servers:
198
+
199
+ ```bash
200
+ gmcp add io.github.upstash/context7 # Adds as "context7"
201
+ gmcp list # Shows "context7"
202
+ gmcp remove context7 # Use the cleaned name
203
+ ```
204
+
205
+ ## Requirements
206
+
207
+ - **Network access required** for: `add`, `search`, `market` commands
208
+ - **Offline OK** for: `list`, `remove`, `sync`, `enable`, `disable` commands
209
+
210
+ gmcp fetches server data from the official MCP Registry API. Ensure you have an active internet connection for registry operations.
211
+
212
+ ## Supported Runtimes
213
+
214
+ Servers can run via:
215
+
216
+ - **npm (npx)** - Installed via npm registry
217
+ - **Docker** - Runs as OCI container
218
+
219
+ gmcp will prompt you to choose if a server supports both options.
220
+
221
+ ## Troubleshooting
222
+
223
+ ### Cannot reach the MCP Registry API
224
+
225
+ ```
226
+ Error: Cannot reach MCP Registry API. Check your internet connection.
227
+ ```
228
+
229
+ Ensure you have internet access and the API is reachable.
230
+
231
+ ### Registry API temporarily unavailable
232
+
233
+ ```
234
+ Error: Registry API is temporarily unavailable. Try again later.
235
+ ```
236
+
237
+ Try again in a few moments.
238
+
239
+ ### Works offline
240
+
241
+ Commands that don't need the registry (`list`, `remove`, `sync`, `enable`, `disable`) work without internet.
242
+
243
+ ## License
244
+
245
+ MIT
@@ -0,0 +1,48 @@
1
+ import * as fs from 'fs';
2
+ import { readJsonFile, writeJsonFile, expandHomeDir } from '../utils.js';
3
+ export class ClaudeCodeAdapter {
4
+ constructor() {
5
+ this.name = 'claude-code';
6
+ }
7
+ configPath() {
8
+ return expandHomeDir('~/.claude.json');
9
+ }
10
+ detect() {
11
+ return fs.existsSync(this.configPath());
12
+ }
13
+ readServers() {
14
+ const config = readJsonFile(this.configPath());
15
+ const servers = new Map();
16
+ if (config.mcpServers) {
17
+ for (const [name, entry] of Object.entries(config.mcpServers)) {
18
+ servers.set(name, entry);
19
+ }
20
+ }
21
+ return servers;
22
+ }
23
+ addServer(name, definition, env) {
24
+ const config = readJsonFile(this.configPath());
25
+ if (!config.mcpServers) {
26
+ config.mcpServers = {};
27
+ }
28
+ config.mcpServers[name] = {
29
+ command: definition.runtime,
30
+ args: definition.args,
31
+ ...(Object.keys(env).length > 0 && { env }),
32
+ };
33
+ writeJsonFile(this.configPath(), config);
34
+ }
35
+ removeServer(name) {
36
+ const config = readJsonFile(this.configPath());
37
+ if (config.mcpServers && config.mcpServers[name]) {
38
+ delete config.mcpServers[name];
39
+ writeJsonFile(this.configPath(), config);
40
+ }
41
+ }
42
+ enableServer(name) {
43
+ return null;
44
+ }
45
+ disableServer(name) {
46
+ return null;
47
+ }
48
+ }
@@ -0,0 +1,49 @@
1
+ import * as fs from 'fs';
2
+ import { readJsonFile, writeJsonFile, expandHomeDir } from '../utils.js';
3
+ export class CopilotCliAdapter {
4
+ constructor() {
5
+ this.name = 'copilot-cli';
6
+ }
7
+ configPath() {
8
+ return expandHomeDir('~/.copilot/mcp-config.json');
9
+ }
10
+ detect() {
11
+ return fs.existsSync(this.configPath());
12
+ }
13
+ readServers() {
14
+ const config = readJsonFile(this.configPath());
15
+ const servers = new Map();
16
+ if (config.mcpServers) {
17
+ for (const [name, entry] of Object.entries(config.mcpServers)) {
18
+ servers.set(name, entry);
19
+ }
20
+ }
21
+ return servers;
22
+ }
23
+ addServer(name, definition, env) {
24
+ const config = readJsonFile(this.configPath());
25
+ if (!config.mcpServers) {
26
+ config.mcpServers = {};
27
+ }
28
+ config.mcpServers[name] = {
29
+ type: 'stdio',
30
+ command: definition.runtime,
31
+ args: definition.args,
32
+ env: env,
33
+ };
34
+ writeJsonFile(this.configPath(), config);
35
+ }
36
+ removeServer(name) {
37
+ const config = readJsonFile(this.configPath());
38
+ if (config.mcpServers && config.mcpServers[name]) {
39
+ delete config.mcpServers[name];
40
+ writeJsonFile(this.configPath(), config);
41
+ }
42
+ }
43
+ enableServer(name) {
44
+ return null;
45
+ }
46
+ disableServer(name) {
47
+ return null;
48
+ }
49
+ }
@@ -0,0 +1,14 @@
1
+ import { ClaudeCodeAdapter } from './claude-code.js';
2
+ import { CopilotCliAdapter } from './copilot-cli.js';
3
+ import { WindsurfAdapter } from './windsurf.js';
4
+ export const adapters = {
5
+ 'claude-code': new ClaudeCodeAdapter(),
6
+ 'copilot-cli': new CopilotCliAdapter(),
7
+ 'windsurf': new WindsurfAdapter(),
8
+ };
9
+ export function getAdapter(platform) {
10
+ return adapters[platform];
11
+ }
12
+ export function getAllAdapters() {
13
+ return Object.values(adapters);
14
+ }
@@ -0,0 +1,59 @@
1
+ import * as fs from 'fs';
2
+ import { readJsonFile, writeJsonFile, expandHomeDir } from '../utils.js';
3
+ export class WindsurfAdapter {
4
+ constructor() {
5
+ this.name = 'windsurf';
6
+ }
7
+ configPath() {
8
+ return expandHomeDir('~/.codeium/windsurf/mcp_config.json');
9
+ }
10
+ detect() {
11
+ return fs.existsSync(this.configPath());
12
+ }
13
+ readServers() {
14
+ const config = readJsonFile(this.configPath());
15
+ const servers = new Map();
16
+ if (config.mcpServers) {
17
+ for (const [name, entry] of Object.entries(config.mcpServers)) {
18
+ servers.set(name, entry);
19
+ }
20
+ }
21
+ return servers;
22
+ }
23
+ addServer(name, definition, env) {
24
+ const config = readJsonFile(this.configPath());
25
+ if (!config.mcpServers) {
26
+ config.mcpServers = {};
27
+ }
28
+ config.mcpServers[name] = {
29
+ command: definition.runtime,
30
+ args: definition.args,
31
+ disabled: false,
32
+ env: env,
33
+ };
34
+ writeJsonFile(this.configPath(), config);
35
+ }
36
+ removeServer(name) {
37
+ const config = readJsonFile(this.configPath());
38
+ if (config.mcpServers && config.mcpServers[name]) {
39
+ delete config.mcpServers[name];
40
+ writeJsonFile(this.configPath(), config);
41
+ }
42
+ }
43
+ enableServer(name) {
44
+ const config = readJsonFile(this.configPath());
45
+ if (!config.mcpServers || !config.mcpServers[name]) {
46
+ return null;
47
+ }
48
+ config.mcpServers[name].disabled = false;
49
+ writeJsonFile(this.configPath(), config);
50
+ }
51
+ disableServer(name) {
52
+ const config = readJsonFile(this.configPath());
53
+ if (!config.mcpServers || !config.mcpServers[name]) {
54
+ return null;
55
+ }
56
+ config.mcpServers[name].disabled = true;
57
+ writeJsonFile(this.configPath(), config);
58
+ }
59
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * API client for the official MCP Registry
3
+ * https://registry.modelcontextprotocol.io
4
+ */
5
+ export const API_BASE_URL = 'https://registry.modelcontextprotocol.io/v0.1';
6
+ /**
7
+ * Fetch all servers from the MCP Registry API with pagination
8
+ */
9
+ export async function fetchServers() {
10
+ const servers = [];
11
+ let cursor = null;
12
+ do {
13
+ const url = cursor
14
+ ? `${API_BASE_URL}/servers?limit=100&cursor=${encodeURIComponent(cursor)}`
15
+ : `${API_BASE_URL}/servers?limit=100`;
16
+ try {
17
+ const response = await fetch(url);
18
+ if (!response.ok) {
19
+ if (response.status >= 500) {
20
+ throw new Error('Registry API is temporarily unavailable. Please try again later.\n' +
21
+ 'If the issue persists, check: https://status.modelcontextprotocol.io');
22
+ }
23
+ throw new Error(`Registry API returned status ${response.status}.\n` +
24
+ 'Please try again or check: https://status.modelcontextprotocol.io');
25
+ }
26
+ const data = await response.json();
27
+ // Extract servers from response
28
+ for (const item of data.servers) {
29
+ servers.push(item.server);
30
+ }
31
+ cursor = data.metadata.nextCursor || null;
32
+ }
33
+ catch (error) {
34
+ if (error instanceof TypeError && error.message.includes('fetch')) {
35
+ throw new Error('Cannot reach MCP Registry API. Check your internet connection.\n' +
36
+ 'Note: You can still use `gmcp list`, `gmcp remove`, and `gmcp sync` offline.');
37
+ }
38
+ if (error instanceof SyntaxError) {
39
+ throw new Error('Received invalid data from Registry API.\n' +
40
+ 'This may indicate an API issue. Please try again or report at:\n' +
41
+ 'https://github.com/modelcontextprotocol/registry');
42
+ }
43
+ throw error;
44
+ }
45
+ } while (cursor);
46
+ return servers;
47
+ }
48
+ /**
49
+ * Search servers by name using the API search endpoint
50
+ */
51
+ export async function searchServers(query) {
52
+ const servers = [];
53
+ let cursor = null;
54
+ do {
55
+ const url = cursor
56
+ ? `${API_BASE_URL}/servers?search=${encodeURIComponent(query)}&limit=100&cursor=${encodeURIComponent(cursor)}`
57
+ : `${API_BASE_URL}/servers?search=${encodeURIComponent(query)}&limit=100`;
58
+ try {
59
+ const response = await fetch(url);
60
+ if (!response.ok) {
61
+ if (response.status >= 500) {
62
+ throw new Error('Registry API is temporarily unavailable. Please try again later.\n' +
63
+ 'If the issue persists, check: https://status.modelcontextprotocol.io');
64
+ }
65
+ throw new Error(`Registry API returned status ${response.status}.\n` +
66
+ 'Please try again or check: https://status.modelcontextprotocol.io');
67
+ }
68
+ const data = await response.json();
69
+ for (const item of data.servers) {
70
+ servers.push(item.server);
71
+ }
72
+ cursor = data.metadata.nextCursor || null;
73
+ }
74
+ catch (error) {
75
+ if (error instanceof TypeError && error.message.includes('fetch')) {
76
+ throw new Error('Cannot reach MCP Registry API. Check your internet connection.\n' +
77
+ 'Note: You can still use `gmcp list`, `gmcp remove`, and `gmcp sync` offline.');
78
+ }
79
+ if (error instanceof SyntaxError) {
80
+ throw new Error('Received invalid data from Registry API.\n' +
81
+ 'This may indicate an API issue. Please try again or report at:\n' +
82
+ 'https://github.com/modelcontextprotocol/registry');
83
+ }
84
+ throw error;
85
+ }
86
+ } while (cursor);
87
+ return servers;
88
+ }
package/dist/cli.js ADDED
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { readFileSync } from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, join } from 'path';
6
+ import { isInitialized } from './config.js';
7
+ import { initCommand } from './commands/init.js';
8
+ import { addCommand } from './commands/add.js';
9
+ import { removeCommand } from './commands/remove.js';
10
+ import { listCommand } from './commands/list.js';
11
+ import { searchCommand } from './commands/search.js';
12
+ import { enableCommand, disableCommand } from './commands/enable-disable.js';
13
+ import { syncCommand } from './commands/sync.js';
14
+ import { updateCommand } from './commands/update.js';
15
+ import { platformsCommand } from './commands/platforms.js';
16
+ import { marketCommand } from './commands/market.js';
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = dirname(__filename);
19
+ const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
20
+ const program = new Command();
21
+ program
22
+ .name('gmcp')
23
+ .description('Global MCP config manager - manage MCP servers across platforms')
24
+ .version(packageJson.version);
25
+ function validateOS(commandName) {
26
+ if (commandName !== 'init') {
27
+ return;
28
+ }
29
+ if (process.platform !== 'darwin' && process.platform !== 'win32') {
30
+ console.error('Error: gmcp only supports macOS and Windows');
31
+ process.exit(1);
32
+ }
33
+ }
34
+ function requireInit(commandName) {
35
+ if (commandName === 'init' || commandName === 'update' || commandName === 'platforms') {
36
+ return;
37
+ }
38
+ if (!isInitialized()) {
39
+ console.error('No platforms configured. Run `gmcp init` first.');
40
+ process.exit(1);
41
+ }
42
+ }
43
+ program.hook('preAction', (_thisCommand, actionCommand) => {
44
+ validateOS(actionCommand.name());
45
+ requireInit(actionCommand.name());
46
+ });
47
+ program
48
+ .command('init')
49
+ .description('Select platforms to manage with gmcp')
50
+ .action(initCommand);
51
+ program
52
+ .command('add [name]')
53
+ .description('Add an MCP server from the API registry (npm/Docker packages). Prompts for package type if multiple options available.')
54
+ .option('--only <platforms>', 'Only add to specific platforms (comma-separated, e.g., "claude-code,windsurf")')
55
+ .option('--manual', 'Manually add a server (not from registry). Detects duplicates by command/args.')
56
+ .action(addCommand);
57
+ program
58
+ .command('remove <name>')
59
+ .description('Remove an MCP server from platforms')
60
+ .option('--only <platforms>', 'Only remove from specific platforms (comma-separated, e.g., "claude-code,windsurf")')
61
+ .action(removeCommand);
62
+ program
63
+ .command('list')
64
+ .description('List all MCP servers across platforms')
65
+ .action(listCommand);
66
+ program
67
+ .command('platforms')
68
+ .description('Display configured platforms and their detection status')
69
+ .action(platformsCommand);
70
+ program
71
+ .command('search <query>')
72
+ .description('Search MCP servers by name using the API. Shows local packages (npm/Docker) with interactive install.')
73
+ .action(searchCommand);
74
+ program
75
+ .command('market')
76
+ .description('Browse all MCP servers from the API registry (200+ servers). Paginated display with interactive install.')
77
+ .action(marketCommand);
78
+ program
79
+ .command('enable <name>')
80
+ .description('Enable an MCP server (Windsurf only)')
81
+ .action(enableCommand);
82
+ program
83
+ .command('disable <name>')
84
+ .description('Disable an MCP server (Windsurf only)')
85
+ .action(disableCommand);
86
+ program
87
+ .command('sync')
88
+ .description('Sync MCP servers across platforms')
89
+ .action(syncCommand);
90
+ program
91
+ .command('update')
92
+ .description('Update gmcp to the latest version')
93
+ .action(updateCommand);
94
+ program.parse();