@akms/mcp-wsl 0.0.1 โ 0.0.2
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/LICENSE.md +23 -23
- package/README.md +152 -152
- package/lib/cli.cjs +1 -1
- package/lib/cli.cjs.map +1 -1
- package/lib/cli.js +1 -1
- package/lib/cli.js.map +1 -1
- package/lib/index.cjs +1 -1
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/package.json +9 -1
package/LICENSE.md
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
## MIT License
|
|
2
|
-
|
|
3
|
-
The MIT License (MIT)
|
|
4
|
-
|
|
5
|
-
Copyright (c) 2026 Alkemic Studio.
|
|
6
|
-
|
|
7
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
-
in the Software without restriction, including without limitation the rights
|
|
10
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
-
furnished to do so, subject to the following conditions:
|
|
13
|
-
|
|
14
|
-
The above copyright notice and this permission notice shall be included in all
|
|
15
|
-
copies or substantial portions of the Software.
|
|
16
|
-
|
|
17
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
-
SOFTWARE.
|
|
1
|
+
## MIT License
|
|
2
|
+
|
|
3
|
+
The MIT License (MIT)
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2026 Alkemic Studio.
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,152 +1,152 @@
|
|
|
1
|
-
# @akms/mcp-wsl
|
|
2
|
-
|
|
3
|
-
> ๐ Internal use only โ built for our organization's services.
|
|
4
|
-
|
|
5
|
-
MCP server that lets an AI agent run shell commands and transfer files in a WSL distro **on this machine**, through `wsl.exe`.
|
|
6
|
-
|
|
7
|
-
No sshd in the distro, no key, no port, no networking mode to keep alive: the first call starts the distro if it is stopped, and the Linux user is one variable. The distro is **pre-registered** in the server's own environment; the agent never names a distro or a user, and no tool takes one. Every command and path is screened by the [`@akms/mcp-ssh`](https://www.npmjs.com/package/@akms/mcp-ssh) guard policy plus the rules that only a WSL host needs.
|
|
8
|
-
|
|
9
|
-
Windows only โ the transport is `wsl.exe`. For reaching a WSL distro from *another* machine, keep using `@akms/mcp-ssh` against its sshd.
|
|
10
|
-
|
|
11
|
-
## ๐ฆ Installation
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
npm install -g @akms/mcp-wsl # installs the `akms-mcp-wsl` command
|
|
15
|
-
npx -y @akms/mcp-wsl --help # or run it straight from the registry
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## โ๏ธ MCP client setup
|
|
19
|
-
|
|
20
|
-
**One server entry per distro and user** โ plain `WSL_*` variables, no config file. Claude Code reads `.mcp.json` in the project or `~/.claude.json` globally; Claude Desktop takes the same block inside `claude_desktop_config.json`:
|
|
21
|
-
|
|
22
|
-
```json
|
|
23
|
-
{
|
|
24
|
-
"mcpServers": {
|
|
25
|
-
"wsl_ubuntu_root": {
|
|
26
|
-
"command": "npx",
|
|
27
|
-
"args": ["-y", "@akms/mcp-wsl"],
|
|
28
|
-
"env": { "WSL_DISTRO": "ubuntu", "WSL_USER": "root", "WSL_DESCRIPTION": "nginx, cloudflared and the test stack" }
|
|
29
|
-
},
|
|
30
|
-
"wsl_ubuntu_deploy": {
|
|
31
|
-
"command": "npx",
|
|
32
|
-
"args": ["-y", "@akms/mcp-wsl"],
|
|
33
|
-
"env": { "WSL_DISTRO": "ubuntu", "WSL_USER": "deploy", "WSL_READONLY": "true", "WSL_ALLOWED_PATHS": "/var/log,/opt/app" }
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
`WSL_DISTRO` is the whole minimum (the name as `wsl -l -q` prints it). Because each server fronts one distro *as one user*, the agent never picks either: `wsl_exec({ command: "df -h" })` is a complete call, and root access is a matter of which entry exists โ not of an argument. The server name is what the agent sees in its tool list, so put the user in it.
|
|
40
|
-
|
|
41
|
-
The server speaks MCP over **stdio**: stdout carries the JSON-RPC stream and all logging goes to stderr.
|
|
42
|
-
|
|
43
|
-
## ๐๏ธ Distro configuration
|
|
44
|
-
|
|
45
|
-
| `WSL_*` variable | Description |
|
|
46
|
-
|---|---|
|
|
47
|
-
| `WSL_DISTRO` โ
| Distro name as `wsl -l -q` prints it. Required by name โ following `wsl --set-default` would let a machine-wide setting silently redirect every command |
|
|
48
|
-
| `WSL_USER` | Linux user (`wsl -u`). The distro's default user when unset |
|
|
49
|
-
| `WSL_NAME` | Alias shown to the agent; defaults to the distro name |
|
|
50
|
-
| `WSL_DESCRIPTION` | Shown to the agent โ say what the distro is for |
|
|
51
|
-
| `WSL_CWD` | Directory new sessions start in |
|
|
52
|
-
|
|
53
|
-
### Policy
|
|
54
|
-
|
|
55
|
-
| `WSL_*` variable | Default | Effect |
|
|
56
|
-
|---|---|---|
|
|
57
|
-
| `WSL_READONLY` | `false` | Rejects write commands, output redirection, package installs, uploads and writes |
|
|
58
|
-
| `WSL_ALLOW_SUDO` | `true` | When false, rejects `sudo` / `su` / `doas` / `pkexec` / `runuser` / `chroot` |
|
|
59
|
-
| `WSL_ALLOW_WINDOWS` | **`false`** | When false, rejects Windows interop executables (`powershell.exe`, `cmd.exe`, any `.exe`) and **writes** whose target is under `/mnt/<drive>/`. Reads and copies *out of* `/mnt` are always allowed |
|
|
60
|
-
| `WSL_ALLOW_COMMANDS` | *(none)* | When set, only these binaries may run (`pwd` is added automatically so sessions can open) |
|
|
61
|
-
| `WSL_DENY_PATTERNS` | *(none)* | Extra regex sources, compiled at startup and tested per command segment |
|
|
62
|
-
| `WSL_ALLOWED_PATHS` | *(none)* | When set, the file tools accept only absolute paths inside these prefixes |
|
|
63
|
-
| `WSL_EXEC_TIMEOUT_MS` | `60000` | Per-command wall clock; `wsl.exe` is killed when it elapses |
|
|
64
|
-
| `WSL_MAX_OUTPUT` | `100000` | stdout and stderr are each truncated past this |
|
|
65
|
-
| `WSL_MAX_READ_BYTES` | `200000` | `wsl_read_file` ceiling |
|
|
66
|
-
|
|
67
|
-
The stance is `@akms/mcp-ssh`'s: **permissive by default**, restriction opt-in, and a malformed value fails startup (`WSL_READONLY=ture` is an error, not "not read-only"). The one default that differs is `WSL_ALLOW_WINDOWS`, and the reason is in the security notes below.
|
|
68
|
-
|
|
69
|
-
### Check it before wiring it up
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
akms-mcp-wsl --check # or: npx -y @akms/mcp-wsl --check
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
```
|
|
76
|
-
Checking ubuntu โ root@ubuntu
|
|
77
|
-
|
|
78
|
-
OK uid=0(root) gid=0(root) groups=0(root) (115ms)
|
|
79
|
-
distro: Running ยท WSL 2 ยท networking mirrored ยท systemd ยท kernel 5.15.146.1-microsoft-standard-WSL2
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## ๐งฐ Tools
|
|
83
|
-
|
|
84
|
-
| Tool | Purpose |
|
|
85
|
-
|---|---|
|
|
86
|
-
| `wsl_list_hosts` | The configured distro and user, the guard policy, and the distro's state: running/stopped, WSL version, networking mode, whether systemd is PID 1 |
|
|
87
|
-
| `wsl_connect` | Open a session (a remembered working directory), returns a session id |
|
|
88
|
-
| `wsl_disconnect` | Forget a session |
|
|
89
|
-
| `wsl_list_sessions` | Open sessions with cwd, command count and idle time |
|
|
90
|
-
| `wsl_exec` | Run a command in a login bash; returns stdout, stderr, exit code, duration |
|
|
91
|
-
| `wsl_list_dir` | Directory listing (type, mode, size, mtime) |
|
|
92
|
-
| `wsl_read_file` | Read a text file, truncated to the byte ceiling |
|
|
93
|
-
| `wsl_write_file` | Write or append UTF-8 text |
|
|
94
|
-
| `wsl_upload` | Local โ distro file transfer |
|
|
95
|
-
| `wsl_download` | Distro โ local file transfer |
|
|
96
|
-
|
|
97
|
-
### Every call is its own process
|
|
98
|
-
|
|
99
|
-
There is no connection to keep. Each tool call spawns `wsl.exe -d <distro> -u <user> --exec bash -lc <script>` and waits for it โ about 100 ms of overhead, no handshake. A **session** therefore carries exactly one thing between calls: the working directory, so `cd /opt/app` in one `wsl_exec` still applies to the next. Environment variables, background jobs and `sudo` timestamps do not carry over, because the process that held them has exited.
|
|
100
|
-
|
|
101
|
-
`--exec`, never `--`: with `--`, `wsl.exe` hands the command to the distro's default shell for a *second* parse, which strips backslashes and expands `$HOME` before bash ever sees the script โ the text the guard screened would not be the text that runs.
|
|
102
|
-
|
|
103
|
-
### Commands run without a terminal
|
|
104
|
-
|
|
105
|
-
stdin is closed, so anything that waits for input fails fast instead of hanging until the timeout: `sudo` without `NOPASSWD` fails with `a password is required`, prompts need their non-interactive flag (`apt-get -y`), full-screen tools have no TTY (`top -bn1`, `wsl_write_file` instead of an editor).
|
|
106
|
-
|
|
107
|
-
**A background job must detach all three descriptors**, or `wsl.exe` waits on the open pipe until the timeout kills it:
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
nohup ./build.sh > /tmp/build.log 2>&1 < /dev/null &
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
When the timeout fires, `wsl.exe` is killed; the process inside the distro may survive. The reply says so.
|
|
114
|
-
|
|
115
|
-
### File tools go through the distro process
|
|
116
|
-
|
|
117
|
-
`wsl_read_file` is `head -c`, `wsl_write_file` is `cat >`, transfers are `cat` over stdin/stdout โ byte-clean pipes, so binaries round-trip intact. This is deliberate: the `\\wsl$\` share opens every file as the distro's *default* user, so a `root` entry could not have written `/etc/nginx/โฆ` through it. Through the process, the registered user's permissions are the permissions.
|
|
118
|
-
|
|
119
|
-
These scripts have no variable part but the quoted path, so they bypass the command guard the way SFTP does in `@akms/mcp-ssh`; the **path** guard (`WSL_ALLOWED_PATHS`, read-only, the `/mnt` rules) governs them instead. `WSL_ALLOW_COMMANDS` need not list `cat` or `find`.
|
|
120
|
-
|
|
121
|
-
## ๐ก๏ธ Guard policy
|
|
122
|
-
|
|
123
|
-
The command guard is `@akms/mcp-ssh`'s, unchanged โ catastrophe rules always on (`rm -rf /`, `mkfs`, `dd of=/dev/sda`, `shutdown`, fork bombsโฆ), the rest opt-in. Its threat model is a **mistaken agent, not an adversary**, and so is this server's. On top:
|
|
124
|
-
|
|
125
|
-
| Layer | Scope | Examples |
|
|
126
|
-
|---|---|---|
|
|
127
|
-
| **WSL catastrophe** | Always | `wsl --shutdown` / `--terminate` / `--unregister`, `wslconfig /t`, `poweroff`, `systemctl poweroff`, `init 0` |
|
|
128
|
-
| `WSL_ALLOW_WINDOWS=false` | Default | any segment whose binary is a Windows executable (`powershell.exe`, `cmd.exe`, `explorer.exe`, `*.exe`); any write-shaped segment whose **target** is under `/mnt/<drive>/` โ a redirection, `cp`'s last operand, `tar -c`'s archive, `rm`/`sed -i`/`mv` operands |
|
|
129
|
-
| **Operator paths** | Always, even when Windows is open | `/mnt/c/Users/<you>/.ssh`, `.aws`, `.gnupg`, `*.pem`, `.claude.json`, `.mcp.json`, `claude_desktop_config.json` โ the same list `@akms/mcp-ssh` protects on the local side, reached through the mount |
|
|
130
|
-
|
|
131
|
-
Both `wsl_exec` and the file tools enforce these; the rules live in the layer every command and path passes through, so a new tool cannot forget them. A rejection returns the rule that fired, the offending segment, and the note that nothing was run.
|
|
132
|
-
|
|
133
|
-
## โ ๏ธ Security notes
|
|
134
|
-
|
|
135
|
-
**In WSL, the "remote" is your own machine.** `@akms/mcp-ssh` can say "the remote account is the real boundary" because a compromised remote account stays remote. Here the distro shares the Windows filesystem under `/mnt` and can launch Windows programs through interop โ and among the files it can reach that way is the MCP client configuration that defines this server's own `WSL_*` policy. An agent that writes `~/.claude.json` rewrites its guards for the next run. That is why `WSL_ALLOW_WINDOWS` is the one restriction on by default, and why the operator's credential and configuration paths stay refused even when it is turned on.
|
|
136
|
-
|
|
137
|
-
- Prefer a dedicated Linux user per entry over `root`, with `sudoers` scoped to what it needs โ the guard is a seatbelt, the account is the containment.
|
|
138
|
-
- The catastrophe rules read the command as text. A shell can express the same operation in unlimited ways; the guard stops mistakes, not intent.
|
|
139
|
-
- Output from the distro is untrusted input: a file the agent reads can contain instructions.
|
|
140
|
-
- Local paths in `wsl_upload` / `wsl_download` are resolved on Windows, so a POSIX-looking `/tmp/x` becomes `<current drive>\tmp\x`. Every transfer reply prints the resolved path โ check it.
|
|
141
|
-
|
|
142
|
-
## ๐ Logging
|
|
143
|
-
|
|
144
|
-
Everything goes to **stderr**. `WSL_MCP_LOG_LEVEL` picks the level โ `silent` / `debug` / `info` / `warn` / `error`, default `info`. Guard rejections are logged at `warn` with the full command; full command text is otherwise only logged at `debug`.
|
|
145
|
-
|
|
146
|
-
## ๐งโ๐ป Development
|
|
147
|
-
|
|
148
|
-
Building on this server, embedding it as a library, or changing the guards โ see [README_DEV.md](README_DEV.md).
|
|
149
|
-
|
|
150
|
-
## ๐ License
|
|
151
|
-
|
|
152
|
-
MIT โ see [LICENSE.md](LICENSE.md).
|
|
1
|
+
# @akms/mcp-wsl
|
|
2
|
+
|
|
3
|
+
> ๐ Internal use only โ built for our organization's services.
|
|
4
|
+
|
|
5
|
+
MCP server that lets an AI agent run shell commands and transfer files in a WSL distro **on this machine**, through `wsl.exe`.
|
|
6
|
+
|
|
7
|
+
No sshd in the distro, no key, no port, no networking mode to keep alive: the first call starts the distro if it is stopped, and the Linux user is one variable. The distro is **pre-registered** in the server's own environment; the agent never names a distro or a user, and no tool takes one. Every command and path is screened by the [`@akms/mcp-ssh`](https://www.npmjs.com/package/@akms/mcp-ssh) guard policy plus the rules that only a WSL host needs.
|
|
8
|
+
|
|
9
|
+
Windows only โ the transport is `wsl.exe`. For reaching a WSL distro from *another* machine, keep using `@akms/mcp-ssh` against its sshd.
|
|
10
|
+
|
|
11
|
+
## ๐ฆ Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g @akms/mcp-wsl # installs the `akms-mcp-wsl` command
|
|
15
|
+
npx -y @akms/mcp-wsl --help # or run it straight from the registry
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## โ๏ธ MCP client setup
|
|
19
|
+
|
|
20
|
+
**One server entry per distro and user** โ plain `WSL_*` variables, no config file. Claude Code reads `.mcp.json` in the project or `~/.claude.json` globally; Claude Desktop takes the same block inside `claude_desktop_config.json`:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"mcpServers": {
|
|
25
|
+
"wsl_ubuntu_root": {
|
|
26
|
+
"command": "npx",
|
|
27
|
+
"args": ["-y", "@akms/mcp-wsl"],
|
|
28
|
+
"env": { "WSL_DISTRO": "ubuntu", "WSL_USER": "root", "WSL_DESCRIPTION": "nginx, cloudflared and the test stack" }
|
|
29
|
+
},
|
|
30
|
+
"wsl_ubuntu_deploy": {
|
|
31
|
+
"command": "npx",
|
|
32
|
+
"args": ["-y", "@akms/mcp-wsl"],
|
|
33
|
+
"env": { "WSL_DISTRO": "ubuntu", "WSL_USER": "deploy", "WSL_READONLY": "true", "WSL_ALLOWED_PATHS": "/var/log,/opt/app" }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`WSL_DISTRO` is the whole minimum (the name as `wsl -l -q` prints it). Because each server fronts one distro *as one user*, the agent never picks either: `wsl_exec({ command: "df -h" })` is a complete call, and root access is a matter of which entry exists โ not of an argument. The server name is what the agent sees in its tool list, so put the user in it.
|
|
40
|
+
|
|
41
|
+
The server speaks MCP over **stdio**: stdout carries the JSON-RPC stream and all logging goes to stderr.
|
|
42
|
+
|
|
43
|
+
## ๐๏ธ Distro configuration
|
|
44
|
+
|
|
45
|
+
| `WSL_*` variable | Description |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `WSL_DISTRO` โ
| Distro name as `wsl -l -q` prints it. Required by name โ following `wsl --set-default` would let a machine-wide setting silently redirect every command |
|
|
48
|
+
| `WSL_USER` | Linux user (`wsl -u`). The distro's default user when unset |
|
|
49
|
+
| `WSL_NAME` | Alias shown to the agent; defaults to the distro name |
|
|
50
|
+
| `WSL_DESCRIPTION` | Shown to the agent โ say what the distro is for |
|
|
51
|
+
| `WSL_CWD` | Directory new sessions start in |
|
|
52
|
+
|
|
53
|
+
### Policy
|
|
54
|
+
|
|
55
|
+
| `WSL_*` variable | Default | Effect |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `WSL_READONLY` | `false` | Rejects write commands, output redirection, package installs, uploads and writes |
|
|
58
|
+
| `WSL_ALLOW_SUDO` | `true` | When false, rejects `sudo` / `su` / `doas` / `pkexec` / `runuser` / `chroot` |
|
|
59
|
+
| `WSL_ALLOW_WINDOWS` | **`false`** | When false, rejects Windows interop executables (`powershell.exe`, `cmd.exe`, any `.exe`) and **writes** whose target is under `/mnt/<drive>/`. Reads and copies *out of* `/mnt` are always allowed |
|
|
60
|
+
| `WSL_ALLOW_COMMANDS` | *(none)* | When set, only these binaries may run (`pwd` is added automatically so sessions can open) |
|
|
61
|
+
| `WSL_DENY_PATTERNS` | *(none)* | Extra regex sources, compiled at startup and tested per command segment |
|
|
62
|
+
| `WSL_ALLOWED_PATHS` | *(none)* | When set, the file tools accept only absolute paths inside these prefixes |
|
|
63
|
+
| `WSL_EXEC_TIMEOUT_MS` | `60000` | Per-command wall clock; `wsl.exe` is killed when it elapses |
|
|
64
|
+
| `WSL_MAX_OUTPUT` | `100000` | stdout and stderr are each truncated past this |
|
|
65
|
+
| `WSL_MAX_READ_BYTES` | `200000` | `wsl_read_file` ceiling |
|
|
66
|
+
|
|
67
|
+
The stance is `@akms/mcp-ssh`'s: **permissive by default**, restriction opt-in, and a malformed value fails startup (`WSL_READONLY=ture` is an error, not "not read-only"). The one default that differs is `WSL_ALLOW_WINDOWS`, and the reason is in the security notes below.
|
|
68
|
+
|
|
69
|
+
### Check it before wiring it up
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
akms-mcp-wsl --check # or: npx -y @akms/mcp-wsl --check
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
Checking ubuntu โ root@ubuntu
|
|
77
|
+
|
|
78
|
+
OK uid=0(root) gid=0(root) groups=0(root) (115ms)
|
|
79
|
+
distro: Running ยท WSL 2 ยท networking mirrored ยท systemd ยท kernel 5.15.146.1-microsoft-standard-WSL2
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## ๐งฐ Tools
|
|
83
|
+
|
|
84
|
+
| Tool | Purpose |
|
|
85
|
+
|---|---|
|
|
86
|
+
| `wsl_list_hosts` | The configured distro and user, the guard policy, and the distro's state: running/stopped, WSL version, networking mode, whether systemd is PID 1 |
|
|
87
|
+
| `wsl_connect` | Open a session (a remembered working directory), returns a session id |
|
|
88
|
+
| `wsl_disconnect` | Forget a session |
|
|
89
|
+
| `wsl_list_sessions` | Open sessions with cwd, command count and idle time |
|
|
90
|
+
| `wsl_exec` | Run a command in a login bash; returns stdout, stderr, exit code, duration |
|
|
91
|
+
| `wsl_list_dir` | Directory listing (type, mode, size, mtime) |
|
|
92
|
+
| `wsl_read_file` | Read a text file, truncated to the byte ceiling |
|
|
93
|
+
| `wsl_write_file` | Write or append UTF-8 text |
|
|
94
|
+
| `wsl_upload` | Local โ distro file transfer |
|
|
95
|
+
| `wsl_download` | Distro โ local file transfer |
|
|
96
|
+
|
|
97
|
+
### Every call is its own process
|
|
98
|
+
|
|
99
|
+
There is no connection to keep. Each tool call spawns `wsl.exe -d <distro> -u <user> --exec bash -lc <script>` and waits for it โ about 100 ms of overhead, no handshake. A **session** therefore carries exactly one thing between calls: the working directory, so `cd /opt/app` in one `wsl_exec` still applies to the next. Environment variables, background jobs and `sudo` timestamps do not carry over, because the process that held them has exited.
|
|
100
|
+
|
|
101
|
+
`--exec`, never `--`: with `--`, `wsl.exe` hands the command to the distro's default shell for a *second* parse, which strips backslashes and expands `$HOME` before bash ever sees the script โ the text the guard screened would not be the text that runs.
|
|
102
|
+
|
|
103
|
+
### Commands run without a terminal
|
|
104
|
+
|
|
105
|
+
stdin is closed, so anything that waits for input fails fast instead of hanging until the timeout: `sudo` without `NOPASSWD` fails with `a password is required`, prompts need their non-interactive flag (`apt-get -y`), full-screen tools have no TTY (`top -bn1`, `wsl_write_file` instead of an editor).
|
|
106
|
+
|
|
107
|
+
**A background job must detach all three descriptors**, or `wsl.exe` waits on the open pipe until the timeout kills it:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
nohup ./build.sh > /tmp/build.log 2>&1 < /dev/null &
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
When the timeout fires, `wsl.exe` is killed; the process inside the distro may survive. The reply says so.
|
|
114
|
+
|
|
115
|
+
### File tools go through the distro process
|
|
116
|
+
|
|
117
|
+
`wsl_read_file` is `head -c`, `wsl_write_file` is `cat >`, transfers are `cat` over stdin/stdout โ byte-clean pipes, so binaries round-trip intact. This is deliberate: the `\\wsl$\` share opens every file as the distro's *default* user, so a `root` entry could not have written `/etc/nginx/โฆ` through it. Through the process, the registered user's permissions are the permissions.
|
|
118
|
+
|
|
119
|
+
These scripts have no variable part but the quoted path, so they bypass the command guard the way SFTP does in `@akms/mcp-ssh`; the **path** guard (`WSL_ALLOWED_PATHS`, read-only, the `/mnt` rules) governs them instead. `WSL_ALLOW_COMMANDS` need not list `cat` or `find`.
|
|
120
|
+
|
|
121
|
+
## ๐ก๏ธ Guard policy
|
|
122
|
+
|
|
123
|
+
The command guard is `@akms/mcp-ssh`'s, unchanged โ catastrophe rules always on (`rm -rf /`, `mkfs`, `dd of=/dev/sda`, `shutdown`, fork bombsโฆ), the rest opt-in. Its threat model is a **mistaken agent, not an adversary**, and so is this server's. On top:
|
|
124
|
+
|
|
125
|
+
| Layer | Scope | Examples |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| **WSL catastrophe** | Always | `wsl --shutdown` / `--terminate` / `--unregister`, `wslconfig /t`, `poweroff`, `systemctl poweroff`, `init 0` |
|
|
128
|
+
| `WSL_ALLOW_WINDOWS=false` | Default | any segment whose binary is a Windows executable (`powershell.exe`, `cmd.exe`, `explorer.exe`, `*.exe`); any write-shaped segment whose **target** is under `/mnt/<drive>/` โ a redirection, `cp`'s last operand, `tar -c`'s archive, `rm`/`sed -i`/`mv` operands |
|
|
129
|
+
| **Operator paths** | Always, even when Windows is open | `/mnt/c/Users/<you>/.ssh`, `.aws`, `.gnupg`, `*.pem`, `.claude.json`, `.mcp.json`, `claude_desktop_config.json` โ the same list `@akms/mcp-ssh` protects on the local side, reached through the mount |
|
|
130
|
+
|
|
131
|
+
Both `wsl_exec` and the file tools enforce these; the rules live in the layer every command and path passes through, so a new tool cannot forget them. A rejection returns the rule that fired, the offending segment, and the note that nothing was run.
|
|
132
|
+
|
|
133
|
+
## โ ๏ธ Security notes
|
|
134
|
+
|
|
135
|
+
**In WSL, the "remote" is your own machine.** `@akms/mcp-ssh` can say "the remote account is the real boundary" because a compromised remote account stays remote. Here the distro shares the Windows filesystem under `/mnt` and can launch Windows programs through interop โ and among the files it can reach that way is the MCP client configuration that defines this server's own `WSL_*` policy. An agent that writes `~/.claude.json` rewrites its guards for the next run. That is why `WSL_ALLOW_WINDOWS` is the one restriction on by default, and why the operator's credential and configuration paths stay refused even when it is turned on.
|
|
136
|
+
|
|
137
|
+
- Prefer a dedicated Linux user per entry over `root`, with `sudoers` scoped to what it needs โ the guard is a seatbelt, the account is the containment.
|
|
138
|
+
- The catastrophe rules read the command as text. A shell can express the same operation in unlimited ways; the guard stops mistakes, not intent.
|
|
139
|
+
- Output from the distro is untrusted input: a file the agent reads can contain instructions.
|
|
140
|
+
- Local paths in `wsl_upload` / `wsl_download` are resolved on Windows, so a POSIX-looking `/tmp/x` becomes `<current drive>\tmp\x`. Every transfer reply prints the resolved path โ check it.
|
|
141
|
+
|
|
142
|
+
## ๐ Logging
|
|
143
|
+
|
|
144
|
+
Everything goes to **stderr**. `WSL_MCP_LOG_LEVEL` picks the level โ `silent` / `debug` / `info` / `warn` / `error`, default `info`. Guard rejections are logged at `warn` with the full command; full command text is otherwise only logged at `debug`.
|
|
145
|
+
|
|
146
|
+
## ๐งโ๐ป Development
|
|
147
|
+
|
|
148
|
+
Building on this server, embedding it as a library, or changing the guards โ see [README_DEV.md](README_DEV.md).
|
|
149
|
+
|
|
150
|
+
## ๐ License
|
|
151
|
+
|
|
152
|
+
MIT โ see [LICENSE.md](LICENSE.md).
|
package/lib/cli.cjs
CHANGED
|
@@ -31,7 +31,7 @@ var MAX_DIRECTORY_ENTRIES = 1e3;
|
|
|
31
31
|
var WSL_EXECUTABLE = "wsl.exe";
|
|
32
32
|
var WSL_FAILURE_EXIT_CODES = [4294967295, -1];
|
|
33
33
|
var SERVER_NAME = "akms-mcp-wsl";
|
|
34
|
-
var SERVER_VERSION = "0.0.
|
|
34
|
+
var SERVER_VERSION = "0.0.2";
|
|
35
35
|
|
|
36
36
|
// src/_defs/env-vars.ts
|
|
37
37
|
var SINGLE_HOST_ENV = {
|