Skip to content

feat(core): Add MCP server icon support - #85

Merged
gabrypavanello merged 8 commits into
mainfrom
claude/add-mcp-server-icon-eXjOv
Jan 14, 2026
Merged

feat(core): Add MCP server icon support#85
gabrypavanello merged 8 commits into
mainfrom
claude/add-mcp-server-icon-eXjOv

Conversation

@gabrypavanello

Copy link
Copy Markdown
Contributor

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:

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: { ... }
});

@coderabbitai

coderabbitai Bot commented Jan 11, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added icon configuration support to MCP applications, allowing icons to be defined as a shorthand string or as a full array with multiple sizes and theme variants.
    • Added utility to convert local image files to icon data URIs for easy integration.
    • Icons are validated and normalized automatically during app initialization.
  • Tests

    • Added comprehensive test coverage for icon configuration, file conversion, and validation logic.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds icon support: new Icon/IconTheme types and config fields, iconFromFile utility (file→data URI), icon validation/normalize helpers in server, propagation of icon(s) through createApp/version merges, example usage, comprehensive unit tests, and a small float-generator tweak.

Changes

Cohort / File(s) Summary
Type Definitions
packages/core/src/types/config.ts
Add IconTheme and Icon types; add icon?: string and icons?: Icon[] to AppConfig and VersionsConfig.
Icon Utilities
packages/core/src/utils/icons.ts
New IconFromFileOptions and iconFromFile(filePath, options?): sync read, size limit, MIME detection/override, validate image MIME, produce base64 data URI Icon.
Public Exports
packages/core/src/index.ts
Re-export Icon, IconTheme, iconFromFile, and IconFromFileOptions on the package public surface.
Server Integration
packages/core/src/server/index.ts
Add validateIcon* helpers and normalizeIcons(icon, icons); normalize and validate icons and pass resulting icons into server initialization.
App/Version Merge
packages/core/src/createApp.ts
Extend mergeVersionConfig to accept globalIcon/globalIcons and include icon/icons on merged AppConfig; propagate app-level icon fields into per-version merges.
Examples
examples/minimal/src/index.ts
Use iconFromFile("./src/logo.png").src and add icon to the createApp config (exposes icon URL/data URI).
Tests (icons)
packages/core/tests/unit/icons.test.ts
New comprehensive tests for Icon shapes, shorthand vs array precedence, theme handling, iconFromFile conversions/errors/size limits, and normalizeIcons.
Versioning Tests
packages/core/tests/unit/versioning.test.ts
Add tests asserting global icon and icons propagate to versions.
Property Generators
packages/testing/src/eval/property/generators.ts
Float generator now sets noNaN: true when unbounded (disallow NaN by default in that case).

Sequence Diagram(s)

sequenceDiagram
  participant Dev as Developer/CLI
  participant FS as Filesystem
  participant Util as iconFromFile
  participant App as createApp/mergeVersionConfig
  participant Server as MCP Server Init
  participant Client as MCP Client

  Dev ->> Util: call iconFromFile("./src/logo.png")
  Util ->> FS: read file bytes
  Util ->> Util: validate size & MIME -> base64-encode -> build Icon
  Util -->> Dev: return Icon (data URI/src)
  Dev ->> App: call createApp({ ..., icon or icons })
  App ->> App: mergeVersionConfig(..., globalIcon, globalIcons) -> attach icons to AppConfig
  Dev ->> Server: start server with AppConfig
  Server ->> Server: normalizeIcons(config.icon, config.icons) -> validated icons[]
  Server ->> Client: expose icons metadata in server info response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding MCP server icon support. It is concise, specific, and clearly reflects the primary feature introduced in this changeset.
Description check ✅ Passed The description is directly related to the changeset, clearly explaining the icon support feature, the new types (Icon and IconTheme), usage patterns, and how icons are passed to McpServer. It provides relevant context and examples.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings


📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c1f7992 and d2df9d3.

⛔ Files ignored due to path filters (1)
  • examples/minimal/src/logo.png is excluded by !**/*.png
📒 Files selected for processing (9)
  • examples/minimal/src/index.ts
  • packages/core/src/createApp.ts
  • packages/core/src/index.ts
  • packages/core/src/server/index.ts
  • packages/core/src/types/config.ts
  • packages/core/src/utils/icons.ts
  • packages/core/tests/unit/icons.test.ts
  • packages/core/tests/unit/versioning.test.ts
  • packages/testing/src/eval/property/generators.ts

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1b915d2e51

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/types/config.ts
@claude

claude Bot commented Jan 11, 2026

Copy link
Copy Markdown
Contributor

Code Review: MCP Server Icon Support

Summary

This PR implements server icon configuration following the MCP specification (2025-11-25). The implementation adds icon support with both shorthand (icon) and full array (icons) formats, plus a helpful iconFromFile utility.


✅ Strengths

1. Excellent Type Safety & Documentation

  • Well-defined Icon and IconTheme types with comprehensive JSDoc
  • Clear examples in type definitions
  • Proper TypeScript exports with export type for type-only exports ✓

2. Good API Design

  • Dual API (shorthand icon string + full icons array) provides flexibility
  • iconFromFile helper is a great DX improvement for local files
  • Icons precedence is clearly documented (icons array > icon string)

3. Strong Test Coverage

  • 261 lines of tests covering types, config, and utility functions
  • Tests cover edge cases (unsupported formats, MIME type override, etc.)
  • Proper mocking of file system operations

4. Follows Project Conventions

  • Exports through index.ts ✓
  • No any types ✓
  • Clear separation of concerns

🔍 Issues & Recommendations

1. Missing Integration Tests for normalizeIcons Function ⚠️

The normalizeIcons function in packages/core/src/server/index.ts:51 is not covered by tests. This is a critical function that determines which icon configuration to use.

Recommendation: Add integration tests to verify:

// Test that icons array takes precedence
const app = createApp({
  name: "test",
  version: "1.0.0",
  icon: "https://example.com/fallback.png",
  icons: [{ src: "https://example.com/primary.png" }],
  tools: { ... }
});
// Verify McpServer receives icons from 'icons' array, not 'icon'

Files to add tests to:

  • packages/core/tests/integration/server.test.ts or create packages/core/tests/integration/icons.test.ts

2. Potential Path Traversal Vulnerability in iconFromFile 🔒

packages/core/src/utils/icons.ts:85

const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
const fileBuffer = fs.readFileSync(absolutePath);

Issue: The function accepts arbitrary file paths without validation. A malicious developer could potentially read sensitive files:

iconFromFile("../../../etc/passwd.png", { mimeType: "image/png" })

Severity: Low-Medium (requires developer access, not runtime exploit)

Recommendations:

  1. Add path validation to ensure resolved path stays within expected boundaries
  2. Document security considerations in JSDoc
  3. Consider adding an optional baseDir parameter to restrict file access
export function iconFromFile(
  filePath: string, 
  options: IconFromFileOptions & { baseDir?: string } = {}
): Icon {
  const baseDir = options.baseDir ?? process.cwd();
  const absolutePath = path.isAbsolute(filePath) 
    ? filePath 
    : path.resolve(baseDir, filePath);
  
  // Validate path is within baseDir
  const relativePath = path.relative(baseDir, absolutePath);
  if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
    throw new Error(`File path must be within base directory: ${baseDir}`);
  }
  
  // ... rest of implementation
}

3. File Size Concerns 📦

packages/core/src/utils/icons.ts:88

The iconFromFile function reads entire files into memory and converts to base64, which increases size by ~33%. No size limit is enforced.

Potential Issues:

  • Large icon files (e.g., high-res PNG) could create massive base64 strings
  • Could lead to memory issues or slow initialization
  • MCP clients may have payload size limits

Recommendations:

  1. Add a size limit check with clear error message:
const MAX_ICON_SIZE = 1024 * 1024; // 1MB

const fileBuffer = fs.readFileSync(absolutePath);
if (fileBuffer.length > MAX_ICON_SIZE) {
  throw new Error(
    `Icon file too large: ${fileBuffer.length} bytes (max: ${MAX_ICON_SIZE}). ` +
    `Consider using a smaller image or hosting it externally.`
  );
}
  1. Document recommended icon sizes in JSDoc
  2. Suggest SVG for logos (smaller size, scalable)

4. Missing Error Handling for File Read 💥

packages/core/src/utils/icons.ts:88

const fileBuffer = fs.readFileSync(absolutePath);

Issue: readFileSync can throw various errors (ENOENT, EACCES, etc.) but they're not caught or wrapped with helpful context.

Recommendation:

try {
  const fileBuffer = fs.readFileSync(absolutePath);
} catch (error) {
  throw new Error(
    `Failed to read icon file at "${filePath}": ${error.message}`
  );
}

This provides better error messages to developers during setup.


5. Type Safety: MIME Type Could Be Undefined 🐛

packages/core/src/utils/icons.ts:102-111

const mimeType = options.mimeType ?? detectedMimeType;
// ...
const icon: Icon = {
  src: dataUri,
  mimeType,  // Could be undefined if both options.mimeType and detectedMimeType are undefined
};

Issue: While the code throws an error earlier if both are undefined, TypeScript doesn't narrow the type. The mimeType field should always be set for better MCP client compatibility.

Recommendation:

// After line 102
const mimeType = options.mimeType ?? detectedMimeType!; // Assert non-null after validation

Or better yet, ensure the Icon always has mimeType:

const icon: Icon = {
  src: dataUri,
  mimeType: mimeType!, // Non-null assertion is safe after validation
};

6. Missing Validation for Icon Array ⚠️

packages/core/src/server/index.ts:51-63

The normalizeIcons function checks icons.length > 0 but doesn't validate icon objects themselves.

Potential Issues:

  • Empty src strings
  • Invalid data URIs
  • Malformed size strings (should be "WxH" format)

Recommendation:
Add runtime validation or at least document expected format:

function normalizeIcons(icon: string | undefined, icons: Icon[] | undefined): Icon[] | undefined {
  if (icons && icons.length > 0) {
    // Optional: Validate icons have required fields
    const invalidIcons = icons.filter(i => !i.src || i.src.trim() === '');
    if (invalidIcons.length > 0) {
      throw new Error('All icons must have a non-empty src property');
    }
    return icons;
  }
  
  if (icon) {
    if (!icon.trim()) {
      throw new Error('Icon src must be a non-empty string');
    }
    return [{ src: icon }];
  }
  
  return undefined;
}

📊 Test Coverage Verification

According to CLAUDE.md, the project requires 50% test coverage minimum. Please verify coverage after adding integration tests:

pnpm test -- --coverage

Current test file covers:

  • ✅ Type definitions
  • iconFromFile utility
  • normalizeIcons function (not tested)
  • ❌ Integration with McpServer (not tested)

🎯 Action Items

Must Fix (Blocking):

  1. Add integration tests for normalizeIcons function
  2. Add integration test for icons being passed to McpServer
  3. Fix type safety issue with potentially undefined mimeType

Should Fix (High Priority):
4. Add file size limit to iconFromFile (prevent accidental huge files)
5. Add better error handling/messages for file read failures
6. Add security note to JSDoc about file path handling

Nice to Have:
7. Add validation for icon src non-empty
8. Document recommended icon sizes and formats
9. Add example in README showing icon usage


🚀 Overall Assessment

This is a solid implementation with excellent type safety and documentation. The API design is thoughtful and follows MCP spec correctly. The main concerns are around:

  • Missing integration tests (coverage)
  • File security/size validation
  • Error handling improvements

The code is production-ready after addressing the "Must Fix" items. Great work on the comprehensive unit tests!

Recommendation: Request Changes - primarily for test coverage and type safety fix.


📚 References Checked

  • ✅ MCP Specification (2025-11-25) - Icon format matches spec
  • ✅ Project conventions (CLAUDE.md) - Uses export type, proper exports
  • ✅ Zod 4 compatibility - Not applicable (no Zod schemas for icons)
  • ✅ No any types - Confirmed strict TypeScript

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @packages/core/src/utils/icons.ts:
- Around line 83-88: The iconFromFile function currently accepts unvalidated
filePath and uses fs.readFileSync, enabling path traversal and blocking IO; fix
by normalizing and resolving the input (use path.resolve and path.normalize),
ensure the resolved path is within an allowed base directory (compare with
path.relative to reject traversal outside the intended root), validate the file
exists and its size against a configurable max (reject oversized files to avoid
memory issues), and replace fs.readFileSync with an async read using
fs.promises.readFile (and make iconFromFile async or provide an async variant)
so that IconFromFileOptions can include baseDir and maxSize settings and you
throw clear errors on invalid paths or sizes.

In @packages/core/tests/unit/icons.test.ts:
- Around line 145-261: The tests lack a check that normalized icons are actually
passed into the server constructor; add a unit that mocks the McpServer export
from "@modelcontextprotocol/sdk/server/mcp.js", calls normalizeIcons (the
function under test) and then verifies the McpServer constructor was called with
an options object containing the expected icons value (e.g., expect(new
McpServer).toHaveBeenCalledWith(expect.objectContaining({ icons: ... }))).
Ensure you import or reference normalizeIcons and the mocked McpServer symbol,
set up the mock before invoking normalizeIcons, and restore/clear mocks after
the test.
🧹 Nitpick comments (5)
examples/minimal/src/index.ts (1)

121-124: Example icon URL is fine, but consider showing icons (mimeType/sizes/theme) or avoiding a brittle remote dependency.
This example may fail in environments where GitHub raw URLs are blocked/rate-limited; also it doesn’t demonstrate sizes/theme.

packages/core/tests/unit/icons.test.ts (1)

1-143: Nice coverage for Icon/IconTheme and AppConfig acceptance tests.

packages/core/src/server/index.ts (1)

41-63: normalizeIcons precedence behavior is clear and matches the PR intent.

packages/core/src/types/config.ts (1)

10-83: Icon types are straightforward and well-documented against the spec.

packages/core/src/utils/icons.ts (1)

25-43: Use export type instead of export interface.

Per the coding guidelines: "Use export type for type-only exports to prevent runtime imports of types." Consider changing from export interface to export type for consistency.

♻️ Suggested refactor
-export interface IconFromFileOptions {
+export type 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;
-}
+};

Based on coding guidelines.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c48cc0e and 1b915d2.

📒 Files selected for processing (6)
  • examples/minimal/src/index.ts
  • packages/core/src/index.ts
  • packages/core/src/server/index.ts
  • packages/core/src/types/config.ts
  • packages/core/src/utils/icons.ts
  • packages/core/tests/unit/icons.test.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use strict TypeScript - no any type allowed; use unknown with type narrowing instead
Middleware must always await next() or the middleware chain will break - follow Koa-style middleware patterns
Use export type for type-only exports to prevent runtime imports of types

Files:

  • packages/core/tests/unit/icons.test.ts
  • packages/core/src/index.ts
  • examples/minimal/src/index.ts
  • packages/core/src/utils/icons.ts
  • packages/core/src/types/config.ts
  • packages/core/src/server/index.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use defineTool and defineUI (or defineReactUI for React components) for type inference instead of manual definitions

Files:

  • packages/core/tests/unit/icons.test.ts
  • packages/core/src/index.ts
  • examples/minimal/src/index.ts
  • packages/core/src/utils/icons.ts
  • packages/core/src/types/config.ts
  • packages/core/src/server/index.ts
**/index.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Export only through index.ts files to keep the public API clean and make refactoring safe

Files:

  • packages/core/src/index.ts
  • examples/minimal/src/index.ts
  • packages/core/src/server/index.ts
🧠 Learnings (2)
📚 Learning: 2026-01-11T02:21:51.116Z
Learnt from: CR
Repo: AndurilCode/mcp-apps-kit PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T02:21:51.116Z
Learning: Applies to **/index.ts : Export only through `index.ts` files to keep the public API clean and make refactoring safe

Applied to files:

  • packages/core/src/index.ts
📚 Learning: 2026-01-11T02:21:51.116Z
Learnt from: CR
Repo: AndurilCode/mcp-apps-kit PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T02:21:51.116Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `defineTool` and `defineUI` (or `defineReactUI` for React components) for type inference instead of manual definitions

Applied to files:

  • packages/core/src/server/index.ts
🧬 Code graph analysis (2)
packages/core/tests/unit/icons.test.ts (2)
packages/core/src/index.ts (4)
  • Icon (65-65)
  • IconTheme (66-66)
  • ToolDefs (39-39)
  • AppConfig (58-58)
packages/core/src/types/config.ts (3)
  • Icon (41-83)
  • IconTheme (17-17)
  • AppConfig (588-668)
packages/core/src/types/config.ts (1)
packages/core/src/index.ts (2)
  • IconTheme (66-66)
  • Icon (65-65)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: claude-review
  • GitHub Check: test-and-lint
🔇 Additional comments (7)
packages/core/src/index.ts (1)

53-67: Public API exports look clean and TS-safe (export type used appropriately).
Based on learnings, this keeps the public surface centralized in packages/core/src/index.ts.

Also applies to: 182-185

packages/core/src/types/config.ts (1)

542-562: Config surface additions (icon/icons) look good—please ensure any runtime config validation is updated too.
If createApp/validation code strips unknown keys, these fields could be ignored unless explicitly allowed.

Also applies to: 633-667

packages/core/src/server/index.ts (1)

110-118: The McpServer constructor in @modelcontextprotocol/sdk does support an icons option. The option accepts an array of Icon objects with the shape { src: string, mimeType?: string, sizes?: string[], theme?: string }. The normalizeIcons function correctly returns Icon[] | undefined, which matches the SDK's expected format. When an icon string is provided, it is properly converted to [{ src: icon }], and when an icons array is provided, it is passed through as-is. The code's use of ...(icons && { icons }) is the correct pattern for conditionally spreading optional properties.

packages/core/src/utils/icons.ts (4)

1-7: LGTM - Clean imports.

The imports follow best practices with the node: protocol for built-in modules and proper type imports.


9-20: LGTM - Comprehensive MIME type mapping.

The MIME type mappings cover common image formats and are correctly defined. The design allows for extension via the mimeType option override.


90-106: LGTM - Solid MIME type detection with helpful error messages.

The MIME type detection logic correctly handles auto-detection from extensions and allows override. The error message is descriptive and guides users on how to handle unsupported formats.


108-122: LGTM - Clean Icon object construction.

The Icon object is built correctly with conditional properties. The explicit approach makes the logic clear and avoids adding undefined fields.

Comment thread packages/core/src/utils/icons.ts Outdated
Comment on lines +145 to +261
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('<svg xmlns="http://www.w3.org/2000/svg"></svg>');
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");
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find packages/core -type f -name "*.ts" -o -name "*.tsx" | head -20

Repository: AndurilCode/mcp-apps-kit

Length of output: 824


🏁 Script executed:

wc -l packages/core/tests/unit/icons.test.ts

Repository: AndurilCode/mcp-apps-kit

Length of output: 110


🏁 Script executed:

cat -n packages/core/tests/unit/icons.test.ts

Repository: AndurilCode/mcp-apps-kit

Length of output: 10117


🏁 Script executed:

find packages/core -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs grep -l "createServerInstance" | head -10

Repository: AndurilCode/mcp-apps-kit

Length of output: 132


🏁 Script executed:

find packages/core -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs grep -l "McpServer" | head -10

Repository: AndurilCode/mcp-apps-kit

Length of output: 265


🏁 Script executed:

cat -n packages/core/src/createApp.ts

Repository: AndurilCode/mcp-apps-kit

Length of output: 50382


🏁 Script executed:

cat -n packages/core/src/server/index.ts

Repository: AndurilCode/mcp-apps-kit

Length of output: 46510


🏁 Script executed:

find packages/core/tests -name "*.test.ts" -o -name "*.test.tsx" | xargs grep -l "createServerInstance\|McpServer" | head -10

Repository: AndurilCode/mcp-apps-kit

Length of output: 106


🏁 Script executed:

grep -r "createServerInstance" packages/core/tests --include="*.ts" --include="*.tsx"

Repository: AndurilCode/mcp-apps-kit

Length of output: 50


🏁 Script executed:

grep -r "new McpServer" packages/core/tests --include="*.ts" --include="*.tsx"

Repository: AndurilCode/mcp-apps-kit

Length of output: 50


🏁 Script executed:

cat -n packages/core/tests/unit/stdio.test.ts

Repository: AndurilCode/mcp-apps-kit

Length of output: 3559


🏁 Script executed:

find packages/core/tests -type f \( -name "*.test.ts" -o -name "*.test.tsx" \) | head -20

Repository: AndurilCode/mcp-apps-kit

Length of output: 1073


🏁 Script executed:

cat packages/core/tests/integration/server.test.ts | head -200

Repository: AndurilCode/mcp-apps-kit

Length of output: 5924


🏁 Script executed:

grep -A 10 -B 5 "icons" packages/core/tests/integration/server.test.ts

Repository: AndurilCode/mcp-apps-kit

Length of output: 50


🏁 Script executed:

grep -n "icon" packages/core/tests/integration/server.test.ts

Repository: AndurilCode/mcp-apps-kit

Length of output: 50


🏁 Script executed:

grep -rn "McpServer\|icons" packages/core/tests/contract packages/core/tests/integration --include="*.ts" | grep -i icon

Repository: AndurilCode/mcp-apps-kit

Length of output: 50


🏁 Script executed:

cat -n packages/core/src/server/index.ts | sed -n '110,120p'

Repository: AndurilCode/mcp-apps-kit

Length of output: 494


Add a test that asserts server wiring (normalizeIconsnew McpServer({ icons })) to prevent SDK/API drift.

Right now the suite doesn't verify the integration between icon config and server initialization. Mocking @modelcontextprotocol/sdk/server/mcp.js and asserting the McpServer constructor receives the correct icons option would catch breakage early when the SDK API changes.

🤖 Prompt for AI Agents
In @packages/core/tests/unit/icons.test.ts around lines 145 - 261, The tests
lack a check that normalized icons are actually passed into the server
constructor; add a unit that mocks the McpServer export from
"@modelcontextprotocol/sdk/server/mcp.js", calls normalizeIcons (the function
under test) and then verifies the McpServer constructor was called with an
options object containing the expected icons value (e.g., expect(new
McpServer).toHaveBeenCalledWith(expect.objectContaining({ icons: ... }))).
Ensure you import or reference normalizeIcons and the mocked McpServer symbol,
set up the mock before invoking normalizeIcons, and restore/clear mocks after
the test.

@claude

claude Bot commented Jan 11, 2026

Copy link
Copy Markdown
Contributor

Code Review: MCP Server Icon Support

Summary

This PR adds icon configuration support following the MCP specification (2025-11-25). The implementation is well-structured with comprehensive tests and good documentation. Overall quality is high, but I've identified several issues that should be addressed.


Critical Issues

1. Missing Tests for normalizeIcons and validateIcon Functions

Location: packages/core/src/server/index.ts:48-84

The normalizeIcons and validateIcon functions are not tested. These are critical validation functions that could cause runtime errors if they fail.

Recommended Action:
Add unit tests for these functions, either in a separate test file (server.test.ts) or extend the existing icons.test.ts. Test cases should include:

  • Valid single icon string
  • Valid icons array
  • Empty string validation
  • Null/undefined icon validation
  • Icons array precedence over icon string
  • Invalid src (empty, whitespace-only)

Example test structure:

describe('normalizeIcons', () => {
  it('should convert icon string to icons array', () => { ... });
  it('should throw on empty icon string', () => { ... });
  it('should prioritize icons over icon', () => { ... });
  it('should validate all icons in array', () => { ... });
});

2. No Integration Tests for Icon Flow

Impact: While unit tests cover individual functions, there are no integration tests verifying that icons are correctly passed to McpServer.

Recommended Action:
Add integration test in packages/core/tests/integration/server.test.ts or createApp.test.ts:

it('should pass icons to McpServer', async () => {
  const app = createApp({
    name: 'test-app',
    version: '1.0.0',
    icon: 'https://example.com/icon.png',
    tools: { ... }
  });
  
  const server = app.getServer();
  // Verify icons are set on the MCP server
});

Type Safety Issues

3. Variable Shadowing in normalizeIcons

Location: packages/core/src/server/index.ts:67

for (let i = 0; i < icons.length; i++) {
  const icon = icons[i];  // Shadows parameter 'icon'
  if (icon) {
    validateIcon(icon, i);
  }
}

Issue: The local variable icon shadows the function parameter icon: string | undefined. While not a bug, this violates the project's strict TypeScript standards and reduces code clarity.

Fix:

for (let i = 0; i < icons.length; i++) {
  const iconItem = icons[i];
  if (iconItem) {
    validateIcon(iconItem, i);
  }
}

Code Quality

4. Sparse Array Handling

Location: packages/core/src/server/index.ts:68

The check if (icon) allows sparse arrays to pass validation silently. While unlikely in practice, this could lead to confusing behavior.

Recommendation:
Either:

  1. Explicitly reject sparse arrays: if (icon === undefined) throw new Error(...)
  2. Document that sparse arrays are intentionally allowed (seems unlikely to be desired)

5. Missing Export Validation in index.ts

Location: packages/core/src/index.ts:65-66, 183-184

Good: The types Icon and IconTheme are exported with export type (follows CLAUDE.md guideline).

Minor issue: No explicit test verifies these types are exported correctly. Consider adding a simple contract test:

import type { Icon, IconTheme, IconFromFileOptions } from '@mcp-apps-kit/core';
// Verify types are importable

Documentation

6. Strong Documentation Throughout ✅

Positive: Excellent JSDoc comments with examples in:

  • Icon interface (packages/core/src/types/config.ts:19-76)
  • iconFromFile function (packages/core/src/utils/icons.ts:51-92)
  • AppConfig properties (packages/core/src/types/config.ts:544-577, 632-666)

The examples follow the MCP spec and provide clear guidance for users.


Security Considerations

7. File Size Limit is Appropriate ✅

Location: packages/core/src/utils/icons.ts:13

The 1MB limit is reasonable for icon files and prevents abuse. The error message helpfully suggests alternatives (SVG, external hosting).

8. No URL Validation for Icon Sources

Consideration: Neither URL icons nor data URIs are validated beyond basic string checks. This is likely acceptable since:

  • The MCP server will handle these
  • Client validation is out of scope
  • Malformed URLs will fail at runtime with clear errors

Recommendation: Document this behavior if users might expect validation.


Performance

9. Synchronous File Reading is Acceptable ✅

Location: packages/core/src/utils/icons.ts:100

Using fs.readFileSync is appropriate here since iconFromFile is typically called during app initialization (not per-request). Async would add complexity without meaningful benefit.


Test Coverage

10. Comprehensive Unit Tests for iconFromFile

Location: packages/core/tests/unit/icons.test.ts:143-287

Excellent coverage including:

  • All supported image formats
  • Size limit validation
  • Error handling
  • Option handling
  • Edge cases (exact 1MB file, unsupported formats)

Quality: Tests use proper mocking and clear assertions.

11. Type Tests Are Well-Structured ✅

Location: packages/core/tests/unit/icons.test.ts:20-140

Good use of expectTypeOf for compile-time type checking and runtime validation of the Icon and AppConfig interfaces.


Recommendations Summary

Must Fix (Blocking):

  1. Add tests for normalizeIcons and validateIcon functions
  2. Fix variable shadowing in normalizeIcons loop

Should Fix:
3. Add integration test for icon flow through createAppMcpServer
4. Clarify sparse array handling in normalizeIcons

Nice to Have:
5. Add export contract test
6. Document URL validation behavior


Conclusion

This is a well-implemented feature with strong documentation and comprehensive unit tests. The code follows the project's patterns (proper type exports, clear error messages, helpful utilities). The main gaps are:

  1. Missing tests for critical validation logic
  2. Minor variable shadowing issue

Once the validation tests are added and the shadowing is fixed, this will be ready to merge. Great work overall! 🎉


Checklist:

  • Add tests for normalizeIcons and validateIcon
  • Fix variable shadowing in loop
  • Add integration test (optional but recommended)
  • Run pnpm build && pnpm test && pnpm lint && pnpm typecheck before merging

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/core/tests/unit/icons.test.ts (1)

145-287: Excellent test coverage for iconFromFile!

The test suite comprehensively covers:

  • Multiple image formats (PNG, JPEG, SVG, WebP)
  • All configuration options (sizes, theme, mimeType)
  • Error scenarios (unsupported formats, file size limits, read failures)
  • Boundary conditions (exactly 1MB file)
  • Base64 encoding correctness with known input

The mock setup and assertions are precise and well-structured.

💡 Optional: Consider testing path resolution behavior

You could add a test to verify that relative paths are resolved correctly to absolute paths:

it("should resolve relative paths to absolute", () => {
  const pngData = Buffer.from([0x89, 0x50, 0x4e, 0x47]);
  mockReadFileSync.mockReturnValue(pngData);

  iconFromFile("./relative/icon.png");

  // Verify readFileSync was called with an absolute path
  expect(mockReadFileSync).toHaveBeenCalledWith(
    expect.stringMatching(/^\/.*\/relative\/icon\.png$/)
  );
});

This would verify the path resolution logic mentioned in the implementation, but it's not essential given the current comprehensive coverage.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1b915d2 and 5c26be6.

📒 Files selected for processing (3)
  • packages/core/src/server/index.ts
  • packages/core/src/utils/icons.ts
  • packages/core/tests/unit/icons.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/core/src/server/index.ts
  • packages/core/src/utils/icons.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use strict TypeScript - no any type allowed; use unknown with type narrowing instead
Middleware must always await next() or the middleware chain will break - follow Koa-style middleware patterns
Use export type for type-only exports to prevent runtime imports of types

Files:

  • packages/core/tests/unit/icons.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use defineTool and defineUI (or defineReactUI for React components) for type inference instead of manual definitions

Files:

  • packages/core/tests/unit/icons.test.ts
🧬 Code graph analysis (1)
packages/core/tests/unit/icons.test.ts (3)
packages/core/src/index.ts (5)
  • Icon (65-65)
  • IconTheme (66-66)
  • ToolDefs (39-39)
  • AppConfig (58-58)
  • iconFromFile (183-183)
packages/core/src/types/config.ts (3)
  • Icon (41-83)
  • IconTheme (17-17)
  • AppConfig (588-668)
packages/core/src/utils/icons.ts (1)
  • iconFromFile (93-149)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test-and-lint
  • GitHub Check: claude-review
🔇 Additional comments (3)
packages/core/tests/unit/icons.test.ts (3)

12-18: LGTM! Proper mocking setup.

The mock is correctly configured before importing iconFromFile, which ensures the module uses the mocked file system functions during tests.


20-65: LGTM! Comprehensive Icon interface tests.

The tests cover minimal configurations, fully specified icons, data URIs, and theme values. The type-level assertion on line 61 is a nice touch for verifying the IconTheme union type.


67-142: LGTM! Thorough AppConfig icon tests.

The test suite validates all icon configuration patterns: shorthand strings, arrays, theme-specific icons, and data URIs. The test at line 103 correctly verifies that both icon and icons fields can coexist in the configuration (the precedence logic is tested in the server implementation).

@claude

claude Bot commented Jan 11, 2026

Copy link
Copy Markdown
Contributor

Code Review: MCP Server Icon Support

Strengths

Excellent implementation overall! The code is well-structured, thoroughly tested, and follows MCP specification closely.

  1. Strong Type Safety: Great use of TypeScript with proper type exports and validation
  2. Comprehensive Testing: 396 lines of tests covering edge cases, validation, and all code paths
  3. Clear Documentation: Excellent JSDoc comments with examples
  4. Developer Experience: The shorthand icon and full icons array provide good flexibility
  5. Error Handling: Helpful error messages with actionable guidance
  6. Follows Codebase Conventions: Uses export type correctly, proper file organization

Critical Issues

Issue 1: Empty String Icon Handling
Location: packages/core/src/server/index.ts:80

In normalizeIcons, an empty string for icon parameter returns undefined instead of throwing an error. The check if (icon) evaluates to false for empty strings, so they are silently ignored. This is tested and intentional (line 309), but inconsistent with the error message.

Recommendation: Either document this behavior (empty string = no icon) or change line 79 to if (icon !== undefined) to catch empty strings.

Issue 2: Sparse Array Handling
Location: packages/core/src/server/index.ts:69-73

If someone passes a sparse array, undefined entries are skipped without validation. This could lead to runtime errors in the MCP SDK.

Fix: Add validation for null/undefined entries before calling validateIcon.

Issue 3: MIME Type Always Included
Location: packages/core/src/utils/icons.ts:128-137

If mimeType is an empty string, it is still added to the Icon object. Per the Icon interface, mimeType is optional, so it should be omitted if not detected. Though realistically this cannot happen due to validation at line 119-125.

Best Practices & Suggestions

Security: Path Traversal
Consider validating that the resolved path does not escape the project directory in iconFromFile. Though this might be overly restrictive for legitimate use cases.

Performance: Synchronous File Reading
iconFromFile uses fs.readFileSync which blocks the event loop. Since icons are typically loaded during app initialization, this is probably fine. But consider documenting that this is a synchronous operation.

Test Coverage: Missing Integration Test
You have excellent unit tests but no integration test showing icons being passed to McpServer. Consider adding an integration test.

Export Consistency
The normalizeIcons function is exported but it is an internal implementation detail. Consider documenting it as @internal if it needs to be exported for testing.

Test Coverage

The test file is excellent with 396 lines covering type validation, all image formats, size limits, error cases, icon normalization logic, and edge cases. This easily exceeds the 50% coverage requirement!

Code Quality

  • Follows CLAUDE.md: Uses proper type exports, clear documentation
  • Consistent with Codebase: Matches existing patterns
  • No any Types: All types are properly defined
  • Zod 4 Compatible: No issues detected

Before Merging

Please verify:

  1. pnpm build && pnpm test && pnpm lint && pnpm typecheck all pass
  2. Fix the sparse array handling (issue 2)
  3. Consider documenting empty string behavior (issue 1)
  4. Review the MIME type handling (issue 3)

Overall, this is high-quality work with just a few minor issues to address. The feature is well-designed and will be a great addition to the framework!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/core/src/server/index.ts (1)

67-76: Rename the loop variable to avoid shadowing the function parameter.

The variable icon on line 70 shadows the function parameter icon (string | undefined). This reduces code clarity and could lead to confusion.

♻️ Proposed fix
   // 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);
+      const iconItem = icons[i];
+      if (iconItem) {
+        validateIcon(iconItem, i);
       }
     }
     return icons;
   }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c26be6 and 4e176f3.

📒 Files selected for processing (3)
  • packages/core/src/index.ts
  • packages/core/src/server/index.ts
  • packages/core/tests/unit/icons.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/tests/unit/icons.test.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use strict TypeScript - no any type allowed; use unknown with type narrowing instead
Middleware must always await next() or the middleware chain will break - follow Koa-style middleware patterns
Use export type for type-only exports to prevent runtime imports of types

Files:

  • packages/core/src/server/index.ts
  • packages/core/src/index.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use defineTool and defineUI (or defineReactUI for React components) for type inference instead of manual definitions

Files:

  • packages/core/src/server/index.ts
  • packages/core/src/index.ts
**/index.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Export only through index.ts files to keep the public API clean and make refactoring safe

Files:

  • packages/core/src/server/index.ts
  • packages/core/src/index.ts
🧠 Learnings (2)
📚 Learning: 2026-01-11T02:21:51.116Z
Learnt from: CR
Repo: AndurilCode/mcp-apps-kit PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T02:21:51.116Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `defineTool` and `defineUI` (or `defineReactUI` for React components) for type inference instead of manual definitions

Applied to files:

  • packages/core/src/server/index.ts
📚 Learning: 2026-01-11T02:21:51.116Z
Learnt from: CR
Repo: AndurilCode/mcp-apps-kit PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T02:21:51.116Z
Learning: Applies to **/index.ts : Export only through `index.ts` files to keep the public API clean and make refactoring safe

Applied to files:

  • packages/core/src/index.ts
🧬 Code graph analysis (1)
packages/core/src/server/index.ts (1)
packages/core/src/types/config.ts (1)
  • Icon (41-83)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test-and-lint
  • GitHub Check: claude-review
🔇 Additional comments (3)
packages/core/src/index.ts (1)

65-66: LGTM! Icon exports follow TypeScript best practices.

The new icon-related exports are properly structured:

  • Type exports use export type (Icon, IconTheme, IconFromFileOptions)
  • Runtime function exports correctly omit export type (iconFromFile, normalizeIcons)
  • Exports are organized logically with clear section comments

Also applies to: 182-185

packages/core/src/server/index.ts (2)

45-52: LGTM! Validation logic is correct and focused.

The validation correctly enforces the only required field (src) and provides clear error messages for debugging.


134-142: Icon integration with McpServer is properly implemented.

The icon normalization and conditional spreading pattern are correct. MCP SDK version 1.25.1 supports the icons field on McpServer, and the implementation properly handles both shorthand icon strings and full icons arrays with appropriate validation.

@claude

claude Bot commented Jan 11, 2026

Copy link
Copy Markdown
Contributor

Code Review - PR #85: MCP Server Icon Support

Great work implementing MCP server icon configuration! The implementation is well-structured, thoroughly tested, and follows the MCP specification. Here's my detailed review:


Strengths

  1. Excellent Type Safety

    • Clean Icon and IconTheme type definitions following the MCP spec
    • Proper use of optional fields with strict type checking
    • Good TypeScript documentation with examples
  2. Robust Validation

    • normalizeIcons() validates icon sources (non-empty, non-whitespace)
    • iconFromFile() includes size limit validation (1MB)
    • Clear, actionable error messages with helpful suggestions
  3. Great Test Coverage (396 lines!)

    • Comprehensive unit tests for all functions and edge cases
    • Tests cover type validation, error handling, and all supported formats
    • Proper mocking of file system operations
    • Good edge case coverage (empty strings, whitespace, size limits)
  4. Developer Experience

    • Convenient shorthand icon property for simple use cases
    • Full icons array for advanced scenarios (multiple sizes, themes)
    • iconFromFile() utility makes it easy to use local files
    • Excellent JSDoc with multiple usage examples
  5. Clean Architecture

    • Proper separation: types in config.ts, utilities in icons.ts, server logic in server/index.ts
    • Follows project export patterns (everything through index.ts)
    • Normalized icons passed cleanly to McpServer constructor

🔍 Issues & Recommendations

1. Potential Type Safety Issue in normalizeIcons()

Location: packages/core/src/server/index.ts:70

const icon = icons[i];
if (icon) {
  validateIcon(icon, i);
}

Issue: The if (icon) check is unnecessary and could mask bugs. TypeScript already knows icons[i] is of type Icon, not Icon | undefined. This check only catches falsy values like null, undefined, 0, "", but sparse arrays shouldn't occur in practice here.

Recommendation: Remove the conditional and validate directly:

for (let i = 0; i < icons.length; i++) {
  validateIcon(icons[i], i);
}

Or use forEach:

icons.forEach((icon, i) => validateIcon(icon, i));

2. Missing Validation for Invalid sizes Format

Location: packages/core/src/utils/icons.ts & validation in normalizeIcons()

Issue: The code doesn't validate that sizes strings follow the "WxH" format (e.g., "48x48"). Invalid formats like ["48"], ["not-a-size"], or ["48x"] would be silently accepted.

Recommendation: Add validation for the sizes format:

function validateIconSizes(sizes: string[] | undefined, index: number): void {
  if (!sizes) return;
  
  for (const size of sizes) {
    if (size !== "any" && !/^\d+x\d+$/.test(size)) {
      throw new Error(
        `Invalid icon size at index ${index}: "${size}". ` +
        `Sizes must be in "WxH" format (e.g., "48x48") or "any" for scalable formats.`
      );
    }
  }
}

Call this in validateIcon() and add tests for it.


3. No Validation for Data URI Format

Location: packages/core/src/utils/icons.ts & normalizeIcons()

Issue: The code accepts any string as a src, including invalid data URIs like "data:not-valid" or malformed URLs.

Recommendation: Add basic data URI validation:

function isValidDataUri(uri: string): boolean {
  return /^data:[^;]+;base64,/.test(uri);
}

function isValidUrl(url: string): boolean {
  try {
    new URL(url);
    return true;
  } catch {
    return false;
  }
}

Then in validation:

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`);
  }
  
  const src = icon.src.trim();
  const isDataUri = src.startsWith("data:");
  
  if (isDataUri && !isValidDataUri(src)) {
    throw new Error(`Invalid icon at index ${index}: malformed data URI`);
  } else if (!isDataUri && !isValidUrl(src)) {
    throw new Error(`Invalid icon at index ${index}: 'src' must be a valid URL or data URI`);
  }
}

4. Unintended Behavior with Empty String icon

Location: packages/core/src/server/index.ts:79-84

Issue: Empty string icon: "" returns undefined instead of throwing an error. This is inconsistent with the behavior for whitespace-only strings (which throw).

Current behavior:

normalizeIcons("", undefined)   // returns undefined (falsy check)
normalizeIcons("   ", undefined) // throws error

Recommendation: For consistency, validate before the falsy check:

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 }];
}

Alternatively, document this behavior explicitly as intended.


5. Missing MIME Type Validation

Location: packages/core/src/utils/icons.ts

Issue: Users can provide arbitrary MIME types via options.mimeType override, including invalid ones like "text/plain" or "application/json".

Recommendation: Add MIME type validation:

const VALID_MIME_TYPES = new Set([
  "image/png",
  "image/jpeg", 
  "image/svg+xml",
  "image/webp",
  "image/gif",
  "image/x-icon"
]);

// In iconFromFile():
const mimeType = options.mimeType ?? detectedMimeType ?? "";

if (!VALID_MIME_TYPES.has(mimeType) && !mimeType.startsWith("image/")) {
  throw new Error(
    `Invalid MIME type: "${mimeType}". Must be an image MIME type.`
  );
}

6. Inconsistent Export Location

Location: packages/core/src/index.ts:185

Issue: normalizeIcons is exported from the main package, but it's really an internal function used by the server creation logic. Exporting it might encourage users to call it directly when they shouldn't need to.

Recommendation:

  • If it's meant to be public API, add JSDoc explaining when/why users would call it
  • If it's internal, don't export it from the main index.ts

The PR description and examples don't show users calling normalizeIcons() directly, suggesting it's internal.


7. Minor: Fix in Property Generators Unrelated to Icons

Location: packages/testing/src/eval/property/generators.ts:133-142

Issue: The changes to add noNaN: true to float generators are good (prevents NaN in property tests), but this seems unrelated to icon support.

Recommendation: This should ideally be a separate commit/PR, but it's a minor improvement so it's acceptable. Just note it in the PR description for clarity.


🎯 Performance & Security

Performance:

  • ✅ 1MB size limit prevents memory issues from large embedded images
  • ✅ Synchronous readFileSync is appropriate for build-time icon loading
  • ✅ Base64 encoding is standard and efficient

Security:

  • ✅ No command injection risks (no shell execution)
  • ✅ Path traversal handled by Node's path resolution
  • ⚠️ Consider validating that data URIs don't contain executable content (though this is client's responsibility)
  • ⚠️ No validation that URLs use HTTPS (HTTP icons could be MitM'd)

Optional security enhancement:

if (!isDataUri && !src.startsWith("https://")) {
  console.warn(`Warning: Icon URL uses insecure HTTP: ${src}`);
}

📋 Checklist Before Merge

Based on CLAUDE.md:

  • Run pnpm build && pnpm test && pnpm lint && pnpm typecheck - all must pass
  • Verify no circular dependencies introduced
  • Confirm exports are type-only where appropriate (export type)
  • Check that test coverage stays ≥ 50%

📚 Documentation

Excellent:

  • Detailed JSDoc for all public APIs
  • Multiple usage examples in both types and functions
  • MCP specification link included

Suggestions:

  • Add a note in the main README about icon support (if applicable)
  • Consider adding an example to examples/minimal showing iconFromFile() usage (already has URL example)

🎉 Summary

This is a solid implementation with excellent test coverage. The code follows project conventions and provides a great developer experience. The issues identified are mostly edge cases and potential improvements rather than blocking problems.

Priority fixes:

  1. Fix validation inconsistencies (Add Claude Code GitHub Workflow #1, Fix linting errors and warnings #4)
  2. Add sizes format validation (Create FUNDING.yml #2)
  3. Add data URI/URL validation (Fix documentation misalignment in codebase #3)

Nice-to-have:
4. MIME type validation (#5)
5. Review export decisions (#6)
6. Security warnings for HTTP URLs

Great work! 🚀

@claude

claude Bot commented Jan 11, 2026

Copy link
Copy Markdown
Contributor

Code Review: MCP Server Icon Support

Overview

This PR successfully implements server icon configuration following the MCP specification (2025-11-25). The implementation is well-structured with comprehensive type definitions, validation, and test coverage.


✅ Strengths

1. Excellent Type Safety & Documentation

  • Complete TypeScript types with thorough JSDoc comments
  • Clear examples in documentation showing both URL and data URI usage
  • Proper type exports from the package index

2. Robust Validation

  • normalizeIcons validates icon src is non-empty (packages/core/src/server/index.ts:70-74)
  • Size format validation with clear error messages (packages/core/src/server/index.ts:53-64)
  • File size limit enforcement (1MB) in iconFromFile utility (packages/core/src/utils/icons.ts:107-113)

3. Developer Experience

  • Dual API: simple icon string shorthand + advanced icons array
  • Helpful iconFromFile utility with auto MIME type detection
  • Clear precedence rule: icons array overrides icon shorthand

4. Comprehensive Test Coverage

  • 446 lines of tests covering edge cases and error scenarios
  • Tests for validation, normalization, file conversion, and type safety
  • Good use of mocking for file system operations

🔍 Potential Issues & Suggestions

1. Security: Insufficient Data URI Validation

Severity: Medium

The normalizeIcons function accepts any string for src without validating data URIs. This could accept data:text/html,... or other non-image content.

Recommendation: Add basic data URI validation to ensure MIME type starts with image/

2. Edge Case: Empty Icons Array Behavior

Severity: Low

When icons: [] is provided, normalizeIcons returns undefined and falls back to icon. Consider if empty array should explicitly disable icons instead.

Test case missing:

normalizeIcons("https://example.com/fallback.png", []) // Returns undefined

3. Code Quality: Redundant MIME Type Assignment

Severity: Very Low

In iconFromFile line 128, the ?? "" fallback is unnecessary after validation ensures one is defined.

4. Maintainability: Exported Internal Function

Severity: Low

normalizeIcons is exported but marked @internal. Consider moving to utils/icons.ts or keeping truly internal.


📋 Testing Observations

Excellent coverage overall. Additional test cases to consider:

  1. Data URI validation tests (if recommendation Add Claude Code GitHub Workflow #1 implemented)
  2. Empty icons array with icon fallback behavior
  3. Mixed valid/invalid icons in array
  4. Integration test verifying icons passed to McpServer

🎯 Performance & Best Practices

File Size Limit ✅ 1MB limit well-justified with clear messaging

Synchronous File Reading ⚠️ iconFromFile uses fs.readFileSync - appropriate for startup, just ensure not called during request handling

Project Conventions ✅ Follows all CLAUDE.md requirements


🔒 Security Assessment

No critical security issues. The data URI validation gap should be addressed to prevent potential content injection if icons are rendered in untrusted contexts.


✅ Final Recommendation

APPROVE with minor changes recommended

This is a solid implementation following MCP specifications and project conventions. Well-tested, well-documented, and provides excellent developer experience.

Before merging:

  1. ✅ Add data URI validation for image MIME types (security)
  2. ⚠️ Consider adding test case for empty icons array behavior
  3. ℹ️ Optional: Improve MIME type typing for better autocomplete

Nice work! The dual API and iconFromFile utility are particularly user-friendly.


📦 Checklist

  • ✅ Code quality and best practices
  • ✅ No critical bugs (1 security improvement recommended)
  • ✅ Performance appropriate for use case
  • ⚠️ Security (data URI validation recommended)
  • ✅ Test coverage exceeds 50% requirement
  • ✅ Follows CLAUDE.md conventions
  • ✅ TypeScript strict mode compliance

@claude

claude Bot commented Jan 11, 2026

Copy link
Copy Markdown
Contributor

Code Review: MCP Server Icon Support

Summary

This PR implements MCP server icon configuration following the MCP specification (2025-11-25). The implementation is well-structured with comprehensive tests and good documentation. Overall, this is high-quality work with only minor concerns.

Strengths

1. Excellent Test Coverage

  • 446 lines of thorough unit tests covering edge cases
  • Proper mocking of node:fs module
  • Tests for validation, normalization, and error conditions
  • Type-level tests using expectTypeOf

2. Strong Type Safety

  • Well-defined Icon and IconTheme types
  • Proper exports using export type for type-only exports (follows CLAUDE.md)
  • Good use of TypeScript strict mode features

3. Great Documentation

  • Comprehensive JSDoc comments with examples
  • Links to MCP specification
  • Clear usage examples in both code and README

4. Good API Design

  • Dual support for shorthand icon string and full icons array
  • Sensible precedence (icons array over icon shorthand)
  • Helper utility iconFromFile() for local files
  • Clear validation with helpful error messages

Issues and Suggestions

1. Potential Security Concern: Data URI Size Limit (Medium)
Location: packages/core/src/utils/icons.ts:13

The 1MB file size limit is enforced for local files, but there is no validation for data URIs passed directly via the icon or icons config. An attacker or careless developer could pass a multi-megabyte base64 data URI that would bloat the server configuration.

Recommendation: Add data URI size validation in normalizeIcons() for data URIs to prevent configuration bloat.

2. Missing Validation Test (Low)
Based on the visible tests, there is likely no test for validating data URI sizes when passed directly to normalizeIcons().

3. Export Inconsistency (Low)
Location: packages/core/src/server/index.ts:85

The normalizeIcons() function is exported but marked as @internal. Internal functions should generally not be exported unless there is a specific reason.

4. Error Message Consistency (Minor)
Error messages use different formats with inconsistent quoting styles. Use consistent format throughout.

5. Missing File Extension Edge Case (Low)
Files without extensions could have more helpful error messages explicitly mentioning the missing extension.

6. Performance: Synchronous File I/O (Low)
Using fs.readFileSync() blocks the event loop. Consider adding JSDoc note that this should only be called during server initialization.

Code Quality Notes

Following CLAUDE.md Guidelines:

  • Uses strict TypeScript (no any)
  • Proper export type usage
  • Tests mirror source structure
  • Comprehensive JSDoc comments
  • Follows existing patterns

Best Practices:

  • Immutable data structures
  • Clear separation of concerns
  • Defensive validation
  • Helpful error messages

Before Merge Checklist

According to CLAUDE.md, all four must pass:

pnpm build && pnpm test && pnpm lint && pnpm typecheck

Please confirm these all pass before merging.

Verdict

APPROVE with suggestions - This is solid work that adds valuable functionality. The security concern about data URI size validation should be addressed, but it is not a blocker. The other items are nice-to-haves that would improve robustness.

Great job on the comprehensive testing and documentation!


Review by Claude Code

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/core/tests/unit/versioning.test.ts (1)

290-327: Consider strengthening icon propagation verification.

The tests successfully verify that app creation doesn't fail with global icons, but they don't actually verify that icons are propagated to the version configurations or server instances. The tests would pass even if icon propagation was completely broken.

While this pattern is consistent with similar tests in this file (e.g., the plugin merging test at lines 263-288), you could strengthen these tests by:

  1. Accessing version apps via app.getVersion() and verifying icon configuration
  2. Checking if icons are passed to server initialization
  3. Using the server's info/metadata endpoint (if available) to verify icons are exposed
Example: Potential verification approach
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();
  
  // If there's a way to access the config or verify server initialization:
  // const v1App = app.getVersion("v1");
  // const v1Server = v1App?.getServer();
  // expect(v1Server.serverInfo.icon).toBe("https://example.com/icon.png");
});

Note: This example assumes there's a way to access the icon through the API, which may not be currently exposed.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 071a3b9 and 3bf42b8.

📒 Files selected for processing (2)
  • packages/core/src/createApp.ts
  • packages/core/tests/unit/versioning.test.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use strict TypeScript - no any type allowed; use unknown with type narrowing instead
Middleware must always await next() or the middleware chain will break - follow Koa-style middleware patterns
Use export type for type-only exports to prevent runtime imports of types

Files:

  • packages/core/tests/unit/versioning.test.ts
  • packages/core/src/createApp.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use defineTool and defineUI (or defineReactUI for React components) for type inference instead of manual definitions

Files:

  • packages/core/tests/unit/versioning.test.ts
  • packages/core/src/createApp.ts
🧬 Code graph analysis (2)
packages/core/tests/unit/versioning.test.ts (2)
packages/core/src/createApp.ts (1)
  • createApp (540-550)
packages/core/src/index.ts (1)
  • createApp (190-190)
packages/core/src/createApp.ts (1)
packages/core/src/types/config.ts (1)
  • Icon (41-83)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test-and-lint
  • GitHub Check: claude-review
🔇 Additional comments (2)
packages/core/src/createApp.ts (2)

430-491: LGTM! Icon propagation implementation is correct.

The implementation correctly propagates global icon and icons to all versions. The design choice to only support global icons (no per-version overrides) aligns with the PR objectives which state "Icons provided in the app configuration are passed to McpServer during initialization."

Note: If per-version icon overrides are needed in the future, the signature would need to accept versionConfig.icon and versionConfig.icons with precedence logic similar to how plugins are merged.


817-824: LGTM! Multi-version icon propagation is correctly implemented.

The call to mergeVersionConfig properly passes the global icon and icons fields, ensuring they're propagated to each version's AppConfig.

@gabrypavanello
gabrypavanello force-pushed the claude/add-mcp-server-icon-eXjOv branch from 3bf42b8 to 53b5754 Compare January 11, 2026 17:07
@claude

claude Bot commented Jan 11, 2026

Copy link
Copy Markdown
Contributor

Code Review for PR #85: Add MCP Server Icon Support

Summary

This PR implements MCP server icon configuration following the MCP specification (2025-11-25). The implementation is well-structured with comprehensive testing and good error handling.


✅ Strengths

  1. Excellent Test Coverage: 446 lines of comprehensive unit tests covering type validation, icon normalization logic, file reading with mocks, edge cases and error scenarios, size validation and MIME type validation

  2. Good Error Handling: Clear, actionable error messages with proper validation and helpful suggestions. File size limits (1MB) prevent memory issues.

  3. Type Safety: Proper TypeScript types exported, follows existing patterns, good use of Zod for validation in tests

  4. Follows Codebase Conventions: Uses export type for type-only exports, exports only through index.ts, proper JSDoc comments throughout

  5. User-Friendly API: Shorthand icon string for simple cases, full icons array for advanced scenarios, convenience iconFromFile helper function


🔍 Code Quality Issues

1. Potential Security Concern ⚠️

Location: packages/core/src/utils/icons.ts:100

The iconFromFile function uses synchronous file I/O (fs.readFileSync), which blocks the event loop during file reads and could be a DoS vector if called repeatedly with slow storage.

Recommendation: Document that iconFromFile should only be called during app initialization (not per-request), or consider providing an async version.

2. Missing MIME Type Validation 🐛

Location: packages/core/src/server/index.ts:66

The validateIcon function validates MIME type in iconFromFile but not in validateIcon for icons passed directly. Should validate that mimeType starts with "image/" when provided.

3. Internal Function Export Pattern 🔧

Location: packages/core/src/server/index.ts:47

The normalizeIcons function is exported with JSDoc @internal comment. Commit message says "Remove normalizeIcons from public exports" but it's still exported from the module (just not from index.ts).

Recommendation: Move to a separate internal module for cleaner separation.


📝 Minor Issues

  1. Inconsistent Error Message Format: Some errors include periods at the end, others don't
  2. Missing Size Validation in iconFromFile: Invalid sizes like ["48", "invalid"] would only fail later during normalization

🧪 Testing

Good Coverage: Empty/whitespace validation, file size limits, MIME detection, error handling, theme-specific icons, data URI encoding

Missing Tests: Integration test showing icons passed to McpServer, test for malformed data URIs


🎯 Architecture Question

Version Config Propagation (packages/core/src/createApp.ts:487-488): Should version-specific icons override or merge with global icons? Current implementation always uses global icons. Test only checks propagation, not version-specific overrides.


🔒 Security Assessment: ✅ No critical issues

  • ✅ File size limit prevents memory exhaustion
  • ✅ Path resolution prevents directory traversal
  • ⚠️ Synchronous file I/O could be DoS vector
  • ✅ Base64 encoding is safe
  • ✅ No injection vulnerabilities

📊 Performance: Acceptable for initialization use case

  • Base64 overhead (~33%) documented ✅
  • Sync I/O: OK for init, would be problematic per-request ⚠️
  • Validation loops efficient ✅

✨ What I Really Like

  1. Progressive enhancement: simple shorthand, powerful array option
  2. Helpful error messages with actionable guidance
  3. Type safety with no any types
  4. Excellent JSDoc with examples
  5. Thorough unit tests with proper mocking

📋 Action Items

Before Merge:

  • Add MIME type validation in validateIcon
  • Move normalizeIcons to internal module or document export pattern
  • Verify CI passes

Nice to Have (follow-up PR):

  • Add async iconFromFileAsync variant
  • Add early size validation in iconFromFile
  • Add integration test for icon propagation
  • Consider version-specific icon override behavior

Overall: Excellent work! 🎉 Solid implementation with great testing. Address the MIME validation issue and this will be ready to merge.


Review by Claude Code following CLAUDE.md conventions

@gabrypavanello
gabrypavanello force-pushed the claude/add-mcp-server-icon-eXjOv branch from 53b5754 to ab2a237 Compare January 12, 2026 13:19
@claude

claude Bot commented Jan 12, 2026

Copy link
Copy Markdown
Contributor

Code Review: MCP Server Icon Support

Summary

This PR implements MCP server icon configuration following the MCP specification (2025-11-25). The implementation is well-designed with excellent test coverage and proper type safety.

✅ Strengths

1. Excellent Type Safety

  • Clean TypeScript types with comprehensive JSDoc documentation
  • Proper Icon and IconTheme interfaces matching MCP spec
  • Good use of optional fields and union types
  • All exports properly typed (no any usage)

2. Comprehensive Test Coverage

  • 446 lines of thorough unit tests covering:
    • Type validation (Icon interface, IconTheme, AppConfig)
    • iconFromFile utility (all formats, error cases, size limits)
    • normalizeIcons function (precedence, validation, edge cases)
  • Tests use proper mocking for file system operations
  • Good edge case coverage (empty strings, whitespace, size validation)
  • Versioning tests added (lines 290-327 in versioning.test.ts)

3. Well-Documented API

  • Clear examples in JSDoc comments
  • Helpful error messages with actionable guidance
  • Good developer experience with iconFromFile utility

4. Follows Codebase Conventions

  • ✅ Uses proper export type for type-only exports
  • ✅ Exports through index.ts
  • ✅ Strict TypeScript (no any)
  • ✅ Proper Zod usage in tests
  • ✅ Consistent with existing patterns

🔍 Issues & Suggestions

1. Missing Integration Test (Minor)

The icon configuration is passed to McpServer but there's no integration test verifying that icons are actually exposed via the MCP protocol. Consider adding a test that:

  • Creates an app with icons
  • Connects via MCP client
  • Verifies the server info includes the icons

Location: packages/core/tests/unit/icons.test.ts

Suggested addition:

describe("MCP integration", () => {
  it("should expose icons via MCP server info", async () => {
    const app = createApp({
      name: "test-app",
      version: "1.0.0",
      icon: "https://example.com/icon.png",
      tools: {},
    });
    
    await app.start({ port: 3200 });
    const transport = new StreamableHTTPClientTransport(new URL("http://localhost:3200/mcp"));
    const client = new Client({ name: "test", version: "1.0.0" }, { capabilities: {} });
    await client.connect(transport);
    
    // Verify server implementation includes icons
    // (requires accessing server info from MCP SDK)
    
    await client.close();
    await app.stop();
  });
});

2. Potential Runtime Error with Invalid MIME Type (Minor)

In iconFromFile (lines 127-128), there's a fallback to empty string that could theoretically pass validation:

const mimeType = options.mimeType ?? detectedMimeType ?? "";

However, this is actually safe because of the validation on line 119-125 that would throw before this point if both are undefined. Consider adding a comment or assertion to make this more explicit.

3. Version-Specific Icon Override Not Supported (Design Question)

Currently, icons are only configurable at the global level and propagated to all versions. There's no way to specify different icons per version. Is this intentional?

Location: packages/core/src/createApp.ts:487-489

If version-specific icons are desired in the future, you'd need to update mergeVersionConfig and VersionConfig types.

4. URL Validation Not Performed (Minor Security)

The icon and Icon.src fields accept any string. While this provides flexibility, there's no validation that URLs are well-formed or safe. Consider adding:

  • URL format validation for non-data URIs
  • Data URI format validation (proper base64, valid MIME type structure)

Example:

function validateIconSrc(src: string): void {
  if (src.startsWith('data:')) {
    // Validate data URI format
    if (!/^data:image\/[\w+.-]+;base64,/.test(src)) {
      throw new Error('Invalid data URI format');
    }
  } else if (src.startsWith('http://') || src.startsWith('https://')) {
    try {
      new URL(src);
    } catch {
      throw new Error('Invalid URL format');
    }
  } else {
    throw new Error('Icon src must be a valid HTTP(S) URL or data URI');
  }
}

5. Example Uses External Icon URL (Minor)

In examples/minimal/src/index.ts:122, the example uses:

icon: "https://raw.githubusercontent.com/anthropics/claude-code/main/assets/icon.svg",

This references an external repository. Consider:

  • Using a data URI example with iconFromFile
  • Or documenting that this is just an example URL

🎯 Performance Considerations

  1. Base64 Encoding Size: The 1MB limit on iconFromFile is reasonable. Base64 adds ~33% overhead, so actual data URI will be ~1.33MB. This is well-documented.

  2. Synchronous File Reading: iconFromFile uses readFileSync, which is fine for initialization but would block if called during request handling. Since this is only called at app creation time, this is acceptable.

🔒 Security Considerations

  1. File Path Handling: iconFromFile properly resolves paths and handles errors. Good protection against path traversal.

  2. Size Limits: 1MB limit prevents DoS via large files. ✅

  3. MIME Type Validation: Validates that MIME types start with image/. Could be stricter (see suggestion Fix linting errors and warnings #4).

📝 Code Quality

  1. Error Messages: Excellent - clear, actionable, and include examples
  2. Code Organization: Well-structured with clear separation of concerns
  3. Naming: Consistent and descriptive
  4. Documentation: Comprehensive JSDoc with examples

✅ Verification Checklist

Per CLAUDE.md, all four commands must pass:

  • ⚠️ Not yet verified: pnpm build && pnpm test && pnpm lint && pnpm typecheck
  • Please run these locally and confirm all pass

🎉 Conclusion

This is a high-quality implementation that follows the codebase conventions and provides a great developer experience. The test coverage is excellent, and the API design is clean and intuitive.

Recommendation: Approve with minor suggestions

The suggestions above are all minor improvements that could be addressed in follow-up PRs. The core implementation is solid and ready to merge.

Priority of Suggestions:

  1. High: Run full CI validation (pnpm build && pnpm test && pnpm lint && pnpm typecheck)
  2. Medium: Add integration test for MCP protocol exposure
  3. Low: Consider URL validation for security hardening
  4. Optional: Version-specific icon support (if needed)

Great work! 🎨

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/core/src/utils/icons.ts (1)

127-135: Consider the unreachable empty string case on Line 128.

The fallback to empty string (?? "") on Line 128 is defensive but unreachable due to the validation on Lines 119-125 which throws if both detectedMimeType and options.mimeType are falsy. The subsequent startsWith("image/") check on Line 131 would also catch an empty string, so this is safe but slightly redundant.

♻️ Optional: Simplify with non-null assertion
-  // mimeType is guaranteed to be defined after validation above
-  const mimeType = options.mimeType ?? detectedMimeType ?? "";
+  // mimeType is guaranteed to be defined after validation above
+  const mimeType = (options.mimeType ?? detectedMimeType)!;
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3bf42b8 and ab2a237.

📒 Files selected for processing (9)
  • examples/minimal/src/index.ts
  • packages/core/src/createApp.ts
  • packages/core/src/index.ts
  • packages/core/src/server/index.ts
  • packages/core/src/types/config.ts
  • packages/core/src/utils/icons.ts
  • packages/core/tests/unit/icons.test.ts
  • packages/core/tests/unit/versioning.test.ts
  • packages/testing/src/eval/property/generators.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • examples/minimal/src/index.ts
  • packages/core/tests/unit/versioning.test.ts
  • packages/core/src/index.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Express 5 (not 4) - async error handling works differently

Files:

  • packages/core/tests/unit/icons.test.ts
  • packages/core/src/createApp.ts
  • packages/core/src/server/index.ts
  • packages/core/src/types/config.ts
  • packages/core/src/utils/icons.ts
  • packages/testing/src/eval/property/generators.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use strict TypeScript - no any types, use unknown with type narrowing instead
Use defineTool and defineUI (or defineReactUI for React components) for type inference
Use export type for type-only exports to prevent runtime imports of types

Files:

  • packages/core/tests/unit/icons.test.ts
  • packages/core/src/createApp.ts
  • packages/core/src/server/index.ts
  • packages/core/src/types/config.ts
  • packages/core/src/utils/icons.ts
  • packages/testing/src/eval/property/generators.ts
packages/core/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (packages/core/AGENTS.md)

packages/core/src/**/*.{ts,tsx}: Use defineTool and defineUI for type inference instead of inline objects
Ensure Zod 4 APIs are used, not Zod 3 APIs which have breaking changes
Always validate input with Zod schema before processing, never trust raw input

Files:

  • packages/core/src/createApp.ts
  • packages/core/src/server/index.ts
  • packages/core/src/types/config.ts
  • packages/core/src/utils/icons.ts
**/index.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Export only through index.ts files to keep public API clean and enable safe refactoring

Files:

  • packages/core/src/server/index.ts
packages/core/src/**/index.ts

📄 CodeRabbit inference engine (packages/core/AGENTS.md)

All public API exports must go through src/index.ts, not individual subdirectory barrel files

Files:

  • packages/core/src/server/index.ts
packages/core/src/{adapters,debug,events,middleware,plugins,server,server/oauth}/index.ts

📄 CodeRabbit inference engine (packages/core/AGENTS.md)

Subdirectory barrel files in adapters/, debug/, events/, middleware/, plugins/, server/, and server/oauth/ should only contain local re-exports

Files:

  • packages/core/src/server/index.ts
🧠 Learnings (7)
📚 Learning: 2026-01-11T17:08:24.463Z
Learnt from: CR
Repo: AndurilCode/mcp-apps-kit PR: 0
File: packages/testing/AGENTS.md:0-0
Timestamp: 2026-01-11T17:08:24.463Z
Learning: Applies to packages/testing/**/*.test.{ts,tsx,js,jsx} : Use the fluent assertion API with `expectToolResult()` for validating tool execution results

Applied to files:

  • packages/core/tests/unit/icons.test.ts
📚 Learning: 2026-01-11T17:08:24.463Z
Learnt from: CR
Repo: AndurilCode/mcp-apps-kit PR: 0
File: packages/testing/AGENTS.md:0-0
Timestamp: 2026-01-11T17:08:24.463Z
Learning: Applies to packages/testing/**/*.test.{ts,tsx,js,jsx} : Always call `await env.cleanup()` after test completion to ensure the server is properly shut down

Applied to files:

  • packages/core/tests/unit/icons.test.ts
📚 Learning: 2026-01-11T17:08:42.176Z
Learnt from: CR
Repo: AndurilCode/mcp-apps-kit PR: 0
File: packages/ui/AGENTS.md:0-0
Timestamp: 2026-01-11T17:08:42.176Z
Learning: Applies to packages/ui/**/*.{ts,tsx} : Provide type parameter to `createClient<typeof app.tools()>()` for proper typed tool calls

Applied to files:

  • packages/core/src/createApp.ts
  • packages/core/src/server/index.ts
📚 Learning: 2026-01-11T17:08:42.176Z
Learnt from: CR
Repo: AndurilCode/mcp-apps-kit PR: 0
File: packages/ui/AGENTS.md:0-0
Timestamp: 2026-01-11T17:08:42.176Z
Learning: Applies to packages/ui/**/*.{ts,tsx,js,jsx} : Handle both MCP and OpenAI response formats when calling tools

Applied to files:

  • packages/core/src/server/index.ts
📚 Learning: 2026-01-11T17:08:13.651Z
Learnt from: CR
Repo: AndurilCode/mcp-apps-kit PR: 0
File: packages/core/AGENTS.md:0-0
Timestamp: 2026-01-11T17:08:13.651Z
Learning: Applies to packages/core/src/**/*.{ts,tsx} : Use `defineTool` and `defineUI` for type inference instead of inline objects

Applied to files:

  • packages/core/src/server/index.ts
  • packages/core/src/types/config.ts
  • packages/core/src/utils/icons.ts
📚 Learning: 2026-01-11T17:08:03.538Z
Learnt from: CR
Repo: AndurilCode/mcp-apps-kit PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:08:03.538Z
Learning: Applies to **/*.{ts,tsx} : Use `defineTool` and `defineUI` (or `defineReactUI` for React components) for type inference

Applied to files:

  • packages/core/src/server/index.ts
📚 Learning: 2026-01-11T17:08:19.638Z
Learnt from: CR
Repo: AndurilCode/mcp-apps-kit PR: 0
File: packages/create-app/AGENTS.md:0-0
Timestamp: 2026-01-11T17:08:19.638Z
Learning: Applies to packages/create-app/**/*.{ts,tsx,js} : Use path.join() instead of string concatenation for cross-platform file path operations in the mcp-apps-kit/create-app scaffolding tool

Applied to files:

  • packages/core/src/server/index.ts
  • packages/core/src/utils/icons.ts
🔇 Additional comments (18)
packages/testing/src/eval/property/generators.ts (1)

132-144: LGTM! Consistent NaN exclusion across all float generator branches.

The addition of noNaN: true to all branches is a sensible default for property testing, as NaN values often cause unexpected test failures. This is a behavioral change worth noting in release notes if consumers were relying on NaN generation.

packages/core/src/types/config.ts (3)

10-83: Well-documented Icon types following the MCP specification.

The Icon interface is well-structured with comprehensive JSDoc examples covering URL references, data URIs, and theme-specific icons. The optional fields (mimeType, sizes, theme) provide flexibility while keeping src as the only required field.


541-562: Global icon configuration for multi-version apps looks good.

The icon shorthand and icons array fields on VersionsConfig enable global icon configuration that applies to all versions. The documentation clearly explains the relationship between the two fields.


632-668: Consistent icon configuration for single-version apps.

The icon fields mirror the VersionsConfig structure, maintaining API consistency. The examples are helpful and cover both URL and data URI use cases.

packages/core/src/utils/icons.ts (3)

1-26: Good foundation for icon utilities.

The 1MB size limit is reasonable, and the MIME type mappings cover common image formats. The comment about base64 encoding increasing size by ~33% is helpful context.


93-104: Robust file reading with helpful error messages.

The error handling wraps the underlying error with context about which file failed, making debugging easier. Using path.isAbsolute check before resolving is correct.


141-156: Clean Icon object construction with optional field handling.

The conditional addition of sizes and theme only when provided keeps the output clean and avoids unnecessary undefined properties.

packages/core/tests/unit/icons.test.ts (4)

12-19: Good mock setup for file system operations.

Mocking node:fs before importing iconFromFile ensures the mock is in place when the module loads. The import order (mock → iconFromFile → readFileSync) is correct.


68-76: Consider using defineTool for test tool definitions.

Per coding guidelines, defineTool should be used for type inference instead of inline objects. However, since this is a test file focused on icon configuration rather than tool behavior, the inline definition is acceptable for test simplicity.


257-271: Thorough boundary testing for file size limits.

Testing both the "exceeds limit" (1MB + 1 byte) and "exactly at limit" (1MB) cases ensures the boundary condition is correctly handled with > rather than >=.


416-445: Comprehensive sizes format validation tests.

Good coverage of valid formats (WxH, any) and invalid formats (48, 48x, large). This ensures the regex pattern in normalizeIcons is correctly enforced.

packages/core/src/server/index.ts (3)

41-64: Well-structured icon validation helpers.

The SIZES_PATTERN regex correctly validates WxH format, and validateIconSizes properly handles the special "any" value for scalable formats. Error messages include the index for easier debugging.


76-107: Clarify the export status of normalizeIcons.

The function is marked @internal in the JSDoc but is exported publicly. If it's truly internal, remove the export keyword or remove the @internal tag if it's intentionally part of the public API. Additionally, verify that this export aligns with the guideline that subdirectory barrel files like server/index.ts should only contain local re-exports and that all public API exports must go through packages/core/src/index.ts.


154-162: Verify icon parameter placement in McpServer constructor.

The code passes icons as a top-level parameter to McpServer, but the SDK documentation indicates icons should be nested within an implementation object structure. Verify that the normalizeIcons function and McpServer instantiation are correctly handling the icon structure per the @modelcontextprotocol/sdk specification.

packages/core/src/createApp.ts (4)

15-15: LGTM!

The Icon type is correctly imported using import type, ensuring no runtime import overhead. As per coding guidelines, this is the proper approach for type-only imports.


430-436: LGTM!

Clean signature extension with optional parameters maintains backward compatibility. The function signature clearly distinguishes between the shorthand icon (string URL or data URI) and the full icons array, aligning with the MCP specification's dual approach.


487-490: LGTM!

Global icons are correctly propagated to each version's merged config. The implementation aligns with the PR objective of passing app-level icons to McpServer during initialization. The comment clearly documents the intent.


817-824: LGTM!

The call site correctly passes both config.icon and config.icons from the global VersionsConfig to the merge function. This ensures icon configuration defined at the app level is propagated to all version instances.

@gabrypavanello
gabrypavanello force-pushed the claude/add-mcp-server-icon-eXjOv branch from ab2a237 to c1f7992 Compare January 14, 2026 13:49
@claude

claude Bot commented Jan 14, 2026

Copy link
Copy Markdown
Contributor

Code Review: MCP Server Icon Support

Summary

This PR implements server icon configuration following the MCP specification. The implementation is well-structured with good test coverage and proper error handling. Overall, this is a high-quality PR with thoughtful design decisions.

Strengths

  1. Excellent Test Coverage - 446 lines of comprehensive tests covering edge cases and validation
  2. Good Error Handling - Clear, actionable error messages with proper file size limits
  3. Well-Documented API - Excellent JSDoc comments with examples
  4. Type Safety - Proper TypeScript usage with no any types
  5. Versioning Support - Icons properly propagated from global to version configs

Issues and Suggestions

1. Missing MIME Type Validation (Medium Priority)

Location: packages/core/src/server/index.ts:69-74

The validateIcon function validates src and sizes, but NOT mimeType. This creates inconsistency because iconFromFile validates MIME types must start with image/ but manually created icons bypass this check.

Recommendation: Add MIME type validation to validateIcon to ensure all icons have valid image MIME types.

2. normalizeIcons Export (Low Priority)

The function is marked @internal but exported from server/index.ts. Consider making it truly internal or documenting why it needs to be exported.

3. Icon/Icons Precedence (Low Priority)

When both icon and icons are provided, icons takes precedence and icon is silently ignored. Consider adding a debug warning for this case.

4. Security: URL Scheme Validation

Users can provide arbitrary URLs with any scheme (file://, javascript:, etc.). Consider validating that only http, https, and data schemes are allowed.

5. Performance: Synchronous File Reading

iconFromFile uses fs.readFileSync which blocks the event loop. Add JSDoc note that this is intended for initialization only, not runtime.

Security Considerations

Good practices:

  • 1MB file size limit prevents memory exhaustion
  • MIME type validation in iconFromFile
  • Proper path resolution
  • Error messages don't leak sensitive info

Concerns:

  • No URL scheme validation
  • Data URIs can bypass size limits if created manually
  • Arbitrary remote URLs allowed

Test Coverage

Comprehensive coverage including:

  • Type checking, icon normalization, iconFromFile
  • Validation tests for sizes, empty strings, whitespace
  • Version config propagation
  • Edge cases and error scenarios

Missing: MIME type validation tests for manual icons, URL scheme tests

Verdict

APPROVE with suggestions

Well-implemented feature with excellent tests and documentation. Issues are minor and mostly involve additional validation for robustness.

Before Merge:

  1. Must run: pnpm build && pnpm test && pnpm lint && pnpm typecheck
  2. Strongly recommend: Add MIME type validation to validateIcon
  3. Consider: URL scheme validation for security

Great work!

claude and others added 8 commits January 14, 2026 15:11
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: { ... }
});
```
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: { ... }
});
```
- 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
- 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
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.
- 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
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.
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.
@gabrypavanello
gabrypavanello force-pushed the claude/add-mcp-server-icon-eXjOv branch from 84d1479 to d2df9d3 Compare January 14, 2026 14:11
@gabrypavanello
gabrypavanello merged commit 0fa55cc into main Jan 14, 2026
3 checks passed
@gabrypavanello
gabrypavanello deleted the claude/add-mcp-server-icon-eXjOv branch January 14, 2026 14:12
@claude

claude Bot commented Jan 14, 2026

Copy link
Copy Markdown
Contributor

PR Review: Add MCP Server Icon Support

Summary

This PR implements server icon configuration following the MCP specification (2025-11-25). The implementation is well-structured and production-ready with excellent test coverage and documentation.


✅ Strengths

1. Excellent Code Quality

  • Clean separation of concerns: types, utilities, validation, and integration
  • Comprehensive JSDoc documentation with examples
  • Follows existing codebase patterns (e.g., defineTool, type-only exports)
  • Proper error messages with actionable guidance

2. Robust Validation

  • Icon size format validation (/^\d+x\d+$/ pattern for "WxH", "any" for scalable)
  • Empty/whitespace string detection for both icon and icons.src
  • MIME type validation (must start with "image/")
  • File size limit (1MB) with helpful error messages

3. Strong Test Coverage

  • 446 lines of tests in icons.test.ts
  • Tests cover all edge cases: validation, precedence, size limits, formats
  • Integration tests in versioning.test.ts verify multi-version propagation
  • Mock strategy is clean and effective

4. Developer Experience

  • Convenient iconFromFile helper for local files
  • Dual API: shorthand icon string OR full icons array
  • Smart precedence: icons array takes precedence over icon shorthand
  • Clear documentation about size limits and recommendations

🔍 Issues Found

1. Security: Path Traversal Vulnerability (Medium Severity)

Location: packages/core/src/utils/icons.ts:95

const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);

Issue: No validation that the resolved path stays within expected boundaries. An attacker could use ../../../../etc/passwd to read arbitrary files if iconFromFile is ever exposed through a user-controlled API.

Recommendation: While this is likely build-time only, add defensive checks:

const absolutePath = path.isAbsolute(filePath) 
  ? filePath 
  : path.resolve(process.cwd(), filePath);

// Ensure the resolved path doesn't escape expected directories
const normalized = path.normalize(absolutePath);
if (normalized.includes('..')) {
  throw new Error(`Invalid file path: path traversal not allowed`);
}

Mitigation: Document that iconFromFile should ONLY be used at build time, never with user input.


2. Bug: Incorrect Empty Array Handling

Location: packages/core/src/server/index.ts:90

if (icons && icons.length > 0) {
  icons.forEach((ic, i) => {
    validateIcon(ic, i);
  });
  return icons;
}

Issue: Empty arrays [] return undefined, but the test at line 410 expects this behavior. However, this creates inconsistency: explicitly passing icons: [] is treated differently than icons: undefined.

Recommendation: Be explicit about the contract:

// Option 1: Treat empty array as undefined (current behavior - document it)
// Option 2: Preserve empty array (be explicit that it means "no icons")
if (icons !== undefined) {
  if (icons.length === 0) {
    return undefined; // Document: empty array = no icons
  }
  // ... validation
}

3. Type Safety: detectedMimeType Can Be Undefined

Location: packages/core/src/utils/icons.ts:128

const mimeType = options.mimeType ?? detectedMimeType ?? "";

Issue: While functionally correct, the ?? "" fallback is a code smell. The empty string would fail the next check anyway.

Better approach:

const mimeType = options.mimeType ?? detectedMimeType;
if (!mimeType) {
  // This case is actually already handled by lines 119-125
  throw new Error("MIME type could not be determined");
}

Though the current logic at lines 119-125 already ensures this won't happen, making it explicit improves clarity.


📋 Minor Suggestions

1. Missing Export in Example

Location: examples/minimal/src/index.ts:16

The example imports iconFromFile but doesn't demonstrate the full API. Consider showing both approaches:

// Approach 1: Using iconFromFile helper
icon: iconFromFile("./src/logo.png").src,

// Approach 2: Using full icons array
icons: [
  iconFromFile("./src/logo-48.png", { sizes: ["48x48"] }),
  iconFromFile("./src/logo-dark.png", { theme: "dark" }),
],

2. Test Organization

The test file is well-organized but at 446 lines could benefit from splitting:

  • icons-validation.test.ts - validation logic
  • icons-from-file.test.ts - iconFromFile utility
  • Keep versioning.test.ts for integration

3. Documentation Gap

Consider adding to packages/core/README.md:

  • Section on icon configuration
  • Link to MCP spec
  • Performance notes (1MB limit, prefer SVG)

🔒 Security Assessment

Overall: LOW RISK

  1. Data URIs: Base64 encoding is safe for icons
  2. MIME Type Validation: Properly restricts to image types
  3. Size Limits: 1MB cap prevents abuse
  4. Path Traversal: Low risk if used at build-time only (see issue Add Claude Code GitHub Workflow #1)

Recommendations:

  • Document that iconFromFile is for build-time use only
  • Add path validation for defense-in-depth
  • Consider adding CSP headers for served icons (out of scope for this PR)

🚀 Performance Considerations

✅ Good:

  • Base64 encoding happens at build time (via iconFromFile)
  • Icons are passed directly to MCP server initialization (no runtime overhead)
  • 1MB limit prevents excessive memory usage

⚠️ Consider:

  • Base64 encoding increases size by ~33% (documented ✅)
  • Multiple large icons could increase bundle size
  • Recommendation to use external URLs for large icons is good ✅

📊 Test Coverage Analysis

Coverage: EXCELLENT 🎯

  • Type tests: ✅ Icon interface, AppConfig integration
  • Validation tests: ✅ Size format, src validation, MIME types
  • Integration tests: ✅ Multi-version propagation, precedence
  • Edge cases: ✅ Empty strings, whitespace, size limits, unsupported formats
  • Error messages: ✅ Verified helpful error text

Missing coverage:

  • None! This is exemplary test coverage.

🎯 Compliance with CLAUDE.md

Zod 4 - No Zod schemas added (only types)
Express 5 - No Express changes
Strict TS - No any types used
50% test coverage - Far exceeds minimum
Patterns - Consistent with defineTool style
Exports - Properly exported through index.ts
No circular deps - Clean dependency graph


📝 Final Verdict

APPROVE WITH MINOR FIXES

This is a high-quality PR that follows best practices and integrates cleanly with the existing codebase. The only blocking issue is the potential path traversal vulnerability, which should be addressed with documentation and/or validation.

Action Items (Priority Order):

  1. 🔴 HIGH: Add documentation that iconFromFile is build-time only OR add path traversal validation
  2. 🟡 MEDIUM: Clarify empty array behavior in normalizeIcons
  3. 🟢 LOW: Consider splitting test file and adding README documentation

Estimated effort: 30 minutes to address high-priority items.


Great work @gabrypavanello! This implementation is thorough, well-tested, and provides excellent DX. 🎉

@claude

claude Bot commented Jan 14, 2026

Copy link
Copy Markdown
Contributor

Code Review: MCP Server Icon Support

Summary

This PR implements server icon configuration following the MCP specification (2025-11-25). The implementation is well-structured with good test coverage and comprehensive documentation.


✅ Strengths

  1. Excellent Test Coverage: 446 lines of comprehensive unit tests covering edge cases, validation, error handling, and type safety
  2. Strong Type Safety: Proper TypeScript types with good JSDoc documentation
  3. Good API Design: Clean shorthand (icon) and advanced (icons) options with clear precedence rules
  4. Robust Validation: Thorough input validation with helpful error messages
  5. MCP Spec Compliance: Follows the MCP specification for Implementation icons
  6. Clear Documentation: Excellent JSDoc comments with usage examples

🔍 Issues Found

1. Security: Potential Path Traversal Vulnerability (High Priority)

Location: packages/core/src/utils/icons.ts:95

The function accepts user-provided file paths without validation, allowing path traversal attacks like iconFromFile('../../../etc/passwd').

Recommendation: Add path validation to restrict file access to the project directory.


2. Exported Internal Function (Medium Priority)

Location: packages/core/src/server/index.ts:85

The normalizeIcons function is marked @internal but is exported and used in tests. This breaks encapsulation.


3. Example Usage Issue (Medium Priority)

Location: examples/minimal/src/index.ts:182

The example extracts .src unnecessarily, loads the 56KB file at module init (blocking), and embeds it as ~75KB base64 in every response. Consider using a URL or lazy loading instead.


4. Missing Dimension Validation (Low Priority)

Location: packages/core/src/server/index.ts:48

The SIZES_PATTERN regex accepts invalid sizes like 0x0 or 99999x99999. Consider adding bounds checking (e.g., 1-4096 range).


📊 Test Coverage

The new code appears well-tested with 446 lines of comprehensive unit tests. Per CLAUDE.md, verify 50% coverage minimum passes.


🔧 Pre-Merge Checklist

Per CLAUDE.md, confirm all four pass:

pnpm build && pnpm test && pnpm lint && pnpm typecheck

🎯 Priority Actions

  1. HIGH: Fix path traversal vulnerability in iconFromFile
  2. MEDIUM: Update example to use better pattern (URL or icons array)
  3. MEDIUM: Clarify internal vs public API for normalizeIcons
  4. LOW: Consider dimension validation for icon sizes

✨ Conclusion

This is a solid implementation with excellent testing and documentation. The main concern is the path traversal vulnerability which should be addressed before merge. The other issues are minor improvements.

Recommendation: Request changes for the security issue, then approve after fix.

Great work overall! 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants