* Validate icon field in manifest * @param iconPath - The icon path from manifest.json * @param baseDir - The base directory containing the manifest * @returns Validation result with errors and warnings
( iconPath: string, baseDir: string, )
| 37 | * @returns Validation result with errors and warnings |
| 38 | */ |
| 39 | function validateIcon( |
| 40 | iconPath: string, |
| 41 | baseDir: string, |
| 42 | ): { valid: boolean; errors: string[]; warnings: string[] } { |
| 43 | const errors: string[] = []; |
| 44 | const warnings: string[] = []; |
| 45 | |
| 46 | const isRemoteUrl = |
| 47 | iconPath.startsWith("http://") || iconPath.startsWith("https://"); |
| 48 | const hasVariableSubstitution = iconPath.includes("${__dirname}"); |
| 49 | const isAbsolutePath = isAbsolute(iconPath); |
| 50 | |
| 51 | // Warn about remote URLs (best practice: use local files) |
| 52 | if (isRemoteUrl) { |
| 53 | warnings.push( |
| 54 | "Icon path uses a remote URL. " + |
| 55 | 'Best practice for local MCP servers: Use local files like "icon": "icon.png" for maximum compatibility. ' + |
| 56 | "Claude Desktop currently only supports local icon files in bundles.", |
| 57 | ); |
| 58 | } |
| 59 | |
| 60 | // Check for ${__dirname} variable (error - doesn't work) |
| 61 | if (hasVariableSubstitution) { |
| 62 | errors.push( |
| 63 | "Icon path should not use ${__dirname} variable substitution. " + |
| 64 | 'Use a simple relative path like "icon.png" instead of "${__dirname}/icon.png".', |
| 65 | ); |
| 66 | } |
| 67 | |
| 68 | // Check for absolute path (error - not portable) |
| 69 | if (isAbsolutePath) { |
| 70 | errors.push( |
| 71 | "Icon path must be relative to the bundle root, not an absolute path. " + |
| 72 | `Found: "${iconPath}"`, |
| 73 | ); |
| 74 | } |
| 75 | |
| 76 | // Only proceed with file checks if the path looks like a local file |
| 77 | if (!isRemoteUrl && !isAbsolutePath && !hasVariableSubstitution) { |
| 78 | // Check file existence |
| 79 | const fullIconPath = join(baseDir, iconPath); |
| 80 | if (!existsSync(fullIconPath)) { |
| 81 | errors.push(`Icon file not found at path: ${iconPath}`); |
| 82 | } else { |
| 83 | try { |
| 84 | // Check PNG format |
| 85 | const buffer = readFileSync(fullIconPath); |
| 86 | if (!isPNG(buffer)) { |
| 87 | errors.push( |
| 88 | `Icon file must be PNG format. The file at "${iconPath}" does not appear to be a valid PNG file.`, |
| 89 | ); |
| 90 | } else { |
| 91 | // File exists and is a valid PNG - add recommendation |
| 92 | warnings.push( |
| 93 | "Icon validation passed. Recommended size is 512×512 pixels for best display in Claude Desktop.", |
| 94 | ); |
| 95 | } |
| 96 | } catch (error) { |
no test coverage detected
searching dependent graphs…