Build a legislation Q&A agent

This guide answers a plain-language question about a title in three calls: find the title, find the provisions about the question, fetch the text. The answer carries everything a reader needs to rely on it: the compilation, the status as confirmed with the Federal Register, the verification label and a citation they can follow.

Before you begin

Steps

  1. Treat errors as answers. Every tool returns JSON. Check for error before reading anything else, and branch on error.code. A lapsed subscription returns plain text with a renewal link, so handle that too.
  2. Find the title with resolve_reference, and stop if it is not in force: Advisor will not serve its text, and neither should your agent.
  3. Find the provisions with search_provisions, scoped to the title and to sections.
  4. Fetch the text with get_provision_text, and keep its status, anchor and link with it.
  5. Show the status with the text. Say when a newer compilation exists (state other than current).
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"]
        self.error = error


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's renewal message
    if isinstance(body, dict) and "error" in body:
        raise AdvisorError(body["error"])
    return body


async def answer(title, question):
    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. Which title? The best candidate; show close alternatives to your user.
            found = await call(session, "resolve_reference", {"text": title})
            best = found["candidates"][0]
            if not best["in_force"]["in_force"]:
                return f"{best['title']} is not in force. See {best['in_force']['frl_link']}"

            # 2. Which provisions are about the question?
            hits = await call(session, "search_provisions", {
                "query": question, "legislation_id": best["id"],
                "provision_types": ["section"], "limit": 3})
            if not hits["result"]:
                return f"No provision of {best['title']} matched. Try other words."
            top = hits["result"][0]

            # 3. The text, with its status and verification.
            prov = await call(session, "get_provision_text", {
                "legislation_id": best["id"], "provision_id": top["provision_id"]})
            status, comp = prov["status"], prov["status"]["compilation"]
            lines = [
                f"{best['title']}, {prov['pinpoint']} {prov['heading']}",
                prov["text"],
                "",
                f"Compilation {comp['compilation_no']} ({comp['register_id']}), state: {comp['state']}.",
                f"In force, as confirmed with the Federal Register at {status['confirmed_with_frl_at']}.",
                f"Text check: {prov['anchor']['label']}.",
                f"Cite: https://advisor.lawatlas.ai{prov['link']['url']}" if prov.get("link") else
                f"Source: {status['frl_link']}",
            ]
            if comp["state"] != "current":
                lines.append("A newer compilation is registered; check the Federal Register before relying on this.")
            return "\n".join(lines)


if __name__ == "__main__":
    print(asyncio.run(answer("privacy act", "notifying individuals of an eligible data breach")))

Check it worked

Run python qa_agent.py. Run on 10 October 2026, it printed section 26WL of the Privacy Act 1988 ("Entity must notify eligible data breach") with these closing lines:

Compilation 104 (C2026C00227), state: current.
In force, as confirmed with the Federal Register at 2026-10-09T16:37:46.550767Z.
Text check: Verified.
Cite: https://advisor.lawatlas.ai/l/C2004A03712/compilation_104/main/sec_26wl

Letting a model choose the tools

To let a language model decide which tools to call, connect Advisor to an MCP client (Connect a client): the model then calls these same tools itself. Two rules keep its answers sound.

What to do if it did not

ProblemSolution
AdvisorError: INSUFFICIENT_SCOPEGive the key's client the legislation:read scope
No provision matchedUse the words the law uses, or scope with provision_types removed
HTTP 401Check LAWATLAS_API_KEY; see Troubleshooting

Next steps