Contract Renewal Tracker
A worked example of an advanced workflow: a Claude-managed scheduled agent that, once a week, finds Autotask contracts approaching their end date, buckets them into 30/60/90-day renewal windows, computes the recurring revenue at risk in each window, and posts a forward-looking renewal-pipeline report 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
The finished workflow runs unattended every Monday morning. Each run it searches In-Effect contracts with an end date inside the next 90 days, resolves each contract's company name, groups the results into three renewal windows, sums the monthly recurring value per window and in total, and notifies your account team — turning expiring-contract data into a renewal pipeline the team sees before the window closes, not after.
Prerequisites
Before building, your Claude account needs all of the following:
| Requirement | Notes |
|---|---|
| WYRE MCP Gateway connector | Connected in claude.ai (https://mcp.wyre.ai/v1/mcp). Provides the autotask_* tools. See the Gateway overview. |
| Autotask enabled in the gateway | The gateway's Autotask API user must be able to read contracts and companies. The API user's IP must be allowlisted in Autotask — without this, contract searches hang silently rather than returning a 403. |
| 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. #account-management, plus its channel ID. The Slack connector must have access to it. |
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.
-
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:autotask_search_contractsandautotask_search_companiesfor the gateway connector;slack_create_canvasandslack_send_messagefor Slack. - 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.
- Faster-than-hourly cron cadences are rejected; cron is in UTC.
This routine uses
0 12 * * 1— Monday 08:00 America/New_York is 12:00 UTC. Adjust the UTC offset for your preferred delivery time. - If Slack doesn't appear in the
/scheduleconnector list, pull its details from an existing routine. Use RemoteTrigger to list routines, get one that already uses Slack, and read theconnector_uuidandurlfrom itsmcp_connectionsblock. - Filter to
status=1(In Effect) — not 0 (Inactive) or expired. Autotask contracts that have already lapsed havestatus=0. Without this filter, the bucket totals include contracts that are already gone and the MRR-at-risk figure is meaningless. - Autotask date fields are ISO strings (
YYYY-MM-DD). Pass the horizon window as exact ISO dates, not relative expressions. The routine prompt should compute today's date and today-plus-90-days at run time and substitute them into the filter. - Resolve company names in as few calls as possible. Fetching one
company per contract risks hitting the 60-second tool timeout on a large result
set. Prefer a single
autotask_search_companiescall with an ID-in filter to batch-resolve all companies at once. If a company cannot be resolved, fall back to"Unknown (ID: <id>)"rather than skipping the contract. - If contract searches hang (not a 403), check IP allowlisting. Autotask silently times out requests from un-allowlisted IPs instead of returning an error. Confirm the gateway's egress IP is in the Autotask API user's allowlist before debugging anything else.
- Prefer list/search payloads over per-record
getcalls.autotask_search_contractsreturns the fields the routine needs. Calling a separate get per contract to re-fetch those same fields doubles the tool calls and the timeout risk for no gain.
The one-shot build prompt
With the connectors above in place, paste this to Claude. It confirms the gateway, creates the routine, and verifies it end to end.
Build me a scheduled Contract Renewal Tracker agent. Do all of this end to end:
1. Confirm the WYRE MCP Gateway works and Autotask is reachable: call
autotask_search_contracts with a minimal filter (e.g. status=1, limit=5) and
check that it returns results without hanging. If the call hangs rather than
returning a 403, the gateway's Autotask API user IP likely isn't allowlisted
in Autotask — resolve that before continuing.
2. Confirm a Slack connector is connected. Note the destination channel name and
ID (e.g. #account-management). 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).
3. Create a Claude-managed scheduled routine named "Contract Renewal Tracker":
- Schedule: weekly, cron "0 12 * * 1" (Monday 08:00 America/New_York =
12:00 UTC). Faster-than-hourly cadences are rejected.
- Attach TWO connectors, each with permitted_tools populated:
* WYRE MCP Gateway: autotask_search_contracts, autotask_search_companies
* Slack: slack_create_canvas, slack_send_message
An empty permitted_tools list = the routine runs with no tools.
- Routine prompt: every run, find In-Effect contracts (status=1) whose end
date falls within the next 90 days, bucket them into 0-30 / 31-60 / 61-90
day renewal windows, resolve company names, sum recurring value per bucket
and overall, then publish a renewal-pipeline canvas and post a one-line
summary to the channel. Use the exact routine prompt below.
4. Trigger a manual run and verify: a canvas titled "Contract Renewals - <date>"
was created, a one-line summary landed in the destination channel, and the
bucket counts and total MRR-at-risk figures look plausible against your
Autotask data. The resulting routine prompt
This is the lean prompt the build process installs into the scheduled routine itself. Substitute your destination channel ID; the dates are computed at run time.
You are the Contract Renewal Tracker. You run weekly. Keep it lean.
Use ONLY: autotask_search_contracts, autotask_search_companies,
slack_create_canvas, slack_send_message.
Today's date in ISO format: <today-YYYY-MM-DD>
Horizon end date (today + 90 days): <horizon-YYYY-MM-DD>
Slack channel: #account-management (channel ID: <channel-id>)
1. Call autotask_search_contracts with:
status = 1 (In Effect only)
endDate >= <today-YYYY-MM-DD>
endDate <= <horizon-YYYY-MM-DD>
If no contracts are returned, post a one-line Slack message:
"Contract Renewals - <today>: no contracts due in the next 90 days." and stop.
2. Bucket contracts by days until end date:
Window A: 0-30 days
Window B: 31-60 days
Window C: 61-90 days
3. Collect all unique companyIDs from the results. Resolve them to company names
in as few calls as possible: prefer one autotask_search_companies call with
an id-in filter over a separate get per contract. If a company cannot be
resolved, use "Unknown (ID: <id>)" rather than skipping the contract.
4. For each bucket, list: client name, contract name, end date, and contract
recurring value (monthly). Sum the recurring value per bucket and across all
three buckets (total MRR at risk).
5. Build the canvas body:
Heading: "Contract Renewals — <today>"
Sub-heading: "Total MRR at risk: $<total> across <N> contracts"
One section per window (A / B / C), each with a table:
Client | Contract | End Date | Monthly Value
Omit a window section entirely if it has no contracts.
6. slack_create_canvas with that content, titled "Contract Renewals — <today>".
7. slack_send_message to the channel:
"Contract Renewals <today>: <N> contracts, $<total>/mo at risk in 90 days — see canvas"
Include the canvas URL in the message if available.
8. If autotask_search_contracts fails or times out, post a Slack message:
"Contract Renewal Tracker could not read contracts — check gateway/Autotask
connectivity." and stop. Do not retry in a loop. How it works
A weekly cadence, by design
Contract renewals are a slow-moving calendar problem — the risk accumulates over weeks, not minutes. Running every Monday gives the account team a fresh pipeline view at the start of each week, before meetings and client calls. A weekly cron is also efficient: one Autotask search per week rather than one per hour, with no meaningful loss of signal.
The 30/60/90 renewal-window bucketing
Rather than dumping a flat list of expiring contracts, the routine groups them into three urgency windows: contracts expiring within 30 days (act now), 31–60 days (schedule the conversation), and 61–90 days (plan ahead). Account managers get an at-a-glance triage of where to spend their attention this week versus next month. The 90-day horizon is a balance: wide enough to catch renewals before the client starts evaluating alternatives, narrow enough that the report stays actionable rather than becoming a noise scroll.
MRR at risk as the headline number
Each bucket and the report header lead with the sum of monthly recurring value across its contracts. That single number — total MRR at risk — is the signal a sales or account leader needs to triage the report in a glance. Contracts with a high recurring value that are expiring soon surface immediately; a long list of low-value renewals in the 61–90 window doesn't create false urgency. The routine is read-only on Autotask; the only write is delivering the canvas and summary to Slack.
Canvas plus summary delivery
The full renewal pipeline, grouped by window with a table per bucket, can be a substantial document. The routine publishes it as a Slack canvas — a durable artifact teammates can link, share, and reference throughout the week — and posts only a one-line summary to the channel: contract count, total MRR at risk, and a link to the canvas. The channel stays readable; the detail lives in the canvas. See delivery adapters for other ways to surface a routine's output.
Extending it
The same shape works against any PSA that exposes contract end-date and recurring-
value fields. To run it against HaloPSA instead of Autotask, swap the
autotask_* tools for the halopsa-official contract
reports — the CF_Accounts_Report_Customer_Contract_Summary_Overview
custom-field report returns contract summary data in a similar structure; update
the permitted_tools list and the filter logic in the routine prompt
accordingly. The bucketing, MRR-at-risk calculation, and Slack delivery body of
the routine are identical.
You can also feed the at-risk renewals from this routine into a renewal-risk-analyzer portfolio agent — one that cross-references contract value against support-ticket volume, open incidents, or NPS data to surface which renewals are most at risk of churn, not just which are expiring soonest.
Questions or a workflow you'd like documented?
Open an issue
in the msp-claude-plugins repository.