# MCP Tutorial - Expose Python services as AI tools

Connect Claude Code to a Python service in under 5 minutes.

MCP (Model Context Protocol) lets AI clients like Claude Code, Cursor and ChatGPT discover and call your Python services as tools, with no custom adapter code. You expose services once through an MCP gateway and every compatible client can use them, protected by the same [security definitions and audit trail](https://zato.io/api-management.html) as your REST APIs.

claude\~/projects/my-app

What is the balance for customer CUS-001?

zato - api.customer-balance (MCP)(customer\_id: "CUS-001")

{"customer\_id": "CUS-001", "balance": "1,528.40", "currency": "USD", "transactions": \[{"id": "TX-001", "amount": 450.0}, {"id": "TX-002", "amount": 728.4}, {"id": "TX-003", "amount": 350.0}\]}

Customer CUS-001 has a balance of 1,528.40 USD across three recent transactions: TX-001 (450.00), TX-002 (728.40) and TX-003 (350.00).

> **Remember:** you can [connect your AI copilot](https://zato.io/docs/ai/connect-your-copilot.html) to Zato documentation.

## Connect Claude Code to Zato {#connect-claude-code-to-zato}

Four steps: install Zato, create an [API key](https://zato.io/docs/security/api-keys.html), create an MCP gateway, register it with Claude Code.

### Install Zato {#install-zato}

If you do not have Zato running yet, install it via [Docker](https://zato.io/docs/admin/guide/install/docker.html) - it takes under 5 minutes.

### Create an API key {#create-an-api-key}

MCP gateways always require authentication - only authorized clients can access your tools. Both API keys and [Basic Auth](https://zato.io/docs/security/basic-auth.html) are supported, this tutorial uses an API key.

1. Open the web admin dashboard at **http://localhost:8183**
2. Go to **Security** > **API keys** and click **Create an API key**
3. Enter `mcp-tutorial` as the name and `abc123` as the API key
4. Click **OK**

![New API key](https://zatosource-production.b-cdn.net/docs/gfx/security/apikey-create.webp?v=1790440731)

Dashboard menu: Security > API keys

### Create an MCP gateway {#create-an-mcp-gateway}

A gateway is the endpoint MCP clients connect to - its configuration determines which services are exposed as tools and who may call them. You can create one in the Dashboard or keep it as YAML in git:

**Dashboard**

1. Go to **AI** > **MCP gateways** and click **Create a new MCP gateway**
2. Enter `tutorial` as the name
3. Enter `/mcp/tutorial` as the URL path (the field is pre-filled with `/mcp/`, type the rest)
4. In the **Services** picker, drag `demo.echo` to the **Assigned** zone
5. In the **Security** picker, drag `mcp-tutorial` to the **Assigned** zone
6. Click **OK**

![New MCP gateway](https://zatosource-production.b-cdn.net/docs/gfx/ai/mcp-gateway-create.webp?v=1790934276)

Dashboard menu: AI > MCP gateways

**GitOps**

The same gateway as [enmasse YAML](https://zato.io/docs/ai/enmasse.html), versioned in git and imported per environment:

```yaml
mcp_gateway:

  - name: tutorial
    url_path: /mcp/tutorial
    services:
      - demo.echo
    security_groups:
      - mcp-tutorial
```

To import it, click **System** > **Config** > **Import enmasse** - the [GitOps page](https://zato.io/docs/ai/enmasse.html) covers the full schema and how secrets stay in environment variables.

Your gateway is now live at `http://localhost:11223/mcp/tutorial`.

### Register the gateway with Claude Code {#register-the-gateway-with-claude-code}

Tell [Claude Code](https://docs.anthropic.com/en/docs/claude-code) where your MCP gateway is. You can do this globally (available in every project) or per project:

**Global (all projects)**

Run this once from any directory:

```text
claude mcp add --transport http --header "X-API-Key: abc123" zato http://localhost:11223/mcp/tutorial
```

The server is now available every time you start Claude Code, regardless of the directory.

**Per project (.mcp.json)**

Create a `.mcp.json` file in your project root:

```json
{
  "mcpServers": {
    "zato": {
      "type": "http",
      "url": "http://localhost:11223/mcp/tutorial",
      "headers": {
        "X-API-Key": "abc123"
      }
    }
  }
}
```

This file can be committed to git so your whole team gets the same MCP tools.

### Call your first tool {#call-your-first-tool}

Start Claude Code. It connects automatically - establishing a session, discovering all assigned tools, and making them available for every conversation.

Ask Claude Code: *"What tools do you have access to from Zato?"*

claude\~/projects/my-app

What tools do you have access to from Zato?

I have one tool from the zato MCP server:demo.echo - Echoes back any JSON payload sent to it. Accepts arbitrary key-value pairs and returns them unchanged. Use this service to verify connectivity and to inspect how Zato processes and returns request data.

Now ask: *"Use the demo.echo tool to echo the message 'Hello from MCP'"*

claude\~/projects/my-app

Use the demo.echo tool to echo the message 'Hello from MCP'

zato - demo.echo (MCP)(message: "Hello from MCP")

{"message": "Hello from MCP"}

The service echoed the payload back unchanged: {"message": "Hello from MCP"}

That is it. Claude Code is calling your Zato service as an MCP tool - and a client that sends several calls in one JSON-RPC batch gets them all handled too.

> **Note:** Any MCP-compatible client can use the gateway - Cursor, Continue, OpenAI agents, or your own client. Both MCP protocol revisions are supported; point the client at `http://localhost:11223/mcp/tutorial` with the same `X-API-Key` header.

## Write your own service {#write-your-own-service}

Now that Claude Code is connected, give it something more interesting to work with.

### Deploy the service {#deploy-the-service}

Open the Zato IDE at **http://localhost:8183**, create a new service, paste this code, and click **Deploy**:

```python
from zato.server.service import Service

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

class CustomerBalance(Service):
    """ Returns the account balance and recent transactions for a given customer.
    """
    name = 'api.customer-balance'

    input = 'customer_id'
    output = 'customer_id', 'balance', 'currency', 'transactions'

    def handle(self):

        # Read the customer ID from the request ..
        customer_id = self.request.input.customer_id

        # .. and return a response.
        self.response.payload = {
            'customer_id': customer_id,
            'balance': '1,528.40',
            'currency': 'USD',
            'transactions': [
                {'id': 'TX-001', 'amount': 450.00},
                {'id': 'TX-002', 'amount': 728.40},
                {'id': 'TX-003', 'amount': 350.00},
            ],
        }

# ##############################################################################
```

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

1. Go to **AI** > **MCP gateways** and click **Edit** on the `tutorial` gateway
2. In the **Services** picker, drag `api.customer-balance` to the **Assigned** zone
3. Click **OK**

### Ask Claude Code {#ask-claude-code}

Back in Claude Code, ask: *"What is the balance for customer CUS-001?"*

Claude Code discovers your new tool automatically and calls it:

claude\~/projects/my-app

What is the balance for customer CUS-001?

The zato MCP server now exposes an api.customer-balance tool, I will call it with that customer ID.

zato - api.customer-balance (MCP)(customer\_id: "CUS-001")

{"customer\_id": "CUS-001", "balance": "1,528.40", "currency": "USD", "transactions": \[{"id": "TX-001", "amount": 450.0}, {"id": "TX-002", "amount": 728.4}, {"id": "TX-003", "amount": 350.0}\]}

Customer CUS-001 has a balance of 1,528.40 USD across three recent transactions: TX-001 (450.00), TX-002 (728.40) and TX-003 (350.00).

> **Try it:** Modify the service to return different balances for different customer IDs. Redeploy it and ask Claude Code the same question again.

## How security works {#how-security-works}

Every MCP gateway requires authentication - there are no open gateways. You already set this up: the `mcp-tutorial` API key is assigned to the gateway and every client sends it in the `X-API-Key` header.

Basic Auth works too - clients send a standard `Authorization` header instead. So do [bearer tokens](https://zato.io/docs/security/bearer-tokens/index.html), including JWTs from identity providers such as Keycloak or Microsoft Entra ID, which Zato verifies locally against the provider's keys.

To give access to another client:

1. Go to **Security** > **API keys** or **Security** > **Basic Auth** and create a new definition
2. Go to **AI** > **MCP gateways** and click **Edit** on the `tutorial` gateway
3. In the **Security** picker, drag the new definition to the **Assigned** zone
4. Click **OK**

A gateway can have multiple definitions assigned at the same time - each client gets its own credentials and any of them can be revoked independently. Without valid credentials, requests are rejected.

## Agent filters {#agent-filters}

A tool's full response is not always what an agent needs - with agent filters, the agent requests only the part it needs and the server performs the filtering.

1. Go to **AI** > **MCP gateways** and click **Edit** on the `tutorial` gateway
2. Check **Allow agent filters**
3. Click **OK**

Every tool now accepts an optional `response_filter` parameter - a [JSONata](https://jsonata.org) expression, JSONata being a query language for JSON, that runs on the server before the response is returned.

Ask Claude Code: *"What is the total of all transactions for CUS-001? Use a response filter so you only receive the total."*

The agent passes `$sum(transactions.amount)` in `response_filter` and receives a single number - the server does the arithmetic and the model spends no tokens on a list it would only add up itself.

claude\~/projects/my-app

What is the total of all transactions for CUS-001? Use a response filter so you only receive the total.

zato - api.customer-balance (MCP)(customer\_id: "CUS-001", response\_filter: "$sum(transactions.amount)")

1528.4

The total of all transactions for CUS-001 is 1,528.40.

The [agent filters](https://zato.io/docs/ai/mcp/agent-filters.html) page covers the expressions with more worked examples.

## What you built {#what-you-built}

- An **MCP gateway** that exposes Python services as tools for AI clients
- **Claude Code** connected to your services and using them in conversations
- A **custom service** that LLM clients discover and invoke automatically
- **API key security** so only authorized clients can access your tools
- **Agent filters** that let agents filter tool responses on the server

From here, the [MCP gateway reference](https://zato.io/docs/ai/mcp/index.html) covers everything this tutorial did not - and the gateway's governance controls, from the [audit log](https://zato.io/docs/ai/mcp/audit-log.html) that records what each agent did to the [response controls](https://zato.io/docs/ai/mcp/response-controls.html) that remove PII and cap response sizes, each have a page of their own.

## Learn more {#learn-more}

- [AI Integrations overview](https://zato.io/docs/ai/) - Both directions of AI traffic in one platform, and where to start
- [MCP gateway reference](https://zato.io/docs/ai/mcp/) - Configuration, endpoint behavior and the governance controls
- [Tool schemas](https://zato.io/docs/ai/mcp/tool-schemas.html) - How a service's docstring and declared I/O become its tool definition
- [MCP gateway security](https://zato.io/docs/ai/mcp/security.html) - API keys, Basic Auth and bearer tokens for AI agents
- [MCP response controls](https://zato.io/docs/ai/mcp/response-controls.html) - PII removal, prompt-injection safeguards and token-denominated size caps
- [MCP audit log](https://zato.io/docs/ai/mcp/audit-log.html) - What each agent did, when and with what outcome - payloads never recorded
- [MCP alerts](https://zato.io/docs/ai/mcp/alerts.html) - Failing backends, agents stuck in a loop and gateways with too many tools
- [Cost and limits](https://zato.io/docs/ai/cost-and-limits.html) - Every setting that caps AI cost and traffic, with worked examples
- [GitOps for AI integrations](https://zato.io/docs/ai/enmasse.html) - LLM connections and MCP gateways as YAML in version control
- [AI examples](https://zato.io/docs/ai/examples/) - Complete, runnable scenarios - one real-world need per file
