pi-mcp-adapter 2.2.1 → 2.3.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.
- package/CHANGELOG.md +36 -0
- package/README.md +1 -2
- package/commands.ts +48 -20
- package/direct-tools.ts +11 -3
- package/index.ts +96 -38
- package/init.ts +19 -3
- package/mcp-auth-flow.ts +378 -0
- package/mcp-auth.ts +277 -0
- package/mcp-callback-server.ts +251 -0
- package/mcp-oauth-provider.ts +265 -0
- package/package.json +14 -6
- package/proxy-modes.ts +31 -0
- package/server-manager.ts +66 -24
- package/types.ts +27 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.3.2] - 2026-04-11
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Optional `oauth.grantType: "client_credentials"` for non-interactive machine-to-machine OAuth on HTTP MCP servers.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- `/mcp-auth <server>` now handles `client_credentials` without browser/callback flow.
|
|
17
|
+
- MCP panel status no longer marks `client_credentials` servers as auth-blocked solely because no stored user tokens exist yet.
|
|
18
|
+
- OAuth auth flow now closes temporary transports consistently on success, refresh, and auth removal paths.
|
|
19
|
+
- Init paths now preserve debug-level context for previously silent direct-tool bootstrap and lazy-connect failures.
|
|
20
|
+
|
|
21
|
+
## [2.3.1] - 2026-04-11
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- Removed `/mcp-auth-callback`. OAuth auth now hard-cuts to `/mcp-auth <server>` only.
|
|
25
|
+
|
|
26
|
+
## [2.3.0] - 2026-04-11
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- OAuth callback server initialization on session start and a deprecated `/mcp-auth-callback` command that now points users to `/mcp-auth <server>`.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
- OAuth `needs-auth` handling across `/mcp` status/panel, `mcp({ connect })`, `mcp({ tool })`, reconnect flow, lazy/direct tool execution, and startup bootstrap.
|
|
33
|
+
- OAuth callback cleanup now cancels by stored OAuth state and closes pending transports on failure/cancel paths.
|
|
34
|
+
- Callback server now fails fast when the OAuth callback port is occupied by another process.
|
|
35
|
+
- Package manifest test now ignores root `*.test.ts` files.
|
|
36
|
+
|
|
37
|
+
## [2.2.2] - 2026-04-03
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
- Session lifecycle teardown now handles repeated `session_start` transitions safely and prevents stale async init results from replacing newer state.
|
|
41
|
+
- Shutdown now still runs `gracefulShutdown()` even if metadata cache flushing throws, avoiding leaked MCP processes.
|
|
42
|
+
- Proxy/direct tool init error paths now preserve and surface underlying error messages instead of returning generic failures.
|
|
43
|
+
- Invalid `mcp` tool `args` now fail by throwing with parse/type context instead of returning non-failing tool payloads.
|
|
44
|
+
- Added focused lifecycle regressions tests for stale init cleanup and init-error visibility.
|
|
45
|
+
|
|
10
46
|
## [2.2.1] - 2026-03-23
|
|
11
47
|
|
|
12
48
|
### Fixed
|
package/README.md
CHANGED
|
@@ -85,6 +85,7 @@ Two calls instead of 26 tools cluttering the context.
|
|
|
85
85
|
| `cwd` | Working directory |
|
|
86
86
|
| `url` | HTTP endpoint (StreamableHTTP with SSE fallback) |
|
|
87
87
|
| `auth` | `"bearer"` or `"oauth"` |
|
|
88
|
+
| `oauth.grantType` | `"authorization_code"` (default) or `"client_credentials"` for non-interactive machine auth |
|
|
88
89
|
| `bearerToken` / `bearerTokenEnv` | Token or env var name |
|
|
89
90
|
| `lifecycle` | `"lazy"` (default), `"eager"`, or `"keep-alive"` |
|
|
90
91
|
| `idleTimeout` | Minutes before idle disconnect (overrides global) |
|
|
@@ -291,6 +292,4 @@ Tool names are fuzzy-matched on hyphens and underscores — `context7_resolve_li
|
|
|
291
292
|
|
|
292
293
|
## Limitations
|
|
293
294
|
|
|
294
|
-
- OAuth tokens obtained externally (no browser flow)
|
|
295
|
-
- No automatic token refresh
|
|
296
295
|
- Cross-session server sharing not yet implemented (each Pi session runs its own server processes)
|
package/commands.ts
CHANGED
|
@@ -4,8 +4,9 @@ import type { McpConfig, ServerEntry, McpPanelCallbacks, McpPanelResult } from "
|
|
|
4
4
|
import { getServerProvenance, writeDirectToolsConfig } from "./config.js";
|
|
5
5
|
import { lazyConnect, updateMetadataCache, updateStatusBar, getFailureAgeSeconds } from "./init.js";
|
|
6
6
|
import { loadMetadataCache } from "./metadata-cache.js";
|
|
7
|
-
import { getStoredTokens } from "./oauth-handler.js";
|
|
8
7
|
import { buildToolMetadata } from "./tool-metadata.js";
|
|
8
|
+
import { supportsOAuth, authenticate } from "./mcp-auth-flow.js";
|
|
9
|
+
import { hasStoredTokens } from "./mcp-auth.js";
|
|
9
10
|
|
|
10
11
|
export async function showStatus(state: McpExtensionState, ctx: ExtensionContext): Promise<void> {
|
|
11
12
|
if (!ctx.hasUI) return;
|
|
@@ -24,6 +25,9 @@ export async function showStatus(state: McpExtensionState, ctx: ExtensionContext
|
|
|
24
25
|
if (connection?.status === "connected") {
|
|
25
26
|
status = "connected";
|
|
26
27
|
statusIcon = "✓";
|
|
28
|
+
} else if (connection?.status === "needs-auth") {
|
|
29
|
+
status = "needs auth";
|
|
30
|
+
statusIcon = "⚠";
|
|
27
31
|
} else if (failedAgo !== null) {
|
|
28
32
|
status = `failed ${failedAgo}s ago`;
|
|
29
33
|
statusIcon = "✗";
|
|
@@ -85,6 +89,12 @@ export async function reconnectServers(
|
|
|
85
89
|
await state.manager.close(name);
|
|
86
90
|
|
|
87
91
|
const connection = await state.manager.connect(name, definition);
|
|
92
|
+
if (connection.status === "needs-auth") {
|
|
93
|
+
if (ctx.hasUI) {
|
|
94
|
+
ctx.ui.notify(`MCP: ${name} requires OAuth. Run /mcp-auth ${name} first.`, "warning");
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
88
98
|
const prefix = state.config.settings?.toolPrefix ?? "server";
|
|
89
99
|
|
|
90
100
|
const { metadata, failedTools } = buildToolMetadata(connection.tools, connection.resources, definition, name, prefix);
|
|
@@ -126,10 +136,10 @@ export async function authenticateServer(
|
|
|
126
136
|
return;
|
|
127
137
|
}
|
|
128
138
|
|
|
129
|
-
if (definition
|
|
139
|
+
if (!supportsOAuth(definition)) {
|
|
130
140
|
ctx.ui.notify(
|
|
131
141
|
`Server "${serverName}" does not use OAuth authentication.\n` +
|
|
132
|
-
`
|
|
142
|
+
`Set "auth": "oauth" or omit auth for auto-detection.`,
|
|
133
143
|
"error"
|
|
134
144
|
);
|
|
135
145
|
return;
|
|
@@ -143,21 +153,31 @@ export async function authenticateServer(
|
|
|
143
153
|
return;
|
|
144
154
|
}
|
|
145
155
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
156
|
+
// Full automatic OAuth flow using SDK
|
|
157
|
+
try {
|
|
158
|
+
ctx.ui.setStatus("mcp-auth", `Authenticating ${serverName}...`);
|
|
159
|
+
|
|
160
|
+
// Runs the configured OAuth flow (interactive browser or non-interactive client_credentials)
|
|
161
|
+
const status = await authenticate(serverName, definition.url, definition);
|
|
162
|
+
|
|
163
|
+
if (status === "authenticated") {
|
|
164
|
+
ctx.ui.notify(
|
|
165
|
+
`OAuth authentication successful for "${serverName}"!\n` +
|
|
166
|
+
`Run /mcp reconnect ${serverName} to connect with the new token.`,
|
|
167
|
+
"success"
|
|
168
|
+
);
|
|
169
|
+
} else {
|
|
170
|
+
ctx.ui.notify(
|
|
171
|
+
`OAuth authentication failed for "${serverName}".`,
|
|
172
|
+
"error"
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
} catch (error) {
|
|
176
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
177
|
+
ctx.ui.notify(`Failed to authenticate "${serverName}": ${message}`, "error");
|
|
178
|
+
} finally {
|
|
179
|
+
ctx.ui.setStatus("mcp-auth", undefined);
|
|
180
|
+
}
|
|
161
181
|
}
|
|
162
182
|
|
|
163
183
|
export async function openMcpPanel(
|
|
@@ -176,10 +196,18 @@ export async function openMcpPanel(
|
|
|
176
196
|
},
|
|
177
197
|
getConnectionStatus: (serverName: string) => {
|
|
178
198
|
const definition = config.mcpServers[serverName];
|
|
179
|
-
|
|
199
|
+
const connection = state.manager.getConnection(serverName);
|
|
200
|
+
if (connection?.status === "needs-auth") {
|
|
201
|
+
return "needs-auth";
|
|
202
|
+
}
|
|
203
|
+
if (
|
|
204
|
+
definition?.auth === "oauth"
|
|
205
|
+
&& definition.oauth !== false
|
|
206
|
+
&& definition.oauth?.grantType !== "client_credentials"
|
|
207
|
+
&& !hasStoredTokens(serverName)
|
|
208
|
+
) {
|
|
180
209
|
return "needs-auth";
|
|
181
210
|
}
|
|
182
|
-
const connection = state.manager.getConnection(serverName);
|
|
183
211
|
if (connection?.status === "connected") return "connected";
|
|
184
212
|
if (getFailureAgeSeconds(state, serverName) !== null) return "failed";
|
|
185
213
|
return "idle";
|
package/direct-tools.ts
CHANGED
|
@@ -181,10 +181,11 @@ export function createDirectToolExecutor(
|
|
|
181
181
|
if (!state && initPromise) {
|
|
182
182
|
try {
|
|
183
183
|
state = await initPromise;
|
|
184
|
-
} catch {
|
|
184
|
+
} catch (error) {
|
|
185
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
185
186
|
return {
|
|
186
|
-
content: [{ type: "text" as const, text:
|
|
187
|
-
details: { error: "init_failed" },
|
|
187
|
+
content: [{ type: "text" as const, text: `MCP initialization failed: ${message}` }],
|
|
188
|
+
details: { error: "init_failed", message },
|
|
188
189
|
};
|
|
189
190
|
}
|
|
190
191
|
}
|
|
@@ -197,6 +198,13 @@ export function createDirectToolExecutor(
|
|
|
197
198
|
|
|
198
199
|
const connected = await lazyConnect(state, spec.serverName);
|
|
199
200
|
if (!connected) {
|
|
201
|
+
const authConnection = state.manager.getConnection(spec.serverName);
|
|
202
|
+
if (authConnection?.status === "needs-auth") {
|
|
203
|
+
return {
|
|
204
|
+
content: [{ type: "text" as const, text: `MCP server "${spec.serverName}" requires OAuth authentication. Run /mcp-auth ${spec.serverName} first.` }],
|
|
205
|
+
details: { error: "auth_required", server: spec.serverName },
|
|
206
|
+
};
|
|
207
|
+
}
|
|
200
208
|
const failedAgo = getFailureAgeSeconds(state, spec.serverName);
|
|
201
209
|
return {
|
|
202
210
|
content: [{ type: "text" as const, text: `MCP server "${spec.serverName}" not available${failedAgo !== null ? ` (failed ${failedAgo}s ago)` : ""}` }],
|
package/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionAPI,
|
|
1
|
+
import type { ExtensionAPI, ToolInfo } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import type { McpExtensionState } from "./state.js";
|
|
3
3
|
import { Type } from "@sinclair/typebox";
|
|
4
4
|
import { showStatus, showTools, reconnectServers, authenticateServer, openMcpPanel } from "./commands.js";
|
|
@@ -8,10 +8,42 @@ import { flushMetadataCache, initializeMcp, updateStatusBar } from "./init.js";
|
|
|
8
8
|
import { loadMetadataCache } from "./metadata-cache.js";
|
|
9
9
|
import { executeCall, executeConnect, executeDescribe, executeList, executeSearch, executeStatus, executeUiMessages } from "./proxy-modes.js";
|
|
10
10
|
import { getConfigPathFromArgv, truncateAtWord } from "./utils.js";
|
|
11
|
+
import { initializeOAuth } from "./mcp-auth-flow.js";
|
|
11
12
|
|
|
12
13
|
export default function mcpAdapter(pi: ExtensionAPI) {
|
|
13
14
|
let state: McpExtensionState | null = null;
|
|
14
15
|
let initPromise: Promise<McpExtensionState> | null = null;
|
|
16
|
+
let lifecycleGeneration = 0;
|
|
17
|
+
|
|
18
|
+
async function shutdownState(currentState: McpExtensionState | null, reason: string): Promise<void> {
|
|
19
|
+
if (!currentState) return;
|
|
20
|
+
|
|
21
|
+
if (currentState.uiServer) {
|
|
22
|
+
currentState.uiServer.close(reason);
|
|
23
|
+
currentState.uiServer = null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let flushError: unknown;
|
|
27
|
+
try {
|
|
28
|
+
flushMetadataCache(currentState);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
flushError = error;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await currentState.lifecycle.gracefulShutdown();
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (flushError) {
|
|
37
|
+
console.error("MCP: graceful shutdown failed after metadata flush error", error);
|
|
38
|
+
} else {
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (flushError) {
|
|
44
|
+
throw flushError;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
15
47
|
|
|
16
48
|
const earlyConfigPath = getConfigPathFromArgv();
|
|
17
49
|
const earlyConfig = loadMcpConfig(earlyConfigPath);
|
|
@@ -47,35 +79,63 @@ export default function mcpAdapter(pi: ExtensionAPI) {
|
|
|
47
79
|
});
|
|
48
80
|
|
|
49
81
|
pi.on("session_start", async (_event, ctx) => {
|
|
50
|
-
|
|
82
|
+
const generation = ++lifecycleGeneration;
|
|
83
|
+
const previousState = state;
|
|
84
|
+
state = null;
|
|
85
|
+
initPromise = null;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
await shutdownState(previousState, "session_restart");
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error("MCP: failed to shut down previous session state", error);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (generation !== lifecycleGeneration) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
await initializeOAuth().catch(err => {
|
|
98
|
+
console.error("MCP OAuth initialization failed:", err);
|
|
99
|
+
});
|
|
51
100
|
|
|
52
|
-
|
|
53
|
-
|
|
101
|
+
const promise = initializeMcp(pi, ctx);
|
|
102
|
+
initPromise = promise;
|
|
103
|
+
|
|
104
|
+
promise.then(async (nextState) => {
|
|
105
|
+
if (generation !== lifecycleGeneration || initPromise !== promise) {
|
|
106
|
+
try {
|
|
107
|
+
await shutdownState(nextState, "stale_session_start");
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error("MCP: failed to clean stale session state", error);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
state = nextState;
|
|
115
|
+
updateStatusBar(nextState);
|
|
54
116
|
initPromise = null;
|
|
55
|
-
updateStatusBar(s);
|
|
56
117
|
}).catch(err => {
|
|
118
|
+
if (generation !== lifecycleGeneration) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (initPromise !== promise && initPromise !== null) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
57
124
|
console.error("MCP initialization failed:", err);
|
|
58
125
|
initPromise = null;
|
|
59
126
|
});
|
|
60
127
|
});
|
|
61
128
|
|
|
62
129
|
pi.on("session_shutdown", async () => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// Initialization failed, nothing to clean up
|
|
68
|
-
}
|
|
69
|
-
}
|
|
130
|
+
++lifecycleGeneration;
|
|
131
|
+
const currentState = state;
|
|
132
|
+
state = null;
|
|
133
|
+
initPromise = null;
|
|
70
134
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
flushMetadataCache(state);
|
|
77
|
-
await state.lifecycle.gracefulShutdown();
|
|
78
|
-
state = null;
|
|
135
|
+
try {
|
|
136
|
+
await shutdownState(currentState, "session_shutdown");
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error("MCP: session shutdown cleanup failed", error);
|
|
79
139
|
}
|
|
80
140
|
});
|
|
81
141
|
|
|
@@ -85,8 +145,9 @@ export default function mcpAdapter(pi: ExtensionAPI) {
|
|
|
85
145
|
if (!state && initPromise) {
|
|
86
146
|
try {
|
|
87
147
|
state = await initPromise;
|
|
88
|
-
} catch {
|
|
89
|
-
|
|
148
|
+
} catch (error) {
|
|
149
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
150
|
+
if (ctx.hasUI) ctx.ui.notify(`MCP initialization failed: ${message}`, "error");
|
|
90
151
|
return;
|
|
91
152
|
}
|
|
92
153
|
}
|
|
@@ -131,8 +192,9 @@ export default function mcpAdapter(pi: ExtensionAPI) {
|
|
|
131
192
|
if (!state && initPromise) {
|
|
132
193
|
try {
|
|
133
194
|
state = await initPromise;
|
|
134
|
-
} catch {
|
|
135
|
-
|
|
195
|
+
} catch (error) {
|
|
196
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
197
|
+
if (ctx.hasUI) ctx.ui.notify(`MCP initialization failed: ${message}`, "error");
|
|
136
198
|
return;
|
|
137
199
|
}
|
|
138
200
|
}
|
|
@@ -178,28 +240,24 @@ export default function mcpAdapter(pi: ExtensionAPI) {
|
|
|
178
240
|
parsedArgs = JSON.parse(params.args);
|
|
179
241
|
if (typeof parsedArgs !== "object" || parsedArgs === null || Array.isArray(parsedArgs)) {
|
|
180
242
|
const gotType = Array.isArray(parsedArgs) ? "array" : parsedArgs === null ? "null" : typeof parsedArgs;
|
|
181
|
-
|
|
182
|
-
content: [{ type: "text" as const, text: `Invalid args: expected a JSON object, got ${gotType}` }],
|
|
183
|
-
isError: true,
|
|
184
|
-
details: { error: "invalid_args_type" },
|
|
185
|
-
};
|
|
243
|
+
throw new Error(`Invalid args: expected a JSON object, got ${gotType}`);
|
|
186
244
|
}
|
|
187
|
-
} catch (
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
};
|
|
245
|
+
} catch (error) {
|
|
246
|
+
if (error instanceof SyntaxError) {
|
|
247
|
+
throw new Error(`Invalid args JSON: ${error.message}`, { cause: error });
|
|
248
|
+
}
|
|
249
|
+
throw error;
|
|
193
250
|
}
|
|
194
251
|
}
|
|
195
252
|
|
|
196
253
|
if (!state && initPromise) {
|
|
197
254
|
try {
|
|
198
255
|
state = await initPromise;
|
|
199
|
-
} catch {
|
|
256
|
+
} catch (error) {
|
|
257
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
200
258
|
return {
|
|
201
|
-
content: [{ type: "text" as const, text:
|
|
202
|
-
details: { error: "init_failed" },
|
|
259
|
+
content: [{ type: "text" as const, text: `MCP initialization failed: ${message}` }],
|
|
260
|
+
details: { error: "init_failed", message },
|
|
203
261
|
};
|
|
204
262
|
}
|
|
205
263
|
}
|
package/init.ts
CHANGED
|
@@ -108,6 +108,9 @@ export async function initializeMcp(
|
|
|
108
108
|
const results = await parallelLimit(startupServers, 10, async ([name, definition]) => {
|
|
109
109
|
try {
|
|
110
110
|
const connection = await manager.connect(name, definition);
|
|
111
|
+
if (connection.status === "needs-auth") {
|
|
112
|
+
return { name, definition, connection: null, error: `OAuth authentication required. Run /mcp-auth ${name}.` };
|
|
113
|
+
}
|
|
111
114
|
return { name, definition, connection, error: null };
|
|
112
115
|
} catch (error) {
|
|
113
116
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -169,11 +172,16 @@ export async function initializeMcp(
|
|
|
169
172
|
const definition = config.mcpServers[name];
|
|
170
173
|
try {
|
|
171
174
|
const connection = await manager.connect(name, definition);
|
|
175
|
+
if (connection.status === "needs-auth") {
|
|
176
|
+
return { name, ok: false };
|
|
177
|
+
}
|
|
172
178
|
const { metadata } = buildToolMetadata(connection.tools, connection.resources, definition, name, prefix);
|
|
173
179
|
toolMetadata.set(name, metadata);
|
|
174
180
|
updateMetadataCache(state, name);
|
|
175
181
|
return { name, ok: true };
|
|
176
|
-
} catch {
|
|
182
|
+
} catch (error) {
|
|
183
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
184
|
+
logger.debug(`MCP: direct-tools bootstrap failed for ${name}: ${message}`);
|
|
177
185
|
return { name, ok: false };
|
|
178
186
|
}
|
|
179
187
|
},
|
|
@@ -279,6 +287,9 @@ export function getFailureAgeSeconds(state: McpExtensionState, serverName: strin
|
|
|
279
287
|
|
|
280
288
|
export async function lazyConnect(state: McpExtensionState, serverName: string): Promise<boolean> {
|
|
281
289
|
const connection = state.manager.getConnection(serverName);
|
|
290
|
+
if (connection?.status === "needs-auth") {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
282
293
|
if (connection?.status === "connected") {
|
|
283
294
|
updateServerMetadata(state, serverName);
|
|
284
295
|
return true;
|
|
@@ -294,14 +305,19 @@ export async function lazyConnect(state: McpExtensionState, serverName: string):
|
|
|
294
305
|
if (state.ui) {
|
|
295
306
|
state.ui.setStatus("mcp", `MCP: connecting to ${serverName}...`);
|
|
296
307
|
}
|
|
297
|
-
await state.manager.connect(serverName, definition);
|
|
308
|
+
const newConnection = await state.manager.connect(serverName, definition);
|
|
309
|
+
if (newConnection.status === "needs-auth") {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
298
312
|
state.failureTracker.delete(serverName);
|
|
299
313
|
updateServerMetadata(state, serverName);
|
|
300
314
|
updateMetadataCache(state, serverName);
|
|
301
315
|
updateStatusBar(state);
|
|
302
316
|
return true;
|
|
303
|
-
} catch {
|
|
317
|
+
} catch (error) {
|
|
304
318
|
state.failureTracker.set(serverName, Date.now());
|
|
319
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
320
|
+
logger.debug(`MCP: lazy connect failed for ${serverName}: ${message}`);
|
|
305
321
|
updateStatusBar(state);
|
|
306
322
|
return false;
|
|
307
323
|
}
|