align-mcp-remote 0.1.38 → 0.1.39

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.
@@ -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,92 @@ 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, waitForAuthCode) {
18096
+ debugLog("Starting downstream OAuth flow", { downstreamResource });
18097
+ const downstreamServerMetadata = await fetchAuthorizationServerMetadata(downstreamResource.authorization_server);
18098
+ if (!downstreamServerMetadata) {
18099
+ throw new Error(`Failed to fetch downstream authorization server metadata from ${downstreamResource.authorization_server}`);
18100
+ }
18101
+ const redirectUrl = `http://${authProvider.options.host || "localhost"}:${callbackPort}/oauth/callback`;
18102
+ const scope = downstreamResource.scopes_required?.join(" ") ?? "";
18103
+ const { authorizationUrl, codeVerifier } = await startAuthorization(downstreamResource.authorization_server, {
18104
+ metadata: downstreamServerMetadata,
18105
+ clientInformation: await authProvider.clientInformation(),
18106
+ redirectUrl,
18107
+ scope,
18108
+ resource: new URL(downstreamResource.resource)
18109
+ });
18110
+ log(`
18111
+ Please authorize downstream resource by visiting:
18112
+ ${authorizationUrl.toString()}
18113
+ `);
18114
+ try {
18115
+ const { default: open2 } = await import("open");
18116
+ await open2(authorizationUrl.toString());
18117
+ log("Browser opened automatically for downstream authorization.");
18118
+ } catch {
18119
+ log("Could not open browser automatically. Please copy and paste the URL above.");
18120
+ }
18121
+ const code = await waitForAuthCode();
18122
+ const tokens = await exchangeAuthorization(downstreamResource.authorization_server, {
18123
+ metadata: downstreamServerMetadata,
18124
+ clientInformation: await authProvider.clientInformation(),
18125
+ authorizationCode: code,
18126
+ codeVerifier,
18127
+ redirectUri: redirectUrl
18128
+ });
18129
+ await authProvider.saveDownstreamTokens(tokens);
18130
+ debugLog("Downstream OAuth flow completed, tokens saved");
18131
+ }
18132
+ async function connectToRemoteServer(client, serverUrl, authProvider, headers, authInitializer, transportStrategy = "http-first", downstreamTokenHeader, recursionReasons = /* @__PURE__ */ new Set()) {
18083
18133
  log(`[${pid}] Connecting to remote server: ${serverUrl}`);
18084
18134
  const url2 = new URL(serverUrl);
18135
+ const resolveDownstreamHeader = async () => {
18136
+ if (!downstreamTokenHeader) return {};
18137
+ const provider = authProvider;
18138
+ if (typeof provider.downstreamTokens !== "function") return {};
18139
+ const downstreamTokens = await provider.downstreamTokens();
18140
+ if (downstreamTokens?.access_token) {
18141
+ return { [downstreamTokenHeader]: `Bearer ${downstreamTokens.access_token}` };
18142
+ }
18143
+ return {};
18144
+ };
18085
18145
  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
- );
18146
+ fetch: async (url3, init) => {
18147
+ const [tokens, downstreamHeader2] = await Promise.all([authProvider?.tokens?.(), resolveDownstreamHeader()]);
18148
+ return fetch2(url3, {
18149
+ ...init,
18150
+ headers: {
18151
+ ...init?.headers instanceof Headers2 ? Object.fromEntries(init?.headers.entries()) : init?.headers || {},
18152
+ ...headers,
18153
+ ...tokens?.access_token ? { Authorization: `Bearer ${tokens.access_token}` } : {},
18154
+ ...downstreamHeader2,
18155
+ Accept: "text/event-stream"
18156
+ }
18157
+ });
18098
18158
  }
18099
18159
  };
18100
18160
  log(`Using transport strategy: ${transportStrategy}`);
18101
18161
  const shouldAttemptFallback2 = transportStrategy === "http-first" || transportStrategy === "sse-first";
18102
18162
  const sseTransport = transportStrategy === "sse-only" || transportStrategy === "sse-first";
18163
+ const downstreamHeader = await resolveDownstreamHeader();
18164
+ const mergedHeaders = { ...headers, ...downstreamHeader };
18103
18165
  const transport = sseTransport ? new SSEClientTransport(url2, {
18104
18166
  authProvider,
18105
- requestInit: { headers },
18167
+ requestInit: { headers: mergedHeaders },
18106
18168
  eventSourceInit
18107
18169
  }) : new StreamableHTTPClientTransport(url2, {
18108
18170
  authProvider,
18109
- requestInit: { headers }
18171
+ requestInit: { headers: mergedHeaders }
18110
18172
  });
18111
18173
  try {
18112
18174
  debugLog("Attempting to connect to remote server", { sseTransport });
@@ -18145,6 +18207,7 @@ async function connectToRemoteServer(client, serverUrl, authProvider, headers, a
18145
18207
  headers,
18146
18208
  authInitializer,
18147
18209
  sseTransport ? "http-only" : "sse-only",
18210
+ downstreamTokenHeader,
18148
18211
  recursionReasons
18149
18212
  );
18150
18213
  } else if (error2 instanceof UnauthorizedError || error2 instanceof Error && error2.message.includes("Unauthorized")) {
@@ -18179,7 +18242,7 @@ async function connectToRemoteServer(client, serverUrl, authProvider, headers, a
18179
18242
  recursionReasons.add(REASON_AUTH_NEEDED);
18180
18243
  log(`Recursively reconnecting for reason: ${REASON_AUTH_NEEDED}`);
18181
18244
  debugLog("Recursively reconnecting after auth", { recursionReasons: Array.from(recursionReasons) });
18182
- return connectToRemoteServer(client, serverUrl, authProvider, headers, authInitializer, transportStrategy, recursionReasons);
18245
+ return connectToRemoteServer(client, serverUrl, authProvider, headers, authInitializer, transportStrategy, downstreamTokenHeader, recursionReasons);
18183
18246
  } catch (authError) {
18184
18247
  log("Authorization error:", authError);
18185
18248
  debugLog("Authorization error during finishAuth", {
@@ -18759,6 +18822,20 @@ ${authorizationUrl.toString()}
18759
18822
  debugLog("Code verifier found:", !!verifier);
18760
18823
  return verifier;
18761
18824
  }
18825
+ /**
18826
+ * Gets the downstream OAuth tokens (Layer 2) if they exist
18827
+ */
18828
+ async downstreamTokens() {
18829
+ debugLog("Reading downstream OAuth tokens");
18830
+ return readJsonFile(this.serverUrlHash, "downstream_tokens.json", OAuthTokensSchema);
18831
+ }
18832
+ /**
18833
+ * Saves downstream OAuth tokens (Layer 2)
18834
+ */
18835
+ async saveDownstreamTokens(tokens) {
18836
+ debugLog("Saving downstream tokens", { hasAccessToken: !!tokens.access_token });
18837
+ await writeJsonFile(this.serverUrlHash, "downstream_tokens.json", tokens);
18838
+ }
18762
18839
  /**
18763
18840
  * Invalidates the specified credentials
18764
18841
  * @param scope The scope of credentials to invalidate
@@ -18770,6 +18847,7 @@ ${authorizationUrl.toString()}
18770
18847
  await Promise.all([
18771
18848
  deleteConfigFile(this.serverUrlHash, "client_info.json"),
18772
18849
  deleteConfigFile(this.serverUrlHash, "tokens.json"),
18850
+ deleteConfigFile(this.serverUrlHash, "downstream_tokens.json"),
18773
18851
  deleteConfigFile(this.serverUrlHash, "code_verifier.txt")
18774
18852
  ]);
18775
18853
  this._clientInfo = void 0;
@@ -18990,6 +19068,7 @@ export {
18990
19068
  log,
18991
19069
  mcpProxy,
18992
19070
  discoverOAuthServerInfo2 as discoverOAuthServerInfo,
19071
+ performDownstreamAuth,
18993
19072
  connectToRemoteServer,
18994
19073
  parseCommandLineArgs,
18995
19074
  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-V2QU44UI.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, authState.waitForAuthCode);
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-V2QU44UI.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, authState.waitForAuthCode);
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.39",
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",