Skip to main content
Glama
DanFashauer

SignalGrid MCP

by DanFashauer
README.md
# 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

A4.3/5.0

Scored across 22 tools

Disambiguation5/5

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.

Naming Consistency5/5

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.

Tool Count3/5

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.

Completeness5/5

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.

Maintenance

ActivitySlowing
ResponsivenessNo issues