Every MCP server for Qdrant until now has been a Python process: an interpreter, a virtualenv and a dependency tree that your agent spawns and kills over and over. That is a lot of machinery to answer "find me the nearest ten vectors".
Kdrant 2.3.0 ships kdrant-mcp, which is one static binary. 8.0 MB on macOS arm64, 9.1 MB on Windows, 16.4 MB on Linux x64. Nothing to install, no runtime on the host, and a cold start you do not notice.
{
"mcpServers": {
"qdrant": {
"command": "/usr/local/bin/kdrant-mcp",
"args": ["--host", "localhost", "--port", "6333"]
}
}
}
Point a client at it and your model gets six tools: list_collections, describe_collection, search_points, scroll_points, retrieve_points and count_points.
All six only read. upsert_points and delete_points exist and stay off unless you start the server with --allow-writes, because letting a model write into somebody's index is a different risk class from letting it read one, and that is the operator's call to make rather than mine to make for them.
If you are writing your own MCP server, take this one for free
Stdout is the protocol. Anything else that writes there corrupts the stream, and the failure is invisible from where you are testing: you pipe a handshake in by hand, read a clean response with your eyes, and a real client fails to parse it and drops the connection.
The one that will catch you on the JVM or Kotlin/Native is the logging. The Kotlin MCP SDK logs through kotlin-logging, whose default appender prints to stdout on every target and announces itself with a banner before your first frame. Redirect it before anything else runs:
KotlinLoggingConfiguration.logStartupMessage = false
KotlinLoggingConfiguration.loggerFactory = DirectLoggerFactory
KotlinLoggingConfiguration.direct.appender = StandardErrorAppender
Stderr is where MCP expects a server's diagnostics anyway, so you keep them and the protocol stays clean.
The test worth writing is not the handshake. It is asserting that every line your server puts on stdout parses as JSON-RPC. Ours runs against a real Qdrant on every push.
The client underneath
kdrant-mcp is a front end on Kdrant, a coroutine-first Kotlin client for Qdrant. If you write Kotlin against a vector database, the difference from the official Java client is what a call looks like:
val hits = qdrant.search("docs") {
query(embedding)
limit = 10
filter { must { "lang" eq "en"; "year" gte 2024 } }
withPayload = WithPayload.All
}
Suspending functions and a typed filter DSL, kotlinx-serialization models instead of protobuf builders, and a wire you choose. The default REST engine pulls in no gRPC, no protobuf and no Netty: 3 to 5 MB of dependencies against 15 to 20 MB, with shaded Netty alone accounting for about 9 MB of the latter. It compiles to a GraalVM native image that answers its first search 37 ms after process start in a 42 MB binary, with no reflection config for you to write.
Does the nice API cost you throughput?
That is the fair question about a client like this, so I measured it rather than asserting it. Same server, same JVM, one workflow run:
| Kdrant, REST | Kdrant, gRPC | Official client, gRPC | |
|---|---|---|---|
| Single search | 1.41 ms | 0.65 ms | 0.56 ms |
| Batch search, 10 queries | 3.38 ms | 1.43 ms | 1.32 ms |
| Upsert, 500 points | 74.6 ms | 10.4 ms | 8.0 ms |
| Full scroll, 2 000 points | 36.0 ms | 9.5 ms | 8.3 ms |
Compare like with like: over gRPC, Kdrant lands within 8% to 30% of the official client, and a chunk of that widest gap is a round trip rather than serialization, because Kdrant splits an upsert at 256 points by default to bound the memory an ingest holds. So the coroutines and the DSL cost nothing measurable.
What the first column shows is the engine choice, not the library. REST trades milliseconds for a quarter of the dependency weight and a native image that needs no configuration, which is the right trade for most retrieval workloads. When it is not, kdrant-transport-grpc is the same QdrantClient behind the same API and one dependency away.
What else is in 2.3.0
Qdrant 1.19's query and storage surface, so you can reach it from Kotlin: prefix matching on keyword indexes, slice filtering for splitting a scan across workers or taking a reproducible sample, per-component memory tiers, and TurboQuant 4-bit as primary vector storage.
Two things for people running clusters. A routing token pins a session's reads to one replica, so read-your-own-writes stops costing you wait = true on the write. And the cluster quota is readable through the client, so an ingest can slow down before it gets refused instead of after.
There is also kdrant, a 5.7 MB CLI with no JVM, for the operations that are not requests: migrating a collection between clusters with a resumable checkpoint, snapshots down to shard scope, and health probes.
Which Qdrant versions all this works against is a table in the README generated from a CI run rather than written by hand, and it goes down to 1.16.
Getting it
implementation("io.github.nacode-studios:kdrant-transport-rest:2.3.0")
Twelve artifacts on Maven Central, nine platforms including iOS, macOS, Linux and Windows, Apache 2.0. The binaries are attached to the release with checksums and build provenance, and the changelog has the rest.
Repository: NaCode-Studios/Kdrant.
If you are pointing an agent at a vector database, I would like to know which tools you actually want exposed. Search, scroll and retrieve were obvious. The rest of the list is a guess, and I would rather fix it from what people are building than from what I imagined.
Top comments (0)