@evantahler/mcpcli 0.2.3 → 0.3.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 +8 -17
- package/package.json +1 -1
- package/src/client/oauth.ts +38 -1
- package/src/commands/add.ts +19 -1
package/README.md
CHANGED
|
@@ -80,7 +80,6 @@ mcpcli search -q "manage pull requests"
|
|
|
80
80
|
| `-v, --verbose` | Show HTTP request/response headers and timing |
|
|
81
81
|
| `-S, --show-secrets` | Show full auth tokens in verbose output (unmasked) |
|
|
82
82
|
| `-j, --json` | Force JSON output (default when piped) |
|
|
83
|
-
| `--no-daemon` | Disable connection pooling |
|
|
84
83
|
|
|
85
84
|
## Managing Servers
|
|
86
85
|
|
|
@@ -236,22 +235,14 @@ Scenarios and keywords are extracted heuristically from tool names and descripti
|
|
|
236
235
|
|
|
237
236
|
## Environment Variables
|
|
238
237
|
|
|
239
|
-
| Variable
|
|
240
|
-
|
|
|
241
|
-
| `MCP_CONFIG_PATH`
|
|
242
|
-
| `MCP_DEBUG`
|
|
243
|
-
| `MCP_TIMEOUT`
|
|
244
|
-
| `MCP_CONCURRENCY`
|
|
245
|
-
| `MCP_MAX_RETRIES`
|
|
246
|
-
| `MCP_STRICT_ENV`
|
|
247
|
-
| `MCP_NO_DAEMON` | Disable connection pooling | `false` |
|
|
248
|
-
| `MCP_DAEMON_TIMEOUT` | Idle connection timeout (seconds) | `60` |
|
|
249
|
-
|
|
250
|
-
## Connection Pooling
|
|
251
|
-
|
|
252
|
-
mcpcli runs a lightweight daemon that keeps MCP server connections warm. Stdio processes stay alive and HTTP connections are reused across invocations. The daemon exits after `MCP_DAEMON_TIMEOUT` seconds of inactivity (default 60s).
|
|
253
|
-
|
|
254
|
-
Disable with `--no-daemon` or `MCP_NO_DAEMON=true` for one-shot usage.
|
|
238
|
+
| Variable | Purpose | Default |
|
|
239
|
+
| ----------------- | --------------------------- | ------------------- |
|
|
240
|
+
| `MCP_CONFIG_PATH` | Config directory path | `~/.config/mcpcli/` |
|
|
241
|
+
| `MCP_DEBUG` | Enable debug output | `false` |
|
|
242
|
+
| `MCP_TIMEOUT` | Request timeout (seconds) | `1800` |
|
|
243
|
+
| `MCP_CONCURRENCY` | Parallel server connections | `5` |
|
|
244
|
+
| `MCP_MAX_RETRIES` | Retry attempts | `3` |
|
|
245
|
+
| `MCP_STRICT_ENV` | Error on missing `${VAR}` | `true` |
|
|
255
246
|
|
|
256
247
|
## OAuth Flow
|
|
257
248
|
|
package/package.json
CHANGED
package/src/client/oauth.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { exec } from "child_process";
|
|
2
2
|
import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
auth,
|
|
5
|
+
discoverOAuthServerInfo,
|
|
6
|
+
refreshAuthorization,
|
|
7
|
+
} from "@modelcontextprotocol/sdk/client/auth.js";
|
|
4
8
|
import type {
|
|
5
9
|
OAuthClientMetadata,
|
|
6
10
|
OAuthClientInformationMixed,
|
|
@@ -8,6 +12,8 @@ import type {
|
|
|
8
12
|
} from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
9
13
|
import type { AuthFile } from "../config/schemas.ts";
|
|
10
14
|
import { saveAuth } from "../config/loader.ts";
|
|
15
|
+
import type { FormatOptions } from "../output/formatter.ts";
|
|
16
|
+
import { startSpinner } from "../output/spinner.ts";
|
|
11
17
|
|
|
12
18
|
export class McpOAuthProvider implements OAuthClientProvider {
|
|
13
19
|
private serverName: string;
|
|
@@ -243,6 +249,37 @@ export function startCallbackServer(): {
|
|
|
243
249
|
return { server, authCodePromise };
|
|
244
250
|
}
|
|
245
251
|
|
|
252
|
+
/** Probe for OAuth support and run the auth flow if the server supports it.
|
|
253
|
+
* Returns true if auth ran, false if server doesn't support OAuth (silent skip). */
|
|
254
|
+
export async function tryOAuthIfSupported(
|
|
255
|
+
serverName: string,
|
|
256
|
+
serverUrl: string,
|
|
257
|
+
configDir: string,
|
|
258
|
+
auth: AuthFile,
|
|
259
|
+
formatOptions: FormatOptions,
|
|
260
|
+
): Promise<boolean> {
|
|
261
|
+
let oauthSupported: boolean;
|
|
262
|
+
try {
|
|
263
|
+
const info = await discoverOAuthServerInfo(serverUrl);
|
|
264
|
+
oauthSupported = info.authorizationServerMetadata !== undefined;
|
|
265
|
+
} catch {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!oauthSupported) return false;
|
|
270
|
+
|
|
271
|
+
const provider = new McpOAuthProvider({ serverName, configDir, auth });
|
|
272
|
+
const spinner = startSpinner(`Authenticating with "${serverName}"…`, formatOptions);
|
|
273
|
+
try {
|
|
274
|
+
await runOAuthFlow(serverUrl, provider);
|
|
275
|
+
spinner.success(`Authenticated with "${serverName}"`);
|
|
276
|
+
return true;
|
|
277
|
+
} catch (err) {
|
|
278
|
+
spinner.error(`Authentication failed: ${err instanceof Error ? err.message : err}`);
|
|
279
|
+
throw err;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
246
283
|
/** Run a full OAuth authorization flow for an HTTP MCP server */
|
|
247
284
|
export async function runOAuthFlow(serverUrl: string, provider: McpOAuthProvider): Promise<void> {
|
|
248
285
|
// Clear any leftover state from a previously cancelled auth flow
|
package/src/commands/add.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
2
|
import type { ServerConfig } from "../config/schemas.ts";
|
|
3
|
-
import { loadRawServers, saveServers } from "../config/loader.ts";
|
|
3
|
+
import { loadRawAuth, loadRawServers, saveServers } from "../config/loader.ts";
|
|
4
|
+
import { tryOAuthIfSupported } from "../client/oauth.ts";
|
|
4
5
|
import { runIndex } from "./index.ts";
|
|
5
6
|
|
|
6
7
|
export function registerAddCommand(program: Command) {
|
|
@@ -16,6 +17,7 @@ export function registerAddCommand(program: Command) {
|
|
|
16
17
|
.option("--allowed-tools <tools>", "comma-separated list of allowed tools")
|
|
17
18
|
.option("--disabled-tools <tools>", "comma-separated list of disabled tools")
|
|
18
19
|
.option("-f, --force", "overwrite if server already exists")
|
|
20
|
+
.option("--no-auth", "skip automatic OAuth authentication after adding an HTTP server")
|
|
19
21
|
.option("--no-index", "skip rebuilding the search index after adding")
|
|
20
22
|
.action(
|
|
21
23
|
async (
|
|
@@ -30,6 +32,7 @@ export function registerAddCommand(program: Command) {
|
|
|
30
32
|
allowedTools?: string;
|
|
31
33
|
disabledTools?: string;
|
|
32
34
|
force?: boolean;
|
|
35
|
+
auth?: boolean;
|
|
33
36
|
index?: boolean;
|
|
34
37
|
},
|
|
35
38
|
) => {
|
|
@@ -73,6 +76,21 @@ export function registerAddCommand(program: Command) {
|
|
|
73
76
|
await saveServers(configDir, servers);
|
|
74
77
|
console.log(`Added server "${name}" to ${configDir}/servers.json`);
|
|
75
78
|
|
|
79
|
+
// Auto-auth: probe for OAuth support and run the flow if supported
|
|
80
|
+
if (hasUrl && options.auth !== false) {
|
|
81
|
+
const auth = await loadRawAuth(configDir);
|
|
82
|
+
const formatOptions = {
|
|
83
|
+
json: !!program.opts().json,
|
|
84
|
+
verbose: !!program.opts().verbose,
|
|
85
|
+
showSecrets: false,
|
|
86
|
+
};
|
|
87
|
+
try {
|
|
88
|
+
await tryOAuthIfSupported(name, options.url!, configDir, auth, formatOptions);
|
|
89
|
+
} catch {
|
|
90
|
+
console.error(`Warning: OAuth authentication failed. Run: mcpcli auth ${name}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
76
94
|
// Commander treats --no-index as index=false (default true)
|
|
77
95
|
if (options.index !== false) {
|
|
78
96
|
await runIndex(program);
|