Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# IntuneGraph

**Turn your Microsoft Intune tenant into an interactive relationship graph.** See what actually applies to a device and *why*, preview the blast radius before you touch a group, and find the orphaned junk, all from a local, read-only snapshot.
**Turn your Microsoft Intune tenant into an interactive relationship graph.** See what actually applies to a device and *why*, preview the blast radius before you touch a group, and find the orphaned junk, all from a local, read-only snapshot — from PowerShell, in a self-contained HTML viewer that makes zero network calls, or by asking your AI assistant through the bundled MCP server.

[![PowerShell Gallery](https://img.shields.io/powershellgallery/v/IntuneGraphKit?label=PSGallery)](https://www.powershellgallery.com/packages/IntuneGraphKit)
[![PowerShell](https://img.shields.io/badge/PowerShell-5.1%20%7C%207%2B-5391FE)](#requirements)
Expand Down Expand Up @@ -33,11 +33,9 @@ Export-IntuneGraph -DemoData -PassThru | Show-IntuneGraph -Open

That builds the bundled **Contoso** demo tenant and opens the interactive graph in your browser — zero Graph auth, zero setup. The whole tool works offline against demo data, which means you can evaluate it (and contribute to it) without ever touching a real tenant.

![IntuneGraph interactive demo — the Contoso tenant as a relationship graph](docs/img/demo.gif)

---

## New: ask your AI *why* a policy applies — and get the group path, not a guess
## Ask your AI *why* a policy applies — and get the group path, not a guess

IntuneGraph ships an **MCP server**, so Claude Code or GitHub Copilot can query the
graph directly:
Expand Down Expand Up @@ -73,6 +71,23 @@ when to refresh it. Six read-only tools: `intune_target`, `intune_blast_radius`,
`intune_orphans`, `intune_path`, `intune_node`, `intune_summary` — see
[docs/mcp.md](docs/mcp.md).

Packaging the same six tools as a Claude Code plugin with a bundled skill — one install
for the server and the method for using it — is specified in
[docs/skill.md](docs/skill.md). It is a specification, not yet something you can install.

---

## One HTML file, zero network calls

![IntuneGraph interactive demo — the Contoso tenant as a relationship graph](docs/img/demo.gif)

`Show-IntuneGraph` writes the graph to a single self-contained HTML file. The renderer,
the styles and the data are all inlined, so the page makes **zero network calls**: no
CDN, no web fonts, no telemetry. It opens from disk, offline, with nothing to install,
and a Pester test fails the build if the generated report links to anything external.
Because the data travels inside the file, treat a report from a real tenant the way you
would treat the `graph.json` it came from.

---

## The three questions it answers
Expand Down
16 changes: 10 additions & 6 deletions docs/discoverability.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ maintenance task — should change this file in the same commit, with the reason

The README's first screen is the fourth surface, and the only one that converts a
visitor into a user. It leads with the demo command that needs no tenant, because the
cost of trying this tool is the thing most likely to stop someone.
cost of trying this tool is the thing most likely to stop someone. The MCP server comes
straight after it, ahead of the demo GIF, so the capability the `mcp` topics are there
for is visible without scrolling rather than a screen further down.

---

Expand Down Expand Up @@ -129,13 +131,15 @@ not an addition to what is there.

## Description

> Turn your Microsoft Intune tenant into an interactive relationship graph. See what
> applies to a device or user and why, preview the blast radius before you touch a
> group, and find orphaned or broken assignments. Read-only Graph, offline-capable
> demo, self-contained HTML viewer. Zero write scopes.
> Turn your Microsoft Intune tenant into an interactive relationship graph, and ask
> your AI why a policy applies through the bundled MCP server. See what applies to a
> device or user and why, preview a group's blast radius, find broken assignments.
> Read-only Graph, offline demo, self-contained HTML viewer. Zero write scopes.

Search results and the topic pages truncate long descriptions, so the first sentence
has to carry the whole pitch on its own — and it does. What follows it is there for the
has to carry the whole pitch on its own. That is why the MCP server is in the first
sentence rather than the tail: it is what separates this from flat-list assignment
tools, and a truncated description should still say so. What follows it is there for the
visitor who has already clicked: the three questions the tool answers, then the two
objections an admin raises before installing anything that touches their tenant
(*does it write?* and *do I need a tenant to try it?*), answered before they are asked.
Expand Down
211 changes: 211 additions & 0 deletions docs/skill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# Claude Code skill packaging

How IntuneGraph is meant to ship as a Claude Code plugin: one install that brings the
MCP server's six read-only tools **and** a skill that tells the assistant how to use
them.

**Status: specified, not shipped.** Nothing in this repository installs as a plugin yet,
and the README says so. This file is the specification the shipping change has to meet;
the checklist at the end is its acceptance criteria.

---

## Why package it at all

The MCP server gives an assistant tools. It does not give it the method: check how old
the snapshot is before trusting it, pin down an exact name before querying, quote the
`Via` path rather than paraphrase it, run a what-if before anyone touches a group, and
never ask for tenant credentials. Today that method lives in the admin's head and in
[docs/mcp.md](mcp.md). A skill is where Claude Code loads it from.

What changes is packaging, and only packaging. The same six tools, the same launcher,
the same `graph.json` snapshot — no new capability and no new code path to a tenant.

---

## Shape: the repository root is the plugin

```text
.claude-plugin/
├── marketplace.json # makes this repository an installable marketplace
└── plugin.json # the manifest
skills/
└── intune-assignments/
└── SKILL.md # the method
```

Nothing else is added. The manifest points at the existing launcher,
`tools/mcp/mcp-server.ps1`, which imports the module from `src/IntuneGraphKit`.

The root is the plugin, rather than a subdirectory, because of how Claude Code installs
plugins. It copies a marketplace plugin into its local cache
(`~/.claude/plugins/cache`) and does not let a plugin reference files outside its own
directory. A plugin in a subdirectory could not reach the launcher or the module; it
would need a second copy of both, or a PowerShell Gallery install as a prerequisite.
With `"source": "./"` in the marketplace entry, the launcher and the module travel with
the plugin. The cost is that the cache holds the whole repository — site, tests and
fixtures included — rather than just the module.

Once shipped, installing it is two commands in Claude Code:

```text
/plugin marketplace add juandresrodca/IntuneGraph
/plugin install intunegraph@intunegraph
```

---

## The manifest — `.claude-plugin/plugin.json`

```json
{
"name": "intunegraph",
"displayName": "IntuneGraph",
"description": "Ask why an Intune policy applies to a device or user and get the group path. Six read-only tools over a local graph.json snapshot; never connects to your tenant.",
"author": { "name": "Juan Andres Rodriguez" },
"homepage": "https://juandresrodca.github.io/IntuneGraph/",
"repository": "https://github.com/juandresrodca/IntuneGraph",
"license": "MIT",
"keywords": ["intune", "microsoft-intune", "entra-id", "mcp", "powershell"],
"userConfig": {
"snapshot": {
"type": "file",
"title": "IntuneGraph snapshot",
"description": "A graph.json written by Export-IntuneGraph. The server reads this file and nothing else.",
"required": true
}
},
"mcpServers": {
"intunegraph": {
"command": "pwsh",
"args": [
"-NoLogo", "-NoProfile", "-NonInteractive",
"-File", "${CLAUDE_PLUGIN_ROOT}/tools/mcp/mcp-server.ps1",
"-Path", "${user_config.snapshot}"
]
}
}
}
```

Each choice, and why:

| Choice | Reason |
|---|---|
| Snapshot path as `userConfig`, type `file` | Claude Code asks for it when the plugin is enabled and substitutes it into `args` as `${user_config.snapshot}`, so the server needs no new configuration code. It is not marked `sensitive`: the value is a path, stored in user `settings.json`. The file it points at *is* sensitive — see [SECURITY.md](../SECURITY.md). |
| `${CLAUDE_PLUGIN_ROOT}` for the launcher | Resolves to the plugin's installed copy in the cache, wherever that is. |
| `-File` with the launcher, not `-Command` | With `-Command`, the host tries to CLIXML-deserialise the redirected JSON-RPC stream. [docs/mcp.md](mcp.md) has the detail. |
| `-NoProfile` | Load-bearing, not decoration: a profile that prints anything corrupts stdout before the handshake. |
| `pwsh` only | A manifest declares one command. The plugin therefore needs PowerShell 7; Windows PowerShell 5.1 users keep the `claude mcp add` route in [docs/mcp.md](mcp.md), which works today. |
| No `version` | Setting it pins users to that string until it is bumped — a second version number to keep in step with `ModuleVersion`. Revisit at the first tagged release. |
| No `allowed-tools` in the skill | The skill does not pre-approve its own tools. Whether a tool call runs without asking stays with the user's permission settings — the right default for anything pointed at tenant data, even read-only. |

Enabled through the plugin, the tools are scoped to it — `intune_target` becomes
`mcp__plugin_intunegraph_intunegraph__intune_target`, and so on. That scoped name is
what a hook matcher has to use.

## The marketplace entry — `.claude-plugin/marketplace.json`

```json
{
"name": "intunegraph",
"owner": { "name": "Juan Andres Rodriguez" },
"plugins": [
{
"name": "intunegraph",
"source": "./",
"description": "Ask why an Intune policy applies to a device or user and get the group path. Read-only, over a local snapshot."
}
]
}
```

`intunegraph` is not one of the marketplace names Claude Code reserves.

---

## The six tools

Exactly the tools `Start-IntuneGraphMcp` already serves, unchanged. All six are
read-only; none of them can reach Microsoft Graph, because the server holds the session
disconnected for its whole lifetime.

| Tool | Answers | Required input | Optional input |
|---|---|---|---|
| `intune_target` | What applies to a device or user, and the `Via` path that causes each assignment | `identity` | `type`, `includeExcluded` |
| `intune_blast_radius` | What reaches a group's members — and, with a what-if, exactly what a membership change would add or remove | `group` | `whatIfAddMember`, `whatIfRemoveMember` |
| `intune_orphans` | Assignment hygiene: unassigned workloads, empty or deleted targets, include/exclude collisions, unused filters, mixed targeting | — | `check`, `severity` |
| `intune_path` | How two things are connected: why a workload reaches an entity, how a device nests into a group | `from`, `to` | — |
| `intune_node` | Name search, for pinning down the exact name to use | — | `query`, `type`, `limit` |
| `intune_summary` | Tenant, snapshot age and object counts | — | — |

---

## The skill — `skills/intune-assignments/SKILL.md`

The whole file. It is the method from *Why package it at all*, written for the
assistant rather than the admin.

````markdown
---
name: intune-assignments
description: Explain Microsoft Intune assignments from a local IntuneGraph snapshot - why a policy, app or script applies (or does not) to a device or user, with the group path through nesting, exclusions and filters; what adding or removing a group member would change; and which assignments are broken or orphaned. Use when someone asks why something applies, what touching a group would affect, or for an assignment hygiene check.
---

# Intune assignments, from the graph

You have six read-only tools over a `graph.json` snapshot of one Intune tenant. They
cannot change the tenant and do not connect to it. Answer from the tools, not from
general knowledge of how Intune assignments usually behave.

## Before the first answer

1. Call `intune_summary` and note `exportedAt`. When the answer depends on recent
changes, say how old the snapshot is. Stale data is fixed by the user re-running
`Export-IntuneGraph`, not by you.
2. If a name is not exact, find it with `intune_node` first; wildcards work
(`*Finance*`). If an identity is ambiguous the tool returns the candidates - ask
which one is meant rather than picking.

## Which tool for which question

- Why does X apply to Y? - `intune_path` from the device or user to the workload.
Quote the `via` path exactly as returned.
- What applies to Y? - `intune_target`. Add `includeExcluded` when the question is
about something that is missing.
- What if a member is added or removed? - `intune_blast_radius` with
`whatIfAddMember` or `whatIfRemoveMember`. Report the gains and the losses; the
simulation changes nothing.
- What is broken? - `intune_orphans`, High severity first.

## Reading the answers

- `Applies`: the assignment reaches the entity through the path shown.
- `AppliesPreFilter`: the assignment reaches it, but an assignment filter decides at
check-in. IntuneGraph does not evaluate filter rules - name the filter and its mode,
and say the outcome depends on the device.
- `Excluded`: an exclusion blocks it, and an exclusion wins over an include.

## Limits to state, not hide

- This is a snapshot, not the live tenant.
- Nothing here can make a change. If a change is wanted, give the steps for the Intune
portal and say they have to be carried out there.
- Never ask for tenant credentials. The server holds none and needs none.
````

---

## What shipping it takes

- [ ] `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json` and
`skills/intune-assignments/SKILL.md` added as specified here, and
`claude plugin validate` passes.
- [ ] Added from a local checkout with `/plugin marketplace add ./`, the plugin asks for
the snapshot when it is enabled and lists exactly six tools.
- [ ] Against an `Export-IntuneGraph -DemoData` snapshot, *why does Win11 Security
Baseline apply to DEV-FIN-01?* comes back as
`DEV-FIN-01 -> SG-Finance -> SG-AllStaff`.
- [ ] No change to `Start-IntuneGraphMcp`, the launcher or the tools.
- [ ] The README gains an install line in the same change that makes the plugin
installable, and not before.
2 changes: 1 addition & 1 deletion src/IntuneGraphKit/IntuneGraphKit.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
CompanyName = 'IntuneGraph'
Copyright = '(c) 2026 Juan Andres Rodriguez. MIT License.'
# Same text as the GitHub repository description - change both together.
Description = 'Turn your Microsoft Intune tenant into an interactive relationship graph. See what applies to a device or user and why, preview the blast radius before you touch a group, and find orphaned or broken assignments. Read-only Graph, offline-capable demo, self-contained HTML viewer. Zero write scopes.'
Description = 'Turn your Microsoft Intune tenant into an interactive relationship graph, and ask your AI why a policy applies through the bundled MCP server. See what applies to a device or user and why, preview a group''s blast radius, find broken assignments. Read-only Graph, offline demo, self-contained HTML viewer. Zero write scopes.'

# 5.1 floor broadens adoption (many Intune admins are still on Windows PowerShell).
# PowerShell 7+ is recommended but not required.
Expand Down
Loading