Compliance Drift Reporter
A worked example of an advanced workflow: a Claude-managed scheduled agent that, once a week, pulls two independent compliance signals — the last seven days of Liongard configuration-change detections, and the CIPP picture of which tenants have a compliance baseline and which tenants have healthy delegated access — and publishes a combined digest to Slack as a canvas with a one-line summary. There are no servers and no code to deploy: the agent is a saved prompt plus a schedule plus two MCP connectors, running in Claude’s cloud.
What it builds
Section titled “What it builds”The finished workflow runs unattended on a weekly cron. Each run it works through four phases: collect the week’s Liongard detections, collect the CIPP tenant and baseline picture, compute a short posture scorecard, and deliver a two-section report to Slack. It turns a stream of scattered configuration changes and a partially rolled-out compliance program into a single weekly drift digest with a Slack trail.
Prerequisites
Section titled “Prerequisites”Before building, your Claude account needs all of the following:
| Requirement | Notes |
|---|---|
| Conduit connector | Connected in claude.ai (https://conduit.wyre.ai/v1/mcp). Provides the liongard__* and cipp__* tools. See Connecting an AI Client. |
| Liongard connected in Conduit | The Liongard API credentials behind the vendor connection must be able to read detections via liongard__liongard_detections_list. |
| CIPP connected in Conduit | The CIPP vendor connection must be able to read tenants via cipp__cipp_list_tenants and assigned standards via cipp__cipp_list_standards. |
| Slack connector | The first-party Slack connector connected in claude.ai (https://mcp.slack.com/mcp). Provides slack_create_canvas and slack_send_message. |
| Scheduled routines access | Created via the /schedule capability; managed at claude.ai/code/routines. |
| A destination Slack channel | e.g. #sw-dev, plus its channel ID. The Slack connector must have access to it. |
Known gotchas
Section titled “Known gotchas”These are the things that cost real time the first time through. Account for them up front and the build genuinely takes minutes.
- Liongard’s
liongard__liongard_detections_listcarries a very largeChangeDetectionfield per detection — the routine reads only the small metadata and windows to 7 days to keep runs light. Each detection’sChangeDetectionfield is 5KB–44KB; even five detections is a ~145KB response. The routine reads onlyID,SystemName,Name,Date, andSystemTypeper detection and never touchesChangeDetection, and it scopes to a 7-day window so the total stays bounded. - A week yields hundreds of Liongard detections — paginate small and
cap. A typical 7-day window returns several hundred detections. The
routine pages at
pageSize5 and caps at the 25 most recent, reportingData.Pagination.TotalRowsas the true total and noting when it is showing a capped subset. - CIPP’s Best Practice Analyzer is deprecated — never call
cipp__cipp_list_bpa. The endpoint returns HTTP 503: “The Best Practice Analyser has been deprecated and will be removed in a future release.” The CIPP posture signal in this routine comes from assigned Standards and from per-tenant delegated-access health, not from BPA. cipp__cipp_list_standardsis empty until baselines are assigned in CIPP — that empty state is reported truthfully, never fabricated over. While a compliance-baseline rollout is in progress,cipp__cipp_list_standardslegitimately returns an empty array. The routine reports this as “N tenants unmanaged” and is explicitly instructed never to invent per-tenant compliance findings that are not in the data. As baselines get assigned in CIPP, the report fills in on its own.cipp__cipp_list_tenantsreturns one flat array of ~34 tenants — read only the small fields and never echoLastGraphError. Each tenant object carries aLastGraphErrorblob that can be long. The routine reads onlycustomerId,displayName,defaultDomainName,GraphErrorCount, andExcluded, and reports broken delegated access as just a tenant name plus the fact thatGraphErrorCountis non-zero.permitted_toolsmust be populated per connector. A routine with a connector attached but an emptypermitted_toolslist runs with no tools and silently does nothing — no error, no output. List the exact tool names the routine needs.- A routine reaches only its attached connectors. The routine sandbox blocks arbitrary network egress, so notifications must go through the Slack connector’s tools, not an outbound webhook. Attach every connector the workflow touches.
The one-shot build prompt
Section titled “The one-shot build prompt”With the connectors above in place, paste this to Claude. It confirms the Conduit connection, updates the existing routine, and verifies it end to end.
Build me the expanded Compliance Drift Reporter — a scheduled agent that combines Liongard configuration-change detections with CIPP baseline and posture data. Do all of this end to end:
1. Confirm the Conduit connector works: - Liongard: call liongard__liongard_detections_list with a recent 7-day startDate/endDate and pageSize 5; confirm a Data.Detections array plus a Data.Pagination envelope. - CIPP tenants: call cipp__cipp_list_tenants; confirm a flat array of tenant objects, each with customerId, displayName, defaultDomainName, GraphErrorCount and Excluded. - CIPP standards: call cipp__cipp_list_standards with tenantFilter "allTenants"; note whether it returns assigned baselines or an empty array. An empty array is expected until baselines are assigned in CIPP - it is not an error. Do NOT call cipp__cipp_list_bpa: the Best Practice Analyser is deprecated and returns HTTP 503.2. Note the shapes. Each Liongard detection carries a very large ChangeDetection field (5KB-44KB); the routine reads only ID, SystemName, Name, Date, SystemType and never ChangeDetection. cipp__cipp_list_tenants returns roughly 34 tenants; the routine reads only customerId, displayName, defaultDomainName, GraphErrorCount and Excluded, and never echoes LastGraphError.3. Confirm a Slack connector is connected and note the destination channel name and ID (e.g. #sw-dev, C0931CKJ75X). If Slack does not show in the /schedule connector list, read its connector_uuid and url from an existing routine that already uses Slack (RemoteTrigger list -> get -> mcp_connections).4. Update the existing scheduled routine named "Compliance Drift Reporter" (trigger trig_01KdNPXeYMep1SFEDEzCrPQV): - Keep the schedule: weekly, cron "0 12 * * 1" (Monday 08:00 America/New_York = 12:00 UTC). - On the Conduit connector set permitted_tools to: liongard__liongard_detections_list, cipp__cipp_list_tenants, cipp__cipp_list_standards. - On the Slack connector set permitted_tools to: slack_create_canvas, slack_send_message. - An empty permitted_tools list = the routine runs with no tools. - Install the exact routine prompt below.5. Trigger a manual run and verify: a canvas titled "Compliance Drift - <date>" was created with a posture scorecard and two sections (Liongard, CIPP), a one-line summary landed in the destination channel, the Liongard count matches Data.Pagination.TotalRows, and the CIPP counts (tenants with a baseline + unmanaged) sum to the non-excluded tenant total.The resulting routine prompt
Section titled “The resulting routine prompt”This is the lean prompt the build process installs into the scheduled routine itself. Substitute your own destination channel ID.
You are the Compliance Drift Reporter, a weekly routine. You report two compliance signals in one digest: Liongard configuration-change detections, and CIPP baseline and posture state. Treat the two sources independently - a failure in one must not block the other.
PHASE 1 - Liongard detections.Compute a 7-day window: endDate = now (ISO-8601), startDate = 7 days before now. Call liongard__liongard_detections_list with that startDate and endDate, pageSize 5, page 1.- The envelope is Data.Detections (array) and Data.Pagination ({ TotalRows, HasMoreRows, CurrentPage, TotalPages, PageSize }).- For EACH detection read ONLY: ID, SystemName, Name, Date, SystemType. NEVER read or echo ChangeDetection - it is 5KB-44KB each.- Paginate by incrementing page until you have collected 25 detections OR HasMoreRows is false. CAP at 25 (5 pages).- Record Data.Pagination.TotalRows as the true weekly total.- If liongard__liongard_detections_list errors, mark the Liongard source UNAVAILABLE and continue to Phase 2.
PHASE 2 - CIPP baseline and access health.Call cipp__cipp_list_tenants with no arguments. It returns a flat array of tenant objects.- For each tenant read ONLY: customerId, displayName, defaultDomainName, GraphErrorCount, Excluded. Skip any tenant where Excluded is true.- Access health: a tenant with GraphErrorCount > 0 has BROKEN delegated access. Record its displayName. Do NOT echo LastGraphError.Call cipp__cipp_list_standards with tenantFilter "allTenants". It returns the assigned compliance baselines and their state, or an empty array.- A non-excluded tenant absent from the standards result has NO baseline assigned - classify it UNMANAGED.- A tenant present with a passing state is PASS; with a failing state is FAIL.- If the standards result is an empty array, EVERY non-excluded tenant is UNMANAGED. This is expected while baselines are still being rolled out - it is NOT an error, and you must NEVER invent per-tenant compliance findings that are not in the data.- If cipp__cipp_list_tenants errors, mark the CIPP source UNAVAILABLE.Never call cipp__cipp_list_bpa (deprecated, HTTP 503) or cipp__cipp_run_standards_check (a write operation).
PHASE 3 - Total failure check.If BOTH the Liongard source and the CIPP source are UNAVAILABLE, call slack_send_message to channel C0931CKJ75X with: 'Compliance Drift Reporter needs a human: neither Liongard nor CIPP could be read this week.' Then stop.
PHASE 4 - Zero findings check.If every available source succeeded AND there are zero Liongard detections AND zero baseline FAIL tenants AND zero broken-access tenants, call slack_send_message to channel C0931CKJ75X with the single line: 'Compliance drift: no configuration changes, no baseline failures, no access issues this week.' Then stop. Tenants being UNMANAGED is a reportable finding, not zero findings - if any tenant is unmanaged, continue to Phase 5.
PHASE 5 - Build the report.Compose a markdown report:- SCORECARD at the top: 'Configuration changes this week: <Liongard TotalRows>. Tenants with a baseline assigned: <X> of <N>. Tenants failing baseline: <F>. Tenants with broken delegated access: <B>.' If a source was UNAVAILABLE, say so on its scorecard line instead of a number.- SECTION 'Configuration changes (Liongard)': group the collected detections by SystemName, sort each group by Date descending, one line per detection (Name and human-readable Date). If TotalRows exceeds the number collected, note 'Showing the 25 most recent of <TotalRows>.' If the Liongard source was UNAVAILABLE, the section body is the single line 'Liongard data unavailable this run.'- SECTION 'Baseline compliance (CIPP)': * Baseline coverage: if any tenant has a standard assigned, list PASS and FAIL tenants by displayName, then list UNMANAGED tenants. If no standards are assigned at all, write the single line 'No compliance baselines assigned in CIPP yet - <N> tenants unmanaged.' * Tenant access health: list the displayName of every tenant with broken delegated access, or 'All tenants have healthy delegated access.' if none. If the CIPP source was UNAVAILABLE, the section body is the single line 'CIPP data unavailable this run.'
PHASE 6 - Deliver.Call slack_create_canvas titled 'Compliance Drift - <today's date YYYY-MM-DD>' with the scorecard and both sections as markdown. Then call slack_send_message to channel C0931CKJ75X with a one-line summary linking the canvas, e.g. 'Compliance drift report ready: <TotalRows> config changes, <F> baseline failures, <B> tenants with access issues. See canvas: <link>.'
Use only these tools: liongard__liongard_detections_list, cipp__cipp_list_tenants, cipp__cipp_list_standards, slack_create_canvas, slack_send_message. Keep every step light - never read Liongard's ChangeDetection field or CIPP's LastGraphError blobs.How it works
Section titled “How it works”Two sources, reported independently
Section titled “Two sources, reported independently”The routine reads two unrelated systems — Liongard and CIPP — and they fail independently. So it treats them as separate sources: if one is unreachable the routine still delivers the other half and notes the gap in the report itself. Only when both sources fail does it escalate to a human and stop. A vendor outage never silently blanks the whole digest, and a partial report never hides that it is partial.
A weekly cadence, by design
Section titled “A weekly cadence, by design”Compliance drift is a slow-moving signal — configurations change and baselines roll out across days and weeks. The routine runs every Monday morning so the digest is waiting before the week’s review work starts. A weekly cron is also kind to the APIs: one windowed sweep per week, not one per hour.
CIPP baseline drift and the empty-state rule
Section titled “CIPP baseline drift and the empty-state rule”A CIPP Standard is a compliance baseline assigned to a tenant; a tenant
that fails its assigned Standard has drifted from baseline. The routine
reads the assigned Standards via cipp__cipp_list_standards and
classifies every tenant as pass, fail, or unmanaged (no baseline
assigned). While a baseline rollout is still in progress that endpoint
returns an empty array — so the routine is built to report the empty state
honestly as a count of unmanaged tenants, and is explicitly told never to
fabricate compliance findings over empty data. The report doubles as a
live rollout tracker: “7 tenants unmanaged” is a standing nudge to
finish assigning baselines.
Tenant access health is a day-one signal
Section titled “Tenant access health is a day-one signal”Each tenant in cipp__cipp_list_tenants carries a GraphErrorCount:
when it is non-zero, CIPP’s delegated access into that customer’s
Microsoft tenant is broken and CIPP can see nothing there. That is a real
posture problem with data available today, independent of the baseline
rollout, so the routine surfaces the list of tenants with broken delegated
access every week alongside the baseline picture.
Delivery is a Slack canvas plus a summary
Section titled “Delivery is a Slack canvas plus a summary”The combined report can be long, so the routine publishes it as a Slack canvas, a durable document teammates can scroll and reference, and posts only a one-line summary to the channel linking it. When there are no detections, no baseline failures, and no access issues at all, it posts a single line and skips the canvas. See Delivery Adapters for other ways to surface a routine’s output.
Extending it
Section titled “Extending it”The natural next step is escalation. For a system with repeated Liongard
detections week over week, or a tenant that fails its CIPP baseline run
after run, the routine could open a PSA ticket (via the autotask or
halopsa vendor connections in Conduit) so the recurring drift becomes
tracked remediation work rather than a line in a digest. A second
extension targets the access-health signal: a tenant whose delegated
access stays broken for weeks is a candidate for an automated
GDAP-relationship repair workflow. The Best Practice Analyzer is not an
extension path — CIPP has deprecated it.
Questions or a workflow you’d like documented? See Getting support.

