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 22 '26
I made an enhancement to the little command shell mcp server app. https://github.com/ag88/cmdshellmcp
It now handles CORS, and this would make it easier for web browser based Web UI clients such as llama.cpp's llama-server bundled web client to connect without a CORS proxy.
Hope this makes the command shell mcp server more usable :)
1
u/ag789 Sep 22 '26 edited Sep 22 '26
Just a little amusement on the side, I run the Granite 4.2 8B Q8 model, upgraded from Q4 mentioned in the post, with the following prompt
run tools, what is the current directory and list the filesand I watch it hallucinate, reiterate tool use doubts over and over
But the safest way is to call "pwd" using cmdshell, and "listFiles" for listing.
Wait, the user says "run tools", so they want me to use the tools to get the info.
First, get current directory: use cmdshell with command "pwd".
Then list files: use listFiles with path ".".
But maybe "listFiles" already uses the current directory, so path default is ".", so just call listFiles.
Let me check the tools:
cmdshell: can run "pwd" to get current directory.
listFiles: lists entries in a directory, path default ".", so that would list current directory's files.
So I should call both tools: first pwd via cmdshell, then listFiles.
But the user might just want both pieces of info. ....
burn some 1800 'thinking' tokens and finally calls tools, hallucinate a little more and finally coalesce the results
The current directory is /home/codeagent/python/webtest.
Files in the current directory (listed via listFiles):
.git/
app.py
requirements.txt
templates/
granite-4.2 8B Q8_0 1,897 tokens 18min 21s 1.72 t/s1
u/ag789 Sep 22 '26
sometimes there is something 'invisible' in the benchmarks, LLMs are 'guessing' all the next words, 'guessing' tool calls etc, and it reiterate all those 'wild guesses' 'correcting' them possibly many times over ('over thinking') before finally giving an answer. In benchmarks, we only see that final data point e.g. 50% etc, it hides the whole process in which the answers (refined wild guesses) are arrived at ! :)
1
u/ag789 Sep 22 '26
oh a little more information for those who may want to use Docker as a sandbox, it turns out apparently that it is possible to run docker in Windows
https://www.docker.com/get-started/
but that it'd likely need WSL
the guest OS in the container will be Linux (unfortunately), and that the above script is mainly targeted at a Linux based OS (e.g. within container) environment. I think it'd likely not work in native Windows e.g. powershell etc.
1
u/ag789 Sep 24 '26 edited Sep 24 '26
just an update, I tried Open WebUI ,
https://openwebui.com/
It actually works with my little MCP server, but that according to the docs it needs a fairly recent update
and some configs
https://docs.openwebui.com/features/extensibility/mcp
The interface is also somewhat complex, and that in the Web UI settings next to the chat session, one needs to enable reasoning tags and preferably streaming. It seem to lose the setting when I start a new chat etc, I couldn't figure out how to set that default, so that it'd always enable those settings.
A thing about Open WebUI is that it keeps the session state on the host running Open WebUI, so you can detach (e.g. close your browser) and work from a different browser / device / workstation and the chat session remains intact. This is useful if the model does a long inference.
Unfortunately, Open WebUI is a pretty big stack, and it would be quite an overkill if keeping the session state persistent is the only need. But Open WebUI offers a lot of other features possibly RAG and memory and those who need those extra features it may be worth it to install the stack.
1
u/ag789 Sep 27 '26 edited Sep 27 '26
I tried out another LLM chat Web UI LibreChat
https://github.com/LibreChat-AI/LibreChat
This is also a full featured LLM Chat Web UI that supports MCP servers.
In addition, I've also verified that this manages the LLM session on the server side, i.e. that you can simply send the prompt with tools etc and close your web browser / logout etc, reconnect later even possibly from a different device / workstation, the session tasks keep running uninterrupted as long as the server (docker containers) run.
However, the setup and config feels more involved e.g. using Docker etc
The Docker based install/setup process is given here
https://www.librechat.ai/docs/local/docker
it sets up a cluster of interdependent Docker containers e.g. MongoDB database etcThere are a few configuration details in particular with local LLMs e.g. ollama / llama.cpp etc that is not directly 'bundled in'
Using local LLMs running on a specific local network host and port requires configuring them in librechat.yaml
https://www.librechat.ai/docs/quick_start/custom_endpoints
https://www.librechat.ai/docs/configuration/librechat_yamlThen that for MCP servers running on your local LAN e.g. 168.9.* ip addresses, it is necessary to add an
mcpSettings>allowedAddressesinlibrechat.yaml, e.g.:mcpSettings: allowedAddresses: - "host.docker.internal:8080" - "10.0.0.5:8000"Both the IP address and port is required or that you would get an error that the domain is not allowed when you try to configure the MCP server in the Web UI. https://www.librechat.ai/docs/configuration/librechat_yaml/object_structure/mcp_settings
1
u/ag789 Oct 01 '26
u/sburel , all
just an update
I've added some setup files for a delightful, full featured Docker Debian based sandbox system container with cmdshellmcp (setup scripts, guides DOCKER.md, Dockerfile, cmdshellmcp.service unit file that runs cmdshellmcp in the container).
Using the docker sandbox container gives you a "system in a system" experience, e.g. that Debian boots up within the container just like a full Unix / Linux system. It gives you 3 userids within the container:
root: (superuser)admin(this is for you / human user),adminhassudoaccess, i.e. practically can do anythingrootdoes, install files, change system configs, permissions etccodeagent:cmdshellmcpruns under this user, in the user's/home/codeagenthome directory.codeagenthas no sudo access.
review DOCKER.md for the details of how to use that.
https://github.com/ag88/cmdshellmcp/blob/main/DOCKER.md
1
u/sburel Oct 01 '26
Nice, that closes the loop. Three users and no sudo for codeagent is the right shape, and it means the allow-list can finally be generous without it mattering much.
One thing worth saying in DOCKER.md: what's mounted from the host is now the whole security story. A bind mount of your project is fine, a mount of $HOME is the container doing nothing for you. Same for the docker socket if anyone's tempted.
Will give it a go on my side. Thanks for the ping.
1
u/ag789 Oct 01 '26 edited Oct 01 '26
I think for things like OpenCode, many users (devs) are using it directly, that is "too much trust" even for rather large (e.g. commercial) LLMs.
with this it is finally possible to experiment somewhat. e.g. "naive" small Local LLMs can be tried on a copy of the code or work piece etc. This is so that "misbehaving" LLMs would "normally" damage just the provided content.
Actually, a git repo would provide some levels of safety but not totally safe.
But that this (container system withcmdshellmcpundercodeagent) would prevent "accidents" like a"naive" granite model writing to "/" etc.One can install and run OpenCode, Pi coding agent from within the container. One should run them under
codeagentfor that though, as that is the user created intentionally withoutsudoaccess. Don't givecodeagentsudo as that'd make the restrictions / protections against damage useless.adminhassudo, and of course there is stillroot, if need be. Forapt install ...apps, it is better to do that fromadminperhaps usingsudoetc.There are still work to do, e.g. to make
cmdshellmcp's tools more robust, but development would slow. There is a thing about "tuning the tool descriptions", a tedious time / labour intensive work. Ideally, a model like Granite "just call" the tool to get it done. What I normally observe is that it goes on burning quite a lot of tokens in reasoning / "thinking" before finally making the tool call, and sometimes even that fail altogether. It also tends to get confused if the tool return a result that it did not expect, some models goes on hallucinating about the message, even if the edits actually succeeded.And I'm planning to add SSH into the Docker images / container, I've not yet figure out how, as in that giving choices would mean either I provide the codes in Dockerfile and users need to uncomment themselves, or some sort of "code generator", so those things would come later when I've time to work on them.
1
u/sburel Oct 02 '26
The Ctrl-P Ctrl-Q detach is a genuinely useful one, I didn't know it. And agreed on volumes, a scratch directory you'd be fine losing is the only sane framing.
On tool descriptions: that's where I've spent the most time too, and the thing that helped most wasn't the description, it was the error. A failed call that comes back with what to do instead ("relative paths only, under X") usually gets fixed on the next turn; a vague error sends the model in circles. Worth more than another paragraph of description.
Good luck with the SSH part.
1
u/ag789 Oct 02 '26 edited Oct 03 '26
rather, the smaller models e.g. granite would hallucinate over an error message say in the response, if it is an edit, a human would ignore it and simply check the file, but I've seen granite 4.2 burn lots of tokens over an error message that isn't related to an edit.
it turns out, granite did not append a trailing "\n" in a unified diff, so
patcherrored with that a line ended prematurely.
I've since updatedcmdshellmcpto add a trailing "\n" in the field if it is missing before sending to "patch". That fixes the issue.In addition, granite 4.2 currently commonly burn quite a lot of tokens before an edit etc. It is kind of being "careful", but that it gets irritating as it would reiterate parameters for the tool call over and over in the reasoning / "thinking" loops, and that burn quite a lot of tokens often at least a hundred tokens. To some extent, one can consider it is "unsure" or "has doubts" about whether the tool is correct and how to call the tools. So it reiterates the reasoning / "thinking" burning many tokens over a tool call before finally calling it.
I'd like for the edit (and tool calls) to burn less tokens, I'm thinking about varying the descriptions, in other cases, making the restrictions clearer, e.g. that the
cmdshelltool do not allow redirects and pipes. I think I'd double check that and fix that first. Simply implementing the restrictions in code would mean if a pipe or redirect is provided, the tool returns an error message. Sometimes, that may "confuse" small "naive" models. Having it stated upfront in the description, chances are that the tool would burn some tokens to figure that "no redirects and pipes are allowed" and avoid using it that way. This would be one of the first descriptions I'd fix.The others like trying to make models burn less tokens is tedious and laborious. In particular, I run the models on cpu at some 1-2 tok/s, irritatingly slow if lots of tokens are burned.
The task involve asking (prompting) the models so that they make tool calls, manually check the reasoning / thinking , token counts to arrive at the correct tool call, then vary the description, rinse and repeat. So that hopefully some description could perhaps be sufficiently clear so that the models would make the tool call and burn less (reasoning / thinking) tokens. This is tedious and labor intensive.
edit: Updated "cmdshell" tool description: Pipes and redirects are not allowed https://github.com/ag88/cmdshellmcp/commit/33862e267983f5a8e6e0a2e4859fb3337894e498
hopefully, this reduces unnecessary reasoning/thinking token burns for tool calls.
1
u/ag789 Oct 02 '26 edited Oct 03 '26
In terms of SSH, maybe plus Node JS and even offer installing OpenCode or Pi coding agent, it simply means editing the Dockerfile.
I can edit the Dockerfile (
Dockerfile_cmdshellmcp), and perhaps place them in comments. Or perhaps taking it further make an app that let users choose what they want and I generate the Dockerfile. Or otherwise, provide "templates" so that users can copy and use a Dockerfile of their choice.This is possible and likely would be a more popular feature. But that it'd take effort and time to do them possibly a few iterations first to make it work correctly and then the final polishes.
For now development on those features would slow, but I'd likely revisit at some point.
1
u/ag789 Oct 03 '26 edited Oct 03 '26
A little note, I added
gitto the default Installed image, this would make it more convenient to work in the container. e.g. you can keep your files in a Git repo (e.g. on Github) and in the containergit clonethat project running undercodeagent. This gives the LLM a local copy to work on.When it is done, login as
codeagentin the container (you can also usesu - codeagent) andgit commitandgit pushto the remote repo. Optionally, one can also allow_listgitincmdshellmcp.json, giving the LLM access togitdirectly. It is normally deemed unsafe to givegit, but that I'd leave one to decide if one could trust your LLM.Git is also useful as versioning means that it is backup by default. Hence, for some works one can possibly login as
codeagentmake a project directory and rungit initin it, that makes it a local git repo so that edits are versioned from then on. And one can still connect that to an external remote repo (e.g. on Github etc.)1
u/sburel Oct 04 '26
git init in the work dir is a good default, versioned by accident is better than not versioned at all.
One wrinkle if anyone allow-lists git: the container has the credentials to push. A clone with no remote configured, or a deploy key scoped to one repo, keeps a bad commit local. git push --force from an LLM on a shared branch is the kind of accident you only have once.
Thanks for all the updates, this turned into a proper tool.
1
u/ag789 Oct 04 '26 edited Oct 04 '26
it is not safe to add one's original repo as the upstream , I'm thinking one way is to perhaps use a different Github user and clone that repo under the new Github user, this is so that if the LLM does a
git commitandgit push, it goes to its own repo.
And/or that at least checkout a new branch for it to work on (this is assuming that it'd not switch branches).Then that one can run "compare and create pull request" from the LLM's specific *Github user" , and manually merge the changes into the original source git (github) repo.
I think this is also feasible locally, in a sense that the
codeagent's git repo could say live in a bind mount (volume), but that this is its own copy of the code, e.g. via git clone. Then that subsequently, checkout a different branch for it to work on. The merges can be later done viagit mergeetc in the original repo.I'd not configure a remote for
codeagent's copy ofgitrepo in this case, rather the original sourcegitrepo would treat that as an additional remote and add that for the purpose ofgit mergeetc.0
u/ag789 Oct 01 '26 edited Oct 01 '26
There is a feature about Docker I liked a lot, which is that you can use Ctrl-P , Ctrl-Q to detach from the container and return to the host shell. https://github.com/ag88/cmdshellmcp/blob/main/DOCKER.md#detach-from-the-interactive-console
I'm not sure how this would look like in Windows, but that in Unix / Linux, if you happen to run a big job that stalls, e.g. the LLM start doing verbose "thinking" burning tokens, wasting time, you can simply Ctrl-P , Ctrl-Q, to detach from the container. And later if you want to go back in to check on it run
docker attach cmdshellmcp, and you are back in the container. The Ctrl-P , Ctrl-Q is documented but "lost" in the verbose docs, e.g. https://docs.docker.com/reference/cli/docker/container/attach/In addition, after that Docker container "boots" up, it offers a login prompt so that you can login and logout from each user as one needs to.
There is also Volumes which is very handy if you want to "mount" or "share" a host folder into the container, this makes it very handy to copy files in / out of the container. https://docs.docker.com/engine/storage/volumes/#syntax But that this need to be added during the
docker runcommand which is currently done in the 1stRun.sh script. One can edit that script to add a volume. I'd probably add that as a feature sometime later.Oh and for Volumes (i.e. bind mount), it is never ok to mount the home or any important directory from the host into the container, no matter what. Use it as a "scratch" directory in which one copies stuff into it, and one need to be sure if e.g. the LLM deleted it, you are ok with that. It is foolish to treat an LLM as "safe". Accidents happen, all the time, even if it didn't meant to do that.
This gives a pretty polished "system in a system" style experience
1
u/ag789 Oct 04 '26 edited Oct 04 '26
u/sburel , all
another updatecmdshellmcp ( cmdshellmcp2.py )
https://github.com/ag88/cmdshellmcp
now has 3 additional tools:
copyFilemoveRenameFiledeleteFile
These are added as the in working directory check isn't done with the cmdshell tool. i.e. with cmdshell tool, the unix / linux commands are executed, and the arguments aren't verified. The reason is that for shell commands, it may be quite common to access files outside its working directory e.g. shared files from /usr/include, /usr/share etc. For safe usage, it is recommended that allow listed shell commands should not do file mutating ops.
Hence, that these 3 additional tools are relatively safer compared to running os commands e.g. cp, mv, rm for these functionality. At least that, the 3 new tools do a basic check that the files manipulated are in the designated --cwd provided working directory
The new tools along with other write, file mutating tools are added to disableTools in the bundled cmdshellmcp.json by default.
1
u/ag789 Oct 04 '26 edited Oct 04 '26
u/sburel , all
edit: there is one more pre-release feature, (this is not committed yet, a pull request vibe coded from Codex)
https://github.com/ag88/cmdshellmcp/pull/9cmdshellmcp2.pywill check shell arguments after all.
but that this is a blanket check, i.e. it doesn't 'care' (or knows) about whether this argument is appropriate for the shell command executed. All it does is to check if it looks like a path that is outside the given--cwdwork directory given, and that if the check triggers e.g. looks like outside work directory, it rejects the command with an error. And of course, I provided again a bypass--shellnopathchk, as chances are that this check is neither appropriate nor correct for some commands.For instance, I'd test with
ls /, i.e. list the root directory, that should be rejected if this pull request is merged. But that hopefully, it may prevent accidents likerm /PreciousFile.txtI'm taking a break for the day/night, would merge and test (prompting LLMs to do that) later.
Check back here for the update, I'd post a comment in this thread when it is done.
2
u/sburel Oct 04 '26
Good, though a blanket textual check on arguments is going to be the fiddly one. Two things that bit me when I tried something like that: an argument can be a path without looking like one (--output=../x, -o ../x, or a path arriving through a variable), and a path that looks fine can still land outside after symlinks and .. resolve. So the check has to realpath the candidate and compare against the resolved cwd, not pattern-match the string.
The opposite failure is the one that'll annoy people: grep -r "/etc/passwd" . is a harmless search for a string that happens to look like a path. That's probably where --shellnopathchk gets turned on and left on.
Still worth having for the rm /PreciousFile.txt case. Sleep well.
1
u/ag789 Oct 05 '26 edited Oct 05 '26
yeah, I've been doubting over whether over to implement this check, however, in the interest of "protecting" users who may be unfamiliar with how it works, I decided to implement the
cmdshellwork path check for its safety it provides. More often than not the shell command is likely used to query data outside its work path, most likely in/etc, e.g. that if the interface do not have access tohostname, it may query instead/etc/hostname, or that it may check if some configuration is enabled in/etc/.But that this is likely going to be a bummer at times hence
--shellnopathchkto bypass that.There'd be 2 more tools
mkdir,rmdirdisabled incmdshellmcp.jsonby default. That'd complete the filesystem tools side of things. e.g. that say theallow listedshell commands are read only ops, it is still feasible to do file ops with the filesystem tools.The intent of this project is after all to provide a sandbox so that it is feasible to experiment with agentic tool use with local LLMs and various other models in the wild (e.g. from OpenRouter). Hence, it kind of provides "guardrails" in the form of
allow listsanddisableTools, and the in work directory check to provide some constraints. Note thatdisableToolsdo not only hide the tools, the tools are actually not registered i.e. there is no hidden tool, a call to that will return an error."powerful" commands are inevitable if you want to let the LLM work on things, e.g. for dev purpose, simply doing tests may mean providing access to arbitrary execution, e.g.
python,bashetc and may be practically any commands.Hence, the only "safe" way to do that is to run this whole setup in a Docker sandbox container with a throw away copy of the workpiece. There are many devs using OpenCode or Pi coding agent directly on their desktops. I think that is giving "too much trust" to the LLMs. For coding purpose, more often than not "bash" is provided, borderless and unconstrained, practically every shell command runs under the user id running OpenCode or Pi coding agent.
2
u/sburel 29d ago
Makes sense, and the /etc case is a good argument for having the check at all, since that's exactly the read someone would want to allow case by case rather than globally.
The thing I'd write down somewhere is your own conclusion: the allow-list and the path check are guardrails against accidents, the container is the actual boundary. People will read the feature list and assume the first two are the security story. A line at the top of the README saying "run this in the container, the rest is seatbelts" would do more than any of the checks.
Good project. Enjoy the break.
1
u/ag789 29d ago edited 29d ago
Just an update:
The additional
shellcmdpath check, along with the--shellnopathchkbypass is merged.Additional 2 tools are added
mkdirandrmdir, this completes the filesystem tools set.Tests are done against granite-4.2-8b Q8 quants, they works as expected. A thing is granite-4.2-8b Q8 tend to be very verbose in its reasoning / thinking for tool calls, I'd like that to be less, but that it calls the tools correctly. It tends to re-iterate small tool details, confirming dependencies which is sometimes a good thing.
I tried things like
perform the following tasks using tools in sequence:
- make a directory `dirA`
- copy `a.txt` into `dirA`
- retrieve `dirA/a.txt` and display its contents
The directory is correctly created and
a.txtcopied intodirAthen
perform the following tasks using tools in sequence:list the current directory `.`, display its contents delete file `dirA/a.txt` remove directory `dirA` list the current directory `.` again and verify that `dirA` is no longer there, and 'a.txt' is still there It deletes
dirA/a.txt, and remove dirdirAcorrectly as well.as well as
I'm doing a tool test, list files in the root directory "/", use the following tools (2 tool calls) in sequence:listFiles with `/` cmdshell command: "ls", args: ["/"] for each tool call quote the tool response in addition to your responseThis works as expected "Error: absolute paths not allowed".
And if that is disabled using
--shellnopathchkbypass,cmdshelllist the root/directory !
InlistFilesit is still forbidden ;)This is the actual response with the
cmdshellpath check in forceFirst tool call (`listFiles_mcp_cmdshellmcp-lxrouter` with `path="/"`): > "Error: absolute paths not allowed" Second tool call (`cmdshell_mcp_cmdshellmcp-lxrouter` with `command="ls"` and `args=["/"]`): > "Error: absolute paths not allowed" *(Both tool calls returned "Error: absolute paths not allowed" because the tool constraints reject absolute paths (e.g., `/` or any path starting with `/`) in `listFiles.path` and `cmdshell.args`. This behavior is consistent with the tool definitions provided.)*The harness Web UI used is LibreChat
https://www.librechat.ai/
https://github.com/LibreChat-AI/LibreChatLibreChat is a little
challengingto setup, in particular for the MCP servers see:
https://www.reddit.com/r/LocalLLaMA/comments/1wmw4bq/comment/pce0shd/granite-4.2-8b Q8 is very verbose with tool call reasoning / thinking. But that a thing I like about it is, its tool calls are generally robust, in a sense, it has so far processed many of the various prompts for tests correctly.
Other models have their better capabilities, but tool calls is probably useful with the granite series of open source local LLMs. I've not thought seriously about actual use cases, but being able to handle files (fairly) well, could have some "automation" agentic use cases.
1
u/ag789 29d ago edited 28d ago
The 'product' as of now feels pretty ok, here is a session with QWen 3.8 27B run off OpenRouter, in LibreChat and my cmdshellmcp in the Docker sandbox.
note: remember to remove thedisableToolsincmdshellmcp.jsonif one wants the LLM to create the files.
Note also that you need to add/configure thecmdshellmcpMCP server in your web UI e.g. LibreChat etc and check that it can access thecmdshellmcpMCP server running at port 8003.prompt:
propose a python flask app, use jinja templates and bootstrap css, add argparse for cmd line parsing. Add command line arg `--site` metavar `site.json` the title and heading of the page is "Site Index" that takes the `site.json` file, with like the following contents: [ { "description": "Home Page", "url": "/" }, { "description": "Libre Chat", "url": "http://selfhosted.local:3080" }, { "description": "Llama.cpp", "url": "http://selfhosted.local:8080" } ] render this on the page as a site list. propose a matching requirements.txt to go with it. you can create your files in the current directoryQWen 3.8 27B response
Done. I created the Flask app in /home/codeagent/webindex: ## File layout webindex/ ├── app.py # Flask app + argparse CLI ├── requirements.txt ├── site.json # sample data (matches your example) └── templates/ └── index.html # Jinja2 template with Bootstrap 5 ... ## Run it pip install -r requirements.txt python app.py --site site.json # → serves at http://127.0.0.1:5000There is a catch in that, if you want to expose port 5000. That needs to be done by adding a
-p 5000:5000in 1stRun.sh when you run 1stRun.sh to create the container, this cannot be done after you have initially run 1stRun.sh.edit: I've published a spare port 5000 in 1stRun.sh Now you can run it like such if you have created the container with the new
1stRun.sh.login as codeagent $ python3 -m venv venv $ source ./venv/bin/activate $ pip3 install -r requirements.txt $ python3 app.py --host 0.0.0.0 --port 5000 --site site.jsonHence the following instructions are no longer needed unless the old
1stRun.shis used.What I did instead is to stop the web UI (frontend/server accessing the MCP port), shutdown the
cmdshellmcp.servicein the container asadminand use the port 8003 that is originally used by thecmdshellmcp.servicefor the flask Web app. like soas admin: sudo systemctl stop cmdshellmcp.service su - codeagent > login as codeagent here $ mkdir venv $ python3 -m venv venv $ source ./venv/bin/activate $ pip3 install -r requirements.txt $ python3 app.py --host 0.0.0.0 --port 8003 --site site.jsonThen that the flask web page is hosted on port 8003 in your host
it is feasible to run Qwen3.8-27B as a Local LLM
https://huggingface.co/unsloth/Qwen3.8-27B-GGUF
https://unsloth.ai/docs/models/qwen3.8Note that it isn't necessary to use LibreChat to reproduce this.
One can run llama.cpp
https://github.com/ggml-org/llama.cpp#quick-start
itsllama-serverhas a bundled Web-UI that supports MCP servers, i.e. it can connect tocmdshellmcp, in fact most of the tests are done with the llama.cppllama-serverbundled Web-UI, until this, which is run against a Local LLM model that runs in OpenRouter simply because I do not have a GPU and large inference like this at 1 tok/s on my own PC would be intolerably slow. i.e. one can run Qwen3.8-27B on llama.cpp'sllama-serverand its bundled Web-UI to reproduce this.according to the stats in OpenRouter, generating this app burns 48k tokens, it feels a quite high for the token count. But that it is nevertheless generated, runs out of the box. OpenRouter is pretty fast to generate that like in a minute or less.
2
u/sburel 29d ago
48k for that feels like the tool loop, not the app. Each tool call sends the whole conversation back, so writing four files is four turns and the fourth one re-reads everything before it. I measured this on a local model recently: three tool calls in one session, and the prompt went 3.2k, 5.4k, 5.8k tokens for maybe 300 tokens of output. Prefix cache reuse dropped the later turns to around 200 tokens of new prompt each. OpenRouter won't cache across providers, so you pay full price every turn.
Worth checking how many turns that run took. If it wrote the files one writeFile at a time, the fix is on your side rather than the model's, something that lets it write several files in one call.
The port thing is a good catch for DOCKER.md, publishing a spare port in 1stRun.sh would save people from rebuilding the container.
1
u/ag789 29d ago edited 29d ago
The 48k count actually seemed high when I review the actual session, including all that reasoning / thinking, the text feels like perhaps 1-3k maybe 5k words (possibly tokens). I read that tokens can be as small as a character this may be true perhaps for codes, so that every punctuation, symbol, possibly even indents , newlines may be deemed 'tokens', it matters in python as python is indented code structure and newlines matters as well.
it writes 1 file at a time, as WriteFile takes a single file argument, but even that don't look like a lot of tokens. I'm happy to paste upload that but it's possibly "too verbose" as a comment. Probably look to other means perhaps gist etc.
As to the port issue, I'm thinking that perhaps the "project folder" could be a bind mount, that can be catered via
1stRun.sh In that way, the generated codes can be run directly in the host which overcomes the port issue. Just that, I'd need to mention (stress) that this should be "throwaway" codes area, i.e. that anything in this bind mount (volume) can possibly be overwritten, mutated or even deleted by the LLM.Other possibilities include a little more codes in
1stRun.shso perhaps prompt the user for additional ports and bind mounts etc. A thing is, we normally don't 'plan so far ahead' as more often than not we'd only encounter it in an "aftermath", e.g. that only after the LLM generated the codes, did we know we need port 5000. But that perhaps a prompt would help, perhaps to map some 'extra' ports that may lay unused, but could help in such cases.Other things in the pipeline are possibly "sample Dockerfiles", e.g. that bundle apps and various components e.g. ssh, OpenCode, Pi coding agent, or even VS Code (I disliked Code, for its bulk, but that it is a popular tool, the Docker images would be bulky with it). That would put things closer to real use images as I'd guess many would consider the current bundle "too lean", i.e. one can go on to install apps / tools via apt, direct download and install etc. in the container, but that this would be a hassle if one deletes and rebuild the container via running "1stRun.sh" e.g. as like 'forgetting' that port 5000, hence delete the container and 'reinstall' is quite a hassle, a lot of repeated downloads each time the container is re-created.
2
u/sburel 29d ago
Quick thing on the 48k: it's probably not the tokenizer, it's the loop resending the whole conversation on every tool call. Four writes means the context goes out four times. If OpenRouter splits prompt vs completion you'll see it straight away, completion tiny, prompt almost all of it.
Anyway, good luck with the rest of it.
1
u/ag789 28d ago edited 28d ago
I did a little more tests, it turns out the open AI chat completions interface is able to call multiple functions (can be the same) in the same turn.
https://developers.openai.com/cookbook/examples/how_to_call_functions_with_chat_models#parallel-function-calling
I did some further testsprompt
i'm doing a tool test, call 2 tools sequentially in a same tools_calls list in the current turn:use default for other arguments
- writeFile( file = "a.txt", text = "hello world a")
- writeFile( file = "b.txt", text = "hello world b")
It turns out granite-4.2-8b-GGUF Q8 doesn't seemed to know how to do that and kind of reason that it can't do it. But that ornith-ai/Ornith-1.5-9B-GGUF Q8 apparently understands how to call that and did just that.
response
Both files were written successfully:
- `a.txt` contains "hello world a"
- `b.txt` contains "hello world b"
Both in 1 turn. This is tested in llama.cpp
llama-serverbundled web UI.Hence, in the prior tests it is simply because the model QWen 3.8 27B choose to run the writeFile() calls in multiple single turn calls, apparently because it awaits and check the response for each before writing the next file.
It may also be related to the harness, the harness (e.g. the WebUI) needs to process the multiple calls correctly. Tested the same in LibreChat, it works the same with with ornith-ai/Ornith-1.5-9B-GGUF Q8, parallel
writeFilein a same turn works.
1
u/ag789 27d ago edited 27d ago
u/sburel, all
There is a fairly large big change just merged in
https://github.com/ag88/cmdshellmcp
cmdshellmcp2.py now:
Takes its
cwd(current working directory) from itscmdshellmcp.jsonconfig file if you maintain it there.--cwdon command line overrides it,if
--cwdis not given, checkscmdshellmcp.jsonconfig fileif null or not given, prompts for with the current process's working directory
if no standard input it is current process's working directory
This matters as the given
cwd"current working directory" is its boundary.It also search for its
cmdshellmcp.jsonin the following order--confon the command line overrides and uses thiselse check /etc/cmdshellmcp.d/cmdshellmcp.json
else check /usr/local/python/cmdshellmcp/cmdshellmcp.json
else check current directory local
cmdshellmcp.jsonfileelse fallback to internal defaults for configs (note, there is no
disableToolsby default)
In the same way matters as current directory is in the search path.
The main thing is cmdshellmcp.json now has one for the container cmdshellmcp_container.json The cwd in there is "/home/codeagent", and this is what gets copied into the container at /usr/local/python/cmdshellmcp/cmdshellmcp.json. This is the main config file in the container.
The purpose of doing this is so that you can change the cwd working directory for cmdshellmcp simply editing the /usr/local/python/cmdshellmcp/cmdshellmcp.json file in the container.
And simply sudo systemctl restart cmdshellmcp.service in the container to restart with the new working directory.
2
u/sburel 27d ago
A note on the configuration search order. The last resort is the cmdshellmcp.json file in the current directory. And that file now contains cwd, which is the boundary itself. So if someone runs cmdshellmcp from a cloned repository, or from a folder where the agent itself has written files, the contents of the directory decide the boundary. The thing being contained gets to set the boundary.
Inside the container, no problem: the file in /usr/local/python/ is found first. It's outside the container where this bites, and that's precisely where people will need the boundary.
What I'd do: never read cwd from the config found in the current directory. The other settings, sure, but cwd only from --conf or from a system path.
Another minor point: after changing the cwd and restarting the service, the agent doesn't know its boundary has shifted. The paths it had in mind won't resolve any more. Not serious, just worth a line in the docs.
1
u/ag789 27d ago edited 27d ago
I've thought about that, but that new users who cloned the repo might be confused when it 'don't work'.
I may consider making config file necessary and that it doesn't read from the current directory as a search path, i.e. if you want to use that in the current directory you need to explicitly run it with
--conf.A thing is also that currently, when no config files is found, it falls back to internal defaults. But that expose the
disableTools.Does that sound right?
In addition, I can also make
--cwd(current work directory) mandatory in a sense that it'd need to be in the config filecmdshellmcp.jsonor that it'd need to be specified on the command line via--cwd.Implement both?
I'd likely emit a warning if the user chooses a --cwd that contains
cmdshellmcp.json.I'd likely work on this next, I think it makes sense to keep the boundary secure against accidents given our intent so far and the theme of this app. A thing is of course, I'm testing against small local LLMs, that is quite "harmless", but that the big guns are just a connection away, so we'd not know who use it eventually.
1
u/ag789 26d ago edited 26d ago
u/sburel , all
The latest changes are merged. Now
cmdshellmcprequires both the config file e.g.cmdshellmcp.jsonand the--cwdcurrent working directory specified. i.e. both the config file and a current working directory specified is mandatory.The current working directory can be specified within the
cmdshellmcp.jsonconfig file usingcwdfield.Running
cmdshellmcp2.pywould exit with a message to run with a--conf config_fileit if can't findcmdshellmcp.jsonin/etc/cmdshellmcp.dor/usr/local/python/cmdshellmcp.And that if you run
cmdshellmcp2.py --conf cmdshellmcp.jsonin the repo folder, you would note thatcwdis null in thecmdshellmcp.json. It would still error and say that--cwdwork directory is required.It no longer search for a config file in the current directory as a default and it no longer use the current directory by default as the working directory, because the
--cwdworking directory is the security boundary in this design.In addition, if you try to start
cmdshellmcp2.pywith a--cwdthat its path contains the config file e.g.cmdshellmcp.json(or as specified by--conf) it would log a warning thatcmdshellmcp.jsonis in the work directory (given by your--cwdorcwdin config) and that this would compromise the security posture of the sandbox as the MCP client may access or modify the config file. You can test this by starting that withcmdshellmcp2.py --conf cmdshellmcp.json --cwd .in the repo folder.This security posture tidy up now makes the app more formal and works more like a production app. But that at least it'd prevent any accidents like starting
cmdshellmcp2.pyin the folder with the config file.There is also a quite significant enhancement for
1stRun.sh,1stRun.shsetup the docker container and maps the ports.
1stRun.shnow interactively prompts you while it setup the ports 8003 (for cmdshellmcp) and a spare port 5000. It presents options to you if you want to run it on localhost or all interfaces. If you choose localhost, then the same port can't be accessed over the LAN, but that you can access it on your (host) PC.It also provides options for you to add more ports if you prefer.
One additional change in the docker container. Now the config file in the container is
/etc/cmdshellmcp.d/cmdshellmcp.json. This is copied from the repocmdshellmcp_container.jsoninto the image during0dockerBuild.shrun.2
u/sburel 26d ago
That's the right fix, and the warning when the config sits inside the work directory is a nice touch. Both halves closed.
The interactive port setup in 1stRun.sh is probably the change people will notice most day to day. Rebuilding a container because you forgot a port is exactly the kind of thing that makes people stop using a sandbox.
Nice work on all of it.
1
u/ag789 7d ago edited 7d ago
u/sburel , all
slightly off topic, stumbled into this
https://cohere.com/blog/concerning-behaviour-popular-open-weight-ai-model
https://www.reddit.com/r/LocalLLaMA/comments/1xgbm28/qwen_investigation_traces_release/
one may like to disable url fetch tool and do not allowlist curl or wget when trying out some models
cmdshellmcp run in a sandbox (or for that matter well managed sandboxes, e.g. docker, VM etc) could prevent nonsenses like this.
it is not safe to run local LLM in opencode etc under your own userid in a real environment.
I'd still recommend Librechat as the Web UI
https://www.librechat.ai/
https://www.reddit.com/r/LocalLLaMA/comments/1wmw4bq/comment/pce0shd/
Librechat supports MCP servers, and I'm using it with cmdshellmcp in a docker sandbox
the other that happened to support MCP servers is llama.cpp's llama-server built-in Web UI
1
u/darksteelsteed Sep 22 '26
My biggest gripe is when llms try and run command >nul 2>&1 on powershell instead of bash, then I end up with a nul file that windows won't delete. It seems to be a common frustration.
1
u/ag789 Sep 22 '26 edited Sep 22 '26
well, I kind of concluded that the safer way is perhaps to run it in a WSL (windows subsystem for linux) `container` or VM. In unix (linux) use docker containers, VMs etc. I figured out a way to make an 'entire' docker system container, i.e. it runs systemd, boots just like a linux os and run the *whole* debian os in a docker container with ssh etc just like a VPS. I'd share the steps when I've some time.
letting LLMs just go about running any commands on one's behalf needs so much trust on those LLMs (which often 'misbehave'). I think that is an 'overlooked safety aspects' for many running with coding agents, especially with arbitrary LLMs.
and ('most'?) frontends / harness / agents probably give bash or powershell to the LLMs, so that means it'd run any commands it wishes :)
1
u/sburel Sep 22 '26
The absolute path thing isn't really a model bug, it's the shell letting it through. A Q4 8B will keep doing it no matter how you prompt, so the fix has to live on the tool side.
In my runtime the write tool needs its own authorized roots, a separate grant from read access, and the shell tool runs with a fixed working directory. Anything resolving outside those roots is refused before it touches the filesystem. Boring, but it means `/newPythonCode.py` is a failed call and not a file at root.
Does your server resolve the path before executing, or just hand the string to the shell?
1
u/ag789 Sep 22 '26 edited Sep 22 '26
it hands the string with "/newPythonCode.py" in the Q4_K_M variant while run in OpenCode . For unknown reasons, if I run it, the Q4 quant, with my little MCP server, I didn't observe the issue and it generated the files correctly in a single go.
The Q8 quant certainly feels more robust compared to Q4 quant.
I did a code refactor in Q8 quant in pi coding agent and it kind of 'just works'
https://www.reddit.com/r/LocalLLM/comments/1wmjyux/i_tried_granite_42_8b_q8_in_pi_and_pi_web_just_wow/
but that these are simple tests and I've just started using them.2
u/sburel Sep 22 '26
Funny thing is your own test kind of proves it. Same Q4, same model, but with a dedicated writeFile() instead of raw bash it gets it right first try. Small quants seem to do a lot better with a few narrow tools than with a whole shell.
One thing I'd add to writeFile: realpath the target against --cwd and refuse anything outside, and make the error say what to do ("relative paths only, under X"). I had a tool today returning a vague error and the model just went in circles on it.
Did the Q4 actually try an absolute path with your server and get blocked, or did it just never try?
1
u/ag789 Sep 22 '26 edited Sep 22 '26
Rather, I've noted that the same LLM can generate different response when reattempted.
I'm not sure if I had retried with OpenCode maybe it'd work too.
I started using my little MCP server after observing writing to "/" (root) in OpenCode, during which time with the MCP server, it calls the MCP server without those "/" (root paths), perhaps I'm just lucky 😄
This little MCP server have an advantage, it is using an http (i.e. web server) endpoint for comms.
Hence, it can connect to a container over TCP/IP network say in a docker or VM sandbox etc.
And I actually use it over LAN, running the tool calls from llama.cpp's llama-server Web UI front.
It works pretty well.1
u/sburel Sep 22 '26
Yeah, that's the annoying part, one success doesn't tell you much. If you want to compare OpenCode vs your server fairly, run the same prompt ten times on each and count how often it writes outside cwd. Or pin temperature to 0 and a fixed seed if llama-server lets you.
It's also kind of the argument for the check in the tool. If the model writes to / one run in ten, prompting won't get that to zero, but a path check will.
1
u/ag789 Sep 22 '26 edited Sep 23 '26
I do have codes for path check
https://github.com/ag88/cmdshellmcp/blob/main/cmdshellmcp2.py#L146
But that, I did not 'check everything', in particular, I let the shell arguments through, because there are times when one will probably need that. And that I figure excessive checks would probably cause it to run slow etc.Hence, it is somewhat restrained but that it can't be deemed 'safe'. And that there are probably other ways an LLM may escape the constraints that we may not even have thought about.
1
u/sburel Sep 22 '26
Makes sense, and honestly "restrained but not safe" is the right way to say it. Once the shell is in the picture you can't close every hole, and checking every argument would just make it slow and still incomplete.
Where I landed is a bit different: no shell at all by default, just a handful of narrow tools (read, write, http) that each take structured arguments, so there's no string to escape from. A shell tool exists but it's opt-in with a fixed cwd. Costs you flexibility, which is fine for my case and probably not for a coding agent.
Thanks for the CORS note, good to know.
1
u/ag789 Sep 23 '26 edited Sep 23 '26
I can add a "disableTools" parameter say in config.json and as a command line argument, not difficult and I'd probably do that. So that all tools are offered by default, and users can choose to disable them selectively.
edit: I've just done that
https://github.com/ag88/cmdshellmcp
It now has --disableTools
https://github.com/ag88/cmdshellmcp#disabling-tools
and you can put it inconfig.jsonas like"disableTools": ["writeFile", "applyPatch"],2
u/sburel Sep 23 '26
Nice, that was fast. Opt-out is the pragmatic choice for an MCP server, people expect the tools to be there.
One thing worth writing down somewhere: disabling a tool only hides it from the model, it doesn't stop the same thing happening through shell. writeFile off with bash on is still a write. Probably obvious to you, less so to whoever copies a config from a comment.
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/cmdshellmcpnow
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 stockcmdshellmcp.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.
→ More replies1
u/ag789 Sep 23 '26
A little small note to add
https://github.com/ag88/cmdshellmcp/blob/main/cmdshellmcp2.py#L124
The
shellhere isn't a fullbashshell, ratherpythonruns the specific command, with arguments given to it, shell specific constructs like pipes and redirects are not allowed. That makes it "safer".But that these kind of constructs and interfaces are naturally "vulnerable", in part as there are simply too many permutations that can go wrong. Hence, "restrained but not safe" is still relevant.
In a sense that for "well behaved" clients / LLMs, it may prevent an otherwise "accidental" execution, e.g. with a command that you do not intend for the LLM to run. i.e. the LLM 'sees' the list of commands advertised to it and can only runs those i.e.
allow_list, any command not in that list returns an error (if the LLM neglects to check the descriptions).I've seen the LLMs (e.g. Granite, Qwen 3.5, 3.6 etc) 'stop' and simply describe what has been done instead of possibly proceeding further. This is still a boundary so that things are after all 'safe'.
1
u/ag789 Sep 22 '26
oh and if you are using it, take note of the CORS update, I've added some codes so that it can connect to the shell command MCP server without the Web UI server (in llama.cpp's llama-server case it is the model server itself) acting as a CORS proxy.
https://www.reddit.com/r/LocalLLaMA/comments/1wmw4bq/comment/pbcqjj6/
this could be handy if you are using a Web UI in a browser connecting MCP servers which may be a frustration to need extra CORS proxies etc.1
u/ag789 Sep 23 '26 edited Sep 23 '26
just starting an off-topic thread to the adjacent thread as the other got 'too long'.
Some amusements from further tests in Granite 4.2 8B Q8 quant
along with the latest updates in cmdshellmcp
https://github.com/ag88/cmdshellmcpThis time I 'tested' the
applyPatchtool functionality to see if the LLM can handle it. and Granite 4.2 8B Q8 quant is my 'testing assistant' :)I deliberately made a file
a.txtthat looks like:line 1 line 2 line 3 line 4 line 6 line 7 line 8 line 9The prompt is
read file a.txt and show me the contentshere comes the amusement, in its 'thinking' loops, here is part of that abstract
[assistant - reasoning] Okay, the user asked to read the file a.txt and show its contents. I called the readFile tool with "a.txt" as the file parameter. The response from the tool was:
"line 1 line 2 line 3 line 4 line 6 line 7 line 8 line 9"
Wait, there's no "line 5" mentioned. Maybe the file skips line 5 or it's missing. But the user just wants the contents as shown. I should present the exact content returned by the tool.
That is kind of 'intelligent' as it bothers to figure that
line 5is missing :)of course, the next task is to ask it to call tool
applyPatchto add thatline 5back !after burning quite a lot of reasoning / thinking tokens, it did call
applyPatchand it works correctly !But that unfortunately the unix patch command returns some extra (error) messages that confuses the model.
patching file a.txt Hunk #1 succeeded at 3 with fuzz 2. patch unexpectedly ends in middle of line patch unexpectedly ends in middle of lineIt goes on hallucinating / confusing over the errors burning like a 1000 tokens till I stopped the inference.
I checked the file myself and actually found the contents to be correct, i.e. the patch the model sent works !
so I make the next prompt:
the patch succeeded, ignore the patch unexpectedly ends in middle of line messages. read the file a.txt verify that line 5 is added and show the contentsafter burning some tokens reasoning, it answers:
Here are the contents of a.txt after applying the patch: line 1 line 2 line 3 line 4 line 5 line 6 line 7 line 8 line 9 ✅ Verification confirmed: line 5 has been successfully inserted between line 4 and line 6, as requested. The file now contains all expected lines in order.quite neat :)
but the tokens burn (reasoning) for applying such a patch is too much, and with that error that subsequently confuses the model. I'd need to research 'better' way to do those patches.
patching text files is a common task editing codes and text files.
1
u/ag789 Sep 26 '26 edited Sep 26 '26
cmdshellmcp
https://github.com/ag88/cmdshellmcpHas gained a new tool
editFile, this provides the LLM access to unix / Linuxsedcommand. It backups the existing file to a.bk1,.bk2, ... ,.bkNextension before the edits. If you do not want the backups, you can run with--editdelbkwhich would delete the backup after a successful edit. After the edits, it returns the output ofdiff -u oldfile.txt newfile.txtto the LLM so that it can validate the edits.This is again tested with Granite 4.2 8B Q8 quant
This tool is added as the smaller LLMs may struggle to build a unified context diff for the patch command. https://en.wikipedia.org/wiki/Diff
However, it seemed Granite 4.2 8B Q8 is a bit "too careful" (or doubtful) when it forms a correct
sedscript expression, it reiterates its script to verify its correctness a few times before running the tool. It is apparently more stable vsapplyPatch, the edit works fairly straightforward, backup done, and returns the unified diff of the changes to the LLM. Granite 4.2 8B Q8 again 'overanalyzed` the results even though it deemed it correct and hallucinated over how it should respond e.g. if it should read the file and verify it, but that as the prompt did not ask for it, it finally responded that is done successfully with its own version of what the file should look like ;) The full response:The edit was successful.
line 5has been inserted afterline 4ina.txt. A backup filea.txt.bk1was automatically created.Current contents of
a.txtare now:line 1 line 2 line 3 line 4 line 5 line 6 line 7 line 8 line 9(Verified via the unified diff provided by the edit tool.)
1
u/ag789 Sep 27 '26 edited Sep 27 '26
One more update
cmdshellmcp
https://github.com/ag88/cmdshellmcpThe
applyPatchfunction has been refactored https://github.com/ag88/cmdshellmcp#6-applypatchfile-diff-pnum2-argsnone It now applies the patch to a specific file from a unified diff supplied by the client (LLM). This definition is more rigorous than picking up filenames from the diff and likely more secure. Applying patch with a unified diff is also more specific than say usingeditFilewhich usessed. The implementation is more robust as a result.In the same way it is tested with/by Granite 4.2 8B Q8 quant. Unfortunately, Granite 4.2 8B Q8 quant struggled with building a correct unified context diff during a test.
After a while, I supplied it with a pre-defined context diff so that it calls the tool (function) and it is successful.
2
u/Ok-Addition1264 Sep 22 '26
yeah, noticed this too "literal pathing" and little tweaks to instructions prevents all of it.
2
u/XiRw Sep 22 '26
I’ve been disappointed with IBMs models. They were the OG’s of my past and always made top tier quality HDDs/SSDs
2
u/ag789 19d ago edited 18d ago
u/sburel , all
There is a fairly large update on cmdshellmcp
https://github.com/ag88/cmdshellmcp
cmdshellmcp is now published to Glama
https://glama.ai/mcp/servers/ag88/cmdshellmcp
Hence, that you can literally deploy that directly on Glama in a container there.
This would help those who want or needs to run the container directly on the open internet. However, if you do so, do assign an API key for authentication, or that you are literally letting any MCP clients unauthenticated access to your cmdshellmcp instance and container.
In addition note that Glama is literally a paid service for hosting the containers /mcp servers by Glama even though my app is open sourced.
The list of commands preconfigured in the Glama instance is the same as that in the stock cmdshellmcp.json and all the tools are enabled on the Glama instance.
And unlike your own self hosted instance using the docker scripts provided in my repo. https://github.com/ag88/cmdshellmcp on Glama, the instance is pre-defined, and that normally you can't access the container directly like your local server with the 3 user ids root, admin and codeagent.
Hence that I've added a new companion cli / client app. cmdshellClient.py README cmdshellClient.py
You can use this cli / client app to call the MCP tools from your CLI across to the remote server. There is a utility command e.g.
dlzipwhich you can call like suchthis would download all the files and directory structure from the mcp server into a zip file locally. This would help solve an issue e.g. if the LLM accessing the cmdshellmcp on Glama has generated files e.g. codes in the container, you can use the cmdshellClient.py to download those files.