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:
- Creating an Aiven for PostgreSQL® service
- Creating an Aiven for Apache Kafka® service, and sending an Avro message to it
- Deploying the app from my previous blog post
Starting simple - connect to Aiven for PostgreSQL®
Note
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
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:
- Define an Avro schema for your data, e.g.
{"type": "record", "name": "Person", "fields": [{"name": "name", "type": "string"}, {"name": "age", "type": "int"}]} - Register it with the Schema Registry (Aiven has one built into Kafka services)
- 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_producecan 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 yourtibs-kafka-near-ukservice? 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
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):
- Uvicorn started on
0.0.0.0:3000and immediately began the async background setup task. - CLIP model downloaded from HuggingFace (
openai/clip-vit-base-patch32) and loaded successfully — running on CPU (no GPU on this plan). - PostgreSQL setup — pgvector enabled,
picturestable created, and it started populating the database with 1,000 image embeddings in batches of 100. - 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
picturestable 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
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-uk —
POWEROFF - tibs-pg-near-uk —
POWEROFF - mm-search-query-app —
POWEROFF(and its deployment status also updated toCOMPLETED— that earlierFAILEDstatus has resolved itself)
I especially like that last note about the search app status.
Note
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.
Table of contents

