mcp-compress-router 1.5.1 → 1.5.2

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.
@@ -20,3 +20,76 @@ export class GuidedAuthError extends Error {
20
20
  this.serverName = serverName;
21
21
  }
22
22
  }
23
+ /**
24
+ * HTTP status codes that indicate an authentication or authorization
25
+ * failure at the transport layer. The MCP SDK's streamable HTTP
26
+ * transport surfaces the upstream response status as a numeric `code`
27
+ * property on the thrown error — e.g. 401 when the server rejects a
28
+ * request without driving the SDK's `redirectToAuthorization` flow.
29
+ */
30
+ const AUTH_ERROR_STATUSES = new Set([401, 403]);
31
+ /**
32
+ * Error message substrings that indicate a downstream OAuth /
33
+ * access-token failure rather than a network, transport, or tool-level
34
+ * error. These surface when a server rejects a request in the response
35
+ * body (e.g. with an OAuth error code) instead of returning a clean 401
36
+ * that would drive the SDK's `redirectToAuthorization` flow (which
37
+ * throws {@link GuidedAuthError} directly).
38
+ *
39
+ * Matching is case-insensitive to tolerate casing differences across
40
+ * SDK versions and server implementations.
41
+ */
42
+ const AUTH_ERROR_PATTERNS = [
43
+ 'invalid_token',
44
+ 'invalid_grant',
45
+ 'invalid_client',
46
+ 'missing or invalid access token',
47
+ ];
48
+ /**
49
+ * Reads the HTTP status code attached to an error, if any.
50
+ *
51
+ * The MCP SDK's `StreamableHTTPError` exposes the upstream HTTP status
52
+ * as a numeric `code` property. JSON-RPC error codes (e.g. `-32601`
53
+ * Method not found) are negative and never collide with HTTP status
54
+ * codes, so a positive value here is unambiguously an HTTP status.
55
+ * Duck-typed to avoid importing the SDK error class (mirrors the
56
+ * `isMethodNotFound` approach in `discovery.ts`).
57
+ *
58
+ * @param err - The thrown value from connect, reconnect, or invoke.
59
+ * @returns The HTTP status code, or `undefined` when none is present.
60
+ */
61
+ function getHttpStatus(err) {
62
+ if (typeof err === 'object' && err !== null && 'code' in err) {
63
+ const code = err.code;
64
+ return typeof code === 'number' ? code : undefined;
65
+ }
66
+ return undefined;
67
+ }
68
+ /**
69
+ * Determines whether an error represents an authentication failure
70
+ * (missing, invalid, or expired OAuth credentials) rather than a
71
+ * network, transport, or tool-level error.
72
+ *
73
+ * Returns true for {@link GuidedAuthError} instances, for raw
74
+ * transport errors carrying an HTTP 401/403 status (read from the
75
+ * `code` property), and for errors whose message carries an OAuth
76
+ * error code such as `invalid_token` or `invalid_grant`. Without
77
+ * this, a server that rejects a request is misclassified as a generic
78
+ * connection failure, and the guided error tells the user to check
79
+ * their network instead of running `login`.
80
+ *
81
+ * @param err - The error thrown during connect, reconnect, or invoke.
82
+ * @returns True when the error indicates authentication is required.
83
+ */
84
+ export function isAuthError(err) {
85
+ if (err instanceof GuidedAuthError) {
86
+ return true;
87
+ }
88
+ const status = getHttpStatus(err);
89
+ if (status !== undefined && AUTH_ERROR_STATUSES.has(status)) {
90
+ return true;
91
+ }
92
+ const message = err instanceof Error ? err.message : String(err);
93
+ const lower = message.toLowerCase();
94
+ return AUTH_ERROR_PATTERNS.some((pattern) => lower.includes(pattern));
95
+ }
@@ -95,8 +95,7 @@ function restartGuidance(isStdio) {
95
95
  : 'If the issue persists, restart the MCP server in your coding agent.';
96
96
  return (restartClause +
97
97
  ' If you have already fixed the issue, restart the MCP server in your ' +
98
- 'coding agent (e.g. restart Claude Code, opencode, or Codex) so it ' +
99
- 're-initializes the connection.');
98
+ 'coding agent so it re-initializes the connection.');
100
99
  }
101
100
  function extractMessage(err) {
102
101
  if (err instanceof Error) {
@@ -9,5 +9,5 @@ export { saveToolCache } from './tool-cache.js';
9
9
  export { OAuthCredentialManager } from './oauth.js';
10
10
  export { computeAuthStatus, persistAuthRequirements } from './auth-status.js';
11
11
  export { discoverAuth } from './oauth-discovery.js';
12
- export { GuidedAuthError } from './auth-errors.js';
12
+ export { GuidedAuthError, isAuthError } from './auth-errors.js';
13
13
  export { buildGuidedError } from './guided-error.js';
@@ -1,5 +1,5 @@
1
1
  import { updateServerInCatalog } from './catalog.js';
2
- import { buildGuidedError, GuidedAuthError } from './index.js';
2
+ import { buildGuidedError, isAuthError } from './index.js';
3
3
  /**
4
4
  * Set of error message substrings that indicate a recoverable
5
5
  * network/transport failure (as opposed to a tool-level or protocol
@@ -30,7 +30,7 @@ const RECOVERABLE_PATTERNS = [
30
30
  * @returns True when a reconnect + retry might succeed.
31
31
  */
32
32
  export function isRecoverable(err) {
33
- if (err instanceof GuidedAuthError) {
33
+ if (isAuthError(err)) {
34
34
  return true;
35
35
  }
36
36
  const message = err instanceof Error ? err.message : String(err);
@@ -154,7 +154,7 @@ async function invokeToolWithRetry(conn, tool, args, server, serverConfig, selec
154
154
  // server is reachable, so 'unavailable' would be misleading —
155
155
  // surface auth errors as 'authentication required', re-throw
156
156
  // the rest as-is so the caller sees the real downstream error.
157
- if (retryErr instanceof GuidedAuthError) {
157
+ if (isAuthError(retryErr)) {
158
158
  throw buildGuidedError(serverConfig, retryErr, 'unauthorized', true);
159
159
  }
160
160
  throw retryErr;
@@ -162,7 +162,7 @@ async function invokeToolWithRetry(conn, tool, args, server, serverConfig, selec
162
162
  // Reconnect itself failed: the server is down or requires auth.
163
163
  // doReconnect has already transitioned the connection's status
164
164
  // + cooldown so subsequent calls back off.
165
- const status = retryErr instanceof GuidedAuthError ? 'unauthorized' : 'unavailable';
165
+ const status = isAuthError(retryErr) ? 'unauthorized' : 'unavailable';
166
166
  throw buildGuidedError(serverConfig, retryErr, status, true);
167
167
  }
168
168
  }
@@ -2,7 +2,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
2
  import { createTransport, listToolsOrEmpty } from './discovery.js';
3
3
  import { OAuthCredentialManager } from './oauth.js';
4
4
  import { saveToolCache, loadToolCache } from './tool-cache.js';
5
- import { GuidedAuthError } from './index.js';
5
+ import { isAuthError } from './index.js';
6
6
  /**
7
7
  * 30-second cooldown after a failed reconnect attempt. Subsequent
8
8
  * `invokeWithRecovery` calls within this window return the cached
@@ -122,7 +122,7 @@ export class ServerConnection {
122
122
  cause: err,
123
123
  });
124
124
  }
125
- const status = err instanceof GuidedAuthError ? 'unauthorized' : 'unavailable';
125
+ const status = isAuthError(err) ? 'unauthorized' : 'unavailable';
126
126
  this._status = status;
127
127
  this.logger.warn(`Server "${this.server.name}" failed (${status}) — using ${cachedTools.length} cached tools`, { server: this.server.name, type: this.server.type, error: message, status });
128
128
  return {
@@ -202,7 +202,7 @@ export class ServerConnection {
202
202
  // backoff. Auth failures are classified as 'unauthorized' so
203
203
  // guided errors point at `login` instead of "connection failed".
204
204
  this._lastError = err instanceof Error ? err.message : String(err);
205
- this._status = err instanceof GuidedAuthError ? 'unauthorized' : 'unavailable';
205
+ this._status = isAuthError(err) ? 'unauthorized' : 'unavailable';
206
206
  throw err;
207
207
  }
208
208
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-compress-router",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "description": "Compress all connected MCP servers into a single router MCP to save tokens",
5
5
  "license": "MIT",
6
6
  "type": "module",