mcpcac 0.0.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/dist/auth.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import type { OAuthFlowResult, StartOAuthFlowOptions } from "./types.js";
2
+ /**
3
+ * Start the OAuth flow for an MCP server.
4
+ * This is an internal function - consumers should not call this directly.
5
+ * It is automatically triggered by addMcpCommands when a 401 error occurs.
6
+ *
7
+ * This function:
8
+ * 1. Starts a local callback server on a random port
9
+ * 2. Initiates OAuth with the MCP server
10
+ * 3. Opens the browser for user authorization
11
+ * 4. Waits for the callback with the authorization code
12
+ * 5. Exchanges the code for tokens
13
+ * 6. Returns the OAuth state for persistence
14
+ */
15
+ export declare function startOAuthFlow(options: StartOAuthFlowOptions): Promise<OAuthFlowResult>;
16
+ /**
17
+ * Check if an error indicates authentication is required.
18
+ * Internal function used by addMcpCommands.
19
+ */
20
+ export declare function isAuthRequiredError(err: unknown): boolean;
21
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAiB,eAAe,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AA0BxF;;;;;;;;;;;;GAYG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC,CA4E7F;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAazD"}
package/dist/auth.js ADDED
@@ -0,0 +1,122 @@
1
+ import { auth } from "@modelcontextprotocol/sdk/client/auth.js";
2
+ import { FileOAuthProvider } from "./oauth-provider.js";
3
+ import { startCallbackServer } from "./local-callback-server.js";
4
+ /**
5
+ * Open a URL in the default browser.
6
+ * Uses platform-specific commands.
7
+ */
8
+ async function openBrowser(url) {
9
+ const { exec } = await import("node:child_process");
10
+ const { promisify } = await import("node:util");
11
+ const execAsync = promisify(exec);
12
+ const platform = process.platform;
13
+ const command = (() => {
14
+ if (platform === "darwin") {
15
+ return `open "${url}"`;
16
+ }
17
+ if (platform === "win32") {
18
+ return `start "" "${url}"`;
19
+ }
20
+ // Linux and others
21
+ return `xdg-open "${url}"`;
22
+ })();
23
+ await execAsync(command);
24
+ }
25
+ /**
26
+ * Start the OAuth flow for an MCP server.
27
+ * This is an internal function - consumers should not call this directly.
28
+ * It is automatically triggered by addMcpCommands when a 401 error occurs.
29
+ *
30
+ * This function:
31
+ * 1. Starts a local callback server on a random port
32
+ * 2. Initiates OAuth with the MCP server
33
+ * 3. Opens the browser for user authorization
34
+ * 4. Waits for the callback with the authorization code
35
+ * 5. Exchanges the code for tokens
36
+ * 6. Returns the OAuth state for persistence
37
+ */
38
+ export async function startOAuthFlow(options) {
39
+ const { serverUrl, clientName, existingState, onAuthUrl, timeout } = options;
40
+ // Start local callback server on random port
41
+ const callbackServer = await startCallbackServer({ timeout });
42
+ const { redirectUri, waitForCallback, close } = callbackServer;
43
+ try {
44
+ // Create OAuth provider with the dynamic redirect URI
45
+ const oauthProvider = new FileOAuthProvider({
46
+ serverUrl,
47
+ redirectUri,
48
+ clientName,
49
+ tokens: existingState?.tokens,
50
+ clientInformation: existingState?.clientInformation,
51
+ codeVerifier: existingState?.codeVerifier,
52
+ });
53
+ // Start the OAuth flow - this will trigger dynamic client registration
54
+ // and set the authorization URL on the provider
55
+ const authResult = await auth(oauthProvider, { serverUrl });
56
+ if (authResult !== "REDIRECT") {
57
+ // Auth succeeded without redirect (had valid tokens)
58
+ close();
59
+ return {
60
+ success: true,
61
+ state: oauthProvider.getState(),
62
+ };
63
+ }
64
+ // Get the authorization URL
65
+ const authUrl = oauthProvider.redirectStartAuthUrl;
66
+ if (!authUrl) {
67
+ close();
68
+ return {
69
+ success: false,
70
+ error: "No authorization URL returned from OAuth flow",
71
+ };
72
+ }
73
+ // Open browser or call custom handler
74
+ const authUrlString = authUrl.toString();
75
+ if (onAuthUrl) {
76
+ onAuthUrl(authUrlString);
77
+ }
78
+ else {
79
+ await openBrowser(authUrlString);
80
+ }
81
+ // Wait for the callback
82
+ const callback = await waitForCallback();
83
+ // Complete the OAuth flow by exchanging the code for tokens
84
+ const finalResult = await auth(oauthProvider, {
85
+ serverUrl,
86
+ authorizationCode: callback.code,
87
+ });
88
+ if (finalResult === "REDIRECT") {
89
+ return {
90
+ success: false,
91
+ error: "Unexpected redirect after code exchange",
92
+ };
93
+ }
94
+ return {
95
+ success: true,
96
+ state: oauthProvider.getState(),
97
+ };
98
+ }
99
+ catch (err) {
100
+ close();
101
+ return {
102
+ success: false,
103
+ error: err instanceof Error ? err.message : String(err),
104
+ };
105
+ }
106
+ }
107
+ /**
108
+ * Check if an error indicates authentication is required.
109
+ * Internal function used by addMcpCommands.
110
+ */
111
+ export function isAuthRequiredError(err) {
112
+ if (!(err instanceof Error)) {
113
+ return false;
114
+ }
115
+ const message = err.message.toLowerCase();
116
+ return (message.includes("401") ||
117
+ message.includes("unauthorized") ||
118
+ message.includes("authentication required") ||
119
+ message.includes("not authenticated") ||
120
+ message.includes("invalid_token") ||
121
+ message.includes("missing or invalid access token"));
122
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * # MCP to CLI
3
+ *
4
+ * Dynamically generates CLI commands from MCP (Model Context Protocol) server tools.
5
+ * This module connects to any MCP server, discovers available tools, and creates
6
+ * corresponding CLI commands with proper argument parsing and validation.
7
+ *
8
+ * ## Features
9
+ *
10
+ * - **Auto-discovery**: Fetches all tools from the MCP server and creates CLI commands
11
+ * - **Caching**: Tools are cached for 1 hour to avoid reconnecting on every invocation
12
+ * - **Session reuse**: MCP session IDs are cached to skip initialization handshake
13
+ * - **Type-aware parsing**: Handles string, number, boolean, object, and array arguments
14
+ * - **JSON schema support**: Generates CLI options from tool input schemas
15
+ * - **OAuth support**: Automatic OAuth authentication on 401 errors (lazy auth)
16
+ *
17
+ * ## Example Usage
18
+ *
19
+ * ```ts
20
+ * import { cac } from '@xmorse/cac'
21
+ * import { addMcpCommands } from 'mcpcac'
22
+ *
23
+ * const cli = cac('mycli')
24
+ *
25
+ * await addMcpCommands({
26
+ * cli,
27
+ * commandPrefix: 'mcp',
28
+ * clientName: 'my-mcp-client',
29
+ * getMcpUrl: () => loadConfig().mcpUrl,
30
+ * oauth: {
31
+ * clientName: 'My CLI',
32
+ * load: () => loadConfig().mcpOauth,
33
+ * save: (state) => saveConfig({ mcpOauth: state }),
34
+ * },
35
+ * loadCache: () => loadConfig().cachedMcpTools,
36
+ * saveCache: (cache) => saveConfig({ cachedMcpTools: cache }),
37
+ * })
38
+ *
39
+ * // Login command just saves URL - no auth check, fast!
40
+ * cli.command('login [url]').action((url) => {
41
+ * saveConfig({ mcpUrl: url })
42
+ * console.log('URL saved.')
43
+ * })
44
+ *
45
+ * cli.parse()
46
+ * ```
47
+ *
48
+ * @module mcpcac
49
+ */
50
+ import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
51
+ import type { CAC } from "@xmorse/cac";
52
+ import type { McpOAuthConfig } from "./types.js";
53
+ export type { Transport };
54
+ export type { McpOAuthConfig, McpOAuthState } from "./types.js";
55
+ export interface CachedMcpTools {
56
+ tools: Array<{
57
+ name: string;
58
+ description?: string;
59
+ inputSchema?: unknown;
60
+ }>;
61
+ timestamp: number;
62
+ sessionId?: string;
63
+ }
64
+ export interface AddMcpCommandsOptions {
65
+ cli: CAC;
66
+ commandPrefix: string;
67
+ /**
68
+ * Name used when connecting to the MCP server.
69
+ * @default 'mcp-cli-client'
70
+ */
71
+ clientName?: string;
72
+ /**
73
+ * Returns the MCP server URL, or undefined if not configured.
74
+ * Required when using the oauth option.
75
+ */
76
+ getMcpUrl?: () => string | undefined;
77
+ /**
78
+ * Returns a transport to connect to the MCP server, or null if not configured.
79
+ * If null is returned, no MCP tool commands will be registered.
80
+ * @param sessionId - Optional session ID from cache to reuse existing session
81
+ *
82
+ * @deprecated Use getMcpUrl + oauth instead for simpler setup
83
+ */
84
+ getMcpTransport?: (sessionId?: string) => Transport | null | Promise<Transport | null>;
85
+ /**
86
+ * OAuth configuration. When provided, enables automatic OAuth authentication.
87
+ *
88
+ * OAuth is lazy - no auth check happens on startup. Authentication is only
89
+ * triggered when a tool call returns 401. After successful auth, the tool
90
+ * call is automatically retried.
91
+ *
92
+ * The library handles everything internally:
93
+ * - Detecting 401 errors
94
+ * - Starting local callback server on random port
95
+ * - Opening browser for authorization
96
+ * - Exchanging code for tokens
97
+ * - Persisting tokens via save()
98
+ * - Retrying the failed tool call
99
+ */
100
+ oauth?: McpOAuthConfig;
101
+ /**
102
+ * Load cached MCP tools. Return undefined if no cache exists.
103
+ */
104
+ loadCache: () => CachedMcpTools | undefined;
105
+ /**
106
+ * Save cached MCP tools. Pass undefined to clear the cache.
107
+ */
108
+ saveCache: (cache: CachedMcpTools | undefined) => void;
109
+ }
110
+ /**
111
+ * Adds MCP tool commands to a cac CLI instance.
112
+ *
113
+ * Tools are cached for 1 hour to avoid connecting on every CLI invocation.
114
+ * Session ID is also cached to skip MCP initialization handshake.
115
+ *
116
+ * OAuth is lazy - authentication only happens when a 401 error occurs.
117
+ * After successful auth, the operation is automatically retried.
118
+ */
119
+ export declare function addMcpCommands(options: AddMcpCommandsOptions): Promise<void>;
120
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAC/E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAGvC,OAAO,KAAK,EAAE,cAAc,EAAiB,MAAM,YAAY,CAAC;AAGhE,YAAY,EAAE,SAAS,EAAE,CAAC;AAC1B,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,KAAK,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,OAAO,CAAC;KACvB,CAAC,CAAC;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,GAAG,CAAC;IACT,aAAa,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAErC;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IAEvF;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,EAAE,cAAc,CAAC;IAEvB;;OAEG;IACH,SAAS,EAAE,MAAM,cAAc,GAAG,SAAS,CAAC;IAE5C;;OAEG;IACH,SAAS,EAAE,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,KAAK,IAAI,CAAC;CACxD;AAkHD;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqMlF"}
package/dist/index.js ADDED
@@ -0,0 +1,311 @@
1
+ /**
2
+ * # MCP to CLI
3
+ *
4
+ * Dynamically generates CLI commands from MCP (Model Context Protocol) server tools.
5
+ * This module connects to any MCP server, discovers available tools, and creates
6
+ * corresponding CLI commands with proper argument parsing and validation.
7
+ *
8
+ * ## Features
9
+ *
10
+ * - **Auto-discovery**: Fetches all tools from the MCP server and creates CLI commands
11
+ * - **Caching**: Tools are cached for 1 hour to avoid reconnecting on every invocation
12
+ * - **Session reuse**: MCP session IDs are cached to skip initialization handshake
13
+ * - **Type-aware parsing**: Handles string, number, boolean, object, and array arguments
14
+ * - **JSON schema support**: Generates CLI options from tool input schemas
15
+ * - **OAuth support**: Automatic OAuth authentication on 401 errors (lazy auth)
16
+ *
17
+ * ## Example Usage
18
+ *
19
+ * ```ts
20
+ * import { cac } from '@xmorse/cac'
21
+ * import { addMcpCommands } from 'mcpcac'
22
+ *
23
+ * const cli = cac('mycli')
24
+ *
25
+ * await addMcpCommands({
26
+ * cli,
27
+ * commandPrefix: 'mcp',
28
+ * clientName: 'my-mcp-client',
29
+ * getMcpUrl: () => loadConfig().mcpUrl,
30
+ * oauth: {
31
+ * clientName: 'My CLI',
32
+ * load: () => loadConfig().mcpOauth,
33
+ * save: (state) => saveConfig({ mcpOauth: state }),
34
+ * },
35
+ * loadCache: () => loadConfig().cachedMcpTools,
36
+ * saveCache: (cache) => saveConfig({ cachedMcpTools: cache }),
37
+ * })
38
+ *
39
+ * // Login command just saves URL - no auth check, fast!
40
+ * cli.command('login [url]').action((url) => {
41
+ * saveConfig({ mcpUrl: url })
42
+ * console.log('URL saved.')
43
+ * })
44
+ *
45
+ * cli.parse()
46
+ * ```
47
+ *
48
+ * @module mcpcac
49
+ */
50
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
51
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
52
+ import { FileOAuthProvider } from "./oauth-provider.js";
53
+ import { startOAuthFlow, isAuthRequiredError } from "./auth.js";
54
+ const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
55
+ /**
56
+ * Convert JSON schema to compact JSON string for display
57
+ */
58
+ function schemaToString(schema) {
59
+ const compact = { ...schema };
60
+ delete compact.description;
61
+ return JSON.stringify(compact);
62
+ }
63
+ function parseToolArguments(options, inputSchema) {
64
+ const args = {};
65
+ if (!inputSchema?.properties) {
66
+ return args;
67
+ }
68
+ for (const [name, schema] of Object.entries(inputSchema.properties)) {
69
+ let value = options[name];
70
+ if (value === undefined) {
71
+ continue;
72
+ }
73
+ if (Array.isArray(value) && value.length === 1) {
74
+ value = value[0];
75
+ }
76
+ const type = schema.type || "string";
77
+ if ((type === "object" || type === "array") && typeof value === "string") {
78
+ try {
79
+ args[name] = JSON.parse(value);
80
+ }
81
+ catch {
82
+ console.error(`Invalid JSON for --${name}: ${value}`);
83
+ process.exit(1);
84
+ }
85
+ }
86
+ else {
87
+ args[name] = value;
88
+ }
89
+ }
90
+ return args;
91
+ }
92
+ function outputResult(result) {
93
+ for (const block of result.content) {
94
+ if (block.type === "text" && block.text) {
95
+ console.log(block.text);
96
+ }
97
+ else if (block.type === "image") {
98
+ console.log("[Image content omitted]");
99
+ }
100
+ else {
101
+ console.log(JSON.stringify(block, null, 2));
102
+ }
103
+ }
104
+ }
105
+ /**
106
+ * Create a transport with optional OAuth authentication
107
+ */
108
+ function createTransportWithAuth(url, sessionId, oauthState, oauth) {
109
+ let authProvider;
110
+ if (oauth && oauthState?.tokens) {
111
+ authProvider = new FileOAuthProvider({
112
+ serverUrl: url.toString(),
113
+ redirectUri: "http://localhost/callback", // Placeholder, real one set during auth flow
114
+ clientName: oauth.clientName,
115
+ tokens: oauthState.tokens,
116
+ clientInformation: oauthState.clientInformation,
117
+ codeVerifier: oauthState.codeVerifier,
118
+ onStateUpdated: (newState) => {
119
+ oauth.save(newState);
120
+ },
121
+ });
122
+ }
123
+ return new StreamableHTTPClientTransport(url, {
124
+ sessionId,
125
+ authProvider,
126
+ });
127
+ }
128
+ /**
129
+ * Normalize MCP URL for StreamableHTTP transport
130
+ */
131
+ function normalizeUrl(mcpUrl) {
132
+ const url = new URL(mcpUrl);
133
+ if (url.pathname.endsWith("/sse")) {
134
+ url.pathname = url.pathname.replace(/\/sse$/, "/mcp");
135
+ }
136
+ return url;
137
+ }
138
+ /**
139
+ * Adds MCP tool commands to a cac CLI instance.
140
+ *
141
+ * Tools are cached for 1 hour to avoid connecting on every CLI invocation.
142
+ * Session ID is also cached to skip MCP initialization handshake.
143
+ *
144
+ * OAuth is lazy - authentication only happens when a 401 error occurs.
145
+ * After successful auth, the operation is automatically retried.
146
+ */
147
+ export async function addMcpCommands(options) {
148
+ const { cli, commandPrefix, clientName = "mcp-cli-client", getMcpUrl, getMcpTransport, oauth, loadCache, saveCache, } = options;
149
+ // Helper to get transport - supports both old and new API
150
+ const getTransport = async (sessionId) => {
151
+ // New API: getMcpUrl + oauth
152
+ if (getMcpUrl) {
153
+ const mcpUrl = getMcpUrl();
154
+ if (!mcpUrl) {
155
+ return null;
156
+ }
157
+ const url = normalizeUrl(mcpUrl);
158
+ const oauthState = oauth?.load();
159
+ return createTransportWithAuth(url, sessionId, oauthState, oauth);
160
+ }
161
+ // Legacy API: getMcpTransport
162
+ if (getMcpTransport) {
163
+ return getMcpTransport(sessionId);
164
+ }
165
+ return null;
166
+ };
167
+ // Handle auth required - triggers OAuth flow internally
168
+ const handleAuthRequired = async (serverUrl) => {
169
+ if (!oauth) {
170
+ console.error("Authentication required but OAuth not configured.");
171
+ console.error("Add oauth config to addMcpCommands() to enable automatic authentication.");
172
+ return false;
173
+ }
174
+ console.log("\n🔐 Authentication required. Opening browser...\n");
175
+ const result = await startOAuthFlow({
176
+ serverUrl,
177
+ clientName: oauth.clientName,
178
+ existingState: oauth.load(),
179
+ onAuthUrl: oauth.onAuthUrl,
180
+ });
181
+ if (result.success && result.state) {
182
+ oauth.save(result.state);
183
+ oauth.onAuthSuccess?.();
184
+ console.log("✓ Authentication successful! Retrying...\n");
185
+ return true;
186
+ }
187
+ oauth.onAuthError?.(result.error || "Unknown error");
188
+ console.error(`✗ Authentication failed: ${result.error}\n`);
189
+ return false;
190
+ };
191
+ // Try to use cached tools first (fast path - no network)
192
+ const cachedTools = loadCache();
193
+ const isCacheValid = cachedTools && (Date.now() - cachedTools.timestamp) < CACHE_TTL_MS;
194
+ let tools;
195
+ let cachedSessionId;
196
+ if (isCacheValid) {
197
+ tools = cachedTools.tools;
198
+ cachedSessionId = cachedTools.sessionId;
199
+ }
200
+ else {
201
+ // Cache invalid/missing - connect to fetch tools
202
+ const transport = await getTransport();
203
+ if (!transport) {
204
+ return;
205
+ }
206
+ const client = new Client({ name: clientName, version: "1.0.0" }, { capabilities: {} });
207
+ try {
208
+ await client.connect(transport);
209
+ const result = await client.listTools();
210
+ tools = result.tools;
211
+ const sessionId = transport.sessionId;
212
+ saveCache({
213
+ tools: tools.map((t) => ({
214
+ name: t.name,
215
+ description: t.description,
216
+ inputSchema: t.inputSchema,
217
+ })),
218
+ timestamp: Date.now(),
219
+ sessionId,
220
+ });
221
+ cachedSessionId = sessionId;
222
+ }
223
+ catch (err) {
224
+ // Check if auth is required during tool discovery
225
+ if (isAuthRequiredError(err) && oauth && getMcpUrl) {
226
+ const mcpUrl = getMcpUrl();
227
+ if (mcpUrl) {
228
+ const authSuccess = await handleAuthRequired(normalizeUrl(mcpUrl).toString());
229
+ if (authSuccess) {
230
+ // Retry after auth
231
+ return addMcpCommands(options);
232
+ }
233
+ }
234
+ }
235
+ console.error(`Failed to connect to MCP server: ${err instanceof Error ? err.message : err}`);
236
+ return;
237
+ }
238
+ finally {
239
+ await client.close();
240
+ }
241
+ }
242
+ // Register CLI commands for each tool
243
+ for (const tool of tools) {
244
+ const inputSchema = tool.inputSchema;
245
+ const cmdName = `${commandPrefix} ${tool.name}`;
246
+ const description = tool.description || `Run MCP tool ${tool.name}`;
247
+ const cmd = cli.command(cmdName, description);
248
+ if (inputSchema?.properties) {
249
+ for (const [propName, propSchema] of Object.entries(inputSchema.properties)) {
250
+ const isRequired = inputSchema.required?.includes(propName) ?? false;
251
+ const schemaType = propSchema.type || "string";
252
+ const optionStr = schemaType === "boolean" ? `--${propName}` : `--${propName} <${propName}>`;
253
+ let optionDesc = propSchema.description || propName;
254
+ if (isRequired) {
255
+ optionDesc += " (required)";
256
+ }
257
+ if (schemaType === "object" || schemaType === "array") {
258
+ optionDesc += ` (JSON: ${schemaToString(propSchema)})`;
259
+ }
260
+ const optionConfig = {};
261
+ if (propSchema.default !== undefined) {
262
+ optionConfig.default = propSchema.default;
263
+ }
264
+ if (schemaType === "number" || schemaType === "integer") {
265
+ optionConfig.type = [Number];
266
+ }
267
+ else if (schemaType !== "boolean") {
268
+ optionConfig.type = [String];
269
+ }
270
+ cmd.option(optionStr, optionDesc, optionConfig);
271
+ }
272
+ }
273
+ cmd.action(async (cliOptions) => {
274
+ const parsedArgs = parseToolArguments(cliOptions, inputSchema);
275
+ const executeWithRetry = async (isRetry = false) => {
276
+ const transport = await getTransport(isRetry ? undefined : cachedSessionId);
277
+ if (!transport) {
278
+ console.error("MCP transport not available. Run login command first.");
279
+ process.exit(1);
280
+ }
281
+ const actionClient = new Client({ name: clientName, version: "1.0.0" }, { capabilities: {} });
282
+ try {
283
+ await actionClient.connect(transport);
284
+ const result = await actionClient.callTool({ name: tool.name, arguments: parsedArgs });
285
+ outputResult(result);
286
+ }
287
+ catch (err) {
288
+ // On 401, trigger OAuth and retry (only once)
289
+ if (!isRetry && isAuthRequiredError(err) && oauth && getMcpUrl) {
290
+ const mcpUrl = getMcpUrl();
291
+ if (mcpUrl) {
292
+ const authSuccess = await handleAuthRequired(normalizeUrl(mcpUrl).toString());
293
+ if (authSuccess) {
294
+ await actionClient.close();
295
+ return executeWithRetry(true);
296
+ }
297
+ }
298
+ }
299
+ // Clear cache on error (might be stale)
300
+ saveCache(undefined);
301
+ console.error(`Error calling ${tool.name}:`, err instanceof Error ? err.message : err);
302
+ process.exit(1);
303
+ }
304
+ finally {
305
+ await actionClient.close();
306
+ }
307
+ };
308
+ await executeWithRetry();
309
+ });
310
+ }
311
+ }
@@ -0,0 +1,14 @@
1
+ import type { CallbackResult, CallbackServerOptions } from "./types.js";
2
+ /**
3
+ * Start a local HTTP server to receive OAuth callbacks.
4
+ * Uses a random available port to avoid conflicts.
5
+ *
6
+ * @returns Object with port, redirectUri, waitForCallback promise, and close function
7
+ */
8
+ export declare function startCallbackServer(options?: CallbackServerOptions): Promise<{
9
+ port: number;
10
+ redirectUri: string;
11
+ waitForCallback: () => Promise<CallbackResult>;
12
+ close: () => void;
13
+ }>;
14
+ //# sourceMappingURL=local-callback-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-callback-server.d.ts","sourceRoot":"","sources":["../src/local-callback-server.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AA2FxE;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC;IACtF,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,CAAC;IAC/C,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC,CAgFD"}