# Interface health for AI agents

Review recorded HL7 failures across interfaces in one result.

The clinical audit log records HL7 failures with their interface, outcome and error. This page builds a service that groups those events by interface and exposes the result as an MCP tool, so you can ask your AI which interfaces had failures overnight.

Start with [Expose services as MCP](https://zato.io/docs/dev/healthcare/ai/agents.html) - it creates the `clinical` MCP gateway and the [API key](https://zato.io/docs/security/api-keys.html) that this page builds on. The service below reads the same [clinical audit log](https://zato.io/docs/dev/healthcare/audit-log.html) that every MLLP channel already writes to.

## Write the service {#write-the-service}

The service searches the failures through [`self.audit`](https://zato.io/docs/admin/audit-log/python.html) and groups them by the channel they arrived on. How its docstring and declared fields become the tool's definition is covered under [Expose services as MCP](https://zato.io/docs/dev/healthcare/ai/agents.html#write-the-service):

```python
# -*- coding: utf-8 -*-

# Zato
from zato.common.audit_log.api import AuditOutcome, AuditSource
from zato.server.service import Service

# #####################################################################
# #####################################################################

class InterfaceHealth(Service):
    """ Returns, per interface, the HL7 messages that failed since a given
    time, with the control ID and error of each failure.
    """
    name = 'interface.health'

    input  = 'since'
    output = 'since', 'has_failures', 'interfaces'

    def handle(self) -> 'None':

        # Read the start of the requested period ..
        since = self.request.input.since

        # .. look up every failure recorded since then ..
        failures = self.audit.search(
            source=AuditSource.MLLP_Channel,
            outcome=AuditOutcome.Error,
            time_from=since,
        )

        # .. group the failures by the channel they arrived on ..
        by_interface = {}

        for failure in failures:

            # .. find or start the group of this failure's channel ..
            interface = failure['object_name']
            if interface not in by_interface:
                by_interface[interface] = []

            # .. copy only the fields needed by the caller ..
            by_interface[interface].append({
                'control_id': failure['msg_id'],
                'error': failure['status'],
                'received_at': failure['event_time_iso'],
            })

        # .. build one interface result per group ..
        interfaces = []

        for interface, interface_failures in by_interface.items():

            # .. with the failures of that interface and their count ..
            interfaces.append({
                'interface': interface,
                'failure_count': len(interface_failures),
                'failures': interface_failures,
            })

        # .. determine whether the period contains failures ..
        has_failures = len(interfaces) > 0

        # .. and return the declared tool response.
        self.response.payload = {
            'since': since,
            'has_failures': has_failures,
            'interfaces': interfaces,
        }
```

## Add the tool to the gateway {#add-the-tool-to-the-gateway}

Assign the new service to the `clinical` gateway created under [Expose services as MCP](https://zato.io/docs/dev/healthcare/ai/agents.html#configure-the-gateway):

1. Go to **AI** > **MCP gateways** and open the `clinical` gateway
2. In the **Services** picker, drag `interface.health` to the **Assigned** zone
3. Click **OK**

## Verify the lookup {#verify-the-lookup}

You can now ask your AI:

Which interfaces had failures overnight?

One interface had failures overnight - Results from Lab rejected two results, both with an unknown test code in OBX-3, the first at 02:41.

Behind this exchange, the model selected `interface.health` and called it with:

```json
{
  "since": "2026-03-15T22:00:00"
}
```

With two failures recorded overnight on the lab feed, the tool returned:

```json
{
  "since": "2026-03-15T22:00:00",
  "has_failures": true,
  "interfaces": [{
    "interface": "Results from Lab",
    "failure_count": 2,
    "failures": [
      {
        "control_id": "MSG00214",
        "error": "Unknown test code in OBX-3",
        "received_at": "2026-03-16T02:41:07"
      },
      {
        "control_id": "MSG00219",
        "error": "Unknown test code in OBX-3",
        "received_at": "2026-03-16T03:15:44"
      }
    ]
  }]
}
```

And this is the same question on a night with no failures:

Which interfaces had failures overnight?

None - no interface had any failures overnight.

This time, the tool returned an empty result, which is how AI knew there were no failures:

```json
{
  "since": "2026-03-15T22:00:00",
  "has_failures": false,
  "interfaces": []
}
```

- [Feed silence for AI agents](https://zato.io/docs/dev/healthcare/ai/feed-silence.html)
- [Expose services as MCP](https://zato.io/docs/dev/healthcare/ai/agents.html)
