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
|
|
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) {
|
package/build/services/index.js
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
205
|
+
this._status = isAuthError(err) ? 'unauthorized' : 'unavailable';
|
|
206
206
|
throw err;
|
|
207
207
|
}
|
|
208
208
|
}
|