feat: Introduce MCP Resource Types and Service Methods

This commit is contained in:
Aleksander Grygier 2026-01-28 18:28:02 +01:00
parent 85a61a7c96
commit 89166a79d4
3 changed files with 358 additions and 2 deletions

View File

@ -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<MCPResource[]> {
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<MCPResourceTemplate[]> {
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<MCPReadResourceResult> {
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<void> {
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<void> {
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;
}
}

View File

@ -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

View File

@ -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<string, unknown>;
}
/**
* 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<string, unknown>;
}
/**
* 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<string, unknown>;
}
/**
* 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;
}