Simplify MCP connection handling and remove health checks

- Remove auth type selection from UI - servers will auto-detect OAuth when needed
- Remove auto-reconnect logic and health checks to reduce complexity
- Always initialize OAuth provider for all connections
- Simplify transport selection - always try streamable-http first, then SSE
- Remove unused config options (transport preference, auto-reconnect)
- Add TODO comment about CORS issues for failed fetch errors
- Make authType and transport optional in connection types
- Clean up connection state notifications for better UI updates

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jerome 2025-06-18 18:33:55 +01:00
commit 728fea3665
No known key found for this signature in database
3 changed files with 65 additions and 193 deletions

View file

@ -9,7 +9,6 @@ export function MCPTab() {
const [showAddForm, setShowAddForm] = useState(false); const [showAddForm, setShowAddForm] = useState(false);
const [newServerName, setNewServerName] = useState(''); const [newServerName, setNewServerName] = useState('');
const [newServerUrl, setNewServerUrl] = useState(''); const [newServerUrl, setNewServerUrl] = useState('');
const [authType, setAuthType] = useState<'none' | 'oauth'>('none');
// Collapsible section states // Collapsible section states
const [showSummary, setShowSummary] = useState(true); const [showSummary, setShowSummary] = useState(true);
@ -21,7 +20,6 @@ export function MCPTab() {
await addMcpServer({ await addMcpServer({
name: 'Example Server', name: 'Example Server',
url: 'https://example-server.modelcontextprotocol.io/sse', url: 'https://example-server.modelcontextprotocol.io/sse',
authType: 'oauth',
}); });
} catch (error) { } catch (error) {
console.error('Failed to add example server:', error); console.error('Failed to add example server:', error);
@ -36,13 +34,11 @@ export function MCPTab() {
await addMcpServer({ await addMcpServer({
name: newServerName, name: newServerName,
url: newServerUrl, url: newServerUrl,
authType,
}); });
// Reset form // Reset form
setNewServerName(''); setNewServerName('');
setNewServerUrl(''); setNewServerUrl('');
setAuthType('none');
setShowAddForm(false); setShowAddForm(false);
} catch (error) { } catch (error) {
console.error('Failed to add custom server:', error); console.error('Failed to add custom server:', error);
@ -175,21 +171,6 @@ export function MCPTab() {
required required
/> />
</div> </div>
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
Authentication
</label>
<select
value={authType}
onChange={(e) => setAuthType(e.target.value as 'none' | 'oauth')}
className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="none">None</option>
<option value="oauth">OAuth</option>
</select>
</div>
<button <button
type="submit" type="submit"
disabled={isLoading || !newServerName.trim() || !newServerUrl.trim()} disabled={isLoading || !newServerName.trim() || !newServerUrl.trim()}

View file

@ -4,7 +4,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'; import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
import { import {
auth, auth,
type OAuthClientProvider type OAuthClientProvider
} from '@modelcontextprotocol/sdk/client/auth.js'; } from '@modelcontextprotocol/sdk/client/auth.js';
@ -60,18 +60,18 @@ class MCPOAuthProvider implements OAuthClientProvider {
pendingAuthorizationCode?: string; pendingAuthorizationCode?: string;
authError?: string; authError?: string;
private onOAuthComplete?: () => void; private onOAuthComplete?: () => void;
constructor(connectionId: string, serverName: string, serverUrl: string, onOAuthComplete?: () => void) { constructor(connectionId: string, serverName: string, serverUrl: string, onOAuthComplete?: () => void) {
this.connectionId = connectionId; this.connectionId = connectionId;
this.serverName = serverName; this.serverName = serverName;
this.serverUrl = serverUrl; this.serverUrl = serverUrl;
this.onOAuthComplete = onOAuthComplete; this.onOAuthComplete = onOAuthComplete;
} }
get redirectUrl(): string { get redirectUrl(): string {
return `${window.location.origin}/oauth/mcp/callback`; return `${window.location.origin}/oauth/mcp/callback`;
} }
get clientMetadata(): OAuthClientMetadata { get clientMetadata(): OAuthClientMetadata {
return { return {
redirect_uris: [this.redirectUrl], redirect_uris: [this.redirectUrl],
@ -81,13 +81,13 @@ class MCPOAuthProvider implements OAuthClientProvider {
token_endpoint_auth_method: 'none', // Public client token_endpoint_auth_method: 'none', // Public client
}; };
} }
state(): string { state(): string {
// Encode connection ID in the state parameter for callback identification // Encode connection ID in the state parameter for callback identification
const randomPart = this.generateRandomString(8); const randomPart = this.generateRandomString(8);
return `${this.connectionId}.${randomPart}`; return `${this.connectionId}.${randomPart}`;
} }
// Generate a consistent key for the server based on URL // Generate a consistent key for the server based on URL
getServerKey(): string { getServerKey(): string {
try { try {
@ -106,23 +106,23 @@ class MCPOAuthProvider implements OAuthClientProvider {
const stored = localStorage.getItem(`mcp_oauth_client_${serverKey}`); const stored = localStorage.getItem(`mcp_oauth_client_${serverKey}`);
return stored ? JSON.parse(stored) : undefined; return stored ? JSON.parse(stored) : undefined;
} }
async saveClientInformation(clientInformation: OAuthClientInformation): Promise<void> { async saveClientInformation(clientInformation: OAuthClientInformation): Promise<void> {
const serverKey = this.getServerKey(); const serverKey = this.getServerKey();
localStorage.setItem(`mcp_oauth_client_${serverKey}`, JSON.stringify(clientInformation)); localStorage.setItem(`mcp_oauth_client_${serverKey}`, JSON.stringify(clientInformation));
} }
tokens(): OAuthTokens | undefined { tokens(): OAuthTokens | undefined {
const stored = localStorage.getItem(`mcp_oauth_tokens_${this.connectionId}`); const stored = localStorage.getItem(`mcp_oauth_tokens_${this.connectionId}`);
return stored ? JSON.parse(stored) : undefined; return stored ? JSON.parse(stored) : undefined;
} }
async saveTokens(tokens: OAuthTokens): Promise<void> { async saveTokens(tokens: OAuthTokens): Promise<void> {
localStorage.setItem(`mcp_oauth_tokens_${this.connectionId}`, JSON.stringify(tokens)); localStorage.setItem(`mcp_oauth_tokens_${this.connectionId}`, JSON.stringify(tokens));
} }
async redirectToAuthorization(authorizationUrl: URL): Promise<void> { async redirectToAuthorization(authorizationUrl: URL): Promise<void> {
// Open popup for OAuth flow // Open popup for OAuth flow
const popup = window.open( const popup = window.open(
authorizationUrl.toString(), authorizationUrl.toString(),
@ -138,7 +138,7 @@ class MCPOAuthProvider implements OAuthClientProvider {
const handleMessage = (event: MessageEvent) => { const handleMessage = (event: MessageEvent) => {
if (event.origin !== window.location.origin) return; if (event.origin !== window.location.origin) return;
if (event.data.type !== 'mcp_oauth_callback') return; if (event.data.type !== 'mcp_oauth_callback') return;
// Extract connection ID from state parameter // Extract connection ID from state parameter
const state = event.data.state; const state = event.data.state;
if (!state || !state.startsWith(this.connectionId + '.')) return; if (!state || !state.startsWith(this.connectionId + '.')) return;
@ -157,7 +157,7 @@ class MCPOAuthProvider implements OAuthClientProvider {
window.addEventListener('message', handleMessage); window.addEventListener('message', handleMessage);
} }
async saveCodeVerifier(codeVerifier: string): Promise<void> { async saveCodeVerifier(codeVerifier: string): Promise<void> {
const oauthState: MCPOAuthState = { const oauthState: MCPOAuthState = {
codeVerifier, codeVerifier,
@ -166,22 +166,22 @@ class MCPOAuthProvider implements OAuthClientProvider {
}; };
localStorage.setItem(`mcp_oauth_state_${this.connectionId}`, JSON.stringify(oauthState)); localStorage.setItem(`mcp_oauth_state_${this.connectionId}`, JSON.stringify(oauthState));
} }
async codeVerifier(): Promise<string> { async codeVerifier(): Promise<string> {
const stored = localStorage.getItem(`mcp_oauth_state_${this.connectionId}`); const stored = localStorage.getItem(`mcp_oauth_state_${this.connectionId}`);
if (!stored) { if (!stored) {
throw new Error('No OAuth state found'); throw new Error('No OAuth state found');
} }
const oauthState: MCPOAuthState = JSON.parse(stored); const oauthState: MCPOAuthState = JSON.parse(stored);
if (Date.now() > oauthState.expiresAt) { if (Date.now() > oauthState.expiresAt) {
localStorage.removeItem(`mcp_oauth_state_${this.connectionId}`); localStorage.removeItem(`mcp_oauth_state_${this.connectionId}`);
throw new Error('OAuth state expired'); throw new Error('OAuth state expired');
} }
return oauthState.codeVerifier; return oauthState.codeVerifier;
} }
private generateRandomString(length: number): string { private generateRandomString(length: number): string {
const array = new Uint8Array(length); const array = new Uint8Array(length);
crypto.getRandomValues(array); crypto.getRandomValues(array);
@ -199,7 +199,7 @@ class MCPOAuthProvider implements OAuthClientProvider {
serverUrl: this.serverUrl, serverUrl: this.serverUrl,
authorizationCode, authorizationCode,
}); });
if (result === 'AUTHORIZED') { if (result === 'AUTHORIZED') {
this.notifyOAuthComplete(); this.notifyOAuthComplete();
} else { } else {
@ -230,7 +230,7 @@ class MCPOAuthProvider implements OAuthClientProvider {
export class MCPConnectionManager { export class MCPConnectionManager {
private connection: MCPConnection; private connection: MCPConnection;
private client?: Client; private client?: Client;
private transport?: Transport; private transport?: Transport | StreamableHTTPClientTransport | SSEClientTransport;
private reconnectTimeout?: NodeJS.Timeout; private reconnectTimeout?: NodeJS.Timeout;
private healthCheckInterval?: NodeJS.Timeout; private healthCheckInterval?: NodeJS.Timeout;
private oauthProvider?: MCPOAuthProvider; private oauthProvider?: MCPOAuthProvider;
@ -243,22 +243,16 @@ export class MCPConnectionManager {
name: config.name, name: config.name,
url: config.url, url: config.url,
status: 'disconnected', status: 'disconnected',
transport: 'streamable-http', // Will be determined during connection
authType: config.authType || 'none',
tools: [], tools: [],
resources: [], resources: [],
prompts: [], prompts: [],
connectionAttempts: 0, connectionAttempts: 0,
config, config,
}; };
this.oauthProvider = new MCPOAuthProvider(id, config.name, config.url, () => {
// Initialize OAuth provider if auth is required // Callback when OAuth completes successfully
if (this.connection.authType === 'oauth') { this.handleOAuthSuccess();
this.oauthProvider = new MCPOAuthProvider(id, config.name, config.url, () => { });
// Callback when OAuth completes successfully
this.handleOAuthSuccess();
});
}
} }
getConnection(): MCPConnection { getConnection(): MCPConnection {
@ -272,7 +266,6 @@ export class MCPConnectionManager {
// Set callback for message monitoring // Set callback for message monitoring
setMessageCallback(callback: (connectionId: string, client: any, message: any, direction: 'sent' | 'received', extra?: any) => void): void { setMessageCallback(callback: (connectionId: string, client: any, message: any, direction: 'sent' | 'received', extra?: any) => void): void {
console.log(`Setting message callback for connection ${this.connection.id}`); // Debug log
this.onMessage = callback; this.onMessage = callback;
} }
@ -286,61 +279,37 @@ export class MCPConnectionManager {
async connect(): Promise<void> { async connect(): Promise<void> {
this.connection.status = 'connecting'; this.connection.status = 'connecting';
this.connection.error = undefined; this.connection.error = undefined;
this.notifyConnectionUpdate();
try { try {
// Clear any existing connections // Clear any existing connections
await this.disconnect(); await this.disconnect();
// Determine transport strategy try {
const transportPreference = this.connection.config.transport || 'auto'; await this.tryStreamableHttp();
this.connection.transport = 'streamable-http';
if (transportPreference === 'auto' || transportPreference === 'streamable-http') { } catch (error) {
try { // TODO: jerome - if this is a TypeError: failed to fetch, then there is likely a CORS (or
await this.tryStreamableHttp(); // Access-Control-Expose-Headers) issue with the server.
this.connection.transport = 'streamable-http';
} catch (error) {
if (transportPreference === 'streamable-http') {
throw error; // Don't fallback if explicitly requested
}
// Try SSE fallback
await this.trySSE();
this.connection.transport = 'sse';
}
} else if (transportPreference === 'sse') {
await this.trySSE(); await this.trySSE();
this.connection.transport = 'sse'; this.connection.transport = 'sse';
} }
// Initialize client capabilities // Initialize client capabilities
await this.initializeCapabilities(); await this.initializeCapabilities();
this.connection.status = 'connected'; this.connection.status = 'connected';
this.connection.lastConnected = new Date(); this.connection.lastConnected = new Date();
this.connection.connectionAttempts = 0; this.connection.connectionAttempts = 0;
this.notifyConnectionUpdate();
// Start health check monitoring
this.startHealthCheck();
} catch (error) { } catch (error) {
this.connection.status = 'failed'; this.connection.status = 'failed';
this.connection.error = error instanceof Error ? error.message : 'Unknown connection error'; this.connection.error = error instanceof Error ? error.message : 'Unknown connection error';
this.connection.connectionAttempts++; this.connection.connectionAttempts++;
this.notifyConnectionUpdate();
// Schedule auto-reconnect if enabled
if (this.connection.config.autoReconnect !== false &&
this.connection.connectionAttempts < (this.connection.config.maxReconnectAttempts || 5)) {
const delay = this.getBackoffDelay();
this.reconnectTimeout = setTimeout(async () => {
try {
await this.reconnect();
} catch (error) {
console.error('Auto-reconnect failed:', error);
// Don't throw here to prevent uncaught promise rejection
}
}, delay);
}
throw this.createMCPError('connection', this.connection.error, error); throw this.createMCPError('connection', this.connection.error, error);
} }
} }
@ -350,12 +319,12 @@ export class MCPConnectionManager {
clearTimeout(this.reconnectTimeout); clearTimeout(this.reconnectTimeout);
this.reconnectTimeout = undefined; this.reconnectTimeout = undefined;
} }
if (this.healthCheckInterval) { if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval); clearInterval(this.healthCheckInterval);
this.healthCheckInterval = undefined; this.healthCheckInterval = undefined;
} }
if (this.client) { if (this.client) {
try { try {
await this.client.close(); await this.client.close();
@ -364,7 +333,7 @@ export class MCPConnectionManager {
} }
this.client = undefined; this.client = undefined;
} }
if (this.transport) { if (this.transport) {
try { try {
await this.transport.close(); await this.transport.close();
@ -373,7 +342,7 @@ export class MCPConnectionManager {
} }
this.transport = undefined; this.transport = undefined;
} }
this.connection.status = 'disconnected'; this.connection.status = 'disconnected';
this.connection.client = undefined; this.connection.client = undefined;
} }
@ -384,12 +353,12 @@ export class MCPConnectionManager {
throw new Error('OAuth provider not initialized'); throw new Error('OAuth provider not initialized');
} }
try { try {
// If we have an active transport, use its finishAuth method // If we have an active transport, use its finishAuth method
if (this.transport && typeof (this.transport as any).finishAuth === 'function') { if (this.transport && typeof (this.transport as any).finishAuth === 'function') {
await (this.transport as any).finishAuth(authorizationCode); await (this.transport as any).finishAuth(authorizationCode);
// Now attempt to connect - the transport should be authenticated // Now attempt to connect - the transport should be authenticated
await this.connect(); await this.connect();
} else { } else {
@ -398,7 +367,7 @@ export class MCPConnectionManager {
this.oauthProvider.pendingAuthorizationCode = authorizationCode; this.oauthProvider.pendingAuthorizationCode = authorizationCode;
await this.connect(); await this.connect();
} }
} catch (error) { } catch (error) {
console.error('OAuth callback processing failed:', error); console.error('OAuth callback processing failed:', error);
throw new Error(`OAuth callback failed: ${error instanceof Error ? error.message : 'Unknown error'}`); throw new Error(`OAuth callback failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
@ -411,7 +380,7 @@ export class MCPConnectionManager {
// Clear connection-specific data // Clear connection-specific data
localStorage.removeItem(`mcp_oauth_tokens_${this.connection.id}`); localStorage.removeItem(`mcp_oauth_tokens_${this.connection.id}`);
localStorage.removeItem(`mcp_oauth_state_${this.connection.id}`); localStorage.removeItem(`mcp_oauth_state_${this.connection.id}`);
// Note: We intentionally don't clear client information here since it's // Note: We intentionally don't clear client information here since it's
// shared across connections to the same server. Use clearSharedClientData() // shared across connections to the same server. Use clearSharedClientData()
// if you need to clear the client registration for this server. // if you need to clear the client registration for this server.
@ -432,57 +401,36 @@ export class MCPConnectionManager {
// Handle successful OAuth completion // Handle successful OAuth completion
private async handleOAuthSuccess(): Promise<void> { private async handleOAuthSuccess(): Promise<void> {
try { try {
// Reset connection state and retry
this.connection.status = 'connecting';
this.connection.error = undefined;
this.notifyConnectionUpdate();
// Attempt to connect now that we have valid tokens // Attempt to connect now that we have valid tokens
await this.connect(); await this.connect();
this.notifyConnectionUpdate();
} catch (error) { } catch (error) {
console.error('Post-OAuth connection failed:', error); console.error('Post-OAuth connection failed:', error);
this.connection.status = 'failed';
this.connection.error = error instanceof Error ? error.message : 'Post-OAuth connection failed';
this.notifyConnectionUpdate();
} }
} }
private async tryStreamableHttp(): Promise<void> { private async tryStreamableHttp(): Promise<void> {
try { try {
const transport = new StreamableHTTPClientTransport(new URL(this.connection.url), {
// Create transport options with OAuth provider if configured authProvider: this.oauthProvider
const transportOptions: any = {}; });
if (this.connection.authType === 'oauth' && this.oauthProvider) {
transportOptions.authProvider = this.oauthProvider;
}
const transport = new StreamableHTTPClientTransport(new URL(this.connection.url), transportOptions);
await this.initializeClient(transport); await this.initializeClient(transport);
} catch (error) { } catch (error) {
console.error('StreamableHTTP connection failed:', error); console.log('StreamableHTTP connection failed:', error);
throw error; throw error;
} }
} }
private async trySSE(): Promise<void> { private async trySSE(): Promise<void> {
try { try {
const transport = new SSEClientTransport(new URL(this.connection.url), {
// Create transport options with OAuth provider if configured authProvider: this.oauthProvider
const transportOptions: any = {}; });
if (this.connection.authType === 'oauth' && this.oauthProvider) {
transportOptions.authProvider = this.oauthProvider;
}
const transport = new SSEClientTransport(new URL(this.connection.url), transportOptions);
await this.initializeClient(transport); await this.initializeClient(transport);
} catch (error) { } catch (error) {
console.log('SSE connection failed:', error);
throw error; throw error;
} }
} }
@ -491,7 +439,7 @@ export class MCPConnectionManager {
try { try {
const debugTransport = new DebugTransport(transport); const debugTransport = new DebugTransport(transport);
this.transport = debugTransport; this.transport = debugTransport;
this.client = new Client( this.client = new Client(
{ {
name: 'example-remote-client', name: 'example-remote-client',
@ -501,20 +449,20 @@ export class MCPConnectionManager {
capabilities: {}, capabilities: {},
} }
); );
// Set up message callbacks to broadcast to UI after client is created // Set up message callbacks to broadcast to UI after client is created
debugTransport.onsendmessage_ = async (message, options) => { debugTransport.onsendmessage_ = async (message, options) => {
if (this.onMessage && this.client) { if (this.onMessage && this.client) {
this.onMessage(this.connection.id, this.client, message, 'sent', { options }); this.onMessage(this.connection.id, this.client, message, 'sent', { options });
} }
}; };
debugTransport.onreceivemessage_ = (message, extra) => { debugTransport.onreceivemessage_ = (message, extra) => {
if (this.onMessage && this.client) { if (this.onMessage && this.client) {
this.onMessage(this.connection.id, this.client, message, 'received', extra); this.onMessage(this.connection.id, this.client, message, 'received', extra);
} }
}; };
await this.client.connect(debugTransport); await this.client.connect(debugTransport);
this.connection.client = this.client; this.connection.client = this.client;
} catch (error) { } catch (error) {
@ -530,7 +478,7 @@ export class MCPConnectionManager {
try { try {
// Discover tools // Discover tools
this.connection.tools = await this.discoverTools(); this.connection.tools = await this.discoverTools();
// Discover resources (if supported) // Discover resources (if supported)
try { try {
this.connection.resources = await this.discoverResources(); this.connection.resources = await this.discoverResources();
@ -538,7 +486,7 @@ export class MCPConnectionManager {
// Resources not supported by this server // Resources not supported by this server
this.connection.resources = []; this.connection.resources = [];
} }
// Discover prompts (if supported) // Discover prompts (if supported)
try { try {
this.connection.prompts = await this.discoverPrompts(); this.connection.prompts = await this.discoverPrompts();
@ -546,7 +494,7 @@ export class MCPConnectionManager {
// Prompts not supported by this server // Prompts not supported by this server
this.connection.prompts = []; this.connection.prompts = [];
} }
} catch (error) { } catch (error) {
throw this.createMCPError('protocol', 'Failed to initialize server capabilities', error); throw this.createMCPError('protocol', 'Failed to initialize server capabilities', error);
} }
@ -620,7 +568,7 @@ export class MCPConnectionManager {
// Remove the server prefix from the tool name (using double underscore separator) // Remove the server prefix from the tool name (using double underscore separator)
const normalizedServerName = normalizeServerName(this.connection.name); const normalizedServerName = normalizeServerName(this.connection.name);
const unprefixedName = toolName.startsWith(`${normalizedServerName}__`) const unprefixedName = toolName.startsWith(`${normalizedServerName}__`)
? toolName.slice(normalizedServerName.length + 2) ? toolName.slice(normalizedServerName.length + 2)
: toolName; : toolName;
@ -640,61 +588,6 @@ export class MCPConnectionManager {
return this.connection.status; return this.connection.status;
} }
private getBackoffDelay(): number {
// Exponential backoff: 1s, 2s, 4s, 8s, 16s
return Math.min(1000 * Math.pow(2, this.connection.connectionAttempts - 1), 16000);
}
private startHealthCheck(): void {
// Clear any existing health check
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
}
// Start health check every 30 seconds
this.healthCheckInterval = setInterval(async () => {
await this.performHealthCheck();
}, 10 * 60 * 1000); // 10 minutes
}
private async performHealthCheck(): Promise<void> {
// Only check if we're supposed to be connected
if (this.connection.status !== 'connected' || !this.client) {
return;
}
try {
// Try to list tools as a health check - this is a lightweight operation
await this.client.ping();
} catch (error) {
console.warn(`Health check failed for ${this.connection.name}:`, error);
await this.handleHealthCheckFailure(error);
}
}
private async handleHealthCheckFailure(error: any): Promise<void> {
// Stop health check during reconnection attempt
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
this.healthCheckInterval = undefined;
}
// Mark as disconnected and attempt reconnection
this.connection.status = 'connecting';
this.connection.error = error instanceof Error ? error.message : 'Health check failed';
this.notifyConnectionUpdate();
try {
// Attempt reconnection
await this.connect();
} catch (reconnectError) {
console.error(`Health check reconnection failed for ${this.connection.name}:`, reconnectError);
// The connect method will handle scheduling retry attempts
}
}
private createMCPError(type: MCPError['type'], message: string, details?: any): MCPError { private createMCPError(type: MCPError['type'], message: string, details?: any): MCPError {
return { return {
type, type,

View file

@ -9,7 +9,6 @@ import type { TransportSendOptions } from '@modelcontextprotocol/sdk/shared/tran
export interface MCPServerConfig { export interface MCPServerConfig {
name: string; // User-provided display name name: string; // User-provided display name
url: string; // Server endpoint URL url: string; // Server endpoint URL
transport?: 'sse' | 'streamable-http' | 'auto'; // Default: auto-detect
authType?: 'none' | 'oauth'; // Default: none authType?: 'none' | 'oauth'; // Default: none
oauthConfig?: { oauthConfig?: {
clientId?: string; clientId?: string;
@ -18,7 +17,6 @@ export interface MCPServerConfig {
scope?: string; // OAuth scope scope?: string; // OAuth scope
redirectUri?: string; // Override default redirect URI redirectUri?: string; // Override default redirect URI
}; };
autoReconnect?: boolean; // Default: true
maxReconnectAttempts?: number; // Default: 5 maxReconnectAttempts?: number; // Default: 5
} }
@ -75,8 +73,8 @@ export interface MCPConnection {
url: string; // Server URL url: string; // Server URL
status: 'connecting' | 'connected' | 'failed' | 'disconnected'; status: 'connecting' | 'connected' | 'failed' | 'disconnected';
client?: Client; // MCP SDK client instance client?: Client; // MCP SDK client instance
transport: 'sse' | 'streamable-http'; transport?: 'sse' | 'streamable-http';
authType: 'none' | 'oauth'; authType?: 'none' | 'oauth';
// Available capabilities // Available capabilities
tools: Tool[]; // Tools with name-prefixed identifiers tools: Tool[]; // Tools with name-prefixed identifiers