@yash_pandit/mcpconfig 1.0.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/dist/add-server-app.d.ts +7 -0
- package/dist/add-server-app.js +166 -0
- package/dist/app.d.ts +11 -0
- package/dist/app.js +18 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +106 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.js +58 -0
- package/dist/servers-app.d.ts +16 -0
- package/dist/servers-app.js +34 -0
- package/package.json +80 -0
- package/readme.md +148 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import React, { useState, useCallback, useMemo } from 'react';
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import TextInput from 'ink-text-input';
|
|
4
|
+
import { addServerToConfig } from './config.js';
|
|
5
|
+
const SELECT_ALL_INDEX = 0;
|
|
6
|
+
const INDICES_OFFSET = 1; // first row is "Select all"
|
|
7
|
+
function parseArgsInput(input) {
|
|
8
|
+
if (!input.trim())
|
|
9
|
+
return [];
|
|
10
|
+
return input.split(',').map((s) => s.trim()).filter(Boolean);
|
|
11
|
+
}
|
|
12
|
+
export default function AddServerApp({ writableConfigs }) {
|
|
13
|
+
const [step, setStep] = useState('app');
|
|
14
|
+
const [selectedPaths, setSelectedPaths] = useState(new Set());
|
|
15
|
+
const [cursorIndex, setCursorIndex] = useState(0);
|
|
16
|
+
const [name, setName] = useState('');
|
|
17
|
+
const [command, setCommand] = useState('');
|
|
18
|
+
const [argsInput, setArgsInput] = useState('');
|
|
19
|
+
const [errorMessage, setErrorMessage] = useState(null);
|
|
20
|
+
const maxIndex = writableConfigs.length; // 0 = Select all, 1..n = configs
|
|
21
|
+
const selectedConfigs = useMemo(() => writableConfigs.filter((c) => selectedPaths.has(c.path)), [writableConfigs, selectedPaths]);
|
|
22
|
+
const allSelected = writableConfigs.length > 0 && selectedPaths.size === writableConfigs.length;
|
|
23
|
+
useInput((input, key) => {
|
|
24
|
+
if (step !== 'app')
|
|
25
|
+
return;
|
|
26
|
+
if (key.downArrow) {
|
|
27
|
+
setCursorIndex((i) => (i < maxIndex ? i + 1 : i));
|
|
28
|
+
}
|
|
29
|
+
else if (key.upArrow) {
|
|
30
|
+
setCursorIndex((i) => (i > 0 ? i - 1 : i));
|
|
31
|
+
}
|
|
32
|
+
else if (input === ' ') {
|
|
33
|
+
if (cursorIndex === SELECT_ALL_INDEX) {
|
|
34
|
+
setSelectedPaths((prev) => {
|
|
35
|
+
if (prev.size === writableConfigs.length)
|
|
36
|
+
return new Set();
|
|
37
|
+
return new Set(writableConfigs.map((c) => c.path));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const config = writableConfigs[cursorIndex - INDICES_OFFSET];
|
|
42
|
+
if (config) {
|
|
43
|
+
setSelectedPaths((prev) => {
|
|
44
|
+
const next = new Set(prev);
|
|
45
|
+
if (next.has(config.path))
|
|
46
|
+
next.delete(config.path);
|
|
47
|
+
else
|
|
48
|
+
next.add(config.path);
|
|
49
|
+
return next;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else if (key.return && selectedConfigs.length > 0) {
|
|
55
|
+
setStep('name');
|
|
56
|
+
}
|
|
57
|
+
}, { isActive: step === 'app' });
|
|
58
|
+
const handleNameSubmit = useCallback((value) => {
|
|
59
|
+
const trimmed = value.trim();
|
|
60
|
+
if (!trimmed)
|
|
61
|
+
return;
|
|
62
|
+
setName(trimmed);
|
|
63
|
+
setStep('command');
|
|
64
|
+
}, []);
|
|
65
|
+
const handleCommandSubmit = useCallback((value) => {
|
|
66
|
+
const trimmed = value.trim();
|
|
67
|
+
if (!trimmed)
|
|
68
|
+
return;
|
|
69
|
+
setCommand(trimmed);
|
|
70
|
+
setStep('args');
|
|
71
|
+
}, []);
|
|
72
|
+
const handleArgsSubmit = useCallback((value) => {
|
|
73
|
+
if (selectedConfigs.length === 0)
|
|
74
|
+
return;
|
|
75
|
+
const args = parseArgsInput(value);
|
|
76
|
+
const server = { name, command, args: args.length > 0 ? args : undefined };
|
|
77
|
+
try {
|
|
78
|
+
for (const config of selectedConfigs) {
|
|
79
|
+
addServerToConfig(config.path, server);
|
|
80
|
+
}
|
|
81
|
+
setStep('done');
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
setErrorMessage(err instanceof Error ? err.message : String(err));
|
|
85
|
+
setStep('error');
|
|
86
|
+
}
|
|
87
|
+
}, [selectedConfigs, name, command]);
|
|
88
|
+
if (writableConfigs.length === 0) {
|
|
89
|
+
return (React.createElement(Box, { flexDirection: "column", paddingY: 1 },
|
|
90
|
+
React.createElement(Text, { color: "yellow" }, "No writable MCP configs found."),
|
|
91
|
+
React.createElement(Text, { dimColor: true }, "Run `mcpconfig list-configs` to see which configs exist. Add-server supports JSON and Codex TOML configs.")));
|
|
92
|
+
}
|
|
93
|
+
if (step === 'app') {
|
|
94
|
+
return (React.createElement(Box, { flexDirection: "column", paddingY: 1 },
|
|
95
|
+
React.createElement(Text, { bold: true }, "Select apps to add the server to (Space to toggle, Enter to confirm)"),
|
|
96
|
+
React.createElement(Text, { dimColor: true }, "\u2191/\u2193 move \u00B7 Space toggle \u00B7 Enter continue"),
|
|
97
|
+
React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
98
|
+
React.createElement(Box, null,
|
|
99
|
+
React.createElement(Text, { color: cursorIndex === SELECT_ALL_INDEX ? 'cyan' : undefined },
|
|
100
|
+
cursorIndex === SELECT_ALL_INDEX ? '❯ ' : ' ',
|
|
101
|
+
allSelected ? '◉' : '○',
|
|
102
|
+
" Select all")),
|
|
103
|
+
writableConfigs.map((c, i) => {
|
|
104
|
+
const rowIndex = i + INDICES_OFFSET;
|
|
105
|
+
const isSelected = selectedPaths.has(c.path);
|
|
106
|
+
const isFocused = cursorIndex === rowIndex;
|
|
107
|
+
return (React.createElement(Box, { key: c.path },
|
|
108
|
+
React.createElement(Text, { color: isFocused ? 'cyan' : undefined },
|
|
109
|
+
isFocused ? '❯ ' : ' ',
|
|
110
|
+
isSelected ? '◉' : '○',
|
|
111
|
+
" ",
|
|
112
|
+
c.name),
|
|
113
|
+
React.createElement(Text, { dimColor: true },
|
|
114
|
+
" (",
|
|
115
|
+
c.path,
|
|
116
|
+
")")));
|
|
117
|
+
})),
|
|
118
|
+
selectedConfigs.length > 0 && (React.createElement(Box, { marginTop: 1 },
|
|
119
|
+
React.createElement(Text, { dimColor: true },
|
|
120
|
+
selectedConfigs.length,
|
|
121
|
+
" selected \u2014 press Enter to continue")))));
|
|
122
|
+
}
|
|
123
|
+
if (step === 'name') {
|
|
124
|
+
return (React.createElement(Box, { flexDirection: "column", paddingY: 1 },
|
|
125
|
+
React.createElement(Text, { bold: true }, "Server name"),
|
|
126
|
+
React.createElement(Text, { dimColor: true }, "Unique key for this MCP server (e.g. my-mcp-server)"),
|
|
127
|
+
React.createElement(Box, { marginTop: 1 },
|
|
128
|
+
React.createElement(TextInput, { value: name, onChange: setName, onSubmit: handleNameSubmit, placeholder: "server-name", focus: true }))));
|
|
129
|
+
}
|
|
130
|
+
if (step === 'command') {
|
|
131
|
+
return (React.createElement(Box, { flexDirection: "column", paddingY: 1 },
|
|
132
|
+
React.createElement(Text, { bold: true }, "Command"),
|
|
133
|
+
React.createElement(Text, { dimColor: true }, "Executable to run (e.g. npx, python, node)"),
|
|
134
|
+
React.createElement(Box, { marginTop: 1 },
|
|
135
|
+
React.createElement(TextInput, { value: command, onChange: setCommand, onSubmit: handleCommandSubmit, placeholder: "npx", focus: true }))));
|
|
136
|
+
}
|
|
137
|
+
if (step === 'args') {
|
|
138
|
+
return (React.createElement(Box, { flexDirection: "column", paddingY: 1 },
|
|
139
|
+
React.createElement(Text, { bold: true }, "Arguments (comma-separated)"),
|
|
140
|
+
React.createElement(Text, { dimColor: true }, "Optional: args passed to the command (e.g. -y, /path/to/script.js)"),
|
|
141
|
+
React.createElement(Box, { marginTop: 1 },
|
|
142
|
+
React.createElement(TextInput, { value: argsInput, onChange: setArgsInput, onSubmit: handleArgsSubmit, placeholder: "arg1, arg2", focus: true }))));
|
|
143
|
+
}
|
|
144
|
+
if (step === 'error') {
|
|
145
|
+
return (React.createElement(Box, { flexDirection: "column", paddingY: 1 },
|
|
146
|
+
React.createElement(Text, { color: "red", bold: true }, "Error"),
|
|
147
|
+
React.createElement(Text, { color: "red" }, errorMessage)));
|
|
148
|
+
}
|
|
149
|
+
// step === 'done'
|
|
150
|
+
return (React.createElement(Box, { flexDirection: "column", paddingY: 1 },
|
|
151
|
+
React.createElement(Text, { color: "green", bold: true },
|
|
152
|
+
"\u2713 Server added to ",
|
|
153
|
+
selectedConfigs.length,
|
|
154
|
+
" config(s)"),
|
|
155
|
+
React.createElement(Text, { dimColor: true },
|
|
156
|
+
" Name: ",
|
|
157
|
+
name),
|
|
158
|
+
React.createElement(Text, { dimColor: true },
|
|
159
|
+
" Command: ",
|
|
160
|
+
command),
|
|
161
|
+
selectedConfigs.map((c) => (React.createElement(Text, { key: c.path, dimColor: true },
|
|
162
|
+
" \u00B7 ",
|
|
163
|
+
c.name,
|
|
164
|
+
": ",
|
|
165
|
+
c.path)))));
|
|
166
|
+
}
|
package/dist/app.d.ts
ADDED
package/dist/app.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text, Box } from 'ink';
|
|
3
|
+
const CHECK = '✓';
|
|
4
|
+
const CROSS = '✗';
|
|
5
|
+
export default function App({ mcpConfigs = [] }) {
|
|
6
|
+
const nameWidth = Math.max(14, ...mcpConfigs.map((c) => c.name.length));
|
|
7
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 0, marginTop: 1 },
|
|
8
|
+
React.createElement(Text, { bold: true, dimColor: true }, "MCP configs"),
|
|
9
|
+
React.createElement(Box, { flexDirection: "column", marginTop: 1 }, mcpConfigs.map((config) => (React.createElement(Box, { key: config.path, flexDirection: "column", marginBottom: 1 },
|
|
10
|
+
React.createElement(Box, null,
|
|
11
|
+
React.createElement(Text, null,
|
|
12
|
+
config.name.padEnd(nameWidth),
|
|
13
|
+
" "),
|
|
14
|
+
React.createElement(Text, { color: config.found ? 'green' : 'red', bold: config.found }, config.found ? CHECK : CROSS)),
|
|
15
|
+
config.found && (React.createElement(Text, { dimColor: true },
|
|
16
|
+
" ",
|
|
17
|
+
config.path))))))));
|
|
18
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { render } from 'ink';
|
|
4
|
+
import meow from 'meow';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import TOML from '@iarna/toml';
|
|
9
|
+
import App from './app.js';
|
|
10
|
+
import ServersApp from './servers-app.js';
|
|
11
|
+
import AddServerApp from './add-server-app.js';
|
|
12
|
+
import { getMcpConfigs, getWritableConfigs } from './config.js';
|
|
13
|
+
function parseServersFromConfig(configPath, raw) {
|
|
14
|
+
const ext = path.extname(configPath).toLowerCase();
|
|
15
|
+
if (ext === '.toml') {
|
|
16
|
+
// Codex config.toml: [mcp_servers.<name>] with command and args
|
|
17
|
+
// https://developers.openai.com/codex/config-reference/
|
|
18
|
+
try {
|
|
19
|
+
const data = TOML.parse(raw);
|
|
20
|
+
const mcpServers = data.mcp_servers ?? {};
|
|
21
|
+
return Object.entries(mcpServers).map(([name, entry]) => ({
|
|
22
|
+
name,
|
|
23
|
+
command: typeof entry?.command === 'string' ? entry.command : undefined,
|
|
24
|
+
args: Array.isArray(entry?.args) ? entry.args : undefined,
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// JSON: mcpServers object (Claude Desktop, Cursor, Cline, etc.)
|
|
32
|
+
try {
|
|
33
|
+
const data = JSON.parse(raw);
|
|
34
|
+
const mcpServers = data.mcpServers ?? {};
|
|
35
|
+
return Object.entries(mcpServers).map(([name, entry]) => ({
|
|
36
|
+
name,
|
|
37
|
+
command: typeof entry?.command === 'string' ? entry.command : undefined,
|
|
38
|
+
args: Array.isArray(entry?.args) ? entry.args : undefined,
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const cli = meow(`
|
|
46
|
+
Usage
|
|
47
|
+
$ mcpconfig <command> [options]
|
|
48
|
+
|
|
49
|
+
Commands
|
|
50
|
+
list-configs List MCP configs (found only, unless --all)
|
|
51
|
+
list-servers List MCP servers from found JSON and Codex TOML configs
|
|
52
|
+
add-server Interactively add an MCP server to a config (JSON or Codex TOML)
|
|
53
|
+
|
|
54
|
+
Options
|
|
55
|
+
--all (list-configs only) Show all configs (found and not found)
|
|
56
|
+
|
|
57
|
+
Examples
|
|
58
|
+
$ mcpconfig list-configs
|
|
59
|
+
$ mcpconfig list-configs --all
|
|
60
|
+
$ mcpconfig list-servers
|
|
61
|
+
$ mcpconfig add-server
|
|
62
|
+
`, {
|
|
63
|
+
importMeta: import.meta,
|
|
64
|
+
flags: {
|
|
65
|
+
all: {
|
|
66
|
+
type: 'boolean',
|
|
67
|
+
shortFlag: 'a',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
const [command] = cli.input;
|
|
72
|
+
if (!command) {
|
|
73
|
+
cli.showHelp(0);
|
|
74
|
+
}
|
|
75
|
+
const allowedCommands = ['list-configs', 'list-servers', 'add-server'];
|
|
76
|
+
if (!allowedCommands.includes(command)) {
|
|
77
|
+
console.error(`Unknown command: ${command}`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
const home = os.homedir();
|
|
81
|
+
const mcpConfigs = getMcpConfigs(home);
|
|
82
|
+
const configsToShow = cli.flags.all ? mcpConfigs : mcpConfigs.filter((c) => c.found);
|
|
83
|
+
if (command === 'add-server') {
|
|
84
|
+
const writableConfigs = getWritableConfigs(home);
|
|
85
|
+
render(React.createElement(AddServerApp, { writableConfigs: writableConfigs }));
|
|
86
|
+
}
|
|
87
|
+
else if (command === 'list-servers') {
|
|
88
|
+
// Configs we can extract servers from: JSON (mcpServers) or Codex TOML (mcp_servers)
|
|
89
|
+
const configsWithServers = mcpConfigs.filter((c) => c.found &&
|
|
90
|
+
(c.path.endsWith('.json') || (c.path.endsWith('.toml') && c.path.includes('.codex'))));
|
|
91
|
+
const serverLists = configsWithServers.map((config) => {
|
|
92
|
+
let servers = [];
|
|
93
|
+
try {
|
|
94
|
+
const raw = fs.readFileSync(config.path, 'utf8');
|
|
95
|
+
servers = parseServersFromConfig(config.path, raw);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// File read or parse error: leave servers empty
|
|
99
|
+
}
|
|
100
|
+
return { appName: config.name, configPath: config.path, servers };
|
|
101
|
+
});
|
|
102
|
+
render(React.createElement(ServersApp, { serverLists: serverLists }));
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
render(React.createElement(App, { mcpConfigs: configsToShow }));
|
|
106
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type McpConfig = {
|
|
2
|
+
name: string;
|
|
3
|
+
path: string;
|
|
4
|
+
found: boolean;
|
|
5
|
+
};
|
|
6
|
+
export type ServerEntry = {
|
|
7
|
+
name: string;
|
|
8
|
+
command?: string;
|
|
9
|
+
args?: string[];
|
|
10
|
+
};
|
|
11
|
+
export declare function getMcpConfigs(home?: string): McpConfig[];
|
|
12
|
+
/** Configs we can add servers to: found and (JSON or Codex TOML). */
|
|
13
|
+
export declare function getWritableConfigs(home?: string): McpConfig[];
|
|
14
|
+
/** Add a server to a config file. Handles JSON (mcpServers) and Codex TOML (mcp_servers). */
|
|
15
|
+
export declare function addServerToConfig(configPath: string, server: ServerEntry): void;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import TOML from '@iarna/toml';
|
|
5
|
+
const MCP_CONFIGS = [
|
|
6
|
+
{ segments: ['Library', 'Application Support', 'Claude'], filename: 'claude_desktop_config.json', name: 'Claude Desktop' },
|
|
7
|
+
{ segments: ['.codex'], filename: 'config.toml', name: 'OpenAI Codex' },
|
|
8
|
+
{ segments: ['.cursor'], filename: 'mcp.json', name: 'Cursor' },
|
|
9
|
+
{ segments: ['.cline', 'data', 'settings'], filename: 'cline_mcp_settings.json', name: 'Cline' },
|
|
10
|
+
{ segments: ['Library', 'Application Support', 'Code', 'User', 'globalStorage', 'rooveterinaryinc.roo-cline', 'settings'], filename: 'mcp_settings.json', name: 'Roo Code' },
|
|
11
|
+
{ segments: ['.codeium', 'windsurf'], filename: 'mcp_config.json', name: 'Windsurf' },
|
|
12
|
+
{ segments: ['.config', 'zed'], filename: 'settings.json', name: 'Zed' },
|
|
13
|
+
{ segments: ['.config', 'cody'], filename: 'mcp_servers.json', name: 'Cody' },
|
|
14
|
+
{ segments: ['.config', 'goose'], filename: 'config.yaml', name: 'Goose' },
|
|
15
|
+
{ segments: ['.continue'], filename: 'config.yaml', name: 'Continue.dev' },
|
|
16
|
+
{ segments: ['.junie', 'mcp'], filename: 'mcp.json', name: 'Junie CLI' },
|
|
17
|
+
];
|
|
18
|
+
export function getMcpConfigs(home = os.homedir()) {
|
|
19
|
+
return MCP_CONFIGS.map(({ segments, filename, name }) => {
|
|
20
|
+
const fullPath = path.join(home, ...segments, filename);
|
|
21
|
+
return { name, path: fullPath, found: fs.existsSync(fullPath) };
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/** Configs we can add servers to: found and (JSON or Codex TOML). */
|
|
25
|
+
export function getWritableConfigs(home = os.homedir()) {
|
|
26
|
+
const all = getMcpConfigs(home);
|
|
27
|
+
return all.filter((c) => c.found &&
|
|
28
|
+
(c.path.endsWith('.json') || (c.path.endsWith('.toml') && c.path.includes('.codex'))));
|
|
29
|
+
}
|
|
30
|
+
/** Add a server to a config file. Handles JSON (mcpServers) and Codex TOML (mcp_servers). */
|
|
31
|
+
export function addServerToConfig(configPath, server) {
|
|
32
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
33
|
+
const ext = path.extname(configPath).toLowerCase();
|
|
34
|
+
if (ext === '.toml') {
|
|
35
|
+
const data = TOML.parse(raw);
|
|
36
|
+
if (!data['mcp_servers'] || typeof data['mcp_servers'] !== 'object') {
|
|
37
|
+
data['mcp_servers'] = {};
|
|
38
|
+
}
|
|
39
|
+
const mcpServers = data['mcp_servers'];
|
|
40
|
+
mcpServers[server.name] = {
|
|
41
|
+
command: server.command ?? '',
|
|
42
|
+
args: server.args?.length ? server.args : undefined,
|
|
43
|
+
};
|
|
44
|
+
fs.writeFileSync(configPath, TOML.stringify(data), 'utf8');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// JSON
|
|
48
|
+
const data = JSON.parse(raw);
|
|
49
|
+
if (!data['mcpServers'] || typeof data['mcpServers'] !== 'object') {
|
|
50
|
+
data['mcpServers'] = {};
|
|
51
|
+
}
|
|
52
|
+
const mcpServers = data['mcpServers'];
|
|
53
|
+
mcpServers[server.name] = {
|
|
54
|
+
command: server.command ?? '',
|
|
55
|
+
...(server.args?.length ? { args: server.args } : {}),
|
|
56
|
+
};
|
|
57
|
+
fs.writeFileSync(configPath, JSON.stringify(data, null, 2), 'utf8');
|
|
58
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type ServerEntry = {
|
|
3
|
+
name: string;
|
|
4
|
+
command?: string;
|
|
5
|
+
args?: string[];
|
|
6
|
+
};
|
|
7
|
+
export type ServerList = {
|
|
8
|
+
appName: string;
|
|
9
|
+
configPath: string;
|
|
10
|
+
servers: ServerEntry[];
|
|
11
|
+
};
|
|
12
|
+
type Props = {
|
|
13
|
+
serverLists: ServerList[];
|
|
14
|
+
};
|
|
15
|
+
export default function ServersApp({ serverLists }: Props): React.JSX.Element;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text, Box } from 'ink';
|
|
3
|
+
function formatArgs(server) {
|
|
4
|
+
if (!Array.isArray(server.args) || server.args.length === 0)
|
|
5
|
+
return '—';
|
|
6
|
+
return server.args.join(' ');
|
|
7
|
+
}
|
|
8
|
+
export default function ServersApp({ serverLists = [] }) {
|
|
9
|
+
const rows = [];
|
|
10
|
+
for (const { appName, servers } of serverLists) {
|
|
11
|
+
for (const server of servers) {
|
|
12
|
+
rows.push({
|
|
13
|
+
app: appName,
|
|
14
|
+
server: server.name,
|
|
15
|
+
command: server.command ?? '—',
|
|
16
|
+
args: formatArgs(server),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (rows.length === 0) {
|
|
21
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 0, marginTop: 1 },
|
|
22
|
+
React.createElement(Text, { dimColor: true }, "No MCP servers in found configs.")));
|
|
23
|
+
}
|
|
24
|
+
const appWidth = Math.max(14, ...rows.map((r) => r.app.length));
|
|
25
|
+
const serverWidth = Math.max(6, ...rows.map((r) => r.server.length));
|
|
26
|
+
const commandWidth = Math.max(7, ...rows.map((r) => r.command.length));
|
|
27
|
+
const header = `${'APP'.padEnd(appWidth + 1)}${'SERVER'.padEnd(serverWidth + 1)}${'COMMAND'.padEnd(commandWidth + 1)}ARGS`;
|
|
28
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 0, marginTop: 1 },
|
|
29
|
+
React.createElement(Text, { bold: true, dimColor: true }, header),
|
|
30
|
+
rows.map((row, i) => {
|
|
31
|
+
const line = `${row.app.padEnd(appWidth + 1)}${row.server.padEnd(serverWidth + 1)}${row.command.padEnd(commandWidth + 1)}${row.args}`;
|
|
32
|
+
return React.createElement(Text, { key: `${row.app}-${row.server}-${i}` }, line);
|
|
33
|
+
})));
|
|
34
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yash_pandit/mcpconfig",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI to discover and inspect MCP (Model Context Protocol) configs and servers across Claude Desktop, Cursor, Codex, Cline, and more",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/YashPandit4u/mcpconfig.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/YashPandit4u/mcpconfig#readme",
|
|
11
|
+
"bugs": "https://github.com/YashPandit4u/mcpconfig/issues",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"mcp",
|
|
14
|
+
"model-context-protocol",
|
|
15
|
+
"claude",
|
|
16
|
+
"cursor",
|
|
17
|
+
"codex",
|
|
18
|
+
"cline",
|
|
19
|
+
"cli"
|
|
20
|
+
],
|
|
21
|
+
"bin": {
|
|
22
|
+
"mcpconfig": "dist/cli.js"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=16"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc",
|
|
30
|
+
"dev": "tsc --watch",
|
|
31
|
+
"start": "node dist/cli.js",
|
|
32
|
+
"prepublishOnly": "npm run build",
|
|
33
|
+
"test": "prettier --check . && xo && ava"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"readme.md"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@iarna/toml": "^2.2.5",
|
|
41
|
+
"ink": "^4.1.0",
|
|
42
|
+
"ink-select-input": "^5.0.0",
|
|
43
|
+
"ink-text-input": "^5.0.1",
|
|
44
|
+
"meow": "^11.0.0",
|
|
45
|
+
"react": "^18.2.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@sindresorhus/tsconfig": "^3.0.1",
|
|
49
|
+
"@types/node": "^25.3.5",
|
|
50
|
+
"@types/react": "^18.0.32",
|
|
51
|
+
"@vdemedes/prettier-config": "^2.0.1",
|
|
52
|
+
"ava": "^5.2.0",
|
|
53
|
+
"chalk": "^5.2.0",
|
|
54
|
+
"eslint-config-xo-react": "^0.27.0",
|
|
55
|
+
"eslint-plugin-react": "^7.32.2",
|
|
56
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
57
|
+
"ink-testing-library": "^3.0.0",
|
|
58
|
+
"prettier": "^2.8.7",
|
|
59
|
+
"ts-node": "^10.9.1",
|
|
60
|
+
"typescript": "^5.0.3",
|
|
61
|
+
"xo": "^0.53.1"
|
|
62
|
+
},
|
|
63
|
+
"ava": {
|
|
64
|
+
"extensions": {
|
|
65
|
+
"ts": "module",
|
|
66
|
+
"tsx": "module"
|
|
67
|
+
},
|
|
68
|
+
"nodeArguments": [
|
|
69
|
+
"--loader=ts-node/esm"
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
"xo": {
|
|
73
|
+
"extends": "xo-react",
|
|
74
|
+
"prettier": true,
|
|
75
|
+
"rules": {
|
|
76
|
+
"react/prop-types": "off"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"prettier": "@vdemedes/prettier-config"
|
|
80
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# mcpconfig
|
|
2
|
+
|
|
3
|
+
**A CLI to discover and inspect MCP (Model Context Protocol) configs and servers across your AI coding tools.**
|
|
4
|
+
|
|
5
|
+
If you use Claude Desktop, Cursor, Codex, Cline, or similar apps, each one stores its MCP configuration in a different place. **mcpconfig** finds those config files and lists them—and can show every MCP server (name, command, args) from all of them in one view.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What is MCP?
|
|
10
|
+
|
|
11
|
+
The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) is a standard that lets AI assistants connect to external tools and data sources (files, databases, APIs). Many AI coding apps support MCP via a config file where you declare “servers” (each with a command and optional args). mcpconfig does not run or manage those servers; it only **lists where configs live** and **what servers are defined** in them.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **List config locations** — See which MCP config files exist on your system and their full paths. Optionally show all known locations (including ones that don’t exist yet).
|
|
18
|
+
- **List servers across configs** — For every found JSON config and Codex TOML, show a combined table of app name, server name, command, and args so you can compare setups at a glance.
|
|
19
|
+
- **Add a server interactively** — Run `add-server` to select one or more apps (multi-select with “Select all”), then enter server name, command, and args; mcpconfig writes to every selected config (JSON or Codex TOML) per each app’s spec.
|
|
20
|
+
- **Multi-app support** — Knows the default config paths for Claude Desktop, Cursor, Codex, Cline, Roo Code, Windsurf, Zed, Cody, Goose, Continue.dev, and Junie CLI.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
- **Node.js** 16 or newer
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
**Global install** (recommended if you want the `mcpconfig` command everywhere):
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install --global mcpconfig
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Run without installing** (e.g. try it once or use in scripts):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx mcpconfig list-configs
|
|
42
|
+
npx mcpconfig list-servers
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**From source** (clone the repo, then):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm install
|
|
49
|
+
npm run build
|
|
50
|
+
npm link
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
After `npm link`, `mcpconfig` is available globally from your dev build. You can also run the built CLI with `npm start` or `node dist/cli.js`.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
mcpconfig <command> [options]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Commands
|
|
64
|
+
|
|
65
|
+
| Command | Description |
|
|
66
|
+
|----------------|-------------|
|
|
67
|
+
| `list-configs` | List MCP config file locations. By default shows only configs that were **found** on disk. |
|
|
68
|
+
| `list-servers` | Read all found **JSON** configs and **Codex** `config.toml`, extract MCP servers (`mcpServers` or `mcp_servers`), and print a table: app name, server name, command, args. |
|
|
69
|
+
| `add-server` | Interactive flow: select one or more apps (↑/↓ move, Space toggle, “Select all” option, Enter to confirm), then enter server name, command, and args. Writes to every selected config (JSON or Codex TOML) per spec. |
|
|
70
|
+
|
|
71
|
+
### Options
|
|
72
|
+
|
|
73
|
+
| Option | Short | Description |
|
|
74
|
+
|-----------|-------|-------------|
|
|
75
|
+
| `--all` | `-a` | **Only for `list-configs`.** Show every known config location (found and not found). |
|
|
76
|
+
|
|
77
|
+
### Examples
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# See which MCP configs exist and where they are
|
|
81
|
+
mcpconfig list-configs
|
|
82
|
+
|
|
83
|
+
# See all known config locations (including missing ones)
|
|
84
|
+
mcpconfig list-configs --all
|
|
85
|
+
|
|
86
|
+
# See all MCP servers from every found JSON config in one table
|
|
87
|
+
mcpconfig list-servers
|
|
88
|
+
|
|
89
|
+
# Interactively add an MCP server to a config (choose app, then name, command, args)
|
|
90
|
+
mcpconfig add-server
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**`list-configs`** prints one line per config: app name, ✓ or ✗ (found or not), and the full path when found.
|
|
94
|
+
|
|
95
|
+
**`list-servers`** prints a table with columns: **APP**, **SERVER**, **COMMAND**, **ARGS**. JSON configs use the `mcpServers` key; Codex uses `~/.codex/config.toml` and the `[mcp_servers.<name>]` tables per the [Codex config reference](https://developers.openai.com/codex/config-reference/). Other TOML/YAML configs are not parsed for servers.
|
|
96
|
+
|
|
97
|
+
**`add-server`** shows only configs that exist and support adding servers (JSON and Codex TOML). You can select multiple apps: use **↑/↓** to move, **Space** to toggle each app (or “Select all”), then **Enter** to continue. You’re then prompted for **name**, **command**, and **args** (comma-separated). The new server is written to every selected config; JSON gets a `mcpServers` entry and Codex TOML gets an `[mcp_servers.<name>]` section.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Supported apps and config paths
|
|
102
|
+
|
|
103
|
+
mcpconfig looks for these files under your home directory:
|
|
104
|
+
|
|
105
|
+
| App | Config path (relative to `~`) |
|
|
106
|
+
|------------------|-------------------------------|
|
|
107
|
+
| Claude Desktop | `Library/Application Support/Claude/claude_desktop_config.json` |
|
|
108
|
+
| OpenAI Codex | `.codex/config.toml` |
|
|
109
|
+
| Cursor | `.cursor/mcp.json` |
|
|
110
|
+
| Cline | `.cline/data/settings/cline_mcp_settings.json` |
|
|
111
|
+
| Roo Code | `Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json` |
|
|
112
|
+
| Windsurf | `.codeium/windsurf/mcp_config.json` |
|
|
113
|
+
| Zed | `.config/zed/settings.json` |
|
|
114
|
+
| Cody | `.config/cody/mcp_servers.json` |
|
|
115
|
+
| Goose | `.config/goose/config.yaml` |
|
|
116
|
+
| Continue.dev | `.continue/config.yaml` |
|
|
117
|
+
| Junie CLI | `.junie/mcp/mcp.json` |
|
|
118
|
+
|
|
119
|
+
Only **JSON** configs are used for `list-servers`; the tool expects an `mcpServers` object in them. TOML/YAML apps are listed by `list-configs` but not parsed for servers.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Development
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Install dependencies
|
|
127
|
+
npm install
|
|
128
|
+
|
|
129
|
+
# Build TypeScript → dist/
|
|
130
|
+
npm run build
|
|
131
|
+
|
|
132
|
+
# Watch mode (rebuild on file changes)
|
|
133
|
+
npm run dev
|
|
134
|
+
|
|
135
|
+
# Run the CLI from the built output
|
|
136
|
+
npm start
|
|
137
|
+
# or
|
|
138
|
+
npx mcpconfig list-configs
|
|
139
|
+
|
|
140
|
+
# Lint and test
|
|
141
|
+
npm test
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT
|