#
tokens: 1909/50000 8/8 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .editorconfig
├── .gitignore
├── .prettierrc
├── bun.lock
├── cli.js
├── index.ts
├── package.json
├── README.md
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------

```
1 | {
2 |   "semi": false
3 | }
4 | 
```

--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------

```
 1 | # EditorConfig is awesome: https://EditorConfig.org
 2 | 
 3 | # top-most EditorConfig file
 4 | root = true
 5 | 
 6 | [*]
 7 | indent_style = space
 8 | indent_size = 2
 9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = false
12 | insert_final_newline = false
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # dependencies (bun install)
 2 | node_modules
 3 | 
 4 | # output
 5 | out
 6 | dist
 7 | *.tgz
 8 | 
 9 | # code coverage
10 | coverage
11 | *.lcov
12 | 
13 | # logs
14 | logs
15 | _.log
16 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
17 | 
18 | # dotenv environment variable files
19 | .env
20 | .env.development.local
21 | .env.test.local
22 | .env.production.local
23 | .env.local
24 | 
25 | # caches
26 | .eslintcache
27 | .cache
28 | *.tsbuildinfo
29 | 
30 | # IntelliJ based IDEs
31 | .idea
32 | 
33 | # Finder (MacOS) folder config
34 | .DS_Store
35 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # shell-command-mcp
 2 | 
 3 | MCP server for executing shell commands.
 4 | 
 5 | This project is sponsored by [ChatWise](https://chatwise.app), an all-in-one LLM chatbot with first-class MCP support.
 6 | 
 7 | ## Usage
 8 | 
 9 | ### Configure manually
10 | 
11 | ```bash
12 | # stdio server
13 | npx -y shell-command-mcp
14 | ```
15 | 
16 | ### JSON config
17 | 
18 | ```json
19 | {
20 |   "mcpServers": {
21 |     "shell-command": {
22 |       "command": "npx",
23 |       "args": ["-y", "shell-command-mcp"],
24 |       "env": {
25 |         "ALLOWED_COMMANDS": "cat,ls,echo"
26 |       }
27 |     }
28 |   }
29 | }
30 | ```
31 | 
32 | ### Allowed commands
33 | 
34 | Use `ALLOWED_COMMANDS` environment variable to explictly allow the commands that this server can run, separate each command by `,`. You can use `*` to allow any command, but this is potentially dangerous.
35 | 
36 | ## License
37 | 
38 | MIT.
39 | 
```

--------------------------------------------------------------------------------
/cli.js:
--------------------------------------------------------------------------------

```javascript
1 | #!/usr/bin/env node
2 | import "./dist/index.js"
3 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "shell-command-mcp",
 3 |   "description": "MCP server for running shell commands",
 4 |   "type": "module",
 5 |   "version": "0.0.1",
 6 |   "files": [
 7 |     "dist",
 8 |     "/cli.js"
 9 |   ],
10 |   "bin": "./cli.js",
11 |   "scripts": {
12 |     "build": "bun build ./index.ts --packages external --outdir dist",
13 |     "prepublishOnly": "npm run build"
14 |   },
15 |   "devDependencies": {
16 |     "@types/bun": "latest",
17 |     "@types/js-yaml": "^4.0.9",
18 |     "typescript": "^5"
19 |   },
20 |   "dependencies": {
21 |     "@modelcontextprotocol/sdk": "^1.8.0",
22 |     "args-tokenizer": "^0.3.0",
23 |     "js-yaml": "^4.1.0",
24 |     "tinyexec": "^1.0.1",
25 |     "zod": "^3.24.2"
26 |   }
27 | }
28 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     // Enable latest features
 4 |     "lib": ["ESNext", "DOM"],
 5 |     "target": "ESNext",
 6 |     "module": "ESNext",
 7 |     "moduleDetection": "force",
 8 |     "jsx": "react-jsx",
 9 |     "allowJs": true,
10 | 
11 |     // Bundler mode
12 |     "moduleResolution": "bundler",
13 |     "allowImportingTsExtensions": true,
14 |     "verbatimModuleSyntax": true,
15 |     "noEmit": true,
16 | 
17 |     // Best practices
18 |     "strict": true,
19 |     "skipLibCheck": true,
20 |     "noFallthroughCasesInSwitch": true,
21 | 
22 |     // Some stricter flags (disabled by default)
23 |     "noUnusedLocals": false,
24 |     "noUnusedParameters": false,
25 |     "noPropertyAccessFromIndexSignature": false
26 |   }
27 | }
28 | 
```

--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
 2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
 3 | import { z } from "zod"
 4 | import { dump } from "js-yaml"
 5 | import { version } from "./package.json"
 6 | import { x } from "tinyexec"
 7 | import { tokenizeArgs } from "args-tokenizer"
 8 | 
 9 | const allowedCommands =
10 |   process.env.ALLOWED_COMMANDS?.split(",").map((cmd) => cmd.trim()) || []
11 | 
12 | const server = new McpServer(
13 |   {
14 |     name: "shell-command-mcp",
15 |     version,
16 |   },
17 |   {
18 |     capabilities: {
19 |       logging: {},
20 |       tools: {},
21 |     },
22 |   }
23 | )
24 | 
25 | server.tool(
26 |   "execute_command",
27 |   "Execute a shell command",
28 |   {
29 |     command: z.string().describe("The shell command to execute"),
30 |   },
31 |   async (args) => {
32 |     const [bin, ...commandArgs] = tokenizeArgs(args.command)
33 | 
34 |     try {
35 |       if (!allowedCommands.includes("*") && !allowedCommands.includes(bin)) {
36 |         throw new Error(
37 |           `Command "${bin}" is not allowed, allowed commands: ${
38 |             allowedCommands.length > 0 ? allowedCommands.join(", ") : "(none)"
39 |           }`
40 |         )
41 |       }
42 | 
43 |       const result = await x(bin, commandArgs)
44 | 
45 |       return {
46 |         content: [
47 |           {
48 |             type: "text",
49 |             text: dump({
50 |               exit_code: result.exitCode,
51 |               stdout: result.stdout,
52 |               stderr: result.stderr,
53 |             }),
54 |           },
55 |         ],
56 |       }
57 |     } catch (error) {
58 |       return {
59 |         content: [
60 |           {
61 |             type: "text",
62 |             text: `Error executing command: ${
63 |               error instanceof Error ? error.message : String(error)
64 |             }`,
65 |           },
66 |         ],
67 |         isError: true,
68 |       }
69 |     }
70 |   }
71 | )
72 | 
73 | const transport = new StdioServerTransport()
74 | await server.connect(transport)
75 | 
```