winops
by HariSankar27
README.md
# winops
An MCP agent that diagnoses Windows service and process faults through
**read-only, least-privilege PowerShell endpoints** — and cannot change
anything without a human saying yes.

## The problem
The easy way to build an ops agent is to hand an LLM a PowerShell session and
a system prompt saying "be careful." That gives a model that read an untrusted
event log the ability to run anything, and makes "be careful" the only thing
between a hallucination and a production outage.
winops assumes the model *will* eventually be wrong or manipulated, and makes
that survivable: it constrains what the agent can even reach, and separately
gates the few things that change state.
## Two independent safety layers
**1. The agent can only reach six functions.** A PowerShell
[JEA](https://learn.microsoft.com/en-us/powershell/scripting/security/remoting/jea/overview)
endpoint runs as a `RestrictedRemoteServer` (NoLanguage mode) exposing exactly
six named, parameter-validated functions. Not `Invoke-Expression`, not raw
cmdlets, not the filesystem — regardless of what the agent asks for.
| Read-only | Mutating (approval-gated) |
|---|---|
| `Get-WinOpsServiceStatus` | `Restart-WinOpsService` |
| `Get-WinOpsFailedServices` | `Stop-WinOpsProcess` |
| `Get-WinOpsProcessInfo` | |
| `Get-WinOpsRecentErrors` | |
**2. The two mutating tools always pause for a human.** They're marked
`always_require_approval` on the MCP tool, so the agent can investigate
freely and forever, but acting requires an explicit yes.
Neither layer trusts the other. A bug in the approval logic still leaves the
blast radius at "restart one named service"; a mis-scoped JEA role still
can't act without a human. See
[docs/decisions/0002](docs/decisions/0002-two-independent-safety-layers.md).
There's a third, quieter layer: `invoke.ps1` takes the function name as a
PowerShell `[ValidateSet(...)]`, so even a fully compromised MCP server can
only pick one of six branches — it can't inject a command name.
## Architecture
```mermaid
flowchart LR
CLI[winops CLI] --> Agent[Microsoft Agent Framework]
Agent -->|MCPStdioTool| MCP[MCP server]
MCP --> Client[jea.client]
Client -->|powershell.exe, ValidateSet| Session[Invoke-Command]
Session -->|PSRemoting, NoLanguage| JEA[(JEA endpoint)]
```
Full write-up in [docs/architecture.md](docs/architecture.md).
## Quickstart
One-time setup, in an **elevated** PowerShell (registers the JEA endpoint,
creates the `WinOpsOperators` group, enables PSRemoting):
```powershell
.\jea\register.ps1
```
Verify the endpoint is really constrained before trusting it:
```powershell
# should work
Invoke-Command -ConfigurationName WinOps -ComputerName localhost -ScriptBlock { Get-WinOpsFailedServices }
# should fail - proof the session is locked down
Invoke-Command -ConfigurationName WinOps -ComputerName localhost -ScriptBlock { Get-ChildItem C:\ }
```
Then:
```bash
cp .env.example .env # add OPENAI_API_KEY (or AZURE_ENDPOINT)
uv sync
uv run winops diagnose-service Spooler
```
The agent investigates with the read-only tools, then proposes a fix and
waits for `y/n` before anything runs.
## Project status
| Area | State |
|---|---|
| JEA role files, MCP server, agent, CLI | Built, 13 tests passing |
| MCP tool + approval-loop logic | Tested with fakes (no API key needed) |
| The JEA endpoint itself | **Not yet run** — `register.ps1` needs elevation |
| Real LLM calls | **Not yet run** — no API key in the build environment |
| Fault-injection evals, audit log, replay | Not built (M4–M6) |
The `$using:` parameter passing in `invoke.ps1` is the documented way to send
values into a NoLanguage session, but it hasn't been exercised against a live
endpoint here — that's the first thing to check after `register.ps1`.
## Why Windows services, not Hyper-V
This was originally scoped around Hyper-V VM faults. Hyper-V isn't available
on Windows Home editions at all — a hard SKU-level restriction, not something
a package manager can install around — so the managed domain moved to services
and processes. Everything that makes the project interesting (MCP, JEA least
privilege, approval-gated mutation) is unchanged; retargeting to Hyper-V means
writing a new `WinOpsHelpers.psm1` with `Get-VM`/`Restart-VM` and a matching
role capability file. Nothing in `jea/client.py`, `mcp_server.py`, or
`agent.py` mentions services by name. See
[docs/decisions/0001](docs/decisions/0001-domain-swap-from-hyper-v.md).
## Safety and limitations
- The agent cannot see or touch the filesystem, the registry, other services'
configuration, or anything else outside those six functions.
- Every JEA session is transcribed to `C:\ProgramData\WinOps\Transcripts`.
- Approval is per-call and never remembered; there's no "approve all".
- Tested against a **local** endpoint. Managing a remote host means pointing
`JEA_COMPUTER_NAME` at it and dealing with WinRM auth, which isn't set up here.
## License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues