add-mcp 1.0.1 → 1.2.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/README.md +17 -12
- package/dist/index.js +126 -21
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Add MCP servers to your favorite coding agents with a single command.
|
|
4
4
|
|
|
5
|
-
Supports **Claude Code**, **Codex**, **Cursor**, **OpenCode**, **VSCode** and [
|
|
5
|
+
Supports **Claude Code**, **Codex**, **Cursor**, **OpenCode**, **VSCode** and [5 more](#supported-agents).
|
|
6
6
|
|
|
7
7
|
## Install an MCP Server
|
|
8
8
|
|
|
@@ -54,6 +54,9 @@ npx add-mcp mcp-server-github --all
|
|
|
54
54
|
|
|
55
55
|
# Install to all agents, globally, without prompts
|
|
56
56
|
npx add-mcp mcp-server-github --all -g -y
|
|
57
|
+
|
|
58
|
+
# Add generated config files to .gitignore
|
|
59
|
+
npx add-mcp https://mcp.example.com/mcp -a cursor -y --gitignore
|
|
57
60
|
```
|
|
58
61
|
|
|
59
62
|
### Options
|
|
@@ -68,6 +71,7 @@ npx add-mcp mcp-server-github --all -g -y
|
|
|
68
71
|
| `-n, --name <name>` | Server name (auto-inferred if not provided) |
|
|
69
72
|
| `-y, --yes` | Skip all confirmation prompts |
|
|
70
73
|
| `--all` | Install to all agents |
|
|
74
|
+
| `--gitignore` | Add generated config files to `.gitignore` |
|
|
71
75
|
|
|
72
76
|
### Additional Commands
|
|
73
77
|
|
|
@@ -135,17 +139,18 @@ Agents that do not support headers: Goose.
|
|
|
135
139
|
|
|
136
140
|
MCP servers can be installed to any of these agents:
|
|
137
141
|
|
|
138
|
-
| Agent
|
|
139
|
-
|
|
|
140
|
-
| Claude Code
|
|
141
|
-
| Claude Desktop
|
|
142
|
-
| Codex
|
|
143
|
-
| Cursor
|
|
144
|
-
| Gemini CLI
|
|
145
|
-
| Goose
|
|
146
|
-
|
|
|
147
|
-
|
|
|
148
|
-
|
|
|
142
|
+
| Agent | `--agent` | Project Path | Global Path |
|
|
143
|
+
| ------------------ | -------------------- | ----------------------- | ----------------------------------------------------------------- |
|
|
144
|
+
| Claude Code | `claude-code` | `.mcp.json` | `~/.claude.json` |
|
|
145
|
+
| Claude Desktop | `claude-desktop` | - | `~/Library/Application Support/Claude/claude_desktop_config.json` |
|
|
146
|
+
| Codex | `codex` | `.codex/config.toml` | `~/.codex/config.toml` |
|
|
147
|
+
| Cursor | `cursor` | `.cursor/mcp.json` | `~/.cursor/mcp.json` |
|
|
148
|
+
| Gemini CLI | `gemini-cli` | `.gemini/settings.json` | `~/.gemini/settings.json` |
|
|
149
|
+
| Goose | `goose` | `.goose/config.yaml` | `~/.config/goose/config.yaml` |
|
|
150
|
+
| GitHub Copilot CLI | `github-copilot-cli` | `.vscode/mcp.json` | `~/.copilot/mcp-config.json` |
|
|
151
|
+
| OpenCode | `opencode` | `opencode.json` | `~/.config/opencode/opencode.json` |
|
|
152
|
+
| VS Code | `vscode` | `.vscode/mcp.json` | `~/Library/Application Support/Code/User/mcp.json` |
|
|
153
|
+
| Zed | `zed` | `.zed/settings.json` | `~/Library/Application Support/Zed/settings.json` |
|
|
149
154
|
|
|
150
155
|
**Aliases:** `gemini` → `gemini-cli`, `github-copilot` → `vscode`
|
|
151
156
|
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ var agentAliases = {
|
|
|
15
15
|
// src/agents.ts
|
|
16
16
|
import * as p from "@clack/prompts";
|
|
17
17
|
import { homedir as homedir2 } from "os";
|
|
18
|
-
import { join as join2 } from "path";
|
|
18
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
19
19
|
import { existsSync } from "fs";
|
|
20
20
|
|
|
21
21
|
// src/mcp-lock.ts
|
|
@@ -98,6 +98,10 @@ function getPlatformPaths() {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
var { appSupport, vscodePath, gooseConfigPath } = getPlatformPaths();
|
|
101
|
+
var copilotConfigPath = join2(
|
|
102
|
+
process.env.XDG_CONFIG_HOME || join2(home, ".copilot"),
|
|
103
|
+
"mcp-config.json"
|
|
104
|
+
);
|
|
101
105
|
function transformGooseConfig(serverName, config) {
|
|
102
106
|
if (config.url) {
|
|
103
107
|
const gooseType = config.type === "sse" ? "sse" : "streamable_http";
|
|
@@ -180,6 +184,32 @@ function transformCursorConfig(_serverName, config) {
|
|
|
180
184
|
}
|
|
181
185
|
return config;
|
|
182
186
|
}
|
|
187
|
+
function transformGitHubCopilotCliConfig(_serverName, config, context) {
|
|
188
|
+
if (context?.local) {
|
|
189
|
+
return config;
|
|
190
|
+
}
|
|
191
|
+
if (config.url) {
|
|
192
|
+
const remoteConfig = {
|
|
193
|
+
type: config.type || "http",
|
|
194
|
+
url: config.url,
|
|
195
|
+
tools: ["*"]
|
|
196
|
+
};
|
|
197
|
+
if (config.headers && Object.keys(config.headers).length > 0) {
|
|
198
|
+
remoteConfig.headers = config.headers;
|
|
199
|
+
}
|
|
200
|
+
return remoteConfig;
|
|
201
|
+
}
|
|
202
|
+
const localConfig = {
|
|
203
|
+
type: "stdio",
|
|
204
|
+
command: config.command,
|
|
205
|
+
args: config.args || [],
|
|
206
|
+
tools: ["*"]
|
|
207
|
+
};
|
|
208
|
+
if (config.env && Object.keys(config.env).length > 0) {
|
|
209
|
+
localConfig.env = config.env;
|
|
210
|
+
}
|
|
211
|
+
return localConfig;
|
|
212
|
+
}
|
|
183
213
|
var agents = {
|
|
184
214
|
"claude-code": {
|
|
185
215
|
name: "claude-code",
|
|
@@ -271,6 +301,22 @@ var agents = {
|
|
|
271
301
|
},
|
|
272
302
|
transformConfig: transformGooseConfig
|
|
273
303
|
},
|
|
304
|
+
"github-copilot-cli": {
|
|
305
|
+
name: "github-copilot-cli",
|
|
306
|
+
displayName: "GitHub Copilot CLI",
|
|
307
|
+
configPath: copilotConfigPath,
|
|
308
|
+
localConfigPath: ".vscode/mcp.json",
|
|
309
|
+
projectDetectPaths: [".vscode"],
|
|
310
|
+
configKey: "mcpServers",
|
|
311
|
+
localConfigKey: "servers",
|
|
312
|
+
format: "json",
|
|
313
|
+
supportedTransports: ["stdio", "http", "sse"],
|
|
314
|
+
supportsHeaders: true,
|
|
315
|
+
detectGlobalInstall: async () => {
|
|
316
|
+
return existsSync(dirname2(copilotConfigPath));
|
|
317
|
+
},
|
|
318
|
+
transformConfig: transformGitHubCopilotCliConfig
|
|
319
|
+
},
|
|
274
320
|
opencode: {
|
|
275
321
|
name: "opencode",
|
|
276
322
|
displayName: "OpenCode",
|
|
@@ -596,12 +642,12 @@ function isRemoteSource(parsed) {
|
|
|
596
642
|
}
|
|
597
643
|
|
|
598
644
|
// src/installer.ts
|
|
599
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync4 } from "fs";
|
|
600
|
-
import { join as join3, dirname as
|
|
645
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
646
|
+
import { join as join3, dirname as dirname6, isAbsolute, relative, sep } from "path";
|
|
601
647
|
|
|
602
648
|
// src/formats/json.ts
|
|
603
649
|
import { readFileSync, writeFileSync, existsSync as existsSync2, mkdirSync } from "fs";
|
|
604
|
-
import { dirname as
|
|
650
|
+
import { dirname as dirname3 } from "path";
|
|
605
651
|
import * as jsonc from "jsonc-parser";
|
|
606
652
|
|
|
607
653
|
// src/formats/utils.ts
|
|
@@ -652,7 +698,7 @@ function detectIndent(text) {
|
|
|
652
698
|
return result || { tabSize: 2, insertSpaces: true };
|
|
653
699
|
}
|
|
654
700
|
function writeJsonConfig(filePath, config, configKey) {
|
|
655
|
-
const dir =
|
|
701
|
+
const dir = dirname3(filePath);
|
|
656
702
|
if (!existsSync2(dir)) {
|
|
657
703
|
mkdirSync(dir, { recursive: true });
|
|
658
704
|
}
|
|
@@ -694,7 +740,7 @@ function setNestedValue(obj, path, value) {
|
|
|
694
740
|
|
|
695
741
|
// src/formats/yaml.ts
|
|
696
742
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
697
|
-
import { dirname as
|
|
743
|
+
import { dirname as dirname4 } from "path";
|
|
698
744
|
import yaml from "js-yaml";
|
|
699
745
|
function readYamlConfig(filePath) {
|
|
700
746
|
if (!existsSync3(filePath)) {
|
|
@@ -705,7 +751,7 @@ function readYamlConfig(filePath) {
|
|
|
705
751
|
return parsed || {};
|
|
706
752
|
}
|
|
707
753
|
function writeYamlConfig(filePath, config) {
|
|
708
|
-
const dir =
|
|
754
|
+
const dir = dirname4(filePath);
|
|
709
755
|
if (!existsSync3(dir)) {
|
|
710
756
|
mkdirSync2(dir, { recursive: true });
|
|
711
757
|
}
|
|
@@ -724,7 +770,7 @@ function writeYamlConfig(filePath, config) {
|
|
|
724
770
|
|
|
725
771
|
// src/formats/toml.ts
|
|
726
772
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
727
|
-
import { dirname as
|
|
773
|
+
import { dirname as dirname5 } from "path";
|
|
728
774
|
import * as TOML from "@iarna/toml";
|
|
729
775
|
function readTomlConfig(filePath) {
|
|
730
776
|
if (!existsSync4(filePath)) {
|
|
@@ -735,7 +781,7 @@ function readTomlConfig(filePath) {
|
|
|
735
781
|
return parsed;
|
|
736
782
|
}
|
|
737
783
|
function writeTomlConfig(filePath, config) {
|
|
738
|
-
const dir =
|
|
784
|
+
const dir = dirname5(filePath);
|
|
739
785
|
if (!existsSync4(dir)) {
|
|
740
786
|
mkdirSync3(dir, { recursive: true });
|
|
741
787
|
}
|
|
@@ -798,6 +844,41 @@ function buildServerConfig(parsed, options = {}) {
|
|
|
798
844
|
args: ["-y", parsed.value]
|
|
799
845
|
};
|
|
800
846
|
}
|
|
847
|
+
function updateGitignoreWithPaths(paths, options = {}) {
|
|
848
|
+
const cwd = options.cwd || process.cwd();
|
|
849
|
+
const gitignorePath = join3(cwd, ".gitignore");
|
|
850
|
+
const existingContent = existsSync5(gitignorePath) ? readFileSync4(gitignorePath, "utf-8") : "";
|
|
851
|
+
const existingEntries = new Set(
|
|
852
|
+
existingContent.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0)
|
|
853
|
+
);
|
|
854
|
+
const entriesToAdd = [];
|
|
855
|
+
for (const filePath of paths) {
|
|
856
|
+
const relativePath = isAbsolute(filePath) ? relative(cwd, filePath) : filePath;
|
|
857
|
+
if (!relativePath || relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
const normalizedPath = relativePath.split(sep).join("/");
|
|
861
|
+
const cleanPath = normalizedPath.startsWith("./") ? normalizedPath.slice(2) : normalizedPath;
|
|
862
|
+
if (!cleanPath || cleanPath === ".gitignore" || existingEntries.has(cleanPath)) {
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
existingEntries.add(cleanPath);
|
|
866
|
+
entriesToAdd.push(cleanPath);
|
|
867
|
+
}
|
|
868
|
+
if (entriesToAdd.length > 0) {
|
|
869
|
+
let nextContent = existingContent;
|
|
870
|
+
if (nextContent.length > 0 && !nextContent.endsWith("\n")) {
|
|
871
|
+
nextContent += "\n";
|
|
872
|
+
}
|
|
873
|
+
nextContent += `${entriesToAdd.join("\n")}
|
|
874
|
+
`;
|
|
875
|
+
writeFileSync4(gitignorePath, nextContent, "utf-8");
|
|
876
|
+
}
|
|
877
|
+
return {
|
|
878
|
+
path: gitignorePath,
|
|
879
|
+
added: entriesToAdd
|
|
880
|
+
};
|
|
881
|
+
}
|
|
801
882
|
function getConfigPath(agent, options = {}) {
|
|
802
883
|
if (options.local && agent.localConfigPath) {
|
|
803
884
|
const cwd = options.cwd || process.cwd();
|
|
@@ -805,21 +886,26 @@ function getConfigPath(agent, options = {}) {
|
|
|
805
886
|
}
|
|
806
887
|
return agent.configPath;
|
|
807
888
|
}
|
|
889
|
+
function getConfigKey(agent, options = {}) {
|
|
890
|
+
if (options.local && agent.localConfigKey) {
|
|
891
|
+
return agent.localConfigKey;
|
|
892
|
+
}
|
|
893
|
+
return agent.configKey;
|
|
894
|
+
}
|
|
808
895
|
function installServerForAgent(serverName, serverConfig, agentType, options = {}) {
|
|
809
896
|
const agent = agents[agentType];
|
|
810
897
|
const configPath = getConfigPath(agent, options);
|
|
811
898
|
try {
|
|
812
|
-
const dir =
|
|
899
|
+
const dir = dirname6(configPath);
|
|
813
900
|
if (!existsSync5(dir)) {
|
|
814
901
|
mkdirSync4(dir, { recursive: true });
|
|
815
902
|
}
|
|
816
|
-
const transformedConfig = agent.transformConfig ? agent.transformConfig(serverName, serverConfig
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
);
|
|
822
|
-
writeConfig(configPath, config, agent.format, agent.configKey);
|
|
903
|
+
const transformedConfig = agent.transformConfig ? agent.transformConfig(serverName, serverConfig, {
|
|
904
|
+
local: Boolean(options.local)
|
|
905
|
+
}) : serverConfig;
|
|
906
|
+
const configKey = getConfigKey(agent, options);
|
|
907
|
+
const config = buildConfigWithKey(configKey, serverName, transformedConfig);
|
|
908
|
+
writeConfig(configPath, config, agent.format, configKey);
|
|
823
909
|
return {
|
|
824
910
|
success: true,
|
|
825
911
|
path: configPath
|
|
@@ -854,7 +940,7 @@ function installServer(serverName, serverConfig, agentTypes, options = {}) {
|
|
|
854
940
|
// package.json
|
|
855
941
|
var package_default = {
|
|
856
942
|
name: "add-mcp",
|
|
857
|
-
version: "1.0
|
|
943
|
+
version: "1.2.0",
|
|
858
944
|
description: "Add MCP servers to your favorite coding agents with a single command.",
|
|
859
945
|
author: "Andre Landgraf <andre@neon.tech>",
|
|
860
946
|
license: "Apache-2.0",
|
|
@@ -870,9 +956,9 @@ var package_default = {
|
|
|
870
956
|
fmt: "prettier --write .",
|
|
871
957
|
build: "tsup src/index.ts --format esm --dts --clean",
|
|
872
958
|
dev: "tsx src/index.ts",
|
|
873
|
-
test: "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/mcp-lock.test.ts && tsx tests/installer.test.ts && tsx tests/e2e/install.test.ts",
|
|
959
|
+
test: "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/mcp-lock.test.ts && tsx tests/installer.test.ts && tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
|
|
874
960
|
"test:unit": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/mcp-lock.test.ts && tsx tests/installer.test.ts",
|
|
875
|
-
"test:e2e": "tsx tests/e2e/install.test.ts",
|
|
961
|
+
"test:e2e": "tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
|
|
876
962
|
typecheck: "tsc --noEmit",
|
|
877
963
|
prepublishOnly: "npm run build"
|
|
878
964
|
},
|
|
@@ -885,6 +971,7 @@ var package_default = {
|
|
|
885
971
|
"claude-desktop",
|
|
886
972
|
"codex",
|
|
887
973
|
"cursor",
|
|
974
|
+
"github-copilot-cli",
|
|
888
975
|
"gemini-cli",
|
|
889
976
|
"goose",
|
|
890
977
|
"opencode",
|
|
@@ -1002,7 +1089,10 @@ program.name("add-mcp").description(
|
|
|
1002
1089
|
"HTTP header for remote servers (repeatable, 'Key: Value')",
|
|
1003
1090
|
collect,
|
|
1004
1091
|
[]
|
|
1005
|
-
).option("-y, --yes", "Skip confirmation prompts").option("--all", "Install to all agents").
|
|
1092
|
+
).option("-y, --yes", "Skip confirmation prompts").option("--all", "Install to all agents").option(
|
|
1093
|
+
"--gitignore",
|
|
1094
|
+
"Add generated project config files to .gitignore"
|
|
1095
|
+
).action(async (target, options) => {
|
|
1006
1096
|
await main(target, options);
|
|
1007
1097
|
});
|
|
1008
1098
|
program.command("list-agents").description("List all supported coding agents").action(() => {
|
|
@@ -1411,6 +1501,21 @@ async function main(target, options) {
|
|
|
1411
1501
|
);
|
|
1412
1502
|
}
|
|
1413
1503
|
}
|
|
1504
|
+
if (options.gitignore && options.global) {
|
|
1505
|
+
p2.log.warn(
|
|
1506
|
+
"--gitignore is only supported for project-scoped installations; ignoring."
|
|
1507
|
+
);
|
|
1508
|
+
} else if (options.gitignore) {
|
|
1509
|
+
const successfulPaths = successful.map(([_, result]) => result.path);
|
|
1510
|
+
const gitignoreUpdate = updateGitignoreWithPaths(successfulPaths);
|
|
1511
|
+
if (gitignoreUpdate.added.length > 0) {
|
|
1512
|
+
p2.log.info(
|
|
1513
|
+
`Added ${gitignoreUpdate.added.length} entr${gitignoreUpdate.added.length === 1 ? "y" : "ies"} to .gitignore`
|
|
1514
|
+
);
|
|
1515
|
+
} else {
|
|
1516
|
+
p2.log.info("No new local config paths to add to .gitignore");
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1414
1519
|
console.log();
|
|
1415
1520
|
p2.outro(chalk.green("Done!"));
|
|
1416
1521
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "add-mcp",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Add MCP servers to your favorite coding agents with a single command.",
|
|
5
5
|
"author": "Andre Landgraf <andre@neon.tech>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"fmt": "prettier --write .",
|
|
17
17
|
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
18
18
|
"dev": "tsx src/index.ts",
|
|
19
|
-
"test": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/mcp-lock.test.ts && tsx tests/installer.test.ts && tsx tests/e2e/install.test.ts",
|
|
19
|
+
"test": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/mcp-lock.test.ts && tsx tests/installer.test.ts && tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
|
|
20
20
|
"test:unit": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/mcp-lock.test.ts && tsx tests/installer.test.ts",
|
|
21
|
-
"test:e2e": "tsx tests/e2e/install.test.ts",
|
|
21
|
+
"test:e2e": "tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
|
|
22
22
|
"typecheck": "tsc --noEmit",
|
|
23
23
|
"prepublishOnly": "npm run build"
|
|
24
24
|
},
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"claude-desktop",
|
|
32
32
|
"codex",
|
|
33
33
|
"cursor",
|
|
34
|
+
"github-copilot-cli",
|
|
34
35
|
"gemini-cli",
|
|
35
36
|
"goose",
|
|
36
37
|
"opencode",
|