Jul 8, 2026

The Aiven MCP in Practice: From Dev Environment to App Deploy

A practical walkthrough of the Aiven MCP: create PostgreSQL® and Apache Kafka® services, send Avro messages, and deploy an Aiven App, by describing what you want.

Tibs (Tony Ibbs) |

RSS Feed

Product Advocate at Aiven

Why I like the MCP

I spend a good amount of my time deploying Aiven services for demos and examples. Traditionally the tools I reach for are:

  • The Aiven web console. This is a great way of exploring what Aiven makes available, and like any GUI (graphical user interface) it tries to make it easy to do the most common tasks. However, it is a point-and-click web interface, and it’s hard to describe what you’ve done without using screenshots.
  • The Aiven command line (CLI). This allows me to do pretty much anything, but at the expense of learning how to use the tool, and it’s not always the nicest way to do things if there’s JSON involved.
  • Terraform. This is excellent for “freezing” behaviour, when I want users to be able to recreate a particular setup. It’s not something I use casually, though, and I personally don’t find it a good way of experimenting when I’m not yet sure what I want to do.

If I’m writing a program, I may also look to the Aiven API, perhaps using curl at the command line or in a shell script, or perhaps with direct HTTP requests in a Python program. The API is how the console and the CLI tool talk to Aiven, but I generally find that too low level to be comfortable, and I always have to look up how to pass in the Aiven user token.

When I’m exploring something new, or developing a new demonstration or example, I keep notes as I go. These are useful for my own future self (“what did I do last week and why?”), but can also often form the basis for documentation and READMEs. If you like, they’re a runbook of my development process.

In practice, this note-taking has forced me to concentrate on the Aiven CLI, so I can copy the commands I’ve used into my notes, and copy them out again when I want to reproduce what I’ve done. That’s frustrating, because it forces me to concentrate on details I generally don’t care about, and my notes separate what I want to do from how I did it.

Now, though, the Aiven MCP gives me a new sweet spot for my experimentation. Using an LLM such as Claude and the MCP, I can describe what I’d like to do (the what), and know it will make it happen (so it’s also the how):

Create me a developer tier Postgres database in project tibs-examples, and call it ‘tibs-demo-pg’

And as it turns out, if I mis-specify or don’t give enough information, the LLM can use the MCP to help me figure out what I actually need to do.

What I’ll cover here

For my examples I’ve been using Claude Cowork, within the Claude Desktop application. I chose this because it’s where I’ve been doing most of my work recently (mostly proofreading) and also because it’s not necessarily as obvious as (for instance) Claude Code - I think it’s important to realise that the MCP doesn’t necessarily have to be used with a model optimised for codebases. I could also use any of a variety of models that support the Aiven MCP - see the documentation for some other ideas, and for how to connect to the MCP server.

I’m just going to refer to “Claude” in this document, but assume it might be any other suitable LLM.

I’m going to show three things:

  1. Creating an Aiven for PostgreSQL® service
  2. Creating an Aiven for Apache Kafka® service, and sending an Avro message to it
  3. Deploying the app from my previous blog post

Starting simple - connect to Aiven for PostgreSQL®

Note

Normally I’d use the Developer tier for examples like these - it’s quite capable of handling what I need. However, in this article I’m using Startup plans because I want to show how to get the MCP to manage the cloud and region for a service.

I’m in the UK, so I probably want a service nearby. Luckily, Claude has at least some idea of what that means. I ask

Which countries are near the UK

It answers

France, Ireland, Belgium, the Netherlands, and Norway are the closest countries to the UK — France being nearest, with the English Channel at its narrowest (the Strait of Dover) just 34km wide.

I know I want a Startup plan (from the Professional tier), but I’m not too fussed about which cloud. So let’s ask

What's the cheapest Startup level Postgres service near the UK? I'm using project tibs-examples

Claude responds

Let me check pricing for the smallest startup PG plan across the nearest UK cloud regions simultaneously:

It then asks me for permission to make a series of queries, starting with

Loading code...

Since these are readonly queries, I will allow them with “Always allow”.

I get the answer

The cheapest is DigitalOcean London (do-lon) at $0.103/hour (~$75/month) for startup-4. Both AWS London and AWS Ireland come in at $0.151/hour — about 50% more. Azure UK South isn't available on this account.

So do-lon wins on price and is geographically closest to the UK.

Note

The answer may change over time - always make your own check

I can now ask Claude to create the database for me

Thanks. Please create me a PG service as suggested, and call it tibs-pg-near-uk

I get back

Claude wants to use aiven_service_create from aiven

Loading code...

Since that agrees with what we’ve discussed, and looks like it makes sense, I give it permission to run this once - I prefer to restrict the ability to run write tasks.

It then goes away and creates the service for me

tibs-pg-near-uk is provisioning on DigitalOcean London (startup-4, PG 17). Let me know when you want me to check if it's ready.

Aside - help when it doesn’t work

When I was doing this the first time, I actually hit a quota limit on my account. Claude came back and told me what the problem was, and suggested which service I could power off (or delete) to free up the most resources. I asked it to power off that service, as I didn’t need it running, and Claude was then able to create my PG service.

Hit a service node quota limit on the tibs-examples project — you've got too many services running at once. To create this one you'd need to either delete or power off something first.

Looking at what's running in tibs-examples: tibs-kafka (business-4, 3 nodes), tibs-pg (running), and tibs-clip-search (the app). The Kafka upgrade to business-4 uses significantly more quota than the old developer plan.

Would you like to power something off to make room, or delete a service you no longer need?

In fact, the Aiven MCP doesn’t currently support deleting services. If asked Claude will try and then suggest either deleting the service manually, or powering it off, which it can do.

The Aiven MCP doesn't have a delete service tool — I can power tibs-kafka off to free up quota, but you'd need to delete it manually from the Aiven console.

Want me to power it off now so the quota is freed and I can create tibs-pg-near-uk? You can then delete it in the console at your leisure.

I responded

Do that please

Claude then proceeded

tibs-kafka is powered off.

Now creating tibs-pg-near-uk:

tibs-pg-near-uk is provisioning on DigitalOcean London (startup-4, PG 17). Let me know when you want me to check if it's ready.

So we’re back where we were before 🙂

Working with Aiven for Apache Kafka®

Create an Apache Kafka service

I ask Claude to create me a Kafka service

Create me a startup Kafka service in the same region and cloud, called tibs-kafka-near-uk

Claude says

I'll load the service create tool and check available Kafka plans for that cloud first.

It then asks me which particular startup plan I want. It recommends startup-4 as matching the PG service, and I agree.

Note

As I found out in an earlier session, if I’d tried to select startup-2 it would have found that didn’t work, saying “startup-2 is deprecated for Kafka 3.9+. startup-4 is the only viable startup plan — shall I go ahead and create the service with startup-4 on DigitalOcean London (do-lon)?”

Claude continues

tibs-kafka-near-uk is provisioning (REBUILDING) on DigitalOcean London with the startup-4 plan. It typically takes a few minutes — let me know when you'd like me to check the status.

In fact, I have the Aiven console open and can see it building, so I wait the old-fashioned way.

Send an Avro message to Kafka

Once I see the Kafka service running, I say

It's running now. Please create me a topic called "people_and_ages"

Claude checks with me that it’s got the right parameters, and does so.

Topic people_and_ages created on tibs-kafka-near-uk with 1 partition and replication factor 3 (across all 3 brokers). It'll be available shortly.

Normally I’d test sending Avro messages to Kafka by either using the message generator in the console, or with a Python script. However, let’s get Claude to do it for me…

Do you know how to send an Avro message (with data {“name”: “Tibs”, “age”: 66}) to Kafka?

Claude responds

Yes — the general approach is:

  1. Define an Avro schema for your data, e.g. {"type": "record", "name": "Person", "fields": [{"name": "name", "type": "string"}, {"name": "age", "type": "int"}]}
  2. Register it with the Schema Registry (Aiven has one built into Kafka services)
  3. Produce the message using an Avro serializer that encodes the data against the schema and prepends the schema ID
    I also have Aiven tools available that can do some of this directly — aiven_kafka_topic_message_produce can produce messages, and there are schema registry tools (aiven_kafka_schema_registry_subjects, aiven_kafka_schema_registry_subject_version_get) for working with schemas.
    Would you like me to walk through doing this against your tibs-kafka-near-uk service? If so, I'd need to know which topic to send to (or create one), and whether you already have a schema registered.

This makes sense, so I give some more details

That schema looks OK. Can you send the message I suggested to the existing topic "people_and_ages"?

Claude responds (the markers are where I’ve left out Claude telling me about how it’s handling this)

Let me first check the service state and any existing schemas.

Used aiven integration, loaded tools

The topic people_and_ages exists, but Kafka REST is currently disabled. I need to enable it before I can produce messages. Let me do that now.

Kafka REST is now enabled. Let me check if it's ready yet.

Kafka REST is up. Now let me send the Avro message — there's no schema registered yet, so I'll include it inline.

It then suggests a way of sending the message, and the options it will use

Claude wants to use aiven_kafka_topic_message_produce from aiven
Request

Loading code...

I accept that, and Claude says

Done! The message was delivered successfully — landed at partition 0, offset 0. The schema was also auto-registered with ID 1, so future messages to this topic can reference it by ID rather than sending the full schema each time.

I ask it to check

Thanks. Please read back all messages in that topic for me.

And Claude says

One message in the topic:

  • partition 0, offset 0{"name": "Tibs", "age": 66}

And indeed, if I use the Aiven console, and go to the Topics page for the service, I can fetch Messages and see my message there as described.

Note

Interestingly, although I can always get the LLM to send me my message to Kafka, the exact approach it takes isn’t the same every time. So remember that LLM interactions are often non-deterministic.

Deploy the CLIP App from my last post

In the blog post A Practical Guide to Deploying LMM-Powered Apps with CLIP and pgvector I showed how to deploy a PostgreSQL / pgvector / CLIP application using Aiven Apps. It turns out that this is something that the MCP does well.

Let's deploy an Aiven Application in this project, using the repository at https://github.com/Aiven-Labs/app-multimodal-search-CLIP-PostgreSQL, and the compose file compose.aiven.yaml. Use the existing PG service tibs-pg-near-uk instead of creating a new one.

Claude responds

Let me run the mandatory pre-deploy checks in parallel — repo visibility, PG service state, and VCS integrations.

The GitHub API fetch was empty — let me check via VCS integration. There's an Aiven-Labs integration, which matches the repo owner. I'll look up the repo there, and simultaneously fetch the compose file to check the port.

Claude fetches files from GitHub and elsewhere, and then says:

Found the repo. Let me now check the Dockerfile to verify port and CMD/ENTRYPOINT.

I have everything I need to review. Here's what the pre-deploy checks found:

✅ Repository — accessible via the Aiven-Labs VCS integration (ID resolved automatically)

✅ Dockerfile — at ./query_app/Dockerfile, has EXPOSE 3000 and CMD ["fastapi", "run", "query_app.py", "--port", "3000"]; FastAPI binds to 0.0.0.0 by default

✅ PG service (tibs-pg-near-uk) — RUNNING on do-lon. The app reads DATABASE_URL, which the platform will auto-inject from the service integration.

✅ Compose file — confirms build context is ./query_app, port 3000, and the existing PG replaces the mm-search-postgres-db service defined there.

Before I deploy, I need you to confirm a couple of things:

It asks me which branch I want to use - I choose main

It also asks me what to call the service, with some useful options - I choose mm-search-query-app

It then asks me where to deploy it, recommending do-lon because that’s where the PG service is. If I accepted that, it would then find it can’t do it as Aiven Apps are only on a restricted set of clouds and regions at the moment - so I tell it to use AWS eu-west1.

Claude says

All checks pass. Deploying now.

Deployment kicked off. The service mm-search-query-app is now in REBUILDING state on AWS eu-west-1 — it's pulling the repo, building the Docker image from ./query_app, and wiring up DATABASE_URL from tibs-pg-near-uk. It'll take a few minutes. You can monitor progress in the Aiven Console, or ask me to check the status when you're ready.

And I can indeed see it building in the Aiven console. As a check, I ask

Can you get me build logs for the new app service?

Claude responds

Build looks healthy — 100 lines from 16:19:17–16:19:25 UTC, all pip downloads succeeded, and the final line shows Installing collected packages with the full dependency set including torch, transformers, fastapi, psycopg, and multimodal-CLIP-demo__query_app itself. No errors. The build was still in progress at the time of capture — want me to fetch a later page to see if it completed?

This corresponds with what I see in the Build logs in the console. It’s a much nicer summary than trying to read through the logs themselves. In case I want the actual content, Claude Desktop also shows me the log lines in a side bar.

When I can see that the App is running, I say

I believe the build has finished - can you tell me what is happening in the runtime logs?

Claude responds

The app is up and running well. Here's the story from the logs (16:27–16:28 UTC):

  1. Uvicorn started on 0.0.0.0:3000 and immediately began the async background setup task.
  2. CLIP model downloaded from HuggingFace (openai/clip-vit-base-patch32) and loaded successfully — running on CPU (no GPU on this plan).
  3. PostgreSQL setup — pgvector enabled, pictures table created, and it started populating the database with 1,000 image embeddings in batches of 100.
  4. App restarted once around 16:28 (likely a platform health-check restart), then repeated the same startup sequence. On the second pass it found the pictures table already existed (DuplicateTable — handled gracefully), but it's still adding the 1,000 embeddings because the first run didn't finish before the restart.
    The app is currently in its "not ready yet" state — if you hit it now it'll say "Adding examples to PostgreSQL database. Please try again later." It should become fully ready once all 1,000 image embeddings are loaded. Want me to check the logs again in a minute to see if it's finished?

I’m not going to show the answer here, but I then asked: What URL is the app on? and it gave me the correct answer. However it also said:

One thing worth flagging: the metadata shows application_deployment_status: "FAILED" even though the service state is RUNNING. This may be related to the restart we saw in the runtime logs. The app might still be in the middle of loading embeddings — worth keeping an eye on.

In fact, the app was crash looping - typically the sign of too small a plan. So I asked

What plan is the app using, and what is the next largest plan?

Claude responded

The app is currently on startup-50-1024 (0.5 vCPU, 1024 MB RAM). The next largest plan is startup-100-2048 (1 vCPU, 2048 MB RAM). Would you like me to upgrade to it?

I said “Yes, please” and Claude said

Done — mm-search-query-app is now on startup-100-2048 (1 vCPU, 2048 MB RAM). The URL stays the same.

Now the App has to redeploy on the new node size, so I wait for that to happen…

Once it’s rebuilt, when I go to the app URL and try to make a search, I do indeed get “Adding examples to PostgreSQL database. Please try again later”, just as Claude said.

I ask about the logs again:

It looks as if it's running OK now, and I get a sensible message from the app when I ask for images. Can you confirm what the runtime logs show?

Claude says

The logs confirm what you're seeing. The timeline (newest-first, so reading bottom-to-top):

  • 16:43 — App restarted on the new plan, Uvicorn came up, CLIP model downloaded and loaded, pgvector enabled, table found already existing, and began loading 1,000 image embeddings in batches of 100.
  • 16:44 — You hit the app (GET / → 200) and submitted a search for "man jumping" (POST /search_form → 200). At that point it returned the "still loading" message because embedding ingestion was still in progress.
  • 16:44 & 16:45 — Background task writing batches of 100 rows to PostgreSQL, still ongoing at the last log entry.
    So the app is live and responding, just still populating the database in the background. Once all 1,000 embeddings are loaded it'll start returning actual image results.

This is an excellent summary of what is going on. As a check, it seems sensible to count the entries in the database

How many entries are in the table in the PG database?

Claude responds

400 out of 1,000 entries so far — the background task is about 40% through loading the embeddings. It's writing in batches of 100, so about 6 more batches to go.

Note

I didn’t specify the database table - it worked that out for itself.

After a little while, the App is running (this time without crashing), and I say

That's finished now, and I've made a successful query on the apps web page. What are the runtime logs saying now?

Claude responds

All good! The story the logs tell:

  • 16:55:44 — All image embeddings added — all 1,000 rows written to PostgreSQL.
  • 16:58:02 — You submitted a search for "man jumping", and the app processed it and returned a 200 — a successful query against the now-fully-loaded database.
    Everything is working as expected.

Which again confirms what I expected.

Tidy up

It’s always a good idea to tidy up resources when we’ve finished with them. We can’t delete our services using the MCP, but we can power them off.

Let's tidy up. Can you please shut down all three services in the tibs-examples project?

Claude then asks me about each service - for instance

Claude wants to use aiven_service_update from aiven
Request
{
`project`: `tibs-examples`,
`service_name`: `mm-search-query-app`,
`powered`: false
}

I accept for each, and at the end it says

All three are now powered off:

  • tibs-kafka-near-ukPOWEROFF
  • tibs-pg-near-ukPOWEROFF
  • mm-search-query-appPOWEROFF (and its deployment status also updated to COMPLETED — that earlier FAILED status has resolved itself)

I especially like that last note about the search app status.

Note

To do this in the console, I’d actually need to visit the Services and Applications pages separately - the LLM figured out what I meant.

Things to bear in mind

Let’s start with the downsides:

  • LLMs are nondeterministic. This means that if there are multiple ways to solve a problem, I may get a different solution each time. If I care, I can be more precise in my requirements, but ultimately I’m nondeterministic in much the same way myself.

  • LLMs don’t actually understand anything. There’s no consciousness, no wider awareness going on. Instead it fakes what understanding looks like. In the context of using an MCP, and particularly if one regards this as a helper, this isn’t a problem, especially as Claude defaults to checking with me whenever it does something new.

  • Sometimes the dialog can take some time. This is partly because the LLM is taking care to explain what it does - enabling more actions by default will speed things up. Sometimes it’s because it checks things where I’d just risk it! On the other hand, I don’t believe that it takes longer than it would take to explore the console, or search the documentation to craft a required command.

There are some definite advantages, though:

  • Backtracking and trying again. The way it can say “that didn’t work, for this reason, so let’s try a different approach”. I do this as a human being, but it normally means going away and consulting documentation to work out what went wrong, and what I might try instead.

    And I should point out - in any reasonably complex system (like the Aiven platform) this is always going to happen sometimes, whether one is using the MCP or doing it by hand.

  • Being able to manage trust levels. This is very important. With Claude, I use a mixture of "allow once", "allow for this task" and "always allow", depending on what I’m doing. For tasks that write or otherwise make changes, it's pretty much always "allow once", and for query-only things it can be "always allow".

  • Support of markdown. LLMs in general have standardized on the use of markdown, and it’s really useful to be able to save (parts of) the current session and integrate them into whatever document I’m working on at the moment. This also makes it easy to re-use text from previous sessions.

Lastly, you may wonder why I use “please” and “thank you” when talking to Claude.

It’s not because I think there’s a person at the other end. It’s pure pragmatism - interacting with an LLM feels like a conversation, even if it really isn’t, and I don’t want to pick up bad habits that I might carry over into actual conversations, with real people.

Also, I’m British. I apologize to a table if I bump into it (I’m told Canadians are the same way).

In summary

I find the MCP great for exploratory development, as it allows me to stay focussed on what I want to do, rather than the mechanics of how, and helps a lot when things (inevitably) go wrong. I like the fact that prompts and output fit well into my notes, and it's really useful when summarising logs, turning them into something I actually want to read.

To get it enabled, see the Aiven MCP documentation, which covers Claude, Cursor, VS Code and more.

And if you want to learn more about Aiven Apps, request access to the Limited Availability program, and give us your feedback on how you use it.