@modeltoolsprotocol/mtpcli 0.2.3 → 0.3.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 +51 -28
- package/dist/index.js +613 -92
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,26 +6,20 @@ The command-line interface for the [Model Tools Protocol](https://github.com/mod
|
|
|
6
6
|
|
|
7
7
|
LLM agents need to discover and use tools. Right now there are two worlds:
|
|
8
8
|
|
|
9
|
-
**CLI tools** are the backbone of software development. They're composable (`|`), scriptable, version-controlled, and work everywhere. But LLMs can't discover what a CLI does
|
|
9
|
+
**CLI tools** are the backbone of software development. They're composable (`|`), scriptable, version-controlled, and work everywhere. But LLMs can't discover what a CLI does. They have to parse `--help` text, guess at arguments, and hope for the best.
|
|
10
10
|
|
|
11
|
-
**MCP (Model Context Protocol)** solves discovery beautifully. Tools declare typed schemas, and LLM hosts discover them via a structured handshake. But MCP
|
|
12
|
-
|
|
13
|
-
These are both good ideas with real tradeoffs:
|
|
11
|
+
**MCP (Model Context Protocol)** solves discovery beautifully. Tools declare typed schemas, and LLM hosts discover them via a structured handshake. But MCP tools aren't composable. Each invocation goes through the model. You can't pipe one MCP tool's output into another. You can't script them without an LLM in the loop.
|
|
14
12
|
|
|
15
13
|
| | CLI tools | MCP tools |
|
|
16
14
|
|---|---|---|
|
|
17
|
-
| **Composability** | First-class. Pipes, shell scripts, xargs
|
|
15
|
+
| **Composability** | First-class. Pipes, shell scripts, xargs. 50 years of Unix | Requires an orchestrator or agent framework |
|
|
18
16
|
| **Discovery** | Poor. Parse `--help` and hope | Excellent. Typed schemas via handshake |
|
|
19
17
|
| **Runtime** | Any shell, anywhere | Needs an MCP host (Claude Desktop, etc.) |
|
|
20
18
|
| **Deployment** | `brew install`, `cargo install`, a binary in PATH | Run a server process, configure the host |
|
|
21
19
|
| **Interop** | Pipes to anything | Talks to MCP clients only |
|
|
22
|
-
| **Scriptable without an LLM** | Yes
|
|
23
|
-
| **Streaming / subscriptions** | Limited (stdout streaming) | Built-in (SSE, notifications) |
|
|
24
|
-
| **Stateful interaction** | Stateless by design (each invocation is fresh) | Stateful sessions with context |
|
|
20
|
+
| **Scriptable without an LLM** | Yes, that's the whole point of CLIs | Not really, designed for LLM interaction |
|
|
25
21
|
| **Adoption cost** | One flag/decorator | New protocol, server scaffolding, SDK |
|
|
26
22
|
|
|
27
|
-
MCP is the right choice when you need stateful sessions, streaming, or deep integration with an LLM host. CLIs are the right choice when you want composability, scriptability, and zero infrastructure.
|
|
28
|
-
|
|
29
23
|
The gap is discovery. `mtpcli` fills it.
|
|
30
24
|
|
|
31
25
|
## Install
|
|
@@ -34,8 +28,52 @@ The gap is discovery. `mtpcli` fills it.
|
|
|
34
28
|
npm install -g @modeltoolsprotocol/mtpcli
|
|
35
29
|
```
|
|
36
30
|
|
|
31
|
+
## What it does
|
|
32
|
+
|
|
33
|
+
**mtpcli** bridges the gap between CLI tools and MCP servers. It turns any `--describe` CLI into an MCP server, turns any MCP server into a composable CLI, and handles discovery, auth, and validation along the way.
|
|
34
|
+
|
|
37
35
|
## Usage
|
|
38
36
|
|
|
37
|
+
### Serve CLI tools over MCP
|
|
38
|
+
|
|
39
|
+
Any CLI that supports `--describe` becomes an MCP server:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
$ mtpcli serve --tool atlasctl --tool mytool
|
|
43
|
+
|
|
44
|
+
mtpcli serve: serving 6 tool(s) from 2 CLI tool(s)
|
|
45
|
+
- atlasctl__confluence page get
|
|
46
|
+
- atlasctl__config set
|
|
47
|
+
- atlasctl__config get
|
|
48
|
+
- mytool__convert
|
|
49
|
+
...
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Drop it into your Claude Desktop config and it works like any other MCP server. The bridge reads `--describe`, translates commands to MCP tools, and shells out to the real CLI when the host calls a tool.
|
|
53
|
+
|
|
54
|
+
### Wrap an MCP server as a CLI
|
|
55
|
+
|
|
56
|
+
Atlassian ships an MCP server at `mcp.atlassian.com`. With `mtpcli wrap`, it's a CLI:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Discover what tools the server offers
|
|
60
|
+
$ mtpcli wrap --url "https://mcp.atlassian.com/v1/mcp" --describe
|
|
61
|
+
|
|
62
|
+
# Fetch a Confluence page
|
|
63
|
+
$ mtpcli wrap --url "https://mcp.atlassian.com/v1/mcp" \
|
|
64
|
+
getConfluencePage -- --cloudId "$CLOUD_ID" --pageId 12345 --contentFormat markdown
|
|
65
|
+
|
|
66
|
+
# Pipe it into jq, grep, or anything else
|
|
67
|
+
$ mtpcli wrap --url "https://mcp.atlassian.com/v1/mcp" \
|
|
68
|
+
getConfluencePage -- --cloudId "$CLOUD_ID" --pageId 12345 --contentFormat markdown \
|
|
69
|
+
| jq -r '.body'
|
|
70
|
+
|
|
71
|
+
# Works with stdio servers too
|
|
72
|
+
$ mtpcli wrap --server "npx @mcp/server-github" --describe
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The 2,500+ MCP servers people have built? They're all CLI tools now. Pipe their output, use them in scripts, compose them with other tools.
|
|
76
|
+
|
|
39
77
|
### Search for tools and commands
|
|
40
78
|
|
|
41
79
|
```bash
|
|
@@ -52,7 +90,7 @@ mtpcli search --scan-path "git commit"
|
|
|
52
90
|
### Authenticate with a tool
|
|
53
91
|
|
|
54
92
|
```bash
|
|
55
|
-
# OAuth2 login
|
|
93
|
+
# OAuth2 login (opens browser, handles callback)
|
|
56
94
|
mtpcli auth login mytool
|
|
57
95
|
|
|
58
96
|
# API key / bearer token login
|
|
@@ -68,22 +106,7 @@ eval $(mtpcli auth env mytool)
|
|
|
68
106
|
mtpcli auth logout mytool
|
|
69
107
|
```
|
|
70
108
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
# Start an MCP server that bridges describe-compatible tools
|
|
75
|
-
mtpcli serve --tool mytool --tool anothertool
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### Wrap an MCP server as a CLI
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
# Describe an MCP server's tools
|
|
82
|
-
mtpcli wrap --server "npx @mcp/server-github" --describe
|
|
83
|
-
|
|
84
|
-
# Call a tool on an MCP server
|
|
85
|
-
mtpcli wrap --server "npx @mcp/server-github" search_repos -- --query mtpcli
|
|
86
|
-
```
|
|
109
|
+
See [AUTH.md](AUTH.md) for details on token storage, usage patterns, and bridge integration.
|
|
87
110
|
|
|
88
111
|
### Validate a tool's --describe output
|
|
89
112
|
|
|
@@ -114,7 +137,7 @@ mtpcli completions fish mytool | source
|
|
|
114
137
|
### Describe self
|
|
115
138
|
|
|
116
139
|
```bash
|
|
117
|
-
#
|
|
140
|
+
# mtpcli is itself an MTP-compliant tool
|
|
118
141
|
mtpcli --describe
|
|
119
142
|
```
|
|
120
143
|
|
package/dist/index.js
CHANGED
|
@@ -7341,6 +7341,31 @@ var init_search2 = __esm(() => {
|
|
|
7341
7341
|
});
|
|
7342
7342
|
|
|
7343
7343
|
// src/auth.ts
|
|
7344
|
+
var exports_auth = {};
|
|
7345
|
+
__export(exports_auth, {
|
|
7346
|
+
storeToken: () => storeToken,
|
|
7347
|
+
runToken: () => runToken,
|
|
7348
|
+
runStatus: () => runStatus,
|
|
7349
|
+
runRefresh: () => runRefresh,
|
|
7350
|
+
runLogout: () => runLogout,
|
|
7351
|
+
runLogin: () => runLogin,
|
|
7352
|
+
runEnv: () => runEnv,
|
|
7353
|
+
refreshAccessToken: () => refreshAccessToken,
|
|
7354
|
+
oauth2Login: () => oauth2Login,
|
|
7355
|
+
login: () => login,
|
|
7356
|
+
loadToken: () => loadToken,
|
|
7357
|
+
isTokenExpired: () => isTokenExpired,
|
|
7358
|
+
getProvider: () => getProvider,
|
|
7359
|
+
getEnvExport: () => getEnvExport,
|
|
7360
|
+
getEnvDict: () => getEnvDict,
|
|
7361
|
+
getAuthConfig: () => getAuthConfig,
|
|
7362
|
+
generatePkce: () => generatePkce,
|
|
7363
|
+
exchangeCode: () => exchangeCode,
|
|
7364
|
+
ensureValidToken: () => ensureValidToken,
|
|
7365
|
+
deleteToken: () => deleteToken,
|
|
7366
|
+
authStatus: () => authStatus,
|
|
7367
|
+
apiKeyLogin: () => apiKeyLogin
|
|
7368
|
+
});
|
|
7344
7369
|
import { createHash, randomBytes } from "node:crypto";
|
|
7345
7370
|
import {
|
|
7346
7371
|
chmodSync,
|
|
@@ -7350,8 +7375,10 @@ import {
|
|
|
7350
7375
|
unlinkSync,
|
|
7351
7376
|
writeFileSync as writeFileSync2
|
|
7352
7377
|
} from "node:fs";
|
|
7378
|
+
import { createServer } from "node:http";
|
|
7353
7379
|
import { homedir as homedir2 } from "node:os";
|
|
7354
7380
|
import { dirname, join as join2 } from "node:path";
|
|
7381
|
+
import { createInterface } from "node:readline";
|
|
7355
7382
|
function tokensDir(baseDir) {
|
|
7356
7383
|
return join2(baseDir ?? join2(homedir2(), ".mtpcli"), "tokens");
|
|
7357
7384
|
}
|
|
@@ -7371,6 +7398,13 @@ function loadToken(toolName, providerId, baseDir) {
|
|
|
7371
7398
|
return null;
|
|
7372
7399
|
return JSON.parse(readFileSync2(path2, "utf-8"));
|
|
7373
7400
|
}
|
|
7401
|
+
function deleteToken(toolName, providerId, baseDir) {
|
|
7402
|
+
const path2 = tokenPath(toolName, providerId, baseDir);
|
|
7403
|
+
if (!existsSync2(path2))
|
|
7404
|
+
return false;
|
|
7405
|
+
unlinkSync(path2);
|
|
7406
|
+
return true;
|
|
7407
|
+
}
|
|
7374
7408
|
function isTokenExpired(token) {
|
|
7375
7409
|
if (!token.expires_at)
|
|
7376
7410
|
return false;
|
|
@@ -7378,12 +7412,66 @@ function isTokenExpired(token) {
|
|
|
7378
7412
|
const buffer = 5 * 60 * 1000;
|
|
7379
7413
|
return exp.getTime() - buffer < Date.now();
|
|
7380
7414
|
}
|
|
7415
|
+
async function getAuthConfig(toolName) {
|
|
7416
|
+
const schema = await getToolSchema2(toolName);
|
|
7417
|
+
return schema?.auth ?? null;
|
|
7418
|
+
}
|
|
7419
|
+
function getProvider(auth, providerId) {
|
|
7420
|
+
if (providerId)
|
|
7421
|
+
return auth.providers.find((p) => p.id === providerId);
|
|
7422
|
+
return auth.providers[0];
|
|
7423
|
+
}
|
|
7381
7424
|
function generatePkce() {
|
|
7382
7425
|
const bytes = randomBytes(64);
|
|
7383
7426
|
const verifier = bytes.toString("base64url").slice(0, 128);
|
|
7384
7427
|
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
7385
7428
|
return { verifier, challenge };
|
|
7386
7429
|
}
|
|
7430
|
+
function waitForCallback(port, timeoutSecs) {
|
|
7431
|
+
return new Promise((resolve, reject) => {
|
|
7432
|
+
const timer = setTimeout(() => {
|
|
7433
|
+
server.close();
|
|
7434
|
+
reject(new Error("OAuth callback timed out"));
|
|
7435
|
+
}, timeoutSecs * 1000);
|
|
7436
|
+
const server = createServer((req, res) => {
|
|
7437
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
7438
|
+
const code = url.searchParams.get("code");
|
|
7439
|
+
const error = url.searchParams.get("error");
|
|
7440
|
+
if (code) {
|
|
7441
|
+
const html = "<html><body><h2>Authentication successful!</h2>" + "<p>You can close this tab and return to your terminal.</p>" + "<script>window.close()</script></body></html>";
|
|
7442
|
+
res.writeHead(200, {
|
|
7443
|
+
"Content-Type": "text/html",
|
|
7444
|
+
"Content-Length": Buffer.byteLength(html).toString()
|
|
7445
|
+
});
|
|
7446
|
+
res.end(html);
|
|
7447
|
+
clearTimeout(timer);
|
|
7448
|
+
server.close();
|
|
7449
|
+
resolve({ code });
|
|
7450
|
+
return;
|
|
7451
|
+
}
|
|
7452
|
+
if (error) {
|
|
7453
|
+
const desc = url.searchParams.get("error_description") ?? "Unknown error";
|
|
7454
|
+
const html = `<html><body><h2>Authentication failed</h2><p>${desc}</p></body></html>`;
|
|
7455
|
+
res.writeHead(400, {
|
|
7456
|
+
"Content-Type": "text/html",
|
|
7457
|
+
"Content-Length": Buffer.byteLength(html).toString()
|
|
7458
|
+
});
|
|
7459
|
+
res.end(html);
|
|
7460
|
+
clearTimeout(timer);
|
|
7461
|
+
server.close();
|
|
7462
|
+
resolve({ error });
|
|
7463
|
+
return;
|
|
7464
|
+
}
|
|
7465
|
+
res.writeHead(404);
|
|
7466
|
+
res.end();
|
|
7467
|
+
});
|
|
7468
|
+
server.listen(port, "127.0.0.1", () => {});
|
|
7469
|
+
server.on("error", (e) => {
|
|
7470
|
+
clearTimeout(timer);
|
|
7471
|
+
reject(new Error(`failed to bind callback server: ${e.message}`));
|
|
7472
|
+
});
|
|
7473
|
+
});
|
|
7474
|
+
}
|
|
7387
7475
|
async function exchangeCode(tokenUrl, code, clientId, redirectUri, codeVerifier, resource) {
|
|
7388
7476
|
const params = new URLSearchParams({
|
|
7389
7477
|
grant_type: "authorization_code",
|
|
@@ -7449,6 +7537,112 @@ function parseTokenResponse(text) {
|
|
|
7449
7537
|
created_at: new Date().toISOString()
|
|
7450
7538
|
};
|
|
7451
7539
|
}
|
|
7540
|
+
async function oauth2Login(toolName, provider, port) {
|
|
7541
|
+
if (!provider.authorizationUrl)
|
|
7542
|
+
throw new Error("provider missing authorizationUrl");
|
|
7543
|
+
if (!provider.tokenUrl)
|
|
7544
|
+
throw new Error("provider missing tokenUrl");
|
|
7545
|
+
if (!provider.clientId)
|
|
7546
|
+
throw new Error("provider missing clientId");
|
|
7547
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
7548
|
+
const state = randomBytes(32).toString("base64url");
|
|
7549
|
+
const params = new URLSearchParams({
|
|
7550
|
+
client_id: provider.clientId,
|
|
7551
|
+
redirect_uri: redirectUri,
|
|
7552
|
+
response_type: "code",
|
|
7553
|
+
state
|
|
7554
|
+
});
|
|
7555
|
+
if (provider.scopes?.length) {
|
|
7556
|
+
params.set("scope", provider.scopes.join(" "));
|
|
7557
|
+
}
|
|
7558
|
+
let codeVerifier;
|
|
7559
|
+
if (provider.type === "oauth2-pkce") {
|
|
7560
|
+
const pkce = generatePkce();
|
|
7561
|
+
codeVerifier = pkce.verifier;
|
|
7562
|
+
params.set("code_challenge", pkce.challenge);
|
|
7563
|
+
params.set("code_challenge_method", "S256");
|
|
7564
|
+
}
|
|
7565
|
+
const fullUrl = `${provider.authorizationUrl}?${params.toString()}`;
|
|
7566
|
+
const display = provider.displayName ?? provider.id;
|
|
7567
|
+
process.stderr.write(`Opening browser for ${display} login...
|
|
7568
|
+
`);
|
|
7569
|
+
process.stderr.write(`If the browser doesn't open, visit:
|
|
7570
|
+
${fullUrl}
|
|
7571
|
+
|
|
7572
|
+
`);
|
|
7573
|
+
open_default(fullUrl).catch(() => {});
|
|
7574
|
+
process.stderr.write(`Waiting for authentication callback on port ${port}...
|
|
7575
|
+
`);
|
|
7576
|
+
const result = await waitForCallback(port, 120);
|
|
7577
|
+
if (result.error)
|
|
7578
|
+
throw new Error(`OAuth error: ${result.error}`);
|
|
7579
|
+
if (!result.code)
|
|
7580
|
+
throw new Error("no authorization code received (timed out?)");
|
|
7581
|
+
process.stderr.write(`Exchanging authorization code for tokens...
|
|
7582
|
+
`);
|
|
7583
|
+
const token = await exchangeCode(provider.tokenUrl, result.code, provider.clientId, redirectUri, codeVerifier);
|
|
7584
|
+
token.provider_id = provider.id;
|
|
7585
|
+
storeToken(toolName, provider.id, token);
|
|
7586
|
+
process.stderr.write(`Authenticated with ${display}
|
|
7587
|
+
`);
|
|
7588
|
+
return token;
|
|
7589
|
+
}
|
|
7590
|
+
function apiKeyLogin(toolName, provider, tokenValue) {
|
|
7591
|
+
return new Promise((resolve, reject) => {
|
|
7592
|
+
const finish = (value) => {
|
|
7593
|
+
if (!value) {
|
|
7594
|
+
reject(new Error("no token provided"));
|
|
7595
|
+
return;
|
|
7596
|
+
}
|
|
7597
|
+
const token = {
|
|
7598
|
+
access_token: value,
|
|
7599
|
+
token_type: provider.type,
|
|
7600
|
+
refresh_token: undefined,
|
|
7601
|
+
expires_at: undefined,
|
|
7602
|
+
scopes: undefined,
|
|
7603
|
+
provider_id: provider.id,
|
|
7604
|
+
created_at: new Date().toISOString()
|
|
7605
|
+
};
|
|
7606
|
+
storeToken(toolName, provider.id, token);
|
|
7607
|
+
const display = provider.displayName ?? provider.id;
|
|
7608
|
+
process.stderr.write(`Token stored for ${display}
|
|
7609
|
+
`);
|
|
7610
|
+
resolve(token);
|
|
7611
|
+
};
|
|
7612
|
+
if (tokenValue) {
|
|
7613
|
+
finish(tokenValue);
|
|
7614
|
+
return;
|
|
7615
|
+
}
|
|
7616
|
+
if (provider.registrationUrl) {
|
|
7617
|
+
process.stderr.write(`Get your API key at: ${provider.registrationUrl}
|
|
7618
|
+
`);
|
|
7619
|
+
}
|
|
7620
|
+
if (provider.instructions) {
|
|
7621
|
+
process.stderr.write(`${provider.instructions}
|
|
7622
|
+
`);
|
|
7623
|
+
}
|
|
7624
|
+
process.stderr.write("Enter your token/API key: ");
|
|
7625
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
7626
|
+
rl.question("", (answer) => {
|
|
7627
|
+
rl.close();
|
|
7628
|
+
finish(answer.trim());
|
|
7629
|
+
});
|
|
7630
|
+
});
|
|
7631
|
+
}
|
|
7632
|
+
async function login(toolName, provider, token, port = 8914) {
|
|
7633
|
+
switch (provider.type) {
|
|
7634
|
+
case "oauth2":
|
|
7635
|
+
case "oauth2-pkce":
|
|
7636
|
+
if (token)
|
|
7637
|
+
return apiKeyLogin(toolName, provider, token);
|
|
7638
|
+
return oauth2Login(toolName, provider, port);
|
|
7639
|
+
case "api-key":
|
|
7640
|
+
case "bearer":
|
|
7641
|
+
return apiKeyLogin(toolName, provider, token);
|
|
7642
|
+
default:
|
|
7643
|
+
throw new Error(`unknown auth type: ${provider.type}`);
|
|
7644
|
+
}
|
|
7645
|
+
}
|
|
7452
7646
|
async function ensureValidToken(toolName, auth) {
|
|
7453
7647
|
const provider = auth.providers[0];
|
|
7454
7648
|
if (!provider)
|
|
@@ -7482,7 +7676,121 @@ async function getEnvDict(toolName, auth) {
|
|
|
7482
7676
|
return {};
|
|
7483
7677
|
return { [auth.envVar]: token };
|
|
7484
7678
|
}
|
|
7679
|
+
async function getEnvExport(toolName, auth) {
|
|
7680
|
+
const env = await getEnvDict(toolName, auth);
|
|
7681
|
+
if (Object.keys(env).length === 0) {
|
|
7682
|
+
return "# No token available. Run: mtpcli auth login <tool>";
|
|
7683
|
+
}
|
|
7684
|
+
return Object.entries(env).map(([key, value]) => {
|
|
7685
|
+
const escaped = value.replace(/'/g, "'\\''");
|
|
7686
|
+
return `export ${key}='${escaped}'`;
|
|
7687
|
+
}).join(`
|
|
7688
|
+
`);
|
|
7689
|
+
}
|
|
7690
|
+
async function authStatus(toolName, auth) {
|
|
7691
|
+
const providers = [];
|
|
7692
|
+
for (const provider of auth.providers) {
|
|
7693
|
+
const token = loadToken(toolName, provider.id);
|
|
7694
|
+
const status = {
|
|
7695
|
+
id: provider.id,
|
|
7696
|
+
type: provider.type,
|
|
7697
|
+
display_name: provider.displayName ?? provider.id,
|
|
7698
|
+
authenticated: token !== null
|
|
7699
|
+
};
|
|
7700
|
+
if (token) {
|
|
7701
|
+
status.expired = isTokenExpired(token);
|
|
7702
|
+
status.has_refresh = !!token.refresh_token;
|
|
7703
|
+
if (token.expires_at)
|
|
7704
|
+
status.expires_at = token.expires_at;
|
|
7705
|
+
if (token.scopes)
|
|
7706
|
+
status.scopes = token.scopes;
|
|
7707
|
+
}
|
|
7708
|
+
providers.push(status);
|
|
7709
|
+
}
|
|
7710
|
+
return {
|
|
7711
|
+
tool: toolName,
|
|
7712
|
+
env_var: auth.envVar,
|
|
7713
|
+
required: auth.required,
|
|
7714
|
+
providers
|
|
7715
|
+
};
|
|
7716
|
+
}
|
|
7717
|
+
async function runLogin(toolName, providerId, token) {
|
|
7718
|
+
const auth = await getAuthConfig(toolName);
|
|
7719
|
+
if (!auth)
|
|
7720
|
+
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
7721
|
+
const provider = getProvider(auth, providerId);
|
|
7722
|
+
if (!provider)
|
|
7723
|
+
throw new Error("no matching auth provider found");
|
|
7724
|
+
await login(toolName, provider, token, 8914);
|
|
7725
|
+
}
|
|
7726
|
+
async function runLogout(toolName) {
|
|
7727
|
+
const auth = await getAuthConfig(toolName);
|
|
7728
|
+
if (!auth)
|
|
7729
|
+
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
7730
|
+
let deleted = false;
|
|
7731
|
+
for (const provider of auth.providers) {
|
|
7732
|
+
if (deleteToken(toolName, provider.id)) {
|
|
7733
|
+
const display = provider.displayName ?? provider.id;
|
|
7734
|
+
process.stderr.write(`Logged out from ${display}
|
|
7735
|
+
`);
|
|
7736
|
+
deleted = true;
|
|
7737
|
+
}
|
|
7738
|
+
}
|
|
7739
|
+
if (!deleted)
|
|
7740
|
+
process.stderr.write(`No tokens found for '${toolName}'
|
|
7741
|
+
`);
|
|
7742
|
+
}
|
|
7743
|
+
async function runStatus(toolName) {
|
|
7744
|
+
const auth = await getAuthConfig(toolName);
|
|
7745
|
+
if (!auth)
|
|
7746
|
+
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
7747
|
+
const status = await authStatus(toolName, auth);
|
|
7748
|
+
console.log(JSON.stringify(status, null, 2));
|
|
7749
|
+
}
|
|
7750
|
+
async function runToken(toolName) {
|
|
7751
|
+
const auth = await getAuthConfig(toolName);
|
|
7752
|
+
if (!auth)
|
|
7753
|
+
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
7754
|
+
const t = await ensureValidToken(toolName, auth);
|
|
7755
|
+
if (!t)
|
|
7756
|
+
throw new Error(`no valid token for '${toolName}'. Run: mtpcli auth login ${toolName}`);
|
|
7757
|
+
console.log(t);
|
|
7758
|
+
}
|
|
7759
|
+
async function runEnv(toolName) {
|
|
7760
|
+
const auth = await getAuthConfig(toolName);
|
|
7761
|
+
if (!auth)
|
|
7762
|
+
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
7763
|
+
console.log(await getEnvExport(toolName, auth));
|
|
7764
|
+
}
|
|
7765
|
+
async function runRefresh(toolName) {
|
|
7766
|
+
const authConfig = await getAuthConfig(toolName);
|
|
7767
|
+
if (!authConfig)
|
|
7768
|
+
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
7769
|
+
const provider = authConfig.providers[0];
|
|
7770
|
+
if (!provider)
|
|
7771
|
+
throw new Error("no auth provider configured");
|
|
7772
|
+
const token = loadToken(toolName, provider.id);
|
|
7773
|
+
if (!token) {
|
|
7774
|
+
throw new Error(`no stored token for '${toolName}'. Run: mtpcli auth login ${toolName}`);
|
|
7775
|
+
}
|
|
7776
|
+
if (!token.refresh_token) {
|
|
7777
|
+
throw new Error(`token for '${toolName}' has no refresh_token (provider type: ${provider.type})`);
|
|
7778
|
+
}
|
|
7779
|
+
if (!provider.tokenUrl || !provider.clientId) {
|
|
7780
|
+
throw new Error("provider missing tokenUrl or clientId for refresh");
|
|
7781
|
+
}
|
|
7782
|
+
const newToken = await refreshAccessToken(provider.tokenUrl, token.refresh_token, provider.clientId);
|
|
7783
|
+
if (!newToken.refresh_token) {
|
|
7784
|
+
newToken.refresh_token = token.refresh_token;
|
|
7785
|
+
}
|
|
7786
|
+
newToken.provider_id = provider.id;
|
|
7787
|
+
storeToken(toolName, provider.id, newToken);
|
|
7788
|
+
const display = provider.displayName ?? provider.id;
|
|
7789
|
+
process.stderr.write(`Token refreshed for ${display}
|
|
7790
|
+
`);
|
|
7791
|
+
}
|
|
7485
7792
|
var init_auth = __esm(() => {
|
|
7793
|
+
init_open();
|
|
7486
7794
|
init_search2();
|
|
7487
7795
|
});
|
|
7488
7796
|
|
|
@@ -7495,6 +7803,10 @@ __export(exports_mcp_oauth, {
|
|
|
7495
7803
|
promptForClientId: () => promptForClientId,
|
|
7496
7804
|
parseWwwAuthenticate: () => parseWwwAuthenticate,
|
|
7497
7805
|
mcpOAuthFlow: () => mcpOAuthFlow,
|
|
7806
|
+
mcpAuthToken: () => mcpAuthToken,
|
|
7807
|
+
mcpAuthStatus: () => mcpAuthStatus,
|
|
7808
|
+
mcpAuthRefresh: () => mcpAuthRefresh,
|
|
7809
|
+
mcpAuthLogout: () => mcpAuthLogout,
|
|
7498
7810
|
loadOrRefreshMcpToken: () => loadOrRefreshMcpToken,
|
|
7499
7811
|
loadClientRegistration: () => loadClientRegistration,
|
|
7500
7812
|
fetchResourceMetadata: () => fetchResourceMetadata,
|
|
@@ -7508,10 +7820,10 @@ import {
|
|
|
7508
7820
|
writeFileSync as writeFileSync3,
|
|
7509
7821
|
chmodSync as chmodSync2
|
|
7510
7822
|
} from "node:fs";
|
|
7511
|
-
import { createServer } from "node:http";
|
|
7823
|
+
import { createServer as createServer2 } from "node:http";
|
|
7512
7824
|
import { homedir as homedir3 } from "node:os";
|
|
7513
7825
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
7514
|
-
import { createInterface } from "node:readline";
|
|
7826
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
7515
7827
|
function serverStorageKey(url) {
|
|
7516
7828
|
const u = new URL(url);
|
|
7517
7829
|
return `mcp_${u.host}`;
|
|
@@ -7612,7 +7924,7 @@ function promptForClientId(serverUrl) {
|
|
|
7612
7924
|
No dynamic registration available for ${serverUrl}.
|
|
7613
7925
|
` + `You need to register a client manually and provide the client_id.
|
|
7614
7926
|
` + `Enter client_id: `);
|
|
7615
|
-
const rl =
|
|
7927
|
+
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
7616
7928
|
rl.question("", (answer) => {
|
|
7617
7929
|
rl.close();
|
|
7618
7930
|
const trimmed = answer.trim();
|
|
@@ -7660,7 +7972,7 @@ function startCallbackServer() {
|
|
|
7660
7972
|
codeResolve = res;
|
|
7661
7973
|
codeReject = rej;
|
|
7662
7974
|
});
|
|
7663
|
-
const server =
|
|
7975
|
+
const server = createServer2((req, res) => {
|
|
7664
7976
|
const url = new URL(req.url ?? "/", `http://127.0.0.1`);
|
|
7665
7977
|
const code = url.searchParams.get("code");
|
|
7666
7978
|
const error = url.searchParams.get("error");
|
|
@@ -7792,6 +8104,58 @@ ${authUrl}
|
|
|
7792
8104
|
`);
|
|
7793
8105
|
return token.access_token;
|
|
7794
8106
|
}
|
|
8107
|
+
function mcpAuthStatus(serverUrl) {
|
|
8108
|
+
const storageKey = serverStorageKey(serverUrl);
|
|
8109
|
+
const origin = new URL(serverUrl).origin;
|
|
8110
|
+
const token = loadToken(storageKey, "mcp-oauth");
|
|
8111
|
+
const reg = loadClientRegistration(origin);
|
|
8112
|
+
const result = {
|
|
8113
|
+
url: serverUrl,
|
|
8114
|
+
authenticated: token !== null
|
|
8115
|
+
};
|
|
8116
|
+
if (token) {
|
|
8117
|
+
result.expired = isTokenExpired(token);
|
|
8118
|
+
result.has_refresh = !!token.refresh_token;
|
|
8119
|
+
if (token.expires_at)
|
|
8120
|
+
result.expires_at = token.expires_at;
|
|
8121
|
+
if (token.scopes)
|
|
8122
|
+
result.scopes = token.scopes;
|
|
8123
|
+
}
|
|
8124
|
+
if (reg) {
|
|
8125
|
+
result.client_id = reg.client.clientId;
|
|
8126
|
+
}
|
|
8127
|
+
return result;
|
|
8128
|
+
}
|
|
8129
|
+
function mcpAuthLogout(serverUrl) {
|
|
8130
|
+
const storageKey = serverStorageKey(serverUrl);
|
|
8131
|
+
return deleteToken(storageKey, "mcp-oauth");
|
|
8132
|
+
}
|
|
8133
|
+
async function mcpAuthToken(serverUrl) {
|
|
8134
|
+
return loadOrRefreshMcpToken(serverUrl);
|
|
8135
|
+
}
|
|
8136
|
+
async function mcpAuthRefresh(serverUrl) {
|
|
8137
|
+
const storageKey = serverStorageKey(serverUrl);
|
|
8138
|
+
const origin = new URL(serverUrl).origin;
|
|
8139
|
+
const token = loadToken(storageKey, "mcp-oauth");
|
|
8140
|
+
if (!token) {
|
|
8141
|
+
throw new Error(`no stored token for '${serverUrl}'. Run: mtpcli auth login --url ${serverUrl}`);
|
|
8142
|
+
}
|
|
8143
|
+
if (!token.refresh_token) {
|
|
8144
|
+
throw new Error(`token for '${serverUrl}' has no refresh_token`);
|
|
8145
|
+
}
|
|
8146
|
+
const cached = loadClientRegistration(origin);
|
|
8147
|
+
if (!cached) {
|
|
8148
|
+
throw new Error(`no cached client registration for ${origin}. Run: mtpcli auth login --url ${serverUrl}`);
|
|
8149
|
+
}
|
|
8150
|
+
const newToken = await refreshAccessToken(cached.metadata.token_endpoint, token.refresh_token, cached.client.clientId);
|
|
8151
|
+
if (!newToken.refresh_token) {
|
|
8152
|
+
newToken.refresh_token = token.refresh_token;
|
|
8153
|
+
}
|
|
8154
|
+
newToken.provider_id = "mcp-oauth";
|
|
8155
|
+
storeToken(storageKey, "mcp-oauth", newToken);
|
|
8156
|
+
process.stderr.write(`Token refreshed for ${serverUrl}
|
|
8157
|
+
`);
|
|
8158
|
+
}
|
|
7795
8159
|
async function loadOrRefreshMcpToken(serverUrl) {
|
|
7796
8160
|
const storageKey = serverStorageKey(serverUrl);
|
|
7797
8161
|
const token = loadToken(storageKey, "mcp-oauth");
|
|
@@ -7826,30 +8190,30 @@ var init_mcp_oauth = __esm(() => {
|
|
|
7826
8190
|
});
|
|
7827
8191
|
|
|
7828
8192
|
// src/auth.ts
|
|
7829
|
-
var
|
|
7830
|
-
__export(
|
|
8193
|
+
var exports_auth2 = {};
|
|
8194
|
+
__export(exports_auth2, {
|
|
7831
8195
|
storeToken: () => storeToken2,
|
|
7832
|
-
runToken: () =>
|
|
7833
|
-
runStatus: () =>
|
|
7834
|
-
runRefresh: () =>
|
|
7835
|
-
runLogout: () =>
|
|
7836
|
-
runLogin: () =>
|
|
7837
|
-
runEnv: () =>
|
|
8196
|
+
runToken: () => runToken2,
|
|
8197
|
+
runStatus: () => runStatus2,
|
|
8198
|
+
runRefresh: () => runRefresh2,
|
|
8199
|
+
runLogout: () => runLogout2,
|
|
8200
|
+
runLogin: () => runLogin2,
|
|
8201
|
+
runEnv: () => runEnv2,
|
|
7838
8202
|
refreshAccessToken: () => refreshAccessToken2,
|
|
7839
|
-
oauth2Login: () =>
|
|
7840
|
-
login: () =>
|
|
8203
|
+
oauth2Login: () => oauth2Login2,
|
|
8204
|
+
login: () => login2,
|
|
7841
8205
|
loadToken: () => loadToken2,
|
|
7842
8206
|
isTokenExpired: () => isTokenExpired2,
|
|
7843
|
-
getProvider: () =>
|
|
7844
|
-
getEnvExport: () =>
|
|
8207
|
+
getProvider: () => getProvider2,
|
|
8208
|
+
getEnvExport: () => getEnvExport2,
|
|
7845
8209
|
getEnvDict: () => getEnvDict2,
|
|
7846
|
-
getAuthConfig: () =>
|
|
8210
|
+
getAuthConfig: () => getAuthConfig2,
|
|
7847
8211
|
generatePkce: () => generatePkce2,
|
|
7848
8212
|
exchangeCode: () => exchangeCode2,
|
|
7849
8213
|
ensureValidToken: () => ensureValidToken2,
|
|
7850
|
-
deleteToken: () =>
|
|
7851
|
-
authStatus: () =>
|
|
7852
|
-
apiKeyLogin: () =>
|
|
8214
|
+
deleteToken: () => deleteToken2,
|
|
8215
|
+
authStatus: () => authStatus2,
|
|
8216
|
+
apiKeyLogin: () => apiKeyLogin2
|
|
7853
8217
|
});
|
|
7854
8218
|
import { createHash as createHash2, randomBytes as randomBytes3 } from "node:crypto";
|
|
7855
8219
|
import {
|
|
@@ -7860,10 +8224,10 @@ import {
|
|
|
7860
8224
|
unlinkSync as unlinkSync2,
|
|
7861
8225
|
writeFileSync as writeFileSync4
|
|
7862
8226
|
} from "node:fs";
|
|
7863
|
-
import { createServer as
|
|
8227
|
+
import { createServer as createServer3 } from "node:http";
|
|
7864
8228
|
import { homedir as homedir4 } from "node:os";
|
|
7865
8229
|
import { dirname as dirname3, join as join4 } from "node:path";
|
|
7866
|
-
import { createInterface as
|
|
8230
|
+
import { createInterface as createInterface3 } from "node:readline";
|
|
7867
8231
|
function tokensDir2(baseDir) {
|
|
7868
8232
|
return join4(baseDir ?? join4(homedir4(), ".mtpcli"), "tokens");
|
|
7869
8233
|
}
|
|
@@ -7883,7 +8247,7 @@ function loadToken2(toolName, providerId, baseDir) {
|
|
|
7883
8247
|
return null;
|
|
7884
8248
|
return JSON.parse(readFileSync4(path2, "utf-8"));
|
|
7885
8249
|
}
|
|
7886
|
-
function
|
|
8250
|
+
function deleteToken2(toolName, providerId, baseDir) {
|
|
7887
8251
|
const path2 = tokenPath2(toolName, providerId, baseDir);
|
|
7888
8252
|
if (!existsSync4(path2))
|
|
7889
8253
|
return false;
|
|
@@ -7897,11 +8261,11 @@ function isTokenExpired2(token) {
|
|
|
7897
8261
|
const buffer = 5 * 60 * 1000;
|
|
7898
8262
|
return exp.getTime() - buffer < Date.now();
|
|
7899
8263
|
}
|
|
7900
|
-
async function
|
|
8264
|
+
async function getAuthConfig2(toolName) {
|
|
7901
8265
|
const schema = await getToolSchema2(toolName);
|
|
7902
8266
|
return schema?.auth ?? null;
|
|
7903
8267
|
}
|
|
7904
|
-
function
|
|
8268
|
+
function getProvider2(auth, providerId) {
|
|
7905
8269
|
if (providerId)
|
|
7906
8270
|
return auth.providers.find((p) => p.id === providerId);
|
|
7907
8271
|
return auth.providers[0];
|
|
@@ -7912,13 +8276,13 @@ function generatePkce2() {
|
|
|
7912
8276
|
const challenge = createHash2("sha256").update(verifier).digest("base64url");
|
|
7913
8277
|
return { verifier, challenge };
|
|
7914
8278
|
}
|
|
7915
|
-
function
|
|
8279
|
+
function waitForCallback2(port, timeoutSecs) {
|
|
7916
8280
|
return new Promise((resolve, reject) => {
|
|
7917
8281
|
const timer = setTimeout(() => {
|
|
7918
8282
|
server.close();
|
|
7919
8283
|
reject(new Error("OAuth callback timed out"));
|
|
7920
8284
|
}, timeoutSecs * 1000);
|
|
7921
|
-
const server =
|
|
8285
|
+
const server = createServer3((req, res) => {
|
|
7922
8286
|
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
7923
8287
|
const code = url.searchParams.get("code");
|
|
7924
8288
|
const error = url.searchParams.get("error");
|
|
@@ -8022,7 +8386,7 @@ function parseTokenResponse2(text) {
|
|
|
8022
8386
|
created_at: new Date().toISOString()
|
|
8023
8387
|
};
|
|
8024
8388
|
}
|
|
8025
|
-
async function
|
|
8389
|
+
async function oauth2Login2(toolName, provider, port) {
|
|
8026
8390
|
if (!provider.authorizationUrl)
|
|
8027
8391
|
throw new Error("provider missing authorizationUrl");
|
|
8028
8392
|
if (!provider.tokenUrl)
|
|
@@ -8058,7 +8422,7 @@ ${fullUrl}
|
|
|
8058
8422
|
open_default(fullUrl).catch(() => {});
|
|
8059
8423
|
process.stderr.write(`Waiting for authentication callback on port ${port}...
|
|
8060
8424
|
`);
|
|
8061
|
-
const result = await
|
|
8425
|
+
const result = await waitForCallback2(port, 120);
|
|
8062
8426
|
if (result.error)
|
|
8063
8427
|
throw new Error(`OAuth error: ${result.error}`);
|
|
8064
8428
|
if (!result.code)
|
|
@@ -8072,7 +8436,7 @@ ${fullUrl}
|
|
|
8072
8436
|
`);
|
|
8073
8437
|
return token;
|
|
8074
8438
|
}
|
|
8075
|
-
function
|
|
8439
|
+
function apiKeyLogin2(toolName, provider, tokenValue) {
|
|
8076
8440
|
return new Promise((resolve, reject) => {
|
|
8077
8441
|
const finish = (value) => {
|
|
8078
8442
|
if (!value) {
|
|
@@ -8107,23 +8471,23 @@ function apiKeyLogin(toolName, provider, tokenValue) {
|
|
|
8107
8471
|
`);
|
|
8108
8472
|
}
|
|
8109
8473
|
process.stderr.write("Enter your token/API key: ");
|
|
8110
|
-
const rl =
|
|
8474
|
+
const rl = createInterface3({ input: process.stdin, output: process.stderr });
|
|
8111
8475
|
rl.question("", (answer) => {
|
|
8112
8476
|
rl.close();
|
|
8113
8477
|
finish(answer.trim());
|
|
8114
8478
|
});
|
|
8115
8479
|
});
|
|
8116
8480
|
}
|
|
8117
|
-
async function
|
|
8481
|
+
async function login2(toolName, provider, token, port = 8914) {
|
|
8118
8482
|
switch (provider.type) {
|
|
8119
8483
|
case "oauth2":
|
|
8120
8484
|
case "oauth2-pkce":
|
|
8121
8485
|
if (token)
|
|
8122
|
-
return
|
|
8123
|
-
return
|
|
8486
|
+
return apiKeyLogin2(toolName, provider, token);
|
|
8487
|
+
return oauth2Login2(toolName, provider, port);
|
|
8124
8488
|
case "api-key":
|
|
8125
8489
|
case "bearer":
|
|
8126
|
-
return
|
|
8490
|
+
return apiKeyLogin2(toolName, provider, token);
|
|
8127
8491
|
default:
|
|
8128
8492
|
throw new Error(`unknown auth type: ${provider.type}`);
|
|
8129
8493
|
}
|
|
@@ -8161,7 +8525,7 @@ async function getEnvDict2(toolName, auth) {
|
|
|
8161
8525
|
return {};
|
|
8162
8526
|
return { [auth.envVar]: token };
|
|
8163
8527
|
}
|
|
8164
|
-
async function
|
|
8528
|
+
async function getEnvExport2(toolName, auth) {
|
|
8165
8529
|
const env = await getEnvDict2(toolName, auth);
|
|
8166
8530
|
if (Object.keys(env).length === 0) {
|
|
8167
8531
|
return "# No token available. Run: mtpcli auth login <tool>";
|
|
@@ -8172,7 +8536,7 @@ async function getEnvExport(toolName, auth) {
|
|
|
8172
8536
|
}).join(`
|
|
8173
8537
|
`);
|
|
8174
8538
|
}
|
|
8175
|
-
async function
|
|
8539
|
+
async function authStatus2(toolName, auth) {
|
|
8176
8540
|
const providers = [];
|
|
8177
8541
|
for (const provider of auth.providers) {
|
|
8178
8542
|
const token = loadToken2(toolName, provider.id);
|
|
@@ -8199,22 +8563,22 @@ async function authStatus(toolName, auth) {
|
|
|
8199
8563
|
providers
|
|
8200
8564
|
};
|
|
8201
8565
|
}
|
|
8202
|
-
async function
|
|
8203
|
-
const auth = await
|
|
8566
|
+
async function runLogin2(toolName, providerId, token) {
|
|
8567
|
+
const auth = await getAuthConfig2(toolName);
|
|
8204
8568
|
if (!auth)
|
|
8205
8569
|
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
8206
|
-
const provider =
|
|
8570
|
+
const provider = getProvider2(auth, providerId);
|
|
8207
8571
|
if (!provider)
|
|
8208
8572
|
throw new Error("no matching auth provider found");
|
|
8209
|
-
await
|
|
8573
|
+
await login2(toolName, provider, token, 8914);
|
|
8210
8574
|
}
|
|
8211
|
-
async function
|
|
8212
|
-
const auth = await
|
|
8575
|
+
async function runLogout2(toolName) {
|
|
8576
|
+
const auth = await getAuthConfig2(toolName);
|
|
8213
8577
|
if (!auth)
|
|
8214
8578
|
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
8215
8579
|
let deleted = false;
|
|
8216
8580
|
for (const provider of auth.providers) {
|
|
8217
|
-
if (
|
|
8581
|
+
if (deleteToken2(toolName, provider.id)) {
|
|
8218
8582
|
const display = provider.displayName ?? provider.id;
|
|
8219
8583
|
process.stderr.write(`Logged out from ${display}
|
|
8220
8584
|
`);
|
|
@@ -8225,15 +8589,15 @@ async function runLogout(toolName) {
|
|
|
8225
8589
|
process.stderr.write(`No tokens found for '${toolName}'
|
|
8226
8590
|
`);
|
|
8227
8591
|
}
|
|
8228
|
-
async function
|
|
8229
|
-
const auth = await
|
|
8592
|
+
async function runStatus2(toolName) {
|
|
8593
|
+
const auth = await getAuthConfig2(toolName);
|
|
8230
8594
|
if (!auth)
|
|
8231
8595
|
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
8232
|
-
const status = await
|
|
8596
|
+
const status = await authStatus2(toolName, auth);
|
|
8233
8597
|
console.log(JSON.stringify(status, null, 2));
|
|
8234
8598
|
}
|
|
8235
|
-
async function
|
|
8236
|
-
const auth = await
|
|
8599
|
+
async function runToken2(toolName) {
|
|
8600
|
+
const auth = await getAuthConfig2(toolName);
|
|
8237
8601
|
if (!auth)
|
|
8238
8602
|
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
8239
8603
|
const t = await ensureValidToken2(toolName, auth);
|
|
@@ -8241,14 +8605,14 @@ async function runToken(toolName) {
|
|
|
8241
8605
|
throw new Error(`no valid token for '${toolName}'. Run: mtpcli auth login ${toolName}`);
|
|
8242
8606
|
console.log(t);
|
|
8243
8607
|
}
|
|
8244
|
-
async function
|
|
8245
|
-
const auth = await
|
|
8608
|
+
async function runEnv2(toolName) {
|
|
8609
|
+
const auth = await getAuthConfig2(toolName);
|
|
8246
8610
|
if (!auth)
|
|
8247
8611
|
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
8248
|
-
console.log(await
|
|
8612
|
+
console.log(await getEnvExport2(toolName, auth));
|
|
8249
8613
|
}
|
|
8250
|
-
async function
|
|
8251
|
-
const authConfig = await
|
|
8614
|
+
async function runRefresh2(toolName) {
|
|
8615
|
+
const authConfig = await getAuthConfig2(toolName);
|
|
8252
8616
|
if (!authConfig)
|
|
8253
8617
|
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
8254
8618
|
const provider = authConfig.providers[0];
|
|
@@ -8340,7 +8704,7 @@ __export(exports_serve, {
|
|
|
8340
8704
|
argToJsonSchema: () => argToJsonSchema
|
|
8341
8705
|
});
|
|
8342
8706
|
import { execFile as execFile9, spawn } from "node:child_process";
|
|
8343
|
-
import { createInterface as
|
|
8707
|
+
import { createInterface as createInterface4 } from "node:readline";
|
|
8344
8708
|
import { promisify as promisify9 } from "node:util";
|
|
8345
8709
|
function argToJsonSchema(arg) {
|
|
8346
8710
|
const schema = {};
|
|
@@ -8563,7 +8927,7 @@ async function run(toolNames) {
|
|
|
8563
8927
|
schemas: allSchemas,
|
|
8564
8928
|
authConfigs: allAuth
|
|
8565
8929
|
};
|
|
8566
|
-
const rl =
|
|
8930
|
+
const rl = createInterface4({ input: process.stdin, crlfDelay: Infinity });
|
|
8567
8931
|
for await (const line of rl) {
|
|
8568
8932
|
const trimmed = line.trim();
|
|
8569
8933
|
if (!trimmed)
|
|
@@ -8602,6 +8966,10 @@ __export(exports_mcp_oauth2, {
|
|
|
8602
8966
|
promptForClientId: () => promptForClientId2,
|
|
8603
8967
|
parseWwwAuthenticate: () => parseWwwAuthenticate2,
|
|
8604
8968
|
mcpOAuthFlow: () => mcpOAuthFlow2,
|
|
8969
|
+
mcpAuthToken: () => mcpAuthToken2,
|
|
8970
|
+
mcpAuthStatus: () => mcpAuthStatus2,
|
|
8971
|
+
mcpAuthRefresh: () => mcpAuthRefresh2,
|
|
8972
|
+
mcpAuthLogout: () => mcpAuthLogout2,
|
|
8605
8973
|
loadOrRefreshMcpToken: () => loadOrRefreshMcpToken2,
|
|
8606
8974
|
loadClientRegistration: () => loadClientRegistration2,
|
|
8607
8975
|
fetchResourceMetadata: () => fetchResourceMetadata2,
|
|
@@ -8615,10 +8983,10 @@ import {
|
|
|
8615
8983
|
writeFileSync as writeFileSync5,
|
|
8616
8984
|
chmodSync as chmodSync4
|
|
8617
8985
|
} from "node:fs";
|
|
8618
|
-
import { createServer as
|
|
8986
|
+
import { createServer as createServer4 } from "node:http";
|
|
8619
8987
|
import { homedir as homedir5 } from "node:os";
|
|
8620
8988
|
import { dirname as dirname4, join as join5 } from "node:path";
|
|
8621
|
-
import { createInterface as
|
|
8989
|
+
import { createInterface as createInterface5 } from "node:readline";
|
|
8622
8990
|
function serverStorageKey2(url) {
|
|
8623
8991
|
const u = new URL(url);
|
|
8624
8992
|
return `mcp_${u.host}`;
|
|
@@ -8719,7 +9087,7 @@ function promptForClientId2(serverUrl) {
|
|
|
8719
9087
|
No dynamic registration available for ${serverUrl}.
|
|
8720
9088
|
` + `You need to register a client manually and provide the client_id.
|
|
8721
9089
|
` + `Enter client_id: `);
|
|
8722
|
-
const rl =
|
|
9090
|
+
const rl = createInterface5({ input: process.stdin, output: process.stderr });
|
|
8723
9091
|
rl.question("", (answer) => {
|
|
8724
9092
|
rl.close();
|
|
8725
9093
|
const trimmed = answer.trim();
|
|
@@ -8767,7 +9135,7 @@ function startCallbackServer2() {
|
|
|
8767
9135
|
codeResolve = res;
|
|
8768
9136
|
codeReject = rej;
|
|
8769
9137
|
});
|
|
8770
|
-
const server =
|
|
9138
|
+
const server = createServer4((req, res) => {
|
|
8771
9139
|
const url = new URL(req.url ?? "/", `http://127.0.0.1`);
|
|
8772
9140
|
const code = url.searchParams.get("code");
|
|
8773
9141
|
const error = url.searchParams.get("error");
|
|
@@ -8899,6 +9267,58 @@ ${authUrl}
|
|
|
8899
9267
|
`);
|
|
8900
9268
|
return token.access_token;
|
|
8901
9269
|
}
|
|
9270
|
+
function mcpAuthStatus2(serverUrl) {
|
|
9271
|
+
const storageKey = serverStorageKey2(serverUrl);
|
|
9272
|
+
const origin = new URL(serverUrl).origin;
|
|
9273
|
+
const token = loadToken(storageKey, "mcp-oauth");
|
|
9274
|
+
const reg = loadClientRegistration2(origin);
|
|
9275
|
+
const result = {
|
|
9276
|
+
url: serverUrl,
|
|
9277
|
+
authenticated: token !== null
|
|
9278
|
+
};
|
|
9279
|
+
if (token) {
|
|
9280
|
+
result.expired = isTokenExpired(token);
|
|
9281
|
+
result.has_refresh = !!token.refresh_token;
|
|
9282
|
+
if (token.expires_at)
|
|
9283
|
+
result.expires_at = token.expires_at;
|
|
9284
|
+
if (token.scopes)
|
|
9285
|
+
result.scopes = token.scopes;
|
|
9286
|
+
}
|
|
9287
|
+
if (reg) {
|
|
9288
|
+
result.client_id = reg.client.clientId;
|
|
9289
|
+
}
|
|
9290
|
+
return result;
|
|
9291
|
+
}
|
|
9292
|
+
function mcpAuthLogout2(serverUrl) {
|
|
9293
|
+
const storageKey = serverStorageKey2(serverUrl);
|
|
9294
|
+
return deleteToken(storageKey, "mcp-oauth");
|
|
9295
|
+
}
|
|
9296
|
+
async function mcpAuthToken2(serverUrl) {
|
|
9297
|
+
return loadOrRefreshMcpToken2(serverUrl);
|
|
9298
|
+
}
|
|
9299
|
+
async function mcpAuthRefresh2(serverUrl) {
|
|
9300
|
+
const storageKey = serverStorageKey2(serverUrl);
|
|
9301
|
+
const origin = new URL(serverUrl).origin;
|
|
9302
|
+
const token = loadToken(storageKey, "mcp-oauth");
|
|
9303
|
+
if (!token) {
|
|
9304
|
+
throw new Error(`no stored token for '${serverUrl}'. Run: mtpcli auth login --url ${serverUrl}`);
|
|
9305
|
+
}
|
|
9306
|
+
if (!token.refresh_token) {
|
|
9307
|
+
throw new Error(`token for '${serverUrl}' has no refresh_token`);
|
|
9308
|
+
}
|
|
9309
|
+
const cached = loadClientRegistration2(origin);
|
|
9310
|
+
if (!cached) {
|
|
9311
|
+
throw new Error(`no cached client registration for ${origin}. Run: mtpcli auth login --url ${serverUrl}`);
|
|
9312
|
+
}
|
|
9313
|
+
const newToken = await refreshAccessToken(cached.metadata.token_endpoint, token.refresh_token, cached.client.clientId);
|
|
9314
|
+
if (!newToken.refresh_token) {
|
|
9315
|
+
newToken.refresh_token = token.refresh_token;
|
|
9316
|
+
}
|
|
9317
|
+
newToken.provider_id = "mcp-oauth";
|
|
9318
|
+
storeToken(storageKey, "mcp-oauth", newToken);
|
|
9319
|
+
process.stderr.write(`Token refreshed for ${serverUrl}
|
|
9320
|
+
`);
|
|
9321
|
+
}
|
|
8902
9322
|
async function loadOrRefreshMcpToken2(serverUrl) {
|
|
8903
9323
|
const storageKey = serverStorageKey2(serverUrl);
|
|
8904
9324
|
const token = loadToken(storageKey, "mcp-oauth");
|
|
@@ -8944,7 +9364,7 @@ __export(exports_wrap, {
|
|
|
8944
9364
|
HttpMcpClient: () => HttpMcpClient
|
|
8945
9365
|
});
|
|
8946
9366
|
import { spawn as spawn2 } from "node:child_process";
|
|
8947
|
-
import { createInterface as
|
|
9367
|
+
import { createInterface as createInterface6 } from "node:readline";
|
|
8948
9368
|
function jsonSchemaToArg(name, prop, required) {
|
|
8949
9369
|
let argType = "string";
|
|
8950
9370
|
let values;
|
|
@@ -9096,7 +9516,7 @@ class McpClient {
|
|
|
9096
9516
|
if (!child.stdout || !child.stdin) {
|
|
9097
9517
|
throw new Error("failed to start MCP server");
|
|
9098
9518
|
}
|
|
9099
|
-
const rl =
|
|
9519
|
+
const rl = createInterface6({ input: child.stdout, crlfDelay: Infinity });
|
|
9100
9520
|
const client = new McpClient(child, rl);
|
|
9101
9521
|
client.sendRequest("initialize", {
|
|
9102
9522
|
protocolVersion: "2024-11-05",
|
|
@@ -9226,6 +9646,16 @@ class HttpMcpClient {
|
|
|
9226
9646
|
throw new Error(`HTTP 401 from ${this.url} (OAuth already attempted, not retrying)`);
|
|
9227
9647
|
}
|
|
9228
9648
|
this.oauthAttempted = true;
|
|
9649
|
+
try {
|
|
9650
|
+
const { mcpAuthRefresh: mcpAuthRefresh3, serverStorageKey: serverStorageKey3 } = await Promise.resolve().then(() => (init_mcp_oauth2(), exports_mcp_oauth2));
|
|
9651
|
+
const { loadToken: loadToken3 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
|
|
9652
|
+
await mcpAuthRefresh3(this.url);
|
|
9653
|
+
const token = loadToken3(serverStorageKey3(this.url), "mcp-oauth");
|
|
9654
|
+
if (token) {
|
|
9655
|
+
this.accessToken = token.access_token;
|
|
9656
|
+
return;
|
|
9657
|
+
}
|
|
9658
|
+
} catch {}
|
|
9229
9659
|
const wwwAuth = resp.headers.get("www-authenticate") ?? "";
|
|
9230
9660
|
const { mcpOAuthFlow: mcpOAuthFlow3 } = await Promise.resolve().then(() => (init_mcp_oauth2(), exports_mcp_oauth2));
|
|
9231
9661
|
this.accessToken = await mcpOAuthFlow3(this.url, wwwAuth, this.clientId);
|
|
@@ -10134,7 +10564,7 @@ function cleanJson(obj) {
|
|
|
10134
10564
|
}
|
|
10135
10565
|
|
|
10136
10566
|
// src/index.ts
|
|
10137
|
-
var VERSION3 = "0.
|
|
10567
|
+
var VERSION3 = "0.3.1";
|
|
10138
10568
|
function selfDescribe() {
|
|
10139
10569
|
const schema = {
|
|
10140
10570
|
name: "mtpcli",
|
|
@@ -10234,11 +10664,21 @@ function selfDescribe() {
|
|
|
10234
10664
|
{
|
|
10235
10665
|
name: "tool",
|
|
10236
10666
|
type: "string",
|
|
10237
|
-
|
|
10238
|
-
|
|
10667
|
+
description: "Tool name (required unless --url is used)"
|
|
10668
|
+
},
|
|
10669
|
+
{
|
|
10670
|
+
name: "--url",
|
|
10671
|
+
type: "string",
|
|
10672
|
+
description: "HTTP MCP server URL"
|
|
10239
10673
|
}
|
|
10240
10674
|
],
|
|
10241
|
-
examples: [
|
|
10675
|
+
examples: [
|
|
10676
|
+
{ command: "mtpcli auth logout gh-tool" },
|
|
10677
|
+
{
|
|
10678
|
+
description: "Logout from HTTP MCP server",
|
|
10679
|
+
command: "mtpcli auth logout --url https://mcp.example.com/v1/mcp"
|
|
10680
|
+
}
|
|
10681
|
+
]
|
|
10242
10682
|
},
|
|
10243
10683
|
{
|
|
10244
10684
|
name: "auth status",
|
|
@@ -10247,11 +10687,21 @@ function selfDescribe() {
|
|
|
10247
10687
|
{
|
|
10248
10688
|
name: "tool",
|
|
10249
10689
|
type: "string",
|
|
10250
|
-
|
|
10251
|
-
|
|
10690
|
+
description: "Tool name (required unless --url is used)"
|
|
10691
|
+
},
|
|
10692
|
+
{
|
|
10693
|
+
name: "--url",
|
|
10694
|
+
type: "string",
|
|
10695
|
+
description: "HTTP MCP server URL"
|
|
10252
10696
|
}
|
|
10253
10697
|
],
|
|
10254
|
-
examples: [
|
|
10698
|
+
examples: [
|
|
10699
|
+
{ command: "mtpcli auth status gh-tool" },
|
|
10700
|
+
{
|
|
10701
|
+
description: "Status for HTTP MCP server",
|
|
10702
|
+
command: "mtpcli auth status --url https://mcp.example.com/v1/mcp"
|
|
10703
|
+
}
|
|
10704
|
+
]
|
|
10255
10705
|
},
|
|
10256
10706
|
{
|
|
10257
10707
|
name: "auth token",
|
|
@@ -10260,11 +10710,21 @@ function selfDescribe() {
|
|
|
10260
10710
|
{
|
|
10261
10711
|
name: "tool",
|
|
10262
10712
|
type: "string",
|
|
10263
|
-
|
|
10264
|
-
|
|
10713
|
+
description: "Tool name (required unless --url is used)"
|
|
10714
|
+
},
|
|
10715
|
+
{
|
|
10716
|
+
name: "--url",
|
|
10717
|
+
type: "string",
|
|
10718
|
+
description: "HTTP MCP server URL"
|
|
10265
10719
|
}
|
|
10266
10720
|
],
|
|
10267
|
-
examples: [
|
|
10721
|
+
examples: [
|
|
10722
|
+
{ command: "mtpcli auth token gh-tool" },
|
|
10723
|
+
{
|
|
10724
|
+
description: "Token for HTTP MCP server",
|
|
10725
|
+
command: "mtpcli auth token --url https://mcp.example.com/v1/mcp"
|
|
10726
|
+
}
|
|
10727
|
+
]
|
|
10268
10728
|
},
|
|
10269
10729
|
{
|
|
10270
10730
|
name: "auth env",
|
|
@@ -10286,11 +10746,21 @@ function selfDescribe() {
|
|
|
10286
10746
|
{
|
|
10287
10747
|
name: "tool",
|
|
10288
10748
|
type: "string",
|
|
10289
|
-
|
|
10290
|
-
|
|
10749
|
+
description: "Tool name (required unless --url is used)"
|
|
10750
|
+
},
|
|
10751
|
+
{
|
|
10752
|
+
name: "--url",
|
|
10753
|
+
type: "string",
|
|
10754
|
+
description: "HTTP MCP server URL"
|
|
10291
10755
|
}
|
|
10292
10756
|
],
|
|
10293
|
-
examples: [
|
|
10757
|
+
examples: [
|
|
10758
|
+
{ command: "mtpcli auth refresh gh-tool" },
|
|
10759
|
+
{
|
|
10760
|
+
description: "Refresh token for HTTP MCP server",
|
|
10761
|
+
command: "mtpcli auth refresh --url https://mcp.example.com/v1/mcp"
|
|
10762
|
+
}
|
|
10763
|
+
]
|
|
10294
10764
|
},
|
|
10295
10765
|
{
|
|
10296
10766
|
name: "serve",
|
|
@@ -10489,28 +10959,79 @@ authCmd.command("login").description("Log in to a tool").argument("[tool]", "Too
|
|
|
10489
10959
|
`);
|
|
10490
10960
|
process.exit(1);
|
|
10491
10961
|
}
|
|
10492
|
-
const { runLogin:
|
|
10493
|
-
await
|
|
10962
|
+
const { runLogin: runLogin3 } = await Promise.resolve().then(() => (init_auth2(), exports_auth2));
|
|
10963
|
+
await runLogin3(tool, opts.provider, opts.token);
|
|
10494
10964
|
});
|
|
10495
|
-
authCmd.command("logout").description("Log out from a tool").argument("
|
|
10496
|
-
|
|
10497
|
-
|
|
10965
|
+
authCmd.command("logout").description("Log out from a tool").argument("[tool]", "Tool name").option("--url <url>", "HTTP MCP server URL").action(async (tool, opts) => {
|
|
10966
|
+
if (opts.url) {
|
|
10967
|
+
const { mcpAuthLogout: mcpAuthLogout3 } = await Promise.resolve().then(() => (init_mcp_oauth(), exports_mcp_oauth));
|
|
10968
|
+
const deleted = mcpAuthLogout3(opts.url);
|
|
10969
|
+
if (deleted) {
|
|
10970
|
+
process.stderr.write(`Logged out from ${opts.url}
|
|
10971
|
+
`);
|
|
10972
|
+
} else {
|
|
10973
|
+
process.stderr.write(`No tokens found for ${opts.url}
|
|
10974
|
+
`);
|
|
10975
|
+
}
|
|
10976
|
+
return;
|
|
10977
|
+
}
|
|
10978
|
+
if (!tool) {
|
|
10979
|
+
process.stderr.write(`error: tool name is required (or use --url)
|
|
10980
|
+
`);
|
|
10981
|
+
process.exit(1);
|
|
10982
|
+
}
|
|
10983
|
+
const { runLogout: runLogout3 } = await Promise.resolve().then(() => (init_auth2(), exports_auth2));
|
|
10984
|
+
await runLogout3(tool);
|
|
10498
10985
|
});
|
|
10499
|
-
authCmd.command("status").description("Show auth status for a tool").argument("
|
|
10500
|
-
|
|
10501
|
-
|
|
10986
|
+
authCmd.command("status").description("Show auth status for a tool").argument("[tool]", "Tool name").option("--url <url>", "HTTP MCP server URL").action(async (tool, opts) => {
|
|
10987
|
+
if (opts.url) {
|
|
10988
|
+
const { mcpAuthStatus: mcpAuthStatus3 } = await Promise.resolve().then(() => (init_mcp_oauth(), exports_mcp_oauth));
|
|
10989
|
+
console.log(JSON.stringify(mcpAuthStatus3(opts.url), null, 2));
|
|
10990
|
+
return;
|
|
10991
|
+
}
|
|
10992
|
+
if (!tool) {
|
|
10993
|
+
process.stderr.write(`error: tool name is required (or use --url)
|
|
10994
|
+
`);
|
|
10995
|
+
process.exit(1);
|
|
10996
|
+
}
|
|
10997
|
+
const { runStatus: runStatus3 } = await Promise.resolve().then(() => (init_auth2(), exports_auth2));
|
|
10998
|
+
await runStatus3(tool);
|
|
10502
10999
|
});
|
|
10503
|
-
authCmd.command("token").description("Print the access token for a tool").argument("
|
|
10504
|
-
|
|
10505
|
-
|
|
11000
|
+
authCmd.command("token").description("Print the access token for a tool").argument("[tool]", "Tool name").option("--url <url>", "HTTP MCP server URL").action(async (tool, opts) => {
|
|
11001
|
+
if (opts.url) {
|
|
11002
|
+
const { mcpAuthToken: mcpAuthToken3 } = await Promise.resolve().then(() => (init_mcp_oauth(), exports_mcp_oauth));
|
|
11003
|
+
const t = await mcpAuthToken3(opts.url);
|
|
11004
|
+
if (!t) {
|
|
11005
|
+
throw new Error(`no valid token for '${opts.url}'. Run: mtpcli auth login --url ${opts.url}`);
|
|
11006
|
+
}
|
|
11007
|
+
console.log(t);
|
|
11008
|
+
return;
|
|
11009
|
+
}
|
|
11010
|
+
if (!tool) {
|
|
11011
|
+
process.stderr.write(`error: tool name is required (or use --url)
|
|
11012
|
+
`);
|
|
11013
|
+
process.exit(1);
|
|
11014
|
+
}
|
|
11015
|
+
const { runToken: runToken3 } = await Promise.resolve().then(() => (init_auth2(), exports_auth2));
|
|
11016
|
+
await runToken3(tool);
|
|
10506
11017
|
});
|
|
10507
11018
|
authCmd.command("env").description("Print shell export statement for eval").argument("<tool>", "Tool name").action(async (tool) => {
|
|
10508
|
-
const { runEnv:
|
|
10509
|
-
await
|
|
11019
|
+
const { runEnv: runEnv3 } = await Promise.resolve().then(() => (init_auth2(), exports_auth2));
|
|
11020
|
+
await runEnv3(tool);
|
|
10510
11021
|
});
|
|
10511
|
-
authCmd.command("refresh").description("Force-refresh the stored OAuth token for a tool").argument("
|
|
10512
|
-
|
|
10513
|
-
|
|
11022
|
+
authCmd.command("refresh").description("Force-refresh the stored OAuth token for a tool").argument("[tool]", "Tool name").option("--url <url>", "HTTP MCP server URL").action(async (tool, opts) => {
|
|
11023
|
+
if (opts.url) {
|
|
11024
|
+
const { mcpAuthRefresh: mcpAuthRefresh3 } = await Promise.resolve().then(() => (init_mcp_oauth(), exports_mcp_oauth));
|
|
11025
|
+
await mcpAuthRefresh3(opts.url);
|
|
11026
|
+
return;
|
|
11027
|
+
}
|
|
11028
|
+
if (!tool) {
|
|
11029
|
+
process.stderr.write(`error: tool name is required (or use --url)
|
|
11030
|
+
`);
|
|
11031
|
+
process.exit(1);
|
|
11032
|
+
}
|
|
11033
|
+
const { runRefresh: runRefresh3 } = await Promise.resolve().then(() => (init_auth2(), exports_auth2));
|
|
11034
|
+
await runRefresh3(tool);
|
|
10514
11035
|
});
|
|
10515
11036
|
program2.command("serve").description("Serve CLI tools as an MCP server (cli2mcp bridge)").requiredOption("--tool <names...>", "Tool(s) to serve").addHelpText("after", `
|
|
10516
11037
|
This command is meant to be used as an MCP server, not run directly.
|
|
@@ -10551,7 +11072,7 @@ Multiple tools can be combined into a single MCP server:
|
|
|
10551
11072
|
const { run: run5 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
|
|
10552
11073
|
await run5(opts.tool);
|
|
10553
11074
|
});
|
|
10554
|
-
program2.command("wrap").description("Wrap an MCP server as a CLI tool (mcp2cli bridge)").option("--server <cmd>", "MCP server command to run (stdio transport)").option("--url <url>", "MCP server URL (Streamable HTTP transport)").option("-H, --header <header...>", "HTTP header(s) for --url (e.g. 'Authorization: Bearer tok')").option("--client-id <id>", "Pre-registered OAuth client ID (for --url)").option("--describe", "Output --describe JSON instead of invoking", false).argument("[tool_name]", "Tool name to invoke").argument("[args...]", "Arguments for the tool (after --)").action(async (toolName, args, opts) => {
|
|
11075
|
+
program2.command("wrap").description("Wrap an MCP server as a CLI tool (mcp2cli bridge)").option("--server <cmd>", "MCP server command to run (stdio transport)").option("--url <url>", "MCP server URL (Streamable HTTP / SSE transport)").option("-H, --header <header...>", "HTTP header(s) for --url (e.g. 'Authorization: Bearer tok')").option("--client-id <id>", "Pre-registered OAuth client ID (for --url)").option("--describe", "Output --describe JSON instead of invoking", false).argument("[tool_name]", "Tool name to invoke").argument("[args...]", "Arguments for the tool (after --)").action(async (toolName, args, opts) => {
|
|
10555
11076
|
if (opts.server && opts.url) {
|
|
10556
11077
|
process.stderr.write(`error: --server and --url are mutually exclusive
|
|
10557
11078
|
`);
|