WinTerm MCP
by capecoma
- winterm-mcp
- 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 { spawn, exec } from 'child_process';
import * as os from 'os';
class WindowsTerminalServer {
private server: Server;
private outputBuffer: string[] = [];
constructor() {
this.server = new Server(
{
name: 'windows-terminal-mcp',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.cleanup();
process.exit(0);
});
}
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'write_to_terminal',
description: 'Write text or commands to the terminal',
inputSchema: {
type: 'object',
properties: {
command: {
type: 'string',
description: 'The text or command to write to the terminal',
},
},
required: ['command'],
},
},
{
name: 'read_terminal_output',
description: 'Read the output from the terminal',
inputSchema: {
type: 'object',
properties: {
linesOfOutput: {
type: 'number',
description: 'Number of lines of output to read',
},
},
required: ['linesOfOutput'],
},
},
{
name: 'send_control_character',
description: 'Send a control character to the terminal',
inputSchema: {
type: 'object',
properties: {
letter: {
type: 'string',
description: 'The letter corresponding to the control character (e.g., "C" for Ctrl+C)',
},
},
required: ['letter'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'write_to_terminal': {
const { command } = request.params.arguments as { command: string };
return new Promise((resolve, reject) => {
const shell = os.platform() === 'win32' ? 'cmd.exe' : 'bash';
const shellArgs = os.platform() === 'win32' ? ['/c', command] : ['-c', command];
const proc = spawn(shell, shellArgs);
let output = '';
proc.stdout.on('data', (data) => {
output += data.toString();
this.outputBuffer.push(...data.toString().split('\n'));
});
proc.stderr.on('data', (data) => {
output += data.toString();
this.outputBuffer.push(...data.toString().split('\n'));
});
proc.on('close', (code) => {
resolve({
content: [
{
type: 'text',
text: `Command executed with exit code ${code}. Output:\n${output}`,
},
],
});
});
proc.on('error', (err) => {
reject(new McpError(ErrorCode.InternalError, err.message));
});
});
}
case 'read_terminal_output': {
const { linesOfOutput } = request.params.arguments as { linesOfOutput: number };
const output = this.outputBuffer.slice(-linesOfOutput).join('\n');
return {
content: [
{
type: 'text',
text: output,
},
],
};
}
case 'send_control_character': {
const { letter } = request.params.arguments as { letter: string };
// On Windows, we'll use taskkill to simulate Ctrl+C
if (letter.toUpperCase() === 'C' && os.platform() === 'win32') {
exec('taskkill /im node.exe /f');
}
return {
content: [
{
type: 'text',
text: `Sent Ctrl+${letter.toUpperCase()} signal`,
},
],
};
}
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
});
}
private async cleanup() {
await this.server.close();
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Windows Terminal MCP server running on stdio');
}
}
const server = new WindowsTerminalServer();
server.run().catch(console.error);