diff --git a/tools/server/webui/src/lib/services/mcp.service.ts b/tools/server/webui/src/lib/services/mcp.service.ts index 2769364ad0..e20b196261 100644 --- a/tools/server/webui/src/lib/services/mcp.service.ts +++ b/tools/server/webui/src/lib/services/mcp.service.ts @@ -33,7 +33,11 @@ import type { MCPConnection, MCPPhaseCallback, MCPConnectionLog, - MCPServerInfo + MCPServerInfo, + MCPResource, + MCPResourceTemplate, + MCPResourceContent, + MCPReadResourceResult } from '$lib/types'; import { MCPConnectionPhase, MCPLogLevel, MCPTransportType } from '$lib/enums'; import { DEFAULT_MCP_CONFIG } from '$lib/constants/mcp'; @@ -435,4 +439,176 @@ export class MCPService { return null; } } + + /** + * + * + * Resources Operations + * + * + */ + + /** + * List resources from a connection. + * @param connection - The MCP connection to use + * @param cursor - Optional pagination cursor + * @returns Array of available resources and optional next cursor + */ + static async listResources( + connection: MCPConnection, + cursor?: string + ): Promise<{ resources: MCPResource[]; nextCursor?: string }> { + try { + const result = await connection.client.listResources(cursor ? { cursor } : undefined); + return { + resources: (result.resources ?? []) as MCPResource[], + nextCursor: result.nextCursor + }; + } catch (error) { + console.warn(`[MCPService][${connection.serverName}] Failed to list resources:`, error); + return { resources: [] }; + } + } + + /** + * List all resources from a connection (handles pagination automatically). + * @param connection - The MCP connection to use + * @returns Array of all available resources + */ + static async listAllResources(connection: MCPConnection): Promise { + const allResources: MCPResource[] = []; + let cursor: string | undefined; + + do { + const result = await this.listResources(connection, cursor); + allResources.push(...result.resources); + cursor = result.nextCursor; + } while (cursor); + + return allResources; + } + + /** + * List resource templates from a connection. + * @param connection - The MCP connection to use + * @param cursor - Optional pagination cursor + * @returns Array of available resource templates and optional next cursor + */ + static async listResourceTemplates( + connection: MCPConnection, + cursor?: string + ): Promise<{ resourceTemplates: MCPResourceTemplate[]; nextCursor?: string }> { + try { + const result = await connection.client.listResourceTemplates(cursor ? { cursor } : undefined); + return { + resourceTemplates: (result.resourceTemplates ?? []) as MCPResourceTemplate[], + nextCursor: result.nextCursor + }; + } catch (error) { + console.warn( + `[MCPService][${connection.serverName}] Failed to list resource templates:`, + error + ); + return { resourceTemplates: [] }; + } + } + + /** + * List all resource templates from a connection (handles pagination automatically). + * @param connection - The MCP connection to use + * @returns Array of all available resource templates + */ + static async listAllResourceTemplates(connection: MCPConnection): Promise { + const allTemplates: MCPResourceTemplate[] = []; + let cursor: string | undefined; + + do { + const result = await this.listResourceTemplates(connection, cursor); + allTemplates.push(...result.resourceTemplates); + cursor = result.nextCursor; + } while (cursor); + + return allTemplates; + } + + /** + * Read the contents of a resource. + * @param connection - The MCP connection to use + * @param uri - The URI of the resource to read + * @returns The resource contents + */ + static async readResource( + connection: MCPConnection, + uri: string + ): Promise { + try { + const result = await connection.client.readResource({ uri }); + return { + contents: (result.contents ?? []) as MCPResourceContent[], + _meta: result._meta + }; + } catch (error) { + console.error(`[MCPService][${connection.serverName}] Failed to read resource:`, error); + throw error; + } + } + + /** + * Subscribe to updates for a resource. + * The server will send notifications/resources/updated when the resource changes. + * @param connection - The MCP connection to use + * @param uri - The URI of the resource to subscribe to + */ + static async subscribeResource(connection: MCPConnection, uri: string): Promise { + try { + await connection.client.subscribeResource({ uri }); + console.log(`[MCPService][${connection.serverName}] Subscribed to resource: ${uri}`); + } catch (error) { + console.error( + `[MCPService][${connection.serverName}] Failed to subscribe to resource:`, + error + ); + throw error; + } + } + + /** + * Unsubscribe from updates for a resource. + * @param connection - The MCP connection to use + * @param uri - The URI of the resource to unsubscribe from + */ + static async unsubscribeResource(connection: MCPConnection, uri: string): Promise { + try { + await connection.client.unsubscribeResource({ uri }); + console.log(`[MCPService][${connection.serverName}] Unsubscribed from resource: ${uri}`); + } catch (error) { + console.error( + `[MCPService][${connection.serverName}] Failed to unsubscribe from resource:`, + error + ); + throw error; + } + } + + /** + * Check if a connection supports resources. + * Per MCP spec: presence of the `resources` key (even as empty object {}) indicates support. + * Empty object means resources are supported but no sub-features (subscribe, listChanged). + * @param connection - The MCP connection to check + * @returns Whether the server supports resources + */ + static supportsResources(connection: MCPConnection): boolean { + // Per MCP spec: "Servers that support resources MUST declare the resources capability" + // The presence of the key indicates support, even if it's an empty object + return connection.serverCapabilities?.resources !== undefined; + } + + /** + * Check if a connection supports resource subscriptions. + * @param connection - The MCP connection to check + * @returns Whether the server supports resource subscriptions + */ + static supportsResourceSubscriptions(connection: MCPConnection): boolean { + return !!connection.serverCapabilities?.resources?.subscribe; + } } diff --git a/tools/server/webui/src/lib/types/index.ts b/tools/server/webui/src/lib/types/index.ts index fd3a553a51..5df5cf039b 100644 --- a/tools/server/webui/src/lib/types/index.ts +++ b/tools/server/webui/src/lib/types/index.ts @@ -118,7 +118,22 @@ export type { Tool, Prompt, GetPromptResult, - PromptMessage + PromptMessage, + MCPProgressState, + MCPResourceAnnotations, + MCPResourceIcon, + MCPResource, + MCPResourceTemplate, + MCPTextResourceContent, + MCPBlobResourceContent, + MCPResourceContent, + MCPReadResourceResult, + MCPResourceInfo, + MCPResourceTemplateInfo, + MCPCachedResource, + MCPResourceAttachment, + MCPResourceSubscription, + MCPServerResources } from './mcp'; // Agentic types diff --git a/tools/server/webui/src/lib/types/mcp.d.ts b/tools/server/webui/src/lib/types/mcp.d.ts index ca1e3e91e9..4fee19e33f 100644 --- a/tools/server/webui/src/lib/types/mcp.d.ts +++ b/tools/server/webui/src/lib/types/mcp.d.ts @@ -167,6 +167,7 @@ export type HealthCheckState = */ export interface HealthCheckParams { id: string; + enabled: boolean; url: string; requestTimeoutSeconds: number; headers?: string; @@ -249,3 +250,167 @@ export interface ToolExecutionResult { content: string; isError: boolean; } + +/** + * Progress tracking state for a specific operation + */ +export interface MCPProgressState { + progressToken: string | number; + serverName: string; + progress: number; + total?: number; + message?: string; + startTime: Date; + lastUpdate: Date; +} + +/** + * Resource annotations for audience and priority hints + */ +export interface MCPResourceAnnotations { + audience?: ('user' | 'assistant')[]; + priority?: number; + lastModified?: string; +} + +/** + * Icon definition for resources + */ +export interface MCPResourceIcon { + src: string; + mimeType?: string; + sizes?: string[]; + theme?: 'light' | 'dark'; +} + +/** + * A known resource that the server is capable of reading + */ +export interface MCPResource { + uri: string; + name: string; + title?: string; + description?: string; + mimeType?: string; + annotations?: MCPResourceAnnotations; + icons?: MCPResourceIcon[]; + _meta?: Record; +} + +/** + * A template for dynamically generating resource URIs + */ +export interface MCPResourceTemplate { + uriTemplate: string; + name: string; + title?: string; + description?: string; + mimeType?: string; + annotations?: MCPResourceAnnotations; + icons?: MCPResourceIcon[]; + _meta?: Record; +} + +/** + * Text content from a resource + */ +export interface MCPTextResourceContent { + uri: string; + mimeType?: string; + text: string; +} + +/** + * Binary (blob) content from a resource + */ +export interface MCPBlobResourceContent { + uri: string; + mimeType?: string; + /** Base64-encoded binary data */ + blob: string; +} + +/** + * Union type for resource content + */ +export type MCPResourceContent = MCPTextResourceContent | MCPBlobResourceContent; + +/** + * Result from reading a resource + */ +export interface MCPReadResourceResult { + contents: MCPResourceContent[]; + _meta?: Record; +} + +/** + * Resource information for display in UI + */ +export interface MCPResourceInfo { + uri: string; + name: string; + title?: string; + description?: string; + mimeType?: string; + serverName: string; + annotations?: MCPResourceAnnotations; + icons?: MCPResourceIcon[]; +} + +/** + * Resource template information for display in UI + */ +export interface MCPResourceTemplateInfo { + uriTemplate: string; + name: string; + title?: string; + description?: string; + mimeType?: string; + serverName: string; + annotations?: MCPResourceAnnotations; + icons?: MCPResourceIcon[]; +} + +/** + * Cached resource content with metadata + */ +export interface MCPCachedResource { + resource: MCPResourceInfo; + content: MCPResourceContent[]; + fetchedAt: Date; + /** Whether this resource has an active subscription */ + subscribed?: boolean; +} + +/** + * Resource attachment for chat context + */ +export interface MCPResourceAttachment { + id: string; + resource: MCPResourceInfo; + content?: MCPResourceContent[]; + loading?: boolean; + error?: string; +} + +/** + * State for resource subscriptions + */ +export interface MCPResourceSubscription { + uri: string; + serverName: string; + subscribedAt: Date; + lastUpdate?: Date; +} + +/** + * Aggregated resources state per server + */ +export interface MCPServerResources { + serverName: string; + resources: MCPResource[]; + templates: MCPResourceTemplate[]; + lastFetched?: Date; + loading: boolean; + error?: string; +}