/** * ResourceServer — extracted from PhotonServer * * Encapsulates all MCP resource handling: * - ListResources, ListResourceTemplates, ReadResource handlers * - UI resource URI resolution (ui:// and photon:// schemes) * - Icon image resolution (file → data URI) * - Asset serving (UI HTML, prompts, static resources) * - MCP Apps bridge script generation for Claude Desktop compatibility * * Dependency direction: PhotonServer → ResourceServer (never the reverse). */ import type { PhotonClassExtended } from '@portel/photon-core'; /** Minimal interface for executing tool calls — avoids importing PhotonLoader */ export interface ResourceToolExecutor { executeTool(photon: PhotonClassExtended, toolName: string, args: Record): Promise; getLoadedPhotons(): Map; } /** Minimal options subset needed by ResourceServer */ export interface ResourceServerOptions { filePath: string; embeddedAssets?: { indexHtml: string; bundleJs: string; }; embeddedUITemplates?: Record>; /** * Embedded `//assets/**` tree for v1.29 directory-style * serving in standalone binaries. Populated by `photon build` when the * companion `assets/` folder exists. Consumed by the directory-style * GET handler so SPA chunks resolve without filesystem access. * Shape: photonName → { relativePath → utf-8 content }. */ embeddedAssetTree?: Record>; } /** * Check if a URI contains template parameters, e.g. `person://{slug}`. * Exported so both STDIO (ResourceServer) and the streamable-HTTP * transport can split static vs templated URIs the same way. */ export declare function isUriTemplate(uri: string): boolean; /** * Test whether `uri` matches the template `pattern`. * Example: `matchUriTemplate('person://{slug}', 'person://alice')` → true. */ export declare function matchUriTemplate(pattern: string, uri: string): boolean; /** * Extract template parameter values from a URI given its template pattern. * Example: `parseUriTemplateParams('person://{slug}', 'person://alice')` * → `{ slug: 'alice' }`. Returns an empty object on mismatch. */ export declare function parseUriTemplateParams(pattern: string, uri: string): Record; /** * Sink that delivers a `notifications/resources/updated` event to a single * client (one MCP server connection — STDIO or one SSE session). */ export type ResourceUpdateSink = (uri: string) => void | Promise; /** * Server-wide registry of `resources/subscribe` state. One instance lives on * `PhotonServer`; both the STDIO server and every SSE session register their * sinks with it. * * - Subscriptions are keyed by *exact* URI (per MCP spec — clients subscribe * to `person://alice`, never to the template `person://{slug}`). * - On session disconnect, call `disconnect(sink)` to purge all that session's * subscriptions in one go. * - `notify(uri)` fans out to every subscribed sink, isolating delivery * failures so one dead transport does not block others. */ export declare class SubscriptionRegistry { private bySink; private byUri; subscribe(sink: ResourceUpdateSink, uri: string): void; unsubscribe(sink: ResourceUpdateSink, uri: string): void; disconnect(sink: ResourceUpdateSink): void; notify(uri: string): Promise; /** True if any sink is subscribed to `uri`. Used by tests. */ hasSubscribers(uri: string): boolean; } export declare class ResourceServer { private toolExecutor; private options; private static readonly ICON_MIME_TYPES; /** Cached resolved icons per tool name */ private resolvedIconsCache; constructor(toolExecutor: ResourceToolExecutor, options: ResourceServerOptions); /** * Build UI resource URI based on detected format */ buildUIResourceUri(photonName: string, uiId: string): string; /** * Build tool metadata for UI based on detected format */ buildUIToolMeta(photonName: string, uiId: string): Record; /** * Get UI mimeType based on detected format and client capabilities */ getUIMimeType(): string; /** * Resolve raw icon image paths to MCP Icon[] format (data URIs). * Results are cached so file I/O only happens once per tool. */ resolveIconImages(iconImages: Array<{ path: string; sizes?: string; theme?: string; }>): Array<{ src: string; mimeType?: string; sizes?: string; theme?: string; }>; isUriTemplate(uri: string): boolean; matchUriPattern(pattern: string, uri: string): boolean; parseUriParams(pattern: string, uri: string): Record; handleListResources(mcp: PhotonClassExtended | null): { resources: any[]; }; handleListResourceTemplates(mcp: PhotonClassExtended | null): { resourceTemplates: any[]; }; handleReadResource(request: any, mcp: PhotonClassExtended | null): Promise; /** * Accept both the canonical UI asset id (`weather-card`) and the linked tool * name (`current`). ChatGPT may surface a tool like `weather/current` while it * is trying to resolve the app card; mapping that to the linked `@ui` asset * keeps the MCP Apps flow tolerant without inventing a private endpoint. */ private resolveUIAssetId; private isKnownUIHost; /** * ChatGPT developer-mode apps may wrap listed resources in an app-scoped * path such as `/My App/link_abc/weather/current` before reading them back. * The final host/tool suffix is still the stable Photon signal, so tolerate * the wrapper and map it to the linked UI asset. */ private resolveAppScopedUIResource; /** * Handle SEP-1865 ui:// resource read */ private handleUIAssetRead; /** * Handle photon:// asset read (Beam format) */ private handleAssetRead; /** * Handle static resource read (for both stdio and SSE handlers) */ private handleStaticRead; /** * Format static result to MCP resource response */ formatStaticResult(result: any, mimeType?: string): any; /** * Generate minimal MCP Apps bridge script for Claude Desktop compatibility * This handles the ui/initialize handshake and tool result delivery */ generateMcpAppsBridge(mcp: PhotonClassExtended | null): string; } //# sourceMappingURL=resource-server.d.ts.map