Practice Lab

Connecting Claude to Clio — and Why I Had to Build It Myself

May 4, 2026· David J.S. Madgett · 15 min read

Ask an AI assistant a question about your practice and you hit the same wall immediately: it does not know anything about your practice.

“Draft a status letter for the Whitfield matter” produces a beautifully written letter about nothing, because the model has never heard of Whitfield. So you paste. The client’s name, the posture, the last three filings, the deadline you are worried about. Then you do it again tomorrow, because tomorrow’s session has never heard of Whitfield either.

That loop is the main reason AI feels like a toy in a law office rather than a tool. It is also a confidentiality decision you are making dozens of times a day without particularly noticing that you are making it.

The fix is obvious in principle: let the assistant read the practice management system directly. Here is what that took, and why it took more than it should have.


What should have existed

There is an open standard for this. The Model Context Protocol is a published specification for connecting AI applications to outside systems — the documentation’s own analogy is that it is a USB-C port for AI. Rather than every assistant building a bespoke integration with every data source, the data source exposes one MCP server, and any MCP client can talk to it. Claude supports it. So does ChatGPT, and VS Code, and a growing list of others. Build the connector once, use it everywhere.

The shape it takes in a law office is worth drawing, because it is not what most lawyers assume:

┌──────────────┐        ┌──────────────────┐        ┌──────────────┐
│  Claude      │◄──────►│  Clio connector  │◄──────►│  Clio API    │
│  (the client)│  MCP   │  (runs on YOUR   │  HTTPS │  app.clio.com│
│              │        │   machine)       │  OAuth │  /api/v4     │
└──────────────┘        └──────────────────┘        └──────────────┘

Three things follow from that diagram and all three are commonly misunderstood.

It is not a data upload. Nothing ships your database anywhere. The connector sits in the middle; when you ask about a matter, the assistant calls one tool, the connector makes one API request, and one answer comes back.

It is not autonomous. The connector exposes a fixed menu. If there is no “create a time entry” tool, no amount of prompting produces a time entry. The boundary is code, not instruction.

It runs where you put it. A connector on your own machine using your own authenticated session is a completely different confidentiality posture from a cloud service holding your credentials. Both get called “connectors.” They are not the same thing.

So: an open standard, broad client support, a sensible architecture. The natural expectation is that your practice management vendor publishes one.

As of this writing, Clio does not. There is no official, first-party MCP server. What exists is a handful of open-source ones written by other firms and small vendors, of varying coverage and varying quality.


So I wrote it

Not as an MCP server, as it happens — my integration predates my interest in the protocol, and it is a plain Python client that Claude Code executes. Three files. The size of each is roughly inverse to its importance.

keychain.py — twenty lines

Two functions wrapping the macOS security command: read a secret, write a secret. Every credential lives in the operating system keychain and nowhere else. No .env file, no config constant, nothing in version control.

I want to be unglamorous about why this is the most important file in the project. A Clio refresh token is a permanent key to every client file the firm has, and it does not expire on its own. If it lands in a file inside a synced folder, it now exists on a vendor’s servers and on every device that folder reaches. Twenty lines is the difference between that and not that.

clio_auth.py — a hundred and twenty lines, run once

The OAuth authorization-code flow. It opens a browser, you approve the application against your own Clio account, it catches the redirect on a local listener, exchanges the code for tokens, writes them to the keychain. Then you never run it again.

Two details worth copying. It generates a random state value before sending you to Clio and verifies the callback returns the same value — the standard defense against somebody else’s authorization response being fed to your listener. And it stores three things: the refresh token, the current access token, and the access token’s expiry, so nothing downstream has to make a pointless round trip to find out whether its token is still good.

clio_client.py — the client itself

Its docstring is one sentence and it is the entire design: a thin Clio v4 REST client with an endpoint allowlist.


The allowlist is the security model

This is the part I would most want another lawyer to take away.

The client does not expose Clio’s API. It exposes a hand-written list of the operations this practice actually needs — about twenty named methods with typed arguments. Search matters. Get a matter. Search contacts. List calendar entries. List tasks. List time entries. A few writes. Each exists because someone needed it for a specific job.

Every other endpoint in Clio’s API is not reachable. Not discouraged, not blocked by an instruction in a prompt. There is no method to call. If a model concluded at three in the morning that deleting a matter was the right move, there is nothing to invoke.

That is a categorically different guarantee than telling an assistant to be careful. A prompt is a request. An unimplemented endpoint is a wall.

The corollary is where the discipline gets tested: adding a method is a deliberate act. The moment you write a generic call_any_clio_endpoint(path, method, body) helper because it would be convenient, you have deleted the allowlist and replaced it with a suggestion.


The plumbing that keeps it usable

Three small behaviors, each present because its absence was maddening.

Token refresh is automatic and invisible. Access tokens last about an hour. Rather than checking expiry everywhere, every request attaches the current token; if Clio answers 401 on the first attempt, the client refreshes and retries once. One retry, not a loop — a persistent 401 is a real problem and should surface.

Rate limiting is handled by waiting. On a 429, read the Retry-After header and sleep that long. That is the whole strategy and it has never needed to be more. The polite response to being asked to slow down is to slow down.

Pagination follows the cursor. Clio returns pages with a meta.paging.next URL. The client yields from a generator and follows the cursor, so calling code asks for “matters matching X, limit 25” and gets 25 without knowing pages exist.

Field selection is a confidentiality control

One that deserves its own heading because it is usually framed wrong. Clio’s API takes a fields parameter specifying exactly what comes back, including nested values like client{id,name}. Every method in my client declares a narrow default.

This gets described as a performance feature. In a law office it is a minimum-necessary control. Whatever you request enters the assistant’s context and becomes part of a conversation. Request everything because enumerating was tedious, and everything about that matter is now in that context — including fields you had no use for. Declaring a narrow field list per operation makes restraint the default and widening it deliberate.


Four things the documentation does not lead with

These cost me time and may save you some.

You cannot create a bill through the API. POST /bills returns a 404. Time entries can be posted programmatically, but generating the actual invoice is a user-interface operation. Any workflow that assumes it can run from captured time straight through to an issued invoice does not exist. Plan for a human step — which, given that a bill is an assertion to a client, is probably where one belongs anyway.

Task creation requires an assignee. Omit it and the call fails in a way that is not obviously about the assignee.

Time is stored in seconds. The quantity field on a time entry is seconds, not hours. Divide by 3,600. This is the kind of thing that produces a report claiming you worked four thousand hours last Tuesday, and once seen is never forgotten.

Rounding happens on Clio’s side, upward, to the tenth of an hour. Stage entries on tenth-of-an-hour multiples before approving, or the number you approved and the number your client is billed will differ.


Why this should not have been my job

Everything above took a weekend, most of it OAuth. I am reasonably technical and I enjoyed it. And it should not have been necessary, for reasons that go past my convenience.

Every firm is writing the same four hundred lines. Right now, in some number of law offices, somebody is working out that quantity is in seconds. Somebody else is discovering that POST /bills 404s. Each of them is solving a problem that has exactly one correct solution, which the vendor already knows, and which the vendor could publish once.

The vendor is the only party positioned to do it properly. A first-party connector could do things no firm-built one can. Genuinely scoped permissions, so a connector could be granted read on matters and calendar and nothing else, enforced server-side rather than by my own restraint in choosing which methods to write. Per-user tokens rather than a firm-wide key. An audit log on the vendor’s side showing what an assistant actually accessed — which, when a client asks what an AI system did with their file, is the difference between an answer and a shrug. And a revocation switch that works even if the machine holding the token is gone.

I built an allowlist because it was the best control available to me. It is a strictly worse control than server-side scopes, and I know it.

The gap is being filled, and that is the worrying part. Because there is no official connector, community ones exist. Firms are installing them. Understand precisely what that means: you are handing someone else’s code an OAuth token to your entire client file.

If you are going to do that, read the code — not skim it. Four questions, answerable from the source or the answer is no: does it ever send anything to a host other than Clio; where does it store the refresh token; does it log request or response bodies to disk, and where; can it write, and under what conditions. Minnesota Rule of Professional Conduct 1.6(c) requires “reasonable efforts to prevent the inadvertent or unauthorized disclosure of, or unauthorized access to, information relating to the representation of a client,” and “I installed a package with good reviews” is not obviously a reasonable effort when the package holds keys to every client file you have.

That is not a knock on the people writing these. They are filling a real gap and some of them are careful. It is that a vendor’s silence has pushed a security decision onto every individual firm, and firms are the least equipped party in the chain to evaluate it.

And there is a cost beyond security. The whole argument of this section is that the price of legal services comes down when lawyers can carry more work well. A solo who could most use this leverage is exactly the person least able to spend a weekend on OAuth plumbing — and every hour any of us spends rebuilding the same connector is overhead that ends up in somebody’s bill, or in a case somebody could not afford to take. A vendor serving tens of thousands of firms is the one place where this work is done once instead of ten thousand times. That is what platforms are for.

The API limits point the same direction. You cannot create a bill. Practice management vendors built their APIs for integrations they anticipated — document management, accounting sync — and an assistant that needs to read broadly and write narrowly is not the shape anyone designed for. That is forgivable as history. It is less forgivable as a roadmap.


Where that leaves you

Build your own if you can. It is a weekend, you control the surface area, no third party holds your token, and it works from cron and launchd as well as from a chat window — which matters, because most of my firm’s Clio traffic is scheduled automation rather than conversation.

If you adopt one, read the source. All four questions. If you cannot answer them, do not run it.

And do not agonize over the protocol. Scripts and MCP are not competing security models — they are competing interfaces to the same one. An MCP server for Clio would be a wrapper around the same allowlist. The wall is the set of operations that exist; the protocol is how you knock on it. The real question is never “MCP or scripts,” it is “have you decided what operations exist, and are the credentials somewhere sane.” If the answer is no, MCP will not save you; it will just make an oversized surface more discoverable.

For what it is worth: if I were starting today with conversational use as the main case, I would write the MCP server, because discovery and typed arguments are genuinely better in a chat. With scheduled jobs as the main case, I would write exactly what I have.


The write boundary, honestly

A few of my allowlisted methods do write — create a matter, a contact, a task, a time entry. Each exists for a specific workflow a person drives, and the governing rule is that nothing writes to Clio without a person approving that specific write. Time entries are staged as a table for review. Matters are created through an intake flow that reads details back before posting. No scheduled job writes to Clio.

I would like to tell you that is enforced structurally. It is not. It is enforced by workflow discipline and by the fact that no automated process happens to call a write method. If I were building it again I would put the write methods in a module the scheduled jobs do not import, so the boundary was a fact about the code rather than a fact about my habits. That is the honest state of it and it is the next thing I will fix.

Which is, in miniature, the same argument I am making about Clio. A control that depends on someone remembering is not a control. It is an intention.


Starting from zero

  1. Register an application in the developer portal. Client ID, client secret, redirect URI pointing at your own machine.
  2. Write the keychain wrapper first. Twenty lines. Decide where the token lives before you have one.
  3. Run the OAuth flow once, with state verification. Store refresh token, access token, expiry.
  4. Write three read methods, not twenty. Search matters, get a matter, list calendar entries. Live with them a week; you will find out which two you actually needed.
  5. Add writes last, and only for a workflow with a human in it.
  6. Keep the allowlist an allowlist. The generic passthrough helper is the moment you gave up.

Sources

General commentary on legal technology and practice management. Not legal advice, not ethics advice, and not an endorsement of any product. Formal Opinion 512 is advisory and not binding in any jurisdiction. No client information, matter number, or account identifier appears in this article; example matters are invented.

The only thing we ask

If something here saves you time, spend some of it on people who could not otherwise afford you.

Everything in the Practice Lab is free. No signup, no subscription, no donations — just take a case you would otherwise have to turn down on economics. More from the Practice Lab →

← All Practice Lab articles