winadmin-mcp
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., "@winadmin-mcpshow me all Windows services that are stopped but set to start automatically"
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.
winadmin-mcp
A Windows-admin MCP server for agents (Claude Code, Codex, Hermes, …): services, event logs, processes, network and diagnostics through 20 sharply typed tools — without the agent having to build PowerShell quotes itself.
Plus a web interface (http://127.0.0.1:8787), in which every tool can be switched on and
off individually. What is off disappears from tools/list at runtime — the agent simply no longer
sees the function and cannot call it either.
Least privilege is the default: on a fresh machine every tool starts off. An agent gets the governance tools and nothing else until you decide what it may do.
Contents · Quick Start · Why So Tightly Built · Tools · Approval Gate · Web UI · Hermes · Configuration · Development
Quick Start
You need Node.js 20 or newer (node --version to check). Then three steps, all inside one
folder:
1. Get the files. Either clone the repository:
git clone https://github.com/prinzalx/winadmin-mcp.git
cd winadmin-mcpOr, without Git: on the GitHub page click Code → Download ZIP, unpack the archive, and open the
resulting folder. Everything has to stay in that one folder — src/, package.json, the config and
the build output belong together.
2. Install and build (this creates dist/ from the TypeScript sources):
npm ci
npm run build3. Start the server:
node dist/index.jsThe terminal answers with a banner and two lines about where the server lives:
██╗ ██╗██╗███╗ ██╗ █████╗ ██████╗ ███╗ ███╗██╗███╗ ██╗ ███╗ ███╗ ██████╗██████╗
██║ ██║██║████╗ ██║██╔══██╗██╔══██╗████╗ ████║██║████╗ ██║ ████╗ ████║██╔═════██╔══██╗
██║ █╗ ██║██║██╔██╗ ██║███████║██║ ██║██╔████╔██║██║██╔██╗ ██║ ██╔████╔██║██║ ██████╔╝
██║███╗██║██║██║╚██╗██║██╔══██║██║ ██║██║╚██╔╝██║██║██║╚██╗██║ ██║╚██╔╝██║██║ ██╔═══╝
╚███╔███╔╝██║██║ ╚████║██║ ██║██████╔╝██║ ╚═╝ ██║██║██║ ╚████║ ██║ ╚═╝ ██║╚██████╗██║
╚══╝╚══╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝╚═╝
web interface http://127.0.0.1:8787
MCP transport stdio — this terminal stays attached to the agentTo be clear about what those lines are: they are output, not arguments. You do not pass the URL anywhere; the server announces it, and you surf to it in a browser. The second line says the same about the terminal — it is the MCP channel, so the command does not return and the window must stay open.
In the browser, open that address and switch on what the agent is allowed to touch — the section
header carries a master switch for all tools at once, and each module group has its own pair. Until
then tools/list returns only list_pending and confirm_action.
stdio is the MCP transport; the same process starts the HTTP interface. Both transports share the same tool list, the same switches and the same audit trail — so the web UI can take tools away from a running agent live, without the agent having to restart.
It is two roles in one process: stdio belongs to the agent, which spawns the server itself at startup and reads and writes within it. The HTTP interface is aimed at the human and is deliberately not a second server with its own configuration.
Binding happens to 127.0.0.1: the UI triggers administrative actions; access from the network
would only be safe after TLS plus authentication.
What It Feels Like
A real call, a real machine:
// tools/call: get_service { "name": "Spooler" }
{
"name": "Spooler",
"display_name": "Print Queue",
"status": "Running",
"start_mode": "Auto",
"log_on": "LocalSystem",
"pid": 6924
}Every response consists of two parts: a readable short text for the chat and structuredContent
with the data. Agents can rely on the text, models with schema awareness on the structure.
Related MCP server: winfolio
Why So Tightly Built
Rule | Reason |
One wrapper, one process | Every action goes through |
Local only | All tools work against the local machine. There is no |
Validation before process start | Zod checks the input before PowerShell starts. A typo in an enum does not cost 300 ms, only an error message with path and reason. |
Escaping centralized | Every input that is interpolated into a PowerShell command goes through |
Structured errors |
|
No streaming | Results are one-shot JSON documents. Anyone who wants to watch processes in real time calls |
Tools
22 tools in total: 20 switchable ones from five modules plus two permanent governance tools.
Module | switchable | permanent |
Services |
| |
Events |
| |
Processes |
| |
Network |
| |
Diagnostics |
| |
Governance |
|
The two governance tools are always active and not switchable in the UI; otherwise an agent could no longer confirm a parked action after its last tool had been disabled.
Reference
Services
Tool | Input | Response |
|
| name, display name, state, start mode, log-on account, PID |
|
| a single service, like the example output above |
|
| short message plus state; destructive |
|
| new start mode; destructive |
Events
Tool | Input | Response |
|
| newest first, each entry with time, ID, level, provider and message (truncated to 400 characters) |
|
| error and warning counts per provider — the fastest route to the culprit |
|
| meaning, severity, typical cause. No process start: pure lookup in |
Processes
Tool | Input | Response |
|
| top processes with memory and command line |
|
| details for all instances of the name |
|
| kills all instances; destructive |
|
| without |
kill_process spares the protected system processes; an attempt on lsass, csrss, svchost,
winlogon, smss or services is rejected instead of destabilizing the machine.
Network
Tool | Input | Response |
|
| reachable yes/no plus average round-trip time |
|
| whether the TCP connection was accepted — the fastest firewall/reachability test |
|
| addresses with type and TTL |
|
| interfaces with state and addresses |
Diagnostics
Tool | Input | Response |
| – | computer name, OS and build, uptime, last boot, CPU load, memory, drives, error count of the last 24 h |
|
| state, start mode, process, dependencies and the recent log entries that mention the service |
|
| update state from the registry plus the newest installation results |
Approval Gate and Switches
The web UI offers two switches per tool:
Switch | Off | On |
visible | tool missing from | usable normally |
approval | call executes immediately | the human is asked — in the client popup if it can show one, otherwise parked with |
flowchart TD
A["Agent calls a tool"] --> B{"visible?"}
B -- no --> X["Error: disabled in the web interface"]
B -- yes --> C{"approval on?"}
C -- no --> E["Start PowerShell, return result"]
C -- yes --> P{"client can show a popup?"}
P -- yes, approved --> E
P -- "yes, declined" --> Y["nothing ran, agent is told"]
P -- no --> D["Park the action, return confirmation_id"]
D --> F["Human decides: Run in the web UI or confirm_action(id)"]
F --> EParked actions appear in the web UI and are decided there with Run or Drop; list_pending
shows them to the agent, confirm_action executes one by ID. Parked actions expire after
10 minutes.
The gate is enforced in one central place, before any tool handler runs, so it applies to every
tool: a read-only call such as list_services parks just like stop_service when its approval
switch is on. The read/write badge only describes what the tool does — it does not decide whether
the gate can be used. An agent that receives a confirmation_id gets no data yet; it has to wait
for the confirmation. An agent whose client answers the popup gets the real result in the same
call.
Where the approval is asked (ask in agent)
A parked action alone has a flaw: the human runs it in the web interface, but the result lands there and never reaches the agent — so the agent calls the tool again, and parks again, forever.
Therefore a gated tool call asks for approval inside the MCP client first, through the standard
MCP elicitation request (elicitation/create). A client with a TUI (Hermes, Claude Desktop, …)
shows a popup right where the chat is — the same mechanism as an "option A / B / C" question. If
the human approves there:
the tool executes immediately,
the result comes back in the same tool call,
nothing parks in the web interface.
Decline or dismissal means: nothing ran, nothing parks, and the agent is told exactly that. The switch ask in agent in the Awaiting approval header turns this off; a client that never declared the elicitation capability is never asked and falls back to parking automatically.
Whether a client can show the popup depends on how it is started, not on the server. Hermes
answers the popup in its TUI (hermes --tui); the plain CLI does not render it, and there the
action parks for the web interface as usual.
The terminal says which case it is, right after the handshake:
client connected: hermes 1.4.0 — popup supportedIf it reads not supported by this client, the client never asked for popups and the web interface
decides instead. Over HTTP a session is kept alive between requests for the same reason: the
tools/call needs both the capabilities from initialize and an open stream to ask on.
Who is allowed to confirm
When an action parks (popup off, or a client without elicitation), an agent that calls
confirm_action right after grants itself permission, which makes the gate a round trip with no
decision in it. So by default a parked action can only be run by a human — through the web
interface — and an agent that tries is refused and told to wait:
restart_service: approval is for the human in the web interface. The parked action stays pending.The switch agent may confirm in the Awaiting approval header turns that off, for an agent that
is allowed to decide. While a refusal is in effect, the parked action stays parked — one failed
attempt does not consume the confirmation_id.
The Web UI
The UI is a single file (src/webui.html, vanilla JS, no build step) with four areas:
Area | Contents |
Tools | per module group one row per tool: name, description, read/write badge, and the two switches. The approval switch is disabled when the tool is off. |
System | the last |
Awaiting approval | the parked actions with tool, arguments and the Run / Drop buttons, plus the ask in agent and agent may confirm switches |
Recent activity | the most recent audit entries: time, tool, success, duration, source, with a Clear button in the heading |
Below the two columns a footer shows the running version, and — when GitHub has a newer release — an
update available 0.x.y link to the releases page.
The lookup happens once at startup and never blocks it; WAMD_NO_UPDATE_CHECK=1 skips it entirely.
The page polls /api/state every two seconds and re-renders only when something actually changed,
so a tool call from the agent or a freshly parked action shows up without a manual reload. The
audit list is also read back from the audit file at startup, which means the history survives a
server restart.
Clear in the Recent activity heading empties the list. It rewrites the audit file as well, so the entries do not come back on the next start; tool switches and pending approvals are untouched.
HTTP Endpoints
Method | Path | Purpose |
|
| the UI |
|
| version, configuration path, audit path, all tools with their switches, |
|
|
|
|
|
|
|
|
|
|
|
|
|
| drop the recorded calls and rewrite the audit file |
|
| MCP over Streamable HTTP, for clients without stdio |
Master switches
Above the list, the section heading carries two switches that act on every tool at once:
all visible and all approval. Each module group repeats the pair in its own header, for the
tools of that module only. Both are rendered as ordinary checkboxes and follow the state of their
members:
Members | Checkbox |
all on | checked |
none on | unchecked |
some on, some off | indeterminate (dash) |
Switching a master writes the value for all its members in one PATCH /api/tools request, so the
config file is saved once and the client gets one list_changed — not one per tool. A master
applies to all its members, including tools that are currently hidden; the per-row approval switch
being greyed out only concerns that row.
Every switch change is persisted and reported to the MCP client via
notifications/tools/list_changed — exactly one notification per change, never one per tool,
which would flood the terminal of anyone watching stdio. A client that ignores the notification
sees the change at the latest on the next tools/list.
What the Terminal Shows
Because stdio belongs to the agent, the terminal is the only window into the running server. It prints one line per call and one line per connecting client:
client connected: Hermes 0.4.1 — popup supported
ok list_services (mcp) 693 ms
ok restart_service (mcp) 0 ms parked, waiting for approval
err stop_service (mcp) 858 msThis is stderr, so it never interferes with the JSON-RPC frames on stdout.
Hermes Agent
Hermes speaks MCP out of the box: our tools appear
there as mcp_winadmin_*, and Hermes picks up changes to the tool list live — so the on/off
switches take effect without restarting the agent. Only enabled tools appear, so on a fresh
install Hermes sees the two governance tools until you switch things on.
Run hermes --tui for the approval popups. In the TUI a gated tool asks for approval right in
the chat and the result arrives in the same call. In the plain CLI Hermes does not render the
elicitation, so the action parks and the web interface decides — the calls still work, they are
just approved over there. Read-only triage is unaffected; it is the gated tools that need the TUI.
# ~/.hermes/config.yaml
mcp_servers:
winadmin:
command: "node"
# your checkout path to the build
args: ["<checkout>/dist/index.js"]
# optional, e.g. for a leaner context window:
# tools:
# exclude: ["get_windows_update"]> hey hermes, my system crashed earlier — take a look at the event logs,
what could be the cause?For this one sentence Hermes needs no special command, but four tools, and those four have to be
visible first: system_health for uptime and the time of the last boot, query_events
(log: System, level: error) for the window since the boot, explain_event_id for Kernel-Power
41, EventLog 6008 or Service Control Manager 7000/7031/7034 — and diagnose_service, if a service
was the trigger. Switch on the events and diagnostics groups (or just those four rows) and the
prompt works; everything else stays out of the model's way.
For exactly this workflow, a sample skill ships at
skills/windows-triage/SKILL.md; it teaches Hermes the triage
routine: look first, then claim.
copy -Recurse skills\windows-triage $env:USERPROFILE\.hermes\skills\After that the skill is available in Hermes under /windows-triage and is offered automatically for
matching requests.
Configuration
node dist/index.js --helpSwitch | Environment variable | Default | Purpose |
|
|
| port of the web interface |
|
| off | stdio only, do not bind an HTTP port |
|
|
| where the switches are stored |
|
| append-only audit trail | |
| off | skip the one-time lookup of the newest release | |
| print the usage text and exit |
The command line wins over the environment; both are optional. --no-web exists for setups where
only the MCP client should reach the machine — no HTTP port, no UI, one less listener.
The web UI writes config.json with one entry per tool; editing it by hand works just as well:
{
"enabledTools": { "query_events": true, "system_health": true }, // the rest: false
"requireApprovalTools": { "stop_service": true }, // the rest: false
"maxBulkItems": 20,
"protectedProcesses": ["lsass", "csrss", "svchost", "winlogon", "smss", "services"],
"allowAgentConfirm": false,
"askInAgent": true
}A config file written by an older version has no allowAgentConfirm entry, which means false: after
an upgrade an agent can no longer confirm its own parked action. A missing askInAgent means true,
so approval moves into the client popup wherever the client supports it.
Every tool starts disabled, so an entry missing from enabledTools means off. A config file that
an older version wrote with everything on keeps everything on — the default only applies to entries
that are not in the file yet. The paths are deliberately absolute; a relative path would be resolved
against the working directory of the MCP client, and that differs per client.
Audit Trail
Every call — success as well as failure — lands as a single JSON line in audit.jsonl:
{"ts":"2026-10-14T14:12:53.332Z","tool":"get_service","args":{"name":"Spooler"},
"ok":true,"durationMs":619,"source":"web"}source distinguishes mcp (agent) from web (human). In memory the trail is capped at the last
200 entries and the UI shows the last 25, so the list cannot grow without bound. Beyond that,
rotation is the operator's job — or the Clear button in the web UI, which drops the list and
rewrites the file.
Development
npm run build # tsc + copy src/webui.html to dist/
npm run dev # build and restart with node --watch
npm run typecheck # tsc --noEmit
npm test # smoke test: real server, real PowerShellnpm test runs scripts/mcp-smoke.mjs,
scripts/mcp-popup-check.mjs and
scripts/mcp-http-popup-check.mjs: the scripts spawn the
server on a free port, perform a real MCP handshake and check 21 + 9 + 8 points — tool list, schema shape,
validation, a real ping_host call, approval parking (for a write tool and for a read-only one),
that an agent may not confirm its own parked action by default, list_pending,
confirm_action, unknown confirmation ID, and enable/disable including list_changed. Against real PowerShell/CIM output, not against a mock: a cmdlet parameter that does
not exist in PowerShell 5.1 shows up here and not in front of the agent.
Project Structure
src/
index.ts stdio and HTTP server, MCP handler, bootstrap
config.ts load/save switches, subscribe to changes
safety.ts approval registry: park, peek/take, expiry after 10 min
audit.ts append-only JSONL trail of all calls
types.ts ToolDefinition, ToolContext
data/event-ids.ts knowledge base for explain_event_id
modules/
services/ list, state, start/stop/restart, start mode
events/ query, summary, event-ID explanation
processes/ process list, details, kill, listening ports
network/ ping, port test, DNS, interfaces
diagnostics/ system_health, diagnose_service, get_windows_update
utils/
powershell.ts the only place that starts a process
result.ts ok/text/fail/pending — uniform results
gate.ts approval gate for all destructive tools
webui.html the complete web UI (no build step needed)
scripts/
copy-webui.mjs src/webui.html -> dist/webui.html
mcp-smoke.mjs end-to-end over the real MCP transport
mcp-popup-check.mjs the approval popup, with a client that declares elicitation
mcp-http-popup-check.mjs the same over Streamable HTTP, in its own session
web-check.mjs check HTML, /api/state and the switches
skills/windows-triage/ sample skill for HermesCreating a new tool is deliberately cheap: a ToolDefinition in a module folder (or a new folder in
src/modules/), done — the UI, the tool list and the /api/state evaluation all draw from the same
list.
Known Limitations
Windows only. Uses
powershell.exe(5.1), CIM namespaces and theGet-NetTCPConnectionandGet-WinEventcmdlets. PowerShell 7 is not detected and is not needed.Localized messages. State texts and event messages come out of Windows exactly as the system is configured (on a German Windows, in German).
explain_event_id, by contrast, always answers in English, because the knowledge base lives in the repo.Time window.
query_eventsandsummary_eventstakehours(default 24). Anyone looking for a crash since the last boot setshourssmall to match — for "since boot" it is best to compute from the uptime insystem_health.Cost per call. Every tool call costs a PowerShell process, typically 0.3-2 s depending on system load.
explain_event_idis the exception: pure data lookup, no process start.maxBulkItemsis prepared but not wired up. The value sits in the configuration and is evaluated by no tool; it is intended for future bulk actions.No auth. The UI is bound to local only. Without TLS and login it does not belong behind a reverse proxy for multiple people.
License
MIT
Available Tools
2 toolsconfirm_actionConfirm actionB
Executes a destructive action that is waiting for approval, by its confirmation_id.
| Name | Required | Description | Default |
|---|---|---|---|
| confirmation_id | Yes | The id returned by the original tool call |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full disclosure burden, and it does flag the most important trait: the target action is destructive and is only being executed now after approval. It omits other consequential behavior — whether the confirmation is single-use/expiring, what authorization is required, and whether the effect is reversible.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with the destructive nature and the required key stated up front, and no filler. Slightly terse for a destructive operation, but nothing is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter tool with no annotations and no output schema, the description covers the core (destructive, awaiting approval, keyed by confirmation_id). It falls short on what the agent should expect after the call and on the lifecycle constraints of a pending confirmation, which matter more here because no structured fields cover them.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% for the single confirmation_id parameter, so the baseline is 3. The description restates the parameter name but adds no syntax, format, or provenance detail beyond what the schema's own 'id returned by the original tool call' already provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb (executes) and a specific resource (a destructive action awaiting approval), plus the mechanism (by confirmation_id). It is clearly distinguishable from the read-only sibling list_pending, though it never names that sibling explicitly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied: you call this once an action is 'waiting for approval' and you hold its confirmation_id. However, it never states that you should obtain the id via list_pending or the original tool call, nor any when-not-to-use condition (e.g. expired or already-confirmed requests).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_pendingList pending actionsB
List destructive actions waiting for approval.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full disclosure burden. It hints that the listed items are destructive and need approval, but says nothing about whether listing itself is a safe read, whether it requires elevated permissions, pagination, or how results are ordered/returned.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with no filler or redundancy. Nothing in it is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a trivial zero-param list tool with no output schema, the description is adequate to identify the tool but stops short of describing the shape of the pending-action records or pointing the agent to confirm_action as the next step. It is minimally sufficient rather than complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so there is nothing for the description to disambiguate. Baseline 4 applies; the schema is empty and complete for this operation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clear verb ("List") plus a precisely qualified resource ("destructive actions waiting for approval"). It is understandable in isolation, but it never mentions or distinguishes itself from the sibling confirm_action, which is the obvious downstream counterpart.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
"Waiting for approval" implies this is the queue you consult before calling confirm_action, so usage is inferable rather than stated. There is no explicit "use this when X, use Y when Z" routing, and no exclusions or prerequisites.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
2 tool updates
v0.4.2- First observed
confirm_action - First observed
list_pending
TDQS
Scored across 2 tools
The two tools have clearly distinct roles: one lists pending destructive actions and the other confirms a specific action by confirmation_id. There is no overlap in action or resource, so an agent can easily choose between them.
Both tools use snake_case and follow a predictable verb_noun style (list_pending, confirm_action). No mixed casing or inconsistent verb conventions are present.
Two tools is borderline thin for an admin approval workflow, even though each tool has a distinct purpose. The set would feel better scoped with at least one additional operation such as reject or cancel.
The surface covers listing pending actions and confirming them, but omits rejecting/denying, canceling, retrieving details, and viewing history. These are significant gaps for a safe destructive-action approval workflow.
Maintenance
Related MCP Connectors
Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.
Context and control for any AI. Agent identity, scoped access, and proof across tools.
Remote Desktop Commander: use files, terminal and processes on your own computers from any AI app.
Agent-native security, trust, reliability, data and procurement tools for AI workflows.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceA lightweight server that enables AI agents to interact with the Windows operating system, allowing for file navigation, application control, UI interaction, and QA testing through various tools.9,021MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI assistants to perform comprehensive Windows system health diagnostics, security checks, developer workflows, and maintenance tasks via 85 tools with actionable recommendations, over MCP and OpenAI-compatible HTTP.MIT
- AlicenseNot gradedqualityAmaintenanceEnables AI clients to securely control and interact with a local Windows machine through 218 configurable tools for files, Git, processes, Windows UI, browser automation, WSL, Office, recovery, skills, and child MCP servers.2 npmMIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to safely perform file operations, run terminal sessions, manage processes, search content, and inspect Git repositories on a local Windows machine under configurable permission and audit controls.Apache 2.0