feat: MCP connection details WIP
This commit is contained in:
parent
2b37f70c37
commit
825d2ea9a9
|
|
@ -2,7 +2,8 @@
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import * as Card from '$lib/components/ui/card';
|
import * as Card from '$lib/components/ui/card';
|
||||||
import type { MCPServerSettingsEntry, HealthCheckState } from '$lib/types';
|
import type { MCPServerSettingsEntry, HealthCheckState } from '$lib/types';
|
||||||
import { MCPConnectionPhase } from '$lib/enums';
|
import { HealthCheckStatus } from '$lib/enums';
|
||||||
|
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||||
import { mcpClient } from '$lib/clients/mcp.client';
|
import { mcpClient } from '$lib/clients/mcp.client';
|
||||||
import McpServerCardHeader from './McpServerCardHeader.svelte';
|
import McpServerCardHeader from './McpServerCardHeader.svelte';
|
||||||
import McpServerCardActions from './McpServerCardActions.svelte';
|
import McpServerCardActions from './McpServerCardActions.svelte';
|
||||||
|
|
@ -22,11 +23,39 @@
|
||||||
let { server, displayName, faviconUrl, onToggle, onUpdate, onDelete }: Props = $props();
|
let { server, displayName, faviconUrl, onToggle, onUpdate, onDelete }: Props = $props();
|
||||||
|
|
||||||
let healthState = $derived<HealthCheckState>(mcpStore.getHealthCheckState(server.id));
|
let healthState = $derived<HealthCheckState>(mcpStore.getHealthCheckState(server.id));
|
||||||
let isHealthChecking = $derived(healthState.status === 'loading');
|
let isHealthChecking = $derived(healthState.status === HealthCheckStatus.Connecting);
|
||||||
let isConnected = $derived(healthState.status === 'success');
|
let isConnected = $derived(healthState.status === HealthCheckStatus.Success);
|
||||||
let isError = $derived(healthState.status === 'error');
|
let isError = $derived(healthState.status === HealthCheckStatus.Error);
|
||||||
let errorMessage = $derived(healthState.status === 'error' ? healthState.message : undefined);
|
let errorMessage = $derived(
|
||||||
let tools = $derived(healthState.status === 'success' ? healthState.tools : []);
|
healthState.status === HealthCheckStatus.Error ? healthState.message : undefined
|
||||||
|
);
|
||||||
|
let tools = $derived(healthState.status === HealthCheckStatus.Success ? healthState.tools : []);
|
||||||
|
|
||||||
|
let connectionLogs = $derived(
|
||||||
|
healthState.status === HealthCheckStatus.Connecting ||
|
||||||
|
healthState.status === HealthCheckStatus.Success ||
|
||||||
|
healthState.status === HealthCheckStatus.Error
|
||||||
|
? healthState.logs
|
||||||
|
: []
|
||||||
|
);
|
||||||
|
let serverInfo = $derived(
|
||||||
|
healthState.status === HealthCheckStatus.Success ? healthState.serverInfo : undefined
|
||||||
|
);
|
||||||
|
let capabilities = $derived(
|
||||||
|
healthState.status === HealthCheckStatus.Success ? healthState.capabilities : undefined
|
||||||
|
);
|
||||||
|
let transportType = $derived(
|
||||||
|
healthState.status === HealthCheckStatus.Success ? healthState.transportType : undefined
|
||||||
|
);
|
||||||
|
let protocolVersion = $derived(
|
||||||
|
healthState.status === HealthCheckStatus.Success ? healthState.protocolVersion : undefined
|
||||||
|
);
|
||||||
|
let connectionTimeMs = $derived(
|
||||||
|
healthState.status === HealthCheckStatus.Success ? healthState.connectionTimeMs : undefined
|
||||||
|
);
|
||||||
|
let instructions = $derived(
|
||||||
|
healthState.status === HealthCheckStatus.Success ? healthState.instructions : undefined
|
||||||
|
);
|
||||||
|
|
||||||
let isEditing = $state(!server.url.trim());
|
let isEditing = $state(!server.url.trim());
|
||||||
let showDeleteDialog = $state(false);
|
let showDeleteDialog = $state(false);
|
||||||
|
|
|
||||||
|
|
@ -49,11 +49,33 @@ interface ToolCallResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MCPService {
|
export class MCPService {
|
||||||
|
/**
|
||||||
|
* Create a connection log entry
|
||||||
|
*/
|
||||||
|
private static createLog(
|
||||||
|
phase: MCPConnectionPhase,
|
||||||
|
message: string,
|
||||||
|
level: MCPLogLevel = MCPLogLevel.Info,
|
||||||
|
details?: unknown
|
||||||
|
): MCPConnectionLog {
|
||||||
|
return {
|
||||||
|
timestamp: new Date(),
|
||||||
|
phase,
|
||||||
|
message,
|
||||||
|
level,
|
||||||
|
details
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create transport based on server configuration.
|
* Create transport based on server configuration.
|
||||||
* Supports WebSocket, StreamableHTTP (modern), and SSE (legacy) transports.
|
* Supports WebSocket, StreamableHTTP (modern), and SSE (legacy) transports.
|
||||||
|
* Returns both transport and the type used.
|
||||||
*/
|
*/
|
||||||
static createTransport(config: MCPServerConfig): Transport {
|
static createTransport(config: MCPServerConfig): {
|
||||||
|
transport: Transport;
|
||||||
|
type: MCPTransportType;
|
||||||
|
} {
|
||||||
if (!config.url) {
|
if (!config.url) {
|
||||||
throw new Error('MCP server configuration is missing url');
|
throw new Error('MCP server configuration is missing url');
|
||||||
}
|
}
|
||||||
|
|
@ -64,26 +86,38 @@ export class MCPService {
|
||||||
if (config.headers) {
|
if (config.headers) {
|
||||||
requestInit.headers = config.headers;
|
requestInit.headers = config.headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.credentials) {
|
if (config.credentials) {
|
||||||
requestInit.credentials = config.credentials;
|
requestInit.credentials = config.credentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.transport === 'websocket') {
|
if (config.transport === 'websocket') {
|
||||||
console.log(`[MCPService] Creating WebSocket transport for ${url.href}`);
|
console.log(`[MCPService] Creating WebSocket transport for ${url.href}`);
|
||||||
return new WebSocketClientTransport(url);
|
|
||||||
|
return {
|
||||||
|
transport: new WebSocketClientTransport(url),
|
||||||
|
type: MCPTransportType.Websocket
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`[MCPService] Creating StreamableHTTP transport for ${url.href}`);
|
console.log(`[MCPService] Creating StreamableHTTP transport for ${url.href}`);
|
||||||
return new StreamableHTTPClientTransport(url, {
|
|
||||||
requestInit,
|
return {
|
||||||
sessionId: config.sessionId
|
transport: new StreamableHTTPClientTransport(url, {
|
||||||
});
|
requestInit,
|
||||||
|
sessionId: config.sessionId
|
||||||
|
}),
|
||||||
|
type: MCPTransportType.StreamableHttp
|
||||||
|
};
|
||||||
} catch (httpError) {
|
} catch (httpError) {
|
||||||
console.warn(`[MCPService] StreamableHTTP failed, trying SSE transport...`, httpError);
|
console.warn(`[MCPService] StreamableHTTP failed, trying SSE transport...`, httpError);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return new SSEClientTransport(url, { requestInit });
|
return {
|
||||||
|
transport: new SSEClientTransport(url, { requestInit }),
|
||||||
|
type: MCPTransportType.SSE
|
||||||
|
};
|
||||||
} catch (sseError) {
|
} catch (sseError) {
|
||||||
const httpMsg = httpError instanceof Error ? httpError.message : String(httpError);
|
const httpMsg = httpError instanceof Error ? httpError.message : String(httpError);
|
||||||
const sseMsg = sseError instanceof Error ? sseError.message : String(sseError);
|
const sseMsg = sseError instanceof Error ? sseError.message : String(sseError);
|
||||||
|
|
@ -93,20 +127,58 @@ export class MCPService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect to a single MCP server.
|
* Extract server info from SDK Implementation type
|
||||||
* Returns connection object with client, transport, and discovered tools.
|
*/
|
||||||
|
private static extractServerInfo(impl: Implementation | undefined): MCPServerInfo | undefined {
|
||||||
|
if (!impl) return undefined;
|
||||||
|
return {
|
||||||
|
name: impl.name,
|
||||||
|
version: impl.version,
|
||||||
|
title: impl.title,
|
||||||
|
description: impl.description,
|
||||||
|
websiteUrl: impl.websiteUrl,
|
||||||
|
icons: impl.icons?.map((icon) => ({
|
||||||
|
src: icon.src,
|
||||||
|
mimeType: icon.mimeType,
|
||||||
|
sizes: icon.sizes
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to a single MCP server with detailed phase tracking.
|
||||||
|
* Returns connection object with client, transport, discovered tools, and connection details.
|
||||||
|
* @param onPhase - Optional callback for connection phase changes
|
||||||
*/
|
*/
|
||||||
static async connect(
|
static async connect(
|
||||||
serverName: string,
|
serverName: string,
|
||||||
serverConfig: MCPServerConfig,
|
serverConfig: MCPServerConfig,
|
||||||
clientInfo?: Implementation,
|
clientInfo?: Implementation,
|
||||||
capabilities?: ClientCapabilities
|
capabilities?: ClientCapabilities,
|
||||||
|
onPhase?: MCPPhaseCallback
|
||||||
): Promise<MCPConnection> {
|
): Promise<MCPConnection> {
|
||||||
|
const startTime = performance.now();
|
||||||
const effectiveClientInfo = clientInfo ?? DEFAULT_MCP_CONFIG.clientInfo;
|
const effectiveClientInfo = clientInfo ?? DEFAULT_MCP_CONFIG.clientInfo;
|
||||||
const effectiveCapabilities = capabilities ?? DEFAULT_MCP_CONFIG.capabilities;
|
const effectiveCapabilities = capabilities ?? DEFAULT_MCP_CONFIG.capabilities;
|
||||||
|
|
||||||
|
// Phase: Creating transport
|
||||||
|
onPhase?.(
|
||||||
|
MCPConnectionPhase.TransportCreating,
|
||||||
|
this.createLog(
|
||||||
|
MCPConnectionPhase.TransportCreating,
|
||||||
|
`Creating transport for ${serverConfig.url}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
console.log(`[MCPService][${serverName}] Creating transport...`);
|
console.log(`[MCPService][${serverName}] Creating transport...`);
|
||||||
const transport = this.createTransport(serverConfig);
|
const { transport, type: transportType } = this.createTransport(serverConfig);
|
||||||
|
|
||||||
|
// Phase: Transport ready
|
||||||
|
onPhase?.(
|
||||||
|
MCPConnectionPhase.TransportReady,
|
||||||
|
this.createLog(MCPConnectionPhase.TransportReady, `Transport ready (${transportType})`),
|
||||||
|
{ transportType }
|
||||||
|
);
|
||||||
|
|
||||||
const client = new Client(
|
const client = new Client(
|
||||||
{
|
{
|
||||||
|
|
@ -116,19 +188,83 @@ export class MCPService {
|
||||||
{ capabilities: effectiveCapabilities }
|
{ capabilities: effectiveCapabilities }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Phase: Initializing
|
||||||
|
onPhase?.(
|
||||||
|
MCPConnectionPhase.Initializing,
|
||||||
|
this.createLog(MCPConnectionPhase.Initializing, 'Sending initialize request...')
|
||||||
|
);
|
||||||
|
|
||||||
console.log(`[MCPService][${serverName}] Connecting to server...`);
|
console.log(`[MCPService][${serverName}] Connecting to server...`);
|
||||||
await client.connect(transport);
|
await client.connect(transport);
|
||||||
|
|
||||||
console.log(`[MCPService][${serverName}] Connected, listing tools...`);
|
const serverVersion = client.getServerVersion();
|
||||||
const tools = await this.listTools({ client, transport, tools: [], serverName });
|
const serverCapabilities = client.getServerCapabilities();
|
||||||
|
const instructions = client.getInstructions();
|
||||||
|
const serverInfo = this.extractServerInfo(serverVersion);
|
||||||
|
|
||||||
console.log(`[MCPService][${serverName}] Initialization complete with ${tools.length} tools`);
|
// Phase: Capabilities exchanged
|
||||||
|
onPhase?.(
|
||||||
|
MCPConnectionPhase.CapabilitiesExchanged,
|
||||||
|
this.createLog(
|
||||||
|
MCPConnectionPhase.CapabilitiesExchanged,
|
||||||
|
'Capabilities exchanged successfully',
|
||||||
|
MCPLogLevel.Info,
|
||||||
|
{
|
||||||
|
serverCapabilities,
|
||||||
|
serverInfo
|
||||||
|
}
|
||||||
|
),
|
||||||
|
{
|
||||||
|
serverInfo,
|
||||||
|
serverCapabilities,
|
||||||
|
clientCapabilities: effectiveCapabilities,
|
||||||
|
instructions
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Phase: Listing tools
|
||||||
|
onPhase?.(
|
||||||
|
MCPConnectionPhase.ListingTools,
|
||||||
|
this.createLog(MCPConnectionPhase.ListingTools, 'Listing available tools...')
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`[MCPService][${serverName}] Connected, listing tools...`);
|
||||||
|
const tools = await this.listTools({
|
||||||
|
client,
|
||||||
|
transport,
|
||||||
|
tools: [],
|
||||||
|
serverName,
|
||||||
|
transportType,
|
||||||
|
connectionTimeMs: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
const connectionTimeMs = Math.round(performance.now() - startTime);
|
||||||
|
|
||||||
|
// Phase: Connected
|
||||||
|
onPhase?.(
|
||||||
|
MCPConnectionPhase.Connected,
|
||||||
|
this.createLog(
|
||||||
|
MCPConnectionPhase.Connected,
|
||||||
|
`Connection established with ${tools.length} tools (${connectionTimeMs}ms)`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[MCPService][${serverName}] Initialization complete with ${tools.length} tools in ${connectionTimeMs}ms`
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
client,
|
client,
|
||||||
transport,
|
transport,
|
||||||
tools,
|
tools,
|
||||||
serverName
|
serverName,
|
||||||
|
transportType,
|
||||||
|
serverInfo,
|
||||||
|
serverCapabilities,
|
||||||
|
clientCapabilities: effectiveCapabilities,
|
||||||
|
protocolVersion: DEFAULT_MCP_CONFIG.protocolVersion,
|
||||||
|
instructions,
|
||||||
|
connectionTimeMs
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue