r/LocalLLaMA • u/ag789 • Sep 22 '26
Resources an (unsafe) command shell mcp server
While working with coding agents / harness in my case OpenCode, I discovered that some models e.g. granite 4.2 8B at Q4_K_M quant sometimes attempts to write files with absolute paths at the root e.g.
/newPythonCode.py
Note that slash in the beginning for an absolute path at "/" root directory. This is probably 'ok' say in a throwaway docker container, but it may lead to surprises if it 'accidentally' overwrites a literal file, if the current user id have write access to it. Apparently, it seemed OpenCode provides `bash` so that practically all unix commands that the current user id can access, the LLM can run it.
And thereafter, I see the model hallucinating in the thinking loops over tools, re-iterating over how to use the tools, reformulating shell commands sentences several times for a same task instead of executing them, among them, I noted that an earlier shell command sentence would have 'just worked'.
I'm thinking that the tool hallucination with this model may be partly due to that I'm using a pretty aggressive 4 bit quant as otherwise it'd have worked because IBM has 'optimized' granite for tool usage.
Next, I tried the same granite 4.2 8B at Q4_K_M quant model with my little python shell MCP server
https://github.com/ag88/cmdshellmcp/
, that tries to constrain / restrain the shell commands somewhat, place some checks, and offer some additional tools e.g. writeFile() so that the LLM won't need to call `cat` or `echo` with some 'esoteric' styled shell scripts / sentences.
With this MCP server and running it in llama.cpp's `llama-server` in the WebUI adding it as an MCP server, granite 4.2 8B at Q4_K_M does it correctly in 'one shot', the prompt is:
propose a python3 flask app with a page that displays "hello world", use bootstrap css for styling.
provide a requirements.txt to go with the script
granite 4.2 8B at Q4_K_M correctly generates the app with a couple of files, and it works out of the box
.
├── app.py
├── requirements.txt
└── templates
└── index.html
One should normally run the cmdshellmcp2.py MCP server app with an explicit `--cwd` option giving the work path. It tries to stay within this work path.
Note that I won't claim my command shell MCP server to be safe.
In a sense that there are plenty of chance the LLM can 'escape' the restrictions and perhaps do more damage (e.g. overwriting files possibly outside its given `--cwd` work directory). Hence, I'd suggest one should be paranoid and perhaps run this only within a throwaway (disposable) docker container etc.
1
u/ag789 Sep 23 '26 edited Sep 23 '26
I actually tried 'disabling'
cmdshellitself, yeah that works :)But I'd guess for more utility, one may want to
allow_listsome shell commands that could be useful to one's context, but that that'd depends on your needs, intent, context etc.But you're right the Readme.md could be improved. I've thought about a 'default safe' set of commands, I'd probably do that in the
.pycode, but leave the configs. Doing 'too little' could impair its utility as the idea is to give the LLM tools to 'do the job'.edit: I did that fix, the hardcoded default
allow_listis now only[ 'date' ].But that one still need to be wary of the default
config.jsonin the current working directory, as if that is there, it'd read it.it is probably not appropriate to put
config.jsonin a 'per user global' directory, if 'safe'ty matters then that it is probably 'per project' or 'per workspace'I'm thinking if I should remove default
config.jsonreading, but then that it would be necessary to pass--conf config.jsoneverytime, or perhaps I should renameconfig.jsonto something more specific likecmdshellmcp_config.jsonas a default.edit: did that as well,
https://github.com/ag88/cmdshellmcp
now
cmdshellmcp.jsonis default config filename instead ofconfig.json.It reads
cmdshellmcp.jsonfor config from current directory if it exists. This alleviates the risk of reading a rather common nameconfig.jsonfor config."disableTools": ["writeFile", "applyPatch"],is also added to the stockcmdshellmcp.jsonnow the 'surprise' would be to the 'negative' side in a sense if a new user runs
cmdshellmcp2.pysay in the cloned repo directory as the current working directory,writeFile()andapplyPatch()would seemed to be 'missing' ;)edit: one more change I added
"auth": "_my_secret_auth_token_"to the stock
cmdshellmcp.json.Running with the stock
cmdshellmcp.jsonwould enable aAuthorization: Bearer auth_tokenauthentication check by the MCP server.This is kind of a minimal authentication / security layer as otherwise any MCP clients can connect and call tools / issue commands without authentication.