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, without any glue 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 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).
? for shortcuts

Remember: you can connect your AI copilot to Zato documentation.

Connect Claude Code to Zato

Four steps: install Zato, create an API key, create an MCP gateway, register it with Claude Code.

Install Zato

If you do not have Zato running yet, install it via Docker - it takes under 5 minutes.

Create an API key

MCP gateways always require authentication - only authorized clients can access your tools. Both API keys and Basic Auth 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

Create an MCP gateway

A gateway is the endpoint MCP clients connect to - it decides 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:

  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

The same gateway as enmasse YAML, versioned in git and imported per environment:

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 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

Tell Claude Code where your MCP gateway is. You can do this globally (available in every project) or per project:

Run this once from any directory:

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.

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

{
  "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

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.
? for shortcuts

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"}
? for shortcuts

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 works the same way - 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

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

Deploy the service

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

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

  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

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).
? for shortcuts
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

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, 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

A tool's full response is not always what an agent needs - with agent filters, the agent asks for exactly the part it wants and the server does the work.

  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 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.
? for shortcuts

The agent filters page covers the expressions with more worked examples.

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 shape tool responses on the server

From here, the MCP gateway reference covers everything this tutorial did not - and the gateway's governance controls, from the audit log that records what each agent did to the response controls that remove PII and cap response sizes, each have a page of their own.

Learn more