@evantahler/mcpcli 0.1.4 → 0.2.1
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 +54 -0
- package/package.json +1 -1
- package/src/cli.ts +5 -2
- package/src/client/manager.ts +83 -21
- package/src/commands/add.ts +129 -0
- package/src/commands/remove.ts +43 -0
- package/src/config/loader.ts +32 -0
- package/src/context.ts +15 -6
package/README.md
CHANGED
|
@@ -65,6 +65,9 @@ mcpcli search -q "manage pull requests"
|
|
|
65
65
|
| `mcpcli auth <server> -s` | Check auth status and token TTL |
|
|
66
66
|
| `mcpcli auth <server> -r` | Force token refresh |
|
|
67
67
|
| `mcpcli deauth <server>` | Remove stored authentication for a server |
|
|
68
|
+
| `mcpcli add <name> --command <cmd>` | Add a stdio MCP server to your config |
|
|
69
|
+
| `mcpcli add <name> --url <url>` | Add an HTTP MCP server to your config |
|
|
70
|
+
| `mcpcli remove <name>` | Remove an MCP server from your config |
|
|
68
71
|
|
|
69
72
|
## Options
|
|
70
73
|
|
|
@@ -79,6 +82,57 @@ mcpcli search -q "manage pull requests"
|
|
|
79
82
|
| `-j, --json` | Force JSON output (default when piped) |
|
|
80
83
|
| `--no-daemon` | Disable connection pooling |
|
|
81
84
|
|
|
85
|
+
## Managing Servers
|
|
86
|
+
|
|
87
|
+
Add and remove servers from the CLI — no manual JSON editing required.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Add a stdio server
|
|
91
|
+
mcpcli add filesystem --command npx --args "-y,@modelcontextprotocol/server-filesystem,/tmp"
|
|
92
|
+
|
|
93
|
+
# Add an HTTP server with headers
|
|
94
|
+
mcpcli add my-api --url https://api.example.com/mcp --header "Authorization:Bearer tok123"
|
|
95
|
+
|
|
96
|
+
# Add with tool filtering
|
|
97
|
+
mcpcli add github --url https://mcp.github.com --allowed-tools "search_*,get_*"
|
|
98
|
+
|
|
99
|
+
# Add with environment variables
|
|
100
|
+
mcpcli add my-server --command node --args "server.js" --env "API_KEY=sk-123,DEBUG=true"
|
|
101
|
+
|
|
102
|
+
# Overwrite an existing server
|
|
103
|
+
mcpcli add filesystem --command echo --force
|
|
104
|
+
|
|
105
|
+
# Remove a server (also cleans up auth.json)
|
|
106
|
+
mcpcli remove filesystem
|
|
107
|
+
|
|
108
|
+
# Remove but keep stored auth credentials
|
|
109
|
+
mcpcli remove my-api --keep-auth
|
|
110
|
+
|
|
111
|
+
# Preview what would be removed
|
|
112
|
+
mcpcli remove my-api --dry-run
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**`add` options:**
|
|
116
|
+
|
|
117
|
+
| Flag | Purpose |
|
|
118
|
+
| -------------------------- | -------------------------------------- |
|
|
119
|
+
| `--command <cmd>` | Command to run (stdio server) |
|
|
120
|
+
| `--args <a1,a2,...>` | Comma-separated arguments |
|
|
121
|
+
| `--env <KEY=VAL,...>` | Comma-separated environment variables |
|
|
122
|
+
| `--cwd <dir>` | Working directory for the command |
|
|
123
|
+
| `--url <url>` | Server URL (HTTP server) |
|
|
124
|
+
| `--header <Key:Value>` | HTTP header (repeatable) |
|
|
125
|
+
| `--allowed-tools <t1,t2>` | Comma-separated allowed tool patterns |
|
|
126
|
+
| `--disabled-tools <t1,t2>` | Comma-separated disabled tool patterns |
|
|
127
|
+
| `-f, --force` | Overwrite if server already exists |
|
|
128
|
+
|
|
129
|
+
**`remove` options:**
|
|
130
|
+
|
|
131
|
+
| Flag | Purpose |
|
|
132
|
+
| ------------- | ------------------------------------------------- |
|
|
133
|
+
| `--keep-auth` | Don't remove stored auth credentials |
|
|
134
|
+
| `--dry-run` | Show what would be removed without changing files |
|
|
135
|
+
|
|
82
136
|
## Configuration
|
|
83
137
|
|
|
84
138
|
Config lives in `~/.config/mcpcli/` (or the current directory). Three files:
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -7,6 +7,8 @@ import { registerSearchCommand } from "./commands/search.ts";
|
|
|
7
7
|
import { registerCallCommand } from "./commands/call.ts";
|
|
8
8
|
import { registerAuthCommand, registerDeauthCommand } from "./commands/auth.ts";
|
|
9
9
|
import { registerIndexCommand } from "./commands/index.ts";
|
|
10
|
+
import { registerAddCommand } from "./commands/add.ts";
|
|
11
|
+
import { registerRemoveCommand } from "./commands/remove.ts";
|
|
10
12
|
|
|
11
13
|
declare const BUILD_VERSION: string | undefined;
|
|
12
14
|
|
|
@@ -20,8 +22,7 @@ program
|
|
|
20
22
|
.option("-d, --with-descriptions", "include tool descriptions in output")
|
|
21
23
|
.option("-j, --json", "force JSON output")
|
|
22
24
|
.option("-v, --verbose", "show HTTP request/response details")
|
|
23
|
-
.option("-S, --show-secrets", "show full auth tokens in verbose output")
|
|
24
|
-
.option("--no-daemon", "disable connection pooling");
|
|
25
|
+
.option("-S, --show-secrets", "show full auth tokens in verbose output");
|
|
25
26
|
|
|
26
27
|
registerListCommand(program);
|
|
27
28
|
registerInfoCommand(program);
|
|
@@ -30,5 +31,7 @@ registerCallCommand(program);
|
|
|
30
31
|
registerAuthCommand(program);
|
|
31
32
|
registerDeauthCommand(program);
|
|
32
33
|
registerIndexCommand(program);
|
|
34
|
+
registerAddCommand(program);
|
|
35
|
+
registerRemoveCommand(program);
|
|
33
36
|
|
|
34
37
|
program.parse();
|
package/src/client/manager.ts
CHANGED
|
@@ -17,6 +17,17 @@ export interface ServerError {
|
|
|
17
17
|
message: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export interface ServerManagerOptions {
|
|
21
|
+
servers: ServersFile;
|
|
22
|
+
configDir: string;
|
|
23
|
+
auth: AuthFile;
|
|
24
|
+
concurrency?: number;
|
|
25
|
+
verbose?: boolean;
|
|
26
|
+
showSecrets?: boolean;
|
|
27
|
+
timeout?: number; // ms, default 1_800_000 (30 min)
|
|
28
|
+
maxRetries?: number; // default 3
|
|
29
|
+
}
|
|
30
|
+
|
|
20
31
|
export class ServerManager {
|
|
21
32
|
private clients = new Map<string, Client>();
|
|
22
33
|
private transports = new Map<string, Transport>();
|
|
@@ -27,21 +38,18 @@ export class ServerManager {
|
|
|
27
38
|
private concurrency: number;
|
|
28
39
|
private verbose: boolean;
|
|
29
40
|
private showSecrets: boolean;
|
|
41
|
+
private timeout: number;
|
|
42
|
+
private maxRetries: number;
|
|
30
43
|
|
|
31
|
-
constructor(
|
|
32
|
-
servers
|
|
33
|
-
configDir
|
|
34
|
-
auth
|
|
35
|
-
concurrency = 5
|
|
36
|
-
verbose = false
|
|
37
|
-
showSecrets = false
|
|
38
|
-
|
|
39
|
-
this.
|
|
40
|
-
this.configDir = configDir;
|
|
41
|
-
this.auth = auth;
|
|
42
|
-
this.concurrency = concurrency;
|
|
43
|
-
this.verbose = verbose;
|
|
44
|
-
this.showSecrets = showSecrets;
|
|
44
|
+
constructor(opts: ServerManagerOptions) {
|
|
45
|
+
this.servers = opts.servers;
|
|
46
|
+
this.configDir = opts.configDir;
|
|
47
|
+
this.auth = opts.auth;
|
|
48
|
+
this.concurrency = opts.concurrency ?? 5;
|
|
49
|
+
this.verbose = opts.verbose ?? false;
|
|
50
|
+
this.showSecrets = opts.showSecrets ?? false;
|
|
51
|
+
this.timeout = opts.timeout ?? 1_800_000;
|
|
52
|
+
this.maxRetries = opts.maxRetries ?? 3;
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
/** Get or create a connected client for a server */
|
|
@@ -71,7 +79,7 @@ export class ServerManager {
|
|
|
71
79
|
this.transports.set(serverName, transport);
|
|
72
80
|
|
|
73
81
|
const client = new Client({ name: "mcpcli", version: "0.1.0" });
|
|
74
|
-
await client.connect(transport);
|
|
82
|
+
await this.withTimeout(client.connect(transport), `connect(${serverName})`);
|
|
75
83
|
this.clients.set(serverName, client);
|
|
76
84
|
|
|
77
85
|
return client;
|
|
@@ -110,12 +118,57 @@ export class ServerManager {
|
|
|
110
118
|
throw new Error("Invalid server config");
|
|
111
119
|
}
|
|
112
120
|
|
|
121
|
+
/** Race a promise against a timeout */
|
|
122
|
+
private withTimeout<T>(promise: Promise<T>, label: string): Promise<T> {
|
|
123
|
+
if (this.timeout <= 0) return promise;
|
|
124
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
125
|
+
return Promise.race([
|
|
126
|
+
promise.finally(() => clearTimeout(timer)),
|
|
127
|
+
new Promise<never>((_, reject) => {
|
|
128
|
+
timer = setTimeout(
|
|
129
|
+
() => reject(new Error(`${label}: timed out after ${this.timeout / 1000}s`)),
|
|
130
|
+
this.timeout,
|
|
131
|
+
);
|
|
132
|
+
timer.unref();
|
|
133
|
+
}),
|
|
134
|
+
]);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Retry a function up to maxRetries times, clearing cached client between attempts */
|
|
138
|
+
private async withRetry<T>(fn: () => Promise<T>, label: string, serverName?: string): Promise<T> {
|
|
139
|
+
let lastError: Error | undefined;
|
|
140
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
141
|
+
try {
|
|
142
|
+
return await fn();
|
|
143
|
+
} catch (err) {
|
|
144
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
145
|
+
if (attempt < this.maxRetries && serverName) {
|
|
146
|
+
// Clear cached client so next attempt reconnects fresh
|
|
147
|
+
try {
|
|
148
|
+
await this.clients.get(serverName)?.close();
|
|
149
|
+
} catch {
|
|
150
|
+
// ignore close errors
|
|
151
|
+
}
|
|
152
|
+
this.clients.delete(serverName);
|
|
153
|
+
this.transports.delete(serverName);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
throw lastError;
|
|
158
|
+
}
|
|
159
|
+
|
|
113
160
|
/** List tools for a single server, applying allowedTools/disabledTools filters */
|
|
114
161
|
async listTools(serverName: string): Promise<Tool[]> {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
162
|
+
return this.withRetry(
|
|
163
|
+
async () => {
|
|
164
|
+
const client = await this.getClient(serverName);
|
|
165
|
+
const result = await this.withTimeout(client.listTools(), `listTools(${serverName})`);
|
|
166
|
+
const config = this.servers.mcpServers[serverName]!;
|
|
167
|
+
return filterTools(result.tools, config.allowedTools, config.disabledTools);
|
|
168
|
+
},
|
|
169
|
+
`listTools(${serverName})`,
|
|
170
|
+
serverName,
|
|
171
|
+
);
|
|
119
172
|
}
|
|
120
173
|
|
|
121
174
|
/** List tools across all configured servers */
|
|
@@ -156,8 +209,17 @@ export class ServerManager {
|
|
|
156
209
|
toolName: string,
|
|
157
210
|
args: Record<string, unknown> = {},
|
|
158
211
|
): Promise<unknown> {
|
|
159
|
-
|
|
160
|
-
|
|
212
|
+
return this.withRetry(
|
|
213
|
+
async () => {
|
|
214
|
+
const client = await this.getClient(serverName);
|
|
215
|
+
return this.withTimeout(
|
|
216
|
+
client.callTool({ name: toolName, arguments: args }),
|
|
217
|
+
`callTool(${serverName}/${toolName})`,
|
|
218
|
+
);
|
|
219
|
+
},
|
|
220
|
+
`callTool(${serverName}/${toolName})`,
|
|
221
|
+
serverName,
|
|
222
|
+
);
|
|
161
223
|
}
|
|
162
224
|
|
|
163
225
|
/** Get the schema for a specific tool */
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { ServerConfig } from "../config/schemas.ts";
|
|
3
|
+
import { loadRawServers, saveServers } from "../config/loader.ts";
|
|
4
|
+
|
|
5
|
+
export function registerAddCommand(program: Command) {
|
|
6
|
+
program
|
|
7
|
+
.command("add <name>")
|
|
8
|
+
.description("add an MCP server to your config")
|
|
9
|
+
.option("--command <cmd>", "command to run (stdio server)")
|
|
10
|
+
.option("--args <args>", "comma-separated arguments for the command")
|
|
11
|
+
.option("--env <vars>", "comma-separated KEY=VAL environment variables")
|
|
12
|
+
.option("--cwd <dir>", "working directory for the command")
|
|
13
|
+
.option("--url <url>", "server URL (HTTP server)")
|
|
14
|
+
.option("--header <h>", "header in Key:Value format (repeatable)", collect, [])
|
|
15
|
+
.option("--allowed-tools <tools>", "comma-separated list of allowed tools")
|
|
16
|
+
.option("--disabled-tools <tools>", "comma-separated list of disabled tools")
|
|
17
|
+
.option("-f, --force", "overwrite if server already exists")
|
|
18
|
+
.action(
|
|
19
|
+
async (
|
|
20
|
+
name: string,
|
|
21
|
+
options: {
|
|
22
|
+
command?: string;
|
|
23
|
+
args?: string;
|
|
24
|
+
env?: string;
|
|
25
|
+
cwd?: string;
|
|
26
|
+
url?: string;
|
|
27
|
+
header?: string[];
|
|
28
|
+
allowedTools?: string;
|
|
29
|
+
disabledTools?: string;
|
|
30
|
+
force?: boolean;
|
|
31
|
+
},
|
|
32
|
+
) => {
|
|
33
|
+
const hasCommand = !!options.command;
|
|
34
|
+
const hasUrl = !!options.url;
|
|
35
|
+
|
|
36
|
+
if (!hasCommand && !hasUrl) {
|
|
37
|
+
console.error("Must specify --command (stdio) or --url (http)");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
if (hasCommand && hasUrl) {
|
|
41
|
+
console.error("Cannot specify both --command and --url");
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const configFlag = program.opts().config;
|
|
46
|
+
const { configDir, servers } = await loadRawServers(configFlag);
|
|
47
|
+
|
|
48
|
+
if (servers.mcpServers[name] && !options.force) {
|
|
49
|
+
console.error(`Server "${name}" already exists (use --force to overwrite)`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let config: ServerConfig;
|
|
54
|
+
|
|
55
|
+
if (hasCommand) {
|
|
56
|
+
config = buildStdioConfig(options);
|
|
57
|
+
} else {
|
|
58
|
+
config = buildHttpConfig(options);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Common options
|
|
62
|
+
if (options.allowedTools) {
|
|
63
|
+
config.allowedTools = options.allowedTools.split(",").map((t) => t.trim());
|
|
64
|
+
}
|
|
65
|
+
if (options.disabledTools) {
|
|
66
|
+
config.disabledTools = options.disabledTools.split(",").map((t) => t.trim());
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
servers.mcpServers[name] = config;
|
|
70
|
+
await saveServers(configDir, servers);
|
|
71
|
+
console.log(`Added server "${name}" to ${configDir}/servers.json`);
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function collect(value: string, previous: string[]): string[] {
|
|
77
|
+
return previous.concat([value]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function buildStdioConfig(options: {
|
|
81
|
+
command?: string;
|
|
82
|
+
args?: string;
|
|
83
|
+
env?: string;
|
|
84
|
+
cwd?: string;
|
|
85
|
+
}): ServerConfig {
|
|
86
|
+
const config: Record<string, unknown> = { command: options.command! };
|
|
87
|
+
|
|
88
|
+
if (options.args) {
|
|
89
|
+
config.args = options.args.split(",").map((a) => a.trim());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (options.env) {
|
|
93
|
+
const env: Record<string, string> = {};
|
|
94
|
+
for (const pair of options.env.split(",")) {
|
|
95
|
+
const eqIdx = pair.indexOf("=");
|
|
96
|
+
if (eqIdx === -1) {
|
|
97
|
+
console.error(`Invalid env format "${pair}", expected KEY=VAL`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
env[pair.slice(0, eqIdx).trim()] = pair.slice(eqIdx + 1).trim();
|
|
101
|
+
}
|
|
102
|
+
config.env = env;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (options.cwd) {
|
|
106
|
+
config.cwd = options.cwd;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return config as ServerConfig;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function buildHttpConfig(options: { url?: string; header?: string[] }): ServerConfig {
|
|
113
|
+
const config: Record<string, unknown> = { url: options.url! };
|
|
114
|
+
|
|
115
|
+
if (options.header && options.header.length > 0) {
|
|
116
|
+
const headers: Record<string, string> = {};
|
|
117
|
+
for (const h of options.header) {
|
|
118
|
+
const colonIdx = h.indexOf(":");
|
|
119
|
+
if (colonIdx === -1) {
|
|
120
|
+
console.error(`Invalid header format "${h}", expected Key:Value`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
headers[h.slice(0, colonIdx).trim()] = h.slice(colonIdx + 1).trim();
|
|
124
|
+
}
|
|
125
|
+
config.headers = headers;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return config as ServerConfig;
|
|
129
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { loadRawServers, loadRawAuth, saveServers, saveAuth } from "../config/loader.ts";
|
|
3
|
+
|
|
4
|
+
export function registerRemoveCommand(program: Command) {
|
|
5
|
+
program
|
|
6
|
+
.command("remove <name>")
|
|
7
|
+
.description("remove an MCP server from your config")
|
|
8
|
+
.option("--keep-auth", "keep stored authentication credentials")
|
|
9
|
+
.option("--dry-run", "show what would be removed without changing files")
|
|
10
|
+
.action(async (name: string, options: { keepAuth?: boolean; dryRun?: boolean }) => {
|
|
11
|
+
const configFlag = program.opts().config;
|
|
12
|
+
const { configDir, servers } = await loadRawServers(configFlag);
|
|
13
|
+
|
|
14
|
+
if (!servers.mcpServers[name]) {
|
|
15
|
+
console.error(`Unknown server: "${name}"`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (options.dryRun) {
|
|
20
|
+
console.log(`Would remove server "${name}" from ${configDir}/servers.json`);
|
|
21
|
+
if (!options.keepAuth) {
|
|
22
|
+
const auth = await loadRawAuth(configDir);
|
|
23
|
+
if (auth[name]) {
|
|
24
|
+
console.log(`Would remove auth for "${name}" from ${configDir}/auth.json`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
delete servers.mcpServers[name];
|
|
31
|
+
await saveServers(configDir, servers);
|
|
32
|
+
console.log(`Removed server "${name}" from ${configDir}/servers.json`);
|
|
33
|
+
|
|
34
|
+
if (!options.keepAuth) {
|
|
35
|
+
const auth = await loadRawAuth(configDir);
|
|
36
|
+
if (auth[name]) {
|
|
37
|
+
delete auth[name];
|
|
38
|
+
await saveAuth(configDir, auth);
|
|
39
|
+
console.log(`Removed auth for "${name}" from ${configDir}/auth.json`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
package/src/config/loader.ts
CHANGED
|
@@ -116,3 +116,35 @@ export async function saveAuth(configDir: string, auth: AuthFile): Promise<void>
|
|
|
116
116
|
export async function saveSearchIndex(configDir: string, index: SearchIndex): Promise<void> {
|
|
117
117
|
await Bun.write(join(configDir, "search.json"), JSON.stringify(index, null, 2) + "\n");
|
|
118
118
|
}
|
|
119
|
+
|
|
120
|
+
/** Save servers.json to the config directory */
|
|
121
|
+
export async function saveServers(configDir: string, servers: ServersFile): Promise<void> {
|
|
122
|
+
await Bun.write(join(configDir, "servers.json"), JSON.stringify(servers, null, 2) + "\n");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Load servers.json without env interpolation (preserves ${VAR} placeholders) */
|
|
126
|
+
export async function loadRawServers(
|
|
127
|
+
configFlag?: string,
|
|
128
|
+
): Promise<{ configDir: string; servers: ServersFile }> {
|
|
129
|
+
let configDir = resolveConfigDir(configFlag);
|
|
130
|
+
|
|
131
|
+
if (!(await hasServersFile(configDir))) {
|
|
132
|
+
const cwd = process.cwd();
|
|
133
|
+
if (await hasServersFile(cwd)) {
|
|
134
|
+
configDir = cwd;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const serversPath = join(configDir, "servers.json");
|
|
139
|
+
const raw = await readJsonFile(serversPath);
|
|
140
|
+
const servers = raw !== undefined ? validateServersFile(raw) : EMPTY_SERVERS;
|
|
141
|
+
|
|
142
|
+
return { configDir, servers };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Load auth.json without loading the full config */
|
|
146
|
+
export async function loadRawAuth(configDir: string): Promise<AuthFile> {
|
|
147
|
+
const authPath = join(configDir, "auth.json");
|
|
148
|
+
const raw = await readJsonFile(authPath);
|
|
149
|
+
return raw !== undefined ? validateAuthFile(raw) : EMPTY_AUTH;
|
|
150
|
+
}
|
package/src/context.ts
CHANGED
|
@@ -18,17 +18,26 @@ export async function getContext(program: Command): Promise<AppContext> {
|
|
|
18
18
|
configFlag: opts.config as string | undefined,
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
const verbose = !!(
|
|
21
|
+
const verbose = !!(
|
|
22
|
+
(opts.verbose as boolean | undefined) ||
|
|
23
|
+
process.env.MCP_DEBUG === "1" ||
|
|
24
|
+
process.env.MCP_DEBUG === "true"
|
|
25
|
+
);
|
|
22
26
|
const showSecrets = !!(opts.showSecrets as boolean | undefined);
|
|
23
27
|
const concurrency = Number(process.env.MCP_CONCURRENCY ?? 5);
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
const timeout = Number(process.env.MCP_TIMEOUT ?? 1800) * 1000;
|
|
29
|
+
const maxRetries = Number(process.env.MCP_MAX_RETRIES ?? 3);
|
|
30
|
+
|
|
31
|
+
const manager = new ServerManager({
|
|
32
|
+
servers: config.servers,
|
|
33
|
+
configDir: config.configDir,
|
|
34
|
+
auth: config.auth,
|
|
28
35
|
concurrency,
|
|
29
36
|
verbose,
|
|
30
37
|
showSecrets,
|
|
31
|
-
|
|
38
|
+
timeout,
|
|
39
|
+
maxRetries,
|
|
40
|
+
});
|
|
32
41
|
|
|
33
42
|
const formatOptions: FormatOptions = {
|
|
34
43
|
json: opts.json as boolean | undefined,
|