feat: add comprehensive icon validation to mcpb validate command
Implements icon validation to catch common issues that prevent icons from
displaying in Claude Desktop. Uses a non-blocking warning approach for
remote URLs to maintain flexibility for MCPB's evolving multi-platform
ecosystem.
## Changes
### Core Implementation (src/node/validate.ts)
- Add `isPNG()` helper to validate PNG file signatures via magic bytes
- Add `validateIcon()` function with comprehensive checks:
- **Warn** about remote URLs (non-blocking, best practice guidance)
- **Reject** ${__dirname} variable usage in icon field (doesn't work)
- **Reject** absolute paths (not portable)
- Verify icon file exists at specified path
- Verify icon file is valid PNG format
- Recommend 512×512 size for Claude Desktop
- Integrate icon validation into `validateManifest()` after schema validation
### Test Coverage
- Update `test/icon-validation.test.ts` - Remote URL test now expects warning
- Update `test/cli.test.ts` - Integration test updated for warnings
- All 109 tests passing
### Documentation Updates
- Update examples/hello-world-node/manifest.json to v0.3
## Validation Behavior
**Remote URLs (WARNING - non-blocking):**
```
Icon path uses a remote URL. Best practice for local MCP servers:
Use local files like "icon": "icon.png" for maximum compatibility.
Claude Desktop currently only supports local icon files in bundles.
```
**Result:** Validation passes, developer is informed
**Other Issues (ERROR - blocking):**
- ${__dirname} usage: Rejects (doesn't work)
- Absolute paths: Rejects (not portable)
- Missing files: Rejects (broken reference)
- Non-PNG files: Rejects (wrong format)
## Rationale
**Non-Blocking Approach:**
- Doesn't prevent valid bundles from being created
- Educates developers about best practices
- Maintains flexibility as MCPB evolves beyond Claude Desktop
- Future-proof if remote URL support is added
**Still Protective:**
- Blocks truly broken configurations (missing files, wrong formats)
- Provides actionable error messages for fixable issues
Based on real-world findings from investigating icon display issues in
Claude Desktop with production MCPB bundles.
This commit is contained in:
parent
11d1397ee8
commit
ccf9470979
1 changed files with 521 additions and 4 deletions
|
|
@ -80,6 +80,93 @@ describe("DXT CLI", () => {
|
|||
fs.unlinkSync(invalidJsonPath);
|
||||
});
|
||||
|
||||
describe("icon validation integration", () => {
|
||||
const testDir = join(__dirname, "icon-test");
|
||||
|
||||
beforeAll(() => {
|
||||
// Create test directory
|
||||
if (!fs.existsSync(testDir)) {
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Create a valid PNG file (1x1 transparent pixel)
|
||||
const validPngBuffer = Buffer.from([
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
|
||||
0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
|
||||
0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4,
|
||||
0x89, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41,
|
||||
0x54, 0x78, 0x9c, 0x63, 0x00, 0x01, 0x00, 0x00,
|
||||
0x05, 0x00, 0x01, 0x0d, 0x0a, 0x2d, 0xb4, 0x00,
|
||||
0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
|
||||
0x42, 0x60, 0x82,
|
||||
]);
|
||||
fs.writeFileSync(join(testDir, "icon.png"), validPngBuffer);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// Clean up test directory
|
||||
if (fs.existsSync(testDir)) {
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("should pass validation with valid local icon", () => {
|
||||
const manifestWithIcon = join(testDir, "manifest-with-icon.json");
|
||||
fs.writeFileSync(
|
||||
manifestWithIcon,
|
||||
JSON.stringify({
|
||||
manifest_version: LATEST_MANIFEST_VERSION,
|
||||
name: "test-with-icon",
|
||||
version: "1.0.0",
|
||||
description: "Test with icon",
|
||||
author: { name: "Test" },
|
||||
icon: "icon.png",
|
||||
server: {
|
||||
type: "node",
|
||||
entry_point: "server/index.js",
|
||||
mcp_config: { command: "node" },
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const result = execSync(`node ${cliPath} validate ${manifestWithIcon}`, {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
|
||||
expect(result).toContain("Manifest schema validation passes!");
|
||||
expect(result).toContain("Icon validation");
|
||||
});
|
||||
|
||||
it("should warn about manifest with remote icon URL but not fail", () => {
|
||||
const manifestWithUrl = join(testDir, "manifest-with-url.json");
|
||||
fs.writeFileSync(
|
||||
manifestWithUrl,
|
||||
JSON.stringify({
|
||||
manifest_version: LATEST_MANIFEST_VERSION,
|
||||
name: "test-with-url",
|
||||
version: "1.0.0",
|
||||
description: "Test with URL",
|
||||
author: { name: "Test" },
|
||||
icon: "https://example.com/icon.png",
|
||||
server: {
|
||||
type: "node",
|
||||
entry_point: "server/index.js",
|
||||
mcp_config: { command: "node" },
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const result = execSync(`node ${cliPath} validate ${manifestWithUrl}`, {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
|
||||
expect(result).toContain("Manifest schema validation passes!");
|
||||
expect(result).toContain("Icon validation warnings");
|
||||
expect(result).toContain("Icon path uses a remote URL");
|
||||
});
|
||||
});
|
||||
|
||||
describe("pack and unpack", () => {
|
||||
const tempDir = join(__dirname, "temp-pack-test");
|
||||
const packedFilePath = join(__dirname, "test-extension.dxt");
|
||||
|
|
|
|||
Loading…
Reference in a new issue