From 7cf6cf6b0cf0ec41b3cc57daa3c6949ff4770936 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Jan 2026 10:29:08 +0000 Subject: [PATCH 1/8] feat(core): Add MCP server icon support Implement server icon configuration following the MCP specification (2025-11-25). Developers can now provide icons for their MCP servers that will be displayed in MCP client UIs. Features: - Add Icon and IconTheme types - Support both shorthand `icon` (string URL/data URI) and full `icons` array - Icons are passed to McpServer during initialization - Export Icon and IconTheme types from package Usage: ```typescript createApp({ name: "my-app", version: "1.0.0", icon: "https://example.com/icon.png", // or data:image/... // OR for multiple icons: icons: [ { src: "https://example.com/icon.png", mimeType: "image/png", sizes: ["48x48"] }, { src: "https://example.com/dark.png", theme: "dark" } ], tools: { ... } }); ``` --- examples/minimal/src/index.ts | 4 + packages/core/src/index.ts | 2 + packages/core/src/server/index.ts | 32 +++++- packages/core/src/types/config.ts | 132 ++++++++++++++++++++++++ packages/core/tests/unit/icons.test.ts | 135 +++++++++++++++++++++++++ 5 files changed, 303 insertions(+), 2 deletions(-) create mode 100644 packages/core/tests/unit/icons.test.ts diff --git a/examples/minimal/src/index.ts b/examples/minimal/src/index.ts index 6106fce2..eb3dc94f 100644 --- a/examples/minimal/src/index.ts +++ b/examples/minimal/src/index.ts @@ -171,6 +171,10 @@ const echoToolV3 = defineTool({ const app = createApp({ name: "minimal-app", + // Server icon - displayed in MCP client UIs + // Can be a URL or base64 data URI + icon: "https://raw.githubusercontent.com/anthropics/claude-code/main/assets/icon.svg", + // Shared config across all versions config: { cors: { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index dc0d6966..56e3dbb4 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -62,6 +62,8 @@ export type { ServerConfig, VersionSpecificConfig, DeepPartialWithNull, + Icon, + IconTheme, } from "./types/config"; // OAuth types diff --git a/packages/core/src/server/index.ts b/packages/core/src/server/index.ts index 45e7cc67..5fec7c30 100644 --- a/packages/core/src/server/index.ts +++ b/packages/core/src/server/index.ts @@ -24,7 +24,7 @@ import type { ToolContext, UserLocation, } from "../types/tools"; -import type { AppConfig, CORSConfig, DebugConfig } from "../types/config"; +import type { AppConfig, CORSConfig, DebugConfig, Icon } from "../types/config"; import type { UIDefs, UIDef } from "../types/ui"; import type { MiddlewareContext } from "../middleware/types"; import type { EventMap } from "../events/types"; @@ -38,6 +38,30 @@ import * as fs from "node:fs"; import * as path from "node:path"; import * as crypto from "node:crypto"; +// ============================================================================= +// ICON HELPERS +// ============================================================================= + +/** + * Normalize icon configuration into an icons array. + * + * Handles both the shorthand `icon` string and the full `icons` array. + * If both are provided, `icons` takes precedence. + */ +function normalizeIcons(icon: string | undefined, icons: Icon[] | undefined): Icon[] | undefined { + // icons array takes precedence + if (icons && icons.length > 0) { + return icons; + } + + // Convert shorthand icon string to icons array + if (icon) { + return [{ src: icon }]; + } + + return undefined; +} + // ============================================================================= // SERVER WRAPPER // ============================================================================= @@ -83,10 +107,14 @@ export function createServerInstance( // Create protocol adapter const adapter = createAdapter(config.config?.protocol ?? "mcp"); - // Create MCP server + // Normalize icons from shorthand or array format + const icons = normalizeIcons(config.icon, config.icons); + + // Create MCP server with icons if provided const mcpServer = new McpServer({ name: config.name, version: config.version, + ...(icons && { icons }), }); // Compute UI resource URIs with content hashes for cache busting diff --git a/packages/core/src/types/config.ts b/packages/core/src/types/config.ts index a9fbd763..4a7f895a 100644 --- a/packages/core/src/types/config.ts +++ b/packages/core/src/types/config.ts @@ -7,6 +7,81 @@ import type { Plugin } from "../plugins/types"; import type { OAuthConfig } from "../server/oauth/types.js"; import type { UIDefs } from "./ui"; +// ============================================================================= +// ICON CONFIGURATION +// ============================================================================= + +/** + * Icon theme for light/dark mode support + */ +export type IconTheme = "light" | "dark"; + +/** + * Icon definition following the MCP specification. + * + * Supports both URL references and inline base64 data URIs. + * + * @see https://modelcontextprotocol.io/specification/2025-11-25/schema#implementation + * + * @example URL reference + * ```typescript + * { src: "https://example.com/icon.png", mimeType: "image/png", sizes: ["48x48"] } + * ``` + * + * @example Base64 data URI + * ```typescript + * { src: "data:image/svg+xml;base64,PHN2Zy...", mimeType: "image/svg+xml", sizes: ["any"] } + * ``` + * + * @example Theme-specific icon + * ```typescript + * { src: "https://example.com/icon-dark.png", theme: "dark" } + * ``` + */ +export interface Icon { + /** + * Icon source URI. + * + * Can be: + * - HTTP/HTTPS URL (e.g., "https://example.com/icon.png") + * - Data URI with base64 encoding (e.g., "data:image/png;base64,...") + */ + src: string; + + /** + * MIME type of the icon. + * + * Required MIME types that clients must support: + * - `"image/png"` + * - `"image/jpeg"` + * + * Optional MIME types: + * - `"image/svg+xml"` + * - `"image/webp"` + * + * @example "image/png" + */ + mimeType?: string; + + /** + * Icon sizes in "WxH" format. + * + * Use `["any"]` for scalable formats like SVG. + * + * @example ["48x48", "96x96"] + * @example ["any"] + */ + sizes?: string[]; + + /** + * Theme this icon is designed for. + * + * When specified, clients can select the appropriate icon + * based on their current light/dark mode setting. + */ + theme?: IconTheme; +} + // ============================================================================= // PROTOCOL CONFIGURATION // ============================================================================= @@ -463,6 +538,27 @@ export interface VersionsConfig { * Merged with each version's plugins. */ plugins?: Plugin[]; + + /** + * Server icon URL or data URI (shorthand for single icon). + * + * Applied to all versions. For multiple icons or advanced configuration, use `icons` instead. + * + * @example + * ```typescript + * icon: "https://example.com/icon.png" + * ``` + */ + icon?: string; + + /** + * Server icons for MCP client display. + * + * Applied to all versions. Follows the MCP specification for Implementation icons. + * + * @see https://modelcontextprotocol.io/specification/2025-11-25/schema#implementation + */ + icons?: Icon[]; } /** @@ -533,6 +629,42 @@ export interface AppConfig { * ``` */ plugins?: Plugin[]; + + /** + * Server icon URL or data URI (shorthand for single icon). + * + * For multiple icons or advanced configuration, use `icons` instead. + * + * @example URL + * ```typescript + * icon: "https://example.com/icon.png" + * ``` + * + * @example Data URI + * ```typescript + * icon: "data:image/svg+xml;base64,PHN2Zy..." + * ``` + */ + icon?: string; + + /** + * Server icons for MCP client display. + * + * Allows specifying multiple icons with different sizes, formats, and themes. + * Follows the MCP specification for Implementation icons. + * + * @see https://modelcontextprotocol.io/specification/2025-11-25/schema#implementation + * + * @example + * ```typescript + * icons: [ + * { src: "https://example.com/icon-48.png", mimeType: "image/png", sizes: ["48x48"] }, + * { src: "https://example.com/icon-96.png", mimeType: "image/png", sizes: ["96x96"] }, + * { src: "https://example.com/icon-dark.png", theme: "dark" } + * ] + * ``` + */ + icons?: Icon[]; } /** diff --git a/packages/core/tests/unit/icons.test.ts b/packages/core/tests/unit/icons.test.ts new file mode 100644 index 00000000..43ea6b86 --- /dev/null +++ b/packages/core/tests/unit/icons.test.ts @@ -0,0 +1,135 @@ +/** + * Unit tests for server icon configuration + * + * Tests the icon types and configuration for MCP server icons. + */ + +import { describe, it, expect, expectTypeOf } from "vitest"; +import { z } from "zod"; +import type { Icon, IconTheme, AppConfig } from "../../src/types/config"; +import type { ToolDefs } from "../../src/types/tools"; + +describe("Icon types", () => { + describe("Icon interface", () => { + it("should accept a minimal icon with just src", () => { + const icon: Icon = { + src: "https://example.com/icon.png", + }; + + expect(icon.src).toBe("https://example.com/icon.png"); + expect(icon.mimeType).toBeUndefined(); + expect(icon.sizes).toBeUndefined(); + expect(icon.theme).toBeUndefined(); + }); + + it("should accept a fully specified icon", () => { + const icon: Icon = { + src: "https://example.com/icon.png", + mimeType: "image/png", + sizes: ["48x48", "96x96"], + theme: "light", + }; + + expect(icon.src).toBe("https://example.com/icon.png"); + expect(icon.mimeType).toBe("image/png"); + expect(icon.sizes).toEqual(["48x48", "96x96"]); + expect(icon.theme).toBe("light"); + }); + + it("should accept data URI for src", () => { + const icon: Icon = { + src: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==", + mimeType: "image/svg+xml", + sizes: ["any"], + }; + + expect(icon.src).toMatch(/^data:image\/svg\+xml;base64,/); + }); + + it("should accept theme values", () => { + const lightIcon: Icon = { src: "https://example.com/light.png", theme: "light" }; + const darkIcon: Icon = { src: "https://example.com/dark.png", theme: "dark" }; + + expectTypeOf().toEqualTypeOf<"light" | "dark">(); + expect(lightIcon.theme).toBe("light"); + expect(darkIcon.theme).toBe("dark"); + }); + }); + + describe("AppConfig with icons", () => { + const baseTools: ToolDefs = { + test: { + description: "Test tool", + input: z.object({}), + output: z.object({}), + handler: async () => ({}), + }, + }; + + it("should accept icon shorthand string", () => { + const config: AppConfig = { + name: "test-app", + version: "1.0.0", + tools: baseTools, + icon: "https://example.com/icon.png", + }; + + expect(config.icon).toBe("https://example.com/icon.png"); + }); + + it("should accept icons array", () => { + const config: AppConfig = { + name: "test-app", + version: "1.0.0", + tools: baseTools, + icons: [ + { src: "https://example.com/icon-48.png", sizes: ["48x48"] }, + { src: "https://example.com/icon-96.png", sizes: ["96x96"] }, + ], + }; + + expect(config.icons).toHaveLength(2); + expect(config.icons?.[0].sizes).toEqual(["48x48"]); + }); + + it("should accept both icon and icons (icons takes precedence)", () => { + const config: AppConfig = { + name: "test-app", + version: "1.0.0", + tools: baseTools, + icon: "https://example.com/fallback.png", + icons: [{ src: "https://example.com/primary.png" }], + }; + + expect(config.icon).toBe("https://example.com/fallback.png"); + expect(config.icons).toHaveLength(1); + }); + + it("should accept theme-specific icons", () => { + const config: AppConfig = { + name: "test-app", + version: "1.0.0", + tools: baseTools, + icons: [ + { src: "https://example.com/light.png", theme: "light" }, + { src: "https://example.com/dark.png", theme: "dark" }, + ], + }; + + expect(config.icons?.[0].theme).toBe("light"); + expect(config.icons?.[1].theme).toBe("dark"); + }); + + it("should accept data URI icons", () => { + const svgBase64 = "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg=="; + const config: AppConfig = { + name: "test-app", + version: "1.0.0", + tools: baseTools, + icon: `data:image/svg+xml;base64,${svgBase64}`, + }; + + expect(config.icon).toMatch(/^data:image\/svg\+xml;base64,/); + }); + }); +}); From c943304002c85b2551088ac098efe20781944145 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Jan 2026 10:56:10 +0000 Subject: [PATCH 2/8] feat(core): Add iconFromFile helper for local image embedding Add a convenience function to convert local image files to base64 data URIs for use with server icon configuration. Features: - Reads local files and converts to base64 data URI - Auto-detects MIME type from file extension - Supports PNG, JPEG, SVG, WebP, GIF, ICO formats - Allows custom mimeType override for unsupported formats - Accepts optional sizes and theme parameters Usage: ```typescript import { createApp, iconFromFile } from "@mcp-apps-kit/core"; const app = createApp({ name: "my-app", version: "1.0.0", icons: [ iconFromFile("./assets/icon.png"), iconFromFile("./assets/icon-48.png", { sizes: ["48x48"] }), iconFromFile("./assets/icon-dark.png", { theme: "dark" }), ], tools: { ... } }); ``` --- packages/core/src/index.ts | 4 + packages/core/src/utils/icons.ts | 123 ++++++++++++++++++++++++ packages/core/tests/unit/icons.test.ts | 128 ++++++++++++++++++++++++- 3 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/utils/icons.ts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 56e3dbb4..d5365d6f 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -205,6 +205,10 @@ export type { OpenAIUIResourceMetadata, } from "./utils/csp"; +// Icon utilities +export { iconFromFile } from "./utils/icons"; +export type { IconFromFileOptions } from "./utils/icons"; + // ============================================================================= // MAIN ENTRY POINTS // ============================================================================= diff --git a/packages/core/src/utils/icons.ts b/packages/core/src/utils/icons.ts new file mode 100644 index 00000000..69dc278d --- /dev/null +++ b/packages/core/src/utils/icons.ts @@ -0,0 +1,123 @@ +/** + * Icon utility functions for MCP server configuration + */ + +import * as fs from "node:fs"; +import * as path from "node:path"; +import type { Icon, IconTheme } from "../types/config"; + +/** + * MIME type mappings for common image formats + */ +const MIME_TYPES: Record = { + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".svg": "image/svg+xml", + ".webp": "image/webp", + ".gif": "image/gif", + ".ico": "image/x-icon", +}; + +/** + * Options for creating an icon from a file + */ +export interface IconFromFileOptions { + /** + * Icon sizes in "WxH" format. + * Use `["any"]` for scalable formats like SVG. + * + * @example ["48x48", "96x96"] + */ + sizes?: string[]; + + /** + * Theme this icon is designed for. + */ + theme?: IconTheme; + + /** + * Override the auto-detected MIME type. + */ + mimeType?: string; +} + +/** + * Create an Icon object from a local image file. + * + * Reads the file, converts it to a base64 data URI, and returns + * an Icon object ready for use in createApp configuration. + * + * @param filePath - Path to the image file (absolute or relative to cwd) + * @param options - Optional icon configuration (sizes, theme, mimeType override) + * @returns Icon object with base64 data URI + * + * @example Basic usage + * ```typescript + * import { createApp, iconFromFile } from "@mcp-apps-kit/core"; + * + * const app = createApp({ + * name: "my-app", + * version: "1.0.0", + * icons: [iconFromFile("./assets/icon.png")], + * tools: { ... } + * }); + * ``` + * + * @example With options + * ```typescript + * const app = createApp({ + * name: "my-app", + * version: "1.0.0", + * icons: [ + * iconFromFile("./assets/icon-48.png", { sizes: ["48x48"] }), + * iconFromFile("./assets/icon-dark.png", { theme: "dark" }), + * iconFromFile("./assets/icon.svg", { sizes: ["any"] }), + * ], + * tools: { ... } + * }); + * ``` + * + * @throws Error if the file cannot be read or has an unsupported extension + */ +export function iconFromFile(filePath: string, options: IconFromFileOptions = {}): Icon { + // Resolve to absolute path + const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath); + + // Read file + const fileBuffer = fs.readFileSync(absolutePath); + + // Detect MIME type from extension + const ext = path.extname(absolutePath).toLowerCase(); + const detectedMimeType = MIME_TYPES[ext]; + + if (!detectedMimeType && !options.mimeType) { + throw new Error( + `Unsupported image format: ${ext}. ` + + `Supported formats: ${Object.keys(MIME_TYPES).join(", ")}. ` + + `You can override this by providing a mimeType option.` + ); + } + + const mimeType = options.mimeType ?? detectedMimeType; + + // Convert to base64 data URI + const base64 = fileBuffer.toString("base64"); + const dataUri = `data:${mimeType};base64,${base64}`; + + // Build Icon object + const icon: Icon = { + src: dataUri, + mimeType, + }; + + if (options.sizes) { + icon.sizes = options.sizes; + } + + if (options.theme) { + icon.theme = options.theme; + } + + return icon; +} diff --git a/packages/core/tests/unit/icons.test.ts b/packages/core/tests/unit/icons.test.ts index 43ea6b86..28cab03f 100644 --- a/packages/core/tests/unit/icons.test.ts +++ b/packages/core/tests/unit/icons.test.ts @@ -4,11 +4,19 @@ * Tests the icon types and configuration for MCP server icons. */ -import { describe, it, expect, expectTypeOf } from "vitest"; +import { describe, it, expect, expectTypeOf, vi, beforeEach } from "vitest"; import { z } from "zod"; import type { Icon, IconTheme, AppConfig } from "../../src/types/config"; import type { ToolDefs } from "../../src/types/tools"; +// Mock node:fs before importing iconFromFile +vi.mock("node:fs", () => ({ + readFileSync: vi.fn(), +})); + +import { iconFromFile } from "../../src/utils/icons"; +import { readFileSync } from "node:fs"; + describe("Icon types", () => { describe("Icon interface", () => { it("should accept a minimal icon with just src", () => { @@ -133,3 +141,121 @@ describe("Icon types", () => { }); }); }); + +describe("iconFromFile", () => { + const mockReadFileSync = vi.mocked(readFileSync); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should convert PNG file to data URI", () => { + const pngData = Buffer.from([0x89, 0x50, 0x4e, 0x47]); // PNG magic bytes + mockReadFileSync.mockReturnValue(pngData); + + const icon = iconFromFile("/path/to/icon.png"); + + expect(icon.src).toMatch(/^data:image\/png;base64,/); + expect(icon.mimeType).toBe("image/png"); + expect(icon.sizes).toBeUndefined(); + expect(icon.theme).toBeUndefined(); + }); + + it("should convert JPEG file to data URI", () => { + const jpegData = Buffer.from([0xff, 0xd8, 0xff, 0xe0]); // JPEG magic bytes + mockReadFileSync.mockReturnValue(jpegData); + + const icon = iconFromFile("/path/to/photo.jpg"); + + expect(icon.src).toMatch(/^data:image\/jpeg;base64,/); + expect(icon.mimeType).toBe("image/jpeg"); + }); + + it("should convert SVG file to data URI", () => { + const svgContent = Buffer.from(''); + mockReadFileSync.mockReturnValue(svgContent); + + const icon = iconFromFile("/path/to/icon.svg"); + + expect(icon.src).toMatch(/^data:image\/svg\+xml;base64,/); + expect(icon.mimeType).toBe("image/svg+xml"); + }); + + it("should handle .jpeg extension", () => { + const jpegData = Buffer.from([0xff, 0xd8, 0xff, 0xe0]); + mockReadFileSync.mockReturnValue(jpegData); + + const icon = iconFromFile("/path/to/photo.jpeg"); + + expect(icon.mimeType).toBe("image/jpeg"); + }); + + it("should handle WebP files", () => { + const webpData = Buffer.from("RIFF....WEBP"); + mockReadFileSync.mockReturnValue(webpData); + + const icon = iconFromFile("/path/to/icon.webp"); + + expect(icon.mimeType).toBe("image/webp"); + }); + + it("should include sizes option when provided", () => { + const pngData = Buffer.from([0x89, 0x50, 0x4e, 0x47]); + mockReadFileSync.mockReturnValue(pngData); + + const icon = iconFromFile("/path/to/icon.png", { sizes: ["48x48", "96x96"] }); + + expect(icon.sizes).toEqual(["48x48", "96x96"]); + }); + + it("should include theme option when provided", () => { + const pngData = Buffer.from([0x89, 0x50, 0x4e, 0x47]); + mockReadFileSync.mockReturnValue(pngData); + + const icon = iconFromFile("/path/to/icon-dark.png", { theme: "dark" }); + + expect(icon.theme).toBe("dark"); + }); + + it("should allow mimeType override", () => { + const customData = Buffer.from("custom format"); + mockReadFileSync.mockReturnValue(customData); + + const icon = iconFromFile("/path/to/icon.custom", { mimeType: "image/x-custom" }); + + expect(icon.src).toMatch(/^data:image\/x-custom;base64,/); + expect(icon.mimeType).toBe("image/x-custom"); + }); + + it("should throw for unsupported extension without mimeType override", () => { + const unknownData = Buffer.from("unknown format"); + mockReadFileSync.mockReturnValue(unknownData); + + expect(() => iconFromFile("/path/to/icon.xyz")).toThrow("Unsupported image format: .xyz"); + }); + + it("should correctly encode file content as base64", () => { + const testContent = Buffer.from("Hello, World!"); + mockReadFileSync.mockReturnValue(testContent); + + const icon = iconFromFile("/path/to/test.png"); + + // "Hello, World!" in base64 is "SGVsbG8sIFdvcmxkIQ==" + expect(icon.src).toBe("data:image/png;base64,SGVsbG8sIFdvcmxkIQ=="); + }); + + it("should handle all options together", () => { + const pngData = Buffer.from([0x89, 0x50, 0x4e, 0x47]); + mockReadFileSync.mockReturnValue(pngData); + + const icon = iconFromFile("/path/to/icon.png", { + sizes: ["48x48"], + theme: "light", + }); + + expect(icon.src).toMatch(/^data:image\/png;base64,/); + expect(icon.mimeType).toBe("image/png"); + expect(icon.sizes).toEqual(["48x48"]); + expect(icon.theme).toBe("light"); + }); +}); From 5667a66f45b1c58aaf097d0c9dcac0c754d951f4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Jan 2026 11:56:04 +0000 Subject: [PATCH 3/8] fix(core): Add validation and safety limits for icon configuration - Add 1MB file size limit to iconFromFile to prevent excessive memory usage - Add error handling wrapper for file read operations with descriptive messages - Add icon validation in normalizeIcons to ensure src is non-empty string - Add comprehensive tests for new validation and error scenarios --- packages/core/src/server/index.ts | 21 ++++++++++++++++ packages/core/src/utils/icons.ts | 34 +++++++++++++++++++++++--- packages/core/tests/unit/icons.test.ts | 26 ++++++++++++++++++++ 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/packages/core/src/server/index.ts b/packages/core/src/server/index.ts index 5fec7c30..d6af7eea 100644 --- a/packages/core/src/server/index.ts +++ b/packages/core/src/server/index.ts @@ -42,20 +42,41 @@ import * as crypto from "node:crypto"; // ICON HELPERS // ============================================================================= +/** + * Validate an icon object has required fields. + */ +function validateIcon(icon: Icon, index: number): void { + if (!icon.src || typeof icon.src !== "string" || icon.src.trim() === "") { + throw new Error(`Invalid icon at index ${index}: 'src' must be a non-empty string`); + } +} + /** * Normalize icon configuration into an icons array. * * Handles both the shorthand `icon` string and the full `icons` array. * If both are provided, `icons` takes precedence. + * + * @throws Error if any icon has an invalid or empty src */ function normalizeIcons(icon: string | undefined, icons: Icon[] | undefined): Icon[] | undefined { // icons array takes precedence if (icons && icons.length > 0) { + // Validate each icon + for (let i = 0; i < icons.length; i++) { + const icon = icons[i]; + if (icon) { + validateIcon(icon, i); + } + } return icons; } // Convert shorthand icon string to icons array if (icon) { + if (typeof icon !== "string" || icon.trim() === "") { + throw new Error("Icon must be a non-empty string URL or data URI"); + } return [{ src: icon }]; } diff --git a/packages/core/src/utils/icons.ts b/packages/core/src/utils/icons.ts index 69dc278d..b472c839 100644 --- a/packages/core/src/utils/icons.ts +++ b/packages/core/src/utils/icons.ts @@ -6,6 +6,12 @@ import * as fs from "node:fs"; import * as path from "node:path"; import type { Icon, IconTheme } from "../types/config"; +/** + * Maximum allowed icon file size (1MB). + * Base64 encoding increases size by ~33%, so this limits data URIs to ~1.33MB. + */ +const MAX_ICON_SIZE = 1024 * 1024; + /** * MIME type mappings for common image formats */ @@ -48,6 +54,10 @@ export interface IconFromFileOptions { * Reads the file, converts it to a base64 data URI, and returns * an Icon object ready for use in createApp configuration. * + * **Size limit:** Files must be under 1MB. For larger images, host them + * externally and use a URL instead. Consider using SVG for logos (smaller + * size, scalable). + * * @param filePath - Path to the image file (absolute or relative to cwd) * @param options - Optional icon configuration (sizes, theme, mimeType override) * @returns Icon object with base64 data URI @@ -78,14 +88,29 @@ export interface IconFromFileOptions { * }); * ``` * - * @throws Error if the file cannot be read or has an unsupported extension + * @throws Error if the file cannot be read, exceeds size limit, or has an unsupported extension */ export function iconFromFile(filePath: string, options: IconFromFileOptions = {}): Icon { // Resolve to absolute path const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath); - // Read file - const fileBuffer = fs.readFileSync(absolutePath); + // Read file with error handling + let fileBuffer: Buffer; + try { + fileBuffer = fs.readFileSync(absolutePath); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to read icon file "${filePath}": ${message}`); + } + + // Check file size + if (fileBuffer.length > MAX_ICON_SIZE) { + const sizeMB = (fileBuffer.length / (1024 * 1024)).toFixed(2); + throw new Error( + `Icon file too large: ${sizeMB}MB (max: 1MB). ` + + `Consider using a smaller image, SVG format, or hosting it externally with a URL.` + ); + } // Detect MIME type from extension const ext = path.extname(absolutePath).toLowerCase(); @@ -99,7 +124,8 @@ export function iconFromFile(filePath: string, options: IconFromFileOptions = {} ); } - const mimeType = options.mimeType ?? detectedMimeType; + // mimeType is guaranteed to be defined after validation above + const mimeType = options.mimeType ?? detectedMimeType ?? ""; // Convert to base64 data URI const base64 = fileBuffer.toString("base64"); diff --git a/packages/core/tests/unit/icons.test.ts b/packages/core/tests/unit/icons.test.ts index 28cab03f..c78b2863 100644 --- a/packages/core/tests/unit/icons.test.ts +++ b/packages/core/tests/unit/icons.test.ts @@ -234,6 +234,32 @@ describe("iconFromFile", () => { expect(() => iconFromFile("/path/to/icon.xyz")).toThrow("Unsupported image format: .xyz"); }); + it("should throw for files exceeding size limit", () => { + // Create buffer larger than 1MB + const largeBuffer = Buffer.alloc(1024 * 1024 + 1); + mockReadFileSync.mockReturnValue(largeBuffer); + + expect(() => iconFromFile("/path/to/large.png")).toThrow(/Icon file too large/); + }); + + it("should accept files at exactly 1MB", () => { + const exactlyOneMB = Buffer.alloc(1024 * 1024); + mockReadFileSync.mockReturnValue(exactlyOneMB); + + const icon = iconFromFile("/path/to/exact.png"); + expect(icon.src).toMatch(/^data:image\/png;base64,/); + }); + + it("should wrap file read errors with helpful message", () => { + mockReadFileSync.mockImplementation(() => { + throw new Error("ENOENT: no such file or directory"); + }); + + expect(() => iconFromFile("./missing.png")).toThrow( + 'Failed to read icon file "./missing.png": ENOENT: no such file or directory' + ); + }); + it("should correctly encode file content as base64", () => { const testContent = Buffer.from("Hello, World!"); mockReadFileSync.mockReturnValue(testContent); From 1da0ffa18d79a4eb9e1bca3e3cb21bf6a84a0287 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Jan 2026 13:00:04 +0000 Subject: [PATCH 4/8] test(core): Add comprehensive unit tests for normalizeIcons function - Export normalizeIcons for testability - Add unit tests for icon shorthand, icons array, and precedence - Add validation tests for empty/whitespace icon values - Test theme-specific icons and edge cases --- packages/core/src/index.ts | 1 + packages/core/src/server/index.ts | 5 +- packages/core/tests/unit/icons.test.ts | 109 +++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 1 deletion(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index d5365d6f..c872a44a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -208,6 +208,7 @@ export type { // Icon utilities export { iconFromFile } from "./utils/icons"; export type { IconFromFileOptions } from "./utils/icons"; +export { normalizeIcons } from "./server/index.js"; // ============================================================================= // MAIN ENTRY POINTS diff --git a/packages/core/src/server/index.ts b/packages/core/src/server/index.ts index d6af7eea..9deafe9b 100644 --- a/packages/core/src/server/index.ts +++ b/packages/core/src/server/index.ts @@ -59,7 +59,10 @@ function validateIcon(icon: Icon, index: number): void { * * @throws Error if any icon has an invalid or empty src */ -function normalizeIcons(icon: string | undefined, icons: Icon[] | undefined): Icon[] | undefined { +export function normalizeIcons( + icon: string | undefined, + icons: Icon[] | undefined +): Icon[] | undefined { // icons array takes precedence if (icons && icons.length > 0) { // Validate each icon diff --git a/packages/core/tests/unit/icons.test.ts b/packages/core/tests/unit/icons.test.ts index c78b2863..3223209c 100644 --- a/packages/core/tests/unit/icons.test.ts +++ b/packages/core/tests/unit/icons.test.ts @@ -15,6 +15,7 @@ vi.mock("node:fs", () => ({ })); import { iconFromFile } from "../../src/utils/icons"; +import { normalizeIcons } from "../../src/server/index"; import { readFileSync } from "node:fs"; describe("Icon types", () => { @@ -285,3 +286,111 @@ describe("iconFromFile", () => { expect(icon.theme).toBe("light"); }); }); + +describe("normalizeIcons", () => { + describe("with icon shorthand", () => { + it("should convert icon string to icons array", () => { + const result = normalizeIcons("https://example.com/icon.png", undefined); + + expect(result).toEqual([{ src: "https://example.com/icon.png" }]); + }); + + it("should convert data URI icon to icons array", () => { + const dataUri = "data:image/svg+xml;base64,PHN2Zw=="; + const result = normalizeIcons(dataUri, undefined); + + expect(result).toEqual([{ src: dataUri }]); + }); + + it("should return undefined for empty string icon", () => { + // Empty string is falsy, so it's treated as "no icon provided" + const result = normalizeIcons("", undefined); + expect(result).toBeUndefined(); + }); + + it("should throw for whitespace-only icon", () => { + expect(() => normalizeIcons(" ", undefined)).toThrow( + "Icon must be a non-empty string URL or data URI" + ); + }); + }); + + describe("with icons array", () => { + it("should return icons array as-is", () => { + const icons = [ + { src: "https://example.com/icon-48.png", sizes: ["48x48"] as string[] }, + { src: "https://example.com/icon-96.png", sizes: ["96x96"] as string[] }, + ]; + + const result = normalizeIcons(undefined, icons); + + expect(result).toBe(icons); + }); + + it("should validate icon src is non-empty", () => { + expect(() => normalizeIcons(undefined, [{ src: "" }])).toThrow( + "Invalid icon at index 0: 'src' must be a non-empty string" + ); + }); + + it("should validate icon src is not whitespace only", () => { + expect(() => normalizeIcons(undefined, [{ src: " " }])).toThrow( + "Invalid icon at index 0: 'src' must be a non-empty string" + ); + }); + + it("should report correct index for invalid icon", () => { + const icons = [{ src: "https://example.com/valid.png" }, { src: "" }]; + + expect(() => normalizeIcons(undefined, icons)).toThrow( + "Invalid icon at index 1: 'src' must be a non-empty string" + ); + }); + + it("should accept theme-specific icons", () => { + const icons = [ + { src: "https://example.com/light.png", theme: "light" as const }, + { src: "https://example.com/dark.png", theme: "dark" as const }, + ]; + + const result = normalizeIcons(undefined, icons); + + expect(result).toBe(icons); + expect(result?.[0].theme).toBe("light"); + expect(result?.[1].theme).toBe("dark"); + }); + }); + + describe("precedence", () => { + it("should prefer icons array over icon shorthand", () => { + const icons = [{ src: "https://example.com/primary.png" }]; + + const result = normalizeIcons("https://example.com/fallback.png", icons); + + expect(result).toBe(icons); + expect(result).toHaveLength(1); + expect(result?.[0].src).toBe("https://example.com/primary.png"); + }); + + it("should ignore icon shorthand when icons array is provided", () => { + const icons = [{ src: "https://example.com/primary.png" }]; + const result = normalizeIcons("https://example.com/ignored.png", icons); + + expect(result).not.toContainEqual({ src: "https://example.com/ignored.png" }); + }); + }); + + describe("with no icons", () => { + it("should return undefined when no icon or icons provided", () => { + const result = normalizeIcons(undefined, undefined); + + expect(result).toBeUndefined(); + }); + + it("should return undefined for empty icons array", () => { + const result = normalizeIcons(undefined, []); + + expect(result).toBeUndefined(); + }); + }); +}); From 81addaed5e8df8a783e741b6339ce16af66612ec Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Jan 2026 13:09:54 +0000 Subject: [PATCH 5/8] fix(testing): Add noNaN option to float generator The fast-check float generator can produce NaN values by default, which caused the "should generate floats within bounds" test to fail intermittently. Adding noNaN: true ensures only valid numeric floats are generated. --- packages/testing/src/eval/property/generators.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/testing/src/eval/property/generators.ts b/packages/testing/src/eval/property/generators.ts index 1efe5f10..1c4008cc 100644 --- a/packages/testing/src/eval/property/generators.ts +++ b/packages/testing/src/eval/property/generators.ts @@ -139,7 +139,7 @@ export const generators = { } else if (max !== undefined) { return fc.float({ max, noNaN: true }); } - return fc.float(); + return fc.float({ noNaN: true }); }); }, From 106d3bce3100cb938a74b883f62c8bf42ff41f48 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Jan 2026 15:18:57 +0000 Subject: [PATCH 6/8] refactor(core): Address code review feedback for icon configuration - Use forEach with block body instead of for loop (type safety) - Add sizes format validation (WxH pattern or "any") - Add MIME type validation (must be image/*) - Make empty string icon throw error (consistent with whitespace) - Remove normalizeIcons from public exports (internal only) - Add comprehensive tests for new validation logic --- packages/core/src/index.ts | 1 - packages/core/src/server/index.ts | 36 ++++++++++++---- packages/core/src/utils/icons.ts | 7 ++++ packages/core/tests/unit/icons.test.ts | 58 ++++++++++++++++++++++++-- 4 files changed, 89 insertions(+), 13 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index c872a44a..d5365d6f 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -208,7 +208,6 @@ export type { // Icon utilities export { iconFromFile } from "./utils/icons"; export type { IconFromFileOptions } from "./utils/icons"; -export { normalizeIcons } from "./server/index.js"; // ============================================================================= // MAIN ENTRY POINTS diff --git a/packages/core/src/server/index.ts b/packages/core/src/server/index.ts index 9deafe9b..14db093b 100644 --- a/packages/core/src/server/index.ts +++ b/packages/core/src/server/index.ts @@ -42,6 +42,27 @@ import * as crypto from "node:crypto"; // ICON HELPERS // ============================================================================= +/** + * Valid sizes format pattern: "WxH" (e.g., "48x48") or "any" for scalable formats. + */ +const SIZES_PATTERN = /^\d+x\d+$/; + +/** + * Validate icon sizes array format. + */ +function validateIconSizes(sizes: string[] | undefined, index: number): void { + if (!sizes) return; + + for (const size of sizes) { + if (size !== "any" && !SIZES_PATTERN.test(size)) { + throw new Error( + `Invalid icon size "${size}" at index ${index}: ` + + `Sizes must be in "WxH" format (e.g., "48x48") or "any" for scalable formats.` + ); + } + } +} + /** * Validate an icon object has required fields. */ @@ -49,6 +70,7 @@ function validateIcon(icon: Icon, index: number): void { if (!icon.src || typeof icon.src !== "string" || icon.src.trim() === "") { throw new Error(`Invalid icon at index ${index}: 'src' must be a non-empty string`); } + validateIconSizes(icon.sizes, index); } /** @@ -57,6 +79,7 @@ function validateIcon(icon: Icon, index: number): void { * Handles both the shorthand `icon` string and the full `icons` array. * If both are provided, `icons` takes precedence. * + * @internal This function is used internally by createApp. Do not use directly. * @throws Error if any icon has an invalid or empty src */ export function normalizeIcons( @@ -65,18 +88,15 @@ export function normalizeIcons( ): Icon[] | undefined { // icons array takes precedence if (icons && icons.length > 0) { - // Validate each icon - for (let i = 0; i < icons.length; i++) { - const icon = icons[i]; - if (icon) { - validateIcon(icon, i); - } - } + icons.forEach((ic, i) => { + validateIcon(ic, i); + }); return icons; } // Convert shorthand icon string to icons array - if (icon) { + // Validate before falsy check for consistency (empty string should also throw) + if (icon !== undefined) { if (typeof icon !== "string" || icon.trim() === "") { throw new Error("Icon must be a non-empty string URL or data URI"); } diff --git a/packages/core/src/utils/icons.ts b/packages/core/src/utils/icons.ts index b472c839..a607b401 100644 --- a/packages/core/src/utils/icons.ts +++ b/packages/core/src/utils/icons.ts @@ -127,6 +127,13 @@ export function iconFromFile(filePath: string, options: IconFromFileOptions = {} // mimeType is guaranteed to be defined after validation above const mimeType = options.mimeType ?? detectedMimeType ?? ""; + // Validate MIME type is an image type + if (!mimeType.startsWith("image/")) { + throw new Error( + `Invalid MIME type: "${mimeType}". Must be an image MIME type (e.g., "image/png").` + ); + } + // Convert to base64 data URI const base64 = fileBuffer.toString("base64"); const dataUri = `data:${mimeType};base64,${base64}`; diff --git a/packages/core/tests/unit/icons.test.ts b/packages/core/tests/unit/icons.test.ts index 3223209c..751f9748 100644 --- a/packages/core/tests/unit/icons.test.ts +++ b/packages/core/tests/unit/icons.test.ts @@ -235,6 +235,25 @@ describe("iconFromFile", () => { expect(() => iconFromFile("/path/to/icon.xyz")).toThrow("Unsupported image format: .xyz"); }); + it("should throw for non-image MIME type override", () => { + const data = Buffer.from("test data"); + mockReadFileSync.mockReturnValue(data); + + expect(() => iconFromFile("/path/to/file.txt", { mimeType: "text/plain" })).toThrow( + 'Invalid MIME type: "text/plain". Must be an image MIME type' + ); + }); + + it("should accept custom image MIME types", () => { + const data = Buffer.from("custom image data"); + mockReadFileSync.mockReturnValue(data); + + const icon = iconFromFile("/path/to/file.custom", { mimeType: "image/x-custom" }); + + expect(icon.mimeType).toBe("image/x-custom"); + expect(icon.src).toMatch(/^data:image\/x-custom;base64,/); + }); + it("should throw for files exceeding size limit", () => { // Create buffer larger than 1MB const largeBuffer = Buffer.alloc(1024 * 1024 + 1); @@ -302,10 +321,10 @@ describe("normalizeIcons", () => { expect(result).toEqual([{ src: dataUri }]); }); - it("should return undefined for empty string icon", () => { - // Empty string is falsy, so it's treated as "no icon provided" - const result = normalizeIcons("", undefined); - expect(result).toBeUndefined(); + it("should throw for empty string icon", () => { + expect(() => normalizeIcons("", undefined)).toThrow( + "Icon must be a non-empty string URL or data URI" + ); }); it("should throw for whitespace-only icon", () => { @@ -393,4 +412,35 @@ describe("normalizeIcons", () => { expect(result).toBeUndefined(); }); }); + + describe("sizes format validation", () => { + it("should accept valid WxH sizes format", () => { + const icons = [{ src: "https://example.com/icon.png", sizes: ["48x48", "96x96"] }]; + const result = normalizeIcons(undefined, icons); + expect(result).toBe(icons); + }); + + it("should accept 'any' as valid size for scalable formats", () => { + const icons = [{ src: "https://example.com/icon.svg", sizes: ["any"] }]; + const result = normalizeIcons(undefined, icons); + expect(result).toBe(icons); + }); + + it("should throw for invalid sizes format", () => { + const icons = [{ src: "https://example.com/icon.png", sizes: ["48"] }]; + expect(() => normalizeIcons(undefined, icons)).toThrow('Invalid icon size "48" at index 0'); + }); + + it("should throw for malformed sizes like 48x", () => { + const icons = [{ src: "https://example.com/icon.png", sizes: ["48x"] }]; + expect(() => normalizeIcons(undefined, icons)).toThrow('Invalid icon size "48x" at index 0'); + }); + + it("should throw for text in sizes", () => { + const icons = [{ src: "https://example.com/icon.png", sizes: ["large"] }]; + expect(() => normalizeIcons(undefined, icons)).toThrow( + 'Invalid icon size "large" at index 0' + ); + }); + }); }); From 4ffb931a6e26ebca5046fc8e1ef7937be3077448 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Jan 2026 15:24:54 +0000 Subject: [PATCH 7/8] fix(core): Propagate global icons into version configs Global icon/icons from VersionsConfig were not being applied to individual versions. Updated mergeVersionConfig to accept and forward these fields, ensuring icons configured at the global level are properly propagated to all versions. --- packages/core/src/createApp.ts | 18 ++++++++-- packages/core/tests/unit/versioning.test.ts | 39 +++++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/packages/core/src/createApp.ts b/packages/core/src/createApp.ts index 3e459f6c..f803c3e4 100644 --- a/packages/core/src/createApp.ts +++ b/packages/core/src/createApp.ts @@ -19,6 +19,7 @@ import type { VersionConfig, GlobalConfig, VersionSpecificConfig, + Icon, } from "./types/config"; import type { UIDef, UIDefs } from "./types/ui"; import type { Middleware } from "./middleware/types"; @@ -436,7 +437,9 @@ function deepMerge>( function mergeVersionConfig( globalConfig: GlobalConfig | undefined, versionConfig: VersionConfig, - globalPlugins: Plugin[] | undefined + globalPlugins: Plugin[] | undefined, + globalIcon?: string, + globalIcons?: Icon[] ): AppConfig & { ui?: UIDefs } { // Handle primitive config properties (null means remove, undefined means inherit) const serverRoute = @@ -488,6 +491,9 @@ function mergeVersionConfig( ui: versionConfig.ui, config: mergedConfig, plugins: mergedPlugins.length > 0 ? mergedPlugins : undefined, + // Propagate global icons to each version + icon: globalIcon, + icons: globalIcons, }; } @@ -820,8 +826,14 @@ function createMultiVersionApp(config: VersionsConfig): A // Create app instance for each version for (const [versionKey, versionConfig] of Object.entries(config.versions)) { - // Merge global and version-specific configs - const mergedConfig = mergeVersionConfig(config.config, versionConfig, config.plugins); + // Merge global and version-specific configs (including icons) + const mergedConfig = mergeVersionConfig( + config.config, + versionConfig, + config.plugins, + config.icon, + config.icons + ); mergedConfig.name = config.name; // Set app name from global config // Extract colocated UIs from tool definitions diff --git a/packages/core/tests/unit/versioning.test.ts b/packages/core/tests/unit/versioning.test.ts index 362f3b10..f964f6be 100644 --- a/packages/core/tests/unit/versioning.test.ts +++ b/packages/core/tests/unit/versioning.test.ts @@ -286,6 +286,45 @@ describe("createApp versioning", () => { expect(app).toBeDefined(); // Both plugins should be registered for v1 }); + + it("should propagate global icon to all versions", () => { + const app = createApp({ + name: "test-app", + icon: "https://example.com/icon.png", + versions: { + v1: { + version: "1.0.0", + tools: {}, + }, + v2: { + version: "2.0.0", + tools: {}, + }, + }, + }); + + expect(app).toBeDefined(); + // Global icon should be applied to both versions + }); + + it("should propagate global icons array to all versions", () => { + const app = createApp({ + name: "test-app", + icons: [ + { src: "https://example.com/icon-48.png", sizes: ["48x48"] }, + { src: "https://example.com/icon-96.png", sizes: ["96x96"] }, + ], + versions: { + v1: { + version: "1.0.0", + tools: {}, + }, + }, + }); + + expect(app).toBeDefined(); + // Global icons should be applied to v1 + }); }); describe("deep config merging", () => { From d2df9d3adc44f82d935619fe1b38a8848cf96d02 Mon Sep 17 00:00:00 2001 From: gabrypavanello Date: Wed, 14 Jan 2026 15:11:24 +0100 Subject: [PATCH 8/8] feat(core): Update icon configuration to use local file support Replaced the server icon URL with a local image file using the new iconFromFile helper. This change enhances the flexibility of icon configuration by allowing local image embedding for server icons. --- examples/minimal/src/index.ts | 10 ++++++++-- examples/minimal/src/logo.png | Bin 0 -> 56749 bytes 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 examples/minimal/src/logo.png diff --git a/examples/minimal/src/index.ts b/examples/minimal/src/index.ts index eb3dc94f..09835073 100644 --- a/examples/minimal/src/index.ts +++ b/examples/minimal/src/index.ts @@ -8,7 +8,13 @@ * - Each version exposed at /v1/mcp and /v2/mcp */ -import { createApp, defineTool, tool, type ClientToolsFromCore } from "@mcp-apps-kit/core"; +import { + createApp, + defineTool, + tool, + type ClientToolsFromCore, + iconFromFile, +} from "@mcp-apps-kit/core"; import { defineReactUI } from "@mcp-apps-kit/ui-react-builder"; import { GreetingWidgetV1 } from "./ui/GreetingWidgetV1"; import { GreetingWidgetV2 } from "./ui/GreetingWidgetV2"; @@ -173,7 +179,7 @@ const app = createApp({ // Server icon - displayed in MCP client UIs // Can be a URL or base64 data URI - icon: "https://raw.githubusercontent.com/anthropics/claude-code/main/assets/icon.svg", + icon: iconFromFile("./src/logo.png").src, // Shared config across all versions config: { diff --git a/examples/minimal/src/logo.png b/examples/minimal/src/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2c8f5df50fea371c2bee32051bb834ff917c239c GIT binary patch literal 56749 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 z%0A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