api-patterns
3CX's native PBX MCP server: the per-PBX endpoint shape (every PBX is its own FQDN and its own OAuth authorization server — there is no shared mcp.3cx.com),…
The M365 subscription → SKU → service-plan model, seat availability versus consumption, assigning and removing licenses through Graph, the audit workflow for finding unused or misallocated seats, common SKU GUIDs, and licensing error causes.
$ npx -y skills add wyre-technology/msp-claude-plugins --skill licensing --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/licensingContext preview
The summary Claude sees to decide when to auto-load this skill.
The M365 subscription → SKU → service-plan model, seat availability versus consumption, assigning and removing licenses through Graph, the audit workflow for finding unused or misallocated seats, common SKU GUIDs, and licensing error causes.
name: "Microsoft 365 Licensing" description: > The M365 subscription → SKU → service-plan model, seat availability versus consumption, assigning and removing licenses through Graph, the audit workflow for finding unused or misallocated seats, common SKU GUIDs, and licensing error causes. when_to_use: >- When checking, assigning, removing, or auditing Microsoft 365 licenses for a customer tenant. Use when: m365 license, microsoft 365 license, m365 seats, m365 sku, license audit, license usage m365, unused license m365, license assignment m365, m365 subscription, or license optimization.
M365 licensing is a top billing concern for MSPs. Licenses are purchased as SKU subscriptions, each containing bundles of service plans (Exchange, Teams, SharePoint, etc.). Efficient license management — finding unused seats, rightsizing SKUs, ensuring all users have what they need — directly impacts both the MSP's margin and the customer's costs.
Assigning a seat and buying a seat are different systems, and the word "license" covers both:
the tenant *owns* happens at the distributor, not in Graph. `subscribedSkus` only reports what was already purchased. Use `pax8` (`pax8-subscriptions`), `sherweb`, or `cipp` for CSP licences.
lives with the distributor and in the PSA contract, not in Entra; use `pax8` (`pax8-invoices`) or the `finance-pack`.
plugin (`cipp-licenses`).
vetted query catalogue answers this without composing a `$filter`; use the `microsoft-graph` plugin's `microsoft-graph-querying` skill.
M365 Business Premium (subscription)
└── GUID: cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46
├── Exchange Online (service plan)
├── Microsoft Teams (service plan)
├── SharePoint Online (service plan)
├── Intune (service plan)
└── Entra ID P1 (service plan)| State | Meaning | |-------|---------| | `Enabled` | Service plan is active and usable | | `Disabled` | Plan is turned off for this user (license still assigned) | | `Error` | Assignment failed — usually missing `usageLocation` | | `LockedOut` | Tenant billing issue | | `PendingInput` | Waiting for additional configuration |
GET /v1.0/subscribedSkus?$select=skuPartNumber,skuId,consumedUnits,prepaidUnits,servicePlans
**Response:**
{
"value": [
{
"skuPartNumber": "SPE_E3",
"skuId": "05e9a617-0261-4cee-bb44-138d3ef5d965",
"consumedUnits": 42,
"prepaidUnits": {
"enabled": 50,
"suspended": 0,
"warning": 0
},
"servicePlans": [...]
}
]
}**Available seats = `prepaidUnits.enabled` - `consumedUnits`**
GET /v1.0/users?$select=id,displayName,userPrincipalName,accountEnabled,assignedLicenses,usageLocation&$top=999
Filter by SKU GUID:
GET /v1.0/users?$filter=assignedLicenses/any(x:x/skuId eq cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46)&$select=id,displayName,userPrincipalName,accountEnabled
GET /v1.0/users?$filter=assignedLicenses/$count eq 0&$count=true&$select=id,displayName,userPrincipalName,accountEnabled
> Requires `ConsistencyLevel: eventual` header and `$count=true`
POST /v1.0/users/{userId}/assignLicense
Content-Type: application/json
{
"addLicenses": [
{
"skuId": "cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46",
"disabledPlans": []
}
],
"removeLicenses": []
}> `usageLocation` must be set on the user before assigning. Use `PATCH /v1.0/users/{id}` with `"usageLocation": "US"` first.
POST /v1.0/users/{userId}/assignLicense
Content-Type: application/json
{
"addLicenses": [],
"removeLicenses": ["cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46"]
}Assign a license but disable specific plans (e.g., give E3 without Teams):
POST /v1.0/users/{userId}/assignLicense
Content-Type: application/json
{
"addLicenses": [
{
"skuId": "05e9a617-0261-4cee-bb44-138d3ef5d965",
"disabledPlans": ["57ff2da0-773e-42df-b2af-ffb7a2317929"]
}
],
"removeLicenses": []
}Pull `subscribedSkus` and calculate:
Find licenses assigned to disabled accounts — these are reclaim candidates:
GET /v1.0/users?$filter=accountEnabled eq false and assignedLicenses/$count ne 0&$count=true&$select=id,displayName,userPrincipalName,assignedLicenses
Users licensed but not signing in (90+ days):
GET /v1.0/users?$filter=accountEnabled eq true&$select=id,displayName,userPrincipalName,assignedLicenses,signInActivity
Filter results where `signInActivity.lastSignInDateTime < (today - 90 days)`.
| Optimization | Estimated Saving | |--------------|----------------| | Remove licenses from disabled accounts | # disabled × monthly seat cost | | Downgrade inactive users to lighter SKU | SKU price delta × count | | Recover unused purchased seats | (purchased - consumed) seats available |
| SKU Part Number | GUID | Notes | |-----------------|------|-------| | `SPE_E3` | `05e9a617-
One command to supercharge Claude Code for MSP workflows. Then restart Claude Code. That's it. Documentation: mcp.wyre.ai
Repo: wyre-technology/msp-claude-plugins
3CX's native PBX MCP server: the per-PBX endpoint shape (every PBX is its own FQDN and its own OAuth authorization server — there is no shared mcp.3cx.com),…
3CX's live-operations surface: read-only visibility into active calls, recordings, voicemail, department and queue membership, and forwarding/presence…
3CX's read-only directory surface: resolving a caller by email or by exact extension, searching the PBX's own phonebooks, searching contacts synced from an…
3CX's system-and-configuration surface: server time, PBX event log and application log search, service status, database schema and the read-only SELECT-only…
Abnormal Security abuse mailbox cases: user-reported email submissions, case statuses and judgments, the case lifecycle, bulk and remediation actions, and…
Abnormal Security message analysis: message retrieval, email header inspection, attachments, sender reputation, delivery context, and SPF/DKIM/DMARC…