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.
Files changed (3) hide show
  1. package/README.md +17 -12
  2. package/dist/index.js +126 -21
  3. 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 [4 more](#supported-agents).
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 | `--agent` | Project Path | Global Path |
139
- | -------------- | ---------------- | ----------------------- | ----------------------------------------------------------------- |
140
- | Claude Code | `claude-code` | `.mcp.json` | `~/.claude.json` |
141
- | Claude Desktop | `claude-desktop` | - | `~/Library/Application Support/Claude/claude_desktop_config.json` |
142
- | Codex | `codex` | `.codex/config.toml` | `~/.codex/config.toml` |
143
- | Cursor | `cursor` | `.cursor/mcp.json` | `~/.cursor/mcp.json` |
144
- | Gemini CLI | `gemini-cli` | `.gemini/settings.json` | `~/.gemini/settings.json` |
145
- | Goose | `goose` | `.goose/config.yaml` | `~/.config/goose/config.yaml` |
146
- | OpenCode | `opencode` | `opencode.json` | `~/.config/opencode/opencode.json` |
147
- | VS Code | `vscode` | `.vscode/mcp.json` | `~/Library/Application Support/Code/User/mcp.json` |
148
- | Zed | `zed` | `.zed/settings.json` | `~/Library/Application Support/Zed/settings.json` |
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 dirname5 } from "path";
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 dirname2 } from "path";
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 = dirname2(filePath);
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 dirname3 } from "path";
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 = dirname3(filePath);
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 dirname4 } from "path";
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 = dirname4(filePath);
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 = dirname5(configPath);
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) : serverConfig;
817
- const config = buildConfigWithKey(
818
- agent.configKey,
819
- serverName,
820
- transformedConfig
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.1",
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").action(async (target, options) => {
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.1",
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",