searxng-mcp-server
by maccam912
- src
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import { readFileSync } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import { CommandExecutor } from './command-executor.js';
import { InvalidConfirmationError } from './errors.js';
import { CommandResponse, PendingConfirmation } from './types.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const pkg = JSON.parse(
readFileSync(join(__dirname, '..', 'package.json'), 'utf8'),
);
const { name, version } = pkg;
class WslServer {
private server: Server;
private command_executor: CommandExecutor;
private pending_confirmations: Map<string, PendingConfirmation>;
constructor() {
this.server = new Server(
{ name, version },
{
capabilities: {
tools: {},
},
},
);
this.command_executor = new CommandExecutor();
this.pending_confirmations = new Map();
this.setup_tool_handlers();
}
private format_output(result: CommandResponse): string {
return [
`Command: ${result.command}`,
result.working_dir
? `Working Directory: ${result.working_dir}`
: null,
`Exit Code: ${result.exit_code}`,
result.stdout.trim()
? `Output:\n${result.stdout.trim()}`
: 'No output',
result.stderr.trim()
? `Errors:\n${result.stderr.trim()}`
: 'No errors',
result.error ? `Error: ${result.error}` : null,
]
.filter(Boolean)
.join('\n');
}
private async execute_wsl_command(
command: string,
working_dir?: string,
timeout?: number,
): Promise<CommandResponse> {
return new Promise((resolve, reject) => {
const requires_confirmation =
this.command_executor.is_dangerous_command(command);
if (requires_confirmation) {
// Generate a unique confirmation ID
const confirmation_id = Math.random()
.toString(36)
.substring(7);
this.pending_confirmations.set(confirmation_id, {
command,
working_dir,
timeout,
resolve,
reject,
});
// Return early with confirmation request
resolve({
stdout: '',
stderr: `Command "${command}" requires confirmation. Use confirm_command with ID: ${confirmation_id}`,
exit_code: null,
command,
requires_confirmation: true,
});
return;
}
this.command_executor
.execute_command(command, working_dir, timeout)
.then(resolve)
.catch(reject);
});
}
private setup_tool_handlers() {
this.server.setRequestHandler(
ListToolsRequestSchema,
async () => ({
tools: [
{
name: 'execute_command',
description: 'Execute a command in WSL',
inputSchema: {
type: 'object',
properties: {
command: {
type: 'string',
description: 'Command to execute',
},
working_dir: {
type: 'string',
description:
'Working directory for command execution',
},
timeout: {
type: 'number',
description: 'Timeout in milliseconds',
},
},
required: ['command'],
},
},
{
name: 'confirm_command',
description: 'Confirm execution of a dangerous command',
inputSchema: {
type: 'object',
properties: {
confirmation_id: {
type: 'string',
description:
'Confirmation ID received from execute_command',
},
confirm: {
type: 'boolean',
description:
'Whether to proceed with the command execution',
},
},
required: ['confirmation_id', 'confirm'],
},
},
],
}),
);
this.server.setRequestHandler(
CallToolRequestSchema,
async (request) => {
try {
switch (request.params.name) {
case 'execute_command': {
const { command, working_dir, timeout } = request.params
.arguments as {
command: string;
working_dir?: string;
timeout?: number;
};
const result = await this.execute_wsl_command(
command,
working_dir,
timeout,
);
if (result.requires_confirmation) {
return {
content: [
{
type: 'text',
text: result.stderr,
},
],
};
}
return {
content: [
{
type: 'text',
text: this.format_output(result),
},
],
};
}
case 'confirm_command': {
const { confirmation_id, confirm } = request.params
.arguments as {
confirmation_id: string;
confirm: boolean;
};
const pending =
this.pending_confirmations.get(confirmation_id);
if (!pending) {
throw new InvalidConfirmationError(confirmation_id);
}
this.pending_confirmations.delete(confirmation_id);
if (!confirm) {
return {
content: [
{
type: 'text',
text: 'Command execution cancelled.',
},
],
};
}
const result = await this.command_executor.execute_command(
pending.command,
pending.working_dir,
pending.timeout,
);
return {
content: [
{
type: 'text',
text: this.format_output(result),
},
],
};
}
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`,
);
}
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error executing command: ${
error instanceof Error
? error.message
: String(error)
}`,
},
],
isError: true,
};
}
},
);
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('WSL MCP server running on stdio');
}
}
const server = new WslServer();
server.run().catch(console.error);