desktop-commander-marcus-ver
Version:
MCP server for terminal operations and file editing
159 lines (158 loc) • 6.07 kB
JavaScript
import { terminalManager } from '../terminal-manager.js';
import { commandManager } from '../command-manager.js';
import { ExecuteCommandArgsSchema, ReadOutputArgsSchema, ForceTerminateArgsSchema } from './schemas.js';
import { capture } from "../utils/capture.js";
export async function executeCommand(args) {
const parsed = ExecuteCommandArgsSchema.safeParse(args);
if (!parsed.success) {
capture('server_execute_command_failed');
return {
content: [{ type: "text", text: `Error: Invalid arguments for execute_command: ${parsed.error}` }],
isError: true,
};
}
try {
// Extract all commands for analytics while ensuring execution continues even if parsing fails
const commands = commandManager.extractCommands(parsed.data.command).join(', ');
capture('server_execute_command', {
command: commandManager.getBaseCommand(parsed.data.command), // Keep original for backward compatibility
commands: commands // Add the array of all identified commands
});
}
catch (error) {
// If anything goes wrong with command extraction, just continue with execution
capture('server_execute_command', {
command: commandManager.getBaseCommand(parsed.data.command)
});
}
// Command validation is now async
const isAllowed = await commandManager.validateCommand(parsed.data.command);
if (!isAllowed) {
return {
content: [{ type: "text", text: `Error: Command not allowed: ${parsed.data.command}` }],
isError: true,
};
}
const result = await terminalManager.executeCommand(parsed.data.command, parsed.data.timeout_ms, parsed.data.shell);
// Check for error condition (pid = -1)
if (result.pid === -1) {
return {
content: [{ type: "text", text: result.output }],
isError: true,
};
}
return {
content: [{
type: "text",
text: `Command started with PID ${result.pid}\nInitial output:\n${result.output}${result.isBlocked ? '\nCommand is still running. Use read_output to get more output.' : ''}`
}],
};
}
export async function readOutput(args) {
const parsed = ReadOutputArgsSchema.safeParse(args);
if (!parsed.success) {
return {
content: [{ type: "text", text: `Error: Invalid arguments for read_output: ${parsed.error}` }],
isError: true,
};
}
const { pid, timeout_ms = 5000 } = parsed.data;
// Check if the process exists
const session = terminalManager.getSession(pid);
if (!session) {
return {
content: [{ type: "text", text: `No session found for PID ${pid}` }],
isError: true,
};
}
// Wait for output with timeout
let output = "";
let timeoutReached = false;
try {
// Create a promise that resolves when new output is available or when timeout is reached
const outputPromise = new Promise((resolve) => {
// Check for initial output
const initialOutput = terminalManager.getNewOutput(pid);
if (initialOutput && initialOutput.length > 0) {
resolve(initialOutput);
return;
}
let resolved = false;
let interval = null;
let timeout = null;
const cleanup = () => {
if (interval) {
clearInterval(interval);
interval = null;
}
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
};
const resolveOnce = (value, isTimeout = false) => {
if (resolved)
return;
resolved = true;
cleanup();
if (isTimeout)
timeoutReached = true;
resolve(value);
};
// Setup an interval to poll for output
interval = setInterval(() => {
const newOutput = terminalManager.getNewOutput(pid);
if (newOutput && newOutput.length > 0) {
resolveOnce(newOutput);
}
}, 300); // Check every 300ms
// Set a timeout to stop waiting
timeout = setTimeout(() => {
const finalOutput = terminalManager.getNewOutput(pid) || "";
resolveOnce(finalOutput, true);
}, timeout_ms);
});
output = await outputPromise;
}
catch (error) {
return {
content: [{ type: "text", text: `Error reading output: ${error}` }],
isError: true,
};
}
return {
content: [{
type: "text",
text: output || 'No new output available' + (timeoutReached ? ' (timeout reached)' : '')
}],
};
}
export async function forceTerminate(args) {
const parsed = ForceTerminateArgsSchema.safeParse(args);
if (!parsed.success) {
return {
content: [{ type: "text", text: `Error: Invalid arguments for force_terminate: ${parsed.error}` }],
isError: true,
};
}
const success = terminalManager.forceTerminate(parsed.data.pid);
return {
content: [{
type: "text",
text: success
? `Successfully initiated termination of session ${parsed.data.pid}`
: `No active session found for PID ${parsed.data.pid}`
}],
};
}
export async function listSessions() {
const sessions = terminalManager.listActiveSessions();
return {
content: [{
type: "text",
text: sessions.length === 0
? 'No active sessions'
: sessions.map(s => `PID: ${s.pid}, Blocked: ${s.isBlocked}, Runtime: ${Math.round(s.runtime / 1000)}s`).join('\n')
}],
};
}