@modeltoolsprotocol/mtpcli 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1630 -118
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7350,6 +7350,7 @@ __export(exports_auth, {
|
|
|
7350
7350
|
runLogout: () => runLogout,
|
|
7351
7351
|
runLogin: () => runLogin,
|
|
7352
7352
|
runEnv: () => runEnv,
|
|
7353
|
+
refreshAccessToken: () => refreshAccessToken,
|
|
7353
7354
|
oauth2Login: () => oauth2Login,
|
|
7354
7355
|
login: () => login,
|
|
7355
7356
|
loadToken: () => loadToken,
|
|
@@ -7358,6 +7359,8 @@ __export(exports_auth, {
|
|
|
7358
7359
|
getEnvExport: () => getEnvExport,
|
|
7359
7360
|
getEnvDict: () => getEnvDict,
|
|
7360
7361
|
getAuthConfig: () => getAuthConfig,
|
|
7362
|
+
generatePkce: () => generatePkce,
|
|
7363
|
+
exchangeCode: () => exchangeCode,
|
|
7361
7364
|
ensureValidToken: () => ensureValidToken,
|
|
7362
7365
|
deleteToken: () => deleteToken,
|
|
7363
7366
|
authStatus: () => authStatus,
|
|
@@ -7469,7 +7472,7 @@ function waitForCallback(port, timeoutSecs) {
|
|
|
7469
7472
|
});
|
|
7470
7473
|
});
|
|
7471
7474
|
}
|
|
7472
|
-
async function exchangeCode(tokenUrl, code, clientId, redirectUri, codeVerifier) {
|
|
7475
|
+
async function exchangeCode(tokenUrl, code, clientId, redirectUri, codeVerifier, resource) {
|
|
7473
7476
|
const params = new URLSearchParams({
|
|
7474
7477
|
grant_type: "authorization_code",
|
|
7475
7478
|
code,
|
|
@@ -7478,6 +7481,8 @@ async function exchangeCode(tokenUrl, code, clientId, redirectUri, codeVerifier)
|
|
|
7478
7481
|
});
|
|
7479
7482
|
if (codeVerifier)
|
|
7480
7483
|
params.set("code_verifier", codeVerifier);
|
|
7484
|
+
if (resource)
|
|
7485
|
+
params.set("resource", resource);
|
|
7481
7486
|
const resp = await fetch(tokenUrl, {
|
|
7482
7487
|
method: "POST",
|
|
7483
7488
|
headers: {
|
|
@@ -7489,12 +7494,14 @@ async function exchangeCode(tokenUrl, code, clientId, redirectUri, codeVerifier)
|
|
|
7489
7494
|
const text = await resp.text();
|
|
7490
7495
|
return parseTokenResponse(text);
|
|
7491
7496
|
}
|
|
7492
|
-
async function refreshAccessToken(tokenUrl, refreshToken, clientId) {
|
|
7497
|
+
async function refreshAccessToken(tokenUrl, refreshToken, clientId, resource) {
|
|
7493
7498
|
const params = new URLSearchParams({
|
|
7494
7499
|
grant_type: "refresh_token",
|
|
7495
7500
|
refresh_token: refreshToken,
|
|
7496
7501
|
client_id: clientId
|
|
7497
7502
|
});
|
|
7503
|
+
if (resource)
|
|
7504
|
+
params.set("resource", resource);
|
|
7498
7505
|
const resp = await fetch(tokenUrl, {
|
|
7499
7506
|
method: "POST",
|
|
7500
7507
|
headers: {
|
|
@@ -7787,35 +7794,465 @@ var init_auth = __esm(() => {
|
|
|
7787
7794
|
init_search2();
|
|
7788
7795
|
});
|
|
7789
7796
|
|
|
7790
|
-
// src/
|
|
7797
|
+
// src/mcp-oauth.ts
|
|
7798
|
+
var exports_mcp_oauth = {};
|
|
7799
|
+
__export(exports_mcp_oauth, {
|
|
7800
|
+
storeClientRegistration: () => storeClientRegistration,
|
|
7801
|
+
serverStorageKey: () => serverStorageKey,
|
|
7802
|
+
registerClient: () => registerClient,
|
|
7803
|
+
promptForClientId: () => promptForClientId,
|
|
7804
|
+
parseWwwAuthenticate: () => parseWwwAuthenticate,
|
|
7805
|
+
mcpOAuthFlow: () => mcpOAuthFlow,
|
|
7806
|
+
mcpAuthToken: () => mcpAuthToken,
|
|
7807
|
+
mcpAuthStatus: () => mcpAuthStatus,
|
|
7808
|
+
mcpAuthRefresh: () => mcpAuthRefresh,
|
|
7809
|
+
mcpAuthLogout: () => mcpAuthLogout,
|
|
7810
|
+
loadOrRefreshMcpToken: () => loadOrRefreshMcpToken,
|
|
7811
|
+
loadClientRegistration: () => loadClientRegistration,
|
|
7812
|
+
fetchResourceMetadata: () => fetchResourceMetadata,
|
|
7813
|
+
fetchAuthServerMetadata: () => fetchAuthServerMetadata
|
|
7814
|
+
});
|
|
7815
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
7791
7816
|
import {
|
|
7792
|
-
chmodSync as chmodSync2,
|
|
7793
7817
|
existsSync as existsSync3,
|
|
7794
7818
|
mkdirSync as mkdirSync3,
|
|
7795
7819
|
readFileSync as readFileSync3,
|
|
7796
|
-
|
|
7797
|
-
|
|
7820
|
+
writeFileSync as writeFileSync3,
|
|
7821
|
+
chmodSync as chmodSync2
|
|
7798
7822
|
} from "node:fs";
|
|
7823
|
+
import { createServer as createServer2 } from "node:http";
|
|
7799
7824
|
import { homedir as homedir3 } from "node:os";
|
|
7800
7825
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
7826
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
7827
|
+
function serverStorageKey(url) {
|
|
7828
|
+
const u = new URL(url);
|
|
7829
|
+
return `mcp_${u.host}`;
|
|
7830
|
+
}
|
|
7831
|
+
function parseWwwAuthenticate(header) {
|
|
7832
|
+
const result = {};
|
|
7833
|
+
const params = header.replace(/^Bearer\s+/i, "");
|
|
7834
|
+
const re = /(\w+)=(?:"([^"]*)"|(\S+))/g;
|
|
7835
|
+
let match;
|
|
7836
|
+
while ((match = re.exec(params)) !== null) {
|
|
7837
|
+
const key = match[1];
|
|
7838
|
+
const val = match[2] ?? match[3];
|
|
7839
|
+
if (key === "resource_metadata")
|
|
7840
|
+
result.resourceMetadata = val;
|
|
7841
|
+
if (key === "scope")
|
|
7842
|
+
result.scope = val;
|
|
7843
|
+
}
|
|
7844
|
+
return result;
|
|
7845
|
+
}
|
|
7846
|
+
async function fetchResourceMetadata(url) {
|
|
7847
|
+
const resp = await fetch(url);
|
|
7848
|
+
if (!resp.ok) {
|
|
7849
|
+
throw new Error(`failed to fetch resource metadata from ${url}: ${resp.status}`);
|
|
7850
|
+
}
|
|
7851
|
+
return resp.json();
|
|
7852
|
+
}
|
|
7853
|
+
async function fetchAuthServerMetadata(serverUrl, wwwAuth) {
|
|
7854
|
+
if (wwwAuth) {
|
|
7855
|
+
const parsed = parseWwwAuthenticate(wwwAuth);
|
|
7856
|
+
if (parsed.resourceMetadata) {
|
|
7857
|
+
try {
|
|
7858
|
+
const resource = await fetchResourceMetadata(parsed.resourceMetadata);
|
|
7859
|
+
if (resource.authorization_servers?.length) {
|
|
7860
|
+
const asUrl = resource.authorization_servers[0];
|
|
7861
|
+
const wellKnown = `${asUrl}/.well-known/oauth-authorization-server`;
|
|
7862
|
+
const resp = await fetch(wellKnown);
|
|
7863
|
+
if (resp.ok) {
|
|
7864
|
+
return resp.json();
|
|
7865
|
+
}
|
|
7866
|
+
}
|
|
7867
|
+
} catch {}
|
|
7868
|
+
}
|
|
7869
|
+
}
|
|
7870
|
+
const origin = new URL(serverUrl).origin;
|
|
7871
|
+
const oauthUrl = `${origin}/.well-known/oauth-authorization-server`;
|
|
7872
|
+
try {
|
|
7873
|
+
const resp = await fetch(oauthUrl);
|
|
7874
|
+
if (resp.ok) {
|
|
7875
|
+
return resp.json();
|
|
7876
|
+
}
|
|
7877
|
+
} catch {}
|
|
7878
|
+
const oidcUrl = `${origin}/.well-known/openid-configuration`;
|
|
7879
|
+
try {
|
|
7880
|
+
const resp = await fetch(oidcUrl);
|
|
7881
|
+
if (resp.ok) {
|
|
7882
|
+
return resp.json();
|
|
7883
|
+
}
|
|
7884
|
+
} catch {}
|
|
7885
|
+
throw new Error(`could not discover OAuth authorization server for ${serverUrl}`);
|
|
7886
|
+
}
|
|
7887
|
+
async function registerClient(metadata, redirectUri) {
|
|
7888
|
+
if (!metadata.registration_endpoint) {
|
|
7889
|
+
throw new Error("no registration_endpoint in auth server metadata");
|
|
7890
|
+
}
|
|
7891
|
+
const body = {
|
|
7892
|
+
client_name: "mtpcli",
|
|
7893
|
+
redirect_uris: [redirectUri],
|
|
7894
|
+
grant_types: ["authorization_code"],
|
|
7895
|
+
response_types: ["code"],
|
|
7896
|
+
token_endpoint_auth_method: "none"
|
|
7897
|
+
};
|
|
7898
|
+
const resp = await fetch(metadata.registration_endpoint, {
|
|
7899
|
+
method: "POST",
|
|
7900
|
+
headers: {
|
|
7901
|
+
"Content-Type": "application/json",
|
|
7902
|
+
Accept: "application/json"
|
|
7903
|
+
},
|
|
7904
|
+
body: JSON.stringify(body)
|
|
7905
|
+
});
|
|
7906
|
+
if (!resp.ok) {
|
|
7907
|
+
const text = await resp.text();
|
|
7908
|
+
throw new Error(`dynamic client registration failed (${resp.status}): ${text}`);
|
|
7909
|
+
}
|
|
7910
|
+
const result = await resp.json();
|
|
7911
|
+
const clientId = result.client_id;
|
|
7912
|
+
if (!clientId)
|
|
7913
|
+
throw new Error("no client_id in registration response");
|
|
7914
|
+
return {
|
|
7915
|
+
clientId,
|
|
7916
|
+
clientSecret: result.client_secret,
|
|
7917
|
+
redirectUri,
|
|
7918
|
+
registeredAt: Date.now()
|
|
7919
|
+
};
|
|
7920
|
+
}
|
|
7921
|
+
function promptForClientId(serverUrl) {
|
|
7922
|
+
return new Promise((resolve, reject) => {
|
|
7923
|
+
process.stderr.write(`
|
|
7924
|
+
No dynamic registration available for ${serverUrl}.
|
|
7925
|
+
` + `You need to register a client manually and provide the client_id.
|
|
7926
|
+
` + `Enter client_id: `);
|
|
7927
|
+
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
7928
|
+
rl.question("", (answer) => {
|
|
7929
|
+
rl.close();
|
|
7930
|
+
const trimmed = answer.trim();
|
|
7931
|
+
if (!trimmed) {
|
|
7932
|
+
reject(new Error("no client_id provided"));
|
|
7933
|
+
return;
|
|
7934
|
+
}
|
|
7935
|
+
resolve(trimmed);
|
|
7936
|
+
});
|
|
7937
|
+
});
|
|
7938
|
+
}
|
|
7939
|
+
function clientStorePath() {
|
|
7940
|
+
return join3(homedir3(), ".mtpcli", "mcp-oauth-clients.json");
|
|
7941
|
+
}
|
|
7942
|
+
function loadClientStore() {
|
|
7943
|
+
const path2 = clientStorePath();
|
|
7944
|
+
if (!existsSync3(path2))
|
|
7945
|
+
return {};
|
|
7946
|
+
try {
|
|
7947
|
+
return JSON.parse(readFileSync3(path2, "utf-8"));
|
|
7948
|
+
} catch {
|
|
7949
|
+
return {};
|
|
7950
|
+
}
|
|
7951
|
+
}
|
|
7952
|
+
function saveClientStore(store) {
|
|
7953
|
+
const path2 = clientStorePath();
|
|
7954
|
+
mkdirSync3(dirname2(path2), { recursive: true });
|
|
7955
|
+
writeFileSync3(path2, JSON.stringify(store, null, 2));
|
|
7956
|
+
chmodSync2(path2, 384);
|
|
7957
|
+
}
|
|
7958
|
+
function loadClientRegistration(origin) {
|
|
7959
|
+
const store = loadClientStore();
|
|
7960
|
+
return store[origin] ?? null;
|
|
7961
|
+
}
|
|
7962
|
+
function storeClientRegistration(origin, client, metadata) {
|
|
7963
|
+
const store = loadClientStore();
|
|
7964
|
+
store[origin] = { client, metadata };
|
|
7965
|
+
saveClientStore(store);
|
|
7966
|
+
}
|
|
7967
|
+
function startCallbackServer() {
|
|
7968
|
+
return new Promise((resolve, reject) => {
|
|
7969
|
+
let codeResolve;
|
|
7970
|
+
let codeReject;
|
|
7971
|
+
const codePromise = new Promise((res, rej) => {
|
|
7972
|
+
codeResolve = res;
|
|
7973
|
+
codeReject = rej;
|
|
7974
|
+
});
|
|
7975
|
+
const server = createServer2((req, res) => {
|
|
7976
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1`);
|
|
7977
|
+
const code = url.searchParams.get("code");
|
|
7978
|
+
const error = url.searchParams.get("error");
|
|
7979
|
+
if (code) {
|
|
7980
|
+
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>";
|
|
7981
|
+
res.writeHead(200, {
|
|
7982
|
+
"Content-Type": "text/html",
|
|
7983
|
+
"Content-Length": Buffer.byteLength(html).toString()
|
|
7984
|
+
});
|
|
7985
|
+
res.end(html);
|
|
7986
|
+
server.close();
|
|
7987
|
+
codeResolve(code);
|
|
7988
|
+
return;
|
|
7989
|
+
}
|
|
7990
|
+
if (error) {
|
|
7991
|
+
const desc = url.searchParams.get("error_description") ?? "Unknown error";
|
|
7992
|
+
const html = `<html><body><h2>Authentication failed</h2><p>${desc}</p></body></html>`;
|
|
7993
|
+
res.writeHead(400, {
|
|
7994
|
+
"Content-Type": "text/html",
|
|
7995
|
+
"Content-Length": Buffer.byteLength(html).toString()
|
|
7996
|
+
});
|
|
7997
|
+
res.end(html);
|
|
7998
|
+
server.close();
|
|
7999
|
+
codeReject(new Error(`OAuth error: ${error} - ${desc}`));
|
|
8000
|
+
return;
|
|
8001
|
+
}
|
|
8002
|
+
res.writeHead(404);
|
|
8003
|
+
res.end();
|
|
8004
|
+
});
|
|
8005
|
+
server.listen(0, "127.0.0.1", () => {
|
|
8006
|
+
const addr = server.address();
|
|
8007
|
+
if (!addr || typeof addr === "string") {
|
|
8008
|
+
reject(new Error("failed to get callback server address"));
|
|
8009
|
+
return;
|
|
8010
|
+
}
|
|
8011
|
+
resolve({
|
|
8012
|
+
port: addr.port,
|
|
8013
|
+
waitForCode: () => {
|
|
8014
|
+
const timer = setTimeout(() => {
|
|
8015
|
+
server.close();
|
|
8016
|
+
codeReject(new Error("OAuth callback timed out (120s)"));
|
|
8017
|
+
}, 120000);
|
|
8018
|
+
return codePromise.finally(() => clearTimeout(timer));
|
|
8019
|
+
},
|
|
8020
|
+
server
|
|
8021
|
+
});
|
|
8022
|
+
});
|
|
8023
|
+
server.on("error", (e) => {
|
|
8024
|
+
reject(new Error(`failed to bind callback server: ${e.message}`));
|
|
8025
|
+
});
|
|
8026
|
+
});
|
|
8027
|
+
}
|
|
8028
|
+
async function mcpOAuthFlow(serverUrl, wwwAuth, clientId) {
|
|
8029
|
+
const origin = new URL(serverUrl).origin;
|
|
8030
|
+
const storageKey = serverStorageKey(serverUrl);
|
|
8031
|
+
const wwwParsed = parseWwwAuthenticate(wwwAuth);
|
|
8032
|
+
process.stderr.write(`Discovering OAuth authorization server...
|
|
8033
|
+
`);
|
|
8034
|
+
const metadata = await fetchAuthServerMetadata(serverUrl, wwwAuth);
|
|
8035
|
+
const challengeMethods = metadata.code_challenge_methods_supported;
|
|
8036
|
+
if (challengeMethods && !challengeMethods.includes("S256")) {
|
|
8037
|
+
throw new Error("authorization server does not support S256 PKCE (required)");
|
|
8038
|
+
}
|
|
8039
|
+
const callback = await startCallbackServer();
|
|
8040
|
+
const redirectUri = `http://127.0.0.1:${callback.port}/callback`;
|
|
8041
|
+
let resolvedClientId = clientId;
|
|
8042
|
+
let registration;
|
|
8043
|
+
if (!resolvedClientId) {
|
|
8044
|
+
const cached = loadClientRegistration(origin);
|
|
8045
|
+
if (cached) {
|
|
8046
|
+
resolvedClientId = cached.client.clientId;
|
|
8047
|
+
registration = cached.client;
|
|
8048
|
+
process.stderr.write(`Using cached client registration for ${origin}
|
|
8049
|
+
`);
|
|
8050
|
+
}
|
|
8051
|
+
}
|
|
8052
|
+
if (!resolvedClientId) {
|
|
8053
|
+
if (metadata.registration_endpoint) {
|
|
8054
|
+
process.stderr.write(`Registering client dynamically...
|
|
8055
|
+
`);
|
|
8056
|
+
registration = await registerClient(metadata, redirectUri);
|
|
8057
|
+
resolvedClientId = registration.clientId;
|
|
8058
|
+
storeClientRegistration(origin, registration, metadata);
|
|
8059
|
+
} else {
|
|
8060
|
+
callback.server.close();
|
|
8061
|
+
resolvedClientId = await promptForClientId(serverUrl);
|
|
8062
|
+
const newCallback = await startCallbackServer();
|
|
8063
|
+
return doOAuthLogin(metadata, resolvedClientId, `http://127.0.0.1:${newCallback.port}/callback`, newCallback, wwwParsed.scope, storageKey, origin);
|
|
8064
|
+
}
|
|
8065
|
+
}
|
|
8066
|
+
return doOAuthLogin(metadata, resolvedClientId, redirectUri, callback, wwwParsed.scope, storageKey, origin);
|
|
8067
|
+
}
|
|
8068
|
+
async function doOAuthLogin(metadata, clientId, redirectUri, callback, scope, storageKey, origin) {
|
|
8069
|
+
const state = randomBytes2(32).toString("base64url");
|
|
8070
|
+
const pkce = generatePkce();
|
|
8071
|
+
const scopes = scope ?? metadata.scopes_supported?.join(" ") ?? undefined;
|
|
8072
|
+
const params = new URLSearchParams({
|
|
8073
|
+
client_id: clientId,
|
|
8074
|
+
redirect_uri: redirectUri,
|
|
8075
|
+
response_type: "code",
|
|
8076
|
+
state,
|
|
8077
|
+
code_challenge: pkce.challenge,
|
|
8078
|
+
code_challenge_method: "S256"
|
|
8079
|
+
});
|
|
8080
|
+
if (scopes)
|
|
8081
|
+
params.set("scope", scopes);
|
|
8082
|
+
const authUrl = `${metadata.authorization_endpoint}?${params.toString()}`;
|
|
8083
|
+
process.stderr.write(`Opening browser for OAuth login...
|
|
8084
|
+
`);
|
|
8085
|
+
process.stderr.write(`If the browser doesn't open, visit:
|
|
8086
|
+
${authUrl}
|
|
8087
|
+
|
|
8088
|
+
`);
|
|
8089
|
+
open_default(authUrl).catch(() => {});
|
|
8090
|
+
process.stderr.write(`Waiting for authentication callback on port ${callback.port}...
|
|
8091
|
+
`);
|
|
8092
|
+
const code = await callback.waitForCode();
|
|
8093
|
+
process.stderr.write(`Exchanging authorization code for tokens...
|
|
8094
|
+
`);
|
|
8095
|
+
const token = await exchangeCode(metadata.token_endpoint, code, clientId, redirectUri, pkce.verifier);
|
|
8096
|
+
token.provider_id = "mcp-oauth";
|
|
8097
|
+
storeToken(storageKey, "mcp-oauth", token);
|
|
8098
|
+
storeClientRegistration(origin, {
|
|
8099
|
+
clientId,
|
|
8100
|
+
redirectUri,
|
|
8101
|
+
registeredAt: Date.now()
|
|
8102
|
+
}, metadata);
|
|
8103
|
+
process.stderr.write(`Authenticated with ${origin}
|
|
8104
|
+
`);
|
|
8105
|
+
return token.access_token;
|
|
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
|
+
}
|
|
8159
|
+
async function loadOrRefreshMcpToken(serverUrl) {
|
|
8160
|
+
const storageKey = serverStorageKey(serverUrl);
|
|
8161
|
+
const token = loadToken(storageKey, "mcp-oauth");
|
|
8162
|
+
if (!token)
|
|
8163
|
+
return null;
|
|
8164
|
+
if (!isTokenExpired(token)) {
|
|
8165
|
+
return token.access_token;
|
|
8166
|
+
}
|
|
8167
|
+
if (token.refresh_token) {
|
|
8168
|
+
const origin = new URL(serverUrl).origin;
|
|
8169
|
+
const cached = loadClientRegistration(origin);
|
|
8170
|
+
if (cached) {
|
|
8171
|
+
try {
|
|
8172
|
+
const newToken = await refreshAccessToken(cached.metadata.token_endpoint, token.refresh_token, cached.client.clientId);
|
|
8173
|
+
if (!newToken.refresh_token) {
|
|
8174
|
+
newToken.refresh_token = token.refresh_token;
|
|
8175
|
+
}
|
|
8176
|
+
newToken.provider_id = "mcp-oauth";
|
|
8177
|
+
storeToken(storageKey, "mcp-oauth", newToken);
|
|
8178
|
+
return newToken.access_token;
|
|
8179
|
+
} catch (e) {
|
|
8180
|
+
process.stderr.write(`warning: token refresh failed: ${e instanceof Error ? e.message : e}
|
|
8181
|
+
`);
|
|
8182
|
+
}
|
|
8183
|
+
}
|
|
8184
|
+
}
|
|
8185
|
+
return null;
|
|
8186
|
+
}
|
|
8187
|
+
var init_mcp_oauth = __esm(() => {
|
|
8188
|
+
init_open();
|
|
8189
|
+
init_auth();
|
|
8190
|
+
});
|
|
8191
|
+
|
|
8192
|
+
// src/auth.ts
|
|
8193
|
+
var exports_auth2 = {};
|
|
8194
|
+
__export(exports_auth2, {
|
|
8195
|
+
storeToken: () => storeToken2,
|
|
8196
|
+
runToken: () => runToken2,
|
|
8197
|
+
runStatus: () => runStatus2,
|
|
8198
|
+
runRefresh: () => runRefresh2,
|
|
8199
|
+
runLogout: () => runLogout2,
|
|
8200
|
+
runLogin: () => runLogin2,
|
|
8201
|
+
runEnv: () => runEnv2,
|
|
8202
|
+
refreshAccessToken: () => refreshAccessToken2,
|
|
8203
|
+
oauth2Login: () => oauth2Login2,
|
|
8204
|
+
login: () => login2,
|
|
8205
|
+
loadToken: () => loadToken2,
|
|
8206
|
+
isTokenExpired: () => isTokenExpired2,
|
|
8207
|
+
getProvider: () => getProvider2,
|
|
8208
|
+
getEnvExport: () => getEnvExport2,
|
|
8209
|
+
getEnvDict: () => getEnvDict2,
|
|
8210
|
+
getAuthConfig: () => getAuthConfig2,
|
|
8211
|
+
generatePkce: () => generatePkce2,
|
|
8212
|
+
exchangeCode: () => exchangeCode2,
|
|
8213
|
+
ensureValidToken: () => ensureValidToken2,
|
|
8214
|
+
deleteToken: () => deleteToken2,
|
|
8215
|
+
authStatus: () => authStatus2,
|
|
8216
|
+
apiKeyLogin: () => apiKeyLogin2
|
|
8217
|
+
});
|
|
8218
|
+
import { createHash as createHash2, randomBytes as randomBytes3 } from "node:crypto";
|
|
8219
|
+
import {
|
|
8220
|
+
chmodSync as chmodSync3,
|
|
8221
|
+
existsSync as existsSync4,
|
|
8222
|
+
mkdirSync as mkdirSync4,
|
|
8223
|
+
readFileSync as readFileSync4,
|
|
8224
|
+
unlinkSync as unlinkSync2,
|
|
8225
|
+
writeFileSync as writeFileSync4
|
|
8226
|
+
} from "node:fs";
|
|
8227
|
+
import { createServer as createServer3 } from "node:http";
|
|
8228
|
+
import { homedir as homedir4 } from "node:os";
|
|
8229
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
8230
|
+
import { createInterface as createInterface3 } from "node:readline";
|
|
7801
8231
|
function tokensDir2(baseDir) {
|
|
7802
|
-
return
|
|
8232
|
+
return join4(baseDir ?? join4(homedir4(), ".mtpcli"), "tokens");
|
|
7803
8233
|
}
|
|
7804
8234
|
function tokenPath2(toolName, providerId, baseDir) {
|
|
7805
|
-
return
|
|
8235
|
+
return join4(tokensDir2(baseDir), toolName, `${providerId}.json`);
|
|
7806
8236
|
}
|
|
7807
8237
|
function storeToken2(toolName, providerId, token, baseDir) {
|
|
7808
8238
|
const path2 = tokenPath2(toolName, providerId, baseDir);
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
8239
|
+
mkdirSync4(dirname3(path2), { recursive: true });
|
|
8240
|
+
writeFileSync4(path2, JSON.stringify(token, null, 2));
|
|
8241
|
+
chmodSync3(path2, 384);
|
|
7812
8242
|
return path2;
|
|
7813
8243
|
}
|
|
7814
8244
|
function loadToken2(toolName, providerId, baseDir) {
|
|
7815
8245
|
const path2 = tokenPath2(toolName, providerId, baseDir);
|
|
7816
|
-
if (!
|
|
8246
|
+
if (!existsSync4(path2))
|
|
7817
8247
|
return null;
|
|
7818
|
-
return JSON.parse(
|
|
8248
|
+
return JSON.parse(readFileSync4(path2, "utf-8"));
|
|
8249
|
+
}
|
|
8250
|
+
function deleteToken2(toolName, providerId, baseDir) {
|
|
8251
|
+
const path2 = tokenPath2(toolName, providerId, baseDir);
|
|
8252
|
+
if (!existsSync4(path2))
|
|
8253
|
+
return false;
|
|
8254
|
+
unlinkSync2(path2);
|
|
8255
|
+
return true;
|
|
7819
8256
|
}
|
|
7820
8257
|
function isTokenExpired2(token) {
|
|
7821
8258
|
if (!token.expires_at)
|
|
@@ -7824,12 +8261,96 @@ function isTokenExpired2(token) {
|
|
|
7824
8261
|
const buffer = 5 * 60 * 1000;
|
|
7825
8262
|
return exp.getTime() - buffer < Date.now();
|
|
7826
8263
|
}
|
|
7827
|
-
async function
|
|
8264
|
+
async function getAuthConfig2(toolName) {
|
|
8265
|
+
const schema = await getToolSchema2(toolName);
|
|
8266
|
+
return schema?.auth ?? null;
|
|
8267
|
+
}
|
|
8268
|
+
function getProvider2(auth, providerId) {
|
|
8269
|
+
if (providerId)
|
|
8270
|
+
return auth.providers.find((p) => p.id === providerId);
|
|
8271
|
+
return auth.providers[0];
|
|
8272
|
+
}
|
|
8273
|
+
function generatePkce2() {
|
|
8274
|
+
const bytes = randomBytes3(64);
|
|
8275
|
+
const verifier = bytes.toString("base64url").slice(0, 128);
|
|
8276
|
+
const challenge = createHash2("sha256").update(verifier).digest("base64url");
|
|
8277
|
+
return { verifier, challenge };
|
|
8278
|
+
}
|
|
8279
|
+
function waitForCallback2(port, timeoutSecs) {
|
|
8280
|
+
return new Promise((resolve, reject) => {
|
|
8281
|
+
const timer = setTimeout(() => {
|
|
8282
|
+
server.close();
|
|
8283
|
+
reject(new Error("OAuth callback timed out"));
|
|
8284
|
+
}, timeoutSecs * 1000);
|
|
8285
|
+
const server = createServer3((req, res) => {
|
|
8286
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
8287
|
+
const code = url.searchParams.get("code");
|
|
8288
|
+
const error = url.searchParams.get("error");
|
|
8289
|
+
if (code) {
|
|
8290
|
+
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>";
|
|
8291
|
+
res.writeHead(200, {
|
|
8292
|
+
"Content-Type": "text/html",
|
|
8293
|
+
"Content-Length": Buffer.byteLength(html).toString()
|
|
8294
|
+
});
|
|
8295
|
+
res.end(html);
|
|
8296
|
+
clearTimeout(timer);
|
|
8297
|
+
server.close();
|
|
8298
|
+
resolve({ code });
|
|
8299
|
+
return;
|
|
8300
|
+
}
|
|
8301
|
+
if (error) {
|
|
8302
|
+
const desc = url.searchParams.get("error_description") ?? "Unknown error";
|
|
8303
|
+
const html = `<html><body><h2>Authentication failed</h2><p>${desc}</p></body></html>`;
|
|
8304
|
+
res.writeHead(400, {
|
|
8305
|
+
"Content-Type": "text/html",
|
|
8306
|
+
"Content-Length": Buffer.byteLength(html).toString()
|
|
8307
|
+
});
|
|
8308
|
+
res.end(html);
|
|
8309
|
+
clearTimeout(timer);
|
|
8310
|
+
server.close();
|
|
8311
|
+
resolve({ error });
|
|
8312
|
+
return;
|
|
8313
|
+
}
|
|
8314
|
+
res.writeHead(404);
|
|
8315
|
+
res.end();
|
|
8316
|
+
});
|
|
8317
|
+
server.listen(port, "127.0.0.1", () => {});
|
|
8318
|
+
server.on("error", (e) => {
|
|
8319
|
+
clearTimeout(timer);
|
|
8320
|
+
reject(new Error(`failed to bind callback server: ${e.message}`));
|
|
8321
|
+
});
|
|
8322
|
+
});
|
|
8323
|
+
}
|
|
8324
|
+
async function exchangeCode2(tokenUrl, code, clientId, redirectUri, codeVerifier, resource) {
|
|
8325
|
+
const params = new URLSearchParams({
|
|
8326
|
+
grant_type: "authorization_code",
|
|
8327
|
+
code,
|
|
8328
|
+
client_id: clientId,
|
|
8329
|
+
redirect_uri: redirectUri
|
|
8330
|
+
});
|
|
8331
|
+
if (codeVerifier)
|
|
8332
|
+
params.set("code_verifier", codeVerifier);
|
|
8333
|
+
if (resource)
|
|
8334
|
+
params.set("resource", resource);
|
|
8335
|
+
const resp = await fetch(tokenUrl, {
|
|
8336
|
+
method: "POST",
|
|
8337
|
+
headers: {
|
|
8338
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
8339
|
+
Accept: "application/json"
|
|
8340
|
+
},
|
|
8341
|
+
body: params.toString()
|
|
8342
|
+
});
|
|
8343
|
+
const text = await resp.text();
|
|
8344
|
+
return parseTokenResponse2(text);
|
|
8345
|
+
}
|
|
8346
|
+
async function refreshAccessToken2(tokenUrl, refreshToken, clientId, resource) {
|
|
7828
8347
|
const params = new URLSearchParams({
|
|
7829
8348
|
grant_type: "refresh_token",
|
|
7830
8349
|
refresh_token: refreshToken,
|
|
7831
8350
|
client_id: clientId
|
|
7832
8351
|
});
|
|
8352
|
+
if (resource)
|
|
8353
|
+
params.set("resource", resource);
|
|
7833
8354
|
const resp = await fetch(tokenUrl, {
|
|
7834
8355
|
method: "POST",
|
|
7835
8356
|
headers: {
|
|
@@ -7865,6 +8386,112 @@ function parseTokenResponse2(text) {
|
|
|
7865
8386
|
created_at: new Date().toISOString()
|
|
7866
8387
|
};
|
|
7867
8388
|
}
|
|
8389
|
+
async function oauth2Login2(toolName, provider, port) {
|
|
8390
|
+
if (!provider.authorizationUrl)
|
|
8391
|
+
throw new Error("provider missing authorizationUrl");
|
|
8392
|
+
if (!provider.tokenUrl)
|
|
8393
|
+
throw new Error("provider missing tokenUrl");
|
|
8394
|
+
if (!provider.clientId)
|
|
8395
|
+
throw new Error("provider missing clientId");
|
|
8396
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
8397
|
+
const state = randomBytes3(32).toString("base64url");
|
|
8398
|
+
const params = new URLSearchParams({
|
|
8399
|
+
client_id: provider.clientId,
|
|
8400
|
+
redirect_uri: redirectUri,
|
|
8401
|
+
response_type: "code",
|
|
8402
|
+
state
|
|
8403
|
+
});
|
|
8404
|
+
if (provider.scopes?.length) {
|
|
8405
|
+
params.set("scope", provider.scopes.join(" "));
|
|
8406
|
+
}
|
|
8407
|
+
let codeVerifier;
|
|
8408
|
+
if (provider.type === "oauth2-pkce") {
|
|
8409
|
+
const pkce = generatePkce2();
|
|
8410
|
+
codeVerifier = pkce.verifier;
|
|
8411
|
+
params.set("code_challenge", pkce.challenge);
|
|
8412
|
+
params.set("code_challenge_method", "S256");
|
|
8413
|
+
}
|
|
8414
|
+
const fullUrl = `${provider.authorizationUrl}?${params.toString()}`;
|
|
8415
|
+
const display = provider.displayName ?? provider.id;
|
|
8416
|
+
process.stderr.write(`Opening browser for ${display} login...
|
|
8417
|
+
`);
|
|
8418
|
+
process.stderr.write(`If the browser doesn't open, visit:
|
|
8419
|
+
${fullUrl}
|
|
8420
|
+
|
|
8421
|
+
`);
|
|
8422
|
+
open_default(fullUrl).catch(() => {});
|
|
8423
|
+
process.stderr.write(`Waiting for authentication callback on port ${port}...
|
|
8424
|
+
`);
|
|
8425
|
+
const result = await waitForCallback2(port, 120);
|
|
8426
|
+
if (result.error)
|
|
8427
|
+
throw new Error(`OAuth error: ${result.error}`);
|
|
8428
|
+
if (!result.code)
|
|
8429
|
+
throw new Error("no authorization code received (timed out?)");
|
|
8430
|
+
process.stderr.write(`Exchanging authorization code for tokens...
|
|
8431
|
+
`);
|
|
8432
|
+
const token = await exchangeCode2(provider.tokenUrl, result.code, provider.clientId, redirectUri, codeVerifier);
|
|
8433
|
+
token.provider_id = provider.id;
|
|
8434
|
+
storeToken2(toolName, provider.id, token);
|
|
8435
|
+
process.stderr.write(`Authenticated with ${display}
|
|
8436
|
+
`);
|
|
8437
|
+
return token;
|
|
8438
|
+
}
|
|
8439
|
+
function apiKeyLogin2(toolName, provider, tokenValue) {
|
|
8440
|
+
return new Promise((resolve, reject) => {
|
|
8441
|
+
const finish = (value) => {
|
|
8442
|
+
if (!value) {
|
|
8443
|
+
reject(new Error("no token provided"));
|
|
8444
|
+
return;
|
|
8445
|
+
}
|
|
8446
|
+
const token = {
|
|
8447
|
+
access_token: value,
|
|
8448
|
+
token_type: provider.type,
|
|
8449
|
+
refresh_token: undefined,
|
|
8450
|
+
expires_at: undefined,
|
|
8451
|
+
scopes: undefined,
|
|
8452
|
+
provider_id: provider.id,
|
|
8453
|
+
created_at: new Date().toISOString()
|
|
8454
|
+
};
|
|
8455
|
+
storeToken2(toolName, provider.id, token);
|
|
8456
|
+
const display = provider.displayName ?? provider.id;
|
|
8457
|
+
process.stderr.write(`Token stored for ${display}
|
|
8458
|
+
`);
|
|
8459
|
+
resolve(token);
|
|
8460
|
+
};
|
|
8461
|
+
if (tokenValue) {
|
|
8462
|
+
finish(tokenValue);
|
|
8463
|
+
return;
|
|
8464
|
+
}
|
|
8465
|
+
if (provider.registrationUrl) {
|
|
8466
|
+
process.stderr.write(`Get your API key at: ${provider.registrationUrl}
|
|
8467
|
+
`);
|
|
8468
|
+
}
|
|
8469
|
+
if (provider.instructions) {
|
|
8470
|
+
process.stderr.write(`${provider.instructions}
|
|
8471
|
+
`);
|
|
8472
|
+
}
|
|
8473
|
+
process.stderr.write("Enter your token/API key: ");
|
|
8474
|
+
const rl = createInterface3({ input: process.stdin, output: process.stderr });
|
|
8475
|
+
rl.question("", (answer) => {
|
|
8476
|
+
rl.close();
|
|
8477
|
+
finish(answer.trim());
|
|
8478
|
+
});
|
|
8479
|
+
});
|
|
8480
|
+
}
|
|
8481
|
+
async function login2(toolName, provider, token, port = 8914) {
|
|
8482
|
+
switch (provider.type) {
|
|
8483
|
+
case "oauth2":
|
|
8484
|
+
case "oauth2-pkce":
|
|
8485
|
+
if (token)
|
|
8486
|
+
return apiKeyLogin2(toolName, provider, token);
|
|
8487
|
+
return oauth2Login2(toolName, provider, port);
|
|
8488
|
+
case "api-key":
|
|
8489
|
+
case "bearer":
|
|
8490
|
+
return apiKeyLogin2(toolName, provider, token);
|
|
8491
|
+
default:
|
|
8492
|
+
throw new Error(`unknown auth type: ${provider.type}`);
|
|
8493
|
+
}
|
|
8494
|
+
}
|
|
7868
8495
|
async function ensureValidToken2(toolName, auth) {
|
|
7869
8496
|
const provider = auth.providers[0];
|
|
7870
8497
|
if (!provider)
|
|
@@ -7898,7 +8525,121 @@ async function getEnvDict2(toolName, auth) {
|
|
|
7898
8525
|
return {};
|
|
7899
8526
|
return { [auth.envVar]: token };
|
|
7900
8527
|
}
|
|
8528
|
+
async function getEnvExport2(toolName, auth) {
|
|
8529
|
+
const env = await getEnvDict2(toolName, auth);
|
|
8530
|
+
if (Object.keys(env).length === 0) {
|
|
8531
|
+
return "# No token available. Run: mtpcli auth login <tool>";
|
|
8532
|
+
}
|
|
8533
|
+
return Object.entries(env).map(([key, value]) => {
|
|
8534
|
+
const escaped = value.replace(/'/g, "'\\''");
|
|
8535
|
+
return `export ${key}='${escaped}'`;
|
|
8536
|
+
}).join(`
|
|
8537
|
+
`);
|
|
8538
|
+
}
|
|
8539
|
+
async function authStatus2(toolName, auth) {
|
|
8540
|
+
const providers = [];
|
|
8541
|
+
for (const provider of auth.providers) {
|
|
8542
|
+
const token = loadToken2(toolName, provider.id);
|
|
8543
|
+
const status = {
|
|
8544
|
+
id: provider.id,
|
|
8545
|
+
type: provider.type,
|
|
8546
|
+
display_name: provider.displayName ?? provider.id,
|
|
8547
|
+
authenticated: token !== null
|
|
8548
|
+
};
|
|
8549
|
+
if (token) {
|
|
8550
|
+
status.expired = isTokenExpired2(token);
|
|
8551
|
+
status.has_refresh = !!token.refresh_token;
|
|
8552
|
+
if (token.expires_at)
|
|
8553
|
+
status.expires_at = token.expires_at;
|
|
8554
|
+
if (token.scopes)
|
|
8555
|
+
status.scopes = token.scopes;
|
|
8556
|
+
}
|
|
8557
|
+
providers.push(status);
|
|
8558
|
+
}
|
|
8559
|
+
return {
|
|
8560
|
+
tool: toolName,
|
|
8561
|
+
env_var: auth.envVar,
|
|
8562
|
+
required: auth.required,
|
|
8563
|
+
providers
|
|
8564
|
+
};
|
|
8565
|
+
}
|
|
8566
|
+
async function runLogin2(toolName, providerId, token) {
|
|
8567
|
+
const auth = await getAuthConfig2(toolName);
|
|
8568
|
+
if (!auth)
|
|
8569
|
+
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
8570
|
+
const provider = getProvider2(auth, providerId);
|
|
8571
|
+
if (!provider)
|
|
8572
|
+
throw new Error("no matching auth provider found");
|
|
8573
|
+
await login2(toolName, provider, token, 8914);
|
|
8574
|
+
}
|
|
8575
|
+
async function runLogout2(toolName) {
|
|
8576
|
+
const auth = await getAuthConfig2(toolName);
|
|
8577
|
+
if (!auth)
|
|
8578
|
+
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
8579
|
+
let deleted = false;
|
|
8580
|
+
for (const provider of auth.providers) {
|
|
8581
|
+
if (deleteToken2(toolName, provider.id)) {
|
|
8582
|
+
const display = provider.displayName ?? provider.id;
|
|
8583
|
+
process.stderr.write(`Logged out from ${display}
|
|
8584
|
+
`);
|
|
8585
|
+
deleted = true;
|
|
8586
|
+
}
|
|
8587
|
+
}
|
|
8588
|
+
if (!deleted)
|
|
8589
|
+
process.stderr.write(`No tokens found for '${toolName}'
|
|
8590
|
+
`);
|
|
8591
|
+
}
|
|
8592
|
+
async function runStatus2(toolName) {
|
|
8593
|
+
const auth = await getAuthConfig2(toolName);
|
|
8594
|
+
if (!auth)
|
|
8595
|
+
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
8596
|
+
const status = await authStatus2(toolName, auth);
|
|
8597
|
+
console.log(JSON.stringify(status, null, 2));
|
|
8598
|
+
}
|
|
8599
|
+
async function runToken2(toolName) {
|
|
8600
|
+
const auth = await getAuthConfig2(toolName);
|
|
8601
|
+
if (!auth)
|
|
8602
|
+
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
8603
|
+
const t = await ensureValidToken2(toolName, auth);
|
|
8604
|
+
if (!t)
|
|
8605
|
+
throw new Error(`no valid token for '${toolName}'. Run: mtpcli auth login ${toolName}`);
|
|
8606
|
+
console.log(t);
|
|
8607
|
+
}
|
|
8608
|
+
async function runEnv2(toolName) {
|
|
8609
|
+
const auth = await getAuthConfig2(toolName);
|
|
8610
|
+
if (!auth)
|
|
8611
|
+
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
8612
|
+
console.log(await getEnvExport2(toolName, auth));
|
|
8613
|
+
}
|
|
8614
|
+
async function runRefresh2(toolName) {
|
|
8615
|
+
const authConfig = await getAuthConfig2(toolName);
|
|
8616
|
+
if (!authConfig)
|
|
8617
|
+
throw new Error(`tool '${toolName}' does not declare auth`);
|
|
8618
|
+
const provider = authConfig.providers[0];
|
|
8619
|
+
if (!provider)
|
|
8620
|
+
throw new Error("no auth provider configured");
|
|
8621
|
+
const token = loadToken2(toolName, provider.id);
|
|
8622
|
+
if (!token) {
|
|
8623
|
+
throw new Error(`no stored token for '${toolName}'. Run: mtpcli auth login ${toolName}`);
|
|
8624
|
+
}
|
|
8625
|
+
if (!token.refresh_token) {
|
|
8626
|
+
throw new Error(`token for '${toolName}' has no refresh_token (provider type: ${provider.type})`);
|
|
8627
|
+
}
|
|
8628
|
+
if (!provider.tokenUrl || !provider.clientId) {
|
|
8629
|
+
throw new Error("provider missing tokenUrl or clientId for refresh");
|
|
8630
|
+
}
|
|
8631
|
+
const newToken = await refreshAccessToken2(provider.tokenUrl, token.refresh_token, provider.clientId);
|
|
8632
|
+
if (!newToken.refresh_token) {
|
|
8633
|
+
newToken.refresh_token = token.refresh_token;
|
|
8634
|
+
}
|
|
8635
|
+
newToken.provider_id = provider.id;
|
|
8636
|
+
storeToken2(toolName, provider.id, newToken);
|
|
8637
|
+
const display = provider.displayName ?? provider.id;
|
|
8638
|
+
process.stderr.write(`Token refreshed for ${display}
|
|
8639
|
+
`);
|
|
8640
|
+
}
|
|
7901
8641
|
var init_auth2 = __esm(() => {
|
|
8642
|
+
init_open();
|
|
7902
8643
|
init_search2();
|
|
7903
8644
|
});
|
|
7904
8645
|
|
|
@@ -7963,7 +8704,7 @@ __export(exports_serve, {
|
|
|
7963
8704
|
argToJsonSchema: () => argToJsonSchema
|
|
7964
8705
|
});
|
|
7965
8706
|
import { execFile as execFile9, spawn } from "node:child_process";
|
|
7966
|
-
import { createInterface as
|
|
8707
|
+
import { createInterface as createInterface4 } from "node:readline";
|
|
7967
8708
|
import { promisify as promisify9 } from "node:util";
|
|
7968
8709
|
function argToJsonSchema(arg) {
|
|
7969
8710
|
const schema = {};
|
|
@@ -8139,81 +8880,476 @@ async function handleRequest(state, req) {
|
|
|
8139
8880
|
let authEnv;
|
|
8140
8881
|
const authConfig = state.authConfigs.get(entry.cliTool);
|
|
8141
8882
|
if (authConfig) {
|
|
8142
|
-
const env = await
|
|
8883
|
+
const env = await getEnvDict(entry.cliTool, authConfig);
|
|
8143
8884
|
if (Object.keys(env).length > 0)
|
|
8144
8885
|
authEnv = env;
|
|
8145
8886
|
}
|
|
8146
8887
|
const result = await invokeCliTool(entry.cliTool, entry.cmdName, arguments_, entry.cmd, authEnv);
|
|
8147
8888
|
return jsonRpcSuccess(id, result);
|
|
8148
8889
|
}
|
|
8149
|
-
default:
|
|
8150
|
-
if (id !== undefined) {
|
|
8151
|
-
return jsonRpcError(id, -32601, `Method not found: ${req.method}`);
|
|
8152
|
-
}
|
|
8153
|
-
return null;
|
|
8890
|
+
default:
|
|
8891
|
+
if (id !== undefined) {
|
|
8892
|
+
return jsonRpcError(id, -32601, `Method not found: ${req.method}`);
|
|
8893
|
+
}
|
|
8894
|
+
return null;
|
|
8895
|
+
}
|
|
8896
|
+
}
|
|
8897
|
+
async function run(toolNames) {
|
|
8898
|
+
const allTools = [];
|
|
8899
|
+
const allSchemas = new Map;
|
|
8900
|
+
const allAuth = new Map;
|
|
8901
|
+
for (const name of toolNames) {
|
|
8902
|
+
const schema = await getToolSchema2(name);
|
|
8903
|
+
if (!schema)
|
|
8904
|
+
throw new Error(`could not get --describe from '${name}'`);
|
|
8905
|
+
if (schema.auth)
|
|
8906
|
+
allAuth.set(name, schema.auth);
|
|
8907
|
+
for (const cmd of schema.commands) {
|
|
8908
|
+
const mcpTool = commandToMcpTool(schema.name, cmd);
|
|
8909
|
+
allSchemas.set(mcpTool.name, {
|
|
8910
|
+
cliTool: name,
|
|
8911
|
+
cmdName: cmd.name,
|
|
8912
|
+
cmd
|
|
8913
|
+
});
|
|
8914
|
+
allTools.push(mcpTool);
|
|
8915
|
+
}
|
|
8916
|
+
}
|
|
8917
|
+
if (allTools.length === 0)
|
|
8918
|
+
throw new Error("no tools loaded");
|
|
8919
|
+
process.stderr.write(`mtpcli serve: serving ${allTools.length} tool(s) from ${toolNames.length} CLI tool(s)
|
|
8920
|
+
`);
|
|
8921
|
+
for (const t of allTools) {
|
|
8922
|
+
process.stderr.write(` - ${t.name}: ${t.description}
|
|
8923
|
+
`);
|
|
8924
|
+
}
|
|
8925
|
+
const state = {
|
|
8926
|
+
tools: allTools,
|
|
8927
|
+
schemas: allSchemas,
|
|
8928
|
+
authConfigs: allAuth
|
|
8929
|
+
};
|
|
8930
|
+
const rl = createInterface4({ input: process.stdin, crlfDelay: Infinity });
|
|
8931
|
+
for await (const line of rl) {
|
|
8932
|
+
const trimmed = line.trim();
|
|
8933
|
+
if (!trimmed)
|
|
8934
|
+
continue;
|
|
8935
|
+
let req;
|
|
8936
|
+
try {
|
|
8937
|
+
req = JsonRpcRequestSchema2.parse(JSON.parse(trimmed));
|
|
8938
|
+
} catch (e) {
|
|
8939
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
8940
|
+
process.stderr.write(`error reading request: ${msg}
|
|
8941
|
+
`);
|
|
8942
|
+
writeResponse(process.stdout, jsonRpcError(null, -32700, `Parse error: ${msg}`));
|
|
8943
|
+
continue;
|
|
8944
|
+
}
|
|
8945
|
+
const resp = await handleRequest(state, req);
|
|
8946
|
+
if (resp) {
|
|
8947
|
+
writeResponse(process.stdout, resp);
|
|
8948
|
+
}
|
|
8949
|
+
}
|
|
8950
|
+
}
|
|
8951
|
+
var execFileAsync7, VERSION = "0.1.0";
|
|
8952
|
+
var init_serve = __esm(() => {
|
|
8953
|
+
init_auth();
|
|
8954
|
+
init_mcp();
|
|
8955
|
+
init_models();
|
|
8956
|
+
init_search2();
|
|
8957
|
+
execFileAsync7 = promisify9(execFile9);
|
|
8958
|
+
});
|
|
8959
|
+
|
|
8960
|
+
// src/mcp-oauth.ts
|
|
8961
|
+
var exports_mcp_oauth2 = {};
|
|
8962
|
+
__export(exports_mcp_oauth2, {
|
|
8963
|
+
storeClientRegistration: () => storeClientRegistration2,
|
|
8964
|
+
serverStorageKey: () => serverStorageKey2,
|
|
8965
|
+
registerClient: () => registerClient2,
|
|
8966
|
+
promptForClientId: () => promptForClientId2,
|
|
8967
|
+
parseWwwAuthenticate: () => parseWwwAuthenticate2,
|
|
8968
|
+
mcpOAuthFlow: () => mcpOAuthFlow2,
|
|
8969
|
+
mcpAuthToken: () => mcpAuthToken2,
|
|
8970
|
+
mcpAuthStatus: () => mcpAuthStatus2,
|
|
8971
|
+
mcpAuthRefresh: () => mcpAuthRefresh2,
|
|
8972
|
+
mcpAuthLogout: () => mcpAuthLogout2,
|
|
8973
|
+
loadOrRefreshMcpToken: () => loadOrRefreshMcpToken2,
|
|
8974
|
+
loadClientRegistration: () => loadClientRegistration2,
|
|
8975
|
+
fetchResourceMetadata: () => fetchResourceMetadata2,
|
|
8976
|
+
fetchAuthServerMetadata: () => fetchAuthServerMetadata2
|
|
8977
|
+
});
|
|
8978
|
+
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
8979
|
+
import {
|
|
8980
|
+
existsSync as existsSync5,
|
|
8981
|
+
mkdirSync as mkdirSync5,
|
|
8982
|
+
readFileSync as readFileSync5,
|
|
8983
|
+
writeFileSync as writeFileSync5,
|
|
8984
|
+
chmodSync as chmodSync4
|
|
8985
|
+
} from "node:fs";
|
|
8986
|
+
import { createServer as createServer4 } from "node:http";
|
|
8987
|
+
import { homedir as homedir5 } from "node:os";
|
|
8988
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
8989
|
+
import { createInterface as createInterface5 } from "node:readline";
|
|
8990
|
+
function serverStorageKey2(url) {
|
|
8991
|
+
const u = new URL(url);
|
|
8992
|
+
return `mcp_${u.host}`;
|
|
8993
|
+
}
|
|
8994
|
+
function parseWwwAuthenticate2(header) {
|
|
8995
|
+
const result = {};
|
|
8996
|
+
const params = header.replace(/^Bearer\s+/i, "");
|
|
8997
|
+
const re = /(\w+)=(?:"([^"]*)"|(\S+))/g;
|
|
8998
|
+
let match;
|
|
8999
|
+
while ((match = re.exec(params)) !== null) {
|
|
9000
|
+
const key = match[1];
|
|
9001
|
+
const val = match[2] ?? match[3];
|
|
9002
|
+
if (key === "resource_metadata")
|
|
9003
|
+
result.resourceMetadata = val;
|
|
9004
|
+
if (key === "scope")
|
|
9005
|
+
result.scope = val;
|
|
9006
|
+
}
|
|
9007
|
+
return result;
|
|
9008
|
+
}
|
|
9009
|
+
async function fetchResourceMetadata2(url) {
|
|
9010
|
+
const resp = await fetch(url);
|
|
9011
|
+
if (!resp.ok) {
|
|
9012
|
+
throw new Error(`failed to fetch resource metadata from ${url}: ${resp.status}`);
|
|
9013
|
+
}
|
|
9014
|
+
return resp.json();
|
|
9015
|
+
}
|
|
9016
|
+
async function fetchAuthServerMetadata2(serverUrl, wwwAuth) {
|
|
9017
|
+
if (wwwAuth) {
|
|
9018
|
+
const parsed = parseWwwAuthenticate2(wwwAuth);
|
|
9019
|
+
if (parsed.resourceMetadata) {
|
|
9020
|
+
try {
|
|
9021
|
+
const resource = await fetchResourceMetadata2(parsed.resourceMetadata);
|
|
9022
|
+
if (resource.authorization_servers?.length) {
|
|
9023
|
+
const asUrl = resource.authorization_servers[0];
|
|
9024
|
+
const wellKnown = `${asUrl}/.well-known/oauth-authorization-server`;
|
|
9025
|
+
const resp = await fetch(wellKnown);
|
|
9026
|
+
if (resp.ok) {
|
|
9027
|
+
return resp.json();
|
|
9028
|
+
}
|
|
9029
|
+
}
|
|
9030
|
+
} catch {}
|
|
9031
|
+
}
|
|
9032
|
+
}
|
|
9033
|
+
const origin = new URL(serverUrl).origin;
|
|
9034
|
+
const oauthUrl = `${origin}/.well-known/oauth-authorization-server`;
|
|
9035
|
+
try {
|
|
9036
|
+
const resp = await fetch(oauthUrl);
|
|
9037
|
+
if (resp.ok) {
|
|
9038
|
+
return resp.json();
|
|
9039
|
+
}
|
|
9040
|
+
} catch {}
|
|
9041
|
+
const oidcUrl = `${origin}/.well-known/openid-configuration`;
|
|
9042
|
+
try {
|
|
9043
|
+
const resp = await fetch(oidcUrl);
|
|
9044
|
+
if (resp.ok) {
|
|
9045
|
+
return resp.json();
|
|
9046
|
+
}
|
|
9047
|
+
} catch {}
|
|
9048
|
+
throw new Error(`could not discover OAuth authorization server for ${serverUrl}`);
|
|
9049
|
+
}
|
|
9050
|
+
async function registerClient2(metadata, redirectUri) {
|
|
9051
|
+
if (!metadata.registration_endpoint) {
|
|
9052
|
+
throw new Error("no registration_endpoint in auth server metadata");
|
|
9053
|
+
}
|
|
9054
|
+
const body = {
|
|
9055
|
+
client_name: "mtpcli",
|
|
9056
|
+
redirect_uris: [redirectUri],
|
|
9057
|
+
grant_types: ["authorization_code"],
|
|
9058
|
+
response_types: ["code"],
|
|
9059
|
+
token_endpoint_auth_method: "none"
|
|
9060
|
+
};
|
|
9061
|
+
const resp = await fetch(metadata.registration_endpoint, {
|
|
9062
|
+
method: "POST",
|
|
9063
|
+
headers: {
|
|
9064
|
+
"Content-Type": "application/json",
|
|
9065
|
+
Accept: "application/json"
|
|
9066
|
+
},
|
|
9067
|
+
body: JSON.stringify(body)
|
|
9068
|
+
});
|
|
9069
|
+
if (!resp.ok) {
|
|
9070
|
+
const text = await resp.text();
|
|
9071
|
+
throw new Error(`dynamic client registration failed (${resp.status}): ${text}`);
|
|
9072
|
+
}
|
|
9073
|
+
const result = await resp.json();
|
|
9074
|
+
const clientId = result.client_id;
|
|
9075
|
+
if (!clientId)
|
|
9076
|
+
throw new Error("no client_id in registration response");
|
|
9077
|
+
return {
|
|
9078
|
+
clientId,
|
|
9079
|
+
clientSecret: result.client_secret,
|
|
9080
|
+
redirectUri,
|
|
9081
|
+
registeredAt: Date.now()
|
|
9082
|
+
};
|
|
9083
|
+
}
|
|
9084
|
+
function promptForClientId2(serverUrl) {
|
|
9085
|
+
return new Promise((resolve, reject) => {
|
|
9086
|
+
process.stderr.write(`
|
|
9087
|
+
No dynamic registration available for ${serverUrl}.
|
|
9088
|
+
` + `You need to register a client manually and provide the client_id.
|
|
9089
|
+
` + `Enter client_id: `);
|
|
9090
|
+
const rl = createInterface5({ input: process.stdin, output: process.stderr });
|
|
9091
|
+
rl.question("", (answer) => {
|
|
9092
|
+
rl.close();
|
|
9093
|
+
const trimmed = answer.trim();
|
|
9094
|
+
if (!trimmed) {
|
|
9095
|
+
reject(new Error("no client_id provided"));
|
|
9096
|
+
return;
|
|
9097
|
+
}
|
|
9098
|
+
resolve(trimmed);
|
|
9099
|
+
});
|
|
9100
|
+
});
|
|
9101
|
+
}
|
|
9102
|
+
function clientStorePath2() {
|
|
9103
|
+
return join5(homedir5(), ".mtpcli", "mcp-oauth-clients.json");
|
|
9104
|
+
}
|
|
9105
|
+
function loadClientStore2() {
|
|
9106
|
+
const path2 = clientStorePath2();
|
|
9107
|
+
if (!existsSync5(path2))
|
|
9108
|
+
return {};
|
|
9109
|
+
try {
|
|
9110
|
+
return JSON.parse(readFileSync5(path2, "utf-8"));
|
|
9111
|
+
} catch {
|
|
9112
|
+
return {};
|
|
9113
|
+
}
|
|
9114
|
+
}
|
|
9115
|
+
function saveClientStore2(store) {
|
|
9116
|
+
const path2 = clientStorePath2();
|
|
9117
|
+
mkdirSync5(dirname4(path2), { recursive: true });
|
|
9118
|
+
writeFileSync5(path2, JSON.stringify(store, null, 2));
|
|
9119
|
+
chmodSync4(path2, 384);
|
|
9120
|
+
}
|
|
9121
|
+
function loadClientRegistration2(origin) {
|
|
9122
|
+
const store = loadClientStore2();
|
|
9123
|
+
return store[origin] ?? null;
|
|
9124
|
+
}
|
|
9125
|
+
function storeClientRegistration2(origin, client, metadata) {
|
|
9126
|
+
const store = loadClientStore2();
|
|
9127
|
+
store[origin] = { client, metadata };
|
|
9128
|
+
saveClientStore2(store);
|
|
9129
|
+
}
|
|
9130
|
+
function startCallbackServer2() {
|
|
9131
|
+
return new Promise((resolve, reject) => {
|
|
9132
|
+
let codeResolve;
|
|
9133
|
+
let codeReject;
|
|
9134
|
+
const codePromise = new Promise((res, rej) => {
|
|
9135
|
+
codeResolve = res;
|
|
9136
|
+
codeReject = rej;
|
|
9137
|
+
});
|
|
9138
|
+
const server = createServer4((req, res) => {
|
|
9139
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1`);
|
|
9140
|
+
const code = url.searchParams.get("code");
|
|
9141
|
+
const error = url.searchParams.get("error");
|
|
9142
|
+
if (code) {
|
|
9143
|
+
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>";
|
|
9144
|
+
res.writeHead(200, {
|
|
9145
|
+
"Content-Type": "text/html",
|
|
9146
|
+
"Content-Length": Buffer.byteLength(html).toString()
|
|
9147
|
+
});
|
|
9148
|
+
res.end(html);
|
|
9149
|
+
server.close();
|
|
9150
|
+
codeResolve(code);
|
|
9151
|
+
return;
|
|
9152
|
+
}
|
|
9153
|
+
if (error) {
|
|
9154
|
+
const desc = url.searchParams.get("error_description") ?? "Unknown error";
|
|
9155
|
+
const html = `<html><body><h2>Authentication failed</h2><p>${desc}</p></body></html>`;
|
|
9156
|
+
res.writeHead(400, {
|
|
9157
|
+
"Content-Type": "text/html",
|
|
9158
|
+
"Content-Length": Buffer.byteLength(html).toString()
|
|
9159
|
+
});
|
|
9160
|
+
res.end(html);
|
|
9161
|
+
server.close();
|
|
9162
|
+
codeReject(new Error(`OAuth error: ${error} - ${desc}`));
|
|
9163
|
+
return;
|
|
9164
|
+
}
|
|
9165
|
+
res.writeHead(404);
|
|
9166
|
+
res.end();
|
|
9167
|
+
});
|
|
9168
|
+
server.listen(0, "127.0.0.1", () => {
|
|
9169
|
+
const addr = server.address();
|
|
9170
|
+
if (!addr || typeof addr === "string") {
|
|
9171
|
+
reject(new Error("failed to get callback server address"));
|
|
9172
|
+
return;
|
|
9173
|
+
}
|
|
9174
|
+
resolve({
|
|
9175
|
+
port: addr.port,
|
|
9176
|
+
waitForCode: () => {
|
|
9177
|
+
const timer = setTimeout(() => {
|
|
9178
|
+
server.close();
|
|
9179
|
+
codeReject(new Error("OAuth callback timed out (120s)"));
|
|
9180
|
+
}, 120000);
|
|
9181
|
+
return codePromise.finally(() => clearTimeout(timer));
|
|
9182
|
+
},
|
|
9183
|
+
server
|
|
9184
|
+
});
|
|
9185
|
+
});
|
|
9186
|
+
server.on("error", (e) => {
|
|
9187
|
+
reject(new Error(`failed to bind callback server: ${e.message}`));
|
|
9188
|
+
});
|
|
9189
|
+
});
|
|
9190
|
+
}
|
|
9191
|
+
async function mcpOAuthFlow2(serverUrl, wwwAuth, clientId) {
|
|
9192
|
+
const origin = new URL(serverUrl).origin;
|
|
9193
|
+
const storageKey = serverStorageKey2(serverUrl);
|
|
9194
|
+
const wwwParsed = parseWwwAuthenticate2(wwwAuth);
|
|
9195
|
+
process.stderr.write(`Discovering OAuth authorization server...
|
|
9196
|
+
`);
|
|
9197
|
+
const metadata = await fetchAuthServerMetadata2(serverUrl, wwwAuth);
|
|
9198
|
+
const challengeMethods = metadata.code_challenge_methods_supported;
|
|
9199
|
+
if (challengeMethods && !challengeMethods.includes("S256")) {
|
|
9200
|
+
throw new Error("authorization server does not support S256 PKCE (required)");
|
|
9201
|
+
}
|
|
9202
|
+
const callback = await startCallbackServer2();
|
|
9203
|
+
const redirectUri = `http://127.0.0.1:${callback.port}/callback`;
|
|
9204
|
+
let resolvedClientId = clientId;
|
|
9205
|
+
let registration;
|
|
9206
|
+
if (!resolvedClientId) {
|
|
9207
|
+
const cached = loadClientRegistration2(origin);
|
|
9208
|
+
if (cached) {
|
|
9209
|
+
resolvedClientId = cached.client.clientId;
|
|
9210
|
+
registration = cached.client;
|
|
9211
|
+
process.stderr.write(`Using cached client registration for ${origin}
|
|
9212
|
+
`);
|
|
9213
|
+
}
|
|
8154
9214
|
}
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
const mcpTool = commandToMcpTool(schema.name, cmd);
|
|
8168
|
-
allSchemas.set(mcpTool.name, {
|
|
8169
|
-
cliTool: name,
|
|
8170
|
-
cmdName: cmd.name,
|
|
8171
|
-
cmd
|
|
8172
|
-
});
|
|
8173
|
-
allTools.push(mcpTool);
|
|
9215
|
+
if (!resolvedClientId) {
|
|
9216
|
+
if (metadata.registration_endpoint) {
|
|
9217
|
+
process.stderr.write(`Registering client dynamically...
|
|
9218
|
+
`);
|
|
9219
|
+
registration = await registerClient2(metadata, redirectUri);
|
|
9220
|
+
resolvedClientId = registration.clientId;
|
|
9221
|
+
storeClientRegistration2(origin, registration, metadata);
|
|
9222
|
+
} else {
|
|
9223
|
+
callback.server.close();
|
|
9224
|
+
resolvedClientId = await promptForClientId2(serverUrl);
|
|
9225
|
+
const newCallback = await startCallbackServer2();
|
|
9226
|
+
return doOAuthLogin2(metadata, resolvedClientId, `http://127.0.0.1:${newCallback.port}/callback`, newCallback, wwwParsed.scope, storageKey, origin);
|
|
8174
9227
|
}
|
|
8175
9228
|
}
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
9229
|
+
return doOAuthLogin2(metadata, resolvedClientId, redirectUri, callback, wwwParsed.scope, storageKey, origin);
|
|
9230
|
+
}
|
|
9231
|
+
async function doOAuthLogin2(metadata, clientId, redirectUri, callback, scope, storageKey, origin) {
|
|
9232
|
+
const state = randomBytes4(32).toString("base64url");
|
|
9233
|
+
const pkce = generatePkce();
|
|
9234
|
+
const scopes = scope ?? metadata.scopes_supported?.join(" ") ?? undefined;
|
|
9235
|
+
const params = new URLSearchParams({
|
|
9236
|
+
client_id: clientId,
|
|
9237
|
+
redirect_uri: redirectUri,
|
|
9238
|
+
response_type: "code",
|
|
9239
|
+
state,
|
|
9240
|
+
code_challenge: pkce.challenge,
|
|
9241
|
+
code_challenge_method: "S256"
|
|
9242
|
+
});
|
|
9243
|
+
if (scopes)
|
|
9244
|
+
params.set("scope", scopes);
|
|
9245
|
+
const authUrl = `${metadata.authorization_endpoint}?${params.toString()}`;
|
|
9246
|
+
process.stderr.write(`Opening browser for OAuth login...
|
|
8179
9247
|
`);
|
|
8180
|
-
|
|
8181
|
-
|
|
9248
|
+
process.stderr.write(`If the browser doesn't open, visit:
|
|
9249
|
+
${authUrl}
|
|
9250
|
+
|
|
8182
9251
|
`);
|
|
8183
|
-
}
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
9252
|
+
open_default(authUrl).catch(() => {});
|
|
9253
|
+
process.stderr.write(`Waiting for authentication callback on port ${callback.port}...
|
|
9254
|
+
`);
|
|
9255
|
+
const code = await callback.waitForCode();
|
|
9256
|
+
process.stderr.write(`Exchanging authorization code for tokens...
|
|
9257
|
+
`);
|
|
9258
|
+
const token = await exchangeCode(metadata.token_endpoint, code, clientId, redirectUri, pkce.verifier);
|
|
9259
|
+
token.provider_id = "mcp-oauth";
|
|
9260
|
+
storeToken(storageKey, "mcp-oauth", token);
|
|
9261
|
+
storeClientRegistration2(origin, {
|
|
9262
|
+
clientId,
|
|
9263
|
+
redirectUri,
|
|
9264
|
+
registeredAt: Date.now()
|
|
9265
|
+
}, metadata);
|
|
9266
|
+
process.stderr.write(`Authenticated with ${origin}
|
|
9267
|
+
`);
|
|
9268
|
+
return token.access_token;
|
|
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
|
|
8188
9278
|
};
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
if (
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
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}
|
|
8200
9320
|
`);
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
9321
|
+
}
|
|
9322
|
+
async function loadOrRefreshMcpToken2(serverUrl) {
|
|
9323
|
+
const storageKey = serverStorageKey2(serverUrl);
|
|
9324
|
+
const token = loadToken(storageKey, "mcp-oauth");
|
|
9325
|
+
if (!token)
|
|
9326
|
+
return null;
|
|
9327
|
+
if (!isTokenExpired(token)) {
|
|
9328
|
+
return token.access_token;
|
|
9329
|
+
}
|
|
9330
|
+
if (token.refresh_token) {
|
|
9331
|
+
const origin = new URL(serverUrl).origin;
|
|
9332
|
+
const cached = loadClientRegistration2(origin);
|
|
9333
|
+
if (cached) {
|
|
9334
|
+
try {
|
|
9335
|
+
const newToken = await refreshAccessToken(cached.metadata.token_endpoint, token.refresh_token, cached.client.clientId);
|
|
9336
|
+
if (!newToken.refresh_token) {
|
|
9337
|
+
newToken.refresh_token = token.refresh_token;
|
|
9338
|
+
}
|
|
9339
|
+
newToken.provider_id = "mcp-oauth";
|
|
9340
|
+
storeToken(storageKey, "mcp-oauth", newToken);
|
|
9341
|
+
return newToken.access_token;
|
|
9342
|
+
} catch (e) {
|
|
9343
|
+
process.stderr.write(`warning: token refresh failed: ${e instanceof Error ? e.message : e}
|
|
9344
|
+
`);
|
|
9345
|
+
}
|
|
8207
9346
|
}
|
|
8208
9347
|
}
|
|
9348
|
+
return null;
|
|
8209
9349
|
}
|
|
8210
|
-
var
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
init_mcp();
|
|
8214
|
-
init_models();
|
|
8215
|
-
init_search2();
|
|
8216
|
-
execFileAsync7 = promisify9(execFile9);
|
|
9350
|
+
var init_mcp_oauth2 = __esm(() => {
|
|
9351
|
+
init_open();
|
|
9352
|
+
init_auth();
|
|
8217
9353
|
});
|
|
8218
9354
|
|
|
8219
9355
|
// src/wrap.ts
|
|
@@ -8221,12 +9357,14 @@ var exports_wrap = {};
|
|
|
8221
9357
|
__export(exports_wrap, {
|
|
8222
9358
|
run: () => run2,
|
|
8223
9359
|
parseToolArgs: () => parseToolArgs,
|
|
9360
|
+
parseHeaders: () => parseHeaders,
|
|
8224
9361
|
mcpToolToCommand: () => mcpToolToCommand,
|
|
8225
9362
|
jsonSchemaToArg: () => jsonSchemaToArg,
|
|
8226
|
-
McpClient: () => McpClient
|
|
9363
|
+
McpClient: () => McpClient,
|
|
9364
|
+
HttpMcpClient: () => HttpMcpClient
|
|
8227
9365
|
});
|
|
8228
9366
|
import { spawn as spawn2 } from "node:child_process";
|
|
8229
|
-
import { createInterface as
|
|
9367
|
+
import { createInterface as createInterface6 } from "node:readline";
|
|
8230
9368
|
function jsonSchemaToArg(name, prop, required) {
|
|
8231
9369
|
let argType = "string";
|
|
8232
9370
|
let values;
|
|
@@ -8378,7 +9516,7 @@ class McpClient {
|
|
|
8378
9516
|
if (!child.stdout || !child.stdin) {
|
|
8379
9517
|
throw new Error("failed to start MCP server");
|
|
8380
9518
|
}
|
|
8381
|
-
const rl =
|
|
9519
|
+
const rl = createInterface6({ input: child.stdout, crlfDelay: Infinity });
|
|
8382
9520
|
const client = new McpClient(child, rl);
|
|
8383
9521
|
client.sendRequest("initialize", {
|
|
8384
9522
|
protocolVersion: "2024-11-05",
|
|
@@ -8417,11 +9555,229 @@ class McpClient {
|
|
|
8417
9555
|
this.child.kill();
|
|
8418
9556
|
}
|
|
8419
9557
|
}
|
|
8420
|
-
|
|
8421
|
-
|
|
9558
|
+
|
|
9559
|
+
class HttpMcpClient {
|
|
9560
|
+
url;
|
|
9561
|
+
sessionId;
|
|
9562
|
+
protocolVersion;
|
|
9563
|
+
extraHeaders;
|
|
9564
|
+
nextId = 0;
|
|
9565
|
+
accessToken;
|
|
9566
|
+
clientId;
|
|
9567
|
+
oauthAttempted = false;
|
|
9568
|
+
constructor(url, extraHeaders, clientId) {
|
|
9569
|
+
this.url = url;
|
|
9570
|
+
this.extraHeaders = extraHeaders;
|
|
9571
|
+
this.clientId = clientId;
|
|
9572
|
+
}
|
|
9573
|
+
static async start(url, extraHeaders = {}, clientId) {
|
|
9574
|
+
const client = new HttpMcpClient(url, extraHeaders, clientId);
|
|
9575
|
+
if (!extraHeaders["Authorization"] && !extraHeaders["authorization"]) {
|
|
9576
|
+
const { loadOrRefreshMcpToken: loadOrRefreshMcpToken3 } = await Promise.resolve().then(() => (init_mcp_oauth2(), exports_mcp_oauth2));
|
|
9577
|
+
const token = await loadOrRefreshMcpToken3(url);
|
|
9578
|
+
if (token) {
|
|
9579
|
+
client.accessToken = token;
|
|
9580
|
+
}
|
|
9581
|
+
}
|
|
9582
|
+
const initResult = await client.sendRequest("initialize", {
|
|
9583
|
+
protocolVersion: "2025-11-25",
|
|
9584
|
+
capabilities: {},
|
|
9585
|
+
clientInfo: { name: "mtpcli-wrap", version: VERSION2 }
|
|
9586
|
+
});
|
|
9587
|
+
client.protocolVersion = initResult.protocolVersion ?? "2025-11-25";
|
|
9588
|
+
await client.sendNotification("notifications/initialized", {});
|
|
9589
|
+
return client;
|
|
9590
|
+
}
|
|
9591
|
+
buildHeaders() {
|
|
9592
|
+
const headers = {
|
|
9593
|
+
"Content-Type": "application/json",
|
|
9594
|
+
Accept: "application/json, text/event-stream"
|
|
9595
|
+
};
|
|
9596
|
+
if (this.accessToken && !this.extraHeaders["Authorization"] && !this.extraHeaders["authorization"]) {
|
|
9597
|
+
headers["Authorization"] = `Bearer ${this.accessToken}`;
|
|
9598
|
+
}
|
|
9599
|
+
Object.assign(headers, this.extraHeaders);
|
|
9600
|
+
if (this.sessionId) {
|
|
9601
|
+
headers["MCP-Session-Id"] = this.sessionId;
|
|
9602
|
+
}
|
|
9603
|
+
if (this.protocolVersion) {
|
|
9604
|
+
headers["MCP-Protocol-Version"] = this.protocolVersion;
|
|
9605
|
+
}
|
|
9606
|
+
return headers;
|
|
9607
|
+
}
|
|
9608
|
+
parseJsonRpcResponse(json) {
|
|
9609
|
+
if (json.error) {
|
|
9610
|
+
const err = json.error;
|
|
9611
|
+
throw new Error(`MCP error: ${err.message} (${err.code})`);
|
|
9612
|
+
}
|
|
9613
|
+
return json.result ?? {};
|
|
9614
|
+
}
|
|
9615
|
+
async readSseResponse(resp, requestId) {
|
|
9616
|
+
const sid = resp.headers.get("mcp-session-id");
|
|
9617
|
+
if (sid)
|
|
9618
|
+
this.sessionId = sid;
|
|
9619
|
+
const text = await resp.text();
|
|
9620
|
+
const events = text.split(/\n\n+/);
|
|
9621
|
+
for (const event of events) {
|
|
9622
|
+
const dataLines = [];
|
|
9623
|
+
for (const line of event.split(`
|
|
9624
|
+
`)) {
|
|
9625
|
+
if (line.startsWith("data:")) {
|
|
9626
|
+
dataLines.push(line.slice(5).trimStart());
|
|
9627
|
+
}
|
|
9628
|
+
}
|
|
9629
|
+
if (dataLines.length === 0)
|
|
9630
|
+
continue;
|
|
9631
|
+
let json;
|
|
9632
|
+
try {
|
|
9633
|
+
json = JSON.parse(dataLines.join(`
|
|
9634
|
+
`));
|
|
9635
|
+
} catch {
|
|
9636
|
+
continue;
|
|
9637
|
+
}
|
|
9638
|
+
if (json.id === requestId) {
|
|
9639
|
+
return this.parseJsonRpcResponse(json);
|
|
9640
|
+
}
|
|
9641
|
+
}
|
|
9642
|
+
throw new Error("SSE stream ended without a response matching request ID " + requestId);
|
|
9643
|
+
}
|
|
9644
|
+
async handleOAuth(resp) {
|
|
9645
|
+
if (this.oauthAttempted) {
|
|
9646
|
+
throw new Error(`HTTP 401 from ${this.url} (OAuth already attempted, not retrying)`);
|
|
9647
|
+
}
|
|
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 {}
|
|
9659
|
+
const wwwAuth = resp.headers.get("www-authenticate") ?? "";
|
|
9660
|
+
const { mcpOAuthFlow: mcpOAuthFlow3 } = await Promise.resolve().then(() => (init_mcp_oauth2(), exports_mcp_oauth2));
|
|
9661
|
+
this.accessToken = await mcpOAuthFlow3(this.url, wwwAuth, this.clientId);
|
|
9662
|
+
}
|
|
9663
|
+
async sendRequest(method, params) {
|
|
9664
|
+
this.nextId++;
|
|
9665
|
+
const body = {
|
|
9666
|
+
jsonrpc: "2.0",
|
|
9667
|
+
id: this.nextId,
|
|
9668
|
+
method,
|
|
9669
|
+
params
|
|
9670
|
+
};
|
|
9671
|
+
const resp = await fetch(this.url, {
|
|
9672
|
+
method: "POST",
|
|
9673
|
+
headers: this.buildHeaders(),
|
|
9674
|
+
body: JSON.stringify(body)
|
|
9675
|
+
});
|
|
9676
|
+
if (resp.status === 401) {
|
|
9677
|
+
await this.handleOAuth(resp);
|
|
9678
|
+
const retryResp = await fetch(this.url, {
|
|
9679
|
+
method: "POST",
|
|
9680
|
+
headers: this.buildHeaders(),
|
|
9681
|
+
body: JSON.stringify(body)
|
|
9682
|
+
});
|
|
9683
|
+
if (!retryResp.ok) {
|
|
9684
|
+
throw new Error(`HTTP ${retryResp.status} ${retryResp.statusText} from ${this.url}`);
|
|
9685
|
+
}
|
|
9686
|
+
const ct2 = retryResp.headers.get("content-type") ?? "";
|
|
9687
|
+
if (ct2.includes("text/event-stream")) {
|
|
9688
|
+
return this.readSseResponse(retryResp, this.nextId);
|
|
9689
|
+
}
|
|
9690
|
+
const sid2 = retryResp.headers.get("mcp-session-id");
|
|
9691
|
+
if (sid2)
|
|
9692
|
+
this.sessionId = sid2;
|
|
9693
|
+
const json2 = await retryResp.json();
|
|
9694
|
+
return this.parseJsonRpcResponse(json2);
|
|
9695
|
+
}
|
|
9696
|
+
if (!resp.ok) {
|
|
9697
|
+
throw new Error(`HTTP ${resp.status} ${resp.statusText} from ${this.url}`);
|
|
9698
|
+
}
|
|
9699
|
+
const ct = resp.headers.get("content-type") ?? "";
|
|
9700
|
+
if (ct.includes("text/event-stream")) {
|
|
9701
|
+
return this.readSseResponse(resp, this.nextId);
|
|
9702
|
+
}
|
|
9703
|
+
const sid = resp.headers.get("mcp-session-id");
|
|
9704
|
+
if (sid) {
|
|
9705
|
+
this.sessionId = sid;
|
|
9706
|
+
}
|
|
9707
|
+
const json = await resp.json();
|
|
9708
|
+
return this.parseJsonRpcResponse(json);
|
|
9709
|
+
}
|
|
9710
|
+
async sendNotification(method, params) {
|
|
9711
|
+
const body = {
|
|
9712
|
+
jsonrpc: "2.0",
|
|
9713
|
+
method,
|
|
9714
|
+
params
|
|
9715
|
+
};
|
|
9716
|
+
const resp = await fetch(this.url, {
|
|
9717
|
+
method: "POST",
|
|
9718
|
+
headers: this.buildHeaders(),
|
|
9719
|
+
body: JSON.stringify(body)
|
|
9720
|
+
});
|
|
9721
|
+
if (resp.status === 401) {
|
|
9722
|
+
await this.handleOAuth(resp);
|
|
9723
|
+
const retryResp = await fetch(this.url, {
|
|
9724
|
+
method: "POST",
|
|
9725
|
+
headers: this.buildHeaders(),
|
|
9726
|
+
body: JSON.stringify(body)
|
|
9727
|
+
});
|
|
9728
|
+
if (!retryResp.ok) {
|
|
9729
|
+
throw new Error(`HTTP ${retryResp.status} ${retryResp.statusText} from ${this.url}`);
|
|
9730
|
+
}
|
|
9731
|
+
const sid2 = retryResp.headers.get("mcp-session-id");
|
|
9732
|
+
if (sid2)
|
|
9733
|
+
this.sessionId = sid2;
|
|
9734
|
+
return;
|
|
9735
|
+
}
|
|
9736
|
+
if (!resp.ok) {
|
|
9737
|
+
throw new Error(`HTTP ${resp.status} ${resp.statusText} from ${this.url}`);
|
|
9738
|
+
}
|
|
9739
|
+
const sid = resp.headers.get("mcp-session-id");
|
|
9740
|
+
if (sid) {
|
|
9741
|
+
this.sessionId = sid;
|
|
9742
|
+
}
|
|
9743
|
+
}
|
|
9744
|
+
async listTools() {
|
|
9745
|
+
const result = await this.sendRequest("tools/list", {});
|
|
9746
|
+
return result.tools ?? [];
|
|
9747
|
+
}
|
|
9748
|
+
async callTool(name, arguments_) {
|
|
9749
|
+
return this.sendRequest("tools/call", { name, arguments: arguments_ });
|
|
9750
|
+
}
|
|
9751
|
+
stop() {}
|
|
9752
|
+
}
|
|
9753
|
+
function parseHeaders(raw) {
|
|
9754
|
+
const headers = {};
|
|
9755
|
+
for (const h of raw) {
|
|
9756
|
+
const idx = h.indexOf(":");
|
|
9757
|
+
if (idx === -1) {
|
|
9758
|
+
throw new Error(`invalid header (missing ':'): ${h}`);
|
|
9759
|
+
}
|
|
9760
|
+
headers[h.slice(0, idx).trim()] = h.slice(idx + 1).trim();
|
|
9761
|
+
}
|
|
9762
|
+
return headers;
|
|
9763
|
+
}
|
|
9764
|
+
async function run2(serverCmd, serverUrl, describeMode, toolName, toolArgs = [], rawHeaders = [], clientId) {
|
|
9765
|
+
let client;
|
|
9766
|
+
let serverName;
|
|
9767
|
+
if (serverUrl) {
|
|
9768
|
+
const headers = parseHeaders(rawHeaders);
|
|
9769
|
+
client = await HttpMcpClient.start(serverUrl, headers, clientId);
|
|
9770
|
+
try {
|
|
9771
|
+
serverName = new URL(serverUrl).hostname;
|
|
9772
|
+
} catch {
|
|
9773
|
+
serverName = serverUrl;
|
|
9774
|
+
}
|
|
9775
|
+
} else {
|
|
9776
|
+
client = await McpClient.start(serverCmd);
|
|
9777
|
+
serverName = serverCmd.split("/").pop()?.split(/\s/)[0] ?? serverCmd;
|
|
9778
|
+
}
|
|
8422
9779
|
try {
|
|
8423
9780
|
const mcpTools = await client.listTools();
|
|
8424
|
-
const serverName = serverCmd.split("/").pop()?.split(/\s/)[0] ?? serverCmd;
|
|
8425
9781
|
const commands = mcpTools.map(mcpToolToCommand);
|
|
8426
9782
|
const schema = {
|
|
8427
9783
|
name: serverName,
|
|
@@ -9208,7 +10564,7 @@ function cleanJson(obj) {
|
|
|
9208
10564
|
}
|
|
9209
10565
|
|
|
9210
10566
|
// src/index.ts
|
|
9211
|
-
var VERSION3 = "0.
|
|
10567
|
+
var VERSION3 = "0.3.0";
|
|
9212
10568
|
function selfDescribe() {
|
|
9213
10569
|
const schema = {
|
|
9214
10570
|
name: "mtpcli",
|
|
@@ -9266,8 +10622,7 @@ function selfDescribe() {
|
|
|
9266
10622
|
{
|
|
9267
10623
|
name: "tool",
|
|
9268
10624
|
type: "string",
|
|
9269
|
-
|
|
9270
|
-
description: "Tool name"
|
|
10625
|
+
description: "Tool name (required unless --url is used)"
|
|
9271
10626
|
},
|
|
9272
10627
|
{
|
|
9273
10628
|
name: "--provider",
|
|
@@ -9278,6 +10633,16 @@ function selfDescribe() {
|
|
|
9278
10633
|
name: "--token",
|
|
9279
10634
|
type: "string",
|
|
9280
10635
|
description: "API key or bearer token"
|
|
10636
|
+
},
|
|
10637
|
+
{
|
|
10638
|
+
name: "--url",
|
|
10639
|
+
type: "string",
|
|
10640
|
+
description: "HTTP MCP server URL (triggers OAuth discovery and login)"
|
|
10641
|
+
},
|
|
10642
|
+
{
|
|
10643
|
+
name: "--client-id",
|
|
10644
|
+
type: "string",
|
|
10645
|
+
description: "Pre-registered OAuth client ID (for --url)"
|
|
9281
10646
|
}
|
|
9282
10647
|
],
|
|
9283
10648
|
examples: [
|
|
@@ -9285,6 +10650,10 @@ function selfDescribe() {
|
|
|
9285
10650
|
{
|
|
9286
10651
|
description: "API key login",
|
|
9287
10652
|
command: "mtpcli auth login my-tool --token sk-xxx"
|
|
10653
|
+
},
|
|
10654
|
+
{
|
|
10655
|
+
description: "Login to HTTP MCP server",
|
|
10656
|
+
command: "mtpcli auth login --url https://mcp.example.com/v1/mcp"
|
|
9288
10657
|
}
|
|
9289
10658
|
]
|
|
9290
10659
|
},
|
|
@@ -9295,11 +10664,21 @@ function selfDescribe() {
|
|
|
9295
10664
|
{
|
|
9296
10665
|
name: "tool",
|
|
9297
10666
|
type: "string",
|
|
9298
|
-
|
|
9299
|
-
|
|
10667
|
+
description: "Tool name (required unless --url is used)"
|
|
10668
|
+
},
|
|
10669
|
+
{
|
|
10670
|
+
name: "--url",
|
|
10671
|
+
type: "string",
|
|
10672
|
+
description: "HTTP MCP server URL"
|
|
9300
10673
|
}
|
|
9301
10674
|
],
|
|
9302
|
-
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
|
+
]
|
|
9303
10682
|
},
|
|
9304
10683
|
{
|
|
9305
10684
|
name: "auth status",
|
|
@@ -9308,11 +10687,21 @@ function selfDescribe() {
|
|
|
9308
10687
|
{
|
|
9309
10688
|
name: "tool",
|
|
9310
10689
|
type: "string",
|
|
9311
|
-
|
|
9312
|
-
|
|
10690
|
+
description: "Tool name (required unless --url is used)"
|
|
10691
|
+
},
|
|
10692
|
+
{
|
|
10693
|
+
name: "--url",
|
|
10694
|
+
type: "string",
|
|
10695
|
+
description: "HTTP MCP server URL"
|
|
9313
10696
|
}
|
|
9314
10697
|
],
|
|
9315
|
-
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
|
+
]
|
|
9316
10705
|
},
|
|
9317
10706
|
{
|
|
9318
10707
|
name: "auth token",
|
|
@@ -9321,11 +10710,21 @@ function selfDescribe() {
|
|
|
9321
10710
|
{
|
|
9322
10711
|
name: "tool",
|
|
9323
10712
|
type: "string",
|
|
9324
|
-
|
|
9325
|
-
|
|
10713
|
+
description: "Tool name (required unless --url is used)"
|
|
10714
|
+
},
|
|
10715
|
+
{
|
|
10716
|
+
name: "--url",
|
|
10717
|
+
type: "string",
|
|
10718
|
+
description: "HTTP MCP server URL"
|
|
9326
10719
|
}
|
|
9327
10720
|
],
|
|
9328
|
-
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
|
+
]
|
|
9329
10728
|
},
|
|
9330
10729
|
{
|
|
9331
10730
|
name: "auth env",
|
|
@@ -9347,11 +10746,21 @@ function selfDescribe() {
|
|
|
9347
10746
|
{
|
|
9348
10747
|
name: "tool",
|
|
9349
10748
|
type: "string",
|
|
9350
|
-
|
|
9351
|
-
|
|
10749
|
+
description: "Tool name (required unless --url is used)"
|
|
10750
|
+
},
|
|
10751
|
+
{
|
|
10752
|
+
name: "--url",
|
|
10753
|
+
type: "string",
|
|
10754
|
+
description: "HTTP MCP server URL"
|
|
9352
10755
|
}
|
|
9353
10756
|
],
|
|
9354
|
-
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
|
+
]
|
|
9355
10764
|
},
|
|
9356
10765
|
{
|
|
9357
10766
|
name: "serve",
|
|
@@ -9382,8 +10791,22 @@ function selfDescribe() {
|
|
|
9382
10791
|
{
|
|
9383
10792
|
name: "--server",
|
|
9384
10793
|
type: "string",
|
|
9385
|
-
|
|
9386
|
-
|
|
10794
|
+
description: "MCP server command (stdio transport)"
|
|
10795
|
+
},
|
|
10796
|
+
{
|
|
10797
|
+
name: "--url",
|
|
10798
|
+
type: "string",
|
|
10799
|
+
description: "MCP server URL (Streamable HTTP transport). Mutually exclusive with --server."
|
|
10800
|
+
},
|
|
10801
|
+
{
|
|
10802
|
+
name: "--header",
|
|
10803
|
+
type: "array",
|
|
10804
|
+
description: "HTTP header(s) for --url, e.g. 'Authorization: Bearer tok'. Repeatable."
|
|
10805
|
+
},
|
|
10806
|
+
{
|
|
10807
|
+
name: "--client-id",
|
|
10808
|
+
type: "string",
|
|
10809
|
+
description: "Pre-registered OAuth client ID (for servers without dynamic registration)"
|
|
9387
10810
|
},
|
|
9388
10811
|
{
|
|
9389
10812
|
name: "--describe",
|
|
@@ -9398,12 +10821,20 @@ function selfDescribe() {
|
|
|
9398
10821
|
],
|
|
9399
10822
|
examples: [
|
|
9400
10823
|
{
|
|
9401
|
-
description: "Describe
|
|
10824
|
+
description: "Describe a stdio MCP server",
|
|
9402
10825
|
command: 'mtpcli wrap --server "npx @mcp/server-github" --describe'
|
|
9403
10826
|
},
|
|
9404
10827
|
{
|
|
9405
|
-
description: "Call a tool",
|
|
10828
|
+
description: "Call a tool on a stdio server",
|
|
9406
10829
|
command: 'mtpcli wrap --server "npx @mcp/server-github" search_repos -- --query mtpcli'
|
|
10830
|
+
},
|
|
10831
|
+
{
|
|
10832
|
+
description: "Describe an HTTP MCP server",
|
|
10833
|
+
command: "mtpcli wrap --url http://localhost:3000/mcp --describe"
|
|
10834
|
+
},
|
|
10835
|
+
{
|
|
10836
|
+
description: "Call a tool on an HTTP server",
|
|
10837
|
+
command: "mtpcli wrap --url http://localhost:3000/mcp search_repos -- --query mtpcli"
|
|
9407
10838
|
}
|
|
9408
10839
|
]
|
|
9409
10840
|
},
|
|
@@ -9517,29 +10948,90 @@ program2.command("search").description("Search across --describe-compatible tool
|
|
|
9517
10948
|
}
|
|
9518
10949
|
});
|
|
9519
10950
|
var authCmd = program2.command("auth").description("Manage authentication for tools");
|
|
9520
|
-
authCmd.command("login").description("Log in to a tool").argument("
|
|
9521
|
-
|
|
9522
|
-
|
|
10951
|
+
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) => {
|
|
10952
|
+
if (opts.url) {
|
|
10953
|
+
const { mcpOAuthFlow: mcpOAuthFlow3 } = await Promise.resolve().then(() => (init_mcp_oauth(), exports_mcp_oauth));
|
|
10954
|
+
await mcpOAuthFlow3(opts.url, "", opts.clientId);
|
|
10955
|
+
return;
|
|
10956
|
+
}
|
|
10957
|
+
if (!tool) {
|
|
10958
|
+
process.stderr.write(`error: tool name is required (or use --url)
|
|
10959
|
+
`);
|
|
10960
|
+
process.exit(1);
|
|
10961
|
+
}
|
|
10962
|
+
const { runLogin: runLogin3 } = await Promise.resolve().then(() => (init_auth2(), exports_auth2));
|
|
10963
|
+
await runLogin3(tool, opts.provider, opts.token);
|
|
9523
10964
|
});
|
|
9524
|
-
authCmd.command("logout").description("Log out from a tool").argument("
|
|
9525
|
-
|
|
9526
|
-
|
|
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);
|
|
9527
10985
|
});
|
|
9528
|
-
authCmd.command("status").description("Show auth status for a tool").argument("
|
|
9529
|
-
|
|
9530
|
-
|
|
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);
|
|
9531
10999
|
});
|
|
9532
|
-
authCmd.command("token").description("Print the access token for a tool").argument("
|
|
9533
|
-
|
|
9534
|
-
|
|
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);
|
|
9535
11017
|
});
|
|
9536
11018
|
authCmd.command("env").description("Print shell export statement for eval").argument("<tool>", "Tool name").action(async (tool) => {
|
|
9537
|
-
const { runEnv:
|
|
9538
|
-
await
|
|
11019
|
+
const { runEnv: runEnv3 } = await Promise.resolve().then(() => (init_auth2(), exports_auth2));
|
|
11020
|
+
await runEnv3(tool);
|
|
9539
11021
|
});
|
|
9540
|
-
authCmd.command("refresh").description("Force-refresh the stored OAuth token for a tool").argument("
|
|
9541
|
-
|
|
9542
|
-
|
|
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);
|
|
9543
11035
|
});
|
|
9544
11036
|
program2.command("serve").description("Serve CLI tools as an MCP server (cli2mcp bridge)").requiredOption("--tool <names...>", "Tool(s) to serve").addHelpText("after", `
|
|
9545
11037
|
This command is meant to be used as an MCP server, not run directly.
|
|
@@ -9580,9 +11072,29 @@ Multiple tools can be combined into a single MCP server:
|
|
|
9580
11072
|
const { run: run5 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
|
|
9581
11073
|
await run5(opts.tool);
|
|
9582
11074
|
});
|
|
9583
|
-
program2.command("wrap").description("Wrap an MCP server as a CLI tool (mcp2cli bridge)").
|
|
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) => {
|
|
11076
|
+
if (opts.server && opts.url) {
|
|
11077
|
+
process.stderr.write(`error: --server and --url are mutually exclusive
|
|
11078
|
+
`);
|
|
11079
|
+
process.exit(1);
|
|
11080
|
+
}
|
|
11081
|
+
if (!opts.server && !opts.url) {
|
|
11082
|
+
process.stderr.write(`error: one of --server or --url is required
|
|
11083
|
+
`);
|
|
11084
|
+
process.exit(1);
|
|
11085
|
+
}
|
|
11086
|
+
if (opts.header && !opts.url) {
|
|
11087
|
+
process.stderr.write(`error: --header requires --url
|
|
11088
|
+
`);
|
|
11089
|
+
process.exit(1);
|
|
11090
|
+
}
|
|
11091
|
+
if (opts.clientId && !opts.url) {
|
|
11092
|
+
process.stderr.write(`error: --client-id requires --url
|
|
11093
|
+
`);
|
|
11094
|
+
process.exit(1);
|
|
11095
|
+
}
|
|
9584
11096
|
const { run: run5 } = await Promise.resolve().then(() => (init_wrap(), exports_wrap));
|
|
9585
|
-
await run5(opts.server, opts.describe, toolName, args);
|
|
11097
|
+
await run5(opts.server, opts.url, opts.describe, toolName, args, opts.header ?? [], opts.clientId);
|
|
9586
11098
|
});
|
|
9587
11099
|
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
11100
|
const { run: run5 } = await Promise.resolve().then(() => (init_validate(), exports_validate));
|