add-mcp 0.1.0 → 0.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 +128 -94
  2. package/dist/index.js +242 -94
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -1,133 +1,167 @@
1
1
  # add-mcp
2
2
 
3
- Install MCP servers onto coding agents with a single command.
3
+ Add MCP servers to your favorite coding agents with a single command.
4
+
5
+ Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [5 more](#supported-agents).
6
+
7
+ ## Install an MCP Server
4
8
 
5
9
  ```bash
6
- npx add-mcp <target>
10
+ npx add-mcp https://mcp.example.com/sse
7
11
  ```
8
12
 
9
- Supports Opencode, Claude Code, Codex, Cursor, and [more](https://github.com/neondatabase/add-mcp/blob/main/README.md#supported-agents).
10
-
11
- ## Features
13
+ ### Source Formats
12
14
 
13
- - Install MCP servers to multiple AI coding agents at once
14
- - Supports both remote (HTTP/SSE) and local (stdio) MCP servers
15
- - Handles different config formats (JSON, YAML, TOML)
16
- - Supports project-level and global installation
15
+ ```bash
16
+ # Remote MCP server (HTTP streamable - default)
17
+ npx add-mcp https://mcp.example.com/mcp
17
18
 
18
- ## Usage
19
+ # Remote MCP server (SSE transport)
20
+ npx add-mcp https://mcp.example.com/sse --transport sse
19
21
 
20
- ### Remote MCP Server (HTTP)
22
+ # npm package (runs via npx)
23
+ npx add-mcp @modelcontextprotocol/server-postgres
21
24
 
22
- ```bash
23
- # Install a remote MCP server
24
- npx add-mcp https://mcp.example.com/api
25
+ # Full command with arguments
26
+ npx add-mcp "npx -y @org/mcp-server --flag value"
25
27
 
26
- # With custom name
27
- npx add-mcp https://mcp.example.com/api --name my-server
28
+ # Node.js script
29
+ npx add-mcp "node /path/to/server.js --port 3000"
28
30
  ```
29
31
 
30
- ### Local MCP Server (Package)
32
+ ### Options
33
+
34
+ | Option | Description |
35
+ | ------------------------ | ------------------------------------------------------------------------ |
36
+ | `-g, --global` | Install to user directory instead of project |
37
+ | `-a, --agent <agent>` | Target specific agents (e.g., `cursor`, `claude-code`). Can be repeated. |
38
+ | `-t, --transport <type>` | Transport type for remote servers: `http` (default), `sse` |
39
+ | `--type <type>` | Alias for `--transport` |
40
+ | `-n, --name <name>` | Server name (auto-inferred if not provided) |
41
+ | `-y, --yes` | Skip all confirmation prompts |
42
+ | `--all` | Install to all agents |
43
+
44
+ ### Examples
31
45
 
32
46
  ```bash
33
- # Install from npm package
34
- npx add-mcp @modelcontextprotocol/server-postgres
47
+ # Install to specific agents
48
+ npx add-mcp https://mcp.example.com/mcp -a cursor -a claude-code
35
49
 
36
- # Install with custom name
37
- npx add-mcp mcp-server-github --name github
38
- ```
50
+ # Install with SSE transport
51
+ npx add-mcp https://mcp.neon.tech/sse --transport sse
39
52
 
40
- ### Local MCP Server (Command)
53
+ # Install with custom server name
54
+ npx add-mcp @modelcontextprotocol/server-postgres --name postgres
41
55
 
42
- ```bash
43
- # Install with full command
44
- npx add-mcp "npx -y @org/mcp-server --flag value"
56
+ # Non-interactive installation (CI/CD friendly)
57
+ npx add-mcp https://mcp.example.com/mcp -g -a claude-code -y
45
58
 
46
- # Node.js script
47
- npx add-mcp "node /path/to/server.js --port 3000"
59
+ # Install to all agents
60
+ npx add-mcp mcp-server-github --all
61
+
62
+ # Install to all agents, globally, without prompts
63
+ npx add-mcp mcp-server-github --all -g -y
48
64
  ```
49
65
 
50
- ## Options
66
+ ### Installation Scope
51
67
 
52
- ```
53
- Usage: add-mcp [options] [target]
54
-
55
- Arguments:
56
- target MCP server URL (remote) or package name (local stdio)
57
-
58
- Options:
59
- -V, --version output the version number
60
- -g, --global Install globally (user-level) instead of project-level
61
- -a, --agent <agents...> Specify agents to install to
62
- -n, --name <name> Server name (auto-inferred from target if not provided)
63
- -y, --yes Skip confirmation prompts
64
- -l, --list List supported agents
65
- --all Install to all agents without prompts (implies -y -g)
66
- -h, --help display help for command
67
- ```
68
+ | Scope | Flag | Location | Use Case |
69
+ | ----------- | --------- | ----------------------- | --------------------------------------------- |
70
+ | **Project** | (default) | `.cursor/mcp.json` etc. | Committed with your project, shared with team |
71
+ | **Global** | `-g` | `~/.cursor/mcp.json` | Available across all projects |
68
72
 
69
- ## Supported Agents
73
+ ### Smart Detection
74
+
75
+ The CLI automatically detects agents based on your environment:
70
76
 
71
- <!-- AGENTS_TABLE_START -->
77
+ **Default (project mode):**
72
78
 
73
- | Agent | CLI Key | Format | Local Support |
74
- | -------------- | ---------------- | ------ | ------------- |
75
- | Claude Code | `claude-code` | JSON | Yes |
76
- | Claude Desktop | `claude-desktop` | JSON | No |
77
- | Codex | `codex` | TOML | No |
78
- | Cursor | `cursor` | JSON | Yes |
79
- | Gemini CLI | `gemini-cli` | JSON | Yes |
80
- | Goose | `goose` | YAML | No |
81
- | OpenCode | `opencode` | JSON | Yes |
82
- | VS Code | `vscode` | JSON | Yes |
83
- | Zed | `zed` | JSON | No |
79
+ - Detects project-level config files (`.cursor/`, `.vscode/`, `.mcp.json`, etc.)
80
+ - Also detects globally-installed agents that only support global config (Claude Desktop, Codex, Zed)
81
+ - Agents are routed appropriately: project-capable agents use project config, global-only agents use global config
84
82
 
85
- <!-- AGENTS_TABLE_END -->
83
+ **With `-g` (global mode):**
84
+
85
+ - Detects all globally-installed agents
86
+ - All agents use global config
87
+
88
+ **No agents detected:**
89
+
90
+ - Interactive mode: Shows error with guidance to use `--global` or run in a project
91
+ - With `--yes`: Installs to all project-capable agents
92
+
93
+ ## Transport Types
94
+
95
+ MCP supports different transport mechanisms for remote servers:
96
+
97
+ | Transport | Flag | Description |
98
+ | --------- | ------------------ | ---------------------------------------------- |
99
+ | **HTTP** | `--transport http` | Streamable HTTP (default, modern standard) |
100
+ | **SSE** | `--transport sse` | Server-Sent Events (legacy, still widely used) |
101
+
102
+ Local servers (npm packages, commands) always use **stdio** transport.
103
+
104
+ ## Supported Agents
105
+
106
+ MCP servers can be installed to any of these agents:
107
+
108
+ | Agent | `--agent` | Project Path | Global Path |
109
+ | -------------- | ---------------- | ----------------------- | ----------------------------------------------------------------- |
110
+ | Claude Code | `claude-code` | `.mcp.json` | `~/.claude.json` |
111
+ | Claude Desktop | `claude-desktop` | - | `~/Library/Application Support/Claude/claude_desktop_config.json` |
112
+ | Codex | `codex` | - | `~/.codex/config.toml` |
113
+ | Cursor | `cursor` | `.cursor/mcp.json` | `~/.cursor/mcp.json` |
114
+ | Gemini CLI | `gemini-cli` | `.gemini/settings.json` | `~/.gemini/settings.json` |
115
+ | Goose | `goose` | `.goose/config.yaml` | `~/.config/goose/config.yaml` |
116
+ | OpenCode | `opencode` | `.opencode.json` | `~/.config/opencode/opencode.json` |
117
+ | VS Code | `vscode` | `.vscode/mcp.json` | `~/Library/Application Support/Code/User/mcp.json` |
118
+ | Zed | `zed` | - | `~/.config/zed/settings.json` |
86
119
 
87
120
  **Aliases:** `github-copilot` → `vscode`
88
121
 
89
- ## Examples
122
+ The CLI uses smart detection to find agents in your project directory and globally installed agents. See [Smart Detection](#smart-detection) for details.
90
123
 
91
- ### Install to specific agents
124
+ ### Transport Support
92
125
 
93
- ```bash
94
- # Install to Cursor and Claude Code only
95
- npx add-mcp https://mcp.example.com/api -a cursor claude-code
126
+ Not all agents support all transport types:
96
127
 
97
- # Install to VS Code (using alias)
98
- npx add-mcp mcp-server -a github-copilot
99
- ```
128
+ | Agent | stdio | http | sse |
129
+ | -------------- | ----- | ---- | --- |
130
+ | Claude Code | ✓ | ✓ | ✓ |
131
+ | Claude Desktop | ✓ | ✓ | ✓ |
132
+ | Codex | ✓ | ✓ | ✓ |
133
+ | Cursor | ✓ | ✓ | ✓ |
134
+ | Gemini CLI | ✓ | ✓ | ✓ |
135
+ | Goose | ✓ | ✓ | ✗ |
136
+ | OpenCode | ✓ | ✓ | ✓ |
137
+ | VS Code | ✓ | ✓ | ✓ |
138
+ | Zed | ✓ | ✓ | ✓ |
100
139
 
101
- ### Project vs Global installation
140
+ ## What are MCP Servers?
102
141
 
103
- ```bash
104
- # Project-level (creates .cursor/mcp.json, .mcp.json, etc.)
105
- npx add-mcp mcp-server
142
+ [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers extend your coding agent's capabilities by providing tools, resources, and context. MCP servers can:
106
143
 
107
- # Global (installs to ~/.cursor/mcp.json, ~/.claude.json, etc.)
108
- npx add-mcp mcp-server --global
109
- ```
144
+ - Connect to databases (PostgreSQL, MySQL, etc.)
145
+ - Integrate with external services (GitHub, Linear, Notion)
146
+ - Provide file system access
147
+ - Offer specialized tools for your workflow
110
148
 
111
- ### Non-interactive mode
149
+ ## Troubleshooting
112
150
 
113
- ```bash
114
- # Skip all prompts, install globally to all detected agents
115
- npx add-mcp https://mcp.example.com/api -y -g
151
+ ### Transport mismatch error
116
152
 
117
- # Install to all agents without any prompts
118
- npx add-mcp mcp-server --all
119
- ```
153
+ If you get an error about transport not being supported, check that the agent supports your chosen transport type. For example, Goose doesn't support SSE transport.
154
+
155
+ ### Server not loading
156
+
157
+ - Verify the server URL is correct and accessible
158
+ - Check the agent's MCP configuration file for syntax errors
159
+ - Ensure the server name doesn't conflict with existing servers
160
+
161
+ ### Permission errors
162
+
163
+ Ensure you have write access to the target configuration directory.
164
+
165
+ ## License
120
166
 
121
- ## Config File Locations
122
-
123
- | Agent | Global | Local |
124
- | -------------- | ----------------------------------------------------------------- | ----------------------- |
125
- | Claude Code | `~/.claude.json` | `.mcp.json` |
126
- | Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` | - |
127
- | Codex | `~/.codex/config.toml` | - |
128
- | Cursor | `~/.cursor/mcp.json` | `.cursor/mcp.json` |
129
- | Gemini CLI | `~/.gemini/settings.json` | `.gemini/settings.json` |
130
- | Goose | `~/.config/goose/config.yaml` | - |
131
- | OpenCode | `~/.config/opencode/opencode.json` | `.opencode.json` |
132
- | VS Code | `~/Library/Application Support/Code/User/mcp.json` | `.vscode/mcp.json` |
133
- | Zed | `~/.config/zed/settings.json` | - |
167
+ Apache 2.0
package/dist/index.js CHANGED
@@ -42,11 +42,9 @@ function transformGooseConfig(serverName, config) {
42
42
  if (config.url) {
43
43
  return {
44
44
  name: serverName,
45
- cmd: "echo",
46
- args: [`Remote MCP servers not directly supported. URL: ${config.url}`],
47
- enabled: false,
48
- envs: {},
49
- type: "stdio",
45
+ type: "streamable_http",
46
+ url: config.url,
47
+ enabled: true,
50
48
  timeout: 300
51
49
  };
52
50
  }
@@ -113,9 +111,11 @@ var agents = {
113
111
  displayName: "Claude Code",
114
112
  configPath: join(home, ".claude.json"),
115
113
  localConfigPath: ".mcp.json",
114
+ projectDetectPaths: [".mcp.json", ".claude"],
116
115
  configKey: "mcpServers",
117
116
  format: "json",
118
- detectInstalled: async () => {
117
+ supportedTransports: ["stdio", "http", "sse"],
118
+ detectGlobalInstall: async () => {
119
119
  return existsSync(join(home, ".claude"));
120
120
  }
121
121
  },
@@ -123,9 +123,12 @@ var agents = {
123
123
  name: "claude-desktop",
124
124
  displayName: "Claude Desktop",
125
125
  configPath: join(appSupport, "Claude", "claude_desktop_config.json"),
126
+ projectDetectPaths: [],
127
+ // Global only - no project support
126
128
  configKey: "mcpServers",
127
129
  format: "json",
128
- detectInstalled: async () => {
130
+ supportedTransports: ["stdio", "http", "sse"],
131
+ detectGlobalInstall: async () => {
129
132
  return existsSync(join(appSupport, "Claude"));
130
133
  }
131
134
  },
@@ -136,9 +139,12 @@ var agents = {
136
139
  process.env.CODEX_HOME || join(home, ".codex"),
137
140
  "config.toml"
138
141
  ),
142
+ projectDetectPaths: [],
143
+ // Global only - no project support
139
144
  configKey: "mcp_servers",
140
145
  format: "toml",
141
- detectInstalled: async () => {
146
+ supportedTransports: ["stdio", "http", "sse"],
147
+ detectGlobalInstall: async () => {
142
148
  return existsSync(join(home, ".codex"));
143
149
  },
144
150
  transformConfig: transformCodexConfig
@@ -148,9 +154,11 @@ var agents = {
148
154
  displayName: "Cursor",
149
155
  configPath: join(home, ".cursor", "mcp.json"),
150
156
  localConfigPath: ".cursor/mcp.json",
157
+ projectDetectPaths: [".cursor"],
151
158
  configKey: "mcpServers",
152
159
  format: "json",
153
- detectInstalled: async () => {
160
+ supportedTransports: ["stdio", "http", "sse"],
161
+ detectGlobalInstall: async () => {
154
162
  return existsSync(join(home, ".cursor"));
155
163
  }
156
164
  },
@@ -159,9 +167,11 @@ var agents = {
159
167
  displayName: "Gemini CLI",
160
168
  configPath: join(home, ".gemini", "settings.json"),
161
169
  localConfigPath: ".gemini/settings.json",
170
+ projectDetectPaths: [".gemini"],
162
171
  configKey: "mcpServers",
163
172
  format: "json",
164
- detectInstalled: async () => {
173
+ supportedTransports: ["stdio", "http", "sse"],
174
+ detectGlobalInstall: async () => {
165
175
  return existsSync(join(home, ".gemini"));
166
176
  }
167
177
  },
@@ -169,9 +179,13 @@ var agents = {
169
179
  name: "goose",
170
180
  displayName: "Goose",
171
181
  configPath: join(home, ".config", "goose", "config.yaml"),
182
+ localConfigPath: ".goose/config.yaml",
183
+ projectDetectPaths: [".goose"],
172
184
  configKey: "extensions",
173
185
  format: "yaml",
174
- detectInstalled: async () => {
186
+ supportedTransports: ["stdio", "http"],
187
+ // Goose does not support SSE
188
+ detectGlobalInstall: async () => {
175
189
  return existsSync(join(home, ".config", "goose"));
176
190
  },
177
191
  transformConfig: transformGooseConfig
@@ -181,9 +195,11 @@ var agents = {
181
195
  displayName: "OpenCode",
182
196
  configPath: join(home, ".config", "opencode", "opencode.json"),
183
197
  localConfigPath: ".opencode.json",
198
+ projectDetectPaths: [".opencode.json", ".opencode"],
184
199
  configKey: "mcp",
185
200
  format: "json",
186
- detectInstalled: async () => {
201
+ supportedTransports: ["stdio", "http", "sse"],
202
+ detectGlobalInstall: async () => {
187
203
  return existsSync(join(home, ".config", "opencode"));
188
204
  },
189
205
  transformConfig: transformOpenCodeConfig
@@ -193,9 +209,11 @@ var agents = {
193
209
  displayName: "VS Code",
194
210
  configPath: join(vscodePath, "mcp.json"),
195
211
  localConfigPath: ".vscode/mcp.json",
212
+ projectDetectPaths: [".vscode"],
196
213
  configKey: "mcpServers",
197
214
  format: "json",
198
- detectInstalled: async () => {
215
+ supportedTransports: ["stdio", "http", "sse"],
216
+ detectGlobalInstall: async () => {
199
217
  return existsSync(vscodePath);
200
218
  }
201
219
  },
@@ -207,9 +225,12 @@ var agents = {
207
225
  "Zed",
208
226
  "settings.json"
209
227
  ) : join(home, ".config", "zed", "settings.json"),
228
+ projectDetectPaths: [],
229
+ // Global only - no project support
210
230
  configKey: "context_servers",
211
231
  format: "json",
212
- detectInstalled: async () => {
232
+ supportedTransports: ["stdio", "http", "sse"],
233
+ detectGlobalInstall: async () => {
213
234
  return existsSync(join(home, ".config", "zed")) || existsSync(join(process.env.APPDATA || "", "Zed"));
214
235
  },
215
236
  transformConfig: transformZedConfig
@@ -218,14 +239,49 @@ var agents = {
218
239
  function getAgentTypes() {
219
240
  return Object.keys(agents);
220
241
  }
221
- async function detectInstalledAgents() {
222
- const installed = [];
242
+ function supportsProjectConfig(agentType) {
243
+ return agents[agentType].localConfigPath !== void 0;
244
+ }
245
+ function getProjectCapableAgents() {
246
+ return Object.keys(agents).filter(
247
+ (type) => supportsProjectConfig(type)
248
+ );
249
+ }
250
+ function detectProjectAgents(cwd) {
251
+ const dir = cwd || process.cwd();
252
+ const detected = [];
223
253
  for (const [type, config] of Object.entries(agents)) {
224
- if (await config.detectInstalled()) {
225
- installed.push(type);
254
+ if (!config.localConfigPath) continue;
255
+ for (const detectPath of config.projectDetectPaths) {
256
+ if (existsSync(join(dir, detectPath))) {
257
+ detected.push(type);
258
+ break;
259
+ }
226
260
  }
227
261
  }
228
- return installed;
262
+ return detected;
263
+ }
264
+ async function detectGlobalOnlyAgents() {
265
+ const detected = [];
266
+ for (const [type, config] of Object.entries(agents)) {
267
+ if (config.localConfigPath) continue;
268
+ if (await config.detectGlobalInstall()) {
269
+ detected.push(type);
270
+ }
271
+ }
272
+ return detected;
273
+ }
274
+ async function detectAllGlobalAgents() {
275
+ const detected = [];
276
+ for (const [type, config] of Object.entries(agents)) {
277
+ if (await config.detectGlobalInstall()) {
278
+ detected.push(type);
279
+ }
280
+ }
281
+ return detected;
282
+ }
283
+ function isTransportSupported(agentType, transport) {
284
+ return agents[agentType].supportedTransports.includes(transport);
229
285
  }
230
286
 
231
287
  // src/source-parser.ts
@@ -532,10 +588,10 @@ function buildConfigWithKey(configKey, serverName, serverConfig) {
532
588
  }
533
589
 
534
590
  // src/installer.ts
535
- function buildServerConfig(parsed) {
591
+ function buildServerConfig(parsed, options = {}) {
536
592
  if (parsed.type === "remote") {
537
593
  return {
538
- type: "http",
594
+ type: options.transport ?? "http",
539
595
  url: parsed.value
540
596
  };
541
597
  }
@@ -590,24 +646,26 @@ function installServerForAgent(serverName, serverConfig, agentType, options = {}
590
646
  function installServer(serverName, serverConfig, agentTypes, options = {}) {
591
647
  const results = /* @__PURE__ */ new Map();
592
648
  for (const agentType of agentTypes) {
649
+ const routing = options.routing?.get(agentType);
650
+ const installOptions = {
651
+ local: routing === "local",
652
+ cwd: options.cwd
653
+ };
593
654
  const result = installServerForAgent(
594
655
  serverName,
595
656
  serverConfig,
596
657
  agentType,
597
- options
658
+ installOptions
598
659
  );
599
660
  results.set(agentType, result);
600
661
  }
601
662
  return results;
602
663
  }
603
- function getAgentsWithLocalSupport() {
604
- return Object.entries(agents).filter(([_, config]) => config.localConfigPath !== void 0).map(([type, _]) => type);
605
- }
606
664
 
607
665
  // package.json
608
666
  var package_default = {
609
667
  name: "add-mcp",
610
- version: "0.1.0",
668
+ version: "0.2.0",
611
669
  description: "Install MCP servers onto coding agents (Claude Code, Cursor, VS Code, OpenCode, Codex)",
612
670
  author: "Andre Landgraf <andre@neon.tech>",
613
671
  license: "Apache-2.0",
@@ -623,8 +681,8 @@ var package_default = {
623
681
  fmt: "prettier --write .",
624
682
  build: "tsup src/index.ts --format esm --dts --clean",
625
683
  dev: "tsx src/index.ts",
626
- test: "tsx tests/source-parser.test.ts && tsx tests/installer.test.ts && tsx tests/e2e/install.test.ts",
627
- "test:unit": "tsx tests/source-parser.test.ts && tsx tests/installer.test.ts",
684
+ test: "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/installer.test.ts && tsx tests/e2e/install.test.ts",
685
+ "test:unit": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/installer.test.ts",
628
686
  "test:e2e": "tsx tests/e2e/install.test.ts",
629
687
  typecheck: "tsc --noEmit",
630
688
  prepublishOnly: "npm run build"
@@ -692,40 +750,27 @@ function resolveAgentType(input) {
692
750
  }
693
751
  return null;
694
752
  }
753
+ function collect(value, previous) {
754
+ return previous.concat([value]);
755
+ }
695
756
  program.name("add-mcp").description(
696
757
  "Install MCP servers onto coding agents (Claude Code, Cursor, VS Code, OpenCode, Codex)"
697
758
  ).version(version).argument("[target]", "MCP server URL (remote) or package name (local stdio)").option(
698
759
  "-g, --global",
699
760
  "Install globally (user-level) instead of project-level"
700
- ).option("-a, --agent <agents...>", "Specify agents to install to").option(
761
+ ).option("-a, --agent <agent>", "Specify agents to install to", collect, []).option(
701
762
  "-n, --name <name>",
702
763
  "Server name (auto-inferred from target if not provided)"
703
- ).option("-y, --yes", "Skip confirmation prompts").option("-l, --list", "List supported agents").option("--all", "Install to all agents without prompts (implies -y -g)").action(async (target, options) => {
764
+ ).option(
765
+ "-t, --transport <type>",
766
+ "Transport type for remote servers (http, sse)"
767
+ ).option("--type <type>", "Alias for --transport").option("-y, --yes", "Skip confirmation prompts").option("--all", "Install to all agents").action(async (target, options) => {
704
768
  await main(target, options);
705
769
  });
706
770
  program.parse();
707
771
  async function main(target, options) {
708
- if (options.all) {
709
- options.yes = true;
710
- options.global = true;
711
- }
712
772
  console.log();
713
773
  p.intro(chalk.bgCyan.black(" add-mcp "));
714
- if (options.list) {
715
- console.log();
716
- p.log.step(chalk.bold("Supported Agents"));
717
- const allTypes = getAgentTypes();
718
- for (const type of allTypes) {
719
- const agent = agents[type];
720
- const hasLocal = agent.localConfigPath ? chalk.dim(" (supports local)") : "";
721
- p.log.message(` ${chalk.cyan(type)} - ${agent.displayName}${hasLocal}`);
722
- }
723
- console.log();
724
- p.log.info(chalk.dim("Aliases: github-copilot \u2192 vscode"));
725
- console.log();
726
- p.outro("Use -a/--agent to specify agents");
727
- return;
728
- }
729
774
  if (!target) {
730
775
  p.log.error("Missing required argument: target");
731
776
  console.log();
@@ -752,9 +797,27 @@ async function main(target, options) {
752
797
  spinner2.stop(`Source: ${chalk.cyan(parsed.value)} (${sourceType})`);
753
798
  const serverName = options.name || parsed.inferredName;
754
799
  p.log.info(`Server name: ${chalk.cyan(serverName)}`);
755
- const serverConfig = buildServerConfig(parsed);
800
+ const transportValue = options.transport || options.type;
801
+ let resolvedTransport;
802
+ if (transportValue) {
803
+ const validTransports = ["http", "sse"];
804
+ if (!validTransports.includes(transportValue)) {
805
+ p.log.error(
806
+ `Invalid transport: ${transportValue}. Valid options: ${validTransports.join(", ")}`
807
+ );
808
+ process.exit(1);
809
+ }
810
+ resolvedTransport = transportValue;
811
+ if (!isRemoteSource(parsed)) {
812
+ p.log.warn("--transport is only used for remote URLs, ignoring");
813
+ }
814
+ }
815
+ const serverConfig = buildServerConfig(parsed, {
816
+ transport: resolvedTransport
817
+ });
756
818
  let targetAgents;
757
819
  const allAgentTypes = getAgentTypes();
820
+ let agentRouting = /* @__PURE__ */ new Map();
758
821
  if (options.agent && options.agent.length > 0) {
759
822
  const resolved = [];
760
823
  const invalid = [];
@@ -776,16 +839,40 @@ async function main(target, options) {
776
839
  targetAgents = allAgentTypes;
777
840
  p.log.info(`Installing to all ${targetAgents.length} agents`);
778
841
  } else {
779
- spinner2.start("Detecting installed agents...");
780
- const installedAgents = await detectInstalledAgents();
842
+ spinner2.start("Detecting agents...");
843
+ let detectedAgents;
844
+ if (options.global) {
845
+ detectedAgents = await detectAllGlobalAgents();
846
+ } else {
847
+ const projectAgents = detectProjectAgents();
848
+ const globalOnlyAgents = await detectGlobalOnlyAgents();
849
+ detectedAgents = [...projectAgents, ...globalOnlyAgents];
850
+ for (const agent of projectAgents) {
851
+ agentRouting.set(agent, "local");
852
+ }
853
+ for (const agent of globalOnlyAgents) {
854
+ agentRouting.set(agent, "global");
855
+ }
856
+ }
781
857
  spinner2.stop(
782
- `Detected ${installedAgents.length} agent${installedAgents.length !== 1 ? "s" : ""}`
858
+ `Detected ${detectedAgents.length} agent${detectedAgents.length !== 1 ? "s" : ""}`
783
859
  );
784
- if (installedAgents.length === 0) {
860
+ if (detectedAgents.length === 0) {
785
861
  if (options.yes) {
786
- targetAgents = allAgentTypes;
787
- p.log.info("Installing to all agents (none detected)");
862
+ targetAgents = getProjectCapableAgents();
863
+ for (const agent of targetAgents) {
864
+ agentRouting.set(agent, "local");
865
+ }
866
+ p.log.info(
867
+ `Installing to ${targetAgents.length} project-capable agents (none detected)`
868
+ );
788
869
  } else {
870
+ if (!options.global) {
871
+ p.log.error(
872
+ "No agents detected in this project. Use --global to install globally, or run in a project with agent config files."
873
+ );
874
+ process.exit(1);
875
+ }
789
876
  p.log.warn(
790
877
  "No coding agents detected. You can still install MCP servers."
791
878
  );
@@ -804,21 +891,25 @@ async function main(target, options) {
804
891
  }
805
892
  targetAgents = selected;
806
893
  }
807
- } else if (installedAgents.length === 1 || options.yes) {
808
- targetAgents = installedAgents;
809
- const agentNames = installedAgents.map((a) => chalk.cyan(agents[a].displayName)).join(", ");
894
+ } else if (detectedAgents.length === 1 || options.yes) {
895
+ targetAgents = detectedAgents;
896
+ const agentNames = detectedAgents.map((a) => chalk.cyan(agents[a].displayName)).join(", ");
810
897
  p.log.info(`Installing to: ${agentNames}`);
811
898
  } else {
812
- const agentChoices = installedAgents.map((a) => ({
813
- value: a,
814
- label: agents[a].displayName,
815
- hint: shortenPath(agents[a].configPath)
816
- }));
899
+ const agentChoices = detectedAgents.map((a) => {
900
+ const routing = agentRouting.get(a);
901
+ const hint = routing === "local" ? "project" : routing === "global" ? "global" : shortenPath(agents[a].configPath);
902
+ return {
903
+ value: a,
904
+ label: agents[a].displayName,
905
+ hint
906
+ };
907
+ });
817
908
  const selected = await p.multiselect({
818
909
  message: "Select agents to install to",
819
910
  options: agentChoices,
820
911
  required: true,
821
- initialValues: installedAgents
912
+ initialValues: detectedAgents
822
913
  });
823
914
  if (p.isCancel(selected)) {
824
915
  p.cancel("Installation cancelled");
@@ -827,47 +918,104 @@ async function main(target, options) {
827
918
  targetAgents = selected;
828
919
  }
829
920
  }
830
- let installGlobally = options.global ?? false;
831
- if (options.global === void 0 && !options.yes) {
832
- const localSupported = getAgentsWithLocalSupport();
921
+ const requiredTransport = isRemoteSource(parsed) ? resolvedTransport ?? "http" : "stdio";
922
+ const unsupportedAgents = targetAgents.filter(
923
+ (a) => !isTransportSupported(a, requiredTransport)
924
+ );
925
+ if (unsupportedAgents.length > 0) {
926
+ const unsupportedNames = unsupportedAgents.map((a) => agents[a].displayName).join(", ");
927
+ if (options.all) {
928
+ p.log.warn(
929
+ `Skipping agents that don't support ${requiredTransport} transport: ${unsupportedNames}`
930
+ );
931
+ targetAgents = targetAgents.filter(
932
+ (a) => isTransportSupported(a, requiredTransport)
933
+ );
934
+ if (targetAgents.length === 0) {
935
+ p.log.error("No agents support this transport type");
936
+ process.exit(1);
937
+ }
938
+ } else {
939
+ p.log.error(
940
+ `The following agents don't support ${requiredTransport} transport: ${unsupportedNames}`
941
+ );
942
+ process.exit(1);
943
+ }
944
+ }
945
+ const hasSmartRouting = agentRouting.size > 0;
946
+ if (options.global) {
947
+ for (const agent of targetAgents) {
948
+ agentRouting.set(agent, "global");
949
+ }
950
+ } else if (!hasSmartRouting) {
833
951
  const selectedWithLocal = targetAgents.filter(
834
- (a) => localSupported.includes(a)
952
+ (a) => supportsProjectConfig(a)
835
953
  );
954
+ const globalOnlySelected = targetAgents.filter(
955
+ (a) => !supportsProjectConfig(a)
956
+ );
957
+ for (const agent of globalOnlySelected) {
958
+ agentRouting.set(agent, "global");
959
+ }
836
960
  if (selectedWithLocal.length > 0) {
837
- const scope = await p.select({
838
- message: "Installation scope",
839
- options: [
840
- {
841
- value: false,
842
- label: "Project",
843
- hint: "Install in current directory (committed with your project)"
844
- },
845
- {
846
- value: true,
847
- label: "Global",
848
- hint: "Install in home directory (available across all projects)"
849
- }
850
- ]
851
- });
852
- if (p.isCancel(scope)) {
853
- p.cancel("Installation cancelled");
854
- process.exit(0);
961
+ let installLocally = true;
962
+ if (!options.yes) {
963
+ const scope = await p.select({
964
+ message: "Installation scope",
965
+ options: [
966
+ {
967
+ value: true,
968
+ label: "Project",
969
+ hint: "Install in current directory (committed with your project)"
970
+ },
971
+ {
972
+ value: false,
973
+ label: "Global",
974
+ hint: "Install in home directory (available across all projects)"
975
+ }
976
+ ]
977
+ });
978
+ if (p.isCancel(scope)) {
979
+ p.cancel("Installation cancelled");
980
+ process.exit(0);
981
+ }
982
+ installLocally = scope;
983
+ }
984
+ for (const agent of selectedWithLocal) {
985
+ agentRouting.set(agent, installLocally ? "local" : "global");
855
986
  }
856
- installGlobally = scope;
857
987
  } else {
858
- installGlobally = true;
859
988
  p.log.info("Selected agents only support global installation");
860
989
  }
861
990
  }
862
991
  const summaryLines = [];
863
992
  summaryLines.push(`${chalk.cyan("Server:")} ${serverName}`);
864
993
  summaryLines.push(`${chalk.cyan("Type:")} ${sourceType}`);
865
- summaryLines.push(
866
- `${chalk.cyan("Scope:")} ${installGlobally ? "Global" : "Project"}`
994
+ const localAgents = targetAgents.filter(
995
+ (a) => agentRouting.get(a) === "local"
867
996
  );
868
- summaryLines.push(
869
- `${chalk.cyan("Agents:")} ${targetAgents.map((a) => agents[a].displayName).join(", ")}`
997
+ const globalAgents = targetAgents.filter(
998
+ (a) => agentRouting.get(a) === "global"
870
999
  );
1000
+ if (localAgents.length > 0 && globalAgents.length > 0) {
1001
+ summaryLines.push(`${chalk.cyan("Scope:")} Mixed (project + global)`);
1002
+ summaryLines.push(
1003
+ `${chalk.cyan(" Project:")} ${localAgents.map((a) => agents[a].displayName).join(", ")}`
1004
+ );
1005
+ summaryLines.push(
1006
+ `${chalk.cyan(" Global:")} ${globalAgents.map((a) => agents[a].displayName).join(", ")}`
1007
+ );
1008
+ } else if (localAgents.length > 0) {
1009
+ summaryLines.push(`${chalk.cyan("Scope:")} Project`);
1010
+ summaryLines.push(
1011
+ `${chalk.cyan("Agents:")} ${localAgents.map((a) => agents[a].displayName).join(", ")}`
1012
+ );
1013
+ } else {
1014
+ summaryLines.push(`${chalk.cyan("Scope:")} Global`);
1015
+ summaryLines.push(
1016
+ `${chalk.cyan("Agents:")} ${globalAgents.map((a) => agents[a].displayName).join(", ")}`
1017
+ );
1018
+ }
871
1019
  console.log();
872
1020
  p.note(summaryLines.join("\n"), "Installation Summary");
873
1021
  if (!options.yes) {
@@ -881,7 +1029,7 @@ async function main(target, options) {
881
1029
  }
882
1030
  spinner2.start("Installing MCP server...");
883
1031
  const results = installServer(serverName, serverConfig, targetAgents, {
884
- local: !installGlobally
1032
+ routing: agentRouting
885
1033
  });
886
1034
  spinner2.stop("Installation complete");
887
1035
  console.log();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "add-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Install MCP servers onto coding agents (Claude Code, Cursor, VS Code, OpenCode, Codex)",
5
5
  "author": "Andre Landgraf <andre@neon.tech>",
6
6
  "license": "Apache-2.0",
@@ -16,8 +16,8 @@
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/installer.test.ts && tsx tests/e2e/install.test.ts",
20
- "test:unit": "tsx tests/source-parser.test.ts && tsx tests/installer.test.ts",
19
+ "test": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/installer.test.ts && tsx tests/e2e/install.test.ts",
20
+ "test:unit": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/installer.test.ts",
21
21
  "test:e2e": "tsx tests/e2e/install.test.ts",
22
22
  "typecheck": "tsc --noEmit",
23
23
  "prepublishOnly": "npm run build"