refactor: MCP types and health check
This commit is contained in:
parent
0180becb8b
commit
0009c0c300
|
|
@ -37,7 +37,7 @@ import type {
|
|||
ChatMessageToolCallTiming,
|
||||
ChatMessageAgenticTurnStats
|
||||
} from '$lib/types/chat';
|
||||
import type { MCPToolCall } from '$lib/types/mcp';
|
||||
import type { MCPToolCall } from '$lib/types';
|
||||
import type { DatabaseMessage, DatabaseMessageExtra, McpServerOverride } from '$lib/types/database';
|
||||
|
||||
export interface AgenticFlowCallbacks {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
// MCP Client
|
||||
export { MCPClient, mcpClient } from './mcp.client';
|
||||
export type { HealthCheckState, HealthCheckParams } from './mcp.client';
|
||||
|
||||
// Chat Client
|
||||
export { ChatClient, chatClient } from './chat.client';
|
||||
|
|
|
|||
|
|
@ -26,31 +26,61 @@
|
|||
*/
|
||||
|
||||
import { browser } from '$app/environment';
|
||||
import { MCPService, type MCPConnection } from '$lib/services/mcp.service';
|
||||
import { MCPService } from '$lib/services/mcp.service';
|
||||
import type {
|
||||
MCPToolCall,
|
||||
OpenAIToolDefinition,
|
||||
ServerStatus,
|
||||
ToolExecutionResult,
|
||||
MCPClientConfig
|
||||
} from '$lib/types/mcp';
|
||||
MCPClientConfig,
|
||||
MCPConnection,
|
||||
HealthCheckState,
|
||||
HealthCheckParams,
|
||||
ServerCapabilities,
|
||||
ClientCapabilities,
|
||||
MCPCapabilitiesInfo,
|
||||
MCPConnectionLog
|
||||
} from '$lib/types';
|
||||
import { MCPConnectionPhase, MCPLogLevel, HealthCheckStatus } from '$lib/enums';
|
||||
import type { McpServerOverride } from '$lib/types/database';
|
||||
import { MCPError } from '$lib/errors';
|
||||
import { buildMcpClientConfig, detectMcpTransportFromUrl } from '$lib/utils/mcp';
|
||||
import { config } from '$lib/stores/settings.svelte';
|
||||
import { DEFAULT_MCP_CONFIG } from '$lib/constants/mcp';
|
||||
|
||||
export type HealthCheckState =
|
||||
| { status: 'idle' }
|
||||
| { status: 'loading' }
|
||||
| { status: 'error'; message: string }
|
||||
| { status: 'success'; tools: { name: string; description?: string }[] };
|
||||
|
||||
export interface HealthCheckParams {
|
||||
id: string;
|
||||
url: string;
|
||||
requestTimeoutSeconds: number;
|
||||
headers?: string;
|
||||
/**
|
||||
* Build capabilities info from server and client capabilities
|
||||
*/
|
||||
function buildCapabilitiesInfo(
|
||||
serverCaps?: ServerCapabilities,
|
||||
clientCaps?: ClientCapabilities
|
||||
): MCPCapabilitiesInfo {
|
||||
return {
|
||||
server: {
|
||||
tools: serverCaps?.tools ? { listChanged: serverCaps.tools.listChanged } : undefined,
|
||||
prompts: serverCaps?.prompts ? { listChanged: serverCaps.prompts.listChanged } : undefined,
|
||||
resources: serverCaps?.resources
|
||||
? {
|
||||
subscribe: serverCaps.resources.subscribe,
|
||||
listChanged: serverCaps.resources.listChanged
|
||||
}
|
||||
: undefined,
|
||||
logging: !!serverCaps?.logging,
|
||||
completions: !!serverCaps?.completions,
|
||||
tasks: !!serverCaps?.tasks
|
||||
},
|
||||
client: {
|
||||
roots: clientCaps?.roots ? { listChanged: clientCaps.roots.listChanged } : undefined,
|
||||
sampling: !!clientCaps?.sampling,
|
||||
elicitation: clientCaps?.elicitation
|
||||
? {
|
||||
form: !!clientCaps.elicitation.form,
|
||||
url: !!clientCaps.elicitation.url
|
||||
}
|
||||
: undefined,
|
||||
tasks: !!clientCaps?.tasks
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export class MCPClient {
|
||||
|
|
@ -531,19 +561,28 @@ export class MCPClient {
|
|||
/**
|
||||
* Run health check for a specific server configuration.
|
||||
* Creates a temporary connection to test connectivity and list tools.
|
||||
* Tracks connection phases and collects detailed connection info.
|
||||
*/
|
||||
async runHealthCheck(server: HealthCheckParams): Promise<void> {
|
||||
const trimmedUrl = server.url.trim();
|
||||
const logs: MCPConnectionLog[] = [];
|
||||
let currentPhase: MCPConnectionPhase = MCPConnectionPhase.Idle;
|
||||
|
||||
if (!trimmedUrl) {
|
||||
this.notifyHealthCheck(server.id, {
|
||||
status: 'error',
|
||||
message: 'Please enter a server URL first.'
|
||||
status: HealthCheckStatus.Error,
|
||||
message: 'Please enter a server URL first.',
|
||||
logs: []
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.notifyHealthCheck(server.id, { status: 'loading' });
|
||||
// Initial connecting state
|
||||
this.notifyHealthCheck(server.id, {
|
||||
status: HealthCheckStatus.Connecting,
|
||||
phase: MCPConnectionPhase.TransportCreating,
|
||||
logs: []
|
||||
});
|
||||
|
||||
const timeoutMs = Math.round(server.requestTimeoutSeconds * 1000);
|
||||
const headers = this.parseHeaders(server.headers);
|
||||
|
|
@ -559,19 +598,57 @@ export class MCPClient {
|
|||
headers
|
||||
},
|
||||
DEFAULT_MCP_CONFIG.clientInfo,
|
||||
DEFAULT_MCP_CONFIG.capabilities
|
||||
DEFAULT_MCP_CONFIG.capabilities,
|
||||
// Phase callback for tracking progress
|
||||
(phase, log) => {
|
||||
currentPhase = phase;
|
||||
logs.push(log);
|
||||
this.notifyHealthCheck(server.id, {
|
||||
status: HealthCheckStatus.Connecting,
|
||||
phase,
|
||||
logs: [...logs]
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const tools = connection.tools.map((tool) => ({
|
||||
name: tool.name,
|
||||
description: tool.description
|
||||
description: tool.description,
|
||||
title: tool.title
|
||||
}));
|
||||
|
||||
this.notifyHealthCheck(server.id, { status: 'success', tools });
|
||||
const capabilities = buildCapabilitiesInfo(
|
||||
connection.serverCapabilities,
|
||||
connection.clientCapabilities
|
||||
);
|
||||
|
||||
this.notifyHealthCheck(server.id, {
|
||||
status: HealthCheckStatus.Success,
|
||||
tools,
|
||||
serverInfo: connection.serverInfo,
|
||||
capabilities,
|
||||
transportType: connection.transportType,
|
||||
protocolVersion: connection.protocolVersion,
|
||||
instructions: connection.instructions,
|
||||
connectionTimeMs: connection.connectionTimeMs,
|
||||
logs
|
||||
});
|
||||
|
||||
await MCPService.disconnect(connection);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error occurred';
|
||||
this.notifyHealthCheck(server.id, { status: 'error', message });
|
||||
logs.push({
|
||||
timestamp: new Date(),
|
||||
phase: MCPConnectionPhase.Error,
|
||||
message: `Connection failed: ${message}`,
|
||||
level: MCPLogLevel.Error
|
||||
});
|
||||
this.notifyHealthCheck(server.id, {
|
||||
status: HealthCheckStatus.Error,
|
||||
message,
|
||||
phase: currentPhase,
|
||||
logs
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@
|
|||
import { settingsStore } from '$lib/stores/settings.svelte';
|
||||
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
||||
import { parseMcpServerSettings, getServerDisplayName, getFaviconUrl } from '$lib/utils/mcp';
|
||||
import type { MCPServerSettingsEntry } from '$lib/types/mcp';
|
||||
import type { MCPServerSettingsEntry } from '$lib/types';
|
||||
import { HealthCheckStatus } from '$lib/enums';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
import { mcpClient } from '$lib/clients/mcp.client';
|
||||
|
||||
|
|
@ -156,7 +157,7 @@
|
|||
|
||||
{#each filteredMcpServers() as server (server.id)}
|
||||
{@const healthState = mcpStore.getHealthCheckState(server.id)}
|
||||
{@const hasError = healthState.status === 'error'}
|
||||
{@const hasError = healthState.status === HealthCheckStatus.Error}
|
||||
{@const isEnabledForChat = isServerEnabledForChat(server)}
|
||||
{@const hasOverride = hasPerChatOverride(server.id)}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import type { MCPServerSettingsEntry } from '$lib/types/mcp';
|
||||
import { mcpStore, type HealthCheckState } from '$lib/stores/mcp.svelte';
|
||||
import type { MCPServerSettingsEntry, HealthCheckState } from '$lib/types';
|
||||
import { MCPConnectionPhase } from '$lib/enums';
|
||||
import { mcpClient } from '$lib/clients/mcp.client';
|
||||
import McpServerCardHeader from './McpServerCardHeader.svelte';
|
||||
import McpServerCardActions from './McpServerCardActions.svelte';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { getServerDisplayName, getFaviconUrl } from '$lib/utils/mcp';
|
||||
import type { MCPServerSettingsEntry } from '$lib/types/mcp';
|
||||
import type { MCPServerSettingsEntry } from '$lib/types';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
import { McpServerCard } from '$lib/components/app/mcp/McpServerCard';
|
||||
import McpServerForm from './McpServerForm.svelte';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { ClientCapabilities, Implementation } from '$lib/types/mcp';
|
||||
import type { ClientCapabilities, Implementation } from '$lib/types';
|
||||
|
||||
export const DEFAULT_MCP_CONFIG = {
|
||||
protocolVersion: '2025-06-18',
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ export {
|
|||
MimeTypeText
|
||||
} from './files';
|
||||
|
||||
export { MCPConnectionPhase, MCPLogLevel, MCPTransportType, HealthCheckStatus } from './mcp';
|
||||
|
||||
export { ModelModality } from './model';
|
||||
|
||||
export { ServerRole, ServerModelStatus } from './server';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* Connection lifecycle phases for MCP protocol
|
||||
*/
|
||||
export enum MCPConnectionPhase {
|
||||
Idle = 'idle',
|
||||
TransportCreating = 'transport_creating',
|
||||
TransportReady = 'transport_ready',
|
||||
Initializing = 'initializing',
|
||||
CapabilitiesExchanged = 'capabilities_exchanged',
|
||||
ListingTools = 'listing_tools',
|
||||
Connected = 'connected',
|
||||
Error = 'error',
|
||||
Disconnected = 'disconnected'
|
||||
}
|
||||
|
||||
/**
|
||||
* Log level for connection events
|
||||
*/
|
||||
export enum MCPLogLevel {
|
||||
Info = 'info',
|
||||
Warn = 'warn',
|
||||
Error = 'error'
|
||||
}
|
||||
|
||||
/**
|
||||
* Transport types for MCP connections
|
||||
*/
|
||||
export enum MCPTransportType {
|
||||
Websocket = 'websocket',
|
||||
StreamableHttp = 'streamable_http',
|
||||
SSE = 'sse'
|
||||
}
|
||||
|
||||
/**
|
||||
* Health check status for MCP servers
|
||||
*/
|
||||
export enum HealthCheckStatus {
|
||||
Idle = 'idle',
|
||||
Connecting = 'connecting',
|
||||
Success = 'success',
|
||||
Error = 'error'
|
||||
}
|
||||
|
|
@ -3,4 +3,4 @@ export { DatabaseService } from './database.service';
|
|||
export { ModelsService } from './models.service';
|
||||
export { PropsService } from './props.service';
|
||||
export { ParameterSyncService } from './parameter-sync.service';
|
||||
export { MCPService, type MCPConnection } from './mcp.service';
|
||||
export { MCPService } from './mcp.service';
|
||||
|
|
|
|||
|
|
@ -24,26 +24,16 @@ import type {
|
|||
ToolCallParams,
|
||||
ToolExecutionResult,
|
||||
Implementation,
|
||||
ClientCapabilities
|
||||
} from '$lib/types/mcp';
|
||||
ClientCapabilities,
|
||||
MCPConnection,
|
||||
MCPPhaseCallback,
|
||||
MCPConnectionLog,
|
||||
MCPServerInfo
|
||||
} from '$lib/types';
|
||||
import { MCPConnectionPhase, MCPLogLevel, MCPTransportType } from '$lib/enums';
|
||||
import { MCPError } from '$lib/errors';
|
||||
import { DEFAULT_MCP_CONFIG } from '$lib/constants/mcp';
|
||||
|
||||
/**
|
||||
* Represents an active MCP server connection.
|
||||
* Returned by MCPService.connect() and used for subsequent operations.
|
||||
*/
|
||||
export interface MCPConnection {
|
||||
/** MCP SDK Client instance */
|
||||
client: Client;
|
||||
/** Active transport */
|
||||
transport: Transport;
|
||||
/** Discovered tools from this server */
|
||||
tools: Tool[];
|
||||
/** Server identifier */
|
||||
serverName: string;
|
||||
}
|
||||
|
||||
interface ToolResultContentItem {
|
||||
type: string;
|
||||
text?: string;
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@
|
|||
*/
|
||||
|
||||
import { browser } from '$app/environment';
|
||||
import { mcpClient, type HealthCheckState } from '$lib/clients';
|
||||
import type { MCPServerSettingsEntry, McpServerUsageStats } from '$lib/types/mcp';
|
||||
import { mcpClient } from '$lib/clients/mcp.client';
|
||||
import type { HealthCheckState, MCPServerSettingsEntry, McpServerUsageStats } from '$lib/types';
|
||||
import type { McpServerOverride } from '$lib/types/database';
|
||||
import { buildMcpClientConfig, parseMcpServerSettings } from '$lib/utils/mcp';
|
||||
import { config, settingsStore } from '$lib/stores/settings.svelte';
|
||||
|
|
@ -52,8 +52,6 @@ function parseMcpServerUsageStats(rawStats: unknown): McpServerUsageStats {
|
|||
return {};
|
||||
}
|
||||
|
||||
export type { HealthCheckState };
|
||||
|
||||
class MCPStore {
|
||||
private _isInitializing = $state(false);
|
||||
private _error = $state<string | null>(null);
|
||||
|
|
|
|||
|
|
@ -69,3 +69,28 @@ export type {
|
|||
|
||||
// Common types
|
||||
export type { KeyValuePair } from './common';
|
||||
|
||||
// MCP types
|
||||
export type {
|
||||
ClientCapabilities,
|
||||
ServerCapabilities,
|
||||
Implementation,
|
||||
MCPConnectionLog,
|
||||
MCPServerInfo,
|
||||
MCPCapabilitiesInfo,
|
||||
MCPToolInfo,
|
||||
MCPConnectionDetails,
|
||||
MCPPhaseCallback,
|
||||
MCPConnection,
|
||||
HealthCheckState,
|
||||
HealthCheckParams,
|
||||
MCPServerConfig,
|
||||
MCPClientConfig,
|
||||
MCPServerSettingsEntry,
|
||||
McpServerUsageStats,
|
||||
MCPToolCall,
|
||||
OpenAIToolDefinition,
|
||||
ServerStatus,
|
||||
ToolCallParams,
|
||||
ToolExecutionResult
|
||||
} from './mcp';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import type { MCPConnectionPhase, MCPLogLevel } from '$lib/enums/mcp';
|
||||
import type {
|
||||
ClientCapabilities as SDKClientCapabilities,
|
||||
ServerCapabilities as SDKServerCapabilities,
|
||||
Implementation as SDKImplementation,
|
||||
Tool,
|
||||
CallToolResult
|
||||
|
|
@ -7,9 +9,148 @@ import type {
|
|||
|
||||
export type { Tool, CallToolResult };
|
||||
export type ClientCapabilities = SDKClientCapabilities;
|
||||
export type ServerCapabilities = SDKServerCapabilities;
|
||||
export type Implementation = SDKImplementation;
|
||||
|
||||
export type MCPTransportType = 'websocket' | 'streamable_http';
|
||||
/**
|
||||
* Log entry for connection events
|
||||
*/
|
||||
export interface MCPConnectionLog {
|
||||
timestamp: Date;
|
||||
phase: MCPConnectionPhase;
|
||||
message: string;
|
||||
details?: unknown;
|
||||
level: MCPLogLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Server information returned after initialization
|
||||
*/
|
||||
export interface MCPServerInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
websiteUrl?: string;
|
||||
icons?: Array<{ src: string; mimeType?: string; sizes?: string[] }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detailed capabilities information
|
||||
*/
|
||||
export interface MCPCapabilitiesInfo {
|
||||
server: {
|
||||
tools?: { listChanged?: boolean };
|
||||
prompts?: { listChanged?: boolean };
|
||||
resources?: { subscribe?: boolean; listChanged?: boolean };
|
||||
logging?: boolean;
|
||||
completions?: boolean;
|
||||
tasks?: boolean;
|
||||
};
|
||||
client: {
|
||||
roots?: { listChanged?: boolean };
|
||||
sampling?: boolean;
|
||||
elicitation?: { form?: boolean; url?: boolean };
|
||||
tasks?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool information for display
|
||||
*/
|
||||
export interface MCPToolInfo {
|
||||
name: string;
|
||||
description?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Full connection details for visualization
|
||||
*/
|
||||
export interface MCPConnectionDetails {
|
||||
phase: MCPConnectionPhase;
|
||||
transportType?: MCPTransportType;
|
||||
protocolVersion?: string;
|
||||
serverInfo?: MCPServerInfo;
|
||||
capabilities?: MCPCapabilitiesInfo;
|
||||
instructions?: string;
|
||||
tools: MCPToolInfo[];
|
||||
connectionTimeMs?: number;
|
||||
error?: string;
|
||||
logs: MCPConnectionLog[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for connection phase changes
|
||||
*/
|
||||
export type MCPPhaseCallback = (
|
||||
phase: MCPConnectionPhase,
|
||||
log: MCPConnectionLog,
|
||||
details?: {
|
||||
transportType?: MCPTransportType;
|
||||
serverInfo?: MCPServerInfo;
|
||||
serverCapabilities?: ServerCapabilities;
|
||||
clientCapabilities?: ClientCapabilities;
|
||||
protocolVersion?: string;
|
||||
instructions?: string;
|
||||
}
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* Represents an active MCP server connection.
|
||||
* Returned by MCPService.connect() and used for subsequent operations.
|
||||
*/
|
||||
export interface MCPConnection {
|
||||
client: import('@modelcontextprotocol/sdk/client').Client;
|
||||
transport: import('@modelcontextprotocol/sdk/shared/transport.js').Transport;
|
||||
tools: import('@modelcontextprotocol/sdk/types.js').Tool[];
|
||||
serverName: string;
|
||||
transportType: MCPTransportType;
|
||||
serverInfo?: MCPServerInfo;
|
||||
serverCapabilities?: ServerCapabilities;
|
||||
clientCapabilities?: ClientCapabilities;
|
||||
protocolVersion?: string;
|
||||
instructions?: string;
|
||||
connectionTimeMs: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended health check state with detailed connection info
|
||||
*/
|
||||
export type HealthCheckState =
|
||||
| { status: import('$lib/enums/mcp').HealthCheckStatus.Idle }
|
||||
| {
|
||||
status: import('$lib/enums/mcp').HealthCheckStatus.Connecting;
|
||||
phase: MCPConnectionPhase;
|
||||
logs: MCPConnectionLog[];
|
||||
}
|
||||
| {
|
||||
status: import('$lib/enums/mcp').HealthCheckStatus.Error;
|
||||
message: string;
|
||||
phase?: MCPConnectionPhase;
|
||||
logs: MCPConnectionLog[];
|
||||
}
|
||||
| {
|
||||
status: import('$lib/enums/mcp').HealthCheckStatus.Success;
|
||||
tools: MCPToolInfo[];
|
||||
serverInfo?: MCPServerInfo;
|
||||
capabilities?: MCPCapabilitiesInfo;
|
||||
transportType?: MCPTransportType;
|
||||
protocolVersion?: string;
|
||||
instructions?: string;
|
||||
connectionTimeMs?: number;
|
||||
logs: MCPConnectionLog[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Health check parameters
|
||||
*/
|
||||
export interface HealthCheckParams {
|
||||
id: string;
|
||||
url: string;
|
||||
requestTimeoutSeconds: number;
|
||||
headers?: string;
|
||||
}
|
||||
|
||||
export type MCPServerConfig = {
|
||||
transport?: MCPTransportType;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
import type {
|
||||
MCPTransportType,
|
||||
MCPClientConfig,
|
||||
MCPServerConfig,
|
||||
MCPServerSettingsEntry
|
||||
} from '$lib/types/mcp';
|
||||
import type { MCPClientConfig, MCPServerConfig, MCPServerSettingsEntry } from '$lib/types';
|
||||
import type { SettingsConfigType } from '$lib/types/settings';
|
||||
import type { McpServerOverride } from '$lib/types/database';
|
||||
import { MCPTransportType } from '$lib/enums';
|
||||
import { DEFAULT_MCP_CONFIG } from '$lib/constants/mcp';
|
||||
import { normalizePositiveNumber } from '$lib/utils/number';
|
||||
|
||||
|
|
@ -17,8 +13,8 @@ export function detectMcpTransportFromUrl(url: string): MCPTransportType {
|
|||
const normalized = url.trim().toLowerCase();
|
||||
|
||||
return normalized.startsWith('ws://') || normalized.startsWith('wss://')
|
||||
? 'websocket'
|
||||
: 'streamable_http';
|
||||
? MCPTransportType.Websocket
|
||||
: MCPTransportType.StreamableHttp;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue