volcano-separator
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@volcano-separatorcheck the Hindsight memory chain and heal anything that's broken"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Volcano Separator
A Windows workstation supervisor: it keeps a service reachable, and it makes the machine say what it is doing.
Every art asset and every piece of UI here is pixel art, window design included, and this tool's
pixel layer is its own: it borrows no font, icon or palette from a sibling project, because a
watchdog whose value is that it depends on nothing cannot depend on one. That rule is shared by four
tools and now lives in exactly one place — Nesarf/NTVI.
How far this tool has actually got with it, and the limit, is in
DESIGN-enforcement.md §12.5.
It started as one thing and grew into three, because the first job kept failing for reasons the second and third exist to explain.
Role | Question it answers | Commands |
Supervisor | why did my memory service go away, and can it come back? |
|
Observer | what is actually running on this machine? |
|
Incident response | something is hiding — what do I do about it? |
|
The problem it was extracted from
uvx (the package installer)
└─ an ephemeral env inside the uv cache ← the daemon's own code lives here
└─ the hindsight daemon ← spawned on demand by a host plugin,
└─ Postgres (pg0) only at session start / on retain
← the data lives hereThe state of a package installer decided whether the service could start, and the service's lifetime was decided by the host's session timing. When any link caught a cold, memory went away — silently.
Measured on the machine this was extracted from (volcano-separator doctor):
12 start attempts, 7 failed on timeout, 1 succeededEvery failure log showed Downloaded botocore / Built claude-agent-sdk==0.2.16x on the start
path. A build and a watchdog were racing each other — the plugin's watchdog allows ~180 s,
while a cold-cache uvx run on a spinning disk needs far more. It loses every time.
Related MCP server: Hermes Toolkit MCP
The fix: stage the work so no watchdog ever races a build
Stage | What it does | Watchdog |
warm | make sure the uv env is complete (idempotent) | none — it may take minutes; it runs to completion |
serve | start the daemon only once the env is hot | yes, but this is seconds now, so it cannot trip |
watch | health probe and repair on demand | heartbeat, near-zero cost when healthy |
The property that makes this work: heal costs about a second when healthy (one TCP probe), so
it can run every few minutes without a resident supervisor process — the scheduler does the
watching.
The other half of the fix is upstream of this tool: point UV_CACHE_DIR at an SSD, so even a cold
start is fast.
Install
git clone https://github.com/Nesarf/volcano-separator.git
cd volcano-separator
node bin/cli.mjs status # no install step: zero dependenciesOptionally put it on PATH:
npm link # provides `volcano-separator` and `vsep`Commands
Service
volcano-separator status [--deep] # whole-chain check; see the note on --deep below
volcano-separator heal [--custody] # intelligent repair: warm -> serve -> watch
volcano-separator warm [--force] # warm the env only (no watchdog)
volcano-separator serve # start the service only
volcano-separator stop | restart
volcano-separator doctor # count historical start failures from the plugin log
volcano-separator guard clean # is a uv cache operation safe right now?
volcano-separator install-service # register the watchdog task (at logon + every N minutes)
volcano-separator service # watchdog task state
volcano-separator resources [n] # free memory, CPU load, largest processes
volcano-separator defer [--wait] # exit 0 = go ahead, 3 = not nowstatus is read-only. The one probe that is not is the uv warmth measurement — it runs
uvx --with ... --help, and uvx builds an environment to run anything — so it is behind --deep
and reported as unmeasured otherwise.
Warmth itself is a question about completeness, not speed: the probe resolves with the
network off, so success proves the environment is complete locally and failure proves it is not.
It cannot download, which matters twice over — the probe cannot leave a half-populated cache
behind, and it cannot make a cold environment look warm by quietly filling it. An earlier version
timed a uvx run and called anything slower than 15 s cold, which meant a busy machine reported
a warm environment as cold and triggered the full warm-up the measurement exists to avoid. If the
probe runs out of time now it says undetermined, because a guess in that direction is expensive.
Activity
volcano-separator activity [n] # the system-wide process/window/persistence record
volcano-separator busy [minutes] # what has actually been running, grouped
volcano-separator ps # what is running now, with ages (spots a wedged process)
volcano-separator reveal windows # every top-level window; --show forces hidden ones visible
volcano-separator reveal process <pid>
volcano-separator reveal chain <pid> # inherited chain, recovered from history
volcano-separator redline [seconds] # what is sitting on C: in user-writable space
volcano-separator cache [--prune] [--apply] # uv cache: duplicates, old versions, idle envsIncident response
volcano-separator signals [minutes] # what looks like stealth, with evidence (observe-only)
volcano-separator decide [minutes] # what would be done about each signal (acts on nothing)
volcano-separator policy show|allow|deny|mode
volcano-separator detain <pid> # freeze it, force its windows open, open a custody window
volcano-separator release <pid> # resume a detained process
volcano-separator detained # what is under custody, reconciled against the live system
volcano-separator timeline # the life of each custody decisionOptions: --profile <name> (default coding-agent), --port <n> (default 9077), --json,
--quiet, --force, --dry-run.
Interfaces
Form | Entry point | For |
CLI |
| humans, scripts, the scheduled task |
Library |
| other tools (pure Node, zero dependencies) |
MCP |
| any harness — 17 tools, listed below |
The MCP surface is read-only apart from heal, and that is the design rather than a stage of
completion.
Read-only | |
service |
|
machine |
|
record |
|
detection |
|
custody |
|
cache |
|
repair |
|
correlate |
|
volcano_heal is the one that starts something: it repairs a service the agent is usually the
reason for needing, removes nothing, and the worst outcome is a slower path to the same place.
It does not block. A healthy service is answered in about 50 ms; a repair that is actually
needed is handed to its own process and the call returns a receipt, which volcano_heal_status
follows. That split is what let this server's tool timeout come down from thirty minutes to five —
before it, every one of these sixteen tools shared a timeout chosen for the slowest possible repair.
There is no volcano_detain, volcano_release, volcano_cache_apply or volcano_policy_allow.
Freezing a process or deleting cache entries are decisions a human should make, and cache_plan
exists so an agent can show a person exactly what a prune would remove and let them run it. An
agent that can act on its own is a worse failure than one that has to ask.
MCP client config:
{
"mcpServers": {
"volcano-separator": {
"command": "node",
"args": ["/path/to/volcano-separator/lib/mcp.mjs"]
}
}
}Real traps it handles for you
1. The daemon's code lives inside the uv cache.
So uv cache clean / prune is blocked by the running daemon's in-use lock, and --force deletes
the running daemon's own files out from under it.
volcano-separator guard <op> checks first and prints the safe order:
stop -> uv cache <op> -> heal.
2. A daemon started by the host plugin is not recognised by the embed manager.
hindsight-embed daemon stop tracks its own PIDs; a daemon the host plugin spawned itself is not in
that record, so:
WARNING - Could not find PID for port 9077
WARNING - Port 9077 is bound but no hindsight daemon could be identified on it
Failed to stop daemonstop therefore has two levels: the official path first, then find the port owner and collect its
process tree. Without that layer, one plugin-spawned daemon would make the supervisor permanently
unable to stop it.
Collecting a process tree is the most destructive thing here, so the identity check is not "the
image is called python.exe". That matches a python from anywhere, and the anchor is only "something
is listening on 9077" — which is not by itself proof of what it is. A chain member has to be a name
the daemon uses and name the daemon or the port in its command line, which every member of the
real chain does. Anything else stops the walk and is reported with its path and the reason:
refused to kill pid 5136 (node.exe): image name 'node.exe' is not one of the daemon's. A refusal
is reported as a refusal, never as "no collectable process was found" — those mean different things
and only one of them tells you what to do next.
3. "The port answers" is not the same as "it works".
A daemon can keep /health green while being unable to reach its database. Observed in the wild: a
saturated disk made the daemon's Postgres connection handshake time out 48 times, the port never
blinked, and every memory write stalled with no visible reason. That is why status also probes
Postgres and scans the daemon's own log — using two windows: errors in the last 5 minutes mean
it is failing now, while anything older is reported as context (recovered) so a past incident
does not keep the chain marked unhealthy forever.
4. Command lines carry secrets.
The activity recorder stores full command lines, because that is often the only thing distinguishing
an expected process from an unexpected one — and for the same reason it is where credentials travel.
--token, --password, AWS_SECRET_ACCESS_KEY=, Authorization: Bearer ... and URL credentials
are redacted before the line is written, because the log is append-only: redacting only on
display would hide the secret from the default view while leaving it in the file.
5. An idle cache is not a small one.
After months the uv cache held 8.6 GB, of which 5.3 GB was eight uvx environments built by earlier
runs and never reaped, and a further 1.9 GB was duplicate copies and superseded versions.
cache reports that, --prune lists the removals and stores the list, --apply carries out
exactly that list. Each removal is staged by renaming the entry first: Windows refuses to rename a
directory while a file inside it is open, so a refused rename is the answer that something is
using it — and it is one atomic operation, where scanning running processes and then deleting is
not.
The rename answers is anything holding this entry. It cannot answer is this still the entry that was reviewed, so every entry is rechecked — same path, same size — before it moves. An entry that changed or vanished is left alone and named, and nothing outside the reviewed plan is ever removed: candidates that appeared since are left for the next plan, where they are visible before they are approved. A plan reviewed as 40 entries executes as those 40.
Boundaries — what it deliberately does not do
It records; it does not block. The activity recorder gives attribution and a timeline, not prevention. Blocking a write needs a filter driver, and claiming otherwise would be the exact kind of false signal this tool was built to remove.
It never quarantines on its own. Nothing this tool detects is acted on by a rule. Every act is a command a person types, and each says what it does to the target:
detainfreezes a process and asks a human. The file is not touched at all, and a suspension is persistent and loud, because one nobody comes back for is worse than one never applied.isolaterewrites the file's ACL and stops it launching. The bytes are untouched.vaultcopies a file into a store on its own volume;vault --moveis the one operation that takes a file out of its place, and it has to be asked for by that word. The default leaves the original exactly where it was, so a mistake costs disk space rather than a file.Every one of them prints the command that reverses it, and that command does not need this tool to exist.
Detection does not act.
signalsreports anddecidesays what it would do; the mode defaults toobserveand nothing reads it.policy mode suspendandpolicy mode rejectare recorded as intentions and implemented nowhere — the command says so when you set one, andpolicy showreports a non-observe mode as recorded intent rather than as an alarm, because a setting that promises an action the tool cannot take is the kind of false signal this project exists to remove. What is left of the old promise is a field that nothing read, and it is gone rather than kept as decoration. Promotion to automatic would first need a measured false-positive rate, and the detectors have to be shown to fire at all before that number means anything. The design for changing this, and the sequence it would have to follow, is inDESIGN-enforcement.md.It does not patch the host plugin. The plugin's
ensureDaemon()starts withif (await isServerHealthy(...)) return;— once the service is healthy it returns immediately and the fragile path is never entered. That is "no longer traversed", not "patched".No extra resident process. Watching is delegated to the OS scheduler.
It only reads the service's logs; it never touches hindsight's own data.
Platform
The platform layer is Windows-only; the staging model is not.
Everything that touches the machine — process enumeration, windows, the registry, service and
scheduled-task control, NtSuspendProcess — goes through PowerShell and Win32. The activity
recorder, resources, reveal, detain and install-service do not work anywhere else today.
What is portable is the supervisor's shape: staged recovery, the health model, and the CLI. Porting
means replacing the platform layer — a launchd plist or systemd timer in place of the scheduled
task — not rewriting the logic. package.json therefore declares no os restriction, because that
field constrains where a package may be installed rather than what it supports, and this package
installs and runs its portable parts anywhere Node does.
Modules
lib/ is layered, and the layers are checkable by reading the import lines rather than by
trusting this table. Two modules import nothing local; everything else imports only downward.
platform · commandline leaves: one spawns and reads, one parses a command line
↑
policy · lock · service · database · uvcache · vault · redline
↑
resources · activity · enforce · live
↑
crypt · custody · signals · chamber
↑
supervisor the staged recovery, and the only thing that starts services
↑
core config + context + the re-export facade
↑
mcp · bin/cli.mjs the two entry pointsTwo things worth knowing that the shape does not show:
cryptandenforceare not re-exported throughcore. The CLI imports them as namespaces (cry.,enf.), because they are the two modules that touch secrets and ACLs and a caller should have to name them to reach them.vaultis likewise not facaded. It is the only operation that moves a file.
chamber sits where it does because it composes two sources that must not be confused: live facts
from a probe, and per-pid history from the record. It reads custody for the second.
entity sits near the bottom because it reads several surfaces at once: it is the one module that
answers about a thing — a pid or a path — instead of about a surface. Events and snapshots are kept
apart there on purpose. activity is a record of events, which have a time and happened once; ps
and redline are snapshots, true when taken and silent about a minute ago. Flattening them into one
table would let "happened at 14:02" be read as "is true", and this tool exists to remove that kind of
answer rather than produce it.
Configuration
Everything is discovered, not hardcoded: uvx comes from PATH, the cache dir from
UV_CACHE_DIR (with junctions resolved to their real target), the embed version from the host
config.
Tune behaviour in DEFAULTS at the top of lib/core.mjs, or in the policy file at
~/.volcano-separator/policy.json:
profile: 'coding-agent',
port: 9077,
withPackages: ['pg0-embedded'],
warmProbeMs: 15000, // safety timeout for the offline probe, not the warm/cold test
warmBudgetMs: 15 * 60 * 1000,
serveBudgetMs: 5 * 60 * 1000,
taskName: 'Volcano-Separator',
taskIntervalMinutes: 5,Planned
Agreed work that is not done, each with what is measured and what would count as finished, is in
TODO.md. The first entry is the largest: today the supervisor knows one specific stack
(uv → the Hindsight daemon → its embedded PostgreSQL), and it should be able to supervise a
comparable local service without being rewritten.
Requirements
Node >= 20.12. Zero third-party dependencies. PowerShell for the platform layer.
Tests
node test/smoke.mjs # or: npm testAbout 170 checks. They are behaviour-level rather than unit-level, and they are not offline: the suite drives the real CLI, so on a machine with the service running it reads live state (and on one without, it asserts the degradation instead — every probe has to say "unavailable" rather than throw). It also invokes PowerShell, and spawns the MCP server to read its handshake back.
Two habits worth knowing if you add to it:
A test that cannot fail is not a test. A detector that reports zero findings and a detector that cannot fire look identical from the outside, so the signal rules are checked against synthetic positive cases, and the redaction rules are checked against a fixture with the expected output written out — agreement between two implementations is not enough, since two identically-wrong implementations agree perfectly.
Assert the observation, not the method. Where a check would otherwise match on a process name, it matches on the file growing instead; a name match finds the shell that is running the check.
There is a second, blunter check next to it:
node test/commands.mjs # or: npm run commandsIt runs every command that reads, once, and looks at the exit code. That is cruder than the smoke
suite and it is aimed at a different failure: the smoke suite asserts behaviour, and a large
refactor does not break behaviour first -- it breaks reachability. A handler that loses an import,
or a function that stops being re-exported, is invisible to tests that call the functions which
survived. The commands that change the machine (stop, restart, serve, detain, release, warm
--force, install-service) are deliberately absent, so this is safe to run on a working machine at
any time; guard clean is included because it must refuse while the service is up, and its
expected codes are written per command rather than assumed.
CI runs the smoke suite on Node 20 and 22, on Linux and Windows.
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Hosted MCP memory and agent control plane for durable conversations, jobs, and operations.
Collision-first MCP diagnostics for MCP, API, webhook and agent failures. Returns structured evidence, bounded repair guidance, agent compatibility checks and safe recovery routing.
Scans remote MCP servers for protocol, security, and TLS issues; exposes scan tools via MCP.
Manage uptime monitors, incidents and status pages from any MCP client.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables management of long-running development processes (such as dev servers, compilers, and watchers) from MCP hosts. Provides tools to start, stop, restart, check status, view logs, and send input to managed processes.MIT
- AlicenseBqualityDmaintenanceA safety-first MCP operations cockpit for Hermes Agent installations, exposing typed, evidence-producing management primitives.73MIT
- AlicenseAqualityCmaintenanceA dependency-free MCP server providing tools for tailing logs, checking endpoint health, and searching/updating a runbook of past fixes.461 npmMIT
- AlicenseNot gradedqualityCmaintenanceExposes MCP tools that start, query, signal and approve durable Temporal workflows which diagnose and remediate broken Kubernetes pods, so a crashed or restarted server simply re-attaches to the same deterministic heal instead of losing state or applying a fix twice.MIT