@evantahler/mcpcli 0.2.4 → 0.3.2
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/package.json +1 -1
- package/skills/mcpcli.md +26 -19
- package/src/client/oauth.ts +38 -1
- package/src/commands/add.ts +19 -1
- package/src/commands/call.ts +0 -13
package/package.json
CHANGED
package/skills/mcpcli.md
CHANGED
|
@@ -17,10 +17,10 @@ mcpcli search "<what you want to do>"
|
|
|
17
17
|
## 2. Inspect the tool schema
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
mcpcli
|
|
20
|
+
mcpcli info <server>/<tool>
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
This shows parameters, types, required fields, and
|
|
23
|
+
This shows parameters, types, required fields, and the full JSON Schema.
|
|
24
24
|
|
|
25
25
|
## 3. Call the tool
|
|
26
26
|
|
|
@@ -34,7 +34,6 @@ mcpcli call <server> <tool> '<json args>'
|
|
|
34
34
|
- Always inspect the schema before calling — validate you have the right arguments
|
|
35
35
|
- Use `mcpcli search -k` for exact name matching
|
|
36
36
|
- Pipe results through `jq` when you need to extract specific fields
|
|
37
|
-
- Tool call results are always JSON with nested JSON strings auto-parsed
|
|
38
37
|
- Use `-v` for verbose HTTP debugging if a call fails unexpectedly
|
|
39
38
|
|
|
40
39
|
## Examples
|
|
@@ -44,14 +43,14 @@ mcpcli call <server> <tool> '<json args>'
|
|
|
44
43
|
mcpcli search "send a message"
|
|
45
44
|
|
|
46
45
|
# See what parameters Slack_SendMessage needs
|
|
47
|
-
mcpcli
|
|
46
|
+
mcpcli info arcade/Slack_SendMessage
|
|
48
47
|
|
|
49
48
|
# Send a message
|
|
50
49
|
mcpcli call arcade Slack_SendMessage '{"channel":"#general","message":"hello"}'
|
|
51
50
|
|
|
52
51
|
# Chain commands — search repos and read the first result
|
|
53
52
|
mcpcli call github search_repositories '{"query":"mcp"}' \
|
|
54
|
-
| jq -r '.content[0].text.items[0].full_name' \
|
|
53
|
+
| jq -r '.content[0].text | fromjson | .items[0].full_name' \
|
|
55
54
|
| xargs -I {} mcpcli call github get_file_contents '{"owner":"{}","path":"README.md"}'
|
|
56
55
|
|
|
57
56
|
# Read args from stdin
|
|
@@ -64,22 +63,30 @@ Some HTTP servers require OAuth. If you see an "Not authenticated" error:
|
|
|
64
63
|
|
|
65
64
|
```bash
|
|
66
65
|
mcpcli auth <server> # authenticate via browser
|
|
67
|
-
mcpcli auth <server> -s
|
|
66
|
+
mcpcli auth <server> -s # check token status and TTL
|
|
67
|
+
mcpcli auth <server> -r # force token refresh
|
|
68
68
|
mcpcli deauth <server> # remove stored auth
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
## Available commands
|
|
72
72
|
|
|
73
|
-
| Command | Purpose
|
|
74
|
-
| -------------------------------------- |
|
|
75
|
-
| `mcpcli` | List all servers and tools
|
|
76
|
-
| `mcpcli -d` | List with descriptions
|
|
77
|
-
| `mcpcli
|
|
78
|
-
| `mcpcli
|
|
79
|
-
| `mcpcli call <server
|
|
80
|
-
| `mcpcli
|
|
81
|
-
| `mcpcli search "<query>"` | Search tools
|
|
82
|
-
| `mcpcli search -k "<pattern>"` | Keyword/glob search only
|
|
83
|
-
| `mcpcli search -q "<query>"` | Semantic search only
|
|
84
|
-
| `mcpcli index` | Build/rebuild search index
|
|
85
|
-
| `mcpcli
|
|
73
|
+
| Command | Purpose |
|
|
74
|
+
| -------------------------------------- | --------------------------------- |
|
|
75
|
+
| `mcpcli` | List all servers and tools |
|
|
76
|
+
| `mcpcli -d` | List with descriptions |
|
|
77
|
+
| `mcpcli info <server>` | Show tools for a server |
|
|
78
|
+
| `mcpcli info <server>/<tool>` | Show tool schema |
|
|
79
|
+
| `mcpcli call <server>` | List tools for a server |
|
|
80
|
+
| `mcpcli call <server> <tool> '<json>'` | Execute a tool |
|
|
81
|
+
| `mcpcli search "<query>"` | Search tools (keyword + semantic) |
|
|
82
|
+
| `mcpcli search -k "<pattern>"` | Keyword/glob search only |
|
|
83
|
+
| `mcpcli search -q "<query>"` | Semantic search only |
|
|
84
|
+
| `mcpcli index` | Build/rebuild search index |
|
|
85
|
+
| `mcpcli index -i` | Show index status |
|
|
86
|
+
| `mcpcli auth <server>` | Authenticate with OAuth |
|
|
87
|
+
| `mcpcli auth <server> -s` | Check token status and TTL |
|
|
88
|
+
| `mcpcli auth <server> -r` | Force token refresh |
|
|
89
|
+
| `mcpcli deauth <server>` | Remove stored authentication |
|
|
90
|
+
| `mcpcli add <name> --command <cmd>` | Add a stdio MCP server |
|
|
91
|
+
| `mcpcli add <name> --url <url>` | Add an HTTP MCP server |
|
|
92
|
+
| `mcpcli remove <name>` | Remove an MCP server |
|
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);
|
package/src/commands/call.ts
CHANGED
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
formatCallResult,
|
|
5
5
|
formatError,
|
|
6
6
|
formatServerTools,
|
|
7
|
-
formatToolHelp,
|
|
8
7
|
formatValidationErrors,
|
|
9
8
|
} from "../output/formatter.ts";
|
|
10
9
|
import { startSpinner } from "../output/spinner.ts";
|
|
@@ -30,18 +29,6 @@ export function registerCallCommand(program: Command) {
|
|
|
30
29
|
return;
|
|
31
30
|
}
|
|
32
31
|
try {
|
|
33
|
-
// No args + interactive terminal → show tool help with example payload
|
|
34
|
-
const hasArgs = !!argsStr;
|
|
35
|
-
const hasStdin = !process.stdin.isTTY;
|
|
36
|
-
if (!hasArgs && !hasStdin) {
|
|
37
|
-
const toolSchema = await manager.getToolSchema(server, tool);
|
|
38
|
-
if (toolSchema) {
|
|
39
|
-
console.log(formatToolHelp(server, toolSchema, formatOptions));
|
|
40
|
-
await manager.close();
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
32
|
// Parse args from argument, stdin, or empty
|
|
46
33
|
let args: Record<string, unknown> = {};
|
|
47
34
|
|