@mcp-abap-adt/configurator 0.0.11 β 0.1.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 +1 -1
- package/bin/mcp-conf-tui.js +157 -49
- package/bin/mcp-conf.js +156 -44
- package/docs/CLIENT_INSTALLERS.md +7 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ mcp-conf tui
|
|
|
27
27
|
|
|
28
28
|
## TUI
|
|
29
29
|
|
|
30
|
-
`mcp-conf tui` starts an interactive wizard (`ls`/`add`/`
|
|
30
|
+
`mcp-conf tui` starts an interactive wizard (`ls`/`show`/`add`/`update`/`rm`/`enable`/`disable`).
|
|
31
31
|
- Step order: `operation` -> `client` -> `scope` (auto-skipped if only one scope is supported).
|
|
32
32
|
- For `add` + `sse/http`: prompts for URL, timeout, and repeatable headers.
|
|
33
33
|
- For `rm`/`enable`/`disable`: server name is selected from existing servers in chosen client/scope.
|
package/bin/mcp-conf-tui.js
CHANGED
|
@@ -35,26 +35,36 @@ async function main() {
|
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
result.tuiAction = await askSelect("Operation", [
|
|
38
|
-
"ls",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"enable",
|
|
44
|
-
"disable",
|
|
38
|
+
{ name: "ls", message: "π ls" },
|
|
39
|
+
{ name: "show", message: "π show" },
|
|
40
|
+
{ name: "sep-view", role: "separator", message: "ββββββββββββ" },
|
|
41
|
+
{ name: "add", message: "β add" },
|
|
42
|
+
{ name: "update", message: "βοΈ update" },
|
|
43
|
+
{ name: "enable", message: "β
enable" },
|
|
44
|
+
{ name: "disable", message: "βΈοΈ disable" },
|
|
45
|
+
{ name: "rm", message: "ποΈ rm" },
|
|
46
|
+
{ name: "sep-exit", role: "separator", message: "ββββββββββββ" },
|
|
47
|
+
{ name: "exit", message: "πͺ exit" },
|
|
45
48
|
]);
|
|
49
|
+
if (result.tuiAction === "exit") {
|
|
50
|
+
emitResult({ tuiAction: "exit" });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
46
53
|
const client = await askSelect(
|
|
47
54
|
"Client",
|
|
48
55
|
CLIENTS.map((item) => item.name),
|
|
49
56
|
CLIENTS.map((item) => item.message),
|
|
50
57
|
);
|
|
51
58
|
result.clients = [client];
|
|
52
|
-
|
|
53
|
-
const scopes = getSupportedScopes(client);
|
|
54
|
-
result.scope = scopes.length === 1 ? scopes[0] : await askSelect("Scope", ["global", "local"]);
|
|
59
|
+
await configureScope(result, client);
|
|
55
60
|
|
|
56
61
|
if (["rm", "enable", "disable", "show", "update"].includes(result.tuiAction)) {
|
|
57
|
-
const serverNames = listExistingServers(
|
|
62
|
+
const serverNames = listExistingServers(
|
|
63
|
+
client,
|
|
64
|
+
result.scope,
|
|
65
|
+
result.allProjects,
|
|
66
|
+
result.projectPath,
|
|
67
|
+
);
|
|
58
68
|
if (serverNames.length === 0) {
|
|
59
69
|
throw new Error(`No existing MCP servers found for ${client} (${result.scope})`);
|
|
60
70
|
}
|
|
@@ -79,32 +89,37 @@ async function main() {
|
|
|
79
89
|
|
|
80
90
|
if (result.transport === "stdio") {
|
|
81
91
|
const authSource = await askSelect("Auth source for stdio", [
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
92
|
+
"destination (--mcp=<name>)",
|
|
93
|
+
"env name (--env=<name>)",
|
|
94
|
+
"env file (--env-path=<name>)",
|
|
85
95
|
]);
|
|
86
|
-
if (authSource.startsWith("
|
|
96
|
+
if (authSource.startsWith("destination")) {
|
|
87
97
|
result.mcpDestination = await askInput("Destination name", "TRIAL");
|
|
88
98
|
result.useSessionEnv = false;
|
|
99
|
+
result.envName = null;
|
|
89
100
|
result.envPath = null;
|
|
90
101
|
result.url = null;
|
|
91
102
|
emitResult(result);
|
|
92
103
|
return;
|
|
93
104
|
}
|
|
94
|
-
if (authSource.startsWith("
|
|
105
|
+
if (authSource.startsWith("env name")) {
|
|
106
|
+
result.envName = await askInput("Env name", "TRIAL");
|
|
107
|
+
result.useSessionEnv = false;
|
|
108
|
+
result.envPath = null;
|
|
109
|
+
result.mcpDestination = null;
|
|
110
|
+
result.url = null;
|
|
111
|
+
emitResult(result);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (authSource.startsWith("env file")) {
|
|
95
115
|
result.envPath = await askInput("Path to .env file");
|
|
96
116
|
result.useSessionEnv = false;
|
|
117
|
+
result.envName = null;
|
|
97
118
|
result.mcpDestination = null;
|
|
98
119
|
result.url = null;
|
|
99
120
|
emitResult(result);
|
|
100
121
|
return;
|
|
101
122
|
}
|
|
102
|
-
result.useSessionEnv = true;
|
|
103
|
-
result.envPath = null;
|
|
104
|
-
result.mcpDestination = null;
|
|
105
|
-
result.url = null;
|
|
106
|
-
emitResult(result);
|
|
107
|
-
return;
|
|
108
123
|
}
|
|
109
124
|
|
|
110
125
|
result.url = await askInput("Server URL (http/https)");
|
|
@@ -118,7 +133,13 @@ async function main() {
|
|
|
118
133
|
}
|
|
119
134
|
|
|
120
135
|
async function configureUpdate(result, client) {
|
|
121
|
-
const current = getServerConfig(
|
|
136
|
+
const current = getServerConfig(
|
|
137
|
+
client,
|
|
138
|
+
result.scope,
|
|
139
|
+
result.name,
|
|
140
|
+
result.allProjects,
|
|
141
|
+
result.projectPath,
|
|
142
|
+
);
|
|
122
143
|
const currentTransport = current.transport || "stdio";
|
|
123
144
|
result.transport = currentTransport;
|
|
124
145
|
result.command = current.command || "mcp-abap-adt";
|
|
@@ -128,42 +149,48 @@ async function configureUpdate(result, client) {
|
|
|
128
149
|
|
|
129
150
|
if (currentTransport === "stdio") {
|
|
130
151
|
const authChoices = [
|
|
131
|
-
"
|
|
132
|
-
"
|
|
133
|
-
"
|
|
152
|
+
"destination (--mcp=<name>)",
|
|
153
|
+
"env name (--env=<name>)",
|
|
154
|
+
"env file (--env-path=<name>)",
|
|
134
155
|
];
|
|
135
156
|
const authType = current.auth?.type || "unknown";
|
|
136
157
|
const authInitial =
|
|
137
158
|
authType === "mcp" ? 0 : authType === "env" ? 1 : authType === "env-path" ? 2 : 0;
|
|
138
159
|
const authSource = await askSelect("Auth source for stdio", authChoices, null, authInitial);
|
|
139
|
-
if (authSource.startsWith("
|
|
160
|
+
if (authSource.startsWith("destination")) {
|
|
140
161
|
result.mcpDestination = await askInput("Destination name", current.auth?.value || "TRIAL");
|
|
141
162
|
result.useSessionEnv = false;
|
|
163
|
+
result.envName = null;
|
|
142
164
|
result.envPath = null;
|
|
143
165
|
result.url = null;
|
|
144
166
|
result.headers = {};
|
|
145
167
|
return;
|
|
146
168
|
}
|
|
147
|
-
if (authSource.startsWith("
|
|
169
|
+
if (authSource.startsWith("env name")) {
|
|
170
|
+
result.envName = await askInput("Env name", current.auth?.value || "TRIAL");
|
|
171
|
+
result.useSessionEnv = false;
|
|
172
|
+
result.envPath = null;
|
|
173
|
+
result.mcpDestination = null;
|
|
174
|
+
result.url = null;
|
|
175
|
+
result.headers = {};
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (authSource.startsWith("env file")) {
|
|
148
179
|
result.envPath = await askInput("Path to .env file", current.auth?.value || undefined);
|
|
149
180
|
result.useSessionEnv = false;
|
|
181
|
+
result.envName = null;
|
|
150
182
|
result.mcpDestination = null;
|
|
151
183
|
result.url = null;
|
|
152
184
|
result.headers = {};
|
|
153
185
|
return;
|
|
154
186
|
}
|
|
155
|
-
result.useSessionEnv = true;
|
|
156
|
-
result.envPath = null;
|
|
157
|
-
result.mcpDestination = null;
|
|
158
|
-
result.url = null;
|
|
159
|
-
result.headers = {};
|
|
160
|
-
return;
|
|
161
187
|
}
|
|
162
188
|
|
|
163
189
|
result.url = await askInput("Server URL (http/https)", current.url || undefined);
|
|
164
190
|
result.timeout = await askPositiveNumber("Timeout seconds", result.timeout);
|
|
165
191
|
result.headers = await askHeaders(current.headers || {});
|
|
166
192
|
result.useSessionEnv = false;
|
|
193
|
+
result.envName = null;
|
|
167
194
|
result.envPath = null;
|
|
168
195
|
result.mcpDestination = null;
|
|
169
196
|
}
|
|
@@ -173,10 +200,15 @@ function emitResult(result) {
|
|
|
173
200
|
}
|
|
174
201
|
|
|
175
202
|
async function askSelect(message, choices, choiceLabels, initial = 0) {
|
|
176
|
-
const promptChoices = choices.map((value, index) =>
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
203
|
+
const promptChoices = choices.map((value, index) => {
|
|
204
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
205
|
+
return value;
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
name: value,
|
|
209
|
+
message: choiceLabels?.[index] || value,
|
|
210
|
+
};
|
|
211
|
+
});
|
|
180
212
|
const select = new Select({
|
|
181
213
|
name: "value",
|
|
182
214
|
message,
|
|
@@ -239,6 +271,33 @@ function getSupportedScopes(clientName) {
|
|
|
239
271
|
return ["global", "local"];
|
|
240
272
|
}
|
|
241
273
|
|
|
274
|
+
async function configureScope(result, clientName) {
|
|
275
|
+
result.allProjects = false;
|
|
276
|
+
result.projectPath = null;
|
|
277
|
+
|
|
278
|
+
if (clientName === "claude") {
|
|
279
|
+
result.scope = await askSelect("Scope", ["global", "local"]);
|
|
280
|
+
if (result.scope === "local") {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (supportsClaudeGlobalAllProjects(result.tuiAction)) {
|
|
284
|
+
const globalMode = await askSelect("Claude global", [
|
|
285
|
+
{ name: "single", message: "for current project" },
|
|
286
|
+
{ name: "all", message: "for all projects" },
|
|
287
|
+
]);
|
|
288
|
+
result.allProjects = globalMode === "all";
|
|
289
|
+
}
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const scopes = getSupportedScopes(clientName);
|
|
294
|
+
result.scope = scopes.length === 1 ? scopes[0] : await askSelect("Scope", ["global", "local"]);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function supportsClaudeGlobalAllProjects(action) {
|
|
298
|
+
return ["ls", "show", "rm", "enable", "disable"].includes(action);
|
|
299
|
+
}
|
|
300
|
+
|
|
242
301
|
function getSupportedTransports(clientName) {
|
|
243
302
|
if (clientName === "codex") {
|
|
244
303
|
return ["stdio", "http"];
|
|
@@ -246,10 +305,16 @@ function getSupportedTransports(clientName) {
|
|
|
246
305
|
return ["stdio", "sse", "http"];
|
|
247
306
|
}
|
|
248
307
|
|
|
249
|
-
function listExistingServers(clientName, scope) {
|
|
308
|
+
function listExistingServers(clientName, scope, allProjects = false, projectPath = null) {
|
|
250
309
|
const cliPath = path.join(__dirname, "mcp-conf.js");
|
|
251
310
|
const scopeArg = scope === "local" ? "--local" : "--global";
|
|
252
|
-
const
|
|
311
|
+
const cliArgs = [cliPath, "ls", "--client", clientName, scopeArg];
|
|
312
|
+
if (allProjects) {
|
|
313
|
+
cliArgs.push("--all-projects");
|
|
314
|
+
} else if (projectPath) {
|
|
315
|
+
cliArgs.push("--project", projectPath);
|
|
316
|
+
}
|
|
317
|
+
const run = spawnSync(process.execPath, cliArgs, {
|
|
253
318
|
encoding: "utf8",
|
|
254
319
|
});
|
|
255
320
|
if (run.status !== 0) {
|
|
@@ -271,16 +336,28 @@ function listExistingServers(clientName, scope) {
|
|
|
271
336
|
return [...new Set(names)].sort((a, b) => a.localeCompare(b));
|
|
272
337
|
}
|
|
273
338
|
|
|
274
|
-
function getServerConfig(clientName, scope, serverName) {
|
|
339
|
+
function getServerConfig(clientName, scope, serverName, allProjects = false, projectPath = null) {
|
|
275
340
|
const cliPath = path.join(__dirname, "mcp-conf.js");
|
|
276
341
|
const scopeArg = scope === "local" ? "--local" : "--global";
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
342
|
+
const cliArgs = [
|
|
343
|
+
cliPath,
|
|
344
|
+
"show",
|
|
345
|
+
"--client",
|
|
346
|
+
clientName,
|
|
347
|
+
"--name",
|
|
348
|
+
serverName,
|
|
349
|
+
scopeArg,
|
|
350
|
+
"--json",
|
|
351
|
+
"--normalized",
|
|
352
|
+
];
|
|
353
|
+
if (allProjects) {
|
|
354
|
+
cliArgs.push("--all-projects");
|
|
355
|
+
} else if (projectPath) {
|
|
356
|
+
cliArgs.push("--project", projectPath);
|
|
357
|
+
}
|
|
358
|
+
const run = spawnSync(process.execPath, cliArgs, {
|
|
359
|
+
encoding: "utf8",
|
|
360
|
+
});
|
|
284
361
|
if (run.status !== 0) {
|
|
285
362
|
const stderr = String(run.stderr || "").trim();
|
|
286
363
|
throw new Error(stderr || "Failed to read existing server config");
|
|
@@ -293,9 +370,40 @@ function getServerConfig(clientName, scope, serverName) {
|
|
|
293
370
|
}
|
|
294
371
|
|
|
295
372
|
main().catch((error) => {
|
|
296
|
-
if (error
|
|
373
|
+
if (isPromptCancelError(error)) {
|
|
374
|
+
process.exit(0);
|
|
375
|
+
}
|
|
376
|
+
process.stderr.write(`${error?.message || String(error)}\n`);
|
|
377
|
+
process.exit(1);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
process.on("uncaughtException", (error) => {
|
|
381
|
+
if (isPromptCancelError(error)) {
|
|
297
382
|
process.exit(0);
|
|
298
383
|
}
|
|
299
384
|
process.stderr.write(`${error?.message || String(error)}\n`);
|
|
300
385
|
process.exit(1);
|
|
301
386
|
});
|
|
387
|
+
|
|
388
|
+
process.on("unhandledRejection", (error) => {
|
|
389
|
+
if (isPromptCancelError(error)) {
|
|
390
|
+
process.exit(0);
|
|
391
|
+
}
|
|
392
|
+
process.stderr.write(`${error?.message || String(error)}\n`);
|
|
393
|
+
process.exit(1);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
function isPromptCancelError(error) {
|
|
397
|
+
if (error === "" || error === null || error === undefined) {
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
if (typeof error === "string") {
|
|
401
|
+
return error.toLowerCase().includes("cancel");
|
|
402
|
+
}
|
|
403
|
+
const message = String(error?.message || "");
|
|
404
|
+
return (
|
|
405
|
+
error?.code === "ERR_USE_AFTER_CLOSE" ||
|
|
406
|
+
message.toLowerCase().includes("cancel") ||
|
|
407
|
+
message.toLowerCase().includes("readline was closed")
|
|
408
|
+
);
|
|
409
|
+
}
|
package/bin/mcp-conf.js
CHANGED
|
@@ -26,7 +26,7 @@ const args = process.argv.slice(2);
|
|
|
26
26
|
const action = args[0] && !args[0].startsWith("-") ? args[0] : null;
|
|
27
27
|
if (
|
|
28
28
|
action &&
|
|
29
|
-
["add", "rm", "ls", "
|
|
29
|
+
["add", "rm", "ls", "show", "enable", "disable", "where", "update", "tui", "help"].includes(
|
|
30
30
|
action,
|
|
31
31
|
)
|
|
32
32
|
) {
|
|
@@ -34,6 +34,7 @@ if (
|
|
|
34
34
|
}
|
|
35
35
|
const options = {
|
|
36
36
|
clients: [],
|
|
37
|
+
envName: null,
|
|
37
38
|
envPath: null,
|
|
38
39
|
useSessionEnv: false,
|
|
39
40
|
mcpDestination: null,
|
|
@@ -52,6 +53,7 @@ const options = {
|
|
|
52
53
|
where: false,
|
|
53
54
|
show: false,
|
|
54
55
|
outputJson: false,
|
|
56
|
+
outputNormalized: false,
|
|
55
57
|
url: null,
|
|
56
58
|
headers: {},
|
|
57
59
|
timeout: 60,
|
|
@@ -62,7 +64,7 @@ if (args.includes("--help") || args.includes("-h") || action === "help") {
|
|
|
62
64
|
action === "help"
|
|
63
65
|
? args[0]
|
|
64
66
|
: action &&
|
|
65
|
-
["add", "rm", "ls", "
|
|
67
|
+
["add", "rm", "ls", "show", "enable", "disable", "where", "update", "tui"].includes(
|
|
66
68
|
action,
|
|
67
69
|
)
|
|
68
70
|
? action
|
|
@@ -76,30 +78,45 @@ for (let i = 0; i < args.length; i += 1) {
|
|
|
76
78
|
if (arg === "--client") {
|
|
77
79
|
options.clients.push(normalizeClientName(args[i + 1]));
|
|
78
80
|
i += 1;
|
|
81
|
+
} else if (arg.startsWith("--env=")) {
|
|
82
|
+
options.envName = arg.slice("--env=".length);
|
|
83
|
+
options.useSessionEnv = false;
|
|
84
|
+
options.envPath = null;
|
|
85
|
+
options.mcpDestination = null;
|
|
79
86
|
} else if (arg === "--env") {
|
|
80
87
|
const maybePath = args[i + 1];
|
|
81
88
|
if (maybePath && !maybePath.startsWith("-")) {
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
if (looksLikeEnvPath(maybePath)) {
|
|
90
|
+
// Backward-compatible form: --env /path/to/.env
|
|
91
|
+
options.envPath = maybePath;
|
|
92
|
+
options.envName = null;
|
|
93
|
+
} else {
|
|
94
|
+
options.envName = maybePath;
|
|
95
|
+
options.envPath = null;
|
|
96
|
+
}
|
|
84
97
|
options.useSessionEnv = false;
|
|
85
98
|
options.mcpDestination = null;
|
|
86
99
|
i += 1;
|
|
87
100
|
} else {
|
|
88
101
|
options.useSessionEnv = true;
|
|
102
|
+
options.envName = null;
|
|
89
103
|
options.envPath = null;
|
|
90
104
|
options.mcpDestination = null;
|
|
91
105
|
}
|
|
92
106
|
} else if (arg === "--env-path") {
|
|
93
107
|
options.envPath = args[i + 1];
|
|
108
|
+
options.envName = null;
|
|
94
109
|
options.useSessionEnv = false;
|
|
95
110
|
options.mcpDestination = null;
|
|
96
111
|
i += 1;
|
|
97
112
|
} else if (arg === "--session-env") {
|
|
98
113
|
options.useSessionEnv = true;
|
|
114
|
+
options.envName = null;
|
|
99
115
|
options.envPath = null;
|
|
100
116
|
options.mcpDestination = null;
|
|
101
117
|
} else if (arg === "--mcp") {
|
|
102
118
|
options.mcpDestination = args[i + 1];
|
|
119
|
+
options.envName = null;
|
|
103
120
|
options.useSessionEnv = false;
|
|
104
121
|
options.envPath = null;
|
|
105
122
|
i += 1;
|
|
@@ -154,14 +171,16 @@ for (let i = 0; i < args.length; i += 1) {
|
|
|
154
171
|
options.force = true;
|
|
155
172
|
} else if (arg === "--json") {
|
|
156
173
|
options.outputJson = true;
|
|
174
|
+
} else if (arg === "--normalized") {
|
|
175
|
+
options.outputNormalized = true;
|
|
157
176
|
}
|
|
158
177
|
}
|
|
159
178
|
|
|
160
179
|
if (
|
|
161
180
|
!action ||
|
|
162
|
-
!["add", "rm", "ls", "
|
|
181
|
+
!["add", "rm", "ls", "show", "enable", "disable", "where", "update", "tui"].includes(action)
|
|
163
182
|
) {
|
|
164
|
-
fail("Provide a command: add | rm | ls | enable | disable | where |
|
|
183
|
+
fail("Provide a command: add | rm | ls | show | enable | disable | where | update | tui.");
|
|
165
184
|
}
|
|
166
185
|
|
|
167
186
|
let effectiveAction = action;
|
|
@@ -169,6 +188,9 @@ if (action === "tui") {
|
|
|
169
188
|
runTuiWizard(options);
|
|
170
189
|
effectiveAction = options.tuiAction || "add";
|
|
171
190
|
}
|
|
191
|
+
if (effectiveAction === "exit") {
|
|
192
|
+
process.exit(0);
|
|
193
|
+
}
|
|
172
194
|
|
|
173
195
|
if (options.clients.length === 0) {
|
|
174
196
|
fail("Provide at least one --client.");
|
|
@@ -242,8 +264,8 @@ const requiresConnectionParams =
|
|
|
242
264
|
!options.remove && !options.toggle && !options.list && !options.where && !options.show;
|
|
243
265
|
|
|
244
266
|
if (requiresConnectionParams && options.transport === "stdio") {
|
|
245
|
-
if (!options.envPath && !options.mcpDestination && !options.useSessionEnv) {
|
|
246
|
-
fail("Provide --env
|
|
267
|
+
if (!options.envName && !options.envPath && !options.mcpDestination && !options.useSessionEnv) {
|
|
268
|
+
fail("Provide --env <name>, --env-path <path>, --session-env, or --mcp <destination>.");
|
|
247
269
|
}
|
|
248
270
|
}
|
|
249
271
|
|
|
@@ -251,8 +273,8 @@ if (requiresConnectionParams && options.transport !== "stdio") {
|
|
|
251
273
|
if (!options.url) {
|
|
252
274
|
fail("Provide --url <http(s)://...> for sse/http transports.");
|
|
253
275
|
}
|
|
254
|
-
if (options.envPath || options.mcpDestination || options.useSessionEnv) {
|
|
255
|
-
fail("--env/--env-path/--mcp are only valid for stdio transport.");
|
|
276
|
+
if (options.envName || options.envPath || options.mcpDestination || options.useSessionEnv) {
|
|
277
|
+
fail("--env/--env-path/--session-env/--mcp are only valid for stdio transport.");
|
|
256
278
|
}
|
|
257
279
|
}
|
|
258
280
|
|
|
@@ -263,13 +285,15 @@ const userProfile = process.env.USERPROFILE || home;
|
|
|
263
285
|
|
|
264
286
|
const serverArgsRaw = [
|
|
265
287
|
`--transport=${options.transport}`,
|
|
266
|
-
options.
|
|
267
|
-
?
|
|
268
|
-
: options.
|
|
269
|
-
?
|
|
270
|
-
: options.
|
|
271
|
-
? `--
|
|
272
|
-
:
|
|
288
|
+
options.envName
|
|
289
|
+
? `--env=${options.envName}`
|
|
290
|
+
: options.useSessionEnv
|
|
291
|
+
? "--session-env"
|
|
292
|
+
: options.envPath
|
|
293
|
+
? `--env-path=${options.envPath}`
|
|
294
|
+
: options.mcpDestination
|
|
295
|
+
? `--mcp=${options.mcpDestination.toLowerCase()}`
|
|
296
|
+
: undefined,
|
|
273
297
|
];
|
|
274
298
|
const serverArgs = serverArgsRaw.filter(Boolean);
|
|
275
299
|
|
|
@@ -489,7 +513,8 @@ function runTuiWizard(opts) {
|
|
|
489
513
|
const rawPayload = run.output?.[3] || "";
|
|
490
514
|
const payload = String(rawPayload).trim();
|
|
491
515
|
if (!payload) {
|
|
492
|
-
|
|
516
|
+
opts.tuiAction = "exit";
|
|
517
|
+
return;
|
|
493
518
|
}
|
|
494
519
|
let selected;
|
|
495
520
|
try {
|
|
@@ -1314,7 +1339,10 @@ function showJsonConfig(filePath, clientType, serverName) {
|
|
|
1314
1339
|
if (!entry) {
|
|
1315
1340
|
fail(`Server "${serverName}" not found in ${filePath}.`);
|
|
1316
1341
|
}
|
|
1317
|
-
|
|
1342
|
+
const details = options.outputNormalized
|
|
1343
|
+
? normalizeServerDetails(clientType, serverName, entry)
|
|
1344
|
+
: cloneJsonLike(entry);
|
|
1345
|
+
outputShow(filePath, serverName, details);
|
|
1318
1346
|
}
|
|
1319
1347
|
|
|
1320
1348
|
function showCodexConfig(filePath, serverName) {
|
|
@@ -1327,7 +1355,10 @@ function showCodexConfig(filePath, serverName) {
|
|
|
1327
1355
|
if (!entry) {
|
|
1328
1356
|
fail(`Server "${serverName}" not found in ${filePath}.`);
|
|
1329
1357
|
}
|
|
1330
|
-
|
|
1358
|
+
const details = options.outputNormalized
|
|
1359
|
+
? normalizeServerDetails("codex", serverName, entry)
|
|
1360
|
+
: cloneJsonLike(entry);
|
|
1361
|
+
outputShow(filePath, serverName, details);
|
|
1331
1362
|
}
|
|
1332
1363
|
|
|
1333
1364
|
function showGooseConfig(filePath, serverName) {
|
|
@@ -1340,7 +1371,10 @@ function showGooseConfig(filePath, serverName) {
|
|
|
1340
1371
|
if (!entry) {
|
|
1341
1372
|
fail(`Server "${serverName}" not found in ${filePath}.`);
|
|
1342
1373
|
}
|
|
1343
|
-
|
|
1374
|
+
const details = options.outputNormalized
|
|
1375
|
+
? normalizeServerDetails("goose", serverName, entry)
|
|
1376
|
+
: cloneJsonLike(entry);
|
|
1377
|
+
outputShow(filePath, serverName, details);
|
|
1344
1378
|
}
|
|
1345
1379
|
|
|
1346
1380
|
function showClaudeConfig(filePath, serverName, allProjects, projectPath) {
|
|
@@ -1355,10 +1389,17 @@ function showClaudeConfig(filePath, serverName, allProjects, projectPath) {
|
|
|
1355
1389
|
const projectNode = data.projects?.[key];
|
|
1356
1390
|
const store = projectNode?.mcpServers || {};
|
|
1357
1391
|
if (store[serverName]) {
|
|
1358
|
-
results.push(
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1392
|
+
results.push(
|
|
1393
|
+
options.outputNormalized
|
|
1394
|
+
? {
|
|
1395
|
+
project: key,
|
|
1396
|
+
...normalizeServerDetails("claude", serverName, store[serverName]),
|
|
1397
|
+
}
|
|
1398
|
+
: {
|
|
1399
|
+
project: key,
|
|
1400
|
+
raw: cloneJsonLike(store[serverName]),
|
|
1401
|
+
},
|
|
1402
|
+
);
|
|
1362
1403
|
}
|
|
1363
1404
|
}
|
|
1364
1405
|
if (!results.length) {
|
|
@@ -1369,7 +1410,12 @@ function showClaudeConfig(filePath, serverName, allProjects, projectPath) {
|
|
|
1369
1410
|
return;
|
|
1370
1411
|
}
|
|
1371
1412
|
for (const result of results) {
|
|
1372
|
-
outputShow(
|
|
1413
|
+
outputShow(
|
|
1414
|
+
filePath,
|
|
1415
|
+
serverName,
|
|
1416
|
+
options.outputNormalized ? result : result.raw,
|
|
1417
|
+
result.project,
|
|
1418
|
+
);
|
|
1373
1419
|
}
|
|
1374
1420
|
return;
|
|
1375
1421
|
}
|
|
@@ -1379,12 +1425,10 @@ function showClaudeConfig(filePath, serverName, allProjects, projectPath) {
|
|
|
1379
1425
|
if (!entry) {
|
|
1380
1426
|
fail(`Server "${serverName}" not found for ${projectKey}.`);
|
|
1381
1427
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
projectKey,
|
|
1387
|
-
);
|
|
1428
|
+
const details = options.outputNormalized
|
|
1429
|
+
? normalizeServerDetails("claude", serverName, entry, projectKey)
|
|
1430
|
+
: cloneJsonLike(entry);
|
|
1431
|
+
outputShow(filePath, serverName, details, projectKey);
|
|
1388
1432
|
return;
|
|
1389
1433
|
}
|
|
1390
1434
|
const store = data.mcpServers || {};
|
|
@@ -1392,7 +1436,10 @@ function showClaudeConfig(filePath, serverName, allProjects, projectPath) {
|
|
|
1392
1436
|
if (!entry) {
|
|
1393
1437
|
fail(`Server "${serverName}" not found in ${filePath}.`);
|
|
1394
1438
|
}
|
|
1395
|
-
|
|
1439
|
+
const details = options.outputNormalized
|
|
1440
|
+
? normalizeServerDetails("claude", serverName, entry)
|
|
1441
|
+
: cloneJsonLike(entry);
|
|
1442
|
+
outputShow(filePath, serverName, details);
|
|
1396
1443
|
}
|
|
1397
1444
|
|
|
1398
1445
|
function normalizeServerDetails(clientType, serverName, entry, projectKey) {
|
|
@@ -1439,26 +1486,72 @@ function compactServerDetails(details) {
|
|
|
1439
1486
|
return compact;
|
|
1440
1487
|
}
|
|
1441
1488
|
|
|
1489
|
+
function cloneJsonLike(value) {
|
|
1490
|
+
if (value === null || value === undefined) {
|
|
1491
|
+
return value;
|
|
1492
|
+
}
|
|
1493
|
+
try {
|
|
1494
|
+
return JSON.parse(JSON.stringify(value));
|
|
1495
|
+
} catch {
|
|
1496
|
+
return value;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1442
1500
|
function parseServerArgs(args) {
|
|
1443
1501
|
const parsed = {
|
|
1444
1502
|
transport: null,
|
|
1503
|
+
envName: null,
|
|
1445
1504
|
envPath: null,
|
|
1446
1505
|
mcpDestination: null,
|
|
1447
1506
|
useSessionEnv: false,
|
|
1448
1507
|
};
|
|
1449
|
-
for (
|
|
1508
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
1509
|
+
const arg = args[i];
|
|
1450
1510
|
if (typeof arg !== "string") {
|
|
1451
1511
|
continue;
|
|
1452
1512
|
}
|
|
1453
1513
|
if (arg.startsWith("--transport=")) {
|
|
1454
1514
|
const value = arg.slice("--transport=".length);
|
|
1455
1515
|
parsed.transport = value === "streamableHttp" ? "http" : value;
|
|
1456
|
-
} else if (arg === "--env") {
|
|
1516
|
+
} else if (arg === "--session-env") {
|
|
1457
1517
|
parsed.useSessionEnv = true;
|
|
1458
|
-
} else if (arg.startsWith("--env
|
|
1518
|
+
} else if (arg.startsWith("--env=")) {
|
|
1519
|
+
const value = arg.slice("--env=".length);
|
|
1520
|
+
if (looksLikeEnvPath(value)) {
|
|
1521
|
+
parsed.envPath = value;
|
|
1522
|
+
} else {
|
|
1523
|
+
parsed.envName = value;
|
|
1524
|
+
}
|
|
1525
|
+
} else if (arg === "--env") {
|
|
1526
|
+
const next = args[i + 1];
|
|
1527
|
+
if (typeof next === "string" && next && !next.startsWith("-")) {
|
|
1528
|
+
if (looksLikeEnvPath(next)) {
|
|
1529
|
+
// Backward-compatible form: --env /path/to/.env
|
|
1530
|
+
parsed.envPath = next;
|
|
1531
|
+
} else {
|
|
1532
|
+
parsed.envName = next;
|
|
1533
|
+
}
|
|
1534
|
+
parsed.useSessionEnv = false;
|
|
1535
|
+
i += 1;
|
|
1536
|
+
} else {
|
|
1537
|
+
parsed.useSessionEnv = true;
|
|
1538
|
+
}
|
|
1539
|
+
} else if (arg.startsWith("--env-path=")) {
|
|
1459
1540
|
parsed.envPath = arg.slice(arg.indexOf("=") + 1);
|
|
1541
|
+
} else if (arg === "--env-path") {
|
|
1542
|
+
const next = args[i + 1];
|
|
1543
|
+
if (typeof next === "string" && next && !next.startsWith("-")) {
|
|
1544
|
+
parsed.envPath = next;
|
|
1545
|
+
i += 1;
|
|
1546
|
+
}
|
|
1460
1547
|
} else if (arg.startsWith("--mcp=")) {
|
|
1461
1548
|
parsed.mcpDestination = arg.slice("--mcp=".length);
|
|
1549
|
+
} else if (arg === "--mcp") {
|
|
1550
|
+
const next = args[i + 1];
|
|
1551
|
+
if (typeof next === "string" && next && !next.startsWith("-")) {
|
|
1552
|
+
parsed.mcpDestination = next;
|
|
1553
|
+
i += 1;
|
|
1554
|
+
}
|
|
1462
1555
|
}
|
|
1463
1556
|
}
|
|
1464
1557
|
return parsed;
|
|
@@ -1534,15 +1627,31 @@ function inferAuth(parsedArgs) {
|
|
|
1534
1627
|
if (parsedArgs.mcpDestination) {
|
|
1535
1628
|
return { type: "mcp", value: parsedArgs.mcpDestination };
|
|
1536
1629
|
}
|
|
1630
|
+
if (parsedArgs.envName) {
|
|
1631
|
+
return { type: "env", value: parsedArgs.envName };
|
|
1632
|
+
}
|
|
1537
1633
|
if (parsedArgs.envPath) {
|
|
1538
1634
|
return { type: "env-path", value: parsedArgs.envPath };
|
|
1539
1635
|
}
|
|
1540
1636
|
if (parsedArgs.useSessionEnv) {
|
|
1541
|
-
return { type: "env", value: null };
|
|
1637
|
+
return { type: "session-env", value: null };
|
|
1542
1638
|
}
|
|
1543
1639
|
return { type: "unknown", value: null };
|
|
1544
1640
|
}
|
|
1545
1641
|
|
|
1642
|
+
function looksLikeEnvPath(value) {
|
|
1643
|
+
if (!value || typeof value !== "string") {
|
|
1644
|
+
return false;
|
|
1645
|
+
}
|
|
1646
|
+
return (
|
|
1647
|
+
value.includes("/") ||
|
|
1648
|
+
value.includes("\\") ||
|
|
1649
|
+
value.endsWith(".env") ||
|
|
1650
|
+
value.startsWith(".") ||
|
|
1651
|
+
value.startsWith("~")
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1546
1655
|
function readJson(filePath) {
|
|
1547
1656
|
if (!fs.existsSync(filePath)) {
|
|
1548
1657
|
return {};
|
|
@@ -1643,7 +1752,7 @@ function printHelp(command) {
|
|
|
1643
1752
|
process.stdout.write(`${header}
|
|
1644
1753
|
|
|
1645
1754
|
Usage:
|
|
1646
|
-
mcp-conf <add|rm|ls|enable|disable|where|
|
|
1755
|
+
mcp-conf <add|rm|ls|show|enable|disable|where|update> --client <name> [options]
|
|
1647
1756
|
mcp-conf tui
|
|
1648
1757
|
mcp-conf help <command>
|
|
1649
1758
|
|
|
@@ -1651,10 +1760,10 @@ Commands:
|
|
|
1651
1760
|
add add or update an MCP server entry
|
|
1652
1761
|
rm remove an MCP server entry
|
|
1653
1762
|
ls list MCP server entries
|
|
1763
|
+
show show server configuration details
|
|
1654
1764
|
enable enable an existing entry
|
|
1655
1765
|
disable disable an existing entry
|
|
1656
1766
|
where show where a server name is defined
|
|
1657
|
-
show show server configuration details
|
|
1658
1767
|
update update an existing server entry
|
|
1659
1768
|
tui interactive setup wizard
|
|
1660
1769
|
|
|
@@ -1674,13 +1783,14 @@ Notes:
|
|
|
1674
1783
|
process.stdout.write(`${header} add
|
|
1675
1784
|
|
|
1676
1785
|
Usage:
|
|
1677
|
-
mcp-conf add --client <name> --name <serverName> [--env | --env-path <path> | --mcp <dest>] [options]
|
|
1786
|
+
mcp-conf add --client <name> --name <serverName> [--env <name> | --env-path <path> | --session-env | --mcp <dest>] [options]
|
|
1678
1787
|
|
|
1679
1788
|
Options:
|
|
1680
1789
|
--client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | crush (repeatable)
|
|
1681
1790
|
--name <serverName> required MCP server name key
|
|
1682
|
-
--env
|
|
1791
|
+
--env <name> env profile name (stdio only), writes --env=<name>
|
|
1683
1792
|
--env-path <path> .env path (stdio only)
|
|
1793
|
+
--session-env use current shell/session env vars (stdio only)
|
|
1684
1794
|
--mcp <dest> destination name (stdio only)
|
|
1685
1795
|
--transport <type> stdio | sse | http (http => streamableHttp)
|
|
1686
1796
|
--command <bin> command to run (default: mcp-abap-adt)
|
|
@@ -1803,6 +1913,7 @@ Options:
|
|
|
1803
1913
|
--all-projects Claude global: show from all projects
|
|
1804
1914
|
--project <path> Claude global: target a specific project path
|
|
1805
1915
|
--json output JSON only (machine-readable)
|
|
1916
|
+
--normalized output normalized view (for tooling); default is raw config entry
|
|
1806
1917
|
|
|
1807
1918
|
Notes:
|
|
1808
1919
|
Antigravity local scope is not supported yet; use --global.
|
|
@@ -1812,13 +1923,14 @@ Notes:
|
|
|
1812
1923
|
process.stdout.write(`${header} update
|
|
1813
1924
|
|
|
1814
1925
|
Usage:
|
|
1815
|
-
mcp-conf update --client <name> --name <serverName> [--env | --env-path <path> | --mcp <dest>] [options]
|
|
1926
|
+
mcp-conf update --client <name> --name <serverName> [--env <name> | --env-path <path> | --session-env | --mcp <dest>] [options]
|
|
1816
1927
|
|
|
1817
1928
|
Options:
|
|
1818
1929
|
--client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | crush (repeatable)
|
|
1819
1930
|
--name <serverName> required MCP server name key
|
|
1820
|
-
--env
|
|
1931
|
+
--env <name> env profile name (stdio only), writes --env=<name>
|
|
1821
1932
|
--env-path <path> .env path (stdio only)
|
|
1933
|
+
--session-env use current shell/session env vars (stdio only)
|
|
1822
1934
|
--mcp <dest> destination name (stdio only)
|
|
1823
1935
|
--transport <type> stdio | sse | http (http => streamableHttp)
|
|
1824
1936
|
--url <http(s)://...> required for sse/http
|
|
@@ -1840,7 +1952,7 @@ Usage:
|
|
|
1840
1952
|
mcp-conf tui
|
|
1841
1953
|
|
|
1842
1954
|
Description:
|
|
1843
|
-
Start interactive setup wizard for ls/add/
|
|
1955
|
+
Start interactive setup wizard for ls/show/add/update/rm/enable/disable.
|
|
1844
1956
|
Flow: operation -> client -> scope (skips scope when only one is supported).
|
|
1845
1957
|
For add/update + sse/http, wizard also asks timeout and repeatable headers.
|
|
1846
1958
|
For rm/enable/disable/show/update, wizard selects server from existing entries.
|
|
@@ -92,10 +92,11 @@ mcp-conf tui
|
|
|
92
92
|
- Controls: arrow keys + Enter, Ctrl+C to cancel.
|
|
93
93
|
|
|
94
94
|
Options:
|
|
95
|
-
- Commands: `add`, `rm`, `ls`, `enable`, `disable`, `where`, `
|
|
95
|
+
- Commands: `add`, `rm`, `ls`, `show`, `enable`, `disable`, `where`, `update`, `tui` (first argument)
|
|
96
96
|
- `--client <name>` (repeatable): `cline`, `codex`, `claude`, `goose`, `cursor`, `windsurf`, `opencode` (`kilo` alias), `copilot`, `antigravity`, `crush`
|
|
97
|
-
- `--env
|
|
97
|
+
- `--env <name>`: use named env profile; writes `--env=<name>` (stdio only)
|
|
98
98
|
- `--env-path <path>`: use a specific `.env` file (stdio only)
|
|
99
|
+
- `--session-env`: use shell/session environment variables (stdio only)
|
|
99
100
|
- `--mcp <destination>`: use service key destination
|
|
100
101
|
- `--name <serverName>`: MCP server name (required)
|
|
101
102
|
- `--transport <type>`: `stdio`, `sse`, or `http` (`http` maps to `streamableHttp`)
|
|
@@ -110,9 +111,9 @@ Options:
|
|
|
110
111
|
- `--json`: JSON-only output for `show`
|
|
111
112
|
|
|
112
113
|
Notes:
|
|
113
|
-
- `disable` and `rm` do not require `--env`, `--env-path`, or `--mcp`.
|
|
114
|
-
- `--env`/`--env-path`/`--mcp` are only valid for `stdio` transport. For `sse/http`, use `--url` and optional `--header`.
|
|
115
|
-
- `mcp-conf tui` starts an interactive wizard for `ls`/`add`/`
|
|
114
|
+
- `disable` and `rm` do not require `--env`, `--env-path`, `--session-env`, or `--mcp`.
|
|
115
|
+
- `--env`/`--env-path`/`--session-env`/`--mcp` are only valid for `stdio` transport. For `sse/http`, use `--url` and optional `--header`.
|
|
116
|
+
- `mcp-conf tui` starts an interactive wizard for `ls`/`show`/`add`/`update`/`rm`/`enable`/`disable`.
|
|
116
117
|
- Cursor/Copilot enable/disable are not implemented yet.
|
|
117
118
|
- Antigravity enable/disable uses `disabled: true|false` on the entry.
|
|
118
119
|
- Antigravity local scope is not supported yet; use `--global`.
|
|
@@ -121,7 +122,7 @@ Notes:
|
|
|
121
122
|
- Antigravity HTTP entries use `serverUrl` instead of `url`.
|
|
122
123
|
- New entries for Cline, Codex, Windsurf, Goose, Claude, OpenCode, and Crush are added **disabled by default**. Use `enable` to turn them on.
|
|
123
124
|
- Windsurf follows `disabled` like Cline. The configurator sets `disabled = true` for default-disabled entries.
|
|
124
|
-
- `enable`/`disable` only work if the server entry already exists. Use add commands with `--env`, `--env-path`, or `--mcp` first.
|
|
125
|
+
- `enable`/`disable` only work if the server entry already exists. Use add commands with `--env`, `--env-path`, `--session-env`, or `--mcp` first.
|
|
125
126
|
- Non-stdio transports are supported for Cline/Cursor/Windsurf/Claude/Goose. Codex supports `http` (streamable HTTP) but not `sse`.
|
|
126
127
|
- Codex writes custom headers under `http_headers` in `~/.codex/config.toml` (or `./.codex/config.toml` for `--local`).
|
|
127
128
|
- Codex HTTP entries include `startup_timeout_sec` (default: 60).
|