align-mcp-remote 0.1.38 → 0.1.40

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.
@@ -15632,7 +15632,7 @@ var Client = class extends Protocol {
15632
15632
  };
15633
15633
 
15634
15634
  // package.json
15635
- var version2 = "0.1.38";
15635
+ var version2 = "0.1.39";
15636
15636
 
15637
15637
  // node_modules/pkce-challenge/dist/index.node.js
15638
15638
  var crypto;
@@ -16410,6 +16410,17 @@ async function executeTokenRequest(authorizationServerUrl, { metadata, tokenRequ
16410
16410
  }
16411
16411
  return OAuthTokensSchema.parse(await response.json());
16412
16412
  }
16413
+ async function exchangeAuthorization(authorizationServerUrl, { metadata, clientInformation, authorizationCode, codeVerifier, redirectUri, resource, addClientAuthentication, fetchFn }) {
16414
+ const tokenRequestParams = prepareAuthorizationCodeRequest(authorizationCode, codeVerifier, redirectUri);
16415
+ return executeTokenRequest(authorizationServerUrl, {
16416
+ metadata,
16417
+ tokenRequestParams,
16418
+ clientInformation,
16419
+ addClientAuthentication,
16420
+ resource,
16421
+ fetchFn
16422
+ });
16423
+ }
16413
16424
  async function refreshAuthorization(authorizationServerUrl, { metadata, clientInformation, refreshToken, resource, addClientAuthentication, fetchFn }) {
16414
16425
  const tokenRequestParams = new URLSearchParams({
16415
16426
  grant_type: "refresh_token",
@@ -18072,41 +18083,109 @@ async function discoverOAuthServerInfo2(serverUrl, headers = {}) {
18072
18083
  debugLog("No Protected Resource Metadata found, falling back to server URL as authorization server");
18073
18084
  }
18074
18085
  const authorizationServerMetadata = await fetchAuthorizationServerMetadata(authorizationServerUrl);
18086
+ const downstreamTokenHeader = protectedResourceMetadata?.x_downstream_resource?.token_header ?? "X-Downstream-Authorization";
18075
18087
  return {
18076
18088
  authorizationServerUrl,
18077
18089
  authorizationServerMetadata,
18078
18090
  protectedResourceMetadata,
18079
- wwwAuthenticateScope
18091
+ wwwAuthenticateScope,
18092
+ downstreamTokenHeader: protectedResourceMetadata?.x_downstream_resource ? downstreamTokenHeader : void 0
18080
18093
  };
18081
18094
  }
18082
- async function connectToRemoteServer(client, serverUrl, authProvider, headers, authInitializer, transportStrategy = "http-first", recursionReasons = /* @__PURE__ */ new Set()) {
18095
+ async function performDownstreamAuth(authProvider, downstreamResource, callbackPort) {
18096
+ debugLog("Starting downstream OAuth flow", { downstreamResource });
18097
+ const downstreamAs = downstreamResource.authorization_servers[0];
18098
+ if (!downstreamAs) {
18099
+ throw new Error("x_downstream_resource.authorization_servers is empty");
18100
+ }
18101
+ const downstreamServerMetadata = await fetchAuthorizationServerMetadata(downstreamAs);
18102
+ if (!downstreamServerMetadata) {
18103
+ throw new Error(`Failed to fetch downstream authorization server metadata from ${downstreamAs}`);
18104
+ }
18105
+ const downstreamCallbackPort = await findAvailablePort(callbackPort + 1);
18106
+ const downstreamEvents = new (await import("events")).EventEmitter();
18107
+ const { server: downstreamServer, waitForAuthCode: waitForDownstreamCode } = setupOAuthCallbackServerWithLongPoll({
18108
+ port: downstreamCallbackPort,
18109
+ path: "/oauth/callback",
18110
+ events: downstreamEvents,
18111
+ authTimeoutMs: 3e5
18112
+ });
18113
+ const host = authProvider.options.host || "localhost";
18114
+ const redirectUrl = `http://${host}:${downstreamCallbackPort}/oauth/callback`;
18115
+ const scope = downstreamResource.scopes_required?.join(" ") ?? "";
18116
+ const { authorizationUrl, codeVerifier } = await startAuthorization(downstreamAs, {
18117
+ metadata: downstreamServerMetadata,
18118
+ clientInformation: await authProvider.clientInformation(),
18119
+ redirectUrl,
18120
+ scope,
18121
+ resource: new URL(downstreamResource.resource)
18122
+ });
18123
+ log(`
18124
+ Please authorize the downstream resource by visiting:
18125
+ ${authorizationUrl.toString()}
18126
+ `);
18127
+ try {
18128
+ const { default: open2 } = await import("open");
18129
+ await open2(authorizationUrl.toString());
18130
+ log("Browser opened automatically for downstream authorization.");
18131
+ } catch {
18132
+ log("Could not open browser automatically. Please copy and paste the URL above.");
18133
+ }
18134
+ try {
18135
+ const code = await waitForDownstreamCode();
18136
+ const tokens = await exchangeAuthorization(downstreamAs, {
18137
+ metadata: downstreamServerMetadata,
18138
+ clientInformation: await authProvider.clientInformation(),
18139
+ authorizationCode: code,
18140
+ codeVerifier,
18141
+ redirectUri: redirectUrl
18142
+ });
18143
+ await authProvider.saveDownstreamTokens(tokens);
18144
+ debugLog("Downstream OAuth flow completed, tokens saved");
18145
+ } finally {
18146
+ downstreamServer.close();
18147
+ }
18148
+ }
18149
+ async function connectToRemoteServer(client, serverUrl, authProvider, headers, authInitializer, transportStrategy = "http-first", downstreamTokenHeader, recursionReasons = /* @__PURE__ */ new Set()) {
18083
18150
  log(`[${pid}] Connecting to remote server: ${serverUrl}`);
18084
18151
  const url2 = new URL(serverUrl);
18152
+ const resolveDownstreamHeader = async () => {
18153
+ if (!downstreamTokenHeader) return {};
18154
+ const provider = authProvider;
18155
+ if (typeof provider.downstreamTokens !== "function") return {};
18156
+ const downstreamTokens = await provider.downstreamTokens();
18157
+ if (downstreamTokens?.access_token) {
18158
+ return { [downstreamTokenHeader]: `Bearer ${downstreamTokens.access_token}` };
18159
+ }
18160
+ return {};
18161
+ };
18085
18162
  const eventSourceInit = {
18086
- fetch: (url3, init) => {
18087
- return Promise.resolve(authProvider?.tokens?.()).then(
18088
- (tokens) => fetch2(url3, {
18089
- ...init,
18090
- headers: {
18091
- ...init?.headers instanceof Headers2 ? Object.fromEntries(init?.headers.entries()) : init?.headers || {},
18092
- ...headers,
18093
- ...tokens?.access_token ? { Authorization: `Bearer ${tokens.access_token}` } : {},
18094
- Accept: "text/event-stream"
18095
- }
18096
- })
18097
- );
18163
+ fetch: async (url3, init) => {
18164
+ const [tokens, downstreamHeader2] = await Promise.all([authProvider?.tokens?.(), resolveDownstreamHeader()]);
18165
+ return fetch2(url3, {
18166
+ ...init,
18167
+ headers: {
18168
+ ...init?.headers instanceof Headers2 ? Object.fromEntries(init?.headers.entries()) : init?.headers || {},
18169
+ ...headers,
18170
+ ...tokens?.access_token ? { Authorization: `Bearer ${tokens.access_token}` } : {},
18171
+ ...downstreamHeader2,
18172
+ Accept: "text/event-stream"
18173
+ }
18174
+ });
18098
18175
  }
18099
18176
  };
18100
18177
  log(`Using transport strategy: ${transportStrategy}`);
18101
18178
  const shouldAttemptFallback2 = transportStrategy === "http-first" || transportStrategy === "sse-first";
18102
18179
  const sseTransport = transportStrategy === "sse-only" || transportStrategy === "sse-first";
18180
+ const downstreamHeader = await resolveDownstreamHeader();
18181
+ const mergedHeaders = { ...headers, ...downstreamHeader };
18103
18182
  const transport = sseTransport ? new SSEClientTransport(url2, {
18104
18183
  authProvider,
18105
- requestInit: { headers },
18184
+ requestInit: { headers: mergedHeaders },
18106
18185
  eventSourceInit
18107
18186
  }) : new StreamableHTTPClientTransport(url2, {
18108
18187
  authProvider,
18109
- requestInit: { headers }
18188
+ requestInit: { headers: mergedHeaders }
18110
18189
  });
18111
18190
  try {
18112
18191
  debugLog("Attempting to connect to remote server", { sseTransport });
@@ -18145,6 +18224,7 @@ async function connectToRemoteServer(client, serverUrl, authProvider, headers, a
18145
18224
  headers,
18146
18225
  authInitializer,
18147
18226
  sseTransport ? "http-only" : "sse-only",
18227
+ downstreamTokenHeader,
18148
18228
  recursionReasons
18149
18229
  );
18150
18230
  } else if (error2 instanceof UnauthorizedError || error2 instanceof Error && error2.message.includes("Unauthorized")) {
@@ -18179,7 +18259,7 @@ async function connectToRemoteServer(client, serverUrl, authProvider, headers, a
18179
18259
  recursionReasons.add(REASON_AUTH_NEEDED);
18180
18260
  log(`Recursively reconnecting for reason: ${REASON_AUTH_NEEDED}`);
18181
18261
  debugLog("Recursively reconnecting after auth", { recursionReasons: Array.from(recursionReasons) });
18182
- return connectToRemoteServer(client, serverUrl, authProvider, headers, authInitializer, transportStrategy, recursionReasons);
18262
+ return connectToRemoteServer(client, serverUrl, authProvider, headers, authInitializer, transportStrategy, downstreamTokenHeader, recursionReasons);
18183
18263
  } catch (authError) {
18184
18264
  log("Authorization error:", authError);
18185
18265
  debugLog("Authorization error during finishAuth", {
@@ -18759,6 +18839,20 @@ ${authorizationUrl.toString()}
18759
18839
  debugLog("Code verifier found:", !!verifier);
18760
18840
  return verifier;
18761
18841
  }
18842
+ /**
18843
+ * Gets the downstream OAuth tokens (Layer 2) if they exist
18844
+ */
18845
+ async downstreamTokens() {
18846
+ debugLog("Reading downstream OAuth tokens");
18847
+ return readJsonFile(this.serverUrlHash, "downstream_tokens.json", OAuthTokensSchema);
18848
+ }
18849
+ /**
18850
+ * Saves downstream OAuth tokens (Layer 2)
18851
+ */
18852
+ async saveDownstreamTokens(tokens) {
18853
+ debugLog("Saving downstream tokens", { hasAccessToken: !!tokens.access_token });
18854
+ await writeJsonFile(this.serverUrlHash, "downstream_tokens.json", tokens);
18855
+ }
18762
18856
  /**
18763
18857
  * Invalidates the specified credentials
18764
18858
  * @param scope The scope of credentials to invalidate
@@ -18770,6 +18864,7 @@ ${authorizationUrl.toString()}
18770
18864
  await Promise.all([
18771
18865
  deleteConfigFile(this.serverUrlHash, "client_info.json"),
18772
18866
  deleteConfigFile(this.serverUrlHash, "tokens.json"),
18867
+ deleteConfigFile(this.serverUrlHash, "downstream_tokens.json"),
18773
18868
  deleteConfigFile(this.serverUrlHash, "code_verifier.txt")
18774
18869
  ]);
18775
18870
  this._clientInfo = void 0;
@@ -18990,6 +19085,7 @@ export {
18990
19085
  log,
18991
19086
  mcpProxy,
18992
19087
  discoverOAuthServerInfo2 as discoverOAuthServerInfo,
19088
+ performDownstreamAuth,
18993
19089
  connectToRemoteServer,
18994
19090
  parseCommandLineArgs,
18995
19091
  setupSignalHandlers,
package/dist/client.js CHANGED
@@ -10,9 +10,10 @@ import {
10
10
  discoverOAuthServerInfo,
11
11
  log,
12
12
  parseCommandLineArgs,
13
+ performDownstreamAuth,
13
14
  setupSignalHandlers,
14
15
  version
15
- } from "./chunk-MMS4PSEI.js";
16
+ } from "./chunk-X7AWYUMO.js";
16
17
 
17
18
  // src/client.ts
18
19
  import { EventEmitter } from "events";
@@ -52,6 +53,7 @@ async function runClient(serverUrl, callbackPort, headers, transportStrategy = "
52
53
  capabilities: {}
53
54
  }
54
55
  );
56
+ const downstreamResource = discoveryResult.protectedResourceMetadata?.x_downstream_resource;
55
57
  let server = null;
56
58
  const authInitializer = async () => {
57
59
  const authState = await authCoordinator.initializeAuth();
@@ -61,12 +63,28 @@ async function runClient(serverUrl, callbackPort, headers, transportStrategy = "
61
63
  await new Promise((res) => setTimeout(res, 1e3));
62
64
  }
63
65
  return {
64
- waitForAuthCode: authState.waitForAuthCode,
66
+ waitForAuthCode: async () => {
67
+ const code = await authState.waitForAuthCode();
68
+ if (downstreamResource && !authState.skipBrowserAuth) {
69
+ log("Layer 1 auth complete. Starting downstream (Layer 2) OAuth flow...");
70
+ await performDownstreamAuth(authProvider, downstreamResource, callbackPort);
71
+ log("Layer 2 auth complete.");
72
+ }
73
+ return code;
74
+ },
65
75
  skipBrowserAuth: authState.skipBrowserAuth
66
76
  };
67
77
  };
68
78
  try {
69
- const transport = await connectToRemoteServer(client, serverUrl, authProvider, headers, authInitializer, transportStrategy);
79
+ const transport = await connectToRemoteServer(
80
+ client,
81
+ serverUrl,
82
+ authProvider,
83
+ headers,
84
+ authInitializer,
85
+ transportStrategy,
86
+ discoveryResult.downstreamTokenHeader
87
+ );
70
88
  transport.onmessage = (message) => {
71
89
  log("Received message:", JSON.stringify(message, null, 2));
72
90
  };
package/dist/proxy.js CHANGED
@@ -9,8 +9,9 @@ import {
9
9
  log,
10
10
  mcpProxy,
11
11
  parseCommandLineArgs,
12
+ performDownstreamAuth,
12
13
  setupSignalHandlers
13
- } from "./chunk-MMS4PSEI.js";
14
+ } from "./chunk-X7AWYUMO.js";
14
15
 
15
16
  // src/proxy.ts
16
17
  import { EventEmitter } from "events";
@@ -138,6 +139,7 @@ async function runProxy(serverUrl, callbackPort, headers, transportStrategy = "h
138
139
  });
139
140
  const localTransport = new StdioServerTransport();
140
141
  let server = null;
142
+ const downstreamResource = discoveryResult.protectedResourceMetadata?.x_downstream_resource;
141
143
  const authInitializer = async () => {
142
144
  const authState = await authCoordinator.initializeAuth();
143
145
  server = authState.server;
@@ -146,12 +148,28 @@ async function runProxy(serverUrl, callbackPort, headers, transportStrategy = "h
146
148
  await new Promise((res) => setTimeout(res, 1e3));
147
149
  }
148
150
  return {
149
- waitForAuthCode: authState.waitForAuthCode,
151
+ waitForAuthCode: async () => {
152
+ const code = await authState.waitForAuthCode();
153
+ if (downstreamResource && !authState.skipBrowserAuth) {
154
+ log("Layer 1 auth complete. Starting downstream (Layer 2) OAuth flow...");
155
+ await performDownstreamAuth(authProvider, downstreamResource, callbackPort);
156
+ log("Layer 2 auth complete.");
157
+ }
158
+ return code;
159
+ },
150
160
  skipBrowserAuth: authState.skipBrowserAuth
151
161
  };
152
162
  };
153
163
  try {
154
- const remoteTransport = await connectToRemoteServer(null, serverUrl, authProvider, headers, authInitializer, transportStrategy);
164
+ const remoteTransport = await connectToRemoteServer(
165
+ null,
166
+ serverUrl,
167
+ authProvider,
168
+ headers,
169
+ authInitializer,
170
+ transportStrategy,
171
+ discoveryResult.downstreamTokenHeader
172
+ );
155
173
  mcpProxy({
156
174
  transportToClient: localTransport,
157
175
  transportToServer: remoteTransport,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "align-mcp-remote",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth",
5
5
  "keywords": [
6
6
  "mcp",