@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 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 | Purpose | Default |
240
- | -------------------- | --------------------------------- | ------------------- |
241
- | `MCP_CONFIG_PATH` | Config directory path | `~/.config/mcpcli/` |
242
- | `MCP_DEBUG` | Enable debug output | `false` |
243
- | `MCP_TIMEOUT` | Request timeout (seconds) | `1800` |
244
- | `MCP_CONCURRENCY` | Parallel server connections | `5` |
245
- | `MCP_MAX_RETRIES` | Retry attempts | `3` |
246
- | `MCP_STRICT_ENV` | Error on missing `${VAR}` | `true` |
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evantahler/mcpcli",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "A command-line interface for MCP servers. curl for MCP.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,10 @@
1
1
  import { exec } from "child_process";
2
2
  import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
3
- import { auth, refreshAuthorization } from "@modelcontextprotocol/sdk/client/auth.js";
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
@@ -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);