Supply GRC data from Advisor

This guide feeds a governance, risk and compliance (GRC) platform from Advisor. In five calls it finds a regulator, finds one of its standards, reads a paragraph verbatim, lists that standard's obligations one row per item, and checks what has changed since the platform last looked. Every row it stores carries a stable id, the drafter's headings, a citation and its verification against the authorised PDF, so the platform can show where each obligation comes from and when it moved.

The worked example is APRA's CPS 230 Operational Risk Management and its business continuity obligations. The same calls work for any regulator list_issuing_authorities returns: AASB and AUASB standards are held by paragraph in the same way; ASIC instruments and Acts are held by section.

Before you begin

Steps

  1. Find the regulator with list_issuing_authorities. Its code is what the other tools take, and in_force_titles says how many of its titles Advisor holds.
  2. Find the standard with search_legislation, passing issuing_authority as a list and the standard's code as the query. Match on standard_code, not on the title: a prudential standard's registered title is a determination's name ("Banking, Insurance, ... determination No. 1 of 2026"), and its code is what a reader knows.
  3. Read a paragraph with get_provision_text and section_number such as para 33. Keep its anchor (the verification), status.compilation (which registered text it is) and link (the citation) with the text.
  4. List the obligations with list_obligations. A paragraph's list of duties is one row per item, each with the lead-in ("An APRA-regulated entity must:"). Narrow with topic (words in the headings) and verified_only, and page with next_cursor until it is null. Store obligation_id; it is stable while the paragraph stays in place.
  5. Stay current. Ask about the ids you stored with list_obligations(obligation_id=[...]): each is active, superseded (with the id that holds its words now), removed, or not found. list_recent with the regulator gives its newest titles, and get_version_changes on a standard says whether it re-made an earlier one and which paragraphs changed.
import asyncio
import json
import os

from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

URL = os.environ.get("LAWATLAS_MCP_URL", "https://advisor.lawatlas.ai/api/mcp-http/mcp")


class AdvisorError(Exception):
    def __init__(self, error):
        super().__init__(f"{error['code']}: {error['message']}")
        self.code = error["code"]


async def call(session, tool, arguments):
    """Call one tool and return its JSON, raising on an error envelope."""
    result = await session.call_tool(tool, arguments)
    text = result.content[0].text
    try:
        body = json.loads(text)
    except ValueError:
        raise RuntimeError(text)  # plain text: a lapsed subscription or a malformed argument
    if isinstance(body, dict) and "error" in body:
        raise AdvisorError(body["error"])
    return body


async def all_obligations(session, arguments):
    """Every row for these arguments, page by page, until next_cursor is null."""
    rows, cursor = [], None
    while True:
        page = await call(session, "list_obligations", {**arguments, **({"cursor": cursor} if cursor else {})})
        rows += page["items"]
        cursor = page["next_cursor"]
        if not cursor:
            return rows, page["counts"], page["not_listed"]


async def supply(regulator, standard_code, paragraph, topic, stored_ids):
    headers = {"Authorization": f"Bearer {os.environ['LAWATLAS_API_KEY']}"}
    async with streamablehttp_client(URL, headers=headers) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()

            # 1. The regulator, by the code the other tools take.
            authorities = await call(session, "list_issuing_authorities", {})
            reg = next((a for a in authorities["items"] if a["code"] == regulator), None)
            if reg is None:
                raise SystemExit(f"No regulator with code {regulator}")
            print(f"{reg['code']}: {reg['name']}, {reg['in_force_titles']} titles in force")

            # 2. The standard, by its code, among that regulator's titles.
            found = await call(session, "search_legislation", {
                "issuing_authority": [regulator], "query": standard_code, "limit": 3})
            std = next((i for i in found["items"] if i.get("standard_code") == standard_code), None)
            if std is None:
                raise SystemExit(f"No {regulator} title with standard code {standard_code}")
            print(f"{std['standard_code']} is {std['title_id']}: {std['title']}")

            # 3. One paragraph, verbatim, with its verification and citation.
            para = await call(session, "get_provision_text", {
                "legislation_id": std["title_id"], "section_number": paragraph})
            comp = para["status"]["compilation"]
            print(f"\n{standard_code} {para['pinpoint']} ({para['anchor']['label']}, "
                  f"{comp['register_id']}, state {comp['state']}):")
            print(para["text"])
            print(f"Cite: https://advisor.lawatlas.ai{para['link']['url']}")

            # 4. Its obligations on a topic, one row per item, verified only.
            rows, counts, not_listed = await all_obligations(session, {
                "title_id": std["title_id"], "topic": topic, "verified_only": True, "limit": 50})
            print(f"\n{counts['total']} verified obligations under headings containing {topic!r}:")
            for r in rows[:6]:
                lead = (r.get("lead_in") or {}).get("text")
                print(f"  {r['obligation_id']}  {r['pinpoint']}  {(lead + ' ') if lead else ''}{r['text']}")
            if len(rows) > 6:
                print(f"  ... {len(rows) - 6} more")
            print(f"Not listed in this title: {not_listed}")

            # 5. Staying current: the ids you stored, the regulator's new titles, and a re-make.
            lookup = await call(session, "list_obligations", {"obligation_id": stored_ids})
            for item in lookup["lookup"]:
                print(f"\nStored {item['obligation_id']}: {item['status']}")
            for missing in lookup.get("not_found") or []:
                print(f"Stored {missing}: not found, never issued")
            recent = await call(session, "list_recent", {"issuing_authority": [regulator], "limit": 3})
            print(f"\nNewest {regulator} titles:")
            for i in recent["items"]:
                print(f"  {i['registered_date']}  {i.get('standard_code') or '-'}  {i['title_id']}  {i['title'][:70]}")
            change = await call(session, "get_version_changes", {"legislation_id": std["title_id"], "limit": 3})
            if change.get("transition") == "remake":
                pred = change["predecessor"]
                print(f"\n{standard_code} re-made: replaces {pred['title_id']} "
                      f"({pred['in_force']['frl_status']} since {pred['in_force']['since']}); "
                      f"paragraph changes {change['summary']}")


if __name__ == "__main__":
    asyncio.run(supply(
        regulator="APRA", standard_code="CPS 230", paragraph="para 33", topic="business continuity",
        stored_ids=["F2026L00475/main/para_33/GRC01", "F2023L01242/main/para_33/GRC01"]))

Check it worked

Run python grc_supply.py. Run on 24 October 2026 against a local Advisor holding the same corpus, it printed:

APRA: Australian Prudential Regulation Authority, 386 titles in force
CPS 230 is F2026L00475: Banking, Insurance, Life Insurance, Health Insurance and Superannuation (prudential standard) determination No. 1 of 2026

CPS 230 para 33 (Verified, F2026L00475, state current):
An APRA-regulated entity must:
(a) define, identify and maintain a register of its critical operations;
(b) take reasonable steps to minimise the likelihood and impact of disruptions to its critical operations;
(c) maintain a credible BCP that sets out how it would maintain its critical operations within tolerance levels through disruptions, including disaster recovery planning for critical information assets;¹²
(d) activate its BCP if needed in the event of a disruption; and
(e) return to normal operations promptly after a disruption is over.
12 An entity may have a number of BCPs. A BCP may include separate crisis management plans and disaster recovery plans.
Cite: https://advisor.lawatlas.ai/l/F2026L00475/as_made/main/para_33

12 verified obligations under headings containing 'business continuity':
  F2026L00475/main/para_33/GRC01  para 33(a)  An APRA-regulated entity must: define, identify and maintain a register of its critical operations;
  F2026L00475/main/para_33/GRC02  para 33(b)  An APRA-regulated entity must: take reasonable steps to minimise the likelihood and impact of disruptions to its critical operations;
  F2026L00475/main/para_33/GRC03  para 33(c)  An APRA-regulated entity must: maintain a credible BCP that sets out how it would maintain its critical operations within tolerance levels through disruptions, including disaster recovery planning for critical information assets;¹²
  F2026L00475/main/para_33/GRC04  para 33(d)  An APRA-regulated entity must: activate its BCP if needed in the event of a disruption; and
  F2026L00475/main/para_33/GRC05  para 33(e)  An APRA-regulated entity must: return to normal operations promptly after a disruption is over.
  F2026L00475/main/para_39/GRC01  para 39(a)  An APRA-regulated entity’s BCP must include: the register of critical operations and associated tolerance levels;
  ... 6 more
Not listed in this title: {'titles': 1, 'titles_without_sections': 0, 'no_stable_address': 0, 'not_separable': 0, 'stopped_amending_text': 0, 'pending_reextraction_on_this_page': 0}

Stored F2026L00475/main/para_33/GRC01: active
Stored F2023L01242/main/para_33/GRC01: not found, never issued

Newest APRA titles:
  2026-10-06  ARS 222.0  F2026L01360  Financial Sector (Collection of Data) (reporting standard) determinati
  2026-10-06  ARS 221.0  F2026L01358  Financial Sector (Collection of Data) (reporting standard) determinati
  2026-09-25  GRS 112.3  F2026L01291  Financial Sector (Collection of Data) (reporting standard) determinati

CPS 230 re-made: replaces F2023L01242 (Repealed since 2026-07-01); paragraph changes {'added': 10, 'removed': 32, 'amended': 9, 'heading_changed': 1, 'moved': 56, 'ambiguous': 5}

What to notice:

What to do if it did not

ProblemSolution
AdvisorError: FORBIDDEN from list_obligationsThe key's organisation owner has no active AI Pro subscription; renew it
RuntimeError: ... Input should be a valid listPass issuing_authority as a list: ["APRA"]
No APRA title with standard code ...No title of that regulator carries that standard_code; try search_legislation with words from the standard's name
HTTP 401Check LAWATLAS_API_KEY; see Troubleshooting

What a row claims

A row says only that the provision's own text says "must" (or "must not", "shall", "is required to", "commits an offence"), quoted, under these headings, in this title, made by this regulator. No model reads or classifies it, and it is never a statement of whom the provision applies to or what it requires of a particular entity. That judgement stays with the GRC platform and its users. See list_obligations for the flags and penalties a row can carry.

Next steps