@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.
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import type { McpConfig } from './config.js';
3
+ type Props = {
4
+ writableConfigs: McpConfig[];
5
+ };
6
+ export default function AddServerApp({ writableConfigs }: Props): React.JSX.Element;
7
+ export {};
@@ -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
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ type McpConfig = {
3
+ name: string;
4
+ path: string;
5
+ found: boolean;
6
+ };
7
+ type Props = {
8
+ mcpConfigs: McpConfig[];
9
+ };
10
+ export default function App({ mcpConfigs }: Props): React.JSX.Element;
11
+ export {};
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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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
+ }
@@ -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