Skip to content
> Jacob Kerr_

Prestige Men's Health20267 min read

EMA: Building an Agentic AI System Inside a Production Healthcare Platform

  • Agentic AI
  • MCP
  • Laravel
  • Healthcare

Part of the Technical Breakdown series on my 4-year engagement with Prestige Men's Health.

Every other post in this series mentions EMA in passing — the AI system that ended up touching SMS, the Kareo/Tebra sync, the Rx pipeline, and the inventory system. It deserves its own post, because by mid-2026 it wasn't a feature bolted onto one subsystem — it was a genuine agentic layer with its own admin UI, its own tool catalog, its own configuration model, and a real, traceable build history. Agentic AI tooling is one of the most talked-about areas in software right now, and this is a grounded account of what building one inside a live, regulated, patient-facing platform actually looked like — not a demo, a production system with four years of surrounding infrastructure to integrate with.

Everything below is scoped to what's verifiable in my own commit history and the real files that exist in the repository — class names, file paths, and commit subjects, not invented functionality.

EMA didn't start as "EMA"

The groundwork was laid through the same wave of autonomous GitHub Copilot coding-agent PRs described throughout this series — specifically a run of real, individually merged PRs in early March 2026: copilot/install-laravel-boost, copilot/update-laravel-boost-chat, copilot/grant-full-db-access-ai-boost, copilot/create-complex-ai-tools, copilot/add-ai-sms-server-class. Laravel Boost — Laravel's own official package for AI-assisted development and MCP (Model Context Protocol) tooling — got installed first, and the agent's own commits are specific about what came out of it:

  • Add make:mcp-server command and AIChatServer MCP server with tools
  • Connect AI chat UI to AIChatServer MCP server via laravel/ai
  • Add 10 new AI tools for patient data and store inventory
  • Add GetDatabaseSchemaTool and ExecuteDatabaseQueryTool for full DB access via AI MCP
  • Add SMS, charge, and group AI tools; improve AI system instructions

That's the actual starting point: a generic internal chat interface (AIChatServer) wired to an MCP server, backed by narrow, individually-named tool classes — GetClientInfoTool, GetClientStatsTool, GetPrescriptionOrdersTool, GetUserMessagesTool, SendPaymentLinkTool. No single giant "do anything" tool — a growing catalog of small, purpose-built ones.

The name "EMA" shows up in my own commits starting March 12, 2026 ("update to ema") — a formalization and branding of what had been a loose collection of AI chat tooling into a named system with its own identity. From there through the summer, the real build-out was mine: voice interaction ("ema + voice major upgrade"), document parsing, scheduling, and a dedicated admin surface.

Architecture: tools registered through MCP, not one monolithic agent

EMA's tool layer is built around MCP (Model Context Protocol) rather than a single hardcoded prompt with every capability jammed in. The real files bear this out:

  • app/Mcp/Support/EmaAiToolCatalog.php — the registry of what tools exist
  • app/Mcp/Support/EmaMcpToolRegistrar.php — registers tools with the MCP server
  • app/Mcp/Tools/EmaAiToolMcpBridge.php — the bridge layer connecting EMA's internal tool classes to the MCP protocol surface

And tools are scoped by where EMA is operating, not pooled into one undifferentiated set — there's a distinct AIChatServer (the internal admin chat context) and a separate AISmsServer (the SMS channel, covered in the SMS deep-dive), each with its own real commit history:

// AFTER — conceptual reconstruction of the scoping pattern
final class AISmsServer extends McpServer
{
    protected function tools(): array
    {
        return [
            SendPatientSmsTool::class,
            SendPaymentLinkTool::class,
            ScheduleSmsTool::class,
            // Only tools relevant to an SMS conversation context —
            // not the full catalog available to the admin chat server.
        ];
    }
}

A real commit on the SMS server side — add phone to aismsserver always needed for tools — is a good small window into what this actually took in practice: tools operating in an SMS context need the phone number as context for almost everything they do, and that had to be made an explicit, always-available piece of context rather than something each tool re-derived inconsistently.

What EMA can actually do, grounded in real commits

  • SMS-embedded assistant. Scheduling, lab-result delivery, payment links, and — per a real March 2026 commit — an "AI-initiated charge flow," meaning EMA could trigger an actual payment charge on a patient's card through a conversational flow, not just send a link for the patient to pay manually.
  • Document parsing. A cluster of commits in May 2026 ("ema parse .docx for groups," "ema more advance docx parse groups") built EMA's ability to read uploaded Word documents and extract patient group definitions from them — turning an unstructured document into structured group-membership data without someone manually re-entering it.
  • Voice interaction. "ema + voice major upgrade" and related SMS voice-command patches gave EMA a voice input/output channel alongside text.
  • Lab results. EMA can generate and send lab-result PDFs directly ("patch ema lab results pdf," "patch send pdf ema").
  • Inventory tools. EmaProductLineResolver and StoreInventoryAdjustmentService — covered in more depth in the inventory deep-dive — give EMA a scoped, tested action for adjusting store stock, not just read access to it.
  • Full database access, for a specific pair of tools. GetDatabaseSchemaTool and ExecuteDatabaseQueryTool are real, and they're a deliberate exception to the narrow-tool pattern everything else follows — these two exist specifically to let EMA introspect schema and run queries directly rather than needing a bespoke tool class written for every new question someone might ask it. That's a genuine tradeoff: broad capability versus the safety of a narrowly scoped action, and it's worth naming plainly rather than glossing over — a general-purpose query tool carries more inherent risk than a tool that can only do one specific, reviewed thing.

The Command Center: making EMA configurable without touching code

app/Filament/Pages/EmaCommandCenter.php is a real, dedicated Filament admin page, backed by a set of Livewire sub-tables that are also real files:

  • BehavioralRulesTable.php — rules governing how EMA should behave in different situations
  • CampaignConfigsTable.php — configuration for AI-driven outreach campaigns
  • FineTuningsTable.php — fine-tuning configuration management
  • InstructionsTable.php — the system instructions/prompts EMA operates under

Real commits track this getting built out in late June 2026: "patch tools in ema AI manager via admin," "upgrade ema control center," "ema campaign editor update." The point of all of it is the same principle this whole platform was built around: a non-engineer running the clinic should be able to adjust how the AI behaves — its rules, its instructions, its campaign configuration — from an admin screen, not by asking an engineer to edit a prompt string in a deployed file somewhere.

That principle shows up again in a specific, real architectural migration: EmaConfigFromCodeImporter and EmaHardcodedConfigCatalog exist specifically to take configuration that used to live hardcoded in PHP and import it into the database-backed config system the Command Center manages — a real "we started with config in code because it was faster to ship, then migrated it to be admin-editable once it mattered" story, not a design that was perfect from day one.

The capability came first, the safety gate came after

Here's an honest sequencing worth being direct about: the AI-initiated charge flow — EMA triggering an actual payment charge through conversation — landed in early March 2026. An explicit approval-token mechanism for EMA's actions ("patch ema approval token") didn't land until July 2026, four months later. That's not necessarily how I'd sequence it building the same thing again — the safer order is usually: design the guardrail before or alongside the capability, not after. But it's the real order this happened in, and the honest lesson is that it's easy to ship a powerful agentic capability faster than you ship the control layer around it, and worth checking that gap deliberately rather than discovering it in hindsight the way this did.

On a lighter note, from the same stretch of work: "log ema see why she cant do tools" — a real commit from a day spent adding better error logging specifically because tool calls were failing silently and nobody could tell why. Agentic tool-calling failure modes are often exactly this unglamorous: not a dramatic bug, just insufficient visibility into why a model decided not to call the tool it should have.

Where this went from here

I'm intentionally keeping this post scoped to Prestige Men's Health and what's verifiable in that repo's history. What I'll say plainly: building EMA — the MCP-based tool registration pattern, the scoped-by-channel server design, the database-driven configuration model, the narrow-tool-versus-broad-tool tradeoff — is directly where my current work building agentic AI tooling for CRM platforms comes from. The specific lessons (name your tools narrowly, scope servers by context, make behavior configurable without a deploy, and sequence your safety gates with the capability, not after it) came from building this system in production, for real patients, under real time pressure.


This is part of a six-post series on the Prestige Men's Health platform — see the main technical breakdown, or the deep-dives on the SMS communication platform, the Kareo/Tebra EHR sync, the Rx order pipeline, the patient portal, and the inventory management system.

More from Prestige Men's Health