@iflow-mcp/shell-command-mcp 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -80
- package/cli.js +2 -0
- package/dist/index.js +56 -0
- package/package.json +18 -34
- package/.dockleignore +0 -15
- package/.eslintrc.json +0 -23
- package/.github/workflows/docker-build.yaml +0 -102
- package/.prettierignore +0 -2
- package/.prettierrc +0 -7
- package/.prompt.yaml +0 -542
- package/Dockerfile +0 -120
- package/LICENSE +0 -21
- package/Makefile +0 -8
- package/build/execute-bash-script-async.js +0 -255
- package/build/execute-bash-script-sync.js +0 -111
- package/build/index.js +0 -26
- package/client-sequence-example.json +0 -9
- package/docker-compose.yaml +0 -17
- package/entrypoint.sh +0 -31
- package/src/execute-bash-script-async.ts +0 -300
- package/src/execute-bash-script-sync.ts +0 -141
- package/src/index.ts +0 -28
- package/tsconfig.json +0 -17
package/README.md
CHANGED
|
@@ -1,96 +1,38 @@
|
|
|
1
|
-
#
|
|
1
|
+
# shell-command-mcp
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
I have created [ai-agent-workspace](https://github.com/kaznak/container-images/tree/main/ai-agent-workspace) as a container to run Claude Code.
|
|
5
|
-
Please use it as needed.
|
|
3
|
+
MCP server for executing shell commands.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
This project is sponsored by [ChatWise](https://chatwise.app), an all-in-one LLM chatbot with first-class MCP support.
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
## Usage
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
### Configure manually
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- complete: notify when the command is completed
|
|
17
|
-
- line: notify on each line of output
|
|
18
|
-
- chunk: notify on each chunk of output
|
|
19
|
-
- character: notify on each character of output
|
|
20
|
-
- Kubernetes tools included: kubectl, helm, kustomize, hemfile
|
|
21
|
-
- Isolated Docker container environment with non-root user
|
|
22
|
-
- host-container userid/groupid mapping implemented. this allows the container to run as the same user as the host, ensuring that files created by the container have the same ownership and permissions as those created by the host.
|
|
23
|
-
- mount a host directory to the container /home/mcp directory for persistence. it become the home directory the AI works in.
|
|
24
|
-
- if the host directory is empty, the initial files will be copied form the backup in the container.
|
|
25
|
-
|
|
26
|
-
## Design Philosophy
|
|
27
|
-
|
|
28
|
-
This MCP server provides AI with a workspace similar to that of a human.
|
|
29
|
-
Authorization is limited not by MCP functions, but by container isolation and external authorization restrictions.
|
|
30
|
-
|
|
31
|
-
It provides more general tools such as shell script execution, so that they can be used without specialized knowledge of tool use.
|
|
32
|
-
|
|
33
|
-
The server implementation is kept as simple as possible to facilitate code auditing.
|
|
34
|
-
|
|
35
|
-
## Getting Started
|
|
36
|
-
|
|
37
|
-
### Prerequisites
|
|
38
|
-
|
|
39
|
-
- Docker
|
|
40
|
-
|
|
41
|
-
### Usage with Claude for Desktop
|
|
42
|
-
|
|
43
|
-
Add the following configuration to your Claude for Desktop configuration file.
|
|
44
|
-
|
|
45
|
-
MacOS:
|
|
46
|
-
|
|
47
|
-
```json
|
|
48
|
-
"shell-command": {
|
|
49
|
-
"command": "docker",
|
|
50
|
-
"args": [
|
|
51
|
-
"run",
|
|
52
|
-
"--rm",
|
|
53
|
-
"-i",
|
|
54
|
-
"--mount",
|
|
55
|
-
"type=bind,src=/Users/user-name/MCPHome,dst=/home/mcp",
|
|
56
|
-
"ghcr.io/kaznak/shell-command-mcp:latest"
|
|
57
|
-
]
|
|
58
|
-
}
|
|
11
|
+
```bash
|
|
12
|
+
# stdio server
|
|
13
|
+
npx -y shell-command-mcp
|
|
59
14
|
```
|
|
60
15
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
Windows:
|
|
16
|
+
### JSON config
|
|
64
17
|
|
|
65
18
|
```json
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
+
}
|
|
76
29
|
}
|
|
77
30
|
```
|
|
78
31
|
|
|
79
|
-
###
|
|
80
|
-
|
|
81
|
-
To Operate the files in the mounted directory.
|
|
82
|
-
|
|
83
|
-
## Available MCP Tools
|
|
84
|
-
|
|
85
|
-
- [execute-bash-script-sync](./src/execute-bash-script-sync.ts)
|
|
86
|
-
- [execute-bash-script-async](./src/execute-bash-script-async.ts)
|
|
87
|
-
|
|
88
|
-
## Security Considerations
|
|
32
|
+
### Allowed commands
|
|
89
33
|
|
|
90
|
-
|
|
91
|
-
- The container does not have access to the host Docker daemon
|
|
92
|
-
- User workspace is mounted from the host for persistence
|
|
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.
|
|
93
35
|
|
|
94
36
|
## License
|
|
95
37
|
|
|
96
|
-
MIT
|
|
38
|
+
MIT.
|
package/cli.js
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// index.ts
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { dump } from "js-yaml";
|
|
6
|
+
// package.json
|
|
7
|
+
var version = "1.0.1";
|
|
8
|
+
|
|
9
|
+
// index.ts
|
|
10
|
+
import { x } from "tinyexec";
|
|
11
|
+
import { tokenizeArgs } from "args-tokenizer";
|
|
12
|
+
var allowedCommands = process.env.ALLOWED_COMMANDS?.split(",").map((cmd) => cmd.trim()) || [];
|
|
13
|
+
var server = new McpServer({
|
|
14
|
+
name: "shell-command-mcp",
|
|
15
|
+
version
|
|
16
|
+
}, {
|
|
17
|
+
capabilities: {
|
|
18
|
+
logging: {},
|
|
19
|
+
tools: {}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
server.tool("execute_command", "Execute a shell command", {
|
|
23
|
+
command: z.string().describe("The shell command to execute")
|
|
24
|
+
}, async (args) => {
|
|
25
|
+
const [bin, ...commandArgs] = tokenizeArgs(args.command);
|
|
26
|
+
try {
|
|
27
|
+
if (!allowedCommands.includes("*") && !allowedCommands.includes(bin)) {
|
|
28
|
+
throw new Error(`Command "${bin}" is not allowed, allowed commands: ${allowedCommands.length > 0 ? allowedCommands.join(", ") : "(none)"}`);
|
|
29
|
+
}
|
|
30
|
+
const result = await x(bin, commandArgs);
|
|
31
|
+
return {
|
|
32
|
+
content: [
|
|
33
|
+
{
|
|
34
|
+
type: "text",
|
|
35
|
+
text: dump({
|
|
36
|
+
exit_code: result.exitCode,
|
|
37
|
+
stdout: result.stdout,
|
|
38
|
+
stderr: result.stderr
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
};
|
|
43
|
+
} catch (error) {
|
|
44
|
+
return {
|
|
45
|
+
content: [
|
|
46
|
+
{
|
|
47
|
+
type: "text",
|
|
48
|
+
text: `Error executing command: ${error instanceof Error ? error.message : String(error)}`
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
isError: true
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
var transport = new StdioServerTransport;
|
|
56
|
+
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,43 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iflow-mcp/shell-command-mcp",
|
|
3
|
-
"
|
|
4
|
-
"description": "MCP server for executing shell commands in a Docker container",
|
|
5
|
-
"main": "build/index.js",
|
|
3
|
+
"description": "MCP server for running shell commands",
|
|
6
4
|
"type": "module",
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
"version": "1.0.1",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"/cli.js"
|
|
9
|
+
],
|
|
10
|
+
"bin": "./cli.js",
|
|
10
11
|
"scripts": {
|
|
11
|
-
"build": "
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
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"
|
|
18
19
|
},
|
|
19
|
-
"keywords": [
|
|
20
|
-
"mcp",
|
|
21
|
-
"shell",
|
|
22
|
-
"docker",
|
|
23
|
-
"kubernetes"
|
|
24
|
-
],
|
|
25
|
-
"author": "",
|
|
26
|
-
"license": "MIT",
|
|
27
20
|
"dependencies": {
|
|
28
21
|
"@modelcontextprotocol/sdk": "^1.8.0",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"@types/node": "^20.10.3",
|
|
34
|
-
"@typescript-eslint/eslint-plugin": "^6.13.2",
|
|
35
|
-
"@typescript-eslint/parser": "^6.13.2",
|
|
36
|
-
"eslint": "^8.55.0",
|
|
37
|
-
"eslint-config-prettier": "^9.1.0",
|
|
38
|
-
"eslint-plugin-prettier": "^5.0.1",
|
|
39
|
-
"prettier": "^3.1.0",
|
|
40
|
-
"tsx": "^4.6.2",
|
|
41
|
-
"typescript": "~5.3.3"
|
|
22
|
+
"args-tokenizer": "^0.3.0",
|
|
23
|
+
"js-yaml": "^4.1.0",
|
|
24
|
+
"tinyexec": "^1.0.1",
|
|
25
|
+
"zod": "^3.24.2"
|
|
42
26
|
}
|
|
43
27
|
}
|
package/.dockleignore
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# Create a user for the container
|
|
2
|
-
## Run as root to map host user to container user.
|
|
3
|
-
CIS-DI-0001
|
|
4
|
-
|
|
5
|
-
# TODO
|
|
6
|
-
# Avoid sudo command
|
|
7
|
-
DKL-DI-0001
|
|
8
|
-
## Enable Content trust for Docker
|
|
9
|
-
CIS-DI-0005
|
|
10
|
-
## Add HEALTHCHECK instruction to the container image
|
|
11
|
-
CIS-DI-0006
|
|
12
|
-
## Confirm safety of setuid/setgid files
|
|
13
|
-
CIS-DI-0008
|
|
14
|
-
## Only put necessary files
|
|
15
|
-
DKL-LI-0003
|
package/.eslintrc.json
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"parser": "@typescript-eslint/parser",
|
|
3
|
-
"extends": [
|
|
4
|
-
"eslint:recommended",
|
|
5
|
-
"plugin:@typescript-eslint/recommended",
|
|
6
|
-
"plugin:prettier/recommended"
|
|
7
|
-
],
|
|
8
|
-
"plugins": ["@typescript-eslint", "prettier"],
|
|
9
|
-
"env": {
|
|
10
|
-
"node": true,
|
|
11
|
-
"es6": true
|
|
12
|
-
},
|
|
13
|
-
"parserOptions": {
|
|
14
|
-
"ecmaVersion": 2022,
|
|
15
|
-
"sourceType": "module"
|
|
16
|
-
},
|
|
17
|
-
"rules": {
|
|
18
|
-
"prettier/prettier": "error",
|
|
19
|
-
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
|
|
20
|
-
"@typescript-eslint/explicit-function-return-type": "off",
|
|
21
|
-
"@typescript-eslint/explicit-module-boundary-types": "off"
|
|
22
|
-
}
|
|
23
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
name: Build and Push Docker Image
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
release:
|
|
5
|
-
types: [published]
|
|
6
|
-
push:
|
|
7
|
-
branches:
|
|
8
|
-
- main
|
|
9
|
-
workflow_dispatch:
|
|
10
|
-
inputs:
|
|
11
|
-
tag:
|
|
12
|
-
description: 'Git tag to build from'
|
|
13
|
-
required: false
|
|
14
|
-
default: ''
|
|
15
|
-
|
|
16
|
-
env:
|
|
17
|
-
REGISTRY: ghcr.io
|
|
18
|
-
IMAGE_NAME: ${{ github.repository }}
|
|
19
|
-
|
|
20
|
-
jobs:
|
|
21
|
-
build-and-push:
|
|
22
|
-
runs-on: ubuntu-latest
|
|
23
|
-
permissions:
|
|
24
|
-
contents: read
|
|
25
|
-
packages: write
|
|
26
|
-
security-events: write
|
|
27
|
-
|
|
28
|
-
steps:
|
|
29
|
-
- name: Get tag to build
|
|
30
|
-
id: get-tag
|
|
31
|
-
run: |
|
|
32
|
-
if [ "${{ github.event_name }}" = "release" ]; then
|
|
33
|
-
echo "tag=${{ github.event.release.tag_name }}"
|
|
34
|
-
elif [ -n "${{ github.event.inputs.tag }}" ]; then
|
|
35
|
-
echo "tag=${{ github.event.inputs.tag }}"
|
|
36
|
-
elif [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
|
37
|
-
echo "tag=main"
|
|
38
|
-
else
|
|
39
|
-
# Get latest release tag if no tag is specified
|
|
40
|
-
LATEST_TAG=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r .tag_name)
|
|
41
|
-
echo "tag=${LATEST_TAG}"
|
|
42
|
-
fi \
|
|
43
|
-
| tee -a $GITHUB_OUTPUT
|
|
44
|
-
|
|
45
|
-
- name: Checkout repository
|
|
46
|
-
uses: actions/checkout@v4
|
|
47
|
-
with:
|
|
48
|
-
ref: ${{ steps.get-tag.outputs.tag }}
|
|
49
|
-
|
|
50
|
-
- name: Set up Docker Buildx
|
|
51
|
-
uses: docker/setup-buildx-action@v3
|
|
52
|
-
|
|
53
|
-
- name: Log in to container registry
|
|
54
|
-
uses: docker/login-action@v3
|
|
55
|
-
with:
|
|
56
|
-
registry: ${{ env.REGISTRY }}
|
|
57
|
-
username: ${{ github.actor }}
|
|
58
|
-
password: ${{ secrets.GITHUB_TOKEN }}
|
|
59
|
-
|
|
60
|
-
- name: Extract Docker metadata
|
|
61
|
-
id: meta
|
|
62
|
-
uses: docker/metadata-action@v5
|
|
63
|
-
with:
|
|
64
|
-
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
65
|
-
tags: |
|
|
66
|
-
type=raw,value=${{ steps.get-tag.outputs.tag }}
|
|
67
|
-
type=raw,value=latest,enable=${{ github.event_name == 'release' || steps.get-tag.outputs.tag == github.event.repository.default_branch }}
|
|
68
|
-
|
|
69
|
-
- name: Build Docker image (for scanning)
|
|
70
|
-
uses: docker/build-push-action@v5
|
|
71
|
-
with:
|
|
72
|
-
context: .
|
|
73
|
-
push: false
|
|
74
|
-
load: true
|
|
75
|
-
tags: ${{ env.IMAGE_NAME }}:test
|
|
76
|
-
labels: ${{ steps.meta.outputs.labels }}
|
|
77
|
-
cache-from: type=gha
|
|
78
|
-
cache-to: type=gha,mode=max
|
|
79
|
-
|
|
80
|
-
- name: Scan image with Dockle
|
|
81
|
-
uses: erzz/dockle-action@v1
|
|
82
|
-
with:
|
|
83
|
-
image: ${{ env.IMAGE_NAME }}:test
|
|
84
|
-
exit-code: 1
|
|
85
|
-
failure-threshold: fatal
|
|
86
|
-
report-format: sarif
|
|
87
|
-
|
|
88
|
-
- name: Upload Dockle scan results
|
|
89
|
-
uses: github/codeql-action/upload-sarif@v3
|
|
90
|
-
with:
|
|
91
|
-
sarif_file: dockle-report.sarif
|
|
92
|
-
category: dockle
|
|
93
|
-
|
|
94
|
-
- name: Push Docker image
|
|
95
|
-
uses: docker/build-push-action@v5
|
|
96
|
-
with:
|
|
97
|
-
context: .
|
|
98
|
-
push: true
|
|
99
|
-
tags: ${{ steps.meta.outputs.tags }}
|
|
100
|
-
labels: ${{ steps.meta.outputs.labels }}
|
|
101
|
-
cache-from: type=gha
|
|
102
|
-
cache-to: type=gha,mode=max
|
package/.prettierignore
DELETED