# Secured APIs as tools

Let agents call OAuth-secured APIs through a tool - two credentials that never meet.

An AI agent needs data from an enterprise API that requires OAuth, and the OAuth client's secret must never be given to the agent. The configuration below gives the agent a tool instead: the agent authenticates to an [MCP gateway](https://zato.io/docs/ai/mcp/index.html) with its own [API key](https://zato.io/docs/security/api-keys.html), the tool is a service, and the service calls the secured API through an outgoing connection whose OAuth credentials are stored in the platform. Two credentials, two directions, and neither side ever obtains the other's secret.

**Prerequisites.** An OAuth-secured REST API to call - any API with a `client_credentials` token endpoint. The example uses a CRM at `https://crm.internal.example.com`.

## The service {#the-service}

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

# Zato
from zato.server.service import Service

class GetCustomerBalance(Service):
    """ Returns the outstanding balance of one customer account from the CRM.
    """
    name = 'example.crm.get-customer-balance'

    input = 'account_id'
    output = 'account_id', 'balance', 'currency'

    def handle(self):

        account_id = self.request.input.account_id

        # The connection injects the Bearer token it obtains and refreshes itself -
        # the OAuth client's id and secret never appear in service code.
        conn = self.rest['CRM API']
        response = conn.get(params={'account_id': account_id})

        crm_data = response.data

        self.response.payload.account_id = account_id
        self.response.payload.balance = crm_data['balance']
        self.response.payload.currency = crm_data['currency']
```

The docstring and the declared I/O are what the agent's model receives about the tool - the [tool schemas](https://zato.io/docs/ai/mcp/tool-schemas.html) page explains the mapping.

## The configuration {#the-configuration}

One [enmasse](https://zato.io/docs/ai/enmasse.html) file holds both credential paths:

```yaml
security:

  # What the agent presents to the gateway - an API key, never the OAuth secret
  - name: crm.agent.key
    type: apikey
    username: crm-agent
    password: Zato_Enmasse_Env.CRM_Agent_Key

  # What the platform presents to the CRM - the OAuth client, never given to any agent.
  # The platform obtains and refreshes the Bearer tokens automatically.
  - name: crm.oauth.client
    type: bearer_token
    username: zato-crm-client
    password: Zato_Enmasse_Env.CRM_OAuth_Secret
    auth_endpoint: https://login.internal.example.com/oauth2/token
    grant_type: client_credentials

groups:
  - name: crm-agents
    members:
      - crm.agent.key

outgoing_rest:
  - name: CRM API
    host: https://crm.internal.example.com
    url_path: /api/v1/balance
    security: crm.oauth.client

mcp_gateway:
  - name: crm
    url_path: /mcp/crm
    services:
      - example.crm.get-customer-balance
    security_groups:
      - crm-agents
    validate_input: true
    is_audit_log_active: true
```

## Connect the agent {#connect-the-agent}

```bash
claude mcp add --transport http crm http://localhost:11223/mcp/crm \
    --header "X-API-Key: the-agent-key-value"
```

Asking the agent about a customer's balance makes it call the `example.crm.get-customer-balance` tool and the reply comes back with the CRM's data - the CRM receives only the platform's OAuth credentials, and the agent holds only its own API key.

## Failure behavior {#failure-behavior}

Each credential fails separately:

- An agent with a wrong or revoked API key gets HTTP 403 from the gateway and the CRM is never contacted - the rejection is in the server log and, as an `auth-failed` event, in the [audit log](https://zato.io/docs/ai/mcp/audit-log.html).
- When the OAuth client is the problem - an expired secret, a revoked grant - the tool call fails with `isError` and the agent is told only that the tool failed. The reason, with the CRM's response, is in the server log under the request's CID - to follow it end to end, see [tracing one call](https://zato.io/docs/ai/examples/tracing-one-call.html).
- Revoking one agent is removing its key from the `crm-agents` group - [membership changes are live](https://zato.io/docs/ai/mcp/security.html#assign-definitions-to-a-gateway), with no effect on the OAuth side.

## See also {#see-also}

- [MCP gateway security](https://zato.io/docs/ai/mcp/security.html) - The credential types and groups agents authenticate with
- [Multi-agent isolation](https://zato.io/docs/ai/examples/multi-agent-isolation.html) - Several agents on one platform that cannot see each other
- [Tool selection](https://zato.io/docs/ai/examples/tool-selection.html) - Docstrings and schemas that steer agents to the right tool
