feat: run mcp with bundled bun and uv (#4949)
* feat: run mcp with bundled bun and uv * chore: clean up * chore: pull binaries windows, linux (#4963) * fix: get bun and uv from execution path * fix: macos * fix: typo --------- Co-authored-by: vansangpfiev <vansangpfiev@gmail.com>
This commit is contained in:
parent
dea29ff8c4
commit
52ac14ad06
3
.gitignore
vendored
3
.gitignore
vendored
@ -43,4 +43,5 @@ src-tauri/resources/themes
|
||||
src-tauri/Cargo.lock
|
||||
src-tauri/icons
|
||||
!src-tauri/icons/icon.png
|
||||
src-tauri/gen/apple
|
||||
src-tauri/gen/apple
|
||||
src-tauri/resources/bin
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
"install:cortex:linux:darwin": "cd src-tauri/binaries && ./download.sh",
|
||||
"install:cortex:win32": "cd src-tauri/binaries && download.bat",
|
||||
"install:cortex": "run-script-os",
|
||||
"download:bin": "node ./scripts/download-bin.js",
|
||||
"dev:tauri": "yarn build:icon && yarn copy:assets:tauri && tauri dev",
|
||||
"build:icon": "tauri icon ./src-tauri/icons/icon.png",
|
||||
"build:server": "cd server && yarn build",
|
||||
@ -51,6 +52,8 @@
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"run-script-os": "^1.1.6",
|
||||
"tar": "^4.4.19",
|
||||
"unzipper": "^0.12.3",
|
||||
"wait-on": "^7.0.1"
|
||||
},
|
||||
"version": "0.0.0",
|
||||
|
||||
219
scripts/download-bin.js
Executable file
219
scripts/download-bin.js
Executable file
@ -0,0 +1,219 @@
|
||||
console.log('Script is running')
|
||||
// scripts/download.js
|
||||
import https from 'https'
|
||||
import fs, { copyFile, mkdirSync } from 'fs'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import unzipper from 'unzipper'
|
||||
import tar from 'tar'
|
||||
import { copySync } from 'cpx'
|
||||
|
||||
function download(url, dest) {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(`Downloading ${url} to ${dest}`)
|
||||
const file = fs.createWriteStream(dest)
|
||||
https
|
||||
.get(url, (response) => {
|
||||
console.log(`Response status code: ${response.statusCode}`)
|
||||
if (
|
||||
response.statusCode >= 300 &&
|
||||
response.statusCode < 400 &&
|
||||
response.headers.location
|
||||
) {
|
||||
// Handle redirect
|
||||
const redirectURL = response.headers.location
|
||||
console.log(`Redirecting to ${redirectURL}`)
|
||||
download(redirectURL, dest).then(resolve, reject) // Recursive call
|
||||
return
|
||||
} else if (response.statusCode !== 200) {
|
||||
reject(`Failed to get '${url}' (${response.statusCode})`)
|
||||
return
|
||||
}
|
||||
response.pipe(file)
|
||||
file.on('finish', () => {
|
||||
file.close(resolve)
|
||||
})
|
||||
})
|
||||
.on('error', (err) => {
|
||||
fs.unlink(dest, () => reject(err.message))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function decompress(filePath, targetDir) {
|
||||
console.log(`Decompressing ${filePath} to ${targetDir}`)
|
||||
if (filePath.endsWith('.zip')) {
|
||||
await fs
|
||||
.createReadStream(filePath)
|
||||
.pipe(unzipper.Extract({ path: targetDir }))
|
||||
.promise()
|
||||
} else if (filePath.endsWith('.tar.gz')) {
|
||||
await tar.x({
|
||||
file: filePath,
|
||||
cwd: targetDir,
|
||||
})
|
||||
} else {
|
||||
throw new Error(`Unsupported archive format: ${filePath}`)
|
||||
}
|
||||
}
|
||||
|
||||
function getPlatformArch() {
|
||||
const platform = os.platform() // 'darwin', 'linux', 'win32'
|
||||
const arch = os.arch() // 'x64', 'arm64', etc.
|
||||
|
||||
let bunPlatform, uvPlatform
|
||||
|
||||
if (platform === 'darwin') {
|
||||
bunPlatform = arch === 'arm64' ? 'darwin-aarch64' : 'darwin-x86'
|
||||
uvPlatform =
|
||||
arch === 'arm64' ? 'aarch64-apple-darwin' : 'x86_64-apple-darwin'
|
||||
} else if (platform === 'linux') {
|
||||
bunPlatform = arch === 'arm64' ? 'linux-aarch64' : 'linux-x64'
|
||||
uvPlatform = arch === 'arm64' ? 'aarch64-unknown-linux-gnu' : 'x86_64-unknown-linux-gnu'
|
||||
} else if (platform === 'win32') {
|
||||
bunPlatform = 'windows-x64' // Bun has limited Windows support
|
||||
uvPlatform = 'x86_64-pc-windows-msvc'
|
||||
} else {
|
||||
throw new Error(`Unsupported platform: ${platform}`)
|
||||
}
|
||||
|
||||
return { bunPlatform, uvPlatform }
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('Starting main function')
|
||||
const platform = os.platform()
|
||||
const { bunPlatform, uvPlatform } = getPlatformArch()
|
||||
console.log(`bunPlatform: ${bunPlatform}, uvPlatform: ${uvPlatform}`)
|
||||
|
||||
const binDir = 'src-tauri/resources/bin'
|
||||
const tempBinDir = 'scripts/dist'
|
||||
const bunPath = `${tempBinDir}/bun-${bunPlatform}.zip`
|
||||
let uvPath = `${tempBinDir}/uv-${uvPlatform}.tar.gz`
|
||||
if (platform === 'win32') {
|
||||
uvPath = `${tempBinDir}/uv-${uvPlatform}.zip`
|
||||
}
|
||||
try {
|
||||
mkdirSync('scripts/dist')
|
||||
} catch (err) {
|
||||
// Expect EEXIST error if the directory already exists
|
||||
}
|
||||
|
||||
// Adjust these URLs based on latest releases
|
||||
const bunVersion = '1.2.10' // Example Bun version
|
||||
const bunUrl = `https://github.com/oven-sh/bun/releases/download/bun-v${bunVersion}/bun-${bunPlatform}.zip`
|
||||
|
||||
const uvVersion = '0.6.17' // Example UV version
|
||||
let uvUrl = `https://github.com/astral-sh/uv/releases/download/${uvVersion}/uv-${uvPlatform}.tar.gz`
|
||||
if (platform === 'win32') {
|
||||
uvUrl = `https://github.com/astral-sh/uv/releases/download/${uvVersion}/uv-${uvPlatform}.zip`
|
||||
}
|
||||
|
||||
console.log(`Downloading Bun for ${bunPlatform}...`)
|
||||
await download(bunUrl, path.join(tempBinDir, `bun-${bunPlatform}.zip`))
|
||||
await decompress(bunPath, tempBinDir)
|
||||
try {
|
||||
copySync(
|
||||
path.join(tempBinDir, `bun-${bunPlatform}`, 'bun'),
|
||||
path.join(binDir)
|
||||
)
|
||||
if(platform === 'darwin') {
|
||||
copyFile(path.join(binDir, 'bun'), path.join(binDir, 'bun-x86_64-apple-darwin'), (err) => {
|
||||
if (err) {
|
||||
console.log("Error Found:", err);
|
||||
}
|
||||
})
|
||||
copyFile(path.join(binDir, 'bun'), path.join(binDir, 'bun-aarch64-apple-darwin'), (err) => {
|
||||
if (err) {
|
||||
console.log("Error Found:", err);
|
||||
}
|
||||
})
|
||||
} else if (platform === 'linux') {
|
||||
copyFile(path.join(binDir, 'bun'), path.join(binDir, 'bun-x86_64-unknown-linux-gnu'), (err) => {
|
||||
if (err) {
|
||||
console.log("Error Found:", err);
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
// Expect EEXIST error
|
||||
}
|
||||
try {
|
||||
copySync(
|
||||
path.join(tempBinDir, `bun-${bunPlatform}`, 'bun.exe'),
|
||||
path.join(binDir)
|
||||
)
|
||||
if (platform === 'win32') {
|
||||
copyFile(path.join(binDir, 'bun.exe'), path.join(binDir, 'bun-x86_64-pc-windows-msvc.exe'), (err) => {
|
||||
if (err) {
|
||||
console.log("Error Found:", err);
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
// Expect EEXIST error
|
||||
}
|
||||
console.log('Bun downloaded.')
|
||||
|
||||
console.log(`Downloading UV for ${uvPlatform}...`)
|
||||
if (platform === 'win32') {
|
||||
await download(uvUrl, path.join(tempBinDir, `uv-${uvPlatform}.zip`))
|
||||
} else {
|
||||
await download(uvUrl, path.join(tempBinDir, `uv-${uvPlatform}.tar.gz`))
|
||||
}
|
||||
await decompress(uvPath, tempBinDir)
|
||||
try {
|
||||
copySync(
|
||||
path.join(tempBinDir, `uv-${uvPlatform}`, 'uv'),
|
||||
path.join(binDir)
|
||||
)
|
||||
if (platform === 'darwin') {
|
||||
copyFile(path.join(binDir, 'uv'), path.join(binDir, 'uv-x86_64-apple-darwin'), (err) => {
|
||||
if (err) {
|
||||
console.log("Error Found:", err);
|
||||
}
|
||||
})
|
||||
copyFile(path.join(binDir, 'uv'), path.join(binDir, 'uv-aarch64-apple-darwin'), (err) => {
|
||||
if (err) {
|
||||
console.log("Error Found:", err);
|
||||
}
|
||||
})
|
||||
} else if (platform === 'linux') {
|
||||
copyFile(path.join(binDir, 'uv'), path.join(binDir, 'uv-x86_64-unknown-linux-gnu'), (err) => {
|
||||
if (err) {
|
||||
console.log("Error Found:", err);
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
// Expect EEXIST error
|
||||
}
|
||||
try {
|
||||
copySync(
|
||||
path.join(tempBinDir, 'uv.exe'),
|
||||
path.join(binDir)
|
||||
)
|
||||
if (platform === 'win32') {
|
||||
copyFile(path.join(binDir, 'uv.exe'), path.join(binDir, 'uv-x86_64-pc-windows-msvc.exe'), (err) => {
|
||||
if (err) {
|
||||
console.log("Error Found:", err);
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
// Expect EEXIST error
|
||||
}
|
||||
console.log('UV downloaded.')
|
||||
|
||||
console.log('Downloads completed.')
|
||||
}
|
||||
|
||||
// Ensure the downloads directory exists
|
||||
if (!fs.existsSync('downloads')) {
|
||||
fs.mkdirSync('downloads')
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('Error:', err)
|
||||
process.exit(1)
|
||||
})
|
||||
@ -41,6 +41,7 @@ rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "mai
|
||||
"tower",
|
||||
] }
|
||||
uuid = { version = "1.7", features = ["v4"] }
|
||||
env = "1.0.1"
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-updater = "2"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::{collections::HashMap, env, sync::Arc};
|
||||
|
||||
use rmcp::{service::RunningService, transport::TokioChildProcess, RoleClient, ServiceExt};
|
||||
use serde_json::Value;
|
||||
@ -33,9 +33,26 @@ pub async fn run_mcp_commands(
|
||||
if let Some(server_map) = mcp_servers.get("mcpServers").and_then(Value::as_object) {
|
||||
log::info!("MCP Servers: {server_map:#?}");
|
||||
|
||||
let exe_path = env::current_exe().expect("Failed to get current exe path");
|
||||
let exe_parent_path = exe_path.parent().expect("Executable must have a parent directory");
|
||||
let bin_path = exe_parent_path.to_path_buf();
|
||||
for (name, config) in server_map {
|
||||
if let Some((command, args, envs)) = extract_command_args(config) {
|
||||
let mut cmd = Command::new(command);
|
||||
let mut cmd = Command::new(command.clone());
|
||||
if command.clone() == "npx" {
|
||||
let bun_x_path = format!("{}/bun", bin_path.display());
|
||||
cmd = Command::new(bun_x_path);
|
||||
cmd.arg("x");
|
||||
}
|
||||
|
||||
if command.clone() == "uvx" {
|
||||
let bun_x_path = format!("{}/uv", bin_path.display());
|
||||
cmd = Command::new(bun_x_path);
|
||||
cmd.arg("tool run");
|
||||
cmd.arg("run");
|
||||
}
|
||||
println!("Command: {cmd:#?}");
|
||||
|
||||
args.iter().filter_map(Value::as_str).for_each(|arg| {
|
||||
cmd.arg(arg);
|
||||
});
|
||||
|
||||
@ -69,6 +69,6 @@
|
||||
"resources/themes/**/*",
|
||||
"resources/pre-install/**/*"
|
||||
],
|
||||
"externalBin": ["binaries/cortex-server"]
|
||||
"externalBin": ["binaries/cortex-server", "resources/bin/bun", "resources/bin/uv"]
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user