Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,15 @@ plans/
planning/

# Test output files
test/test_output/
test/test_output/

# MCPB related files
*.mcpb
mcpb-bundle/
.mcpregistry_github_token
.mcpregistry_registry_token

# Test files
test_files/
test_fix.js
test_search.js
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -954,7 +954,7 @@ External telemetry (sent to analytics services) is enabled by default but can be

**Note:** This only disables external telemetry. Local usage analytics remain active for tool functionality but is not share externally

For complete details about data collection, please see our [Privacy Policy](PRIVACY.md).
For complete details about data collection, please see our [Privacy Policy](https://legal.desktopcommander.app/privacy_desktop_commander_mcp).

## Verifications
[![Verified on MseeP](https://mseep.ai/badge.svg)](https://mseep.ai/app/25ff7a06-58bc-40b8-bd79-ebb715140f1a)
Expand Down
35 changes: 35 additions & 0 deletions manifest.future.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"dxt_version": "0.1",
"manifest_version": "0.1",
"name": "desktop-commander",
"display_name": "Desktop Commander",
"version": "{{VERSION}}",
"description": "Execute long-running terminal commands and manage processes through Model Context Protocol (MCP)",
"author": {
"name": "Desktop Commander Team",
"url": "https://github.com/wonderwhy-er/DesktopCommanderMCP"
},
"privacy_policies": [
"https://legal.desktopcommander.app/privacy_desktop_commander_mcp"
],
"server": {
"type": "node",
"entry_point": "dist/index.js",
"mcp_config": {
"command": "node",
"args": [
"${__dirname}/dist/index.js"
],
"env": {
"NODE_ENV": "production"
}
}
},
"homepage": "https://github.com/wonderwhy-er/DesktopCommanderMCP",
"repository": {
"type": "git",
"url": "https://github.com/wonderwhy-er/DesktopCommanderMCP.git"
},
"license": "MIT",
"icon": "logo.png"
}
31 changes: 31 additions & 0 deletions manifest.template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"dxt_version": "0.1",
"name": "desktop-commander",
"display_name": "Desktop Commander",
"version": "{{VERSION}}",
"description": "Execute long-running terminal commands and manage processes through Model Context Protocol (MCP)",
"author": {
"name": "Desktop Commander Team",
"url": "https://github.com/wonderwhy-er/DesktopCommanderMCP"
},
"server": {
"type": "node",
"entry_point": "dist/index.js",
"mcp_config": {
"command": "node",
"args": [
"${__dirname}/dist/index.js"
],
Comment on lines +25 to +29

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 | 🔴 Critical

Fix the MCP command args path (literal ${__dirname} won’t resolve).

mcp_config.args is passed verbatim to the client process runner, so "${__dirname}/dist/index.js" remains a literal string and the launch fails with “ENOENT: no such file or directory, open '${__dirname}/dist/index.js'”. Use a relative path instead so the bundle starts correctly.

Apply this diff:

-        "${__dirname}/dist/index.js"
+        "./dist/index.js"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"mcp_config": {
"command": "node",
"args": [
"${__dirname}/dist/index.js"
],
"mcp_config": {
"command": "node",
"args": [
"./dist/index.js"
],
🤖 Prompt for AI Agents
In manifest.template.json around lines 24 to 28, the mcp_config.args entry uses
the literal string "${__dirname}/dist/index.js" which won’t be resolved at
runtime; replace that element with a relative path to the bundled entry (for
example "./dist/index.js" or "dist/index.js") so the node process can locate and
start the bundle correctly when invoked by the client process runner.

"env": {
"NODE_ENV": "production"
}
}
},
"homepage": "https://github.com/wonderwhy-er/DesktopCommanderMCP",
"repository": {
"type": "git",
"url": "https://github.com/wonderwhy-er/DesktopCommanderMCP.git"
},
"license": "MIT",
"icon": "logo.png"
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
"link:local": "npm run build && npm link",
"unlink:local": "npm unlink",
"inspector": "npx @modelcontextprotocol/inspector dist/index.js",
"build:mcpb": "node scripts/build-mcpb.cjs",
"build:mcpb:future": "node scripts/build-mcpb.cjs --future",
"logs:view": "npm run build && node scripts/view-fuzzy-logs.js",
"logs:analyze": "npm run build && node scripts/analyze-fuzzy-logs.js",
"logs:clear": "npm run build && node scripts/clear-fuzzy-logs.js",
Expand Down
170 changes: 170 additions & 0 deletions scripts/build-mcpb.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#!/usr/bin/env node

/**
* Build script for creating Desktop Commander MCPB bundle
*
* This script:
* 1. Builds the TypeScript project
* 2. Creates a bundle directory structure
* 3. Generates a proper MCPB manifest.json with privacy policy
* 4. Copies the built server and dependencies
* 5. Uses mcpb CLI to create the final .mcpb bundle
*/

const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

const PROJECT_ROOT = path.resolve(__dirname, '..');
const BUNDLE_DIR = path.join(PROJECT_ROOT, 'mcpb-bundle');
const MANIFEST_PATH = path.join(BUNDLE_DIR, 'manifest.json');

console.log('🏗️ Building Desktop Commander MCPB Bundle...');

// Step 1: Build the TypeScript project
console.log('📦 Building TypeScript project...');
try {
execSync('npm run build', { cwd: PROJECT_ROOT, stdio: 'inherit' });
console.log('✅ TypeScript build completed');
} catch (error) {
console.error('❌ TypeScript build failed:', error.message);
process.exit(1);
}

// Step 2: Clean and create bundle directory
if (fs.existsSync(BUNDLE_DIR)) {
fs.rmSync(BUNDLE_DIR, { recursive: true });
}
fs.mkdirSync(BUNDLE_DIR, { recursive: true });

Comment on lines +35 to +38

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 | 🔴 Critical

Fix bundle directory creation syntax error.

} and fs.mkdirSync are glued together (}fs), so the script dies with Unexpected identifier before doing any work. Split them so the bundle directory actually gets created.

 if (fs.existsSync(BUNDLE_DIR)) {
     fs.rmSync(BUNDLE_DIR, { recursive: true });
-}fs.mkdirSync(BUNDLE_DIR, { recursive: true });
+}
+fs.mkdirSync(BUNDLE_DIR, { recursive: true });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (fs.existsSync(BUNDLE_DIR)) {
fs.rmSync(BUNDLE_DIR, { recursive: true });
}fs.mkdirSync(BUNDLE_DIR, { recursive: true });
if (fs.existsSync(BUNDLE_DIR)) {
fs.rmSync(BUNDLE_DIR, { recursive: true });
}
fs.mkdirSync(BUNDLE_DIR, { recursive: true });
🤖 Prompt for AI Agents
In scripts/build-mcpb.cjs around lines 35 to 38, there's a syntax error caused
by `}` and `fs.mkdirSync` being concatenated (`}fs`) which throws an Unexpected
identifier; separate the statements by placing a semicolon or newline after the
closing brace so the fs.mkdirSync(BUNDLE_DIR, { recursive: true }) call is a
standalone statement, ensuring the bundle directory is created when it does not
exist.

// Step 3: Read package.json for version and metadata
const packageJson = JSON.parse(fs.readFileSync(path.join(PROJECT_ROOT, 'package.json'), 'utf8'));

// Step 4: Load and process manifest template
console.log('📝 Processing manifest template...');

// Check if we should use the future manifest (with privacy policies)
const useFutureManifest = process.argv.includes('--future');
const manifestTemplatePath = path.join(
PROJECT_ROOT,
useFutureManifest ? 'manifest.future.json' : 'manifest.template.json'
);

console.log(`📄 Using manifest: ${useFutureManifest ? 'manifest.future.json' : 'manifest.template.json'}`);

let manifestTemplate;
try {
manifestTemplate = fs.readFileSync(manifestTemplatePath, 'utf8');
} catch (error) {
console.error('❌ Failed to read manifest template:', manifestTemplatePath);
process.exit(1);
}

// Replace template variables
const manifestContent = manifestTemplate.replace('{{VERSION}}', packageJson.version);

// Parse and validate the resulting manifest
let manifest;
try {
manifest = JSON.parse(manifestContent);
} catch (error) {
console.error('❌ Invalid JSON in manifest template:', error.message);
process.exit(1);
}

// Write manifest
fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
console.log('✅ Created manifest.json');

// Step 5: Copy necessary files
const filesToCopy = [
'dist',
'package.json',
'README.md',
'LICENSE',
'PRIVACY.md',
'logo.png'
];

filesToCopy.forEach(file => {
const srcPath = path.join(PROJECT_ROOT, file);
const destPath = path.join(BUNDLE_DIR, file);

if (fs.existsSync(srcPath)) {
if (fs.statSync(srcPath).isDirectory()) {
// Copy directory recursively
fs.cpSync(srcPath, destPath, { recursive: true });
} else {
// Copy file
fs.copyFileSync(srcPath, destPath);
}
console.log(`✅ Copied ${file}`);
} else {
console.log(`⚠️ Skipped ${file} (not found)`);
}
});

// Step 6: Create package.json in bundle for dependency info
const bundlePackageJson = {
name: manifest.name,
version: manifest.version,
description: manifest.description,
main: "dist/index.js",
author: manifest.author,
license: manifest.license,
repository: manifest.repository
};

fs.writeFileSync(
path.join(BUNDLE_DIR, 'package.json'),
JSON.stringify(bundlePackageJson, null, 2)
);

// Step 7: Validate manifest
console.log('🔍 Validating manifest...');
try {
execSync(`mcpb validate "${MANIFEST_PATH}"`, { stdio: 'inherit' });
console.log('✅ Manifest validation passed');
} catch (error) {
console.error('❌ Manifest validation failed:', error.message);
process.exit(1);
}
Comment on lines +137 to +142

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.

🛠️ Refactor suggestion

🧩 Analysis chain

Verify mcpb CLI tool availability.

The script depends on the mcpb CLI tool for validation and packing but doesn't check if it's installed. This could cause runtime failures during the build process.


🏁 Script executed:

#!/bin/bash
# Description: Check if mcpb CLI tool is available and working

# Check if mcpb command exists
if command -v mcpb >/dev/null 2>&1; then
    echo "✅ mcpb CLI tool found"
    mcpb --version 2>/dev/null || echo "mcpb version command failed"
else
    echo "❌ mcpb CLI tool not found"
    echo "This tool is required for bundle validation and packing"
    echo "Install it with: npm install -g mcpb"
    exit 1
fi

Length of output: 118


Add availability check for mcpb CLI before execution.
scripts/build-mcpb.cjs:126-131
The script currently invokes mcpb without verifying it’s installed, leading to opaque failures. Insert a preflight check (e.g. command -v mcpb or spawnSync('mcpb',['--version'])) and emit a clear error with install instructions before calling mcpb validate.

🤖 Prompt for AI Agents
In scripts/build-mcpb.cjs around lines 126 to 131, the script calls `mcpb
validate` without checking that the `mcpb` CLI is installed; add a preflight
availability check (for example use child_process.spawnSync('mcpb',
['--version']) or run a shell `command -v mcpb`) prior to execSync, and if the
check fails log a clear error message that explains mcpb is not found and gives
install instructions (or a link) and exit with code 1; only proceed to
execSync(`mcpb validate ...`) when the check succeeds.


// Step 8: Pack the bundle
console.log('📦 Creating .mcpb bundle...');
const outputFile = path.join(PROJECT_ROOT, `${manifest.name}-${manifest.version}.mcpb`);

try {
execSync(`mcpb pack "${BUNDLE_DIR}" "${outputFile}"`, { stdio: 'inherit' });
console.log('✅ MCPB bundle created successfully!');
console.log(`📁 Bundle location: ${outputFile}`);
} catch (error) {
console.error('❌ Bundle creation failed:', error.message);
process.exit(1);
}

console.log('');
console.log('🎉 Desktop Commander MCPB bundle is ready!');
console.log('');
console.log('Next steps:');
console.log('1. Test the bundle by installing it in Claude Desktop:');
console.log(' Settings → Extensions → Advanced Settings → Install Extension');
console.log(`2. Select the file: ${outputFile}`);
console.log('3. Configure any settings and test the functionality');
console.log('');
console.log('Build options:');
console.log('- Default: npm run build:mcpb (uses manifest.template.json)');
console.log('- Future: npm run build:mcpb -- --future (uses manifest.future.json with privacy policies)');
console.log('');
if (!useFutureManifest) {
console.log('📝 Note: Using basic manifest for Claude Desktop compatibility.');
console.log(' Use --future flag when privacy policies are supported.');
} else {
console.log('🔮 Using future manifest with privacy policies for Anthropic submission.');
}
console.log('');
console.log('To submit to Anthropic directory:');
console.log('- Build with: npm run build:mcpb -- --future');
console.log('- Ensure privacy policy is accessible at the GitHub URL');
console.log('- Complete destructive operation annotations (✅ Done)');
console.log('- Submit via Anthropic desktop extensions interest form');
Loading