@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 +245 -0
- package/dist/adapters/claude-code.js +48 -0
- package/dist/adapters/copilot-cli.js +49 -0
- package/dist/adapters/index.js +14 -0
- package/dist/adapters/windsurf.js +59 -0
- package/dist/api-client.js +88 -0
- package/dist/cli.js +94 -0
- package/dist/commands/add.js +525 -0
- package/dist/commands/enable-disable.js +72 -0
- package/dist/commands/init.js +26 -0
- package/dist/commands/list.js +65 -0
- package/dist/commands/market.js +84 -0
- package/dist/commands/platforms.js +71 -0
- package/dist/commands/remove.js +60 -0
- package/dist/commands/search.js +86 -0
- package/dist/commands/sync.js +84 -0
- package/dist/commands/update.js +34 -0
- package/dist/config.js +34 -0
- package/dist/package-selection.js +35 -0
- package/dist/pagination.js +57 -0
- package/dist/transform.js +78 -0
- package/dist/types.js +1 -0
- package/dist/utils.js +33 -0
- package/package.json +42 -0
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();
|