@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.
Files changed (2) hide show
  1. package/dist/index.js +1630 -118
  2. 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/auth.ts
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
- unlinkSync as unlinkSync2,
7797
- writeFileSync as writeFileSync3
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 join3(baseDir ?? join3(homedir3(), ".mtpcli"), "tokens");
8232
+ return join4(baseDir ?? join4(homedir4(), ".mtpcli"), "tokens");
7803
8233
  }
7804
8234
  function tokenPath2(toolName, providerId, baseDir) {
7805
- return join3(tokensDir2(baseDir), toolName, `${providerId}.json`);
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
- mkdirSync3(dirname2(path2), { recursive: true });
7810
- writeFileSync3(path2, JSON.stringify(token, null, 2));
7811
- chmodSync2(path2, 384);
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 (!existsSync3(path2))
8246
+ if (!existsSync4(path2))
7817
8247
  return null;
7818
- return JSON.parse(readFileSync3(path2, "utf-8"));
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 refreshAccessToken2(tokenUrl, refreshToken, clientId) {
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 createInterface2 } from "node:readline";
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 getEnvDict2(entry.cliTool, authConfig);
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
- async function run(toolNames) {
8157
- const allTools = [];
8158
- const allSchemas = new Map;
8159
- const allAuth = new Map;
8160
- for (const name of toolNames) {
8161
- const schema = await getToolSchema2(name);
8162
- if (!schema)
8163
- throw new Error(`could not get --describe from '${name}'`);
8164
- if (schema.auth)
8165
- allAuth.set(name, schema.auth);
8166
- for (const cmd of schema.commands) {
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
- if (allTools.length === 0)
8177
- throw new Error("no tools loaded");
8178
- process.stderr.write(`mtpcli serve: serving ${allTools.length} tool(s) from ${toolNames.length} CLI tool(s)
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
- for (const t of allTools) {
8181
- process.stderr.write(` - ${t.name}: ${t.description}
9248
+ process.stderr.write(`If the browser doesn't open, visit:
9249
+ ${authUrl}
9250
+
8182
9251
  `);
8183
- }
8184
- const state = {
8185
- tools: allTools,
8186
- schemas: allSchemas,
8187
- authConfigs: allAuth
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
- const rl = createInterface2({ input: process.stdin, crlfDelay: Infinity });
8190
- for await (const line of rl) {
8191
- const trimmed = line.trim();
8192
- if (!trimmed)
8193
- continue;
8194
- let req;
8195
- try {
8196
- req = JsonRpcRequestSchema2.parse(JSON.parse(trimmed));
8197
- } catch (e) {
8198
- const msg = e instanceof Error ? e.message : String(e);
8199
- process.stderr.write(`error reading request: ${msg}
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
- writeResponse(process.stdout, jsonRpcError(null, -32700, `Parse error: ${msg}`));
8202
- continue;
8203
- }
8204
- const resp = await handleRequest(state, req);
8205
- if (resp) {
8206
- writeResponse(process.stdout, resp);
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 execFileAsync7, VERSION = "0.1.0";
8211
- var init_serve = __esm(() => {
8212
- init_auth2();
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 createInterface3 } from "node:readline";
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 = createInterface3({ input: child.stdout, crlfDelay: Infinity });
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
- async function run2(serverCmd, describeMode, toolName, toolArgs = []) {
8421
- const client = await McpClient.start(serverCmd);
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.2.2";
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
- required: true,
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
- required: true,
9299
- description: "Tool name"
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: [{ command: "mtpcli auth logout gh-tool" }]
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
- required: true,
9312
- description: "Tool name"
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: [{ command: "mtpcli auth status gh-tool" }]
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
- required: true,
9325
- description: "Tool name"
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: [{ command: "mtpcli auth token gh-tool" }]
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
- required: true,
9351
- description: "Tool name"
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: [{ command: "mtpcli auth refresh gh-tool" }]
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
- required: true,
9386
- description: "MCP server command"
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 an MCP server",
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("<tool>", "Tool name").option("--provider <id>", "Specific provider ID").option("--token <value>", "API key / bearer token (skip OAuth flow)").action(async (tool, opts) => {
9521
- const { runLogin: runLogin2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
9522
- await runLogin2(tool, opts.provider, opts.token);
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("<tool>", "Tool name").action(async (tool) => {
9525
- const { runLogout: runLogout2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
9526
- await runLogout2(tool);
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("<tool>", "Tool name").action(async (tool) => {
9529
- const { runStatus: runStatus2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
9530
- await runStatus2(tool);
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("<tool>", "Tool name").action(async (tool) => {
9533
- const { runToken: runToken2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
9534
- await runToken2(tool);
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: runEnv2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
9538
- await runEnv2(tool);
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("<tool>", "Tool name").action(async (tool) => {
9541
- const { runRefresh: runRefresh2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
9542
- await runRefresh2(tool);
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)").requiredOption("--server <cmd>", "MCP server command to run").option("--describe", "Output --describe JSON instead of invoking", false).argument("[tool_name]", "Tool name to invoke").argument("[args...]", "Arguments for the tool (after --)").action(async (toolName, args, opts) => {
11075
+ program2.command("wrap").description("Wrap an MCP server as a CLI tool (mcp2cli bridge)").option("--server <cmd>", "MCP server command to run (stdio transport)").option("--url <url>", "MCP server URL (Streamable HTTP / SSE transport)").option("-H, --header <header...>", "HTTP header(s) for --url (e.g. 'Authorization: Bearer tok')").option("--client-id <id>", "Pre-registered OAuth client ID (for --url)").option("--describe", "Output --describe JSON instead of invoking", false).argument("[tool_name]", "Tool name to invoke").argument("[args...]", "Arguments for the tool (after --)").action(async (toolName, args, opts) => {
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));