Releases: sassyconsultingllc/SassyMCP
Release list
v1.10.2 — Full-stack cockpit + concurrency fix
Rolls up the two features from this line: the concurrency fix (1.10.1) and the new Control Panel cockpit (1.10.2).
Control Panel cockpit — read-only tool visualizers
Five new tabs turn the operational tools an LLM rarely calls into a live dashboard, all in the server-served panel (works in Claude Desktop and any browser):
- Server — health, live metrics, tool usage/stats, recent calls (every tier)
- Network — netstat, listening ports, ARP
- Processes — system info, process table, autostart entries
- Security — Defender, firewall, event log
- Screen — live screen glance (image), windows, OCR
A generic renderer auto-detects each tool's output shape — table, image, key/value card, or text — so new read-only views are a one-line entry. Hard allowlist: the panel can invoke only read-only tools (no shell, no writes, no selfmod). Enable with sassy_panel start.
Concurrency fix (from 1.10.1)
Two chats calling tools at once no longer wedge each other. Blocking tool bodies (SQLite, file I/O, screenshots/OCR) are offloaded to worker threads instead of stalling the single stdio event loop. See CHANGELOG for detail.
Assets
sassymcp-v1.10.2.mcpb— Claude Desktop bundle (install this)sassymcp-v1.10.2.dxt— byte-identical, for older clientssassymcp.exe— Windows standalonesassymcp-1.10.2.vsix— VS Code extension
No macOS binary in this release (GitHub Intel runner unavailable); Windows + bundle carry the features.
v1.10.1 — Concurrency fix: multi-client tool calls no longer wedge
Concurrency fix — two chats calling tools at once no longer freeze each other
Claude Desktop multiplexes every chat onto one stdio process / one asyncio event loop. Previously, synchronous blocking tool bodies (SQLite, file I/O, screenshots/OCR) ran inline on that loop, so one chat's in-flight call starved every other client's call until it timed out — the "two chats wedge each other" symptom.
Fix: the audit wrapper now offloads synchronous tools to a worker thread, and is_async is forced so FastMCP awaits the (always-async) wrapper even for def-declared tools. Any blocking tool written as a plain def is now auto-threaded and can never block the loop. Confirmed loop-blockers (state, memory, fileops, vision, crosslink, editor, audit) were converted; SQLite modules use fresh per-call connections.
Verified: 142 pytest + 151 script tests; memory & crosslink smoke-tested under 40–50 concurrent cross-thread calls. Full details in CHANGELOG.md.
Assets
sassymcp-v1.10.1.mcpb— Claude Desktop bundle (install this)sassymcp-v1.10.1.dxt— byte-identical to the .mcpb, for older clients that key on the old namesassymcp.exe— Windows standalone binarysassymcp-1.10.1.vsix— VS Code extension
Note: No macOS binary in this release — GitHub's Intel macOS CI runner was unavailable. Windows + the bundle carry the fix; a macOS build can follow when the runner frees up.
v1.9.0 — Permission engine (modes + sandbox jail + rules)
SassyMCP v1.9.0 — Permission engine (modes + sandbox jail + rules)
A new safety/control layer plus a long-standing interceptor bug fix. Existing installs are unchanged until they opt in — with no permission config set, behavior is derived from your current interceptor.destructiveAction.
Added
Permission policy engine (sassymcp.policy). One decision point for "may this operation proceed?", folding the destructive-command classifier, the protected-path guard, and runtime config into four modes:
- strict — block destructive patterns everywhere (the prior default)
- confirm — destructive patterns return a confirm token
- sandbox — relaxed gating inside configured project roots; any path resolving outside the jail is refused (including
../ symlink escapes). The "run an ungated model, but confined to the project folder" mode. - bypass — allow everything except protected paths (explicit, audited)
A Claude-style allow / ask / deny rules layer (tool-glob + path-glob + command-regex; first match wins) overrides the mode default. The catastrophic block-list (format/mkfs/…) and the protected-path invariant (the SassyMCP source tree + ~/.sassymcp) hold in every mode, including bypass.
sassy_permission tool — drive it all from chat: status, set_mode, add_root, remove_root, add_rule, clear_rules.
Fixed
The shell interceptor treated piped/compound deletes' command names as files. The delete-target parser ignored pipeline and statement boundaries, so a piped or chained delete (e.g. enumerate-then-delete) scooped up the command name and the | / && operators as "files to move" — and sometimes moved the wrong files. Now only a bare leading delete auto-stages (parsing targets from its own statement); embedded deletes route to the normal block/confirm path.
Build
build-mcpb.ps1 now emits a .dxt copy alongside the .mcpb (identical format) so older Claude Desktop builds — and any client still keyed to the old extension — can install the same bundle.
Tests: 122 pytest + 151 interceptor + 15 policy, all green.
v1.8.1 — Dependency maintenance (Dependabot alerts cleared)
Patch release: all open Dependabot alerts implemented and the binary rebuilt.
Python (uv.lock): cryptography 48.0.0→49.0.0, starlette 1.0.1→1.3.1, python-multipart 0.0.29→0.0.32, pyjwt 2.12.1→2.13.0.
VS Code extension (build deps): @vscode/vsce 2.27→3.9.2, markdown-it 14.2.0, undici→7.28.0, form-data→4.0.6 — 2 high-severity advisories cleared, npm audit clean.
Closes #15 #16 #17 #18 #19 #20 #21. No tool-surface or config changes — drop-in over v1.8.0. Verified: full pytest suite green; frozen-exe supervise + stdio init pass against the upgraded tree.
sassymcp-v1.8.1.mcpb attached.
v1.8.0 — Process supervisor (sassymcp supervise)
v1.8.0 — Process supervisor (sassymcp supervise)
What changed
SassyMCP can now supervise its own runtime. sassymcp supervise start
runs the HTTP bridge (and, optionally, the cloudflared tunnel) as managed
children, keeps them alive with health-checked restart-with-backoff, and
guarantees no orphaned child survives the supervisor's death — even a
hard kill -9 / TerminateProcess. That removes the two long-standing
failure modes: an orphaned bridge holding a wedged SQLite/WAL lock (the
old launcher taskkill /f'd the bridge by PID), and a hung-but-alive
bridge that Windows Task Scheduler can never detect.
The bug / motivation
Nothing in the package owned the runtime tree. The bridge + tunnel were
launched by .bat files; shutdown was a netstat-parsed taskkill /f
(no graceful stop → wedged WAL locks); the only "restart" was Task
Scheduler, which fires on exit but never on a hung process. Terminal
sessions and scrcpy were orphaned on any hard exit. See the lifecycle
audit in the v1.8 design brief.
The fix / approach
sassymcp/_jobctl.py— the orphan-proof primitive. Windows: a Job
Object withJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSEviactypes/kernel32
(deliberately notpywin32, which is only transitive and not in the
PyInstaller spec — so the guarantee holds in the frozen exe). POSIX: a
new session per child +killpg, plusPR_SET_PDEATHSIGon Linux.
terminate_tree()does a process-group-aware terminate→grace→kill.sassymcp/supervisor.py— theSupervisorloop: single-instance
pidfile (O_CREAT|O_EXCL+ create_time guard vs PID recycling), on-disk
child registry, HTTP readiness probe (any response = not hung), and
exponential backoff (cap 60s, give up after 5 consecutive restarts,
reset after 30s stable uptime). File command channel forstop/
restart. CLImain()dispatched fromserver.py:_dispatch_subcommand.SASSYMCP_SUPERVISED=1on the bridge child →selfmod._do_restart
exits and lets the supervisor respawn it instead of racing for the port.tests/conftest.py— autouse reaper of leaked test-child processes
(scoped to pytest's descendants) so a failed test can't wedge later ones.sassymcp.spec—sassymcp.supervisor+sassymcp._jobctladded to
hiddenimports (lazy-imported, so PyInstaller needs them declared).
Test coverage
tests/test_supervisor_lifecycle.py (10 tests): start→running→graceful
stop with no orphan; command-channel stop/restart; crash-loop backoff to
failed; --no-restart; pidfile single-instance + stale reclaim;
shutdown does not kill unmanaged neighbors; orphan-proof on hard parent
kill (the headline guarantee — Job Object on Windows, PDEATHSIG on
Linux); and Job-Object construction on the host. Validated end-to-end
against a real bridge child (boot → ready → graceful stop → no orphan)
and in the frozen sassymcp.exe.
Verification
python -m pytest tests/test_supervisor_lifecycle.py -qFrozen-exe / runtime checks (by hand):
dist\sassymcp.exe supervise start --tunnel-mode nonethen, in another
shell,dist\sassymcp.exe supervise status→running:true, bridge
ready;dist\sassymcp.exe supervise stop→ pidfile gone, bridge dead.- Hard-kill the supervisor (
taskkill /f) → the bridge child also dies
(Job Object), leaving no wedged lock.
Compatibility
- Purely additive. Existing launchers (
start-tunnel.bat, stdio mode,
the DXT/.mcpbmcp_config) are unchanged and unsupervised as before.
start-supervised.batis the new recommended launcher. - stdio mode is intentionally not supervised — the MCP client owns
that pipe. - Not yet wired (fast-follow):
server.py:_graceful_shutdowndoes not yet
reap in-bridge children (sessions/scrcpy); the personal autostart task
still launches the bare bridge. Documented in the design brief.
Files touched
sassymcp/_jobctl.py,sassymcp/supervisor.py— new (core).sassymcp/_paths.py— supervisor pidfile/registry/cmd constants.sassymcp/server.py—supervisesubcommand dispatch.sassymcp/modules/selfmod.py— supervised-restart handoff.sassymcp.spec— hiddenimports for the lazy-imported supervisor modules.tests/conftest.py,tests/test_supervisor_lifecycle.py— new.start-supervised.bat— new recommended launcher.
v1.7.2 — .mcpb bundle + first built release of 1.7.x
v1.7.2 — .mcpb bundle + first built release of the 1.7.x line
What changed
The desktop bundle is now an Anthropic MCP Bundle (.mcpb) instead of
a .dxt. Drag-drop install into Claude Desktop is unchanged for the user;
the difference is the manifest format (manifest_version: "0.2", the
deprecated dxt_version removed) and that the bundle is packed and schema-
validated with the official @anthropic-ai/mcpb CLI. The shipped
sassymcp.exe was rebuilt from current main, so the binary now carries
the v1.7.1 install --client auto/all fix.
The bug / motivation
1.7.1 fixed the install --client auto exit-2 crash but was a source-only
tag — no binary was ever published, so a user installing from the last
distributed artifact still hit the crash. 1.7.2 is the first built
release of the 1.7.x line and closes that gap. The .dxt → .mcpb rename
also brings the bundle in line with Anthropic's current MCPB tooling.
The fix / approach
mcpb/manifest.json(wasdxt/manifest.json):dxt_version: "0.1"→
manifest_version: "0.2";server.{type,entry_point,mcp_config}
unchanged and schema-valid.scripts/build-mcpb.ps1(replacesbuild-dxt.ps1): stages
server/sassymcp.exe+manifest.json+ icon/README, syncs the
manifest version tosassymcp/__init__.py, thenmcpb validate→
mcpb pack→mcpb info. Packing via the CLI removes the manual
forward-slash zip workaround the old.dxtscript needed.scripts/gen-icons.pynow writesmcpb/icon.png.
Test coverage
tests/test_install.py— theauto/all/AUTO→ all-clients and
bogus→ exit-2 regression added in 1.7.1, re-run against this build.- Full pytest suite re-run for no regressions from the 1.7.0 mesh removal.
- Frozen-exe smoke checks run against the rebuilt
dist/sassymcp.exe
(not just the source): the install fix in the actual shipped binary.
Verification
The fenced block below is executed by scripts/verify-release.ps1.
# Regression test for the install fix carried into this build
python -m pytest tests/test_install.py -qFrozen-exe checks (run by hand against dist/sassymcp.exe):
dist\sassymcp.exe install --client auto --dry-run→ exit 0, lists clients.dist\sassymcp.exe install --client bogus→ exit 2 with the valid-name hint.- stdio
initializehandshake → returnsserverInfo. dist\sassymcp.exe --httpthenscripts/smoke-test.ps1→ local/mcpHTTP 200.mcpb info dist/sassymcp-v1.7.2.mcpb→ manifest valid,server/sassymcp.exepresent.
Compatibility
- No breaking changes for users. The
.mcpbinstalls into Claude
Desktop exactly like the old.dxt; tool surfaces, env, and the
mcp_configcommand are identical. - The bundle artifact extension changed
.dxt→.mcpb. Any external
link to the oldsassymcp-v*.dxtasset should point at the new
sassymcp-v1.7.2.mcpb.
Files touched
mcpb/manifest.json—manifest_version: "0.2", version 1.7.2 (moved fromdxt/).scripts/build-mcpb.ps1— new CLI-based packer;scripts/build-dxt.ps1removed.scripts/gen-icons.py— icon output pathdxt/→mcpb/.sassymcp/__init__.py,README.md,CHANGELOG.md— version bump.
v1.7.1
v1.7.0
What's Changed
- build(deps): bump starlette from 0.52.1 to 1.0.1 by @dependabot[bot] in #14
- build(deps-dev): bump tmp from 0.2.5 to 0.2.7 in /sassymcp-vscode by @dependabot[bot] in #13
- build(deps-dev): bump qs from 6.15.1 to 6.15.2 in /sassymcp-vscode by @dependabot[bot] in #12
New Contributors
- @dependabot[bot] made their first contribution in #14
Full Changelog: v1.6.0...v1.7.0
v1.6.0
v1.5.0
v1.5.0 — Frozen-exe completeness + Madame memory wiring
What changed
The shipped sassymcp.exe now actually contains the modules the source repo
claims it does. The PyInstaller spec was missing sassymcp.modules.combos
and sassymcp.modules.prompts (added in v1.4.0). Both modules are loaded
dynamically through __import__ at server startup, so PyInstaller's static
analyzer could not see them. The frozen binary booted, logged
Failed to register combos / prompts: No module named ... at WARNING, and
kept running with three combo tools and six slash-menu prompts silently
absent from the registered tool list.
v1.5.0 fixes that. Same source, same start-lan.bat / start-tunnel.bat,
same DXT + VSIX packaging. The exe is just whole again.
The bug
sassymcp/server.py:_load_modules iterates TOOL_GROUPS and calls
_import_module(name) which is a thin wrapper around
__import__(f"sassymcp.modules.{name}", fromlist=[name]). PyInstaller can
only freeze what it sees in the static AST; dynamic imports must be listed
as hiddenimports in the .spec. The combos and prompts groups were
added in v1.4.0 but never added to sassymcp.spec.
Empirical proof against the pre-fix frozen exe:
[INFO] sassymcp: SASSYMCP_LOAD_ALL=1 — loading allowed modules: [..., 'combos', 'prompts']
[WARNING] sassymcp: Failed to register combos: No module named 'sassymcp.modules.combos'
[WARNING] sassymcp: Failed to register prompts: No module named 'sassymcp.modules.prompts'
Tools missing from the registered set in the v1.4.x exes:
sassy_combo_pr_review,sassy_combo_phone_observe,sassy_combo_codebase_grep- Slash-menu prompts:
pr-review,phone-status,resume,codebase-grep,brain-status,setup-sassy
The fix
sassymcp.spec now lists three additional hidden imports:
'sassymcp.modules.combos',
'sassymcp.modules.prompts',
'sassymcp.modules._confirm',_confirm is reached statically via from sassymcp.modules import _confirm
inside shell.py, so PyInstaller already discovered it — listing it
explicitly is a belt-and-braces guard against future refactors that move
that import behind a lazy lookup.
Packaging surface (unchanged shape, version-stamped)
All five distribution channels SaS uses on a typical install are wired
through the unified release pipeline at .github/workflows/release.yml:
| Channel | Artifact | Pipeline job | Source of truth for version |
|---|---|---|---|
| EXE | sassymcp.exe |
build-exe |
sassymcp/__init__.py:__version__ |
| DXT | sassymcp-v1.5.0.dxt |
build-dxt |
dxt/manifest.json (auto-synced) |
| VSIX | sassymcp-1.5.0.vsix |
build-vsix |
sassymcp-vscode/package.json (auto) |
| HTTP | start-lan.bat → sassymcp --http |
(script) | EXE |
| Tunnel | start-tunnel.bat → cloudflared |
(script) | EXE + cloudflared config |
build-exe refuses to build if the git tag (vX.Y.Z) does not match
__version__. build-dxt auto-rewrites dxt/manifest.json to match
__version__ before packaging. build-vsix does the same to
sassymcp-vscode/package.json. One source of truth, three artifacts.
The HTTP and Tunnel modes are not separate binaries — they are launch
scripts that invoke the same sassymcp.exe with different transport
flags. start-lan.bat asks the operator whether to bind 127.0.0.1 or
0.0.0.0 (LAN mode prompts for or auto-generates an auth token).
start-tunnel.bat launches the bridge on 127.0.0.1:21001 and runs
cloudflared tunnel run <name> to expose it; the tunnel name is taken
from %1 or %SASSYMCP_TUNNEL_NAME%, no hostname is baked in.
Generalized for distribution (no vendor-specific defaults)
This release strips the production-deployment hostnames out of the
defaults. v1.4.x shipped with the vendor's own canonical hostname as a
default-allowed Host and as a default smoke-test target — fine for the
maintainer, awkward for a paying buyer running the same code under
their own domain.
sassymcp/server.pydefaultallowed_hostsis nowlocalhost, 127.0.0.1. Tunnel + LAN operators set
SASSYMCP_ALLOWED_HOSTS=mcp.<your-domain>.tld,localhost,127.0.0.1to
add their hostname. DNS-rebinding protection stays on by default; you
just have to declare what you're allowing.scripts/smoke-test.ps1no longer carries a default-RemoteUrl.
Without one it pings only the local bridge; pass-RemoteUrl https://mcp.<your-domain>.tld/mcpto verify your tunnel.sassymcp-oauth/wrangler.tomlwas committed with a real hostname,
KV namespace id, and upstream URL. It is nowwrangler.toml.example
with placeholder values, and the workingwrangler.tomlis
.gitignored. Deployerscpthe example, edit four fields, run
wrangler kv:namespace create OAUTH_KV, set the two secrets, and
wrangler deploy.docs/TUNNEL.md(new) walks a fresh operator through the full
Cloudflare Tunnel setup: cloudflared install, login, create, route
dns, ingress config, allowed-hosts env var, and an optional OAuth
Worker on top for hosted-Claude clients that require DCR/PKCE.
Test coverage
No new tests; the fix is a packaging change that is exercised at exe boot.
Existing test suites still pass. The verification block below re-runs the
high-signal suites and adds a direct boot-time check that the previously
missing modules now register.
Verification
# Existing high-signal suites
python tests/test_intercept.py
python tests/test_shell_auto_promote.py
python tests/test_tool_loader_boost.py
python tests/test_install.py
python tests/test_atomic_write.py
python tests/test_audit_io.py
python tests/test_db_helper.pyRuntime verification against a fresh build (run manually after CI
produces the artifact, or after a local pyinstaller --clean --noconfirm sassymcp.spec):
dist\sassymcp.exe --help→ exits 0, header readsSassyMCP Server v1.5.0.$env:SASSYMCP_LOAD_ALL = "1"; .\dist\sassymcp.exe --stdio(Ctrl-C after a
second) — scan stderr forRegistered module: combosand
Registered module: prompts. There must be no
Failed to register combos/Failed to register promptswarnings.scripts\build-dxt.ps1producesdist\sassymcp-v1.5.0.dxtand reports
the manifest version already matches__version__(no auto-rewrite).- From
sassymcp-vscode/:npx vsce package --out ../dist/produces
dist\sassymcp-1.5.0.vsix. - Cloudflare Tunnel end-to-end:
start-tunnel.batagainst the configured
named tunnel →curl https://<your-hostname>/mcp -H "Authorization: Bearer <token>"returns the MCP capabilities handshake, not a 421 or
403. Then callsassy_combo_pr_reviewover the tunnel — it should
resolve, which it could not in v1.4.x against a frozen exe.
Compatibility
- Any caller that already used the always-available v1.4.x tool set is
unaffected. - Callers that tried to invoke
sassy_combo_*or the slash-menu prompts
against the v1.4.x frozen exe got a "tool not found" error from the
server. v1.5.0 simply registers those tools as the source already
claimed. Source-mode (uv run python -m sassymcp.server) was never
affected; only the frozen exe needed this fix. - DNS-rebinding allowlist behaviour is the same as v1.4.2; the entry was
promoted from "tunnel-host gotcha" to "default allowlist" already in
c5efa6f. - No schema changes, no config migrations, no breaking changes.
Files touched
sassymcp.spec— three hidden imports added.sassymcp/__init__.py—__version__ = "1.5.0".dxt/manifest.json—version: "1.5.0".sassymcp-vscode/package.json—version: "1.5.0".sassymcp-vscode/CHANGELOG.md— 1.5.0 entry.installer.wxs—Version="1.5.0"(UpgradeCode unchanged).build-msi.ps1— reads__version__fromsassymcp/__init__.py,
stable UpgradeCode for MajorUpgrade, deterministic per-file Component
GUIDs, parameterized-SourceDir(defaults todeploy/).sassymcp/server.py— defaultallowed_hostsreduced to loopback.sassymcp-oauth/wrangler.toml→wrangler.toml.example; working file
gitignored.sassymcp-oauth/src/index.js— vendor hostname removed from comments.scripts/smoke-test.ps1—-RemoteUrldefaults to empty (skip).deploy/start-tunnel.bat— synced with root copy.docs/TUNNEL.md— new operator setup guide.docs/releases/v1.5.0.md— this file..gitignore—sassymcp-oauth/wrangler.tomladded.