* Generate an MCPB bundle for a given library. This makes the MCP servers easier to install in * certain MCP clients like Claude Desktop. Reference: https://github.com/modelcontextprotocol/mcpb.
(libraryName, config)
| 117 | * certain MCP clients like Claude Desktop. Reference: https://github.com/modelcontextprotocol/mcpb. |
| 118 | */ |
| 119 | async function generateBundle(libraryName, config) { |
| 120 | const packageJsonPath = path.join(config.packageDir, 'package.json'); |
| 121 | const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); |
| 122 | const stagingDir = path.join(config.outputDir, config.extensionName); |
| 123 | |
| 124 | fs.rmSync(stagingDir, {recursive: true, force: true}); |
| 125 | fs.mkdirSync(stagingDir, {recursive: true}); |
| 126 | |
| 127 | for (const dir of config.srcDirs) { |
| 128 | if (!fs.existsSync(dir.from)) { |
| 129 | throw new Error( |
| 130 | `Missing built MCP output at ${dir.from}. Build ${config.packageName} first.` |
| 131 | ); |
| 132 | } |
| 133 | copyDirectory(dir.from, path.join(stagingDir, dir.to)); |
| 134 | } |
| 135 | |
| 136 | const bundledPackages = new Set(); |
| 137 | for (const dependency of Object.keys(packageJson.dependencies || {})) { |
| 138 | copyDependencyTree(dependency, path.join(stagingDir, 'node_modules'), bundledPackages); |
| 139 | } |
| 140 | |
| 141 | // Convert SVG icon to 512x512 PNG for the bundle. |
| 142 | const iconFile = 'icon.png'; |
| 143 | let svg = fs.readFileSync(config.iconSvg, 'utf8'); |
| 144 | // The React Aria favicon uses light-dark() CSS which sharp doesn't support. |
| 145 | // Replace it with the dark-mode color so the icon works on any background. |
| 146 | svg = svg.replace(/light-dark\([^,]+,\s*([^)]+)\)/, '$1'); |
| 147 | await sharp(Buffer.from(svg)) |
| 148 | .resize(448, 448, {fit: 'contain', background: {r: 0, g: 0, b: 0, alpha: 0}}) |
| 149 | .extend({top: 32, bottom: 32, left: 32, right: 32, background: {r: 0, g: 0, b: 0, alpha: 0}}) |
| 150 | .png() |
| 151 | .toFile(path.join(stagingDir, iconFile)); |
| 152 | |
| 153 | fs.writeFileSync( |
| 154 | path.join(stagingDir, 'package.json'), |
| 155 | JSON.stringify( |
| 156 | { |
| 157 | name: config.extensionName, |
| 158 | version: packageJson.version, |
| 159 | private: true, |
| 160 | type: 'module' |
| 161 | }, |
| 162 | null, |
| 163 | 2 |
| 164 | ) + '\n' |
| 165 | ); |
| 166 | |
| 167 | fs.writeFileSync( |
| 168 | path.join(stagingDir, 'manifest.json'), |
| 169 | JSON.stringify( |
| 170 | { |
| 171 | manifest_version: '0.3', |
| 172 | name: config.extensionName, |
| 173 | display_name: config.displayName, |
| 174 | version: packageJson.version, |
| 175 | description: config.description, |
| 176 | long_description: config.longDescription, |
no test coverage detected