@modeltoolsprotocol/mtpcli 0.2.2 → 0.2.3
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/index.js +1219 -228
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7341,28 +7341,6 @@ 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
|
-
oauth2Login: () => oauth2Login,
|
|
7354
|
-
login: () => login,
|
|
7355
|
-
loadToken: () => loadToken,
|
|
7356
|
-
isTokenExpired: () => isTokenExpired,
|
|
7357
|
-
getProvider: () => getProvider,
|
|
7358
|
-
getEnvExport: () => getEnvExport,
|
|
7359
|
-
getEnvDict: () => getEnvDict,
|
|
7360
|
-
getAuthConfig: () => getAuthConfig,
|
|
7361
|
-
ensureValidToken: () => ensureValidToken,
|
|
7362
|
-
deleteToken: () => deleteToken,
|
|
7363
|
-
authStatus: () => authStatus,
|
|
7364
|
-
apiKeyLogin: () => apiKeyLogin
|
|
7365
|
-
});
|
|
7366
7344
|
import { createHash, randomBytes } from "node:crypto";
|
|
7367
7345
|
import {
|
|
7368
7346
|
chmodSync,
|
|
@@ -7372,10 +7350,8 @@ import {
|
|
|
7372
7350
|
unlinkSync,
|
|
7373
7351
|
writeFileSync as writeFileSync2
|
|
7374
7352
|
} from "node:fs";
|
|
7375
|
-
import { createServer } from "node:http";
|
|
7376
7353
|
import { homedir as homedir2 } from "node:os";
|
|
7377
7354
|
import { dirname, join as join2 } from "node:path";
|
|
7378
|
-
import { createInterface } from "node:readline";
|
|
7379
7355
|
function tokensDir(baseDir) {
|
|
7380
7356
|
return join2(baseDir ?? join2(homedir2(), ".mtpcli"), "tokens");
|
|
7381
7357
|
}
|
|
@@ -7395,14 +7371,526 @@ function loadToken(toolName, providerId, baseDir) {
|
|
|
7395
7371
|
return null;
|
|
7396
7372
|
return JSON.parse(readFileSync2(path2, "utf-8"));
|
|
7397
7373
|
}
|
|
7374
|
+
function isTokenExpired(token) {
|
|
7375
|
+
if (!token.expires_at)
|
|
7376
|
+
return false;
|
|
7377
|
+
const exp = new Date(token.expires_at);
|
|
7378
|
+
const buffer = 5 * 60 * 1000;
|
|
7379
|
+
return exp.getTime() - buffer < Date.now();
|
|
7380
|
+
}
|
|
7381
|
+
function generatePkce() {
|
|
7382
|
+
const bytes = randomBytes(64);
|
|
7383
|
+
const verifier = bytes.toString("base64url").slice(0, 128);
|
|
7384
|
+
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
7385
|
+
return { verifier, challenge };
|
|
7386
|
+
}
|
|
7387
|
+
async function exchangeCode(tokenUrl, code, clientId, redirectUri, codeVerifier, resource) {
|
|
7388
|
+
const params = new URLSearchParams({
|
|
7389
|
+
grant_type: "authorization_code",
|
|
7390
|
+
code,
|
|
7391
|
+
client_id: clientId,
|
|
7392
|
+
redirect_uri: redirectUri
|
|
7393
|
+
});
|
|
7394
|
+
if (codeVerifier)
|
|
7395
|
+
params.set("code_verifier", codeVerifier);
|
|
7396
|
+
if (resource)
|
|
7397
|
+
params.set("resource", resource);
|
|
7398
|
+
const resp = await fetch(tokenUrl, {
|
|
7399
|
+
method: "POST",
|
|
7400
|
+
headers: {
|
|
7401
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
7402
|
+
Accept: "application/json"
|
|
7403
|
+
},
|
|
7404
|
+
body: params.toString()
|
|
7405
|
+
});
|
|
7406
|
+
const text = await resp.text();
|
|
7407
|
+
return parseTokenResponse(text);
|
|
7408
|
+
}
|
|
7409
|
+
async function refreshAccessToken(tokenUrl, refreshToken, clientId, resource) {
|
|
7410
|
+
const params = new URLSearchParams({
|
|
7411
|
+
grant_type: "refresh_token",
|
|
7412
|
+
refresh_token: refreshToken,
|
|
7413
|
+
client_id: clientId
|
|
7414
|
+
});
|
|
7415
|
+
if (resource)
|
|
7416
|
+
params.set("resource", resource);
|
|
7417
|
+
const resp = await fetch(tokenUrl, {
|
|
7418
|
+
method: "POST",
|
|
7419
|
+
headers: {
|
|
7420
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
7421
|
+
Accept: "application/json"
|
|
7422
|
+
},
|
|
7423
|
+
body: params.toString()
|
|
7424
|
+
});
|
|
7425
|
+
const text = await resp.text();
|
|
7426
|
+
return parseTokenResponse(text);
|
|
7427
|
+
}
|
|
7428
|
+
function parseTokenResponse(text) {
|
|
7429
|
+
let val;
|
|
7430
|
+
try {
|
|
7431
|
+
val = JSON.parse(text);
|
|
7432
|
+
} catch {
|
|
7433
|
+
val = Object.fromEntries(new URLSearchParams(text));
|
|
7434
|
+
}
|
|
7435
|
+
const accessToken = val.access_token;
|
|
7436
|
+
if (!accessToken)
|
|
7437
|
+
throw new Error("no access_token in response");
|
|
7438
|
+
const expiresIn = typeof val.expires_in === "number" ? val.expires_in : typeof val.expires_in === "string" ? parseInt(val.expires_in, 10) || undefined : undefined;
|
|
7439
|
+
const expiresAt = expiresIn ? new Date(Date.now() + expiresIn * 1000).toISOString() : undefined;
|
|
7440
|
+
const scope = val.scope;
|
|
7441
|
+
const scopes = scope ? scope.split(/\s+/) : undefined;
|
|
7442
|
+
return {
|
|
7443
|
+
access_token: accessToken,
|
|
7444
|
+
token_type: val.token_type ?? undefined,
|
|
7445
|
+
refresh_token: val.refresh_token ?? undefined,
|
|
7446
|
+
expires_at: expiresAt,
|
|
7447
|
+
scopes,
|
|
7448
|
+
provider_id: "",
|
|
7449
|
+
created_at: new Date().toISOString()
|
|
7450
|
+
};
|
|
7451
|
+
}
|
|
7452
|
+
async function ensureValidToken(toolName, auth) {
|
|
7453
|
+
const provider = auth.providers[0];
|
|
7454
|
+
if (!provider)
|
|
7455
|
+
return null;
|
|
7456
|
+
const token = loadToken(toolName, provider.id);
|
|
7457
|
+
if (!token)
|
|
7458
|
+
return null;
|
|
7459
|
+
if (isTokenExpired(token)) {
|
|
7460
|
+
if (token.refresh_token && provider.tokenUrl && provider.clientId) {
|
|
7461
|
+
try {
|
|
7462
|
+
const newToken = await refreshAccessToken(provider.tokenUrl, token.refresh_token, provider.clientId);
|
|
7463
|
+
if (!newToken.refresh_token) {
|
|
7464
|
+
newToken.refresh_token = token.refresh_token;
|
|
7465
|
+
}
|
|
7466
|
+
newToken.provider_id = provider.id;
|
|
7467
|
+
storeToken(toolName, provider.id, newToken);
|
|
7468
|
+
return newToken.access_token;
|
|
7469
|
+
} catch (e) {
|
|
7470
|
+
process.stderr.write(`warning: token refresh failed: ${e instanceof Error ? e.message : e}
|
|
7471
|
+
`);
|
|
7472
|
+
return null;
|
|
7473
|
+
}
|
|
7474
|
+
}
|
|
7475
|
+
return null;
|
|
7476
|
+
}
|
|
7477
|
+
return token.access_token;
|
|
7478
|
+
}
|
|
7479
|
+
async function getEnvDict(toolName, auth) {
|
|
7480
|
+
const token = await ensureValidToken(toolName, auth);
|
|
7481
|
+
if (!token)
|
|
7482
|
+
return {};
|
|
7483
|
+
return { [auth.envVar]: token };
|
|
7484
|
+
}
|
|
7485
|
+
var init_auth = __esm(() => {
|
|
7486
|
+
init_search2();
|
|
7487
|
+
});
|
|
7488
|
+
|
|
7489
|
+
// src/mcp-oauth.ts
|
|
7490
|
+
var exports_mcp_oauth = {};
|
|
7491
|
+
__export(exports_mcp_oauth, {
|
|
7492
|
+
storeClientRegistration: () => storeClientRegistration,
|
|
7493
|
+
serverStorageKey: () => serverStorageKey,
|
|
7494
|
+
registerClient: () => registerClient,
|
|
7495
|
+
promptForClientId: () => promptForClientId,
|
|
7496
|
+
parseWwwAuthenticate: () => parseWwwAuthenticate,
|
|
7497
|
+
mcpOAuthFlow: () => mcpOAuthFlow,
|
|
7498
|
+
loadOrRefreshMcpToken: () => loadOrRefreshMcpToken,
|
|
7499
|
+
loadClientRegistration: () => loadClientRegistration,
|
|
7500
|
+
fetchResourceMetadata: () => fetchResourceMetadata,
|
|
7501
|
+
fetchAuthServerMetadata: () => fetchAuthServerMetadata
|
|
7502
|
+
});
|
|
7503
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
7504
|
+
import {
|
|
7505
|
+
existsSync as existsSync3,
|
|
7506
|
+
mkdirSync as mkdirSync3,
|
|
7507
|
+
readFileSync as readFileSync3,
|
|
7508
|
+
writeFileSync as writeFileSync3,
|
|
7509
|
+
chmodSync as chmodSync2
|
|
7510
|
+
} from "node:fs";
|
|
7511
|
+
import { createServer } from "node:http";
|
|
7512
|
+
import { homedir as homedir3 } from "node:os";
|
|
7513
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
7514
|
+
import { createInterface } from "node:readline";
|
|
7515
|
+
function serverStorageKey(url) {
|
|
7516
|
+
const u = new URL(url);
|
|
7517
|
+
return `mcp_${u.host}`;
|
|
7518
|
+
}
|
|
7519
|
+
function parseWwwAuthenticate(header) {
|
|
7520
|
+
const result = {};
|
|
7521
|
+
const params = header.replace(/^Bearer\s+/i, "");
|
|
7522
|
+
const re = /(\w+)=(?:"([^"]*)"|(\S+))/g;
|
|
7523
|
+
let match;
|
|
7524
|
+
while ((match = re.exec(params)) !== null) {
|
|
7525
|
+
const key = match[1];
|
|
7526
|
+
const val = match[2] ?? match[3];
|
|
7527
|
+
if (key === "resource_metadata")
|
|
7528
|
+
result.resourceMetadata = val;
|
|
7529
|
+
if (key === "scope")
|
|
7530
|
+
result.scope = val;
|
|
7531
|
+
}
|
|
7532
|
+
return result;
|
|
7533
|
+
}
|
|
7534
|
+
async function fetchResourceMetadata(url) {
|
|
7535
|
+
const resp = await fetch(url);
|
|
7536
|
+
if (!resp.ok) {
|
|
7537
|
+
throw new Error(`failed to fetch resource metadata from ${url}: ${resp.status}`);
|
|
7538
|
+
}
|
|
7539
|
+
return resp.json();
|
|
7540
|
+
}
|
|
7541
|
+
async function fetchAuthServerMetadata(serverUrl, wwwAuth) {
|
|
7542
|
+
if (wwwAuth) {
|
|
7543
|
+
const parsed = parseWwwAuthenticate(wwwAuth);
|
|
7544
|
+
if (parsed.resourceMetadata) {
|
|
7545
|
+
try {
|
|
7546
|
+
const resource = await fetchResourceMetadata(parsed.resourceMetadata);
|
|
7547
|
+
if (resource.authorization_servers?.length) {
|
|
7548
|
+
const asUrl = resource.authorization_servers[0];
|
|
7549
|
+
const wellKnown = `${asUrl}/.well-known/oauth-authorization-server`;
|
|
7550
|
+
const resp = await fetch(wellKnown);
|
|
7551
|
+
if (resp.ok) {
|
|
7552
|
+
return resp.json();
|
|
7553
|
+
}
|
|
7554
|
+
}
|
|
7555
|
+
} catch {}
|
|
7556
|
+
}
|
|
7557
|
+
}
|
|
7558
|
+
const origin = new URL(serverUrl).origin;
|
|
7559
|
+
const oauthUrl = `${origin}/.well-known/oauth-authorization-server`;
|
|
7560
|
+
try {
|
|
7561
|
+
const resp = await fetch(oauthUrl);
|
|
7562
|
+
if (resp.ok) {
|
|
7563
|
+
return resp.json();
|
|
7564
|
+
}
|
|
7565
|
+
} catch {}
|
|
7566
|
+
const oidcUrl = `${origin}/.well-known/openid-configuration`;
|
|
7567
|
+
try {
|
|
7568
|
+
const resp = await fetch(oidcUrl);
|
|
7569
|
+
if (resp.ok) {
|
|
7570
|
+
return resp.json();
|
|
7571
|
+
}
|
|
7572
|
+
} catch {}
|
|
7573
|
+
throw new Error(`could not discover OAuth authorization server for ${serverUrl}`);
|
|
7574
|
+
}
|
|
7575
|
+
async function registerClient(metadata, redirectUri) {
|
|
7576
|
+
if (!metadata.registration_endpoint) {
|
|
7577
|
+
throw new Error("no registration_endpoint in auth server metadata");
|
|
7578
|
+
}
|
|
7579
|
+
const body = {
|
|
7580
|
+
client_name: "mtpcli",
|
|
7581
|
+
redirect_uris: [redirectUri],
|
|
7582
|
+
grant_types: ["authorization_code"],
|
|
7583
|
+
response_types: ["code"],
|
|
7584
|
+
token_endpoint_auth_method: "none"
|
|
7585
|
+
};
|
|
7586
|
+
const resp = await fetch(metadata.registration_endpoint, {
|
|
7587
|
+
method: "POST",
|
|
7588
|
+
headers: {
|
|
7589
|
+
"Content-Type": "application/json",
|
|
7590
|
+
Accept: "application/json"
|
|
7591
|
+
},
|
|
7592
|
+
body: JSON.stringify(body)
|
|
7593
|
+
});
|
|
7594
|
+
if (!resp.ok) {
|
|
7595
|
+
const text = await resp.text();
|
|
7596
|
+
throw new Error(`dynamic client registration failed (${resp.status}): ${text}`);
|
|
7597
|
+
}
|
|
7598
|
+
const result = await resp.json();
|
|
7599
|
+
const clientId = result.client_id;
|
|
7600
|
+
if (!clientId)
|
|
7601
|
+
throw new Error("no client_id in registration response");
|
|
7602
|
+
return {
|
|
7603
|
+
clientId,
|
|
7604
|
+
clientSecret: result.client_secret,
|
|
7605
|
+
redirectUri,
|
|
7606
|
+
registeredAt: Date.now()
|
|
7607
|
+
};
|
|
7608
|
+
}
|
|
7609
|
+
function promptForClientId(serverUrl) {
|
|
7610
|
+
return new Promise((resolve, reject) => {
|
|
7611
|
+
process.stderr.write(`
|
|
7612
|
+
No dynamic registration available for ${serverUrl}.
|
|
7613
|
+
` + `You need to register a client manually and provide the client_id.
|
|
7614
|
+
` + `Enter client_id: `);
|
|
7615
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
7616
|
+
rl.question("", (answer) => {
|
|
7617
|
+
rl.close();
|
|
7618
|
+
const trimmed = answer.trim();
|
|
7619
|
+
if (!trimmed) {
|
|
7620
|
+
reject(new Error("no client_id provided"));
|
|
7621
|
+
return;
|
|
7622
|
+
}
|
|
7623
|
+
resolve(trimmed);
|
|
7624
|
+
});
|
|
7625
|
+
});
|
|
7626
|
+
}
|
|
7627
|
+
function clientStorePath() {
|
|
7628
|
+
return join3(homedir3(), ".mtpcli", "mcp-oauth-clients.json");
|
|
7629
|
+
}
|
|
7630
|
+
function loadClientStore() {
|
|
7631
|
+
const path2 = clientStorePath();
|
|
7632
|
+
if (!existsSync3(path2))
|
|
7633
|
+
return {};
|
|
7634
|
+
try {
|
|
7635
|
+
return JSON.parse(readFileSync3(path2, "utf-8"));
|
|
7636
|
+
} catch {
|
|
7637
|
+
return {};
|
|
7638
|
+
}
|
|
7639
|
+
}
|
|
7640
|
+
function saveClientStore(store) {
|
|
7641
|
+
const path2 = clientStorePath();
|
|
7642
|
+
mkdirSync3(dirname2(path2), { recursive: true });
|
|
7643
|
+
writeFileSync3(path2, JSON.stringify(store, null, 2));
|
|
7644
|
+
chmodSync2(path2, 384);
|
|
7645
|
+
}
|
|
7646
|
+
function loadClientRegistration(origin) {
|
|
7647
|
+
const store = loadClientStore();
|
|
7648
|
+
return store[origin] ?? null;
|
|
7649
|
+
}
|
|
7650
|
+
function storeClientRegistration(origin, client, metadata) {
|
|
7651
|
+
const store = loadClientStore();
|
|
7652
|
+
store[origin] = { client, metadata };
|
|
7653
|
+
saveClientStore(store);
|
|
7654
|
+
}
|
|
7655
|
+
function startCallbackServer() {
|
|
7656
|
+
return new Promise((resolve, reject) => {
|
|
7657
|
+
let codeResolve;
|
|
7658
|
+
let codeReject;
|
|
7659
|
+
const codePromise = new Promise((res, rej) => {
|
|
7660
|
+
codeResolve = res;
|
|
7661
|
+
codeReject = rej;
|
|
7662
|
+
});
|
|
7663
|
+
const server = createServer((req, res) => {
|
|
7664
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1`);
|
|
7665
|
+
const code = url.searchParams.get("code");
|
|
7666
|
+
const error = url.searchParams.get("error");
|
|
7667
|
+
if (code) {
|
|
7668
|
+
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>";
|
|
7669
|
+
res.writeHead(200, {
|
|
7670
|
+
"Content-Type": "text/html",
|
|
7671
|
+
"Content-Length": Buffer.byteLength(html).toString()
|
|
7672
|
+
});
|
|
7673
|
+
res.end(html);
|
|
7674
|
+
server.close();
|
|
7675
|
+
codeResolve(code);
|
|
7676
|
+
return;
|
|
7677
|
+
}
|
|
7678
|
+
if (error) {
|
|
7679
|
+
const desc = url.searchParams.get("error_description") ?? "Unknown error";
|
|
7680
|
+
const html = `<html><body><h2>Authentication failed</h2><p>${desc}</p></body></html>`;
|
|
7681
|
+
res.writeHead(400, {
|
|
7682
|
+
"Content-Type": "text/html",
|
|
7683
|
+
"Content-Length": Buffer.byteLength(html).toString()
|
|
7684
|
+
});
|
|
7685
|
+
res.end(html);
|
|
7686
|
+
server.close();
|
|
7687
|
+
codeReject(new Error(`OAuth error: ${error} - ${desc}`));
|
|
7688
|
+
return;
|
|
7689
|
+
}
|
|
7690
|
+
res.writeHead(404);
|
|
7691
|
+
res.end();
|
|
7692
|
+
});
|
|
7693
|
+
server.listen(0, "127.0.0.1", () => {
|
|
7694
|
+
const addr = server.address();
|
|
7695
|
+
if (!addr || typeof addr === "string") {
|
|
7696
|
+
reject(new Error("failed to get callback server address"));
|
|
7697
|
+
return;
|
|
7698
|
+
}
|
|
7699
|
+
resolve({
|
|
7700
|
+
port: addr.port,
|
|
7701
|
+
waitForCode: () => {
|
|
7702
|
+
const timer = setTimeout(() => {
|
|
7703
|
+
server.close();
|
|
7704
|
+
codeReject(new Error("OAuth callback timed out (120s)"));
|
|
7705
|
+
}, 120000);
|
|
7706
|
+
return codePromise.finally(() => clearTimeout(timer));
|
|
7707
|
+
},
|
|
7708
|
+
server
|
|
7709
|
+
});
|
|
7710
|
+
});
|
|
7711
|
+
server.on("error", (e) => {
|
|
7712
|
+
reject(new Error(`failed to bind callback server: ${e.message}`));
|
|
7713
|
+
});
|
|
7714
|
+
});
|
|
7715
|
+
}
|
|
7716
|
+
async function mcpOAuthFlow(serverUrl, wwwAuth, clientId) {
|
|
7717
|
+
const origin = new URL(serverUrl).origin;
|
|
7718
|
+
const storageKey = serverStorageKey(serverUrl);
|
|
7719
|
+
const wwwParsed = parseWwwAuthenticate(wwwAuth);
|
|
7720
|
+
process.stderr.write(`Discovering OAuth authorization server...
|
|
7721
|
+
`);
|
|
7722
|
+
const metadata = await fetchAuthServerMetadata(serverUrl, wwwAuth);
|
|
7723
|
+
const challengeMethods = metadata.code_challenge_methods_supported;
|
|
7724
|
+
if (challengeMethods && !challengeMethods.includes("S256")) {
|
|
7725
|
+
throw new Error("authorization server does not support S256 PKCE (required)");
|
|
7726
|
+
}
|
|
7727
|
+
const callback = await startCallbackServer();
|
|
7728
|
+
const redirectUri = `http://127.0.0.1:${callback.port}/callback`;
|
|
7729
|
+
let resolvedClientId = clientId;
|
|
7730
|
+
let registration;
|
|
7731
|
+
if (!resolvedClientId) {
|
|
7732
|
+
const cached = loadClientRegistration(origin);
|
|
7733
|
+
if (cached) {
|
|
7734
|
+
resolvedClientId = cached.client.clientId;
|
|
7735
|
+
registration = cached.client;
|
|
7736
|
+
process.stderr.write(`Using cached client registration for ${origin}
|
|
7737
|
+
`);
|
|
7738
|
+
}
|
|
7739
|
+
}
|
|
7740
|
+
if (!resolvedClientId) {
|
|
7741
|
+
if (metadata.registration_endpoint) {
|
|
7742
|
+
process.stderr.write(`Registering client dynamically...
|
|
7743
|
+
`);
|
|
7744
|
+
registration = await registerClient(metadata, redirectUri);
|
|
7745
|
+
resolvedClientId = registration.clientId;
|
|
7746
|
+
storeClientRegistration(origin, registration, metadata);
|
|
7747
|
+
} else {
|
|
7748
|
+
callback.server.close();
|
|
7749
|
+
resolvedClientId = await promptForClientId(serverUrl);
|
|
7750
|
+
const newCallback = await startCallbackServer();
|
|
7751
|
+
return doOAuthLogin(metadata, resolvedClientId, `http://127.0.0.1:${newCallback.port}/callback`, newCallback, wwwParsed.scope, storageKey, origin);
|
|
7752
|
+
}
|
|
7753
|
+
}
|
|
7754
|
+
return doOAuthLogin(metadata, resolvedClientId, redirectUri, callback, wwwParsed.scope, storageKey, origin);
|
|
7755
|
+
}
|
|
7756
|
+
async function doOAuthLogin(metadata, clientId, redirectUri, callback, scope, storageKey, origin) {
|
|
7757
|
+
const state = randomBytes2(32).toString("base64url");
|
|
7758
|
+
const pkce = generatePkce();
|
|
7759
|
+
const scopes = scope ?? metadata.scopes_supported?.join(" ") ?? undefined;
|
|
7760
|
+
const params = new URLSearchParams({
|
|
7761
|
+
client_id: clientId,
|
|
7762
|
+
redirect_uri: redirectUri,
|
|
7763
|
+
response_type: "code",
|
|
7764
|
+
state,
|
|
7765
|
+
code_challenge: pkce.challenge,
|
|
7766
|
+
code_challenge_method: "S256"
|
|
7767
|
+
});
|
|
7768
|
+
if (scopes)
|
|
7769
|
+
params.set("scope", scopes);
|
|
7770
|
+
const authUrl = `${metadata.authorization_endpoint}?${params.toString()}`;
|
|
7771
|
+
process.stderr.write(`Opening browser for OAuth login...
|
|
7772
|
+
`);
|
|
7773
|
+
process.stderr.write(`If the browser doesn't open, visit:
|
|
7774
|
+
${authUrl}
|
|
7775
|
+
|
|
7776
|
+
`);
|
|
7777
|
+
open_default(authUrl).catch(() => {});
|
|
7778
|
+
process.stderr.write(`Waiting for authentication callback on port ${callback.port}...
|
|
7779
|
+
`);
|
|
7780
|
+
const code = await callback.waitForCode();
|
|
7781
|
+
process.stderr.write(`Exchanging authorization code for tokens...
|
|
7782
|
+
`);
|
|
7783
|
+
const token = await exchangeCode(metadata.token_endpoint, code, clientId, redirectUri, pkce.verifier);
|
|
7784
|
+
token.provider_id = "mcp-oauth";
|
|
7785
|
+
storeToken(storageKey, "mcp-oauth", token);
|
|
7786
|
+
storeClientRegistration(origin, {
|
|
7787
|
+
clientId,
|
|
7788
|
+
redirectUri,
|
|
7789
|
+
registeredAt: Date.now()
|
|
7790
|
+
}, metadata);
|
|
7791
|
+
process.stderr.write(`Authenticated with ${origin}
|
|
7792
|
+
`);
|
|
7793
|
+
return token.access_token;
|
|
7794
|
+
}
|
|
7795
|
+
async function loadOrRefreshMcpToken(serverUrl) {
|
|
7796
|
+
const storageKey = serverStorageKey(serverUrl);
|
|
7797
|
+
const token = loadToken(storageKey, "mcp-oauth");
|
|
7798
|
+
if (!token)
|
|
7799
|
+
return null;
|
|
7800
|
+
if (!isTokenExpired(token)) {
|
|
7801
|
+
return token.access_token;
|
|
7802
|
+
}
|
|
7803
|
+
if (token.refresh_token) {
|
|
7804
|
+
const origin = new URL(serverUrl).origin;
|
|
7805
|
+
const cached = loadClientRegistration(origin);
|
|
7806
|
+
if (cached) {
|
|
7807
|
+
try {
|
|
7808
|
+
const newToken = await refreshAccessToken(cached.metadata.token_endpoint, token.refresh_token, cached.client.clientId);
|
|
7809
|
+
if (!newToken.refresh_token) {
|
|
7810
|
+
newToken.refresh_token = token.refresh_token;
|
|
7811
|
+
}
|
|
7812
|
+
newToken.provider_id = "mcp-oauth";
|
|
7813
|
+
storeToken(storageKey, "mcp-oauth", newToken);
|
|
7814
|
+
return newToken.access_token;
|
|
7815
|
+
} catch (e) {
|
|
7816
|
+
process.stderr.write(`warning: token refresh failed: ${e instanceof Error ? e.message : e}
|
|
7817
|
+
`);
|
|
7818
|
+
}
|
|
7819
|
+
}
|
|
7820
|
+
}
|
|
7821
|
+
return null;
|
|
7822
|
+
}
|
|
7823
|
+
var init_mcp_oauth = __esm(() => {
|
|
7824
|
+
init_open();
|
|
7825
|
+
init_auth();
|
|
7826
|
+
});
|
|
7827
|
+
|
|
7828
|
+
// src/auth.ts
|
|
7829
|
+
var exports_auth = {};
|
|
7830
|
+
__export(exports_auth, {
|
|
7831
|
+
storeToken: () => storeToken2,
|
|
7832
|
+
runToken: () => runToken,
|
|
7833
|
+
runStatus: () => runStatus,
|
|
7834
|
+
runRefresh: () => runRefresh,
|
|
7835
|
+
runLogout: () => runLogout,
|
|
7836
|
+
runLogin: () => runLogin,
|
|
7837
|
+
runEnv: () => runEnv,
|
|
7838
|
+
refreshAccessToken: () => refreshAccessToken2,
|
|
7839
|
+
oauth2Login: () => oauth2Login,
|
|
7840
|
+
login: () => login,
|
|
7841
|
+
loadToken: () => loadToken2,
|
|
7842
|
+
isTokenExpired: () => isTokenExpired2,
|
|
7843
|
+
getProvider: () => getProvider,
|
|
7844
|
+
getEnvExport: () => getEnvExport,
|
|
7845
|
+
getEnvDict: () => getEnvDict2,
|
|
7846
|
+
getAuthConfig: () => getAuthConfig,
|
|
7847
|
+
generatePkce: () => generatePkce2,
|
|
7848
|
+
exchangeCode: () => exchangeCode2,
|
|
7849
|
+
ensureValidToken: () => ensureValidToken2,
|
|
7850
|
+
deleteToken: () => deleteToken,
|
|
7851
|
+
authStatus: () => authStatus,
|
|
7852
|
+
apiKeyLogin: () => apiKeyLogin
|
|
7853
|
+
});
|
|
7854
|
+
import { createHash as createHash2, randomBytes as randomBytes3 } from "node:crypto";
|
|
7855
|
+
import {
|
|
7856
|
+
chmodSync as chmodSync3,
|
|
7857
|
+
existsSync as existsSync4,
|
|
7858
|
+
mkdirSync as mkdirSync4,
|
|
7859
|
+
readFileSync as readFileSync4,
|
|
7860
|
+
unlinkSync as unlinkSync2,
|
|
7861
|
+
writeFileSync as writeFileSync4
|
|
7862
|
+
} from "node:fs";
|
|
7863
|
+
import { createServer as createServer2 } from "node:http";
|
|
7864
|
+
import { homedir as homedir4 } from "node:os";
|
|
7865
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
7866
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
7867
|
+
function tokensDir2(baseDir) {
|
|
7868
|
+
return join4(baseDir ?? join4(homedir4(), ".mtpcli"), "tokens");
|
|
7869
|
+
}
|
|
7870
|
+
function tokenPath2(toolName, providerId, baseDir) {
|
|
7871
|
+
return join4(tokensDir2(baseDir), toolName, `${providerId}.json`);
|
|
7872
|
+
}
|
|
7873
|
+
function storeToken2(toolName, providerId, token, baseDir) {
|
|
7874
|
+
const path2 = tokenPath2(toolName, providerId, baseDir);
|
|
7875
|
+
mkdirSync4(dirname3(path2), { recursive: true });
|
|
7876
|
+
writeFileSync4(path2, JSON.stringify(token, null, 2));
|
|
7877
|
+
chmodSync3(path2, 384);
|
|
7878
|
+
return path2;
|
|
7879
|
+
}
|
|
7880
|
+
function loadToken2(toolName, providerId, baseDir) {
|
|
7881
|
+
const path2 = tokenPath2(toolName, providerId, baseDir);
|
|
7882
|
+
if (!existsSync4(path2))
|
|
7883
|
+
return null;
|
|
7884
|
+
return JSON.parse(readFileSync4(path2, "utf-8"));
|
|
7885
|
+
}
|
|
7398
7886
|
function deleteToken(toolName, providerId, baseDir) {
|
|
7399
|
-
const path2 =
|
|
7400
|
-
if (!
|
|
7887
|
+
const path2 = tokenPath2(toolName, providerId, baseDir);
|
|
7888
|
+
if (!existsSync4(path2))
|
|
7401
7889
|
return false;
|
|
7402
|
-
|
|
7890
|
+
unlinkSync2(path2);
|
|
7403
7891
|
return true;
|
|
7404
7892
|
}
|
|
7405
|
-
function
|
|
7893
|
+
function isTokenExpired2(token) {
|
|
7406
7894
|
if (!token.expires_at)
|
|
7407
7895
|
return false;
|
|
7408
7896
|
const exp = new Date(token.expires_at);
|
|
@@ -7418,10 +7906,10 @@ function getProvider(auth, providerId) {
|
|
|
7418
7906
|
return auth.providers.find((p) => p.id === providerId);
|
|
7419
7907
|
return auth.providers[0];
|
|
7420
7908
|
}
|
|
7421
|
-
function
|
|
7422
|
-
const bytes =
|
|
7909
|
+
function generatePkce2() {
|
|
7910
|
+
const bytes = randomBytes3(64);
|
|
7423
7911
|
const verifier = bytes.toString("base64url").slice(0, 128);
|
|
7424
|
-
const challenge =
|
|
7912
|
+
const challenge = createHash2("sha256").update(verifier).digest("base64url");
|
|
7425
7913
|
return { verifier, challenge };
|
|
7426
7914
|
}
|
|
7427
7915
|
function waitForCallback(port, timeoutSecs) {
|
|
@@ -7430,7 +7918,7 @@ function waitForCallback(port, timeoutSecs) {
|
|
|
7430
7918
|
server.close();
|
|
7431
7919
|
reject(new Error("OAuth callback timed out"));
|
|
7432
7920
|
}, timeoutSecs * 1000);
|
|
7433
|
-
const server =
|
|
7921
|
+
const server = createServer2((req, res) => {
|
|
7434
7922
|
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
7435
7923
|
const code = url.searchParams.get("code");
|
|
7436
7924
|
const error = url.searchParams.get("error");
|
|
@@ -7469,7 +7957,7 @@ function waitForCallback(port, timeoutSecs) {
|
|
|
7469
7957
|
});
|
|
7470
7958
|
});
|
|
7471
7959
|
}
|
|
7472
|
-
async function
|
|
7960
|
+
async function exchangeCode2(tokenUrl, code, clientId, redirectUri, codeVerifier, resource) {
|
|
7473
7961
|
const params = new URLSearchParams({
|
|
7474
7962
|
grant_type: "authorization_code",
|
|
7475
7963
|
code,
|
|
@@ -7478,6 +7966,8 @@ async function exchangeCode(tokenUrl, code, clientId, redirectUri, codeVerifier)
|
|
|
7478
7966
|
});
|
|
7479
7967
|
if (codeVerifier)
|
|
7480
7968
|
params.set("code_verifier", codeVerifier);
|
|
7969
|
+
if (resource)
|
|
7970
|
+
params.set("resource", resource);
|
|
7481
7971
|
const resp = await fetch(tokenUrl, {
|
|
7482
7972
|
method: "POST",
|
|
7483
7973
|
headers: {
|
|
@@ -7487,14 +7977,16 @@ async function exchangeCode(tokenUrl, code, clientId, redirectUri, codeVerifier)
|
|
|
7487
7977
|
body: params.toString()
|
|
7488
7978
|
});
|
|
7489
7979
|
const text = await resp.text();
|
|
7490
|
-
return
|
|
7980
|
+
return parseTokenResponse2(text);
|
|
7491
7981
|
}
|
|
7492
|
-
async function
|
|
7982
|
+
async function refreshAccessToken2(tokenUrl, refreshToken, clientId, resource) {
|
|
7493
7983
|
const params = new URLSearchParams({
|
|
7494
7984
|
grant_type: "refresh_token",
|
|
7495
7985
|
refresh_token: refreshToken,
|
|
7496
7986
|
client_id: clientId
|
|
7497
7987
|
});
|
|
7988
|
+
if (resource)
|
|
7989
|
+
params.set("resource", resource);
|
|
7498
7990
|
const resp = await fetch(tokenUrl, {
|
|
7499
7991
|
method: "POST",
|
|
7500
7992
|
headers: {
|
|
@@ -7504,9 +7996,9 @@ async function refreshAccessToken(tokenUrl, refreshToken, clientId) {
|
|
|
7504
7996
|
body: params.toString()
|
|
7505
7997
|
});
|
|
7506
7998
|
const text = await resp.text();
|
|
7507
|
-
return
|
|
7999
|
+
return parseTokenResponse2(text);
|
|
7508
8000
|
}
|
|
7509
|
-
function
|
|
8001
|
+
function parseTokenResponse2(text) {
|
|
7510
8002
|
let val;
|
|
7511
8003
|
try {
|
|
7512
8004
|
val = JSON.parse(text);
|
|
@@ -7538,7 +8030,7 @@ async function oauth2Login(toolName, provider, port) {
|
|
|
7538
8030
|
if (!provider.clientId)
|
|
7539
8031
|
throw new Error("provider missing clientId");
|
|
7540
8032
|
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
7541
|
-
const state =
|
|
8033
|
+
const state = randomBytes3(32).toString("base64url");
|
|
7542
8034
|
const params = new URLSearchParams({
|
|
7543
8035
|
client_id: provider.clientId,
|
|
7544
8036
|
redirect_uri: redirectUri,
|
|
@@ -7550,7 +8042,7 @@ async function oauth2Login(toolName, provider, port) {
|
|
|
7550
8042
|
}
|
|
7551
8043
|
let codeVerifier;
|
|
7552
8044
|
if (provider.type === "oauth2-pkce") {
|
|
7553
|
-
const pkce =
|
|
8045
|
+
const pkce = generatePkce2();
|
|
7554
8046
|
codeVerifier = pkce.verifier;
|
|
7555
8047
|
params.set("code_challenge", pkce.challenge);
|
|
7556
8048
|
params.set("code_challenge_method", "S256");
|
|
@@ -7573,9 +8065,9 @@ ${fullUrl}
|
|
|
7573
8065
|
throw new Error("no authorization code received (timed out?)");
|
|
7574
8066
|
process.stderr.write(`Exchanging authorization code for tokens...
|
|
7575
8067
|
`);
|
|
7576
|
-
const token = await
|
|
8068
|
+
const token = await exchangeCode2(provider.tokenUrl, result.code, provider.clientId, redirectUri, codeVerifier);
|
|
7577
8069
|
token.provider_id = provider.id;
|
|
7578
|
-
|
|
8070
|
+
storeToken2(toolName, provider.id, token);
|
|
7579
8071
|
process.stderr.write(`Authenticated with ${display}
|
|
7580
8072
|
`);
|
|
7581
8073
|
return token;
|
|
@@ -7596,7 +8088,7 @@ function apiKeyLogin(toolName, provider, tokenValue) {
|
|
|
7596
8088
|
provider_id: provider.id,
|
|
7597
8089
|
created_at: new Date().toISOString()
|
|
7598
8090
|
};
|
|
7599
|
-
|
|
8091
|
+
storeToken2(toolName, provider.id, token);
|
|
7600
8092
|
const display = provider.displayName ?? provider.id;
|
|
7601
8093
|
process.stderr.write(`Token stored for ${display}
|
|
7602
8094
|
`);
|
|
@@ -7615,7 +8107,7 @@ function apiKeyLogin(toolName, provider, tokenValue) {
|
|
|
7615
8107
|
`);
|
|
7616
8108
|
}
|
|
7617
8109
|
process.stderr.write("Enter your token/API key: ");
|
|
7618
|
-
const rl =
|
|
8110
|
+
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
7619
8111
|
rl.question("", (answer) => {
|
|
7620
8112
|
rl.close();
|
|
7621
8113
|
finish(answer.trim());
|
|
@@ -7636,22 +8128,22 @@ async function login(toolName, provider, token, port = 8914) {
|
|
|
7636
8128
|
throw new Error(`unknown auth type: ${provider.type}`);
|
|
7637
8129
|
}
|
|
7638
8130
|
}
|
|
7639
|
-
async function
|
|
8131
|
+
async function ensureValidToken2(toolName, auth) {
|
|
7640
8132
|
const provider = auth.providers[0];
|
|
7641
8133
|
if (!provider)
|
|
7642
8134
|
return null;
|
|
7643
|
-
const token =
|
|
8135
|
+
const token = loadToken2(toolName, provider.id);
|
|
7644
8136
|
if (!token)
|
|
7645
8137
|
return null;
|
|
7646
|
-
if (
|
|
8138
|
+
if (isTokenExpired2(token)) {
|
|
7647
8139
|
if (token.refresh_token && provider.tokenUrl && provider.clientId) {
|
|
7648
8140
|
try {
|
|
7649
|
-
const newToken = await
|
|
8141
|
+
const newToken = await refreshAccessToken2(provider.tokenUrl, token.refresh_token, provider.clientId);
|
|
7650
8142
|
if (!newToken.refresh_token) {
|
|
7651
8143
|
newToken.refresh_token = token.refresh_token;
|
|
7652
8144
|
}
|
|
7653
8145
|
newToken.provider_id = provider.id;
|
|
7654
|
-
|
|
8146
|
+
storeToken2(toolName, provider.id, newToken);
|
|
7655
8147
|
return newToken.access_token;
|
|
7656
8148
|
} catch (e) {
|
|
7657
8149
|
process.stderr.write(`warning: token refresh failed: ${e instanceof Error ? e.message : e}
|
|
@@ -7663,14 +8155,14 @@ async function ensureValidToken(toolName, auth) {
|
|
|
7663
8155
|
}
|
|
7664
8156
|
return token.access_token;
|
|
7665
8157
|
}
|
|
7666
|
-
async function
|
|
7667
|
-
const token = await
|
|
8158
|
+
async function getEnvDict2(toolName, auth) {
|
|
8159
|
+
const token = await ensureValidToken2(toolName, auth);
|
|
7668
8160
|
if (!token)
|
|
7669
8161
|
return {};
|
|
7670
8162
|
return { [auth.envVar]: token };
|
|
7671
8163
|
}
|
|
7672
8164
|
async function getEnvExport(toolName, auth) {
|
|
7673
|
-
const env = await
|
|
8165
|
+
const env = await getEnvDict2(toolName, auth);
|
|
7674
8166
|
if (Object.keys(env).length === 0) {
|
|
7675
8167
|
return "# No token available. Run: mtpcli auth login <tool>";
|
|
7676
8168
|
}
|
|
@@ -7683,7 +8175,7 @@ async function getEnvExport(toolName, auth) {
|
|
|
7683
8175
|
async function authStatus(toolName, auth) {
|
|
7684
8176
|
const providers = [];
|
|
7685
8177
|
for (const provider of auth.providers) {
|
|
7686
|
-
const token =
|
|
8178
|
+
const token = loadToken2(toolName, provider.id);
|
|
7687
8179
|
const status = {
|
|
7688
8180
|
id: provider.id,
|
|
7689
8181
|
type: provider.type,
|
|
@@ -7691,7 +8183,7 @@ async function authStatus(toolName, auth) {
|
|
|
7691
8183
|
authenticated: token !== null
|
|
7692
8184
|
};
|
|
7693
8185
|
if (token) {
|
|
7694
|
-
status.expired =
|
|
8186
|
+
status.expired = isTokenExpired2(token);
|
|
7695
8187
|
status.has_refresh = !!token.refresh_token;
|
|
7696
8188
|
if (token.expires_at)
|
|
7697
8189
|
status.expires_at = token.expires_at;
|
|
@@ -7744,161 +8236,46 @@ async function runToken(toolName) {
|
|
|
7744
8236
|
const auth = await getAuthConfig(toolName);
|
|
7745
8237
|
if (!auth)
|
|
7746
8238
|
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
7747
|
-
const t = await
|
|
8239
|
+
const t = await ensureValidToken2(toolName, auth);
|
|
7748
8240
|
if (!t)
|
|
7749
8241
|
throw new Error(`no valid token for '${toolName}'. Run: mtpcli auth login ${toolName}`);
|
|
7750
8242
|
console.log(t);
|
|
7751
8243
|
}
|
|
7752
|
-
async function runEnv(toolName) {
|
|
7753
|
-
const auth = await getAuthConfig(toolName);
|
|
7754
|
-
if (!auth)
|
|
7755
|
-
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
7756
|
-
console.log(await getEnvExport(toolName, auth));
|
|
7757
|
-
}
|
|
7758
|
-
async function runRefresh(toolName) {
|
|
7759
|
-
const authConfig = await getAuthConfig(toolName);
|
|
7760
|
-
if (!authConfig)
|
|
7761
|
-
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
7762
|
-
const provider = authConfig.providers[0];
|
|
7763
|
-
if (!provider)
|
|
7764
|
-
throw new Error("no auth provider configured");
|
|
7765
|
-
const token =
|
|
7766
|
-
if (!token) {
|
|
7767
|
-
throw new Error(`no stored token for '${toolName}'. Run: mtpcli auth login ${toolName}`);
|
|
7768
|
-
}
|
|
7769
|
-
if (!token.refresh_token) {
|
|
7770
|
-
throw new Error(`token for '${toolName}' has no refresh_token (provider type: ${provider.type})`);
|
|
7771
|
-
}
|
|
7772
|
-
if (!provider.tokenUrl || !provider.clientId) {
|
|
7773
|
-
throw new Error("provider missing tokenUrl or clientId for refresh");
|
|
7774
|
-
}
|
|
7775
|
-
const newToken = await
|
|
7776
|
-
if (!newToken.refresh_token) {
|
|
7777
|
-
newToken.refresh_token = token.refresh_token;
|
|
7778
|
-
}
|
|
7779
|
-
newToken.provider_id = provider.id;
|
|
7780
|
-
|
|
7781
|
-
const display = provider.displayName ?? provider.id;
|
|
7782
|
-
process.stderr.write(`Token refreshed for ${display}
|
|
7783
|
-
`);
|
|
7784
|
-
}
|
|
7785
|
-
var init_auth = __esm(() => {
|
|
7786
|
-
init_open();
|
|
7787
|
-
init_search2();
|
|
7788
|
-
});
|
|
7789
|
-
|
|
7790
|
-
// src/auth.ts
|
|
7791
|
-
import {
|
|
7792
|
-
chmodSync as chmodSync2,
|
|
7793
|
-
existsSync as existsSync3,
|
|
7794
|
-
mkdirSync as mkdirSync3,
|
|
7795
|
-
readFileSync as readFileSync3,
|
|
7796
|
-
unlinkSync as unlinkSync2,
|
|
7797
|
-
writeFileSync as writeFileSync3
|
|
7798
|
-
} from "node:fs";
|
|
7799
|
-
import { homedir as homedir3 } from "node:os";
|
|
7800
|
-
import { dirname as dirname2, join as join3 } from "node:path";
|
|
7801
|
-
function tokensDir2(baseDir) {
|
|
7802
|
-
return join3(baseDir ?? join3(homedir3(), ".mtpcli"), "tokens");
|
|
7803
|
-
}
|
|
7804
|
-
function tokenPath2(toolName, providerId, baseDir) {
|
|
7805
|
-
return join3(tokensDir2(baseDir), toolName, `${providerId}.json`);
|
|
7806
|
-
}
|
|
7807
|
-
function storeToken2(toolName, providerId, token, baseDir) {
|
|
7808
|
-
const path2 = tokenPath2(toolName, providerId, baseDir);
|
|
7809
|
-
mkdirSync3(dirname2(path2), { recursive: true });
|
|
7810
|
-
writeFileSync3(path2, JSON.stringify(token, null, 2));
|
|
7811
|
-
chmodSync2(path2, 384);
|
|
7812
|
-
return path2;
|
|
7813
|
-
}
|
|
7814
|
-
function loadToken2(toolName, providerId, baseDir) {
|
|
7815
|
-
const path2 = tokenPath2(toolName, providerId, baseDir);
|
|
7816
|
-
if (!existsSync3(path2))
|
|
7817
|
-
return null;
|
|
7818
|
-
return JSON.parse(readFileSync3(path2, "utf-8"));
|
|
7819
|
-
}
|
|
7820
|
-
function isTokenExpired2(token) {
|
|
7821
|
-
if (!token.expires_at)
|
|
7822
|
-
return false;
|
|
7823
|
-
const exp = new Date(token.expires_at);
|
|
7824
|
-
const buffer = 5 * 60 * 1000;
|
|
7825
|
-
return exp.getTime() - buffer < Date.now();
|
|
7826
|
-
}
|
|
7827
|
-
async function refreshAccessToken2(tokenUrl, refreshToken, clientId) {
|
|
7828
|
-
const params = new URLSearchParams({
|
|
7829
|
-
grant_type: "refresh_token",
|
|
7830
|
-
refresh_token: refreshToken,
|
|
7831
|
-
client_id: clientId
|
|
7832
|
-
});
|
|
7833
|
-
const resp = await fetch(tokenUrl, {
|
|
7834
|
-
method: "POST",
|
|
7835
|
-
headers: {
|
|
7836
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
7837
|
-
Accept: "application/json"
|
|
7838
|
-
},
|
|
7839
|
-
body: params.toString()
|
|
7840
|
-
});
|
|
7841
|
-
const text = await resp.text();
|
|
7842
|
-
return parseTokenResponse2(text);
|
|
7843
|
-
}
|
|
7844
|
-
function parseTokenResponse2(text) {
|
|
7845
|
-
let val;
|
|
7846
|
-
try {
|
|
7847
|
-
val = JSON.parse(text);
|
|
7848
|
-
} catch {
|
|
7849
|
-
val = Object.fromEntries(new URLSearchParams(text));
|
|
7850
|
-
}
|
|
7851
|
-
const accessToken = val.access_token;
|
|
7852
|
-
if (!accessToken)
|
|
7853
|
-
throw new Error("no access_token in response");
|
|
7854
|
-
const expiresIn = typeof val.expires_in === "number" ? val.expires_in : typeof val.expires_in === "string" ? parseInt(val.expires_in, 10) || undefined : undefined;
|
|
7855
|
-
const expiresAt = expiresIn ? new Date(Date.now() + expiresIn * 1000).toISOString() : undefined;
|
|
7856
|
-
const scope = val.scope;
|
|
7857
|
-
const scopes = scope ? scope.split(/\s+/) : undefined;
|
|
7858
|
-
return {
|
|
7859
|
-
access_token: accessToken,
|
|
7860
|
-
token_type: val.token_type ?? undefined,
|
|
7861
|
-
refresh_token: val.refresh_token ?? undefined,
|
|
7862
|
-
expires_at: expiresAt,
|
|
7863
|
-
scopes,
|
|
7864
|
-
provider_id: "",
|
|
7865
|
-
created_at: new Date().toISOString()
|
|
7866
|
-
};
|
|
7867
|
-
}
|
|
7868
|
-
async function ensureValidToken2(toolName, auth) {
|
|
7869
|
-
const provider = auth.providers[0];
|
|
7870
|
-
if (!provider)
|
|
7871
|
-
return null;
|
|
7872
|
-
const token = loadToken2(toolName, provider.id);
|
|
7873
|
-
if (!token)
|
|
7874
|
-
return null;
|
|
7875
|
-
if (isTokenExpired2(token)) {
|
|
7876
|
-
if (token.refresh_token && provider.tokenUrl && provider.clientId) {
|
|
7877
|
-
try {
|
|
7878
|
-
const newToken = await refreshAccessToken2(provider.tokenUrl, token.refresh_token, provider.clientId);
|
|
7879
|
-
if (!newToken.refresh_token) {
|
|
7880
|
-
newToken.refresh_token = token.refresh_token;
|
|
7881
|
-
}
|
|
7882
|
-
newToken.provider_id = provider.id;
|
|
7883
|
-
storeToken2(toolName, provider.id, newToken);
|
|
7884
|
-
return newToken.access_token;
|
|
7885
|
-
} catch (e) {
|
|
7886
|
-
process.stderr.write(`warning: token refresh failed: ${e instanceof Error ? e.message : e}
|
|
7887
|
-
`);
|
|
7888
|
-
return null;
|
|
7889
|
-
}
|
|
7890
|
-
}
|
|
7891
|
-
return null;
|
|
7892
|
-
}
|
|
7893
|
-
return token.access_token;
|
|
7894
|
-
}
|
|
7895
|
-
async function getEnvDict2(toolName, auth) {
|
|
7896
|
-
const token = await ensureValidToken2(toolName, auth);
|
|
7897
|
-
if (!token)
|
|
7898
|
-
return {};
|
|
7899
|
-
return { [auth.envVar]: token };
|
|
8244
|
+
async function runEnv(toolName) {
|
|
8245
|
+
const auth = await getAuthConfig(toolName);
|
|
8246
|
+
if (!auth)
|
|
8247
|
+
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
8248
|
+
console.log(await getEnvExport(toolName, auth));
|
|
8249
|
+
}
|
|
8250
|
+
async function runRefresh(toolName) {
|
|
8251
|
+
const authConfig = await getAuthConfig(toolName);
|
|
8252
|
+
if (!authConfig)
|
|
8253
|
+
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
8254
|
+
const provider = authConfig.providers[0];
|
|
8255
|
+
if (!provider)
|
|
8256
|
+
throw new Error("no auth provider configured");
|
|
8257
|
+
const token = loadToken2(toolName, provider.id);
|
|
8258
|
+
if (!token) {
|
|
8259
|
+
throw new Error(`no stored token for '${toolName}'. Run: mtpcli auth login ${toolName}`);
|
|
8260
|
+
}
|
|
8261
|
+
if (!token.refresh_token) {
|
|
8262
|
+
throw new Error(`token for '${toolName}' has no refresh_token (provider type: ${provider.type})`);
|
|
8263
|
+
}
|
|
8264
|
+
if (!provider.tokenUrl || !provider.clientId) {
|
|
8265
|
+
throw new Error("provider missing tokenUrl or clientId for refresh");
|
|
8266
|
+
}
|
|
8267
|
+
const newToken = await refreshAccessToken2(provider.tokenUrl, token.refresh_token, provider.clientId);
|
|
8268
|
+
if (!newToken.refresh_token) {
|
|
8269
|
+
newToken.refresh_token = token.refresh_token;
|
|
8270
|
+
}
|
|
8271
|
+
newToken.provider_id = provider.id;
|
|
8272
|
+
storeToken2(toolName, provider.id, newToken);
|
|
8273
|
+
const display = provider.displayName ?? provider.id;
|
|
8274
|
+
process.stderr.write(`Token refreshed for ${display}
|
|
8275
|
+
`);
|
|
7900
8276
|
}
|
|
7901
8277
|
var init_auth2 = __esm(() => {
|
|
8278
|
+
init_open();
|
|
7902
8279
|
init_search2();
|
|
7903
8280
|
});
|
|
7904
8281
|
|
|
@@ -7963,7 +8340,7 @@ __export(exports_serve, {
|
|
|
7963
8340
|
argToJsonSchema: () => argToJsonSchema
|
|
7964
8341
|
});
|
|
7965
8342
|
import { execFile as execFile9, spawn } from "node:child_process";
|
|
7966
|
-
import { createInterface as
|
|
8343
|
+
import { createInterface as createInterface3 } from "node:readline";
|
|
7967
8344
|
import { promisify as promisify9 } from "node:util";
|
|
7968
8345
|
function argToJsonSchema(arg) {
|
|
7969
8346
|
const schema = {};
|
|
@@ -8139,7 +8516,7 @@ async function handleRequest(state, req) {
|
|
|
8139
8516
|
let authEnv;
|
|
8140
8517
|
const authConfig = state.authConfigs.get(entry.cliTool);
|
|
8141
8518
|
if (authConfig) {
|
|
8142
|
-
const env = await
|
|
8519
|
+
const env = await getEnvDict(entry.cliTool, authConfig);
|
|
8143
8520
|
if (Object.keys(env).length > 0)
|
|
8144
8521
|
authEnv = env;
|
|
8145
8522
|
}
|
|
@@ -8186,7 +8563,7 @@ async function run(toolNames) {
|
|
|
8186
8563
|
schemas: allSchemas,
|
|
8187
8564
|
authConfigs: allAuth
|
|
8188
8565
|
};
|
|
8189
|
-
const rl =
|
|
8566
|
+
const rl = createInterface3({ input: process.stdin, crlfDelay: Infinity });
|
|
8190
8567
|
for await (const line of rl) {
|
|
8191
8568
|
const trimmed = line.trim();
|
|
8192
8569
|
if (!trimmed)
|
|
@@ -8209,24 +8586,365 @@ async function run(toolNames) {
|
|
|
8209
8586
|
}
|
|
8210
8587
|
var execFileAsync7, VERSION = "0.1.0";
|
|
8211
8588
|
var init_serve = __esm(() => {
|
|
8212
|
-
|
|
8589
|
+
init_auth();
|
|
8213
8590
|
init_mcp();
|
|
8214
8591
|
init_models();
|
|
8215
8592
|
init_search2();
|
|
8216
8593
|
execFileAsync7 = promisify9(execFile9);
|
|
8217
8594
|
});
|
|
8218
8595
|
|
|
8596
|
+
// src/mcp-oauth.ts
|
|
8597
|
+
var exports_mcp_oauth2 = {};
|
|
8598
|
+
__export(exports_mcp_oauth2, {
|
|
8599
|
+
storeClientRegistration: () => storeClientRegistration2,
|
|
8600
|
+
serverStorageKey: () => serverStorageKey2,
|
|
8601
|
+
registerClient: () => registerClient2,
|
|
8602
|
+
promptForClientId: () => promptForClientId2,
|
|
8603
|
+
parseWwwAuthenticate: () => parseWwwAuthenticate2,
|
|
8604
|
+
mcpOAuthFlow: () => mcpOAuthFlow2,
|
|
8605
|
+
loadOrRefreshMcpToken: () => loadOrRefreshMcpToken2,
|
|
8606
|
+
loadClientRegistration: () => loadClientRegistration2,
|
|
8607
|
+
fetchResourceMetadata: () => fetchResourceMetadata2,
|
|
8608
|
+
fetchAuthServerMetadata: () => fetchAuthServerMetadata2
|
|
8609
|
+
});
|
|
8610
|
+
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
8611
|
+
import {
|
|
8612
|
+
existsSync as existsSync5,
|
|
8613
|
+
mkdirSync as mkdirSync5,
|
|
8614
|
+
readFileSync as readFileSync5,
|
|
8615
|
+
writeFileSync as writeFileSync5,
|
|
8616
|
+
chmodSync as chmodSync4
|
|
8617
|
+
} from "node:fs";
|
|
8618
|
+
import { createServer as createServer3 } from "node:http";
|
|
8619
|
+
import { homedir as homedir5 } from "node:os";
|
|
8620
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
8621
|
+
import { createInterface as createInterface4 } from "node:readline";
|
|
8622
|
+
function serverStorageKey2(url) {
|
|
8623
|
+
const u = new URL(url);
|
|
8624
|
+
return `mcp_${u.host}`;
|
|
8625
|
+
}
|
|
8626
|
+
function parseWwwAuthenticate2(header) {
|
|
8627
|
+
const result = {};
|
|
8628
|
+
const params = header.replace(/^Bearer\s+/i, "");
|
|
8629
|
+
const re = /(\w+)=(?:"([^"]*)"|(\S+))/g;
|
|
8630
|
+
let match;
|
|
8631
|
+
while ((match = re.exec(params)) !== null) {
|
|
8632
|
+
const key = match[1];
|
|
8633
|
+
const val = match[2] ?? match[3];
|
|
8634
|
+
if (key === "resource_metadata")
|
|
8635
|
+
result.resourceMetadata = val;
|
|
8636
|
+
if (key === "scope")
|
|
8637
|
+
result.scope = val;
|
|
8638
|
+
}
|
|
8639
|
+
return result;
|
|
8640
|
+
}
|
|
8641
|
+
async function fetchResourceMetadata2(url) {
|
|
8642
|
+
const resp = await fetch(url);
|
|
8643
|
+
if (!resp.ok) {
|
|
8644
|
+
throw new Error(`failed to fetch resource metadata from ${url}: ${resp.status}`);
|
|
8645
|
+
}
|
|
8646
|
+
return resp.json();
|
|
8647
|
+
}
|
|
8648
|
+
async function fetchAuthServerMetadata2(serverUrl, wwwAuth) {
|
|
8649
|
+
if (wwwAuth) {
|
|
8650
|
+
const parsed = parseWwwAuthenticate2(wwwAuth);
|
|
8651
|
+
if (parsed.resourceMetadata) {
|
|
8652
|
+
try {
|
|
8653
|
+
const resource = await fetchResourceMetadata2(parsed.resourceMetadata);
|
|
8654
|
+
if (resource.authorization_servers?.length) {
|
|
8655
|
+
const asUrl = resource.authorization_servers[0];
|
|
8656
|
+
const wellKnown = `${asUrl}/.well-known/oauth-authorization-server`;
|
|
8657
|
+
const resp = await fetch(wellKnown);
|
|
8658
|
+
if (resp.ok) {
|
|
8659
|
+
return resp.json();
|
|
8660
|
+
}
|
|
8661
|
+
}
|
|
8662
|
+
} catch {}
|
|
8663
|
+
}
|
|
8664
|
+
}
|
|
8665
|
+
const origin = new URL(serverUrl).origin;
|
|
8666
|
+
const oauthUrl = `${origin}/.well-known/oauth-authorization-server`;
|
|
8667
|
+
try {
|
|
8668
|
+
const resp = await fetch(oauthUrl);
|
|
8669
|
+
if (resp.ok) {
|
|
8670
|
+
return resp.json();
|
|
8671
|
+
}
|
|
8672
|
+
} catch {}
|
|
8673
|
+
const oidcUrl = `${origin}/.well-known/openid-configuration`;
|
|
8674
|
+
try {
|
|
8675
|
+
const resp = await fetch(oidcUrl);
|
|
8676
|
+
if (resp.ok) {
|
|
8677
|
+
return resp.json();
|
|
8678
|
+
}
|
|
8679
|
+
} catch {}
|
|
8680
|
+
throw new Error(`could not discover OAuth authorization server for ${serverUrl}`);
|
|
8681
|
+
}
|
|
8682
|
+
async function registerClient2(metadata, redirectUri) {
|
|
8683
|
+
if (!metadata.registration_endpoint) {
|
|
8684
|
+
throw new Error("no registration_endpoint in auth server metadata");
|
|
8685
|
+
}
|
|
8686
|
+
const body = {
|
|
8687
|
+
client_name: "mtpcli",
|
|
8688
|
+
redirect_uris: [redirectUri],
|
|
8689
|
+
grant_types: ["authorization_code"],
|
|
8690
|
+
response_types: ["code"],
|
|
8691
|
+
token_endpoint_auth_method: "none"
|
|
8692
|
+
};
|
|
8693
|
+
const resp = await fetch(metadata.registration_endpoint, {
|
|
8694
|
+
method: "POST",
|
|
8695
|
+
headers: {
|
|
8696
|
+
"Content-Type": "application/json",
|
|
8697
|
+
Accept: "application/json"
|
|
8698
|
+
},
|
|
8699
|
+
body: JSON.stringify(body)
|
|
8700
|
+
});
|
|
8701
|
+
if (!resp.ok) {
|
|
8702
|
+
const text = await resp.text();
|
|
8703
|
+
throw new Error(`dynamic client registration failed (${resp.status}): ${text}`);
|
|
8704
|
+
}
|
|
8705
|
+
const result = await resp.json();
|
|
8706
|
+
const clientId = result.client_id;
|
|
8707
|
+
if (!clientId)
|
|
8708
|
+
throw new Error("no client_id in registration response");
|
|
8709
|
+
return {
|
|
8710
|
+
clientId,
|
|
8711
|
+
clientSecret: result.client_secret,
|
|
8712
|
+
redirectUri,
|
|
8713
|
+
registeredAt: Date.now()
|
|
8714
|
+
};
|
|
8715
|
+
}
|
|
8716
|
+
function promptForClientId2(serverUrl) {
|
|
8717
|
+
return new Promise((resolve, reject) => {
|
|
8718
|
+
process.stderr.write(`
|
|
8719
|
+
No dynamic registration available for ${serverUrl}.
|
|
8720
|
+
` + `You need to register a client manually and provide the client_id.
|
|
8721
|
+
` + `Enter client_id: `);
|
|
8722
|
+
const rl = createInterface4({ input: process.stdin, output: process.stderr });
|
|
8723
|
+
rl.question("", (answer) => {
|
|
8724
|
+
rl.close();
|
|
8725
|
+
const trimmed = answer.trim();
|
|
8726
|
+
if (!trimmed) {
|
|
8727
|
+
reject(new Error("no client_id provided"));
|
|
8728
|
+
return;
|
|
8729
|
+
}
|
|
8730
|
+
resolve(trimmed);
|
|
8731
|
+
});
|
|
8732
|
+
});
|
|
8733
|
+
}
|
|
8734
|
+
function clientStorePath2() {
|
|
8735
|
+
return join5(homedir5(), ".mtpcli", "mcp-oauth-clients.json");
|
|
8736
|
+
}
|
|
8737
|
+
function loadClientStore2() {
|
|
8738
|
+
const path2 = clientStorePath2();
|
|
8739
|
+
if (!existsSync5(path2))
|
|
8740
|
+
return {};
|
|
8741
|
+
try {
|
|
8742
|
+
return JSON.parse(readFileSync5(path2, "utf-8"));
|
|
8743
|
+
} catch {
|
|
8744
|
+
return {};
|
|
8745
|
+
}
|
|
8746
|
+
}
|
|
8747
|
+
function saveClientStore2(store) {
|
|
8748
|
+
const path2 = clientStorePath2();
|
|
8749
|
+
mkdirSync5(dirname4(path2), { recursive: true });
|
|
8750
|
+
writeFileSync5(path2, JSON.stringify(store, null, 2));
|
|
8751
|
+
chmodSync4(path2, 384);
|
|
8752
|
+
}
|
|
8753
|
+
function loadClientRegistration2(origin) {
|
|
8754
|
+
const store = loadClientStore2();
|
|
8755
|
+
return store[origin] ?? null;
|
|
8756
|
+
}
|
|
8757
|
+
function storeClientRegistration2(origin, client, metadata) {
|
|
8758
|
+
const store = loadClientStore2();
|
|
8759
|
+
store[origin] = { client, metadata };
|
|
8760
|
+
saveClientStore2(store);
|
|
8761
|
+
}
|
|
8762
|
+
function startCallbackServer2() {
|
|
8763
|
+
return new Promise((resolve, reject) => {
|
|
8764
|
+
let codeResolve;
|
|
8765
|
+
let codeReject;
|
|
8766
|
+
const codePromise = new Promise((res, rej) => {
|
|
8767
|
+
codeResolve = res;
|
|
8768
|
+
codeReject = rej;
|
|
8769
|
+
});
|
|
8770
|
+
const server = createServer3((req, res) => {
|
|
8771
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1`);
|
|
8772
|
+
const code = url.searchParams.get("code");
|
|
8773
|
+
const error = url.searchParams.get("error");
|
|
8774
|
+
if (code) {
|
|
8775
|
+
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>";
|
|
8776
|
+
res.writeHead(200, {
|
|
8777
|
+
"Content-Type": "text/html",
|
|
8778
|
+
"Content-Length": Buffer.byteLength(html).toString()
|
|
8779
|
+
});
|
|
8780
|
+
res.end(html);
|
|
8781
|
+
server.close();
|
|
8782
|
+
codeResolve(code);
|
|
8783
|
+
return;
|
|
8784
|
+
}
|
|
8785
|
+
if (error) {
|
|
8786
|
+
const desc = url.searchParams.get("error_description") ?? "Unknown error";
|
|
8787
|
+
const html = `<html><body><h2>Authentication failed</h2><p>${desc}</p></body></html>`;
|
|
8788
|
+
res.writeHead(400, {
|
|
8789
|
+
"Content-Type": "text/html",
|
|
8790
|
+
"Content-Length": Buffer.byteLength(html).toString()
|
|
8791
|
+
});
|
|
8792
|
+
res.end(html);
|
|
8793
|
+
server.close();
|
|
8794
|
+
codeReject(new Error(`OAuth error: ${error} - ${desc}`));
|
|
8795
|
+
return;
|
|
8796
|
+
}
|
|
8797
|
+
res.writeHead(404);
|
|
8798
|
+
res.end();
|
|
8799
|
+
});
|
|
8800
|
+
server.listen(0, "127.0.0.1", () => {
|
|
8801
|
+
const addr = server.address();
|
|
8802
|
+
if (!addr || typeof addr === "string") {
|
|
8803
|
+
reject(new Error("failed to get callback server address"));
|
|
8804
|
+
return;
|
|
8805
|
+
}
|
|
8806
|
+
resolve({
|
|
8807
|
+
port: addr.port,
|
|
8808
|
+
waitForCode: () => {
|
|
8809
|
+
const timer = setTimeout(() => {
|
|
8810
|
+
server.close();
|
|
8811
|
+
codeReject(new Error("OAuth callback timed out (120s)"));
|
|
8812
|
+
}, 120000);
|
|
8813
|
+
return codePromise.finally(() => clearTimeout(timer));
|
|
8814
|
+
},
|
|
8815
|
+
server
|
|
8816
|
+
});
|
|
8817
|
+
});
|
|
8818
|
+
server.on("error", (e) => {
|
|
8819
|
+
reject(new Error(`failed to bind callback server: ${e.message}`));
|
|
8820
|
+
});
|
|
8821
|
+
});
|
|
8822
|
+
}
|
|
8823
|
+
async function mcpOAuthFlow2(serverUrl, wwwAuth, clientId) {
|
|
8824
|
+
const origin = new URL(serverUrl).origin;
|
|
8825
|
+
const storageKey = serverStorageKey2(serverUrl);
|
|
8826
|
+
const wwwParsed = parseWwwAuthenticate2(wwwAuth);
|
|
8827
|
+
process.stderr.write(`Discovering OAuth authorization server...
|
|
8828
|
+
`);
|
|
8829
|
+
const metadata = await fetchAuthServerMetadata2(serverUrl, wwwAuth);
|
|
8830
|
+
const challengeMethods = metadata.code_challenge_methods_supported;
|
|
8831
|
+
if (challengeMethods && !challengeMethods.includes("S256")) {
|
|
8832
|
+
throw new Error("authorization server does not support S256 PKCE (required)");
|
|
8833
|
+
}
|
|
8834
|
+
const callback = await startCallbackServer2();
|
|
8835
|
+
const redirectUri = `http://127.0.0.1:${callback.port}/callback`;
|
|
8836
|
+
let resolvedClientId = clientId;
|
|
8837
|
+
let registration;
|
|
8838
|
+
if (!resolvedClientId) {
|
|
8839
|
+
const cached = loadClientRegistration2(origin);
|
|
8840
|
+
if (cached) {
|
|
8841
|
+
resolvedClientId = cached.client.clientId;
|
|
8842
|
+
registration = cached.client;
|
|
8843
|
+
process.stderr.write(`Using cached client registration for ${origin}
|
|
8844
|
+
`);
|
|
8845
|
+
}
|
|
8846
|
+
}
|
|
8847
|
+
if (!resolvedClientId) {
|
|
8848
|
+
if (metadata.registration_endpoint) {
|
|
8849
|
+
process.stderr.write(`Registering client dynamically...
|
|
8850
|
+
`);
|
|
8851
|
+
registration = await registerClient2(metadata, redirectUri);
|
|
8852
|
+
resolvedClientId = registration.clientId;
|
|
8853
|
+
storeClientRegistration2(origin, registration, metadata);
|
|
8854
|
+
} else {
|
|
8855
|
+
callback.server.close();
|
|
8856
|
+
resolvedClientId = await promptForClientId2(serverUrl);
|
|
8857
|
+
const newCallback = await startCallbackServer2();
|
|
8858
|
+
return doOAuthLogin2(metadata, resolvedClientId, `http://127.0.0.1:${newCallback.port}/callback`, newCallback, wwwParsed.scope, storageKey, origin);
|
|
8859
|
+
}
|
|
8860
|
+
}
|
|
8861
|
+
return doOAuthLogin2(metadata, resolvedClientId, redirectUri, callback, wwwParsed.scope, storageKey, origin);
|
|
8862
|
+
}
|
|
8863
|
+
async function doOAuthLogin2(metadata, clientId, redirectUri, callback, scope, storageKey, origin) {
|
|
8864
|
+
const state = randomBytes4(32).toString("base64url");
|
|
8865
|
+
const pkce = generatePkce();
|
|
8866
|
+
const scopes = scope ?? metadata.scopes_supported?.join(" ") ?? undefined;
|
|
8867
|
+
const params = new URLSearchParams({
|
|
8868
|
+
client_id: clientId,
|
|
8869
|
+
redirect_uri: redirectUri,
|
|
8870
|
+
response_type: "code",
|
|
8871
|
+
state,
|
|
8872
|
+
code_challenge: pkce.challenge,
|
|
8873
|
+
code_challenge_method: "S256"
|
|
8874
|
+
});
|
|
8875
|
+
if (scopes)
|
|
8876
|
+
params.set("scope", scopes);
|
|
8877
|
+
const authUrl = `${metadata.authorization_endpoint}?${params.toString()}`;
|
|
8878
|
+
process.stderr.write(`Opening browser for OAuth login...
|
|
8879
|
+
`);
|
|
8880
|
+
process.stderr.write(`If the browser doesn't open, visit:
|
|
8881
|
+
${authUrl}
|
|
8882
|
+
|
|
8883
|
+
`);
|
|
8884
|
+
open_default(authUrl).catch(() => {});
|
|
8885
|
+
process.stderr.write(`Waiting for authentication callback on port ${callback.port}...
|
|
8886
|
+
`);
|
|
8887
|
+
const code = await callback.waitForCode();
|
|
8888
|
+
process.stderr.write(`Exchanging authorization code for tokens...
|
|
8889
|
+
`);
|
|
8890
|
+
const token = await exchangeCode(metadata.token_endpoint, code, clientId, redirectUri, pkce.verifier);
|
|
8891
|
+
token.provider_id = "mcp-oauth";
|
|
8892
|
+
storeToken(storageKey, "mcp-oauth", token);
|
|
8893
|
+
storeClientRegistration2(origin, {
|
|
8894
|
+
clientId,
|
|
8895
|
+
redirectUri,
|
|
8896
|
+
registeredAt: Date.now()
|
|
8897
|
+
}, metadata);
|
|
8898
|
+
process.stderr.write(`Authenticated with ${origin}
|
|
8899
|
+
`);
|
|
8900
|
+
return token.access_token;
|
|
8901
|
+
}
|
|
8902
|
+
async function loadOrRefreshMcpToken2(serverUrl) {
|
|
8903
|
+
const storageKey = serverStorageKey2(serverUrl);
|
|
8904
|
+
const token = loadToken(storageKey, "mcp-oauth");
|
|
8905
|
+
if (!token)
|
|
8906
|
+
return null;
|
|
8907
|
+
if (!isTokenExpired(token)) {
|
|
8908
|
+
return token.access_token;
|
|
8909
|
+
}
|
|
8910
|
+
if (token.refresh_token) {
|
|
8911
|
+
const origin = new URL(serverUrl).origin;
|
|
8912
|
+
const cached = loadClientRegistration2(origin);
|
|
8913
|
+
if (cached) {
|
|
8914
|
+
try {
|
|
8915
|
+
const newToken = await refreshAccessToken(cached.metadata.token_endpoint, token.refresh_token, cached.client.clientId);
|
|
8916
|
+
if (!newToken.refresh_token) {
|
|
8917
|
+
newToken.refresh_token = token.refresh_token;
|
|
8918
|
+
}
|
|
8919
|
+
newToken.provider_id = "mcp-oauth";
|
|
8920
|
+
storeToken(storageKey, "mcp-oauth", newToken);
|
|
8921
|
+
return newToken.access_token;
|
|
8922
|
+
} catch (e) {
|
|
8923
|
+
process.stderr.write(`warning: token refresh failed: ${e instanceof Error ? e.message : e}
|
|
8924
|
+
`);
|
|
8925
|
+
}
|
|
8926
|
+
}
|
|
8927
|
+
}
|
|
8928
|
+
return null;
|
|
8929
|
+
}
|
|
8930
|
+
var init_mcp_oauth2 = __esm(() => {
|
|
8931
|
+
init_open();
|
|
8932
|
+
init_auth();
|
|
8933
|
+
});
|
|
8934
|
+
|
|
8219
8935
|
// src/wrap.ts
|
|
8220
8936
|
var exports_wrap = {};
|
|
8221
8937
|
__export(exports_wrap, {
|
|
8222
8938
|
run: () => run2,
|
|
8223
8939
|
parseToolArgs: () => parseToolArgs,
|
|
8940
|
+
parseHeaders: () => parseHeaders,
|
|
8224
8941
|
mcpToolToCommand: () => mcpToolToCommand,
|
|
8225
8942
|
jsonSchemaToArg: () => jsonSchemaToArg,
|
|
8226
|
-
McpClient: () => McpClient
|
|
8943
|
+
McpClient: () => McpClient,
|
|
8944
|
+
HttpMcpClient: () => HttpMcpClient
|
|
8227
8945
|
});
|
|
8228
8946
|
import { spawn as spawn2 } from "node:child_process";
|
|
8229
|
-
import { createInterface as
|
|
8947
|
+
import { createInterface as createInterface5 } from "node:readline";
|
|
8230
8948
|
function jsonSchemaToArg(name, prop, required) {
|
|
8231
8949
|
let argType = "string";
|
|
8232
8950
|
let values;
|
|
@@ -8378,7 +9096,7 @@ class McpClient {
|
|
|
8378
9096
|
if (!child.stdout || !child.stdin) {
|
|
8379
9097
|
throw new Error("failed to start MCP server");
|
|
8380
9098
|
}
|
|
8381
|
-
const rl =
|
|
9099
|
+
const rl = createInterface5({ input: child.stdout, crlfDelay: Infinity });
|
|
8382
9100
|
const client = new McpClient(child, rl);
|
|
8383
9101
|
client.sendRequest("initialize", {
|
|
8384
9102
|
protocolVersion: "2024-11-05",
|
|
@@ -8417,11 +9135,219 @@ class McpClient {
|
|
|
8417
9135
|
this.child.kill();
|
|
8418
9136
|
}
|
|
8419
9137
|
}
|
|
8420
|
-
|
|
8421
|
-
|
|
9138
|
+
|
|
9139
|
+
class HttpMcpClient {
|
|
9140
|
+
url;
|
|
9141
|
+
sessionId;
|
|
9142
|
+
protocolVersion;
|
|
9143
|
+
extraHeaders;
|
|
9144
|
+
nextId = 0;
|
|
9145
|
+
accessToken;
|
|
9146
|
+
clientId;
|
|
9147
|
+
oauthAttempted = false;
|
|
9148
|
+
constructor(url, extraHeaders, clientId) {
|
|
9149
|
+
this.url = url;
|
|
9150
|
+
this.extraHeaders = extraHeaders;
|
|
9151
|
+
this.clientId = clientId;
|
|
9152
|
+
}
|
|
9153
|
+
static async start(url, extraHeaders = {}, clientId) {
|
|
9154
|
+
const client = new HttpMcpClient(url, extraHeaders, clientId);
|
|
9155
|
+
if (!extraHeaders["Authorization"] && !extraHeaders["authorization"]) {
|
|
9156
|
+
const { loadOrRefreshMcpToken: loadOrRefreshMcpToken3 } = await Promise.resolve().then(() => (init_mcp_oauth2(), exports_mcp_oauth2));
|
|
9157
|
+
const token = await loadOrRefreshMcpToken3(url);
|
|
9158
|
+
if (token) {
|
|
9159
|
+
client.accessToken = token;
|
|
9160
|
+
}
|
|
9161
|
+
}
|
|
9162
|
+
const initResult = await client.sendRequest("initialize", {
|
|
9163
|
+
protocolVersion: "2025-11-25",
|
|
9164
|
+
capabilities: {},
|
|
9165
|
+
clientInfo: { name: "mtpcli-wrap", version: VERSION2 }
|
|
9166
|
+
});
|
|
9167
|
+
client.protocolVersion = initResult.protocolVersion ?? "2025-11-25";
|
|
9168
|
+
await client.sendNotification("notifications/initialized", {});
|
|
9169
|
+
return client;
|
|
9170
|
+
}
|
|
9171
|
+
buildHeaders() {
|
|
9172
|
+
const headers = {
|
|
9173
|
+
"Content-Type": "application/json",
|
|
9174
|
+
Accept: "application/json, text/event-stream"
|
|
9175
|
+
};
|
|
9176
|
+
if (this.accessToken && !this.extraHeaders["Authorization"] && !this.extraHeaders["authorization"]) {
|
|
9177
|
+
headers["Authorization"] = `Bearer ${this.accessToken}`;
|
|
9178
|
+
}
|
|
9179
|
+
Object.assign(headers, this.extraHeaders);
|
|
9180
|
+
if (this.sessionId) {
|
|
9181
|
+
headers["MCP-Session-Id"] = this.sessionId;
|
|
9182
|
+
}
|
|
9183
|
+
if (this.protocolVersion) {
|
|
9184
|
+
headers["MCP-Protocol-Version"] = this.protocolVersion;
|
|
9185
|
+
}
|
|
9186
|
+
return headers;
|
|
9187
|
+
}
|
|
9188
|
+
parseJsonRpcResponse(json) {
|
|
9189
|
+
if (json.error) {
|
|
9190
|
+
const err = json.error;
|
|
9191
|
+
throw new Error(`MCP error: ${err.message} (${err.code})`);
|
|
9192
|
+
}
|
|
9193
|
+
return json.result ?? {};
|
|
9194
|
+
}
|
|
9195
|
+
async readSseResponse(resp, requestId) {
|
|
9196
|
+
const sid = resp.headers.get("mcp-session-id");
|
|
9197
|
+
if (sid)
|
|
9198
|
+
this.sessionId = sid;
|
|
9199
|
+
const text = await resp.text();
|
|
9200
|
+
const events = text.split(/\n\n+/);
|
|
9201
|
+
for (const event of events) {
|
|
9202
|
+
const dataLines = [];
|
|
9203
|
+
for (const line of event.split(`
|
|
9204
|
+
`)) {
|
|
9205
|
+
if (line.startsWith("data:")) {
|
|
9206
|
+
dataLines.push(line.slice(5).trimStart());
|
|
9207
|
+
}
|
|
9208
|
+
}
|
|
9209
|
+
if (dataLines.length === 0)
|
|
9210
|
+
continue;
|
|
9211
|
+
let json;
|
|
9212
|
+
try {
|
|
9213
|
+
json = JSON.parse(dataLines.join(`
|
|
9214
|
+
`));
|
|
9215
|
+
} catch {
|
|
9216
|
+
continue;
|
|
9217
|
+
}
|
|
9218
|
+
if (json.id === requestId) {
|
|
9219
|
+
return this.parseJsonRpcResponse(json);
|
|
9220
|
+
}
|
|
9221
|
+
}
|
|
9222
|
+
throw new Error("SSE stream ended without a response matching request ID " + requestId);
|
|
9223
|
+
}
|
|
9224
|
+
async handleOAuth(resp) {
|
|
9225
|
+
if (this.oauthAttempted) {
|
|
9226
|
+
throw new Error(`HTTP 401 from ${this.url} (OAuth already attempted, not retrying)`);
|
|
9227
|
+
}
|
|
9228
|
+
this.oauthAttempted = true;
|
|
9229
|
+
const wwwAuth = resp.headers.get("www-authenticate") ?? "";
|
|
9230
|
+
const { mcpOAuthFlow: mcpOAuthFlow3 } = await Promise.resolve().then(() => (init_mcp_oauth2(), exports_mcp_oauth2));
|
|
9231
|
+
this.accessToken = await mcpOAuthFlow3(this.url, wwwAuth, this.clientId);
|
|
9232
|
+
}
|
|
9233
|
+
async sendRequest(method, params) {
|
|
9234
|
+
this.nextId++;
|
|
9235
|
+
const body = {
|
|
9236
|
+
jsonrpc: "2.0",
|
|
9237
|
+
id: this.nextId,
|
|
9238
|
+
method,
|
|
9239
|
+
params
|
|
9240
|
+
};
|
|
9241
|
+
const resp = await fetch(this.url, {
|
|
9242
|
+
method: "POST",
|
|
9243
|
+
headers: this.buildHeaders(),
|
|
9244
|
+
body: JSON.stringify(body)
|
|
9245
|
+
});
|
|
9246
|
+
if (resp.status === 401) {
|
|
9247
|
+
await this.handleOAuth(resp);
|
|
9248
|
+
const retryResp = await fetch(this.url, {
|
|
9249
|
+
method: "POST",
|
|
9250
|
+
headers: this.buildHeaders(),
|
|
9251
|
+
body: JSON.stringify(body)
|
|
9252
|
+
});
|
|
9253
|
+
if (!retryResp.ok) {
|
|
9254
|
+
throw new Error(`HTTP ${retryResp.status} ${retryResp.statusText} from ${this.url}`);
|
|
9255
|
+
}
|
|
9256
|
+
const ct2 = retryResp.headers.get("content-type") ?? "";
|
|
9257
|
+
if (ct2.includes("text/event-stream")) {
|
|
9258
|
+
return this.readSseResponse(retryResp, this.nextId);
|
|
9259
|
+
}
|
|
9260
|
+
const sid2 = retryResp.headers.get("mcp-session-id");
|
|
9261
|
+
if (sid2)
|
|
9262
|
+
this.sessionId = sid2;
|
|
9263
|
+
const json2 = await retryResp.json();
|
|
9264
|
+
return this.parseJsonRpcResponse(json2);
|
|
9265
|
+
}
|
|
9266
|
+
if (!resp.ok) {
|
|
9267
|
+
throw new Error(`HTTP ${resp.status} ${resp.statusText} from ${this.url}`);
|
|
9268
|
+
}
|
|
9269
|
+
const ct = resp.headers.get("content-type") ?? "";
|
|
9270
|
+
if (ct.includes("text/event-stream")) {
|
|
9271
|
+
return this.readSseResponse(resp, this.nextId);
|
|
9272
|
+
}
|
|
9273
|
+
const sid = resp.headers.get("mcp-session-id");
|
|
9274
|
+
if (sid) {
|
|
9275
|
+
this.sessionId = sid;
|
|
9276
|
+
}
|
|
9277
|
+
const json = await resp.json();
|
|
9278
|
+
return this.parseJsonRpcResponse(json);
|
|
9279
|
+
}
|
|
9280
|
+
async sendNotification(method, params) {
|
|
9281
|
+
const body = {
|
|
9282
|
+
jsonrpc: "2.0",
|
|
9283
|
+
method,
|
|
9284
|
+
params
|
|
9285
|
+
};
|
|
9286
|
+
const resp = await fetch(this.url, {
|
|
9287
|
+
method: "POST",
|
|
9288
|
+
headers: this.buildHeaders(),
|
|
9289
|
+
body: JSON.stringify(body)
|
|
9290
|
+
});
|
|
9291
|
+
if (resp.status === 401) {
|
|
9292
|
+
await this.handleOAuth(resp);
|
|
9293
|
+
const retryResp = await fetch(this.url, {
|
|
9294
|
+
method: "POST",
|
|
9295
|
+
headers: this.buildHeaders(),
|
|
9296
|
+
body: JSON.stringify(body)
|
|
9297
|
+
});
|
|
9298
|
+
if (!retryResp.ok) {
|
|
9299
|
+
throw new Error(`HTTP ${retryResp.status} ${retryResp.statusText} from ${this.url}`);
|
|
9300
|
+
}
|
|
9301
|
+
const sid2 = retryResp.headers.get("mcp-session-id");
|
|
9302
|
+
if (sid2)
|
|
9303
|
+
this.sessionId = sid2;
|
|
9304
|
+
return;
|
|
9305
|
+
}
|
|
9306
|
+
if (!resp.ok) {
|
|
9307
|
+
throw new Error(`HTTP ${resp.status} ${resp.statusText} from ${this.url}`);
|
|
9308
|
+
}
|
|
9309
|
+
const sid = resp.headers.get("mcp-session-id");
|
|
9310
|
+
if (sid) {
|
|
9311
|
+
this.sessionId = sid;
|
|
9312
|
+
}
|
|
9313
|
+
}
|
|
9314
|
+
async listTools() {
|
|
9315
|
+
const result = await this.sendRequest("tools/list", {});
|
|
9316
|
+
return result.tools ?? [];
|
|
9317
|
+
}
|
|
9318
|
+
async callTool(name, arguments_) {
|
|
9319
|
+
return this.sendRequest("tools/call", { name, arguments: arguments_ });
|
|
9320
|
+
}
|
|
9321
|
+
stop() {}
|
|
9322
|
+
}
|
|
9323
|
+
function parseHeaders(raw) {
|
|
9324
|
+
const headers = {};
|
|
9325
|
+
for (const h of raw) {
|
|
9326
|
+
const idx = h.indexOf(":");
|
|
9327
|
+
if (idx === -1) {
|
|
9328
|
+
throw new Error(`invalid header (missing ':'): ${h}`);
|
|
9329
|
+
}
|
|
9330
|
+
headers[h.slice(0, idx).trim()] = h.slice(idx + 1).trim();
|
|
9331
|
+
}
|
|
9332
|
+
return headers;
|
|
9333
|
+
}
|
|
9334
|
+
async function run2(serverCmd, serverUrl, describeMode, toolName, toolArgs = [], rawHeaders = [], clientId) {
|
|
9335
|
+
let client;
|
|
9336
|
+
let serverName;
|
|
9337
|
+
if (serverUrl) {
|
|
9338
|
+
const headers = parseHeaders(rawHeaders);
|
|
9339
|
+
client = await HttpMcpClient.start(serverUrl, headers, clientId);
|
|
9340
|
+
try {
|
|
9341
|
+
serverName = new URL(serverUrl).hostname;
|
|
9342
|
+
} catch {
|
|
9343
|
+
serverName = serverUrl;
|
|
9344
|
+
}
|
|
9345
|
+
} else {
|
|
9346
|
+
client = await McpClient.start(serverCmd);
|
|
9347
|
+
serverName = serverCmd.split("/").pop()?.split(/\s/)[0] ?? serverCmd;
|
|
9348
|
+
}
|
|
8422
9349
|
try {
|
|
8423
9350
|
const mcpTools = await client.listTools();
|
|
8424
|
-
const serverName = serverCmd.split("/").pop()?.split(/\s/)[0] ?? serverCmd;
|
|
8425
9351
|
const commands = mcpTools.map(mcpToolToCommand);
|
|
8426
9352
|
const schema = {
|
|
8427
9353
|
name: serverName,
|
|
@@ -9208,7 +10134,7 @@ function cleanJson(obj) {
|
|
|
9208
10134
|
}
|
|
9209
10135
|
|
|
9210
10136
|
// src/index.ts
|
|
9211
|
-
var VERSION3 = "0.2.
|
|
10137
|
+
var VERSION3 = "0.2.3";
|
|
9212
10138
|
function selfDescribe() {
|
|
9213
10139
|
const schema = {
|
|
9214
10140
|
name: "mtpcli",
|
|
@@ -9266,8 +10192,7 @@ function selfDescribe() {
|
|
|
9266
10192
|
{
|
|
9267
10193
|
name: "tool",
|
|
9268
10194
|
type: "string",
|
|
9269
|
-
|
|
9270
|
-
description: "Tool name"
|
|
10195
|
+
description: "Tool name (required unless --url is used)"
|
|
9271
10196
|
},
|
|
9272
10197
|
{
|
|
9273
10198
|
name: "--provider",
|
|
@@ -9278,6 +10203,16 @@ function selfDescribe() {
|
|
|
9278
10203
|
name: "--token",
|
|
9279
10204
|
type: "string",
|
|
9280
10205
|
description: "API key or bearer token"
|
|
10206
|
+
},
|
|
10207
|
+
{
|
|
10208
|
+
name: "--url",
|
|
10209
|
+
type: "string",
|
|
10210
|
+
description: "HTTP MCP server URL (triggers OAuth discovery and login)"
|
|
10211
|
+
},
|
|
10212
|
+
{
|
|
10213
|
+
name: "--client-id",
|
|
10214
|
+
type: "string",
|
|
10215
|
+
description: "Pre-registered OAuth client ID (for --url)"
|
|
9281
10216
|
}
|
|
9282
10217
|
],
|
|
9283
10218
|
examples: [
|
|
@@ -9285,6 +10220,10 @@ function selfDescribe() {
|
|
|
9285
10220
|
{
|
|
9286
10221
|
description: "API key login",
|
|
9287
10222
|
command: "mtpcli auth login my-tool --token sk-xxx"
|
|
10223
|
+
},
|
|
10224
|
+
{
|
|
10225
|
+
description: "Login to HTTP MCP server",
|
|
10226
|
+
command: "mtpcli auth login --url https://mcp.example.com/v1/mcp"
|
|
9288
10227
|
}
|
|
9289
10228
|
]
|
|
9290
10229
|
},
|
|
@@ -9382,8 +10321,22 @@ function selfDescribe() {
|
|
|
9382
10321
|
{
|
|
9383
10322
|
name: "--server",
|
|
9384
10323
|
type: "string",
|
|
9385
|
-
|
|
9386
|
-
|
|
10324
|
+
description: "MCP server command (stdio transport)"
|
|
10325
|
+
},
|
|
10326
|
+
{
|
|
10327
|
+
name: "--url",
|
|
10328
|
+
type: "string",
|
|
10329
|
+
description: "MCP server URL (Streamable HTTP transport). Mutually exclusive with --server."
|
|
10330
|
+
},
|
|
10331
|
+
{
|
|
10332
|
+
name: "--header",
|
|
10333
|
+
type: "array",
|
|
10334
|
+
description: "HTTP header(s) for --url, e.g. 'Authorization: Bearer tok'. Repeatable."
|
|
10335
|
+
},
|
|
10336
|
+
{
|
|
10337
|
+
name: "--client-id",
|
|
10338
|
+
type: "string",
|
|
10339
|
+
description: "Pre-registered OAuth client ID (for servers without dynamic registration)"
|
|
9387
10340
|
},
|
|
9388
10341
|
{
|
|
9389
10342
|
name: "--describe",
|
|
@@ -9398,12 +10351,20 @@ function selfDescribe() {
|
|
|
9398
10351
|
],
|
|
9399
10352
|
examples: [
|
|
9400
10353
|
{
|
|
9401
|
-
description: "Describe
|
|
10354
|
+
description: "Describe a stdio MCP server",
|
|
9402
10355
|
command: 'mtpcli wrap --server "npx @mcp/server-github" --describe'
|
|
9403
10356
|
},
|
|
9404
10357
|
{
|
|
9405
|
-
description: "Call a tool",
|
|
10358
|
+
description: "Call a tool on a stdio server",
|
|
9406
10359
|
command: 'mtpcli wrap --server "npx @mcp/server-github" search_repos -- --query mtpcli'
|
|
10360
|
+
},
|
|
10361
|
+
{
|
|
10362
|
+
description: "Describe an HTTP MCP server",
|
|
10363
|
+
command: "mtpcli wrap --url http://localhost:3000/mcp --describe"
|
|
10364
|
+
},
|
|
10365
|
+
{
|
|
10366
|
+
description: "Call a tool on an HTTP server",
|
|
10367
|
+
command: "mtpcli wrap --url http://localhost:3000/mcp search_repos -- --query mtpcli"
|
|
9407
10368
|
}
|
|
9408
10369
|
]
|
|
9409
10370
|
},
|
|
@@ -9517,28 +10478,38 @@ program2.command("search").description("Search across --describe-compatible tool
|
|
|
9517
10478
|
}
|
|
9518
10479
|
});
|
|
9519
10480
|
var authCmd = program2.command("auth").description("Manage authentication for tools");
|
|
9520
|
-
authCmd.command("login").description("Log in to a tool").argument("
|
|
9521
|
-
|
|
10481
|
+
authCmd.command("login").description("Log in to a tool").argument("[tool]", "Tool name").option("--provider <id>", "Specific provider ID").option("--token <value>", "API key / bearer token (skip OAuth flow)").option("--url <url>", "HTTP MCP server URL (triggers OAuth discovery)").option("--client-id <id>", "Pre-registered OAuth client ID (for --url)").action(async (tool, opts) => {
|
|
10482
|
+
if (opts.url) {
|
|
10483
|
+
const { mcpOAuthFlow: mcpOAuthFlow3 } = await Promise.resolve().then(() => (init_mcp_oauth(), exports_mcp_oauth));
|
|
10484
|
+
await mcpOAuthFlow3(opts.url, "", opts.clientId);
|
|
10485
|
+
return;
|
|
10486
|
+
}
|
|
10487
|
+
if (!tool) {
|
|
10488
|
+
process.stderr.write(`error: tool name is required (or use --url)
|
|
10489
|
+
`);
|
|
10490
|
+
process.exit(1);
|
|
10491
|
+
}
|
|
10492
|
+
const { runLogin: runLogin2 } = await Promise.resolve().then(() => (init_auth2(), exports_auth));
|
|
9522
10493
|
await runLogin2(tool, opts.provider, opts.token);
|
|
9523
10494
|
});
|
|
9524
10495
|
authCmd.command("logout").description("Log out from a tool").argument("<tool>", "Tool name").action(async (tool) => {
|
|
9525
|
-
const { runLogout: runLogout2 } = await Promise.resolve().then(() => (
|
|
10496
|
+
const { runLogout: runLogout2 } = await Promise.resolve().then(() => (init_auth2(), exports_auth));
|
|
9526
10497
|
await runLogout2(tool);
|
|
9527
10498
|
});
|
|
9528
10499
|
authCmd.command("status").description("Show auth status for a tool").argument("<tool>", "Tool name").action(async (tool) => {
|
|
9529
|
-
const { runStatus: runStatus2 } = await Promise.resolve().then(() => (
|
|
10500
|
+
const { runStatus: runStatus2 } = await Promise.resolve().then(() => (init_auth2(), exports_auth));
|
|
9530
10501
|
await runStatus2(tool);
|
|
9531
10502
|
});
|
|
9532
10503
|
authCmd.command("token").description("Print the access token for a tool").argument("<tool>", "Tool name").action(async (tool) => {
|
|
9533
|
-
const { runToken: runToken2 } = await Promise.resolve().then(() => (
|
|
10504
|
+
const { runToken: runToken2 } = await Promise.resolve().then(() => (init_auth2(), exports_auth));
|
|
9534
10505
|
await runToken2(tool);
|
|
9535
10506
|
});
|
|
9536
10507
|
authCmd.command("env").description("Print shell export statement for eval").argument("<tool>", "Tool name").action(async (tool) => {
|
|
9537
|
-
const { runEnv: runEnv2 } = await Promise.resolve().then(() => (
|
|
10508
|
+
const { runEnv: runEnv2 } = await Promise.resolve().then(() => (init_auth2(), exports_auth));
|
|
9538
10509
|
await runEnv2(tool);
|
|
9539
10510
|
});
|
|
9540
10511
|
authCmd.command("refresh").description("Force-refresh the stored OAuth token for a tool").argument("<tool>", "Tool name").action(async (tool) => {
|
|
9541
|
-
const { runRefresh: runRefresh2 } = await Promise.resolve().then(() => (
|
|
10512
|
+
const { runRefresh: runRefresh2 } = await Promise.resolve().then(() => (init_auth2(), exports_auth));
|
|
9542
10513
|
await runRefresh2(tool);
|
|
9543
10514
|
});
|
|
9544
10515
|
program2.command("serve").description("Serve CLI tools as an MCP server (cli2mcp bridge)").requiredOption("--tool <names...>", "Tool(s) to serve").addHelpText("after", `
|
|
@@ -9580,9 +10551,29 @@ Multiple tools can be combined into a single MCP server:
|
|
|
9580
10551
|
const { run: run5 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
|
|
9581
10552
|
await run5(opts.tool);
|
|
9582
10553
|
});
|
|
9583
|
-
program2.command("wrap").description("Wrap an MCP server as a CLI tool (mcp2cli bridge)").
|
|
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) => {
|
|
10555
|
+
if (opts.server && opts.url) {
|
|
10556
|
+
process.stderr.write(`error: --server and --url are mutually exclusive
|
|
10557
|
+
`);
|
|
10558
|
+
process.exit(1);
|
|
10559
|
+
}
|
|
10560
|
+
if (!opts.server && !opts.url) {
|
|
10561
|
+
process.stderr.write(`error: one of --server or --url is required
|
|
10562
|
+
`);
|
|
10563
|
+
process.exit(1);
|
|
10564
|
+
}
|
|
10565
|
+
if (opts.header && !opts.url) {
|
|
10566
|
+
process.stderr.write(`error: --header requires --url
|
|
10567
|
+
`);
|
|
10568
|
+
process.exit(1);
|
|
10569
|
+
}
|
|
10570
|
+
if (opts.clientId && !opts.url) {
|
|
10571
|
+
process.stderr.write(`error: --client-id requires --url
|
|
10572
|
+
`);
|
|
10573
|
+
process.exit(1);
|
|
10574
|
+
}
|
|
9584
10575
|
const { run: run5 } = await Promise.resolve().then(() => (init_wrap(), exports_wrap));
|
|
9585
|
-
await run5(opts.server, opts.describe, toolName, args);
|
|
10576
|
+
await run5(opts.server, opts.url, opts.describe, toolName, args, opts.header ?? [], opts.clientId);
|
|
9586
10577
|
});
|
|
9587
10578
|
program2.command("validate").description("Validate a tool's --describe output against the MTP spec").argument("[tool]", "Tool name to validate").option("--json", "Output as JSON", false).option("--stdin", "Read JSON from stdin", false).option("--skip-help", "Skip --help cross-reference", false).action(async (tool, opts) => {
|
|
9588
10579
|
const { run: run5 } = await Promise.resolve().then(() => (init_validate(), exports_validate));
|