339 lines
11 KiB
Markdown
339 lines
11 KiB
Markdown
# Step‑by‑Step Guide: Building an Intune MCP Server (Phase 1 – Read‑Only)
|
||
|
||
***
|
||
|
||
## 0. What you are building (anchor this first)
|
||
|
||
From both documents, the **Intune MCP** is defined as:
|
||
|
||
> A read‑only MCP server that exposes **live Microsoft Intune device state** (inventory, compliance, ownership, last check‑in) to AI clients, using the same delivery pattern as Identity MCP. [\[wheelsinc-...epoint.com\]](https://wheelsinc-my.sharepoint.com/personal/castn1_wheels_com/Documents/Microsoft%20Copilot%20Chat%20Files/intune-mcp-prerequisites-and-checklists.md), [\[wheelsinc-...epoint.com\]](https://wheelsinc-my.sharepoint.com/personal/castn1_wheels_com/Documents/Microsoft%20Copilot%20Chat%20Files/CoPilot%20Generated%20Deployment%20Plan.md)
|
||
|
||
Key constraints (do **not** skip these):
|
||
|
||
* **Read‑only only** in Phase 1
|
||
* **Microsoft Graph is the backend**
|
||
* **Stable tool contracts** (no raw Graph payloads)
|
||
* **STDIO MCP transport**
|
||
* **Per‑tool audit logging**
|
||
* **No device actions yet**
|
||
|
||
***
|
||
|
||
## 1. Complete prerequisites (must be done first)
|
||
|
||
Everything in this section is taken directly from [intune-mcp-prerequisites-and-checklists.md](https://wheelsinc-my.sharepoint.com/personal/castn1_wheels_com/Documents/Microsoft%20Copilot%20Chat%20Files/intune-mcp-prerequisites-and-checklists.md?EntityRepresentationId=aab065d3-d6ce-46e8-8e59-4a042ed7b2f5). [\[wheelsinc-...epoint.com\]](https://wheelsinc-my.sharepoint.com/personal/castn1_wheels_com/Documents/Microsoft%20Copilot%20Chat%20Files/intune-mcp-prerequisites-and-checklists.md)
|
||
|
||
### 1.1 Governance & ownership
|
||
|
||
Confirm and document:
|
||
|
||
* Product owner: Endpoint / Intune
|
||
* Security owner
|
||
* Operational owner (Service Desk / Endpoint Ops)
|
||
* Approved operation list (read operations only)
|
||
* Signed read vs write boundary
|
||
|
||
✅ **Output:** Approved Phase 1 scope document
|
||
|
||
***
|
||
|
||
### 1.2 Microsoft tenant preparation
|
||
|
||
Verify:
|
||
|
||
* Intune tenant is healthy
|
||
* Microsoft Graph access is approved
|
||
* A non‑production tenant or pilot scope exists
|
||
|
||
✅ **Output:** Tenant readiness confirmation
|
||
|
||
***
|
||
|
||
### 1.3 App registration (authentication model)
|
||
|
||
Create an **Azure App Registration** for the Intune MCP:
|
||
|
||
* Authentication:
|
||
* ✅ Certificate‑based auth (preferred)
|
||
* ⛔ Client secret (temporary only)
|
||
* Create a **service principal**
|
||
* Define token lifetime & rotation policy
|
||
|
||
✅ **Output:** App ID, Tenant ID, auth method documented
|
||
|
||
***
|
||
|
||
### 1.4 Microsoft Graph permissions (least privilege)
|
||
|
||
Grant **only** the following for Phase 1:
|
||
|
||
* `DeviceManagementManagedDevices.Read.All`
|
||
* `DeviceManagementConfiguration.Read.All` *(only if policy context is needed)*
|
||
* `Directory.Read.All` *(only if joining user/device info)*
|
||
|
||
Admin consent must be explicitly granted and justified. [\[wheelsinc-...epoint.com\]](https://wheelsinc-my.sharepoint.com/personal/castn1_wheels_com/Documents/Microsoft%20Copilot%20Chat%20Files/intune-mcp-prerequisites-and-checklists.md)
|
||
|
||
✅ **Output:** Permission justification record
|
||
|
||
***
|
||
|
||
### 1.5 Runtime & platform
|
||
|
||
Prepare:
|
||
|
||
* Python 3.10+
|
||
* Dependency manager (`uv` recommended)
|
||
* Secure secret storage (no tokens in code)
|
||
* Logging destination (file or central sink)
|
||
|
||
✅ **Output:** Runtime ready
|
||
|
||
***
|
||
|
||
## 2. Create the MCP project scaffold
|
||
|
||
This follows the **Identity MCP replication pattern** explicitly required in the prerequisites file. [\[wheelsinc-...epoint.com\]](https://wheelsinc-my.sharepoint.com/personal/castn1_wheels_com/Documents/Microsoft%20Copilot%20Chat%20Files/intune-mcp-prerequisites-and-checklists.md)
|
||
|
||
### 2.1 Create project
|
||
|
||
```bash
|
||
mkdir intune-mcp
|
||
cd intune-mcp
|
||
uv init
|
||
uv venv
|
||
uv add "mcp[cli]" httpx
|
||
```
|
||
|
||
***
|
||
|
||
### 2.2 Create file structure
|
||
|
||
From the **Build checklist (implementation)** section: [\[wheelsinc-...epoint.com\]](https://wheelsinc-my.sharepoint.com/personal/castn1_wheels_com/Documents/Microsoft%20Copilot%20Chat%20Files/intune-mcp-prerequisites-and-checklists.md)
|
||
|
||
intune_mcp_server.py
|
||
intune_backend.py
|
||
intune_graph_adapter.py
|
||
tests/
|
||
test_intune_adapter.py
|
||
test_integration.py
|
||
pyproject.toml
|
||
|
||
***
|
||
|
||
## 3. Implement the MCP server entrypoint
|
||
|
||
### 3.1 MCP server (STDIO only)
|
||
|
||
`intune_mcp_server.py`
|
||
|
||
```python
|
||
from mcp.server.fastmcp import FastMCP
|
||
from intune_backend import IntuneBackend
|
||
|
||
mcp = FastMCP("intune-mcp")
|
||
backend = IntuneBackend()
|
||
|
||
@mcp.tool()
|
||
async def intune_get_device(device_id: str):
|
||
"""Return core device metadata."""
|
||
return await backend.get_device(device_id)
|
||
|
||
@mcp.tool()
|
||
async def intune_get_device_last_check_in(device_id: str):
|
||
"""Return last Intune check-in timestamp."""
|
||
return await backend.get_last_check_in(device_id)
|
||
|
||
@mcp.tool()
|
||
async def intune_list_stale_devices(days: int):
|
||
"""List devices that have not checked in for N days."""
|
||
return await backend.list_stale_devices(days)
|
||
|
||
if __name__ == "__main__":
|
||
# STDIO transport is mandatory for safety
|
||
mcp.run(transport="stdio")
|
||
```
|
||
|
||
This aligns with the **recommended Phase 1 tool set**. [\[wheelsinc-...epoint.com\]](https://wheelsinc-my.sharepoint.com/personal/castn1_wheels_com/Documents/Microsoft%20Copilot%20Chat%20Files/intune-mcp-prerequisites-and-checklists.md)
|
||
|
||
***
|
||
|
||
## 4. Implement backend abstraction (safe by default)
|
||
|
||
### 4.1 Backend selector
|
||
|
||
`intune_backend.py`
|
||
|
||
```python
|
||
import os
|
||
from intune_graph_adapter import GraphIntuneAdapter
|
||
|
||
class IntuneBackend:
|
||
def __init__(self):
|
||
backend = os.getenv("INTUNE_BACKEND", "graph")
|
||
if backend == "graph":
|
||
self.adapter = GraphIntuneAdapter()
|
||
else:
|
||
raise ValueError("Unsupported backend")
|
||
|
||
async def get_device(self, device_id):
|
||
return await self.adapter.get_device(device_id)
|
||
|
||
async def get_last_check_in(self, device_id):
|
||
return await self.adapter.get_last_check_in(device_id)
|
||
|
||
async def list_stale_devices(self, days):
|
||
return await self.adapter.list_stale_devices(days)
|
||
```
|
||
|
||
This matches the **environment‑based backend selection** requirement. [\[wheelsinc-...epoint.com\]](https://wheelsinc-my.sharepoint.com/personal/castn1_wheels_com/Documents/Microsoft%20Copilot%20Chat%20Files/intune-mcp-prerequisites-and-checklists.md)
|
||
|
||
***
|
||
|
||
## 5. Implement the Microsoft Graph adapter
|
||
|
||
### 5.1 Graph adapter responsibilities
|
||
|
||
From the checklist, the adapter **must**: [\[wheelsinc-...epoint.com\]](https://wheelsinc-my.sharepoint.com/personal/castn1_wheels_com/Documents/Microsoft%20Copilot%20Chat%20Files/intune-mcp-prerequisites-and-checklists.md)
|
||
|
||
* Handle token acquisition
|
||
* Handle throttling (429)
|
||
* Map Graph fields → stable schemas
|
||
* Never return raw Graph payloads
|
||
|
||
***
|
||
|
||
### 5.2 Example adapter
|
||
|
||
`intune_graph_adapter.py`
|
||
|
||
```python
|
||
import httpx
|
||
import datetime
|
||
|
||
class GraphIntuneAdapter:
|
||
def __init__(self):
|
||
self.base_url = "https://graph.microsoft.com/v1.0"
|
||
|
||
async def get_device(self, device_id):
|
||
data = await self._get(f"/deviceManagement/managedDevices/{device_id}")
|
||
return {
|
||
"device_id": data["id"],
|
||
"device_name": data["deviceName"],
|
||
"os": data["operatingSystem"],
|
||
"owner": data.get("userPrincipalName"),
|
||
"compliance_state": data["complianceState"],
|
||
}
|
||
|
||
async def get_last_check_in(self, device_id):
|
||
data = await self._get(f"/deviceManagement/managedDevices/{device_id}")
|
||
return {
|
||
"device_id": data["id"],
|
||
"last_check_in": data["lastSyncDateTime"],
|
||
}
|
||
|
||
async def list_stale_devices(self, days):
|
||
cutoff = datetime.datetime.utcnow() - datetime.timedelta(days=days)
|
||
devices = await self._get("/deviceManagement/managedDevices")
|
||
return [
|
||
{
|
||
"device_id": d["id"],
|
||
"device_name": d["deviceName"],
|
||
"last_check_in": d["lastSyncDateTime"],
|
||
}
|
||
for d in devices["value"]
|
||
if datetime.datetime.fromisoformat(
|
||
d["lastSyncDateTime"].replace("Z", "")
|
||
) < cutoff
|
||
]
|
||
|
||
async def _get(self, path):
|
||
# token acquisition omitted here by design (handled securely)
|
||
async with httpx.AsyncClient(timeout=10) as client:
|
||
r = await client.get(self.base_url + path, headers=self._headers())
|
||
if r.status_code == 429:
|
||
raise Exception("Graph throttling encountered")
|
||
r.raise_for_status()
|
||
return r.json()
|
||
|
||
def _headers(self):
|
||
return {"Authorization": "Bearer <token>"}
|
||
```
|
||
|
||
***
|
||
|
||
## 6. Logging and audit controls (mandatory)
|
||
|
||
From the prerequisites: [\[wheelsinc-...epoint.com\]](https://wheelsinc-my.sharepoint.com/personal/castn1_wheels_com/Documents/Microsoft%20Copilot%20Chat%20Files/intune-mcp-prerequisites-and-checklists.md)
|
||
|
||
* **STDERR‑only logging**
|
||
* **Per‑tool audit record**
|
||
* **No secrets logged**
|
||
|
||
Implement:
|
||
|
||
* tool name
|
||
* parameters (redacted)
|
||
* result size
|
||
* correlation ID
|
||
|
||
✅ **Failure to do this blocks Phase 1 completion**
|
||
|
||
***
|
||
|
||
## 7. Testing gates
|
||
|
||
### 7.1 Unit tests
|
||
|
||
From the checklist: [\[wheelsinc-...epoint.com\]](https://wheelsinc-my.sharepoint.com/personal/castn1_wheels_com/Documents/Microsoft%20Copilot%20Chat%20Files/intune-mcp-prerequisites-and-checklists.md)
|
||
|
||
* Parser behavior
|
||
* Field mapping
|
||
* Error handling
|
||
|
||
### 7.2 Integration tests
|
||
|
||
* Run against **non‑production tenant**
|
||
* Compare MCP output vs Intune portal for:
|
||
* compliant device
|
||
* non‑compliant device
|
||
* stale device
|
||
|
||
✅ **Output:** Test evidence retained
|
||
|
||
***
|
||
|
||
## 8. Pilot rollout
|
||
|
||
From Phase 5 checklist: [\[wheelsinc-...epoint.com\]](https://wheelsinc-my.sharepoint.com/personal/castn1_wheels_com/Documents/Microsoft%20Copilot%20Chat%20Files/intune-mcp-prerequisites-and-checklists.md)
|
||
|
||
* Enable MCP for pilot users only
|
||
* Validate top service desk questions:
|
||
* “Devices not checking in”
|
||
* “Devices assigned to disabled users”
|
||
* Test rollback by disabling Graph backend
|
||
|
||
***
|
||
|
||
## 9. Definition of Done (Phase 1)
|
||
|
||
All must be true: [\[wheelsinc-...epoint.com\]](https://wheelsinc-my.sharepoint.com/personal/castn1_wheels_com/Documents/Microsoft%20Copilot%20Chat%20Files/intune-mcp-prerequisites-and-checklists.md)
|
||
|
||
* No write tools exist
|
||
* Stable response schemas
|
||
* Friendly errors
|
||
* Complete audit logs
|
||
* Tests passing
|
||
* Security sign‑off recorded
|
||
* Runbook published
|
||
|
||
***
|
||
|
||
## What you have when this is finished
|
||
|
||
* A **real MCP server**
|
||
* Backed by **Microsoft Graph**
|
||
* Answering **live Intune questions**
|
||
* Safe, auditable, and production‑defensible
|
||
* Ready for Phase 2 correlation with Identity + Inventory MCPs [\[wheelsinc-...epoint.com\]](https://wheelsinc-my.sharepoint.com/personal/castn1_wheels_com/Documents/Microsoft%20Copilot%20Chat%20Files/CoPilot%20Generated%20Deployment%20Plan.md)
|
||
|
||
*** |