SignalGrid MCP
# SignalGrid MCP
An MCP (Model Context Protocol) server that exposes **macOS-native device trust
signals** — the facts about a Mac that cannot be gathered from a Linux container
or a cloud runner. Every tool is **strictly read-only**: nothing on the device is
ever mutated.
## What it answers
| Signal | Tool |
|---|---|
| Who is this machine? (serial, UUID, chip, activation lock) | `signalgrid_device_identity` |
| What OS build is it on? | `signalgrid_os_info` |
| Are SIP / FileVault / Gatekeeper / firewall on? | `signalgrid_security_posture` |
| What remote access is exposed? (SSH, Screen Sharing, SMB, ARD) | `signalgrid_sharing_services` |
| Is it MDM/DEP enrolled? What profiles are installed? | `signalgrid_mdm_status` |
| Is it patched? Are auto-updates on? | `signalgrid_software_updates` |
| Are XProtect / MRT malware definitions current? | `signalgrid_xprotect_status` |
| What got installed, and when? | `signalgrid_install_history` |
| DNS, proxies, VPNs, interfaces | `signalgrid_network_posture` |
| What's listening on the network? | `signalgrid_listening_services` |
| What persists across reboots? (launchd items) | `signalgrid_launch_items` |
| Any third-party kernel extensions? | `signalgrid_kernel_extensions` |
| Any stranded/conflicting system extensions? | `signalgrid_system_extensions` |
| Who has accounts? Who is admin? | `signalgrid_local_users` |
| What apps are installed, and who signed them? | `signalgrid_installed_apps` |
| What's running right now? | `signalgrid_process_snapshot` |
| Is removable storage (a data-egress channel) connected? | `signalgrid_removable_media` |
| Does the device auto-lock when left idle? (walk-up risk) | `signalgrid_screen_lock` |
| Is this app properly signed & notarized? | `signalgrid_codesign_inspect` |
| **Everything above the fold, in one call** | `signalgrid_posture_report` |
| **The SignalGrid decision for this Mac (allow / step-up / restrict / deny)** | `signalgrid_trust_verdict` |
The aggregate report is also exposed as an MCP resource at `signalgrid://posture`.
Call `signalgrid_posture_report` with `include_verdict: true` to get the raw facts
**and** the folded allow/step-up/restrict/deny decision in one round-trip (the same
fail-safe computation as `signalgrid_trust_verdict`).
## Design principles
- **Read-only, always.** Every tool carries `readOnlyHint: true`,
`destructiveHint: false`. No command mutates state.
- **Unknown ≠ off.** Posture checks distinguish "the check ran and said X"
from "the check could not run" (missing binary, timeout, needs elevation).
`enabled: null` always means *unknown* — investigate, don't grade.
- **No shell, no injection.** Every command is an argv list executed without a
shell; user input is never interpolated into a command line.
- **Context-efficient.** Large inventories (apps, processes, launch items,
install history, listeners) are paginated (`limit`/`offset`, standard
`total/has_more/next_offset` envelope), filterable (`name_contains`), and
render as a compact markdown table by default or JSON on request.
- **Degrades gracefully.** On a non-macOS host, or when a probe needs
elevation, tools return structured error/unknown text — they never crash.
## How these signals reach the Grid (sourcing)
SignalGrid's decision fabric classifies every signal by *how* it is obtained —
`api` (a vendor read API), `native` (a first-party integration), `grid_collected`
(SignalGrid does the lifting itself), or `unavailable` (a real gap). **This
server is the `grid_collected` path for macOS**: SIP, FileVault, Gatekeeper, MDM
enrollment, XProtect currency and the rest are facts no cloud API hands you
faithfully in real time — you read them on the device.
Every signal here is therefore classified `grid_collected` at **`medium`**
fidelity — deliberately not `high`. The reads are authoritative, but the fabric
never over-trusts a signal it had to collect itself, and some probes degrade to
`unknown` without elevation.
The server publishes this mapping so a connecting fabric can discover each
signal's provenance, as the MCP resource:
```
signalgrid://sourcing
```
It lists every posture-report section → the fabric signal it feeds → its
acquisition method and fidelity. `tests/test_sourcing.py` pins the manifest as a
bijection with the report **sections**, so no section can go un-sourced and no
stale entry can linger. (The per-signal descriptions are prose, not checked
against collector output.)
## Install
Requires Python ≥ 3.10 on the Mac being assessed.
```bash
cd signalgrid-mcp
pip install -e . # or: uv pip install -e .
```
## Run
stdio transport (the server must run **on the Mac it is assessing**, as a
subprocess of the MCP client):
```bash
signalgrid-mcp # console script
# or
python -m signalgrid_mcp.server
# or (back-compat)
python server.py
```
### Claude Desktop / Claude Code config
```json
{
"mcpServers": {
"signalgrid": {
"command": "python3",
"args": ["/Users/<you>/signalgrid-mcp/server.py"]
}
}
}
```
### Inspect interactively
```bash
npx @modelcontextprotocol/inspector python3 server.py
```
## Permissions & elevation
The server intentionally runs unelevated. Some probes therefore report
`null`/unknown rather than an answer:
- `profiles list`, `systemsetup`, and some `launchctl print system/...` targets
want root.
- `tmutil latestbackup` and the Time Machine preference need Full Disk Access.
- Unelevated `lsof` only sees the current user's listeners.
This is by design: an unattended trust agent should not hold root. Treat
`null` as "unresolved signal" and escalate out-of-band if it matters.
## Verify (turnkey, on the Mac)
One command sets up, tests, and inspects the live server end to end:
```bash
./verify.sh
```
It creates a venv, installs, runs `pytest`, then inspects the server over MCP
stdio — listing and calling every tool. See **[RUNBOOK.md](RUNBOOK.md)** for the
step-by-step Mac verification, including how to chase down any signal that reads
`unknown` against real macOS output.
## Testing
```bash
pip install -e ".[dev]"
pytest
```
The smoke tests run on any OS (they exercise the graceful-degradation paths on
Linux CI); the meaningful signal values obviously require macOS. For a Node-free
protocol inspection (no browser, no `npx`):
```bash
python tools/inspect_stdio.py # human-readable
python tools/inspect_stdio.py --json # machine-readable summary
```
## Layout
```
signalgrid-mcp/
├── server.py # back-compat stdio entry point
├── verify.sh # one-command turnkey verify (install + test + inspect)
├── RUNBOOK.md # step-by-step Mac verification runbook
├── src/signalgrid_mcp/
│ ├── app.py # FastMCP instance + shared annotations
│ ├── runner.py # subprocess plumbing (run/text/probe/run_json)
│ ├── formatting.py # pagination, filtering, markdown/JSON rendering
│ ├── sourcing.py # grid_collected sourcing manifest (signalgrid://sourcing)
│ ├── server.py # entry point (main)
│ └── tools/ # one module per signal domain
├── tools/inspect_stdio.py # Node-free MCP inspector (protocol/read-only/honesty)
├── tests/test_smoke.py
├── tests/test_parsers.py # parser fixtures pinned against captured output
└── evaluation.xml # MCP eval suite (read-only Q&A pairs)
```
TDQS
Scored across 22 tools
Every tool targets a distinct macOS subsystem or data source (installed apps, install history, processes, listening services, MDM, etc.), with no two tools doing the same thing. The only aggregate tool, posture_report, explicitly composes the others, and trust_verdict is a derived decision, not a raw collection.
All 22 tools share the signalgrid_ prefix and use lowercase underscore-separated names. The pattern is consistent throughout, with no mix of camelCase or differing verb styles.
At 22 tools, this is in the 16-25 range that feels heavy, but each tool serves a specific security-assessment function and earns its place. The count is borderline and could be seen as slightly over the typical sweet spot.
The tool set provides comprehensive read-only coverage of device trust: identity, OS, security controls, updates, MDM, persistence, network, processes, and aggregate views. Every signal has a follow-up path (e.g., process snapshot to codesign inspect), and the posture_report/trust_verdict tools provide a cohesive summary.