Skip to main content

PMS Sync — architecture and MCP surface

The governed bridge from on-prem Aderant Expert into Clio Operate / Sharedo. Four parts, two MCP services, one write choke-point.

Start here

TL;DR — read this first

YES
This app uses MCP. It runs two Model Context Protocol services — one on the Proxy and one on the SQL API. Both are mounted at /mcp, speak Streamable-HTTP, and sit behind Keystone OAuth 2.1. The Portal deliberately has no MCP — it's the human UI.

🧩 What it is

A bridge that copies matters/clients out of on-prem Aderant and creates them as work items in Sharedo / Clio Operate.

🖥️ How you use it

A web portal — and the same portal embedded inside Sharedo via an iframe widget with single sign-on.

🤖 The AI angle

MCP lets an AI agent (or a Sharedo workflow) search, preview, and import matters as tools — not just humans clicking.

A point-in-time health capture from 2026-06-14 is at the bottom of this page. It is not a live status.

1System context — the whole thing on one screen

Three people-facing entry points on the left, three services we own in the middle, and the systems we depend on on the right. Follow the colours: Portal Proxy SQL API Widget External.

PEOPLE / CLIENTS WHAT WE BUILD & RUN WHAT WE DEPEND ON 👩‍💼 Staff (browser) Standalone portal login 🧑‍⚖️ Sharedo user Portal embedded in Sharedo 🤖 AI agent / workflow Calls MCP tools AderantImportPortal Next.js 15 · :3060 · the UI pms-sync.alterspective.com.au ShareDoAderantAPIProxy Express · :3050 · brain/writer + /mcp 🤖 (7 tools) aderant-proxy.alterspective.com.au AderantSQLAPITypeScript Express · :4102 · DB reader + /mcp 🤖 (5 tools) via ngrok / Azure Relay pms-sync-embed widget Runs inside Sharedo IDE 🔑 Keystone (OIDC + OAuth) 🗄️ Supabase (org config) ⚖️ Sharedo / Clio API 🏢 Aderant SQL (on-prem) 🧠 Synapse LLM (AI mapping) 📦 GitHub Packages (Foundry) iframes
Portal (UI) Proxy (brain + writer + MCP) SQL API (reader + MCP) Sharedo widget External systems 🤖 = MCP endpoint
One glance: who talks to what. Green arrows = our internal chain. Grey = external dependencies.

2The 4 building blocks (what each one actually does)

Think of it like a relay team. Each runner has exactly one job.

Portal AderantImportPortal

Next.js 15 · React 19 · port 3060 · pms-sync.alterspective.com.au

The face. The screens staff use to search matters, preview the transform, click Import, and see history. Holds no business secrets itself — it asks the Proxy to do the real work. Has no MCP by design.

Proxy ShareDoAderantAPIProxy

Express 4 · port 3050 · aderant-proxy.alterspective.com.au

The brain + the writer. Holds tenant secrets, transforms Aderant data → Sharedo shape, and is the only thing that writes into Sharedo/Clio. Exposes the main MCP (7 tools, incl. the write tools).

SQL API AderantSQLAPITypeScript

Express 5 · port 4102 · ngrok / Azure Relay tunnel

The reader. The only thing allowed to touch the on-prem Aderant SQL Server. Read-only, column-whitelisted, parameterised. Exposes a read-only MCP (5 tools). Tunnelled out because the DB lives behind a firewall.

Widget pms-sync-embed

Foundry SDK 0.6 · runs in Sharedo IDE

The doorway. A Sharedo widget that iframes the Portal, mints a signed SSO token so the Sharedo user is logged in automatically, and brokers API calls back through Sharedo. Deployed via Foundry CLI, not Coolify.

Mental model Widget = the door into Sharedo · Portal = the screens · Proxy = the brain that decides & writes · SQL API = the hand that reaches into the old on-prem database.

3How one import actually flows

This is the money path. A user picks a matter; it ends up as a Sharedo work item. Read it top to bottom.

User+Widget Portal Proxy SQL API Sharedo/Clio 1 · search / select matter 2 · POST preview (with org config headers) 3 · read matter / parties rows 4 · transform Aderant → Sharedo shape (in Proxy) preview JSON (mapping, parties, dates) 5 · user reviews preview 6 · click IMPORT (confirm) 7 · POST import ⚠ gate: importWriteEnabled 8 · create ODS parties + work item + key dates workItemId + reference 9 · success → write audit row (Supabase)
Reads go right (Aderant). Writes go far right (Sharedo). The Proxy is the only writer, and step 7 is hard-gated by importWriteEnabled.
Why this shape The Portal never touches the database or holds tenant secrets. The SQL API is read-only (it can't damage Aderant) and the Proxy is the single write choke-point (one place to audit, rate-limit, and gate). That separation is the core safety design.

4Does it use an MCP service? — Yes, two of them

Your direct question, answered with the code. Verified against source — not just the docs.

Proxy /mcp ✅

server.ts:217 · SDK ^1.29.0
  • Streamable-HTTP transport
  • 7 tools (read + write)
  • Scopes: aderant-api-proxy:read / :write
  • Resolves tenant from token, then loads org secrets server-side

SQL API /mcp ✅

index.ts:82 · SDK ^1.29.0
  • Streamable-HTTP transport
  • 5 tools (read-only)
  • Scope: pms-sql-api:read
  • Column-whitelisted, parameterised queries

Portal /mcp ❌

none — by design
  • No @modelcontextprotocol/sdk dependency
  • It's the human UI shell
  • Its capabilities are already exposed by the two backends — a 3rd MCP would just duplicate them
What "MCP" buys us here MCP turns the app's capabilities into tools an AI agent can call — the same search / preview / import operations a human does in the Portal, but available to a Sharedo workflow, an automation, or a Claude agent. This is the "Build-for-AI" surface. The actual data work is still done by the services' own HTTP APIs; MCP is a thin, authenticated, AI-shaped doorway over them.

5How the MCP request actually works

Every MCP call is authenticated and scoped to one tenant before any tool runs. Here's the sequence.

🤖 AI agent Bearer token 🔑 Keystone issues OAuth token /.well-known/jwks.json mcpAuthenticate verify JWT (jose) check issuer + audience extract scopes read org from token claim per-request MCP server built bound to caller's scopes + that org's config tool runs → calls own HTTP API in-process ① get token ② call /mcp with Bearer Write tools must pass THREE gates ① caller has :write scope  ·  ② org's import_write_enabled = true ③ call includes confirm: true …otherwise the tool refuses. Read tools only need the :read scope.
No global server — a fresh MCP server is built per request, bound to that caller's scopes and tenant. Secrets never leave the service.
AspectProxy /mcpSQL API /mcp
Mount pointapp.all('/mcp') · server.ts:217app.all('/mcp') · index.ts:82
Discovery/.well-known/oauth-protected-resource (both)
TransportStreamableHTTPServerTransport · SDK ^1.29.0
Token checkjose.jwtVerify against Keystone JWKS · checks issuer + audience
Audienceaderant-api-proxypms-sql-api
Tenantstrictly from token claim → Supabase org configstateless (read-only)
Tools are…real — they call the service's own HTTP API in-process (not stubs)

6The actual MCP tools you can call

Proxy — 7 tools readwrite

ToolScopeWhat it does
get_statuspublicHealth of the proxy
list_work_typesreadSharedo work types in the caller's tenant
search_mattersreadSearch Aderant matters by name / code / client
list_client_mattersreadAll matters for a given client code
preview_matterreadDry-run: what the import would produce (mapping, parties, dates)
import_matterwriteIrreversible. Creates the work item + ODS parties. Needs scope + import_write_enabled + confirm:true
reconcile_closuresread/writeCompare Aderant status vs Sharedo phase. apply:false = safe report; apply:true = closes drifted matters

SQL API — 5 tools read-only

ToolScopeWhat it does
get_statuspublicHealth of the SQL agent
list_tablesreadDiscovered Aderant tables/views + their columns
query_tablereadRead rows — columns whitelisted, values parameterised, paginated. Filter syntax like CLIENT_CODE=ABC;OPEN_DATE>2024-01-01
search_clientsreadSearch clients by name/code
search_mattersreadSearch matters by name/code/client
Safety note worth knowing The SQL API has no arbitrary-SQL tool. An earlier query_sql stub was deliberately removed as an anti-pattern — every read goes through whitelisted, parameterised access only.

7Auth & identity — three different doors, one identity provider

Everything ultimately trusts Keystone. But there are three distinct ways in, depending on who's knocking.

👩‍💼 Standalone user

Keystone OIDC. Browser → Keystone login → signed handoff JWT → portal verifies (KEYSTONE_HANDOFF_SECRET) → iron-session cookie with the user's real roles.

🧑‍⚖️ Embedded Sharedo user

Signed JWT handoff. Widget asks Proxy to mint a short-lived JWT → passed in the iframe URL #fragment → portal exchanges it for a session with roles:['embedded'] (no admin).

🤖 AI agent / automation

Keystone OAuth 2.1. Bearer token → /mcp verifies via JWKS, checks issuer + audience, enforces scopes & the write gates.

Two security levers to remember 1. importWriteEnabled — a per-org / per-environment boolean in Supabase. If false, all write paths (UI, auto-import, and the MCP write tools) return 403.
2. frame-ancestors / EMBED_ALLOWED_HOSTS — controls which Sharedo tenants are allowed to iframe the portal. A new tenant must be added here or the embed is CSP-blocked.

8Dependency map — the libraries each service leans on

Grouped by job, so you can scan for the one you care about.

JobPortalProxySQL API
Frameworknext 15.5 · react 19express 4.18express 5.1
MCP@modelcontextprotocol/sdk ^1.29@modelcontextprotocol/sdk ^1.29
Auth / JWTjose 5 · iron-session 8jose 6 · express-basic-authjose 6
Databasemssql 12 · axios-ntlmmssql 10
Tunnelhyco-https (Azure Relay)
Data store@supabase/supabase-js 2@supabase/supabase-js 2
HTTP clientaxios 1.9axios 1.16 · http-proxy-middlewarenative fetch
Sandboxquickjs-emscripten (validate transforms)
Loggingpino 9pino 10 · morganstructured JSONL
Security mw(Next headers/CSP)helmet · cors · express-rate-limithelmet · cors
Validationzod 4zod (via MCP tools)zod (via MCP tools)
AI / observabilitylangfuse 3 · @sentry/nextjs
API docsswagger-jsdoc · swagger-uiswagger-jsdoc · swagger-ui

Widget (pms-sync-embed): built on @alterspective-engine/foundry ^0.6 + foundry-cli, bundled with tsup, requires Node ≥18. Pulls Foundry from GitHub Packages.

9External systems we depend on

If one of these is down, here's what breaks.

SystemUsed byForIf it's down…
🔑 Keystone
identity.alterspective.com.au
All fourLogin (OIDC) + MCP token validation (OAuth 2.1)Nobody can log in; MCP rejects all calls
🗄️ SupabasePortal, ProxyPer-org config table aderant_org_configs (secrets, mappings, flags), import historyNo org config → imports can't resolve tenant
⚖️ Sharedo / Clio API
per-tenant
Proxy, WidgetThe write target: ODS parties, work items, participants, key dates, phaseImports fail at the write step
🏢 Aderant SQL Server
on-prem, behind firewall
SQL APIThe source of truth: clients, matters, partiesNothing to read → previews/imports empty
🌐 ngrok / Azure RelaySQL APITunnel so the cloud can reach the on-prem SQL APICloud loses its link to on-prem data
🧠 Synapse LLM
synapse-api.alterspective.com.au
PortalAI-assisted authoring of work-type mapping transforms (Synapse-only; OpenRouter fallback dropped)AI mapping suggestions unavailable (imports still work manually)
📊 Langfuse (optional)PortalTracing & token-budget for the AI callsLose AI observability only
📦 GitHub PackagesWidget (build)Pulls @alterspective-engine/foundryWidget can't build/deploy
Single points of failure to watch The on-prem SQL Server + its tunnel is the most fragile link (firewall, local box, ngrok session). Keystone is the hard dependency for all auth. Everything else degrades gracefully.

10Point-in-time health capture

Captured 2026-06-14 against production. This is a snapshot, not a live status.

ServiceEndpointResult
Portal/api/health🟢 ok · v1.2.1 · production
Proxy/health (note: not /api/health)🟢 ok · v1.1.0
SQL API/api/health/ready🟢 ready · DB connected to SHAREDO-PROXY (19ms) · 20 objects discovered
End-to-end chain (Proxy /health/ready)🟢 db-proxy pass (147ms) · sharedo-api pass (302) · sharedo-identity pass (200)
Heads-up The SQL API's liveness endpoint may say "degraded" — that's benign. It only means Azure Relay isn't configured (it's running via ngrok instead), so the relay check returns warn. Readiness is green and the database is connected.

11One-page cheat sheet

🎯 The 30-second summary

  • Bridge: Aderant (on-prem) → Sharedo/Clio (cloud)
  • 4 parts: Widget · Portal · Proxy · SQL API
  • Yes, it uses MCP — Proxy (7 tools) + SQL API (5 tools)
  • Portal has no MCP — it's the human UI
  • Proxy = only writer · SQL API = only reader
  • All auth = Keystone

🔒 The safety gates

  • importWriteEnabled per org → blocks all writes
  • MCP writes need scope + flag + confirm:true
  • SQL access is whitelisted & parameterised (no raw SQL)
  • Embedded users get roles:['embedded'] (no admin)
  • CSP frame-ancestors limits who can iframe the portal

🌐 Production URLs

  • Portal — pms-sync.alterspective.com.au
  • Proxy — aderant-proxy.alterspective.com.au
  • SQL API — tunnelled (ngrok / Azure Relay)
  • Deploy — Coolify (push to main); widget via Foundry CLI

🤖 What an AI agent can do via MCP

  • Read: search clients/matters, list tables, preview an import
  • Write (gated): import a matter, reconcile closures
  • Same capabilities a human has in the Portal — as tools

Visual overview for PMS Sync. The Innovation Hub page is Practice Management → PMS Sync. Figures are regenerated by re-reading the sources below, not by editing this HTML in place.

Knowledge base: Reference/Registries/estate-projects/EP-alterspective-aderant.md and Reference/Alterspective/assets/AA-006-Aderant-OOTB-vs-Alterspective-One-Pager.md in Alterspective-Intelligence.

Implementation facts (owning app repo): Alterspective-Engine/alterspective-aderant TECHNICAL_DOCUMENTATION.md, README.md, and AGENTS.md § Build-for-AI. File:line citations in the tables point at that repo.

Health table is a 2026-06-14 capture, not live status. Sibling page: deployment topology.