Microsoft Graph is the backbone of “modern Microsoft”, specifically for poking in and around Microsoft 365 via programmatical methods.
Recently, Merill Fernando (Principal Product Manager @ Microsoft) released Lokka, an AI agent tool that brings the power of Microsoft Graph to AI agents and LLMs. But what on earth is that, and what does it mean?
Lokka is an AI agent tool that brings the power of Microsoft Graph to AI agents like GitHub Copilot and Claude. The best part is you can get started for free and it runs on your desktop. Doing so gives you an idea how we may, in the not-too-distant future, administer Microsoft 365. But what does that mean?
Put simply, this is a “plug in” or “addon” to a Desktop AI assistant, which allows you to query your Microsoft infrastructure, via Graph API. Put even more simply, “This allows you to perform administrative tasks using natural language queries.”. So, you can ask the AI assistant “how many non-compliant devices do I have within Intune” and it will tell you.
This is great for less technical folk, and useful for those needing a quick answer to something which might otherwise require data from multiple places.
So how do we configure Microsoft Graph with an AI Desktop assistant? I’ll show you how to install pre-requisites and configure Claude AI Desktop assistant for integration with Microsoft Graph. This doesn’t cost, but unless you do subscribe to Claude, you will be limited to a small number of daily interactions.
Prerequisites
- Create an Entra app
- Install Node.js
- Install Claude AI Desktop assistant
- Configure Claude to utilise the Entra app
Before we start, here’s a bit about the tools required.
What is an Entra App Registration?
Registering your application in Microsoft Entra establishes a trust relationship between your app and the Microsoft identity platform. The trust is unidirectional. Your app trusts the Microsoft identity platform, and not the other way around.
What is Node.js?
Node.js is an open source, cross-platform runtime environment and library that is used for running web applications outside the client’s browser.
It is used for server-side programming, and primarily deployed for non-blocking, event-driven servers, such as traditional web sites and back-end API services, but was originally designed with real-time, push-based architectures in mind. Every browser has its own version of a JS engine, and node.js is built on Google Chrome’s V8 JavaScript engine. Sounds a bit complicated, right?
In simple terms, what this means is that entire sites can be run using a unified ‘stack’, which makes development and maintenance quick and easy, allowing you to focus on meeting the business goals of the project.
The fact that Node.js is open source means that it is free to use and constantly being tweaked and improved by a global community of developers.
An important thing to understand about Node.js is that it is actually neither a framework or a library – as with traditional application software -, but JavaScript a runtime environment.
What is Claude AI?
Claude, developed by Anthropic AI, is both an AI chatbot and the name for the underlying Large Language Models (LLMs) that power it. Claude is trained to have natural, text-based conversations, and it excels in tasks like summarization, editing, Q&A, decision-making, code-writing, and more.
Currently, Anthropic offers three “Claude” models: Claude 1, Claude 2, and Claude-Instant. While all are language-only models, each has subtle differences in capability. Claude is regularly trained on up to date information and can read up to 75,000 words at a time. This means it can read a short book and answer questions about it!
How much does Claude AI cost?
For this guide, and the average user, Anthropic provides free access to their best Claude model via their chat interface at claude.ai. Just sign up for a free account when setting the desktop assistant up!
Claude Pro is also available at $20/£18 per month. Claude Pro offers:
- 5x more usage than the free tier provides, with the ability to send many more messages
- Priority access to Claude.ai during high-traffic periods
- Early access to new features that help you get the most out of Claude
Enough of the sales pitch, how do we do it?!
I’m making a huge assumption you’re running Windows 11, because if you’re not. I can’t help you. We’re going to use winget to install the desktop tools – it simplifies everything, and of course, we need to register an Application in Entra before we do anything at all. We’ll drive using PowerShell for most of this, because again, it’s just easier.
Start by opening PowerShell, either copy this code into the PowerShell window, or more preferably, copy this code out to an editor and save it as a .ps1 – for example, New-GraphAPIAppRegistration.ps1
$Logfile = ".\New-GraphAPIAppRegistration.log"
$displayName = "Lokka.dev Graph Integration"
Function Write-Log
{
Param ([string]$LogString)
$Stamp = (Get-Date).toString("dd/MM/yyyy HH:mm:ss")
$LogMessage = "$Stamp $LogString"
Add-content $LogFile -value $LogMessage -Encoding UTF8
}
Function New-GraphAPIAppRegistration(){
<#
.DESCRIPTION
The function(s) used herein were taken from WPNinjas superb M365Doc module -- https://www.wpninjas.ch/tools/, specifically Thomas Kurth -- https://github.com/ThomasKur/M365Documentation
https://github.com/ThomasKur/M365Documentation/blob/main/PSModule/M365Documentation/Functions/New-M365DocAppRegistration.ps1
This script will create an App registration in Entra. It contains basic permissions for the purpose of performing basic Read actions within Graph API.
Global Admin privileges are required during execution of this function.
Be sure to record the generated ClientSecret after execution, this is only displayed once.
A new client secret can be generated within the App Registration if required.
James Vincent - March 2025
.EXAMPLE
$p = New-GraphAPIAppRegistration
$p | fl
ClientID : Generated-Client-ID
ClientSecret : Generated-ClientSecret
ClientSecretExpiration : Date on which the ClientSecret expires
TenantId : YourTenantID
#>
# Initialization
########################################################
Write-Log "Start Script New-GraphAPIAppRegistration"
$AzureAD = Get-Module -Name Microsoft.Graph.Authentication
if($AzureAD){
Write-Log "Microsoft.Graph.Authentication module is loaded."
} else {
Write-Log "Microsoft.Graph.Authentication module is not loaded, please install by 'Install-Module Microsoft.Graph.Authentication'."
}
$AzureAD2 = Get-Module -Name Microsoft.Graph.Applications
if($AzureAD2){
Write-Log "Microsoft.Graph.Applications module is loaded."
} else {
Write-Log "Microsoft.Graph.Applications module is not loaded, please install by 'Install-Module Microsoft.Graph.Applications'."
}
# Authentication
try{
Get-MgContract -ErrorAction Stop | Out-Null
} catch {
Write-Log "Connecting to MgGraph"
Connect-MgGraph -Scopes "Application.ReadWrite.All"
}
# Main Script
########################################################
$appPermissionsRequired = @(
"Device.Read.All",
"DeviceManagementApps.Read.All",
"DeviceManagementConfiguration.Read.All",
"DeviceManagementManagedDevices.Read.All",
"DeviceManagementRBAC.Read.All",
"DeviceManagementServiceConfig.Read.All",
"Directory.Read.All",
"Group.Read.All",
"User.Read"
)
$targetServicePrincipalName = 'Microsoft Graph'
$context = "Application"
$appPermissionsRequiredResolved = Find-MgGraphPermission | Select-Object Name, PermissionType, Id | Where-Object { $_.Name -in $appPermissionsRequired } | Sort -Property Name
if (!(Get-MgApplication | Where-Object {$_.DisplayName -eq $displayName})) {
$app = New-MgApplication -DisplayName $displayName -SignInAudience "AzureADMyOrg" -Web @{ RedirectUris="urn:ietf:wg:oauth:2.0:oob"; }
$RequiredResourceAccessArray = @()
$permissions = $appPermissionsRequiredResolved | ForEach-Object {
if($_.PermissionType -eq "Application"){
$t = "Role"
} else {
$t = "Scope"
}
$RequiredResourceAccessArray +=
@{
Id = $_.Id
Type = $t
}
}
Update-MgApplication -ApplicationId $app.Id -RequiredResourceAccess @{
ResourceAppId = "00000003-0000-0000-c000-000000000000"
ResourceAccess = $RequiredResourceAccessArray
}
# Create SPN for App Registration
Write-Log ('Creating SPN for App Registration {0}' -f $displayName)
Write-Host ('Creating SPN for App Registration {0}' -f $displayName)
$graphSpId = $(Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'").Id
$sp = New-MgServicePrincipal -AppId $app.appId
$permissions | ForEach-Object {
New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sp.Id -PrincipalId $sp.Id -AppRoleId $_.Id -ResourceId $graphSpId
}
# Create a password (spn key)
$cred = Add-MgApplicationPassword -ApplicationId $app.id
} else {
Write-Log ('App Registration {0} already exists' -f $displayName)
Write-Host ('App Registration {0} already exists' -f $displayName)
}
# Finishing
########################################################
[PSCustomObject]@{
ClientID = $app.AppId
ClientSecret = $cred.secretText
ClientSecretExpiration = $cred.EndDateTime
TenantId = $($(Get-MgContext).TenantId)
}
}
$p = New-GraphAPIAppRegistration
if ([string]::IsNullOrEmpty($p.ClientID)) {
Write-Error "ClientID is empty or null. App Registration already exists, or an error occurred during creation."
} else {
$p | fl
Write-Host "App Registration, $displayName was created."
Write-Host "The ClientSecret shown above is only revealed this one time. Record the ClientSecret now."
Write-Log "App Registration, $displayName was created."
Write-Log "ClientID: $($p.ClientID)"
Write-Log "ClientSecret: Hidden"
Write-Log "ClientSecret Expiration: $($p.ClientSecretExpiration)"
Write-Log "TenantID: $($(Get-MgContext).TenantId)"
Write-Log "Please close the PowerShell session and reopen it."
Write-Host "Please close the PowerShell session and reopen it."
}
Write-Log "End Script $Scriptname"
Executing the script will prompt you for authentication against the tenant in which you wish to create the registration. After the script is completed, you will be given your Application ID, Secret and Tenant ID. All of this information is required, and the secret is only visible this one time, so copy these details into a blank document/somewhere safe for later.

You can review the created application by logging in to Entra and going to App Registrations, this shortcut will take you straight there – enappreg.cmd.ms.
Note: This application contains basic read permissions. The permissions you assign can be tweaked in the script above or retrospectively added within the UI.

Grant admin consent for the App, by clicking on API Permissions in the left menu, and then “Grant admin consent for Tenant“.

Now switch back to PowerShell and type winget search node.js – you’ll see a few results, but the one we want is the OpenJS.NodeJS package.

Type winget install node.js

Then we’ll do the same for Claude, type winget search claude

Followed by winget install Anthropic.Claude. We use the Id because there’s more than one “claude”.

At this point, launch the Claude AI Desktop assistant, and click Get Started. You’ll need to sign in/register. Doing so is simple, enter your email address and you’ll receive a passwordless “sign in” email, which you simply click on for the app to sign in.

With the Claude Desktop app open, click on the burger in the top left and goto File > Settings

Then Developer and Edit Config

Open claude_desktop_config.json and paste in the following, but swap out the details with your Tenant ID, Client/Application Id and Client/Application Secret that we saved out of PowerShell earlier.
{
"mcpServers": {
"Lokka-Microsoft": {
"command": "npx",
"args": ["-y", "@merill/lokka"],
"env": {
"TENANT_ID": "YOUR TENANT ID",
"CLIENT_ID": "YOUR APPLICATION ID",
"CLIENT_SECRET": "YOUR APP SECRET"
}
}
}
}
Save the file and close Claude Desktop from the Task Tray.

If we reopen Claude now, and goto File > Settings > Developer, we should see that the Lokka-Microsoft plugin is running.

Now we have everything linked, we can try it out. Start by asking something fairly simple, I know I have iOS devices in my tenant, so my opening query is; How many iOS Devices are present in my Intune tenant, and what models are they?
On first run, this will display a prompt asking you to grant permission.

Accept/allow the integration to run within Claude, and sit back and relax.

Fun times.
Additional example queries could be.
- How many Windows devices are not running the latest patches?
- How many Windows 10 devices do I have within my Intune tenant?
- How many non-compliant devices do I have within my Intune tenant, and why?
What queries do you think would be useful?
Thanks to
Leave a Reply