Clio Manage MCP
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Clio Manage MCPwhat are my billable hours this week?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Clio Manage MCP
Run your law practice from a chat box. A Model Context Protocol server that connects Clio Manage to Claude as a secure, per-user remote OAuth connector — ask in plain English and your matters, time, billing, calendar, and documents answer back. Deploy it to Azure in one command, or run it locally over stdio.
Built with
This server is the boundary between an AI agent (Claude, or any MCP client) and your firm's Clio Manage instance. It speaks Clio v4 fluently — matters, contacts, time, tasks, notes, calendar, documents, bills — and exposes it as a remote custom connector: each attorney adds one URL in Claude, signs in to their own Clio account, and is connected. Tokens are encrypted at rest, every tool call is audited, and one user's Clio data is never visible to another's session.
The one-minute version
azd up # 1 · deploy to Azure Container Apps (OAuth by default)
# → in Claude: Settings → Connectors → Add custom connector
# → paste https://<your-app>.azurecontainerapps.io/mcp
# → each attorney signs in to their OWN Clio account → connectedNo API keys to hand around and no shared login: Claude does discovery → registration → PKCE, then the user signs in on Clio's own domain. Prefer to stay local? The same binary runs over stdio for solo and development use.
What's in the box
A remote OAuth 2.1 custom connector for Claude: per-user sign-in to Clio, Dynamic Client Registration, PKCE, served from Azure Container Apps
41 tools across 11 Clio domains, plus a generic
clio_api_requestescape hatchA composite intake workflow (
clio_open_new_matter) that chains client + matter + opening note + intake task into one agent actionPer-session, AES-256-GCM encrypted Clio tokens, multi-replica safe on shared storage
Append-only JSONL audit log designed around ABA Formal Opinion 512
What's different about this one
Per-user OAuth, not a shared key. The headline mode (
MCP_AUTH_MODE=oauth) turns this server into an OAuth 2.0 Authorization Server + Protected Resource that bridges each Claude user to their own Clio account. No bearer tokens to hand out, no shared login. Claude does discovery → registration → PKCE → the user signs in on Clio. A simpler shared-tokenstaticmode is still available.Azure-native. A single
azd upprovisions Container Apps + ACR + Key Vault + Azure Files + Log Analytics, defaults to OAuth mode, and auto-setsPUBLIC_BASE_URLfrom the environment domain. Secrets flow from Key Vault to the container via managed identity — never on disk.End-to-end verified.
npm run smoke:stdio,npm run smoke:http, andnpm run smoke:oauthdrive a real MCP session — including the full OAuth discovery handshake — against the built binary. CI blocks regressions in protocol shape, tool registration, auth gating, and resource publication.Multi-region. US / CA / EU / AU Clio endpoints via one
CLIO_REGIONenv.
Contents
Related MCP server: clio-mcp
What you can ask Claude
Once connected, these are real prompts that route through the connector. The tool calls happen transparently; the agent picks what to call from the catalog. In OAuth mode each result reflects that user's Clio account.
Matter lookup
"Show me all open matters for Acme Corp." "What's the status of matter 2024-0042?" "Which matters have been updated since last Monday?"
Time & billing
"How many hours has the team logged on matter 4821 this month?" "What's the outstanding balance on matter 4821 and when was the last invoice issued?" "List all unbilled time entries from Jane in April."
Intake (composite workflow)
"Open a new matter for Jane Smith — landlord/tenant, flat fee $2,500. Add an opening note summarising the consultation, and create an intake task due Friday."
That last one is one clio_open_new_matter call that creates the contact,
opens the matter, applies the flat-fee custom rate, attaches the note, and
schedules the task.
Drafting (writes a note)
"Add a note to matter 4821: today's call covered scope and engagement letter; client confirmed retainer."
Calendar & tasks
"What do I have on the calendar between April 28 and May 2?" "Show my pending tasks across all open matters, grouped by priority."
Reporting / cleanup
"List all bills in
awaiting_paymentstate older than 60 days, grouped by client." "Find every contact created this year that isn't linked to a matter."
The connector retrieves Clio data live on every request. Nothing is cached or mirrored.
How connecting works
There are two ways to connect, selected by MCP_AUTH_MODE.
OAuth — remote custom connector (headline, Azure default). Each user adds the connector once and signs in to their own Clio account:
In Claude: Settings → Connectors → Add custom connector.
Paste the connector URL:
${PUBLIC_BASE_URL}/mcp(e.g.https://ca-cliomcp-prod.<region>.azurecontainerapps.io/mcp).Claude runs OAuth discovery, registers itself via Dynamic Client Registration, and starts a PKCE authorization-code flow.
The user is redirected to Clio to sign in and authorize.
Clio returns to the server's
/oauth/clio/callback, the server bridges the Clio tokens into an MCP session, and Claude lands back connected.
No bearer token is pasted anywhere. Each user's Clio tokens are encrypted and isolated to their own session.
Static — shared bearer token (secondary, single-tenant). A simpler mode for
solo or single-account setups: one shared bearer token gates /mcp, mapped to a
single shared Clio account seeded from a refresh token. See
Quick start — Azure (optional static
variant) and docs/oauth-setup.md.
Local (stdio). For development and solo use, the binary runs as a local stdio MCP server and authorizes through the loopback OAuth flow — no public URL needed. See Quick start — local.
Architecture
Azure deployment in OAuth mode (primary). The server is an OAuth 2.0 Authorization Server + Protected Resource that bridges each Claude user to their own Clio account:
Azure subscription
┌──────────────────────────────────────────────────────────┐
│ │
│ ┌────────────┐ HTTPS ┌──────────────┐ ┌─────────┐ │
│ │ Claude │─────────►│ Container │──►│ Clio v4 │ │
│ │ (each user │ OAuth │ Apps │ │ API │ │
│ │ signs in) │ + /mcp │ (stateless) │ └─────────┘ │
│ └────────────┘ └──────┬───────┘ │
│ ▲ per-user Clio sign-in │ │
│ └──────────────────────────┘ (302 via Clio login) │
│ ┌───────────────┼──────────────┐ │
│ │ │ │ │
│ ┌────▼─────┐ ┌──────▼─────┐ ┌──────▼──────┐ │
│ │ Key Vault│ │ Azure Files│ │ App Insights│ │
│ │ (RBAC) │ │ /state │ │ + Log Anal. │ │
│ └──────────┘ └────────────┘ └─────────────┘ │
│ ▲ (tokens.enc + sessions/ + audit) │
│ ┌────────┴─────────┐ │
│ │ Managed identity │ │
│ │ (KV secrets user │ │
│ │ + ACR pull) │ │
│ └──────────────────┘ │
└──────────────────────────────────────────────────────────┘Or as a Mermaid graph (renders inline on GitHub):
flowchart LR
classDef azure fill:#deebf7,stroke:#08519c,color:#000;
classDef clio fill:#fef3c7,stroke:#92400e,color:#000;
classDef host fill:#dcfce7,stroke:#166534,color:#000;
H[Claude<br/>per-user connector]:::host
subgraph AZ[Azure subscription]
direction TB
CA[Container App<br/>OAuth AS + /mcp]:::azure
KV[Key Vault<br/>RBAC]:::azure
SF[Azure Files<br/>/state mount]:::azure
LA[Log Analytics<br/>App Insights]:::azure
MI[Managed identity]:::azure
ACR[Azure Container<br/>Registry]:::azure
end
CLIO[Clio v4 API<br/>us · ca · eu · au]:::clio
H -- OAuth discovery + DCR + PKCE --> CA
H -- user sign-in (302) --> CLIO
CLIO -- /oauth/clio/callback --> CA
CA -- REST (per-user token) --> CLIO
CA --- SF
CA -. logs/metrics .-> LA
MI -. pull image .-> ACR
MI -. read secrets .-> KV
KV -. inject .-> CA
ACR -. image .-> CAResources provisioned by infra/main.bicep:
Resource | Purpose |
Log Analytics + Application Insights | Logs, metrics, traces |
Azure Container Registry (Basic) | Private image registry, anonymous pull disabled |
User-assigned managed identity | ACR pull + Key Vault Secrets User |
Azure Key Vault (RBAC, soft-delete) | Stores the Clio app + encryption secrets |
Azure Storage + File Share | Persistent |
Container Apps environment | Hosts the workload, file share registered |
Container App | HTTPS ingress, OAuth default, autoscale 1→4 by default |
HTTP surface
Endpoint | Purpose |
| Liveness — always 200 ( |
| Readiness — 200 in OAuth mode; in static/hybrid, 503 until the shared account is authenticated |
| The MCP endpoint (auth-protected). On 401 it returns |
| 405 (the server is stateless POST-only) |
In oauth / hybrid mode the server additionally serves the OAuth
Authorization Server + Protected Resource surface:
Endpoint | Purpose |
| Authorization Server metadata (discovery) |
| Protected Resource metadata for |
| Dynamic Client Registration |
| Authorization endpoint (302s the user to Clio) |
| Token endpoint (PKCE; authorization_code + refresh_token) |
| Token revocation |
| Clio's redirect target; completes the bridge |
Request lifecycle (OAuth mode)
What happens when a connected user invokes a tool — session-token check, Clio token refresh, Clio call, and audit log, end-to-end:
sequenceDiagram
autonumber
participant H as Claude<br/>(connected user)
participant S as Container App<br/>/mcp endpoint
participant V as OAuth provider<br/>(session verify)
participant K as Session store<br/>(/state, AES-256-GCM)
participant C as Clio v4 API
participant A as Audit log<br/>(/state/audit.log)
H->>+S: POST /mcp · Bearer <MCP session token><br/>{ method: tools/call, name, args }
S->>V: verify access token
Note right of V: 401 + WWW-Authenticate<br/>resource_metadata on miss
V->>K: load session → bridged Clio tokens
alt Clio token expired (or near expiry)
K->>+C: POST /oauth/token (refresh_token)
C-->>-K: new access + refresh
K->>K: re-encrypt session (AES-256-GCM)
end
S->>+C: GET/POST /api/v4/... · Bearer <Clio access>
C-->>-S: { data, meta }
S->>A: append { ts, tool, outcome, duration_ms,<br/>user_id, matter_id, result_count, transport, caller_id }
S-->>-H: JSON-RPC resultQuick start — Azure (remote connector)
Primary path. ~15 minutes the first time. Ends with attorneys adding the connector in Claude and signing in to Clio themselves.
Prerequisites
Azure subscription with the
Microsoft.AppandMicrosoft.ContainerRegistryproviders registeredaz,azd, and Docker installed locallyA Clio Developer Application (one per deployment). You'll register its redirect URI in step 4, after
azd uptells you the public URL.
1. Provision (defaults to OAuth mode)
az login
azd auth login
azd env new clio-mcp-prod
azd env set AZURE_LOCATION eastus2
azd env set CLIO_REGION us # us | ca | eu | au
azd up # builds image, runs Bicep, deploysazd up deploys with MCP_AUTH_MODE=oauth (the infra default) and auto-sets
PUBLIC_BASE_URL from the Container Apps environment domain — you never set it
by hand.
2. Populate Key Vault (3 secrets)
OAuth mode needs exactly three secrets. (No shared bearer token, no bootstrap refresh token — each user authorizes themselves.)
KV=$(azd env get-values | awk -F= '/AZURE_KEY_VAULT_NAME/{print $2}' | tr -d '"')
az keyvault secret set --vault-name "$KV" --name clio-client-id --value "<from Clio>"
az keyvault secret set --vault-name "$KV" --name clio-client-secret --value "<from Clio>"
az keyvault secret set --vault-name "$KV" --name clio-encryption-key --value "$(openssl rand -hex 32)"3. Restart the revision so the secrets bind
APP=$(azd env get-values | awk -F= '/SERVICE_API_NAME/{print $2}' | tr -d '"')
RG=$(azd env get-values | awk -F= '/AZURE_RESOURCE_GROUP/{print $2}' | tr -d '"')
az containerapp revision restart -n "$APP" -g "$RG"4. Register the redirect URI in Clio, then verify
Grab the public URL and register the connector callback on your Clio Developer Application (Settings → Developer Applications):
BASE=$(azd env get-values | awk -F= '/SERVICE_API_URI/{print $2}' | tr -d '"')
echo "Register this Redirect URI in Clio: ${BASE}/oauth/clio/callback"
curl -sS "${BASE}/healthz"
# {"status":"ok","server":"clio-mcp","auth_mode":"oauth","region":"us"}5. Add the connector in Claude → sign in to Clio
Share the connector URL with each attorney (it's the same for everyone):
${PUBLIC_BASE_URL}/mcp # e.g. https://<your-app>.<region>.azurecontainerapps.io/mcpIn Claude: Settings → Connectors → Add custom connector → paste the URL. Claude runs OAuth discovery + dynamic client registration, redirects the user to Clio to sign in and authorize, and returns connected. Each user connects their own Clio account.
Full walk-through (custom domain, logs, audit export, rotation, troubleshooting, and the optional shared-account variant): docs/deployment-azure.md.
Optional: shared-account (static) variant
For a single-tenant deployment where one shared Clio login is acceptable, deploy
in static mode instead and seed a shared bearer token + refresh token:
azd env set MCP_AUTH_MODE static
azd up
# then also set the two static-mode secrets in Key Vault and restart:
# clio-http-auth-tokens (a bearer token your clients present on /mcp)
# clio-refresh-token (from examples/bootstrap-refresh-token.mjs)Details: docs/deployment-azure.md and docs/oauth-setup.md.
Quick start — local (stdio)
Development, single-user, or seeding a shared-account refresh token for static mode.
git clone https://github.com/patrickking67/clio-manage-mcp.git
cd clio-manage-mcp
npm install
npm run build
cp .env.example .env
# fill: CLIO_CLIENT_ID, CLIO_CLIENT_SECRET, CLIO_ENCRYPTION_KEY (openssl rand -hex 32)Register http://127.0.0.1:5678/callback as a Redirect URI on your Clio
Developer Application (Clio allows several, so the same app can serve both local
stdio and the remote connector). Then wire the server into Claude Desktop /
Claude Code with one of the examples/, and run authenticate with Clio in a conversation. The encrypted token blob lives at
~/.clio-mcp/tokens.enc and auto-refreshes ahead of expiry. Full guide:
docs/deployment-local.md.
You can also run the HTTP transport locally in
hybridmode for connector development by settingPUBLIC_BASE_URL=http://localhost:8765— see docs/deployment-local.md.
Tool catalog
Domain | Tools |
Auth |
|
Matters |
|
Contacts |
|
Activities |
|
Tasks |
|
Notes |
|
Calendar |
|
Documents |
|
Bills |
|
Users |
|
Practice areas |
|
Workflows |
|
Escape hatch |
|
In OAuth mode the auth tools (clio_authenticate, clio_logout) are not used —
sign-in happens through Claude's connector flow, not from inside a conversation.
clio_auth_status / clio_who_am_i still report the current session.
Destructive operations (clio_delete_*, DELETE via clio_api_request) are
disabled unless CLIO_ALLOW_DESTRUCTIVE=true.
Resources
The server publishes two MCP resources that clients may auto-include at session start:
URI | What it carries |
| ABA Opinion 512 reminder + audit-logging summary |
| Live JSON view of authentication state and configuration |
Configuration
Variables and which mode they apply to. In an Azure deployment the OAuth-mode
variables (MCP_AUTH_MODE, PUBLIC_BASE_URL, transport, ports, state dir) are
set by the Bicep template; you only manage the Key Vault secrets.
Variable | Applies to | Required | Default | Purpose |
| all | yes | — | From your Clio Developer Application |
| all | yes | — | From your Clio Developer Application |
| all | yes | — | 64-hex (32 bytes). |
| all | no |
|
|
| http | no |
|
|
| http (oauth/hybrid) | yes in oauth/hybrid | — | Public HTTPS base URL of this server. Auto-set by Azure Bicep |
| http (oauth/hybrid) | no |
| Lifetime of an issued MCP session (30 days). Clio tokens auto-refresh |
| http (oauth/hybrid) | no | (Clio default) | Space-separated Clio scopes appended to the authorize URL |
| http (static/hybrid) | static: yes | — | Comma-separated shared bearer tokens accepted on |
| http (static/hybrid) | no | — | Seeds the single shared Clio account on first boot |
| all | no |
|
|
| http | no |
| HTTP transport port |
| http | no |
| HTTP transport bind |
| stdio | no |
| Loopback port for the local OAuth callback |
| stdio | no |
| Loopback host for the local OAuth callback |
| all | no |
| Holds |
| all | no |
|
|
| all | no |
| Enables DELETE endpoints |
| all | no |
| Records per Clio API page |
| all | no |
| Hard cap on total records returned by a list tool |
| all | no | — | Default attorney/user id for matter creation |
| all | no |
|
|
† The server's own default is hybrid; the Azure Bicep deploys oauth.
In oauth mode the required secrets are just CLIO_CLIENT_ID,
CLIO_CLIENT_SECRET, and CLIO_ENCRYPTION_KEY. CLIO_HTTP_AUTH_TOKENS and
CLIO_BOOTSTRAP_REFRESH_TOKEN are only consulted in static/hybrid mode.
Security & compliance posture
At a glance
Layer | What this server does | What you should still do |
OAuth (per-user) | OAuth 2.1 + PKCE bridge; each user signs in on Clio's own domain | Use a single Clio Developer Application per deployment |
Token storage | AES-256-GCM at rest, per session; key in Key Vault (Azure) or env | Rotate |
| OAuth session token (oauth) or shared bearer (static), constant-time | In static mode, rotate |
Audit | Append-only JSONL of every tool call (metadata or redacted args) | Export + retain per firm policy; the server does not rotate |
Destructive operations | Off by default ( | Keep off unless you have a specific reason |
Telemetry | None. Only outbound call is to your configured Clio region's API | Pair with Claude Enterprise / API+ZDR for conversation-side controls |
Detail
Per-user OAuth 2.1. In OAuth mode the server is an OAuth Authorization Server + Protected Resource. Claude discovers it, registers via Dynamic Client Registration, and runs PKCE. The user logs in directly on Clio's domain; the server never sees a Clio password. Each user's Clio tokens are bridged into an isolated, encrypted MCP session.
The encryption key never leaves the host. Tampered ciphertext fails decryption —
AES-256-GCMis authenticated encryption, so partial / tampered token blobs cannot be silently used.The audit log captures: ISO timestamp, tool name, outcome, duration in ms, Clio user id, matter id (when applicable), result count, transport identifier, and a per-caller fingerprint. In
fullmode it also records argument payloads with redaction of known-secret keys.The HTTP transport is stateless POST-only on
/mcp.GETandDELETEreturn 405. An unauthenticated/mcprequest returns 401 with an RFC 9728WWW-Authenticatechallenge pointing at the protected-resource metadata. The static-token check uses constant-time comparison to avoid timing side channels.Multi-replica safe. Sessions, registered clients, and pending authorizations live as encrypted records on the shared
/statemount, so any replica can serve any request given the same encryption key.
Threat model + Azure-specific notes: docs/security.md.
Cost (Azure)
For a typical firm at moderate volume (single-digit-thousands of tool calls/day) running one warm replica:
Component | Approx. monthly |
Container App (0.5 vCPU, 1 GiB) | ~$15 |
Container Apps environment | included |
Azure Container Registry (Basic) | ~$5 |
Key Vault (standard, low ops) | <$1 |
Azure Files (10 GiB, transactional) | ~$1 |
Log Analytics (light usage) | ~$2–5 |
Application Insights | included with workspace |
Total | ~$25–30/mo |
Setting minReplicas=0 (scale-to-zero) trades a 3–5s cold start on the
first request after idle for ~80% lower Container App spend. For a per-user
OAuth connector that attorneys hit throughout the day, one warm replica is the
usual choice.
FAQ
How do attorneys connect?
In OAuth mode (the Azure default), each attorney goes to Settings → Connectors
→ Add custom connector in Claude, pastes ${PUBLIC_BASE_URL}/mcp, and signs in
to their own Clio account when redirected. No token to copy. See
How connecting works.
Does everyone share one Clio login?
Not in OAuth mode — each user authorizes their own Clio account and only sees
their own data. The shared-login model exists only in static mode, for
single-tenant setups that opt into it.
Is this safe for client matter data? It's built for it. Clio tokens are encrypted at rest per session, every tool call is audited, no data is cached or mirrored, and no outbound calls happen besides Clio. But this server only secures the Clio-to-AI boundary — pair it with Claude Enterprise or the Claude API with Zero Data Retention so the conversations themselves get the right handling.
Does Claude train on what we send through this? It depends entirely on the Claude tier you pair this with. Claude Pro/Max (consumer): Anthropic does not train on chats by default. Claude Team / Enterprise: explicit no-training contract. Claude API with ZDR: no training, no retention. This server doesn't change any of that; the tier choice you make matters far more than anything in this codebase.
We're on Clio EU / CA / AU. Does it work?
Yes. Set CLIO_REGION to eu, ca, or au and the server routes OAuth +
API + tokens against the matching regional host. Tokens minted in one region
will not authenticate in another, by design.
How do we revoke a user's access?
OAuth mode: revoke the connector from the user's side in Claude, or call
/revoke; the firm can also revoke the Developer Application in Clio
(Settings → Developer Applications), which invalidates everyone. Static mode:
remove the bearer token from clio-http-auth-tokens and restart, or delete the
clio-refresh-token secret.
Do we have to use Azure?
No. Local stdio works completely standalone. Azure Container Apps is the
primary production path because it's the cleanest match for a stateless OAuth
MCP gateway (HTTPS ingress, managed identity, shared file mount, autoscale),
but the Docker image runs on EKS, ECS, Fly, Render, or any other container
host — set MCP_AUTH_MODE, PUBLIC_BASE_URL, and the secrets yourself, and
mount a shared volume at CLIO_STATE_DIR.
Can we use this with hosts besides Claude? The OAuth connector targets Claude's custom-connector flow. The underlying transport is standard MCP (stdio + Streamable HTTP), verified against Claude Desktop, Claude Code, and the MCP Inspector, and expected to work with any client implementing those transports.
What's the trust story for installing this? This isn't on npm. Clone, audit, build from source. No telemetry. The only outbound calls go to your configured Clio region's API.
Verification
Three protocol-level smoke tests plus a unit-test suite drive a real MCP session against the built binary. They assert on tool count, resource publication, auth enforcement, the OAuth discovery/registration handshake, and error shape. All run on every commit (build.yml).
npm run build
npm run smoke:stdio # raw JSON-RPC over spawned --stdio child
npm run smoke:http # SDK Client over Streamable HTTP against spawned --http child (static mode)
npm run smoke:oauth # OAuth discovery: metadata, dynamic client registration, /authorize -> Clio, 401 + WWW-Authenticate
npm test # unit tests: encrypted session store + Clio OAuth providerA passing stdio run:
✓ initialize -> clio-mcp 0.1.0
✓ tools/list -> 41 tools
✓ tool catalog includes expected names
✓ resources/list -> 2 resources
✓ resources/read clio://auth/status -> authenticated:false
✓ tools/call clio_who_am_i (no auth) -> isError:true
ALL CHECKS PASSED ✓A passing HTTP run (the script pins MCP_AUTH_MODE=static so it needs no
public URL):
✓ /healthz ready
✓ /mcp without bearer -> 401
✓ /mcp with wrong bearer -> 401
✓ Client.connect (initialize round-trip) succeeded
✓ tools/list -> 41 tools
✓ resources/read clio://auth/status -> authenticated:false
✓ GET /mcp -> 405 (server is stateless POST-only)
ALL HTTP CHECKS PASSED ✓Confirmed Clio API quirks
Empirical findings from Clio v4 that surprised someone. Baked into the client + tool descriptions so they don't surprise you, and listed here so the next person doesn't have to re-derive:
billing_methodat the matter root is silently ignored. To set a flat fee, PATCH the matter withcustom_rate: { type: "FlatRate", rates: [...] }.clio_create_matter'sflat_rate_amountparameter does this for you.TimeEntry.total = quantity_in_hours × rate(NOT× price). For flat-fee line items useclio_create_expense_entry(total = quantity × price).Activities GET requires explicit
fields— a bare GET returns only idetag.
descriptionis write-only; on GET usenote.rateis not a valid GET field.
Activities list filter is
matter_id(singular int).matterandmatter[id]are silently ignored — you'll get account-wide results back with no error.Mutating payloads must be wrapped
{ data: ... }. The dedicated tools do this for you.clio_api_requestwraps when you passdata:; passbody:to send something verbatim.Address
nameis enum-validated — exactlyWork,Home,Billing, orOther. The tools coerce invalid names toWork.DELETE on bills is soft-delete (void). The bill moves to
voidstate rather than disappearing.Region cross-talk fails. A token minted at
app.clio.comwill not authenticate againsteu.app.clio.com. Pick one and stick with it. The same applies to the OAuth bridge: the redirect and token exchange must both target the configured region.
Claude Code plugin
This repo also ships a Claude Code plugin at plugin/ that bundles
ten skills, two agents, and an .mcp.json wiring on top of the MCP server.
The MCP gives Claude capability (41 tools); the plugin gives Claude
judgment — when to call which tool, how to chain them into intake/billing
workflows, and what ABA Op 512 guardrails apply.
# After cloning and building the MCP (see Quick start — local)
claude /plugin marketplace add ./plugin
claude /plugin install clio-manage@clio-manageOr install from GitHub directly:
claude /plugin marketplace add patrickking67/clio-manage-mcp
claude /plugin install clio-manage@clio-manageSkills — clio-setup, clio-search, clio-best-practices,
clio-matter-intake, clio-time-entry, clio-billing,
clio-document-automation, clio-calendar, clio-contacts,
clio-trust-accounting.
Agents — clio-intake-agent (autonomous intake from a single prompt),
clio-data-analyst (read-only analyses for partner reports, enforced via
tool allowlist).
Full plugin docs: plugin/README.md.
Optional connectors
The remote Clio connector is the one your firm consumes from Claude. Other connectors are recommended for specific skills but never required:
Tier | Connector | What it adds |
Recommended | Microsoft 365 | Outlook, Calendar, SharePoint, Word, Teams |
Recommended | Google Workspace | Gmail, Calendar, Drive, Docs |
Useful | DocuSign | eSignature for engagement letters |
Useful | Stripe | Payments + Clio Payments reconciliation |
Useful | Slack / Teams | Internal firm comms |
Ops | Sentry / App Insights | Monitor the MCP server in production |
Setup steps for each: docs/connectors.md.
Landing page
A static GitHub Pages site lives at docs/ and deploys via
.github/workflows/pages.yml. Enable
Settings → Pages → Source: GitHub Actions on the repo and the next push
to main publishes it. Public URL:
The site is built with Tailwind CSS via CDN (no build step) and renders
hero, feature, tool-catalog, install, plugin, connectors, security, and FAQ
sections from a single docs/index.html.
Development
npm install
npm run dev:stdio # tsx watch, stdio mode
npm run dev:http # tsx watch, http mode
npm run lint # tsc --noEmit
npm run build # tsc + chmod +x
npm run smoke:stdio # protocol smoke test (stdio)
npm run smoke:http # protocol smoke test (http, static mode)
npm run smoke:oauth # protocol smoke test (http, OAuth discovery)
npm test # unit tests (session store + OAuth provider)
npm run inspector # MCP Inspector against the built binaryThe MCP Inspector is the fastest way to iterate on tool schemas against a real Clio account. Source map and declaration files ship with the build so debuggers and IDEs work out of the box.
Project layout:
src/
├── index.ts entry point — picks transport from --stdio/--http
├── config.ts env loading + region routing + auth-mode resolution
├── server.ts McpServer factory
├── audit.ts JSONL audit log with secret redaction
├── resources.ts clio:// MCP resources
├── auth/ Clio OAuth flow, OAuth AS provider, encrypted session store
├── clio/ HTTP client (auth refresh, retry, pagination)
├── transports/ stdio + Streamable HTTP (OAuth/static/hybrid)
├── tools/ tool modules, 41 tools
└── util/ stderr logger, error types
infra/main.bicep Container Apps + ACR + Key Vault + Files (OAuth default)
Dockerfile multi-stage build, distroless-style runtime
scripts/smoke-*.mjs end-to-end MCP protocol tests
docs/ deployment-local, deployment-azure, oauth, security, connectors
examples/ client configs + bootstrap-refresh-token.mjsRelated work
Two prior open-source Clio MCP implementations informed this one, and both are worth reading if you're evaluating options:
oktopeak/clio-mcp — TypeScript, local-stdio focused, ~15 read-mostly tools. Strong on the law-firm-IT install ergonomics; ships an npm package with a clean 6-step setup.
lawyered0/clio-mcp — Python / FastMCP, with a deeply documented set of Clio API quirks (flat-fee
custom_ratesetup, activity field aliases, region routing). Most of the quirks section in this README originates from that prior empirical work.
This implementation contributes: a per-user remote OAuth 2.1 connector for Claude (Authorization Server + Protected Resource bridging to Clio), Azure-native deployment with Bicep + azd, broader tool surface (41 vs. ~15), stateless Streamable HTTP transport with encrypted per-session token storage, end-to-end protocol smoke tests in CI, and a composite intake workflow that chains the most common matter-opening sequence into one agent action.
Roadmap
OS-keychain integration for the encryption key (macOS Keychain, Linux secret-service, Windows Credential Manager) so the key isn't on disk.
Private Endpoint + Front Door / API Management options in Bicep.
DXT packaging for one-click Claude Desktop install.
Webhook subscription tool for live matter / task / bill events.
Per-tool scope minimization (today the OAuth grant asks the broad set).
License
MIT — see LICENSE.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/patrickking67/clio-manage-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server