# Directory Structure ``` ├── .gitignore ├── LICENSE ├── package.json ├── README.md ├── src │ └── index.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Dependencies 2 | node_modules/ 3 | 4 | # Build output 5 | build/ 6 | dist/ 7 | 8 | # Environment variables 9 | .env 10 | .env.local 11 | .env.*.local 12 | 13 | # IDE files 14 | .vscode/ 15 | .idea/ 16 | *.iml 17 | 18 | # Logs 19 | *.log 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | 24 | # System files 25 | .DS_Store 26 | Thumbs.db 27 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # WinTerm MCP 2 | 3 | A Model Context Protocol server that provides programmatic access to the Windows terminal. This server enables AI models to interact with the Windows command line interface through a set of standardized tools. 4 | 5 | ## Features 6 | 7 | - **Write to Terminal**: Execute commands or write text to the Windows terminal 8 | - **Read Terminal Output**: Retrieve output from previously executed commands 9 | - **Send Control Characters**: Send control signals (e.g., Ctrl+C) to the terminal 10 | - **Windows-Native**: Built specifically for Windows command line interaction 11 | 12 | ## Installation 13 | 14 | 1. **Clone the Repository**: 15 | ```bash 16 | git clone https://github.com/capecoma/winterm-mcp.git 17 | cd winterm-mcp 18 | ``` 19 | 20 | 2. **Install Dependencies**: 21 | ```bash 22 | npm install 23 | ``` 24 | 25 | 3. **Build the Project**: 26 | ```bash 27 | npm run build 28 | ``` 29 | 30 | 4. **Configure Claude Desktop**: 31 | 32 | Add the server config to `%APPDATA%/Claude/claude_desktop_config.json`: 33 | 34 | ```json 35 | { 36 | "mcpServers": { 37 | "github.com/capecoma/winterm-mcp": { 38 | "command": "node", 39 | "args": ["path/to/build/index.js"], 40 | "disabled": false, 41 | "autoApprove": [] 42 | } 43 | } 44 | } 45 | ``` 46 | 47 | Note: Replace "path/to/build/index.js" with the actual path to your built index.js file. 48 | 49 | ## Available Tools 50 | 51 | ### write_to_terminal 52 | Writes text or commands to the terminal. 53 | ```json 54 | { 55 | "command": "echo Hello, World!" 56 | } 57 | ``` 58 | 59 | ### read_terminal_output 60 | Reads the specified number of lines from terminal output. 61 | ```json 62 | { 63 | "linesOfOutput": 5 64 | } 65 | ``` 66 | 67 | ### send_control_character 68 | Sends a control character to the terminal (e.g., Ctrl+C). 69 | ```json 70 | { 71 | "letter": "C" 72 | } 73 | ``` 74 | 75 | ## Development 76 | 77 | For development with auto-rebuild: 78 | ```bash 79 | npm run dev 80 | ``` 81 | 82 | ## License 83 | 84 | MIT License - see [LICENSE](LICENSE) file. 85 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "moduleResolution": "node", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "winterm-mcp", 3 | "version": "1.0.0", 4 | "description": "Windows Terminal MCP Server - A Model Context Protocol server for Windows terminal interaction", 5 | "type": "module", 6 | "main": "build/index.js", 7 | "scripts": { 8 | "build": "npx tsc", 9 | "start": "node build/index.js", 10 | "dev": "npx tsc -w" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/capecoma/winterm-mcp.git" 15 | }, 16 | "keywords": [ 17 | "mcp", 18 | "terminal", 19 | "windows", 20 | "claude", 21 | "automation" 22 | ], 23 | "author": "capecoma", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/capecoma/winterm-mcp/issues" 27 | }, 28 | "homepage": "https://github.com/capecoma/winterm-mcp#readme", 29 | "dependencies": { 30 | "@modelcontextprotocol/sdk": "latest", 31 | "@types/node": "^20.0.0", 32 | "typescript": "^5.0.0" 33 | } 34 | } 35 | ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | #!/usr/bin/env node 2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; 3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 4 | import { 5 | CallToolRequestSchema, 6 | ErrorCode, 7 | ListToolsRequestSchema, 8 | McpError, 9 | } from '@modelcontextprotocol/sdk/types.js'; 10 | import { spawn, exec } from 'child_process'; 11 | import * as os from 'os'; 12 | 13 | class WindowsTerminalServer { 14 | private server: Server; 15 | private outputBuffer: string[] = []; 16 | 17 | constructor() { 18 | this.server = new Server( 19 | { 20 | name: 'windows-terminal-mcp', 21 | version: '1.0.0', 22 | }, 23 | { 24 | capabilities: { 25 | tools: {}, 26 | }, 27 | } 28 | ); 29 | 30 | this.setupToolHandlers(); 31 | 32 | this.server.onerror = (error) => console.error('[MCP Error]', error); 33 | process.on('SIGINT', async () => { 34 | await this.cleanup(); 35 | process.exit(0); 36 | }); 37 | } 38 | 39 | private setupToolHandlers() { 40 | this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ 41 | tools: [ 42 | { 43 | name: 'write_to_terminal', 44 | description: 'Write text or commands to the terminal', 45 | inputSchema: { 46 | type: 'object', 47 | properties: { 48 | command: { 49 | type: 'string', 50 | description: 'The text or command to write to the terminal', 51 | }, 52 | }, 53 | required: ['command'], 54 | }, 55 | }, 56 | { 57 | name: 'read_terminal_output', 58 | description: 'Read the output from the terminal', 59 | inputSchema: { 60 | type: 'object', 61 | properties: { 62 | linesOfOutput: { 63 | type: 'number', 64 | description: 'Number of lines of output to read', 65 | }, 66 | }, 67 | required: ['linesOfOutput'], 68 | }, 69 | }, 70 | { 71 | name: 'send_control_character', 72 | description: 'Send a control character to the terminal', 73 | inputSchema: { 74 | type: 'object', 75 | properties: { 76 | letter: { 77 | type: 'string', 78 | description: 'The letter corresponding to the control character (e.g., "C" for Ctrl+C)', 79 | }, 80 | }, 81 | required: ['letter'], 82 | }, 83 | }, 84 | ], 85 | })); 86 | 87 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => { 88 | switch (request.params.name) { 89 | case 'write_to_terminal': { 90 | const { command } = request.params.arguments as { command: string }; 91 | return new Promise((resolve, reject) => { 92 | const shell = os.platform() === 'win32' ? 'cmd.exe' : 'bash'; 93 | const shellArgs = os.platform() === 'win32' ? ['/c', command] : ['-c', command]; 94 | 95 | const proc = spawn(shell, shellArgs); 96 | let output = ''; 97 | 98 | proc.stdout.on('data', (data) => { 99 | output += data.toString(); 100 | this.outputBuffer.push(...data.toString().split('\n')); 101 | }); 102 | 103 | proc.stderr.on('data', (data) => { 104 | output += data.toString(); 105 | this.outputBuffer.push(...data.toString().split('\n')); 106 | }); 107 | 108 | proc.on('close', (code) => { 109 | resolve({ 110 | content: [ 111 | { 112 | type: 'text', 113 | text: `Command executed with exit code ${code}. Output:\n${output}`, 114 | }, 115 | ], 116 | }); 117 | }); 118 | 119 | proc.on('error', (err) => { 120 | reject(new McpError(ErrorCode.InternalError, err.message)); 121 | }); 122 | }); 123 | } 124 | 125 | case 'read_terminal_output': { 126 | const { linesOfOutput } = request.params.arguments as { linesOfOutput: number }; 127 | const output = this.outputBuffer.slice(-linesOfOutput).join('\n'); 128 | return { 129 | content: [ 130 | { 131 | type: 'text', 132 | text: output, 133 | }, 134 | ], 135 | }; 136 | } 137 | 138 | case 'send_control_character': { 139 | const { letter } = request.params.arguments as { letter: string }; 140 | // On Windows, we'll use taskkill to simulate Ctrl+C 141 | if (letter.toUpperCase() === 'C' && os.platform() === 'win32') { 142 | exec('taskkill /im node.exe /f'); 143 | } 144 | return { 145 | content: [ 146 | { 147 | type: 'text', 148 | text: `Sent Ctrl+${letter.toUpperCase()} signal`, 149 | }, 150 | ], 151 | }; 152 | } 153 | 154 | default: 155 | throw new McpError( 156 | ErrorCode.MethodNotFound, 157 | `Unknown tool: ${request.params.name}` 158 | ); 159 | } 160 | }); 161 | } 162 | 163 | private async cleanup() { 164 | await this.server.close(); 165 | } 166 | 167 | async run() { 168 | const transport = new StdioServerTransport(); 169 | await this.server.connect(transport); 170 | console.error('Windows Terminal MCP server running on stdio'); 171 | } 172 | } 173 | 174 | const server = new WindowsTerminalServer(); 175 | server.run().catch(console.error); 176 | ```