Skip to main content
Glama

kafka-mcp

License Coverage Web Release Kafka MCP server – quality and maintenance score on Glama

An MCP server that exposes Kafka debugging as tools an LLM can call. It speaks MCP over stdio by default, optionally serves streamable HTTP, and talks to Kafka with franz-go.

One process can connect to several Kafka clusters. In stdio mode, one endpoint is selected for the session. In HTTP mode, each endpoint has its own path, so a session is bound to one cluster by how it connects rather than by a parameter a caller could forget to send.

Install

Every release ships the same server four ways:

  • Binary: kafka-mcp_<Os>_<arch> archives on the releases page. Put kafka-mcp on your PATH.

  • MCP Bundle: kafka-mcp_<version>_<os>_<arch>.mcpb on the same page, for macOS on Apple Silicon, Linux (amd64, arm64) and Windows (amd64). Open it in a client that installs bundles, such as Claude Desktop. It asks for your configuration file and, when that file has several endpoints, which one to serve. Intel Macs use the binary or the image.

  • Container image: ghcr.io/denizgursoy/kafka-mcp:<tag>. It starts with --server; for stdio, mount the file and pass --server=false:

    docker run -i --rm \
      --mount type=bind,src=$PWD/kafka-mcp.yaml,dst=/config/kafka-mcp.yaml,readonly \
      -e CONFIG_FILE=/config/kafka-mcp.yaml \
      ghcr.io/denizgursoy/kafka-mcp:latest --server=false --endpoint local
  • MCP Registry: listed as io.github.denizgursoy/kafka-mcp in the official registry, with the image and the bundles, so a client that reads the registry can install it from there.

How releases reach the registry and the other catalogs is in PUBLISHING.md.

Related MCP server: kafka-mcp

Configuration

kafka-mcp.{toml,yaml,yml,json} in the working directory, ~/.config/kafka-mcp/ or /etc configures the server. The http block is used only with --server.

http:
  address: ":8090"
  base_path: /kafka-mcp # optional; prefixes every endpoint and /healthz
  # public_url: https://kafka.example.com # optional; the address download links use
clusters:
  prod:
    brokers:
      - kafka-1:9093
      - kafka-2:9093
    security:
      tls:
        enabled: true
        ca_file: /etc/kafka/ca.pem
        # cert_file: /etc/kafka/client.pem # optional mTLS; requires key_file
        # key_file: /run/secrets/client.key
      sasl:
        - scram:
            enabled: true
            algorithm: SCRAM-SHA-256 # or SCRAM-SHA-512
            user: kafka-mcp-readonly
            pass: "{env:KAFKA_PASSWORD}" # or password_file: /run/secrets/kafka
  preprod:
    brokers: kafka-preprod:9093
endpoints:
  prod-read:
    cluster: prod
    path: /mcp
    description: Production investigation and debugging
    read_only: true
  prod-write:
    cluster: prod
    path: /mcp/rw
    description: Approved production changes
    tools:
      copy_message: false
  preprod:
    cluster: preprod
    path: /mcp/preprod

clusters owns Kafka connection details: brokers, TLS and SASL. endpoints owns the policy and, in HTTP mode, the MCP route. Stdio selects an endpoint by name and does not use its path. Several endpoints may reference one cluster, so the example reuses one production connection at /kafka-mcp/mcp in read-only mode and /kafka-mcp/mcp/rw in writable mode. Paths are exact: /mcp does not capture /mcp/rw. description is optional and is reported by server_config so a caller knows what the endpoint is intended for.

http.base_path prefixes endpoint paths and the liveness route. A leading or trailing slash on an endpoint path is optional. Paths must be unique and may not be /, /healthz, or contain a query or fragment. When path is omitted, it defaults to /mcp/<endpoint-name>.

For compatibility, a file with no endpoints block still creates one endpoint per cluster at /mcp/<cluster-name>. Existing cluster-level read_only and tools values continue to apply as a lower bound during migration; an explicit endpoint cannot widen them. New configurations should put both fields under endpoints.

An endpoint may switch individual tools off, by name:

endpoints:
  prod-read:
    cluster: prod
    path: /mcp
    read_only: true
    tools:
      create_topic: false
      commit_offset: false

Cross-cluster writes are granted per endpoint. copy_message, produce_message and transform_messages may name a destination_cluster, and that cluster must be listed in the calling endpoint's destinations:

endpoints:
  prod-read:
    cluster: prod
    read_only: true
    destinations: [preprod]   # this session may copy into preprod, nowhere else

An endpoint with no destinations writes to no other cluster. A listed cluster configured read_only still refuses, and listing the endpoint's own cluster is an error, because that is governed by read_only. Names are checked at startup. A file with no endpoints block keeps its old behaviour: each cluster's endpoint lists every other cluster. Clusters that no endpoint serves are invisible to list_clusters, compare_clusters and destination_cluster.

A tool the map does not mention stays exposed, so the file states only what it withholds rather than relisting every tool and silently losing whatever is added later. Names are exact and lowercase, as listed by server_config. They are checked at startup: a name that is not a tool stops the server, because a typo would leave the tool it was meant to withhold exposed. server_config cannot be switched off, since it is how a session learns which cluster and policy it reached and which tools that endpoint has. The tools map only narrows an endpoint: read_only: true still withholds the writing tools regardless of what the map says.

A minimal local file:

clusters:
  local:
    brokers: localhost:19092
    schema_registry:
      urls: ["http://localhost:18081"]
endpoints:
  local:
    cluster: local
    path: /mcp/local

Run a custom configuration over stdio with CONFIG_FILE=/path/to/config.yaml go run ./cmd/server. If it defines several endpoints, select one with --endpoint <name>. Add --server to serve every configured endpoint over HTTP instead; --endpoint is not used in HTTP mode. Without CONFIG_FILE, chu discovers kafka-mcp.{toml,yaml,yml,json} first in the working directory, then in the operating system's user config directory (~/.config/kafka-mcp/ on Linux), and finally in /etc. It uses the first matching file rather than merging files. Its standard loader order is defaults, file, HTTP, then environment; environment overrides use the KAFKA_MCP_ prefix (for example, KAFKA_MCP_HTTP_ADDRESS=:9000 or KAFKA_MCP_HTTP_BASE_PATH=/kafka-mcp). Logging can be configured with LOG_LEVEL and LOG_PRETTY.

At least one cluster with a broker is required. http.address defaults to :8090 and http.base_path to the HTTP root.

The server never stores files. search_messages and transform_messages with download, and produce_message with upload, work with files on the user's own machine. See Files on the user's machine. Over HTTP, set http.link_key (or http.auth_token) to the same value on every instance behind one address. Links are built on the address each request reached, honouring X-Forwarded-Proto and X-Forwarded-Host; set http.public_url when a proxy hides the real address. The old output_dir setting is refused at startup.

Files on the user's machine

Tools that save or send many messages work with files on the user's own machine, never on the server.

Over stdio the server is a process the agent started on that machine, so download saves the file and upload reads it right away, relative to the process's working directory (usually the project) or at an absolute path. The response names the file in download.path or upload.path; a download writes to a hidden partial file and only then takes its name, and never replaces an existing file. Running the image over stdio needs the directory mounted, e.g. docker run -i --rm -v "$PWD:/work" -w /work ….

Over HTTP a tool cannot write there itself: its result goes to the model, not to disk. So the tools return a link and a curl command, and the agent runs the command on the user's machine:

curl -fsS -o 'orders.csv.part' 'https://kafka.example.com/mcp/prod/files/Q2x…' && mv 'orders.csv.part' 'orders.csv'
curl -fsS -T 'orders-fixed.jsonl' 'https://kafka.example.com/mcp/prod/files/Zk9…'

Nothing is prepared ahead and nothing is stored: the data streams between that machine and Kafka while the command runs, so the message bodies never pass through the model either.

The link carries the request itself, sealed with AES-GCM under a key derived from http.link_key, or http.auth_token when that is unset. The server keeps nothing between handing a link out and its use, so any instance behind a load balancer can serve a link another made, as long as they share the key. Without either setting each process makes a random key and links open only where they were made. Sealing means a link shows nothing of its script in proxy logs, and cannot be changed or made up. When used, the request is checked again with that instance's policy: a cluster turned read-only refuses an upload made before.

A link expires after 10 minutes. Treat it as a secret until then: it needs no bearer token, because the agent's curl does not carry the MCP client's headers, and it can be used more than once until it expires. Running an upload link twice sends the file twice. A failure before any data, such as a deleted topic, is an HTTP error with the reason, which curl -f reports; a failure after the first byte breaks the transfer, so curl -f fails instead of keeping a short file. Links are served below the endpoint at <endpoint path>/files/ and checked for origin like the endpoint. A request too large to fit in a link (about 6 KiB sealed, e.g. a very long script) is refused when the link is made.

brokers accepts either one address as a scalar or several addresses as a YAML list. Each address is passed to Kafka as a separate seed broker.

Keep secrets out of the file with {env:VAR} or password_file. Unknown fields are ignored by chu.

TLS is configured with twmb/tlscfg, using its TLS 1.2 minimum and recommended cipher suites. TLS uses the system trust store; ca_file adds a custom CA. For mTLS, supply both cert_file and key_file. security.sasl is a preference-ordered list: each entry enables either scram or plain, or uses oauth as shown below. For PLAIN, use plain: {enabled: true, user: alice, pass: "{env:KAFKA_PASSWORD}"}. Both accept optional zid (authorization identity) and password_file instead of pass; SCRAM also accepts is_token: true for delegation tokens. Algorithms are case-insensitive. Disabled entries are ignored; repeated mechanisms and entries enabling multiple mechanisms are rejected. Fallback negotiates a broker-supported mechanism; it does not retry invalid credentials. All Kafka connections, including message-reading sessions, use these settings.

Legacy per-cluster tls and sasl: {mechanism, user, password, password_file} remain supported, but cannot be combined with security on the same cluster.

For OAUTHBEARER client credentials, use this cluster security block:

security:
  tls:
    enabled: true
  sasl:
    - oauth:
        enabled: true
        token_url: https://identity.example.com/realms/apps/protocol/openid-connect/token
        client_id: kafka-mcp
        client_secret: "{env:KAFKA_CLIENT_SECRET}"
        scopes: [kafka]
        timeout: 10s
        # zid: optional-authorization-id
        # extensions: {tenant: example}

Alternatively, use oauth: {enabled: true, token: "{env:KAFKA_TOKEN}"} for a static token. Configure exactly one mode: static token, client credentials, or gcp. Client-credentials tokens are fetched on authentication, cached per cluster across admin and reading connections, and renewed near expiry using expires_in. Token requests use the current authentication context and a timeout (default 10 seconds). Static tokens are not renewed. The broker must support OAUTHBEARER and trust the identity provider. Token endpoint error bodies are never reported, only their HTTP status, because they can echo secrets.

oauth.proxy sends token requests through an http, https, socks5 or socks5h proxy; when unset, HTTPS_PROXY, HTTP_PROXY and NO_PROXY apply.

Broker security.tls settings apply to Kafka connections only. The token endpoint uses oauth.tls, which can also present a client certificate (RFC 8705). With a certificate, client_secret is optional: when empty, client_id goes in the request body and the certificate authenticates the client.

    - oauth:
        enabled: true
        token_url: https://identity.example.com/token
        client_id: kafka-mcp
        scopes: [kafka]
        tls:
          cert_file: /etc/certs/oauth/client-cert.pem
          key_file: /etc/certs/oauth/client-key.pem
          ca_file: /etc/certs/oauth/ca.pem # optional; system roots otherwise

oauth.gcp gets Google Cloud access tokens with an X.509 client certificate (workload identity federation over mTLS), optionally impersonating a service account. Use Google's external_account credential file; its credential_source is ignored, because the certificate below is used instead.

    - oauth:
        enabled: true
        gcp:
          enabled: true
          credentials_file: /etc/certs/gcp/wif-credentials.json
          cert_file: /etc/certs/kafka/client-cert.pem
          key_file: /etc/certs/kafka/client-key.pem
          format: managed_kafka # Google Managed Service for Apache Kafka; default raw
          # proxy: http://proxy:3128 # default: oauth.proxy, then the environment
          # lifetime: 1h             # impersonated token lifetime
          # refresh_before: 5m       # refresh this long before expiry
          # scopes: [https://www.googleapis.com/auth/cloud-platform]

audience, token_url and service_account_impersonation_url may be set directly and override the credential file. managed_kafka requires a service account to impersonate. A failed refresh keeps using the cached token until it actually expires.

In oauth.tls and oauth.gcp, cert, key, ca (and credentials for gcp) may be given inline instead of as _file, not both; inline keys and credentials accept {env:VAR}. Files are re-read on each token refresh, so a rotated certificate applies without a restart.

insecure_skip_verify: true disables server certificate and hostname verification. It is accepted under security.tls (brokers), schema_registry.tls, oauth.tls (token endpoint) and oauth.gcp (Google STS and impersonation), and defaults to false. It exposes the connection to man-in-the-middle attacks: prefer ca_file, and do not use it in production.

A cluster that cannot be reached at startup is still served, and list_clusters reports it as disconnected. One cluster being down must not block debugging the others.

Message formats

Messages are decoded to JSON for every tool that shows or searches them, by the first rule that applies:

  1. The topic has a format in topic_formats.

  2. The bytes carry a Schema Registry header (Confluent wire format) and the cluster has a schema_registry: Avro, Protobuf (with message index and referenced schemas) or JSON Schema.

  3. Otherwise JSON, then UTF-8 text, then base64.

clusters:
  prod:
    brokers: kafka-1:9093
    schema_registry:
      urls: ["https://schema-registry:8081"]
      user: kafka-mcp                     # or bearer_token: "{env:SR_TOKEN}"
      password: "{env:SR_PASSWORD}"       # or password_file
      # tls: {enabled: true, ca_file: /etc/sr/ca.pem}
    topic_formats:                        # topics whose messages carry no schema id
      "orders.*":                         # exact name or glob; longest match wins
        value:
          format: protobuf
          proto_files: [shop/order.proto]
          import_paths: [./protos]        # or descriptor_set: ./orders.binpb
          message_type: shop.Order
      events:
        value: {format: avro, schema_file: ./schemas/event.avsc}
      metrics:
        key: {format: text}
        value: {format: msgpack}

Formats are avro, protobuf, json, msgpack, text and binary. Schema files and .proto sources are loaded at startup, so a missing file or type stops the server. A message that names a schema but cannot be decoded is returned as base64 with decode_error saying why, never silently. JSON output has sorted keys; Avro longs beyond 2^53 keep their exact value; unset Protobuf fields are shown with their defaults.

Each rendered message reports format, schema_id and message_type for the value, and key_format / key_schema_id when the key is not plain text.

server_config reports the endpoint name, exact path, description, policy and the cluster it serves, and never the password. Its authentication and sasl_user describe the first configured option; sasl_options lists all configured identities in preference order, not the mechanism negotiated by an individual broker connection. It also reports schema_registry URLs and topic_formats.

Authentication and browser clients

http.auth_token makes every MCP request carry Authorization: Bearer <token>; requests without it get 401. It accepts {env:VAR}. /healthz and CORS preflights need no token. Without it, the endpoints have no authentication of their own, which is safe only where nothing untrusted can reach the listener.

Set it with an environment variable, which keeps the secret out of the config file entirely:

export KAFKA_MCP_HTTP_AUTH_TOKEN="$(openssl rand -hex 32)"
CONFIG_FILE=kafka-mcp.yaml kafka-mcp --server

Every config key can be overridden this way: KAFKA_MCP_ followed by the key path in upper case with _ between levels, so http.auth_token is KAFKA_MCP_HTTP_AUTH_TOKEN. To keep the token in the file but the value in the environment under a name of your choice, reference it instead:

http:
  auth_token: "{env:MY_MCP_TOKEN}"   # the server refuses to start if MY_MCP_TOKEN is unset

Clients then send the token on every request:

curl -X POST http://localhost:8090/mcp/local \
  -H "Authorization: Bearer $KAFKA_MCP_HTTP_AUTH_TOKEN" \
  -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}'

In the client configuration used under Connecting a client, add the header to each remote entry. {env:...} is the client's own substitution, so the token stays out of that file too:

{
  "mcp": {
    "kafka-local": {
      "type": "remote",
      "url": "http://localhost:8090/mcp/local",
      "headers": { "Authorization": "Bearer {env:KAFKA_MCP_HTTP_AUTH_TOKEN}" }
    }
  }
}

In a container, pass it like any other secret:

docker run -e KAFKA_MCP_HTTP_AUTH_TOKEN="$TOKEN" \
  -e KAFKA_MCP_HTTP_CORS_ALLOW_ORIGINS="https://mcp-client.example" \
  -v "$PWD/kafka-mcp.yaml:/etc/kafka-mcp.yaml" -e CONFIG_FILE=/etc/kafka-mcp.yaml \
  -p 8090:8090 kafka-mcp

A command-line client sends no Origin header and is unaffected by CORS. A request that does carry an Origin is refused with 403 unless that origin is in allow_origins. This happens before any tool runs, because CORS on its own only hides the response from the page and the request itself would still execute. No origin is allowed by default, so no web page the user visits can drive the tools. To use a browser client, name its origin:

http:
  address: ":8090"
  cors:
    allow_origins: ["https://mcp-client.example"]
    allow_methods: [GET, POST, DELETE, OPTIONS]
    allow_headers: [content-type, accept, authorization, cache-control, last-event-id, mcp-session-id, mcp-protocol-version]
    expose_headers: [Mcp-Session-Id]
    allow_private_network: true
    max_age: 600

The same list from the environment is comma-separated: KAFKA_MCP_HTTP_CORS_ALLOW_ORIGINS="https://a.example,https://b.example". Setting it to * restores the old allow-everything behaviour.

http.cors is ada's CORS middleware configuration, read straight from the file, so every option that middleware has is available here. Each key is optional and keeps its own default, so setting allow_origins alone does not drop the rest. The defaults are the values above with no allow_origins. allow_origins: ["*"] restores the old allow-everything behaviour.

Three of them are load-bearing for the MCP transport. allow_methods needs GET, POST and DELETE: requests are posted, the event stream is a GET, and a client ends its session with DELETE. allow_headers needs mcp-session-id and mcp-protocol-version, which the client sends from the second request onwards, and a header missing there fails the whole preflight rather than being dropped. Mcp-Session-Id must stay in expose_headers: the session id arrives on the initialize response, and a page that cannot read it cannot make a second call.

allow_private_network answers Chrome's Private Network Access preflight, which a page on a public address must pass before it may reach a server on a private or loopback address; it defaults to on, and is granted only on a preflight the rest of the policy already allowed. Setting allow_credentials together with a wildcard allow_origins is refused by the middleware at startup unless unsafe_wildcard_origin_with_allow_credentials is also set, which it should not be.

An allowed origin can drive every tool with the server's Kafka credentials, so list only pages that should have that power, set auth_token (a browser client then has to send it), and set read_only: true on endpoints that should not write. A less obvious path such as /mcp/rw is not authentication.

Embedding the tools in a Go application

An application that already holds Kafka clients can register these tools on its own MCP server with github.com/denizgursoy/kafka-mcp/pkg/kafkamcp. That package loads no configuration, opens no listener and dials nothing. The host keeps routing, authentication and transport; the tools, results, read-only refusals and audit records are the same as the executable's.

clusters, err := kafkamcp.NewClusters(
    kafkamcp.Connection{Name: "stage", Kafka: stageClient, NewReader: newStageReader},
    kafkamcp.Connection{Name: "prod", Kafka: prodClient, NewReader: newProdReader, ReadOnly: true},
)
server := mcp.NewServer(&mcp.Implementation{Name: "my-app-stage"}, nil)
err = clusters.Register(server, "stage", kafkamcp.Options{Destinations: []string{"prod"}})
http.Handle("/mcp/stage", mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server { return server }, nil))
  • NewReader must return a new client consuming the topic directly (kgo.ConsumePartitions) on every call. The tools close those clients; they never close Kafka, Admin, ManualProducer, BulkProducer or SchemaRegistry.

  • AcquireReader replaces NewReader when the host wants to bound or reuse readers: it lends a client with a release function the tools call exactly once instead of closing it, and may wait for a free slot until the call's context ends. Lend clients with kgo.KeepControlRecords(); markers are skipped, and without them a scan ending on a transaction marker waits for data that never comes.

  • BulkProducer is optional and writes the batches of produce_message upload and transform_messages, so single writes on Kafka do not queue behind them.

  • ProtoMessage is optional and names the protobuf message a topic's key or value holds when written without Schema Registry framing, for hosts that already resolve their own .proto files. Framed messages still go to the registry first, and topic_formats is not needed.

  • Options.Logger receives the audit records (default slog.Default), and Options.User names the person behind a call from its request headers, as the host's authenticating proxy established it.

  • Every cluster passed to NewClusters can be read by compare_clusters and list_clusters. copy_message, produce_message and transform_messages write to another cluster only when it is in Options.Destinations, and a ReadOnly connection refuses every write, whichever server it arrives from.

  • Options.ReadOnly hides and refuses writes to the registered cluster; Options.Tools switches tools off by name, and an unknown name is an error.

  • server_config reports authentication as host-managed, along with Connection.Brokers and Connection.Principal. Both are only reported.

Connecting a client

For the default stdio transport, configure the client to launch the binary. A configuration with one endpoint needs no arguments:

{
  "mcp": {
    "kafka-local": {
      "type": "local",
      "command": ["kafka-mcp"],
      "environment": {"CONFIG_FILE": "/path/to/kafka-mcp.yaml"}
    }
  }
}

When the file contains several endpoints, add the endpoint selection to the command, for example "command": ["kafka-mcp", "--endpoint", "prod-read"]. The process refuses to start without it so it cannot silently connect a session to the wrong cluster or permission policy.

To use HTTP, start kafka-mcp --server and register one remote client entry per endpoint:

{
  "mcp": {
    "kafka-local": {
      "type": "remote",
      "url": "http://localhost:8090/mcp/local"
    },
    "kafka-prod-read": {
      "type": "remote",
      "url": "http://localhost:8090/mcp",
      "enabled": false
    }
  }
}

When the server sets http.auth_token, add "headers": {"Authorization": "Bearer {env:KAFKA_MCP_HTTP_AUTH_TOKEN}"} to each entry; see Authentication and browser clients.

The key becomes the tool prefix, so these appear as kafka-local_list_topics and kafka-prod-read_list_topics. Name entries after both the cluster and the endpoint policy: the prefix is the clearest signal of what a call can hit and change. Use server_config to confirm rather than trusting the client-side name.

Each enabled cluster costs context: these tools are roughly 8k tokens of definitions. Enable only what you need, and put production behind an agent:

{
  "tools": { "kafka-prod*": false },
  "agent": {
    "kafka-prod": {
      "description": "Debugging against production Kafka.",
      "tools": { "kafka-prod*": true }
    }
  }
}

Permissions

Three layers, and only one of them is real security:

Layer

Protects against

Real security?

confirm: true on writes

An LLM changing things on one ambiguous request

No — a guardrail

endpoint read_only: true

Accidental writes with the server's Kafka identity

No — anyone who can edit the config can turn it off

Kafka ACLs on the SASL principal

An unauthorised person

Yes — the broker decides

What a read-only endpoint exposes

read_only: true does more than refuse a write: the endpoint does not list the tools whose only purpose is to write. add_partitions, alter_topic_config, commit_offset, create_topic, delete_consumer_group, delete_records and delete_topic are absent from tools/list on a read-only endpoint, so a client never sees a tool it could not have used, and their preview cannot describe a change this endpoint would never apply.

A writable endpoint can withhold individual tools too, with its tools map. That is the same mechanism seen from the client: the tool is not registered, so it is absent from tools/list and from server_config.

copy_message, produce_message and transform_messages stay, because read_only protects the cluster being written to and the destination is chosen per call. Copying a message out of a read-only production cluster, or seeding a writable preprod cluster from a protected session, is exactly what they are for. The destination must be in the endpoint's destinations, and each refuses to write when the destination is the read-only cluster itself.

Hiding a tool decides what is advertised, not what is permitted: both tools still refuse at the point of mutation, so a registration mistake cannot turn into a write. server_config reports the tools the endpoint actually exposes, which is how a session can tell the two cases apart.

Giving two people different permissions

Kafka enforces permissions against the SASL principal, so two people running the same server with different credentials get different rights. Adding partitions requires ALTER on the topic:

# ali may read but not reshape topics
rpk acl create --allow-principal User:ali \
  --operation read,describe --topic orders

# deniz may also add partitions
rpk acl create --allow-principal User:deniz \
  --operation read,describe,alter --topic orders

Each points CONFIG_FILE at their own file, differing only in security.sasl[0].scram.user and the password. When ali calls add_partitions, the broker refuses:

not authorized to add partitions to "orders": the broker refused this request.
Adding partitions requires ALTER permission on the topic for the principal
this server connects as

ali cannot bypass that by editing config or rebuilding the binary, because the decision is made by Kafka rather than by this server. On a cluster without ACLs, read_only: true is the available protection.

Audit logging

Every tool call is logged. One slog record per call, on the server's own log stream, so nothing extra has to be configured:

{"level":"INFO","msg":"tool call","tool":"produce_message","outcome":"ok",
 "duration_ms":12,"endpoint":"prod-write","cluster":"prod","read_only":false,
 "principal":"kafka-mcp-rw","session":"QY7MZM...","client":"claude-code",
 "client_version":"1.0.0","confirm":true,"item_count":1,"targets":"orders"}

The tools that change a cluster — add_partitions, alter_topic_config, commit_offset, create_topic, copy_message, delete_consumer_group, delete_records, delete_topic, produce_message, transform_messages — are logged at INFO. Everything else is logged at DEBUG, because reads are constant and change nothing, so recording them at the same level would bury the writes among them. Raise the log level to see them.

Two fields carry most of the weight. confirm separates a real write from a preview, and targets names the topic, group, partition and offset each item pointed at, so a record says which topic was touched rather than only that some topic was. Tools without items record their top-level topic and destination, and a produce_message upload records upload. A download records download.

Message content is never logged. produce_message, copy_message and transform_messages carry arbitrary payloads, and an audit log is usually readable by more people than the data it describes, so keys, values and headers are left out. The audit code has no field to unmarshal them into, so content cannot reach a log even by mistake.

None of the recorded identities is a person, and the server cannot make one up. What each actually means:

Field

What it is

principal

The Kafka credential this server connects as, which is what ACLs are enforced against. Everyone reaching the same endpoint shares it

endpoint

Which endpoint policy allowed the call, and so which cluster was touched

session

One MCP session, which groups a sequence of calls into one investigation

client, client_version

The program that connected, as it identified itself at initialize. Self-reported and not verified

user

Present only when an inbound bearer token established it. This server installs no token verifier, so it is absent today

request_id

The HTTP request id, which joins a record to the access log

For attribution to a person, give each person their own endpoint and SASL credentials, as above: the principal in the record is then the answer to who acted. A shared credential cannot be made to answer it.

Tools

Batch operations

describe_topic, sample_messages, get_message, get_schema, consumer_lag, describe_consumer_group, open_transactions, add_partitions, alter_topic_config, create_topic, commit_offset, delete_consumer_group, delete_records, delete_topic, copy_message and produce_message take their target only as an items array. There is no single-target form: one operation is an items array of length one.

{"items": [{"topic": "orders"}]}
{"items": [{"topic": "orders"}, {"topic": "payments"}]}

Everything naming or shaping an operation lives on the item, so each field and its description exist in exactly one place. What governs the whole call stays at the top level, which in practice means confirm.

The message-heavy tools accept at most 500 items; lag, schema and administrative tools accept at most 1000. For more messages than that, transform_messages scans a topic in one call and produce_message sends any number from an upload.

Every response is the same envelope. Results stay in input order, each entry carrying index and either result or error, followed by succeeded, failed, applied and atomic: false. An item's failure is data in the response rather than an error for the call, so it never hides the items that worked. Batch writes preview every item first, then apply the valid ones only when the top-level confirm is true. They are not transactions: Kafka cannot roll back a topic, partition, offset or produced message after a later item fails. Duplicate write targets are refused before anything changes, except in produce_message, where two identical items mean two messages rather than a mistake.

list_clusters

Lists the clusters this server's endpoints serve, with whether each is reachable, whether it is configured read_only, and whether this session may write to it (writable: its own cluster unless the endpoint is read-only, another only when listed in destinations). Takes no parameters. Available from every endpoint, so a session can discover what copy_message, produce_message, transform_messages and compare_clusters may target.

{"clusters": [
  {"name": "prod", "connected": true, "read_only": true, "writable": false},
  {"name": "preprod", "connected": true, "read_only": false, "writable": true}
], "count": 2}

connected is checked when you call, not recorded at startup, so a cluster that has since gone down is reported honestly. Only the name, reachability and writability are reported: broker addresses and credentials are deliberately not, because this tool is reachable from every endpoint.

compare_clusters

Compares the topics of other clusters against the one this endpoint serves, and reports what differs. Use it to find what preproduction has that production does not, or to check whether two environments still match.

Parameter

Type

Required

Meaning

items

object[]

yes

1 to 1000 clusters to compare against

Item fields:

Field

Type

Required

Meaning

cluster

string

yes

The other cluster. Use list_clusters for valid names

search

string

no

Case-insensitive substring a topic name must contain

include_internal

bool

no

Default false: internal topics are excluded

{"here": "preprod", "there": "prod",
 "here_cluster": {"name": "preprod", "brokers": 1, "topics": 12},
 "there_cluster": {"name": "prod", "brokers": 3, "topics": 11},
 "only_here": [{"topic": "orders-v2", "partitions": 6, "replication_factor": 1,
                "configs": {"retention.ms": "604800000"}}],
 "only_there": [],
 "differing": [{"topic": "orders", "differences": ["partitions"],
                "here": {"partitions": 1}, "there": {"partitions": 12}}],
 "in_both": 11}

only_here and only_there name the direction, which is decided by the endpoint you call: "here" is always the cluster this endpoint serves. Entries carry the partition count, replication factor and explicitly-set configs of the cluster that has the topic, so they can be passed straight to create_topic.

This tool creates and changes nothing. To create the missing topics, hand the chosen entries to create_topic, which previews them against the broker first and warns that a partition count can never be reduced.

differing is usually the more valuable half: a topic that exists on both sides with a different partition count or retention.ms is the common reason a bug reproduces in one environment and not the other. Only configs a topic sets for itself are compared, because two clusters may carry different broker defaults and comparing inherited values would report every topic as different.

A difference is not necessarily a mistake. A topic missing from production is often deliberate, so the report says what differs, never what is correct.

Topic listings come from the Kafka client's metadata cache, which is a few seconds old, so a topic created moments earlier may still appear in only_there. Repeat the comparison rather than creating it twice.

list_topics

Lists the topics on the cluster, sorted by name, each with its partition count, replication factor, the configs it sets for itself, and its size on disk.

Parameter

Type

Required

Meaning

script

string

no

JavaScript predicate deciding whether a topic is listed

timeout_seconds

int

no

Limit for evaluating the script. Default 30, at most 3600

The predicate sees:

Variable

Type

Meaning

topic

string

The topic name

partitions

number

Partition count

replication_factor

number

Replicas of the first partition

internal

boolean

Kafka's own topics, such as __consumer_offsets

configs

object

Values this topic sets for itself, e.g. configs['retention.ms']

size_bytes

number

One copy of the topic's log segments on disk; -1 when not reported

return topic.indexOf('orders') >= 0
return size_bytes > 10e9                       // the topics filling the disk
return partitions > 6
return configs['cleanup.policy'] === 'compact'
return replication_factor === 1 && !internal
{"name": "list_topics", "arguments": {"script": "return partitions > 6"}}
{"topics": [{"topic": "orders", "partitions": 12, "replication_factor": 3,
             "configs": {"retention.ms": "604800000"}, "size_bytes": 48318382080}],
 "count": 1}

Filtering is JavaScript only, as it is for search_messages: a name match is return topic.indexOf('orders') >= 0. The predicate can also answer what a substring never could — which topics have more than six partitions, only one replica, or a compacted cleanup policy.

Only configs a topic sets for itself are reported. Inherited cluster defaults are excluded, because including them would make every topic look configured.

A topic whose predicate throws, or is cut short by the timeout, is counted in script_errors rather than listed, so a broken filter is never mistaken for an empty cluster.

Sizes come from DescribeLogDirs, which needs describe on the cluster. When the brokers do not answer it, every size_bytes is -1 and warnings says why. Sizes are bytes on disk after compression.

describe_topic

Reports a topic's partitions, offset ranges, message count, size on disk, time span and full configuration. Use it before searching to see how much data a search would read and how far back the topic can hold data at all.

Parameter

Type

Required

Meaning

items

object[]

yes

1 to 500 topics to describe

Item fields:

Field

Type

Required

Meaning

topic

string

yes

Topic to describe

{"results": [{"index": 0, "result": {
   "topic": "orders", "partition_count": 1, "message_count": 3,
   "size_bytes": 2048, "replicated_size_bytes": 6144,
   "partitions": [{"partition": 0, "start_offset": 0, "end_offset": 3, "message_count": 3, "size_bytes": 2048}],
   "configs": [{"key": "cleanup.policy", "value": "delete", "source": "DYNAMIC_TOPIC_CONFIG", "is_default": false},
               {"key": "retention.ms", "value": "604800000", "source": "DEFAULT_CONFIG", "is_default": true}]}}],
 "succeeded": 1, "failed": 0, "applied": 0, "atomic": false}

configs lists every topic config as the string Kafka reports, where -1 means unlimited. is_default is true when the value is inherited rather than set on the topic. Two entries decide whether a message can still exist at all: retention.ms (how long messages are kept) and cleanup.policy (compact keeps only the latest message per key).

size_bytes is one copy of the topic, per partition and in total; replicated_size_bytes counts every replica, which is what the brokers' disks hold. Both are omitted, with a warning, when the brokers do not report log dirs.

sample_messages

Reads a small sample of the newest messages and reports what they look like: value formats, field paths with their types, key statistics, which value fields carry the message key, and the schemas the values were written with. Avro, Protobuf and JSON Schema values are decoded first, so their field paths are reported like JSON. Use it before searching to decide how to search, and before producing to find the schema to write against.

Parameter

Type

Required

Meaning

items

object[]

yes

1 to 500 topics to sample

Item fields:

Field

Type

Required

Meaning

topic

string

yes

Topic to sample

sample_size

int

no

Messages to read in total. Default 20, at most 1000

partitions

int[]

no

Restrict to these partitions

max_value_bytes

int

no

Value bytes per message. Default 512, at most 1 MiB

{"results": [{"index": 0, "result": {
   "value_formats": {"json": 20, "text": 0, "binary": 0},
   "json_fields": [{"path": "payload.amount", "types": ["number"], "present": 20, "example": "500"}],
   "key_stats": {"present": 20, "absent": 0, "unique": 20, "all_unique": true},
   "key_in_value": ["payload.orderId"],
   "schemas": [{"format": "avro", "schema_id": 7, "message_type": "shop.Order", "count": 20}],
   "value_bytes": {"min": 180, "p50": 412, "max": 9020},
   "sampled_ranges": [{"partition": 0, "start": 980, "end": 1000}]}}],
 "succeeded": 1, "failed": 0, "applied": 0, "atomic": false}

key_in_value naming a field means the key is that identifier, so searching the key alone is the precise, cheap lookup. value_formats also counts avro, protobuf, json_schema, msgpack, null and undecodable — values that named a schema but could not be decoded, which is a configuration problem, not binary data.

search_messages

Scans a bounded range of a topic, filtering messages with a JavaScript expression. Kafka has no server-side search, so this reads messages and filters them client-side; the result reports what was covered.

Parameter

Type

Required

Meaning

topic

string

yes

Topic to search

script

string

no

JavaScript filter. Omit to match every message

parallelism

int

no

Concurrent readers, 1–16. Default 1

partitions

int[]

no

Restrict to these partitions

from_offset / to_offset

int

no

Offset window, end exclusive

from_timestamp / to_timestamp

string

no

RFC3339 time window

direction

string

no

newest_first (default) or oldest_first

max_matches

int

no

Stop after this many matches. Default 10, at most 1000

max_messages_scanned

int

no

Read at most this many. Default 10000, at most 100000000

max_value_bytes

int

no

Value bytes per match. Default 512, at most 1 MiB

timeout_seconds

int

no

Wall-clock limit. Default 30, at most 3600

count_only

bool

no

Return counts only, no message bodies

download

object

no

{format, filename}: saves every match on the user's machine as jsonl (default) or csv, through a link over HTTP

group_by

string

no

Expression returning a bucket per match; counts per bucket

max_groups

int

no

Largest buckets returned with group_by. Default 20, at most 1000

The script

Return true to keep a message. In scope:

Name

Value

value

parsed JSON document; the decoded record for Avro, Protobuf, JSON Schema or a configured format; the raw text otherwise

key

string, the decoded document when the key has a schema, or null when absent

headers

object of header name to string

partition, offset

numbers

timestamp

a Date

value_bytes, key_bytes

raw sizes in bytes, before decoding

format

json, avro, protobuf, json_schema, msgpack, text, binary or null

schema_id

the registry id the value names, or null

decode_error

why a value that named a schema could not be decoded, or null

return key === 'order-123'
return value.eventType === 'NEW' && value.payload.amount >= 500
return value.payload.cancelledAt === null      // present and null
return value.payload.cancelledAt === undefined // field absent
return headers['correlation-id'] === 'corr-999'
return /ORD-\d{4}/.test(value.payload.orderId)
return value.indexOf('ERROR') >= 0             // non-JSON topic: value is a string
return schema_id === 57                        // written with one schema
return value_bytes > 900000                    // close to max.message.bytes

Searching by key exactly is far more precise than searching the body: 123 also appears inside "amount": 1123, and those false positives can fill max_matches and hide the message wanted.

A script that throws on a message is counted in script_errors and the scan continues, so a broken script is distinguishable from a genuine absence of matches. Scripts run in a sandbox with no filesystem, network or host access, and are stopped if they exceed the search timeout. They are not bounded by memory: something like 'x'.repeat(1e12) can exhaust the server process. Scripts are trusted input; the blast radius is this server, not the cluster.

Scan order

Every partition is read together, one chunk deep at a time: the newest chunk of every partition, then the chunk behind it, and so on. A limited newest-first search therefore returns the newest matches in the topic, not the newest in whichever partition happened to be read first.

Kafka orders records within a partition and never across them, so matches are merged and reported by timestamp, with partition and offset breaking ties. Timestamps are set by the producer unless the topic uses LogAppendTime, so they can be skewed; it is still the only thing comparable between partitions.

One scan reads every partition through a single connection, so a wide topic costs no more connections than a narrow one. It does read more: a limited search on a 12-partition topic examines the newest chunk of all twelve rather than stopping inside the first. max_messages_scanned still bounds it, and may become the stopped_reason on a wide topic sooner than on a narrow one.

Parallelism

parallelism reads a single-partition topic with that many concurrent readers, which is what makes a full scan of one large partition fast. Readers take chunks newest first (or oldest first), and a max_matches stop is decided only on chunks completed in that order, so the matches returned are the same as a sequential search would return. Because readers finish out of order, scanned_ranges may list several runs for the partition. A multi-partition topic is already read in parallel across its partitions, so the setting does not apply there, and a range too small to divide is read by one reader.

It pays off for count_only, group_by, download and full scans of a single partition.

Result

{"topic": "orders", "match_count": 1,
 "matches": [{"partition": 0, "offset": 17, "key": "order-42", "value": "...", "encoding": "utf8"}],
 "scanned_messages": 120, "scanned_ranges": [{"partition": 0, "start": 0, "end": 120}],
 "stopped_reason": "range_exhausted", "complete": true}

complete is true only when the whole range was read. An empty match list means "not there" only if complete is true; otherwise check stopped_reason and narrow the search.

For large result sets, use count_only to learn how many matches exist, then download to save them instead of returning them.

Downloads

download scans nothing yet. It checks the topic, the window and the script, then returns a link and the command to run on the user's machine (see Files on the user's machine). The search runs while the link is fetched and streams every match into the file; a search cut short by max_messages_scanned or timeout_seconds breaks the transfer instead of leaving a file that looks complete.

{"name": "search_messages", "arguments": {"topic": "orders-dlq",
  "script": "return value.amount >= 500", "max_messages_scanned": 10000000,
  "download": {"format": "csv", "filename": "orders.csv"}}}
{"topic": "orders-dlq", "matches": [], "stopped_reason": "not_started",
 "download": {"url": "https://kafka.example.com/mcp/prod/files/Q2x…", "method": "GET",
   "command": "curl -fsS -o 'orders.csv.part' 'https://…/files/Q2x…' && mv 'orders.csv.part' 'orders.csv'",
   "expires_at": "2026-10-23T10:10:00Z", "note": "…"}}

The file is JSON lines, or CSV with format: "csv". Both use kafka-console's export layout, so the file can be imported there as it is. The file is a copy rather than a view of the data:

  • values are never cut; max_value_bytes does not apply;

  • a null key, value or header stays null (key_null and value_null columns in CSV), distinct from an empty one;

  • a payload is either its exact bytes, with key_encoding and value_encoding set to utf8 or base64, or, when it carries a Schema Registry schema id, the decoded document with value_format, value_schema_id and value_schema_message, so an import encodes it to the same schema again.

{"partition":0,"offset":3,"timestamp":"2026-10-23T09:00:00Z","key":"o-1","key_encoding":"utf8","key_format":"text",
 "value":"{\"amount\":900,\"id\":\"o-1\"}","value_encoding":"utf8","value_format":"avro","value_schema_id":1,
 "value_schema_message":"shop.Order","headers":[],"key_bytes":3,"value_bytes":11,"truncated":false}

Grouping

group_by summarises instead of listing. It is an expression over the same variables as script that returns a bucket name. Every match in the range is counted per bucket, as with count_only, and no bodies are returned. null and undefined form the bucket null; an object is grouped by its JSON.

{"name": "search_messages", "arguments": {"topic": "orders-dlq",
  "group_by": "return headers['error-reason']", "from_timestamp": "2026-10-10T06:00:00Z"}}
{"topic": "orders-dlq", "match_count": 1840, "matches": [],
 "groups": [{"key": "timeout", "count": 1702, "example": {"partition": 2, "offset": 90311}},
            {"key": "bad_schema", "count": 138, "example": {"partition": 0, "offset": 4410}}],
 "complete": true}

Buckets are sorted by count. groups_truncated is true when more than max_groups existed. A message the expression throws on is counted in script_errors and not bucketed. group_by cannot be combined with download.

get_message

Reads messages at exact offsets, plus optional neighbours.

Parameter

Type

Required

Meaning

items

object[]

yes

1 to 500 addresses to read

Item fields:

Field

Type

Required

Meaning

topic

string

yes

Topic to read from

partition

int

yes

Partition to read from

offset

int

yes

Exact offset to read

context

int

no

Also return this many messages either side, at most 100

max_value_bytes

int

no

Value bytes to return. Default 4096, at most 1 MiB

{"name": "get_message", "arguments": {"items": [
  {"topic": "orders", "partition": 0, "offset": 17, "context": 1}]}}

Schema-encoded values are decoded to JSON, with format, schema_id and message_type saying what they were. Values that could not be decoded and are not valid UTF-8 are base64 encoded, with encoding set to base64; decode_error explains a value that named a schema but could not be decoded.

{"partition": 0, "offset": 17, "key": "o-1",
 "value": "{\"amount\":42,\"id\":\"o-1\"}", "format": "avro",
 "schema_id": 7, "message_type": "shop.Order", "encoding": "utf8", "value_bytes": 12}

get_schema

Reads schemas from the cluster's Schema Registry, by subject or by the schema_id a message carries. Read the schema before producing to a schema-encoded topic: it names every field and enum a value needs, which a sampled message may not show.

Parameter

Type

Required

Meaning

items

object[]

yes

1 to 1000 schemas to look up

Item fields, giving subject or id:

Field

Type

Required

Meaning

subject

string

either

Subject, usually <topic>-value or <topic>-key

version

int

no

Subject version. Defaults to the latest

id

int

either

Schema id, e.g. from get_message

check_schema

object

no

{"schema": "...", "type": "avro"}: test a candidate against the subject, without registering it. Needs subject

{"results": [{"index": 0, "result": {
   "schema_id": 7, "subject": "orders-value", "version": 3, "versions": [1, 2, 3],
   "type": "avro", "schema": "{...}", "references": [],
   "message_types": null, "used_by": null, "compatibility": "BACKWARD",
   "check": {"compatible": false, "messages": ["{errorType:'TYPE_MISMATCH', …}"]}}}],
 "succeeded": 1, "failed": 0, "applied": 0, "atomic": false}

message_types lists Protobuf messages, which produce_message takes as message_type. Looking up an id fills used_by with the subject versions that use it. A subject lookup reports compatibility, the level in force, inherited from the registry's global level when the subject sets none. check_schema asks the registry whether the candidate could be registered as the subject's next version under that level. check.messages gives the registry's reasons when it could not. Nothing is registered. The call fails as a whole when the cluster has no schema_registry.

list_consumer_groups

Lists consumer groups with their state, member count and the topics they consume.

Parameter

Type

Required

Meaning

topic

string

no

Only groups consuming or committed to this topic

states

string[]

no

Filter by state, e.g. Stable, Empty

{"groups": [{"group": "payments", "state": "Stable", "members": 2, "topics": ["orders"]}], "count": 1}

A group in state Empty can still report lag: committed offsets outlive the consumers that made them. Kafka has no topic-to-group index, so filtering by topic describes every group on the cluster.

describe_consumer_group

Describes consumer groups in detail: who the members are, which partitions each one owns, and where the group stands on every partition. Use it to turn "a partition is stuck" into "this pod on this host is stuck".

Parameter

Type

Required

Meaning

items

object[]

yes

1 to 1000 groups, each {"group": "...", "sample_seconds": 30}

sample_seconds (optional, at most 60) watches the group for that long before describing it, reading it every second, and adds observation:

"observation": {"seconds": 30, "samples": 31,
  "states": ["Stable", "PreparingRebalance", "CompletingRebalance", "Stable"],
  "joined": [{"member_id": "payments-7-b…", "client_id": "payments-7", "host": "/10.0.4.17"}],
  "left":   [{"member_id": "payments-7-a…", "client_id": "payments-7", "host": "/10.0.4.17"}],
  "unstable": true}

A member that restarts rejoins with a new member_id, so it appears in both left and joined with the same host. That is how a crash-looping consumer behind a rebalance storm is found. The call blocks for the window.

{"results": [{"index": 0, "result": {
   "group": "payments", "state": "Stable", "protocol_type": "consumer", "assignor": "cooperative-sticky",
   "coordinator": 1, "total_lag": 4200,
   "members": [{"member_id": "payments-7-…", "client_id": "payments-7", "host": "/10.0.4.17",
                "assignments": [{"topic": "orders", "partitions": [0, 1]}]}],
   "partitions": [{"topic": "orders", "partition": 0, "has_commit": true, "committed_offset": 812,
                   "end_offset": 5012, "lag": 4200, "member_id": "payments-7-…",
                   "client_id": "payments-7", "host": "/10.0.4.17"}]}}],
 "succeeded": 1, "failed": 0, "applied": 0, "atomic": false}

Partitions are the union of what members own and what the group has committed, so an Empty group still shows its positions. has_commit: false means the group owns a partition it has never committed on, so where it starts is decided by the consumer's auto.offset.reset, not by an offset.

open_transactions

Finds open transactions holding back read_committed consumers. A transactional producer that hangs or dies mid-transaction leaves the partition's last stable offset stuck, and every read_committed consumer stops there. In consumer_lag that looks exactly like a poison message.

Parameter

Type

Required

Meaning

items

object[]

yes

1 to 1000 topics, each {"topic": "..."}

{"results": [{"index": 0, "result": {
   "topic": "orders", "blocked": true,
   "partitions": [{"partition": 0, "last_stable_offset": 812, "high_watermark": 5012,
                   "unreadable_messages": 4200,
                   "producers": [{"producer_id": 2004, "producer_epoch": 0, "transaction_start_offset": 812,
                                  "transactional_id": "payments-writer-1", "state": "Ongoing",
                                  "started_at": "2026-10-02T08:14:03Z", "open_for": "41m12s", "timeout_ms": 900000}]}]}}],
 "succeeded": 1, "failed": 0, "applied": 0, "atomic": false}

The fix is in the producer: restart or fence the application named by transactional_id, or wait for timeout_ms, after which the broker aborts the transaction. Moving the consumer's offset does not help.

consumer_lag

Measures how far behind a topic's consumers are, how fast messages are produced and consumed, and when the backlog will clear.

Parameter

Type

Required

Meaning

items

object[]

yes

1 to 1000 measurements to take

Item fields:

Field

Type

Required

Meaning

topic

string

yes

Topic to measure

group

string

no

Defaults to every group consuming the topic

sample_seconds

int

no

Consume-rate sample window. Default 5, at most 300. The call blocks

skip_consume_rate

bool

no

Return immediately, without a rate or estimate

measure_backlog_age

bool

no

Also report how old each backlog is, against retention. One fetch per lagging partition

Up to 4 sampling windows run at a time, so the call takes about sample_seconds for every 4 items.

{"results": [{"index": 0, "result": {
   "topic": "orders", "total_lag": 4200,
   "produce_rate": {"last_minute": {"messages": 3000, "per_second": 50, "per_minute": 3000, "per_hour": 180000}},
   "groups": [{"group": "payments", "state": "Stable", "members": 2, "lag": 4200,
               "consume_rate": {"per_second": 120, "sampled_seconds": 5},
               "drain_per_second": 70, "eta_seconds": 60, "eta_human": "1m 0s",
               "status": "draining"}]}}],
 "succeeded": 1, "failed": 0, "applied": 0, "atomic": false}

The two rates are measured differently, and the output says so:

  • produce_rate — historical fact, from message timestamps, over the last second, minute and hour. window_truncated marks a topic younger than the window.

  • consume_rate — a sample: the committed offset is read, then read again sample_seconds later. sample_inconclusive means nothing moved.

The backlog drains at the consume rate minus the produce rate. status says what the numbers mean: caught_up, draining (with an ETA), growing (never clears, with growing_by_per_minute), stalled, no_active_consumers, or not_measured. An ETA is only given when the lag is genuinely shrinking.

Every partition also reports start_offset, and offset_expired: true when the committed offset is below it. Retention has then deleted the group's position: the consumer resets by its auto.offset.reset instead of resuming, and the group carries a warning. produce_rate.partitions gives per-partition counts for the last minute and hour, which separates key skew from a slow consumer.

With measure_backlog_age, each lagging partition reports committed_timestamp (the next message to consume) and lag_seconds. Each group reports oldest_unconsumed_at, and the topic its retention_ms. retention_risk is set, with a warning, once the oldest unconsumed message has used half of the retention: unless the group catches up, retention deletes it before it is consumed.

cluster_health

Checks the cluster this endpoint serves in one call: brokers, controller, and every partition that is not fully healthy.

Parameter

Type

Required

Meaning

search

string

no

Only topics whose name contains this. Case-insensitive

include_internal

bool

no

Also check __consumer_offsets and other internal topics

{"cluster_id": "…", "controller": 1, "healthy": false,
 "brokers": [{"id": 1, "host": "kafka-1", "port": 9092, "rack": "eu-1a", "controller": true, "leaders": 61}],
 "summary": {"topics": 40, "partitions": 182, "offline": 0, "under_replicated": 3, "under_min_isr": 1, "errored": 0},
 "problems": [{"topic": "orders", "partition": 4, "issues": ["under_replicated", "under_min_isr"],
               "leader": 1, "replicas": [1, 2, 3], "isr": [1], "min_insync_replicas": 2}],
 "warnings": []}

offline means the partition has no leader, so nothing can be read or written. under_replicated means a replica is out of sync. under_min_isr is the condition behind NOT_ENOUGH_REPLICAS: producers using acks=all fail until the in-sync replicas recover. A broker leading no partitions while others lead many is usually one that restarted and was never given leadership back.

A problem partition being moved between brokers carries reassigning: true with its adding_replicas and removing_replicas, and summary.reassigning counts them. An added replica is out of sync until it has copied the log, so under_replicated there is expected during the move rather than an outage.

Some Kafka-compatible brokers, Redpanda among them, do not report min.insync.replicas. The topics affected are listed in min_isr_unknown, and under_min_isr is not judged for them rather than guessed.

list_acls

Lists access control entries, for when a client fails with TOPIC_AUTHORIZATION_FAILED or GROUP_AUTHORIZATION_FAILED.

Parameter

Type

Required

Meaning

principal

string

no

Only this principal, e.g. User:payments

resource_type

string

no

topic, group, cluster, transactional_id, delegation_token

resource_name

string

no

Every ACL applied to this name, including prefixed and *. Needs resource_type

{"acls": [{"principal": "User:payments", "host": "*", "resource_type": "topic", "resource_name": "orders",
           "pattern_type": "literal", "operation": "read", "permission": "allow"}], "count": 1}

A deny overrides every allow. A broker without an authorizer fails with SECURITY_DISABLED, which means ACLs are not enforced at all.

server_config

Reports the effective configuration: endpoint name, exact path, description, cluster, brokers, authentication mechanism and principal, TLS, read-only state, export directory and the tools this endpoint exposes. Takes no parameters. The password is never reported.

tools is the list for this endpoint, not for the deployment: a read-only endpoint omits add_partitions, alter_topic_config, commit_offset, create_topic, delete_consumer_group, delete_records and delete_topic, because it does not register them, and any endpoint omits whatever its tools configuration switches off.

Use it when a result is surprising: an empty topic list means something very different on a local broker than on production.

add_partitions

Adds partitions to a topic. Irreversible — Kafka cannot reduce a partition count. Not exposed on a read-only endpoint.

Parameter

Type

Required

Meaning

items

object[]

yes

1 to 1000 topics to change

confirm

bool

no

Default false: preview only, nothing changes

Item fields:

Field

Type

Required

Meaning

topic

string

yes

Topic to change

partitions

int

yes

Final total, not the number to add. Repeating a call is safe

acknowledge_key_ordering

bool

no

Required when messages are keyed

sample_size

int

no

Messages inspected for keys. Default 20

Without confirm it reports what would happen: current and target counts, whether messages are keyed, which consumer groups will rebalance, and warnings.

Adding partitions changes which partition a key hashes to, so existing keys lose their ordering guarantee. A keyed topic therefore requires acknowledge_key_ordering as well. Requesting fewer partitions than the topic has is refused with an explanation rather than attempted.

alter_topic_config

Changes topic-level configuration: retention, cleanup policy, maximum message size and anything else Kafka allows per topic. Changes are incremental, so every key not named keeps its value. Not exposed on a read-only endpoint.

Parameter

Type

Required

Meaning

items

object[]

yes

1 to 1000 topics to change

confirm

bool

no

Default false: preview only, nothing changes

Item fields:

Field

Type

Required

Meaning

topic

string

yes

Topic to change

set

object

no

Keys to set, e.g. {"retention.ms": "86400000"}

delete

string[]

no

Overrides to remove, so the cluster default applies again

{"results": [{"index": 0, "result": {
   "topic": "orders", "applied": false, "messages_past_retention": 18000,
   "changes": [{"key": "retention.ms", "action": "set", "current": "604800000",
                "current_source": "DYNAMIC_TOPIC_CONFIG", "requested": "86400000"}],
   "warnings": ["18000 message(s) are already older than the new retention of 24h0m0s and become eligible for deletion as soon as it applies; Kafka cannot bring them back"]}}],
 "succeeded": 1, "failed": 0, "applied": 0, "atomic": false}

The preview asks the broker to validate the change, so an unknown key or an invalid value is refused before confirm. Shortening retention.ms reports how many messages are already past the new limit; changing cleanup.policy or setting retention.bytes is warned about. After applying, every key is re-read from the broker, which is the only way the inherited value of a deleted override is known.

create_topic

Creates a topic. Refuses a topic that already exists rather than adjusting it. Not exposed on a read-only endpoint.

Parameter

Type

Required

Meaning

items

object[]

yes

1 to 1000 topics to create

confirm

bool

no

Default false: the broker validates the requests and creates nothing

Item fields:

Field

Type

Required

Meaning

topic

string

yes

Name of the topic to create

partitions

int

no

Omit for the broker default on Kafka 2.4+. Can grow later, never shrink

replication_factor

int

no

Omit for the broker default on Kafka 2.4+. Cannot exceed the broker count

configs

map

no

Topic-level config, such as retention.ms or cleanup.policy

Without confirm the request is sent to the broker with ValidateOnly, so the preview reports the cluster's own answer — an invalid name, an unknown config key, a replication factor larger than the cluster — rather than a guess. With confirm the topic is created and the resulting partition count and replication factor are read back from the cluster, which is how an omitted count is reported as the number the broker actually chose.

Using broker defaults requires Kafka 2.4 or newer, whose CreateTopics v4 API introduced -1 as "use the broker default". On an older broker, pass both counts explicitly.

A replication factor larger than the number of brokers is refused here, with the broker count in the message, because brokers differ on whether a validate-only request catches it.

Use add_partitions to change an existing topic's partition count; this tool never modifies a topic it did not create.

delete_topic

Deletes topics. The most destructive tool here: deleting a topic destroys every message in it, Kafka has no undo, and any consumer group reading it breaks. Not exposed on a read-only endpoint.

Parameter

Type

Required

Meaning

items

object[]

yes

1 to 1000 topics to delete

confirm

bool

no

Default false: preview only, nothing is deleted

Item fields:

Field

Type

Required

Meaning

topic

string

yes

Topic to delete. It must exist

acknowledge_data_loss

bool

no

Required when the topic still holds messages

Without confirm it reports, per topic, how many messages would be destroyed, how many partitions it had, and which consumer groups had committed offsets for it:

{"results": [{"index": 0, "result": {
   "topic": "orders-old", "partitions": 6, "message_count": 41207,
   "consumer_groups": ["payments"], "deleted": false, "would_delete": true,
   "warnings": ["41207 message(s) would be destroyed, and Kafka cannot restore them: the only recovery is a backup taken beforehand"]}}],
 "succeeded": 1, "failed": 0, "applied": 0, "atomic": false}

Two separate acknowledgements are required, because they answer different questions. confirm says the caller meant to delete; acknowledge_data_loss says they know what is inside. A topic holding messages is refused without both, and the count is re-read at deletion time, so a topic that gained messages since the preview is still caught.

Internal topics such as __consumer_offsets are refused outright at any level of acknowledgement: they hold cluster state rather than a caller's data, and deleting one breaks every consumer at once.

Duplicate topics in one batch are refused before anything is deleted. Deletion is not atomic — topics removed before a later item failed stay removed.

delete_records

Deletes the oldest messages of a partition without deleting the topic. The topic, its configuration and its consumer groups stay. Irreversible. Not exposed on a read-only endpoint.

Parameter

Type

Required

Meaning

items

object[]

yes

1 to 1000 partitions to truncate

confirm

bool

no

Default false: preview only, nothing is deleted

Item fields:

Field

Type

Required

Meaning

topic

string

yes

Topic to delete from

partition

int

yes*

Partition to delete from. Not used with all_partitions

before_offset

int

one of

Everything below this offset goes; this one becomes the first

before_timestamp

string

one of

RFC3339: everything written before this moment goes, resolved per partition

all_partitions

bool

no

With before_timestamp, cut every partition of the topic

acknowledge_data_loss

bool

no

Required to apply

{"results": [{"index": 0, "result": {
   "topic": "orders", "partition": 0, "start_offset": 0, "end_offset": 5012, "before_offset": 812,
   "messages_deleted": 812, "would_delete": true, "deleted": false,
   "affected_groups": [{"group": "replay-job", "committed_offset": 100, "unprocessed_lost": 712}]}}],
 "succeeded": 1, "failed": 0, "applied": 0, "atomic": false}

affected_groups lists every group committed below the cut, with how many messages it would lose without ever processing them; such a group resumes from the new start. Use the partition's end offset as before_offset to empty it.

before_timestamp resolves to the first offset written at or after the moment, and the preview's before_offset shows what it resolved to. With all_partitions one item covers the whole topic: partitions lists each partition's own cut, start and end, and affected groups; the item carries all_partitions: true and partition: -1, and messages_deleted is their total. Partitions holding nothing older are left alone, and if none does, the item is refused. An offset means nothing across partitions, so all_partitions takes only a timestamp. Cuts are applied partition by partition and are not atomic.

delete_consumer_group

Deletes consumer groups and their committed offsets. Use it to clean up groups whose consumers were decommissioned: their commits keep reporting lag that nobody will ever drain. Not exposed on a read-only endpoint.

Parameter

Type

Required

Meaning

items

object[]

yes

1 to 1000 groups, each {"group": "..."}

confirm

bool

no

Default false: preview only, nothing is deleted

The preview lists each group's committed offsets and lag. A group with active members is refused, and re-checked at deletion time. A consumer that later starts with the same group id begins from its auto.offset.reset, not from where the group left off.

commit_offset

Moves consumer groups' committed offsets. Forward to skip messages, backward to replay them, or to a point in time to reprocess everything since. Irreversible in the sense that skipped messages are never processed. Not exposed on a read-only endpoint.

Parameter

Type

Required

Meaning

items

object[]

yes

1 to 1000 moves to make

confirm

bool

no

Default false: preview only, nothing changes

Item fields — give exactly one of offset, timestamp or position:

Field

Type

Required

Meaning

topic

string

yes

Topic whose offset is moving

group

string

yes

Consumer group to move

partition

int

no

Required with offset. Omit with timestamp or position for every partition

offset

int

—

The offset the group reads next. To skip offset 42, commit 43

timestamp

string

—

RFC3339. Each partition moves to its first message at or after this time

position

string

—

earliest or latest

allow_active_members

bool

no

Proceed despite running consumers

{"items": [{"topic": "orders", "group": "payments", "timestamp": "2026-10-01T09:00:00Z"}]}
{"results": [{"index": 0, "result": {
   "topic": "orders", "group": "payments", "state": "Empty", "members": 0,
   "partitions": [{"partition": 0, "current_offset": 5012, "target_offset": 4100, "replayed_messages": 912},
                  {"partition": 1, "current_offset": 4990, "target_offset": 4021, "replayed_messages": 969}],
   "replayed_messages": 1881, "applied": false}}],
 "succeeded": 1, "failed": 0, "applied": 0, "atomic": false}

A partition with no message at or after timestamp moves to its end, and its entry carries a note saying so. A whole-topic item and a partition item for the same group and topic in one batch are refused, because which one wins would depend on order.

The group must have no active members. A running consumer keeps its position in memory and only reads the committed offset when it joins, so a commit made while it runs is overwritten by its next commit and the group does not move. Stop the consumers first.

copy_message

Copies messages to another topic, preserving key, value and headers. Takes each message's address, never its content, so it can only duplicate a message the cluster already holds.

Parameter

Type

Required

Meaning

items

object[]

yes

1 to 500 copies to make

confirm

bool

no

Default false: preview only, nothing is written

Item fields:

Field

Type

Required

Meaning

source_topic, source_partition, source_offset

yes

Message to copy

destination_topic

string

yes

Where to write it. Must already exist

destination_cluster

string

no

Another cluster to write to. Defaults to this endpoint's own

translate_schema

bool

no

Re-register a schema id in the destination's registry

max_value_bytes

int

no

Preview value limit. The whole value is always copied

Set destination_cluster to copy into another cluster this server serves, which is how a production message is taken into a preproduction topic to be debugged safely. Use list_clusters to see which names are valid.

Every copy carries provenance headers — kafka-mcp-copied-from-cluster, -from-topic, -from-partition, -from-offset, -copied-at, -copied-by-tool, -copied-by-principal — so a message in a dead letter or preproduction topic can be traced back to its original. If the message already carries one of those headers, the original is kept and the collision is reported.

Key and value bytes are copied unchanged. A schema id is only meaningful in the registry that issued it, so when the destination cluster uses a different registry, or none, the response warns. With translate_schema the schema is registered in the destination registry under <destination_topic>-value (or -key), with its references, and the id in the copy is rewritten; the payload is untouched. That registration is a write to the destination registry and happens only with confirm.

For a copy within the endpoint's own cluster, its read_only policy protects the destination. A read-only endpoint can still be the source of a cross-cluster copy, because copying out changes nothing there. A different destination cluster must be listed in the calling endpoint's destinations and not configured read_only; list_clusters reports that as writable. The tool is refused entirely when the destination is read-only, preview included, because writing is all it does.

produce_message

Writes new messages to existing topics. Unlike copy_message, the caller supplies the content, so this can put a message into a topic that no producer ever sent.

Parameter

Type

Required

Meaning

items

object[]

one of

1 to 500 messages to write

upload

object

one of

{filename}: sends a file on the user's machine, through a link over HTTP

confirm

bool

no

Default false: preview only, nothing is written

Item fields:

Field

Type

Required

Meaning

topic

string

yes

Existing topic to write to

value

string

yes

The message body

key

string

no

Decides the partition when partition is omitted

headers

object

no

Header name to value

partition

int

no

Exact partition. Omit to let the key decide

encoding

string

no

utf8 (default) or base64 for exact bytes you already have

value_schema

object

no

Encode value (JSON) to a registry schema

key_schema

object

no

Encode key (JSON) to a registry schema

destination_cluster

string

no

Another cluster to write to. Defaults to this endpoint's own

max_value_bytes

int

no

Preview value limit. The whole value is always written

Every message carries kafka-mcp-produced-at, kafka-mcp-produced-by-tool, kafka-mcp-produced-by-principal and, when the client identifies itself, kafka-mcp-produced-by-client, so a fabricated message stays distinguishable from a genuine one. A header the caller supplies under one of those names is kept as given and the collision is reported.

value_schema and key_schema take {subject, version, id, message_type}, all optional: {} means the latest version of <topic>-value (or -key) in the destination cluster's registry, id pins an exact schema, and message_type picks a Protobuf message. The value is given as JSON, validated against the schema in the preview — a missing or misspelt field is refused by name — and written framed with the schema id, as registry-aware consumers expect. A topic with a format in topic_formats is encoded to it without being asked. Neither can be combined with encoding: base64. The response reports the schema used in value_encoding / key_encoding.

The topic must already exist: a missing one is refused rather than left to auto-creation. Omit partition unless the exact partition is the point — the key decides placement, and naming a partition puts a keyed message where its key does not hash to, which breaks ordering for that key. The response warns whenever an explicit partition is used.

upload sends any number of messages from a file on the user's machine, such as the one a transform_messages download saves. Over stdio the file is read right away; over HTTP the call returns a link and a curl -T command to run there (see Files on the user's machine). Each line is one item as JSON with the item fields above; a misspelt field is refused rather than ignored. The file is read as it uploads and never stored, and the command prints a summary instead of one result per line: lines, valid, invalid, written, the first three messages, and up to 50 failing lines by line number. Without confirm the upload checks the file and writes nothing; ask for a new link with confirm to send it, and the valid lines are written in file order while invalid ones are skipped.

{"name": "produce_message", "arguments": {"upload": {"filename": "orders-fixed.jsonl"}, "confirm": true}}

read_only protects the cluster being written to, so a read-only endpoint may still produce into a different cluster listed in its destinations, and is refused outright — preview included — when writing to its own. A produced message cannot be deleted; it stays until retention removes it.

transform_messages

Rewrites messages with a JavaScript transform and writes the results as new messages, to the same topic, another topic or another cluster. Made for repairs such as a document that was wrapped in a field it should not be in:

{"name": "transform_messages", "arguments": {
  "topic": "orders",
  "script": "return value.payload !== undefined",
  "transform": "return { value: value.payload }",
  "destination_topic": "orders-fixed"
}}

Parameter

Type

Required

Meaning

topic

string

yes

Topic to read from

transform

string

yes

JavaScript returning {value, key, headers}, any subset, or null to skip

script

string

no

Predicate choosing which messages to transform, as in search_messages

partitions

int[]

no

Partitions to read. Default all

from_offset

int

no

Inclusive start offset in every partition read

to_offset

int

no

Exclusive end offset

from_timestamp

string

no

Inclusive RFC3339 start time

to_timestamp

string

no

Exclusive RFC3339 end time

destination_topic

string

no

Existing topic to write to. Default the source topic

destination_cluster

string

no

Another cluster to write to

keep_partition

bool

no

Write to the source partition number instead of letting the key decide

value_schema

object

no

Encode new values to this registry schema, as in produce_message

key_schema

object

no

Same, for keys

max_messages

int

no

Selected messages to process. Default 100000, at most 10000000

timeout_seconds

int

no

Default 300, at most 3600

max_value_bytes

int

no

Example value limit. The whole value is always written

download

object

no

{filename}: saves the new messages on the user's machine as produce_message items, through a link over HTTP

confirm

bool

no

Default false: nothing is written to Kafka

The transform is JavaScript (ES2020: arrow functions, spread, destructuring, regular expressions, JSON, Math, Date; no console, require, timers or network) run once per message, with the same variables as search_messages. It returns an object with any of value, key and headers; a field left out is kept from the original, and an unknown field is refused so a typo cannot silently write the original unchanged. Returning headers replaces them all: spread the originals to keep them, and set one to null to remove it, as in { headers: { ...headers, 'error-reason': null } }.

An integer beyond 2^53, such as a 64-bit id, reaches the transform as a BigInt, so passing it through writes it back exactly. Compare it with a BigInt literal (value.id === 1234567890123456789n) or String(). Turning it into a Number rounds it, and a message whose output holds such a rounded integer is refused rather than written. The script sees values exactly as search_messages does, so a count taken there matches what is selected. A string value is written as its text, anything else as JSON. A value read with a schema id is re-encoded with that same schema; value_schema picks another, and is required when the destination uses a different registry. A topic in topic_formats is encoded to its format.

Without confirm the response counts scanned, matched, transformed, skipped and failed, shows three before/after examples, and groups failures by reason in errors, each with example addresses. With confirm the new messages are produced in batches and written says how many. Every new message carries kafka-mcp-transformed-from-cluster, -from-topic, -from-partition, -from-offset, -at, -by-tool, -by-principal and, when known, -by-client.

The originals are never changed: Kafka cannot edit a message, so the repaired versions are appended. Writing back to the source topic stops at the end offset the call started with, so it never reads its own output. Partitions are read in ascending order. A run cut short by max_messages or the timeout reports complete: false, a stopped_reason and scanned_ranges; resume it with partitions and from_offset. Writing is not atomic, and what was written before a stop stays.

download saves the new messages on the user's machine instead: over stdio at once, and over HTTP through a link, the transform running while the link is fetched and streams the new messages to the user's machine as produce_message items, so the set can be inspected and then sent with a produce_message upload. The first message is transformed before the link is handed out, so a broken transform is reported at once, and a run that fails or is cut short breaks the transfer. Downloading and previewing write nothing to Kafka, so they work on a read-only endpoint; download cannot be combined with confirm, which needs a writable destination. Like copy_message, the tool stays registered on a read-only endpoint, because the destination is chosen per call.

Skills

skills/kafka-debugging/SKILL.md is the one skill an agent loads. It routes to the scenario guides under skills/kafka-debugging/references/, rather than holding every workflow itself, so a session reads only the one it needs:

  • find-message.md — locating a message from something the user knows about it.

  • check-lag.md — measuring lag and throughput, and judging when a backlog will clear.

  • scale-partitions.md — deciding whether more partitions will help, and adding them safely.

  • skip-poison-message.md — unblocking a consumer stuck on a message it cannot process, preserving the message first.

  • create-topic.md — creating a topic with a partition count and retention chosen on purpose, including as a copy_message destination.

  • produce-message.md — writing a message: repairing and re-injecting one, reproducing a failure in another cluster, or seeding a topic.

  • compare-clusters.md — finding what differs between two environments, and creating the topics one of them is missing.

  • delete-topic.md — removing a topic and everything in it, after the user has seen what that destroys.

  • replay-messages.md — reprocessing everything since a moment, after a bug fix ships.

  • tune-topic-config.md — changing retention, cleanup policy or message size on a topic, knowing what the change does to data already there.

  • cluster-health.md — finding offline and under-replicated partitions behind NOT_ENOUGH_REPLICAS and unreadable topics.

  • purge-messages.md — deleting old messages from a partition while keeping the topic, or removing abandoned consumer groups.

  • authorization-error.md — working out which ACL is refusing a client.

  • unstable-consumer-group.md — finding why a group keeps rebalancing and which consumer keeps leaving.

  • partition-skew.md — telling key skew from a slow consumer when one partition lags, and finding the hot keys.

  • schema-error.md — testing a schema change before it ships, and tracing which schema broke producers or consumers.

The umbrella also resolves the overlap between them: "the consumer is behind" opens three of these guides, and consumer_lag's status is what decides which one is right.

Development

Command

Purpose

make env-up / make env-down

Start / stop Redpanda and Console

make build

Build with goreleaser into dist/

make run

Run the HTTP server from source

go test ./...

All tests, including container tests

go test -short ./...

Tests that need no containers

go vet ./...

Vet all packages

The documentation site lives in _docs (Vite, pnpm). pnpm install && pnpm dev there serves it locally; pushing changes under _docs/ to main publishes it to GitHub Pages through .github/workflows/docs.yml.

Tests run against real containers started by internal/domain/testenv (a Redpanda broker plus Console), so Docker must be available for the full suite.

The test and release workflows use the latest stable Go release (go-version: 'stable', check-latest: true), independently of the version in go.mod. The module's minimum Go version still applies.

Start the HTTP server

Requires Go 1.27 or later. Configuration is loaded with chu; set CONFIG_FILE to select a YAML or JSON file. into manages the process lifecycle, ada serves HTTP with context-driven shutdown, and logi initializes structured logging.

make env-up                                  # local Redpanda + Console
make run                                     # runs with --server and serves kafka-mcp.local.yaml

kafka-mcp.local.yaml is committed and points at the compose broker, so a clone works without writing any configuration. make env-up publishes the broker on localhost:19092, the Schema Registry on localhost:18081 and the Redpanda Console on http://localhost:8080.

Check it is up:

curl http://localhost:8090/healthz

Available Tools

25 tools
add_partitionsA

Increase the partition count of 1 to 1000 topics in one call through items, and report each topic's current count, affected consumer groups, sampled key usage and warnings. Changing one topic is an items array of length one.

Kafka cannot remove partitions, and changing the count can break ordering for keyed messages. No change is made unless confirm is true; one confirm covers the whole batch, and changes are not atomic because Kafka cannot roll a successful item back. Results follow items order, each carrying index with result or error.

Keyed samples also require acknowledge_key_ordering on the item. Requires Kafka ALTER permission.

NameRequiredDescriptionDefault
itemsYesThe topics to change, 1 to 1000 of them. Changing one topic is an array of length one. Duplicate topic names are refused before anything changes.
confirmNoOptional. When false or omitted, nothing is changed and the response describes what would happen for every item. Must be true to actually add partitions. One confirm covers the whole batch.

Output Schema

NameRequiredDescription
atomicYes
failedYes
appliedYes
resultsYes
succeededYes

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does it well: it discloses the confirm gate, that no change happens unless confirmed, that the batch is non-atomic with no rollback after partial success, that keyed-message ordering can break, and that Kafka ALTER permission is required. It also states duplicate topic names are refused before anything changes.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose and scale are front-loaded and every paragraph carries real information about risk or gating. Phrasing is slightly awkward ('increase the partition count of 1 to 1000 topics in one call through items') and the trailing note on acknowledge_key_ordering reads as an afterthought rather than integrated with the confirm discussion.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a high-risk mutation with no annotations, the description covers permission needs, gating, atomicity limits, and ordering hazards. An output schema exists, so the brief note about per-item results carrying index with result or error is sufficient rather than a gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the per-item fields are already well documented, so the schema does the heavy lifting. The description reinforces batch semantics ('one confirm covers the whole batch', 'results follow items order, each carrying index with result or error') but adds little that the schema does not already say.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('increase the partition count') plus an explicit scale ('1 to 1000 topics in one call through items'), and clarifies that a single-topic change is just an items array of length one. Combined with the 'Kafka cannot remove partitions' constraint, an agent can tell this is the in-place resize operation, distinct from siblings like create_topic or alter_topic_config.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear operational context: batch via items, a dry-run default when confirm is absent, and the requirement to set acknowledge_key_ordering for keyed topics. It never names a sibling alternative (e.g. create_topic for new topics), so the exclusion side of the guidance is missing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

alter_topic_configA

Change topic-level configuration of 1 to 1000 topics in one call through items. Each item sets keys (set) and removes overrides so the cluster default applies again (delete). Every key not named keeps its value: changes are incremental, never a full replace. Changing one topic is an items array of length one.

No change is made unless confirm is true. The preview asks the broker to validate every item and returns, per key, the current value and its source (DYNAMIC_TOPIC_CONFIG means a deliberate topic setting; anything else is inherited) next to the requested value. Shortening retention.ms reports how many messages are already older than the new limit, because they become eligible for deletion straight away. Changing cleanup.policy or retention.bytes is warned about.

One confirm covers the whole batch, and applying is not atomic: topics changed before a later item failed stay changed. Results follow items order, each carrying index with result or error. Requires Kafka ALTER_CONFIGS permission.

NameRequiredDescriptionDefault
itemsYesThe topics to change, 1 to 1000 of them. Changing one topic is an array of length one. Duplicate topic names are refused before anything changes.
confirmNoOptional. When false or omitted, nothing is changed: the broker validates every item and the response shows each key's current and requested value. Must be true to apply. One confirm covers the whole batch.

Output Schema

NameRequiredDescription
atomicYes
failedYes
appliedYes
resultsYes
succeededYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so: non-atomic batches, incremental merge semantics, duplicate-topic refusal, per-key source reporting (DYNAMIC_TOPIC_CONFIG vs inherited), retention.ms deletion side effect, warnings for cleanup.policy/retention.bytes, and the required Kafka ALTER_CONFIGS permission. These are exactly the traits an agent needs and none are in structured fields.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and incremental semantics, and nearly every sentence carries a distinct behavioral fact with no filler. It is dense but slightly sprawling: warnings and atomicity could be grouped more tightly, and the permission requirement sits at the very end.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation tool with no annotations and a nested items array, the description covers safety (confirm gate), failure modes (non-atomic, per-item errors), side effects (retention eligibility, cleanup.policy warnings) and permissions. An output schema exists, so return-value shape is correctly left to it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents items, set, delete, topic and confirm. The description still adds value by clarifying that one confirm covers the whole batch, that per-key results carry index in items order, and that set/delete are mutually exclusive at the key level.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (Change) and resource (topic-level configuration) with the exact scale (1 to 1000 topics) and the two per-item operations (set/delete). No sibling tool touches topic configs, and the description makes the boundary with add_partitions/create_topic obvious.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explains the payload shape, that changes are incremental rather than full replaces, that one topic is an items array of length one, and that nothing happens unless confirm is true, which effectively describes a preview mode. It stops short of naming explicit alternatives (e.g. server_config for broker-level defaults), but the when-to-use context is unambiguous.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cluster_healthA

Check the health of the cluster this endpoint serves in one call: cluster id, controller, every broker (id, host, port, rack, and how many partitions it leads), a summary count, and every partition that is not fully healthy.

A problem partition lists its issues:

  • offline: no leader, so nothing can be produced to or consumed from it.

  • under_replicated: fewer in-sync replicas than replicas, so a broker is down or falling behind.

  • under_min_isr: fewer in-sync replicas than the topic's min.insync.replicas, so producers using acks=all fail with NOT_ENOUGH_REPLICAS.

  • error: the broker returned an error for the partition.

A problem partition with reassigning true is being moved between brokers; adding_replicas catch up before joining the ISR, so under_replicated there is expected while the move runs, not an outage.

healthy is true only when there are no problems. Use search to limit the check to topics whose name contains a substring (case-insensitive); internal topics are skipped unless include_internal is true. min_isr_unknown lists topics whose min.insync.replicas the broker did not report, so under_min_isr was not judged for them.

NameRequiredDescriptionDefault
searchNoOptional case-insensitive substring that a topic name must contain to be checked. Omit to check every topic. Brokers and the controller are always reported.
include_internalNoOptional. When true, also check Kafka's internal topics such as __consumer_offsets. Defaults to false. An unhealthy internal topic breaks every consumer group, so include them when groups fail across the board.

Output Schema

NameRequiredDescription
brokersYes
healthyYes
summaryYes
problemsYes
warningsYes
cluster_idYes
controllerYes
min_isr_unknownNo

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it explains what each problem flag means operationally, that acks=all producers fail with NOT_ENOUGH_REPLICAS, and that under_replicated during a reassignment is expected rather than an outage. It still never states explicitly that the call is read-only or comment on cost on large clusters.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with a one-sentence summary of what one call returns, then a bulleted glossary of issue types that genuinely aids interpretation. The reassigning and min_isr_unknown paragraphs are useful but push the length past what an agent strictly needs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a two-parameter, fully optional-input diagnostic with an output schema, the definition covers interpretation of the results and edge cases (reassigning brokers, min_isr_unknown topics). Only the explicit read-only/no-side-effect assurance is missing, which matters given the absence of annotations.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and both parameters are already described there with the same case-insensitive substring and default-false semantics, so the description largely restates the schema. The only added value is the 'brokers and the controller are always reported' scope note and the usage framing for include_internal.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Check the health of the cluster') and immediately enumerates the payload (cluster id, controller, per-broker details, summary, problem partitions), which cleanly separates it from siblings like list_clusters or describe_topic.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives concrete scoping guidance ('Use search to limit the check to topics whose name contains a substring') and a decision rule for internal topics (include them when consumer groups fail across the board). It never names a sibling alternative or states when a different tool would be a better choice.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

commit_offsetA

Move 1 to 1000 consumer group positions in one call through items. Each item names a group and topic and exactly one target: offset (one partition, the next offset the group reads), timestamp (RFC3339; each partition moves to its first message at or after that time, which is how messages are replayed since a moment), or position (earliest or latest). With timestamp or position, omit partition to move every partition of the topic. Moving one offset is an items array of length one.

The response previews, per partition and in total, how many messages each move would skip or replay. No change is made unless confirm is true; one confirm covers the whole batch, and applying is not atomic because Kafka cannot roll back commits that succeeded before a later item failed. Results follow items order, each carrying index with result or error.

Active groups are refused unless allow_active_members is set on the item, because running consumers may overwrite the commit. Requires Kafka offset-commit permission.

NameRequiredDescriptionDefault
itemsYesThe moves to make, 1 to 1000 of them. Moving one offset is an array of length one. Two items that would move the same group, topic and partition, including a whole-topic item and a partition item, are refused before anything changes.
confirmNoOptional. When false or omitted, nothing is changed and the response describes what would happen for every item. Must be true to actually move the offsets. One confirm covers the whole batch.

Output Schema

NameRequiredDescription
atomicYes
failedYes
appliedYes
resultsYes
succeededYes

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so: it is a dry-run by default ('No change is made unless confirm is true'), one confirm covers the whole batch, applying is non-atomic and earlier commits cannot be rolled back, results follow items order with index, active groups are refused for a stated reason, and Kafka offset-commit permission is required.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the action and the three targets, then the safety/confirmation behavior, then the active-group caveat. Slight redundancy with the schema in the parameter enumeration, but each paragraph carries distinct operational information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return shape needn't be spelled out, yet the description still signals the preview (per-partition and total skipped/replayed counts), the confirm gate, and the per-item result/error structure. Nothing an agent needs to invoke this mutation correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3, and most of the description's parameter detail (offset meaning, timestamp semantics, position values, allow_active_members rationale) restates what the schema already documents. It adds only marginal framing, like 'Moving one offset is an items array of length one' and the exactly-one-target rule.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource with scope: 'Move 1 to 1000 consumer group positions in one call through items.' No sibling tool (describe_consumer_group, delete_consumer_group, consumer_lag) does offset mutation, and the batch/replay framing makes the domain unmistakable.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explains precisely which of the three mutually exclusive targets to use and under what conditions: offset for a single partition, timestamp 'to replay since a moment', position for earliest/latest, and omit partition to move all partitions. It also describes the single-item case. It stops short of naming sibling alternatives such as describe_consumer_group for inspection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

compare_clustersA

Compare the topics of 1 to 1000 other clusters against the one this endpoint serves, through items. Reports which topics only this cluster has, which only the other has, which exist on both but disagree, and how many brokers and topics each side has. Comparing one cluster is an items array of length one.

Use it to find what preproduction has that production does not, or to check whether two environments still match. Topics reported as only on the other cluster carry their partition count, replication factor and explicitly-set configs, so the report can be handed straight to create_topic.

This tool creates and changes nothing. To create the missing topics, pass them to create_topic, which previews them against the broker first.

Only configs a topic sets for itself are compared. Two clusters may carry different broker defaults, and comparing inherited values would report every topic as different. Internal topics are excluded unless include_internal is set.

Topic listings come from the client's metadata cache, so a topic created within the last few seconds may still be reported as missing. Repeat the comparison after a moment rather than creating it twice.

NameRequiredDescriptionDefault
itemsYesThe clusters to compare against, 1 to 1000 of them. Comparing one cluster is an array of length one. Results follow this order and an unreachable cluster is reported against its own item.

Output Schema

NameRequiredDescription
atomicYes
failedYes
appliedYes
resultsYes
succeededYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, yet the description covers the critical behavioral facts: it is non-mutating, only explicitly-set topic configs are compared (not inherited broker defaults), internal topics are excluded by default, and results come from the client metadata cache so recently created topics may appear missing. It even warns to repeat the comparison rather than double-create, which is exactly the kind of guidance an agent needs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core compare semantics and the report contents before details. It runs five paragraphs, and while most sentences carry information (cache staleness, config comparison rules), the create_topic hand-off and the cache caveat could be tightened.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return values need not be explained, and the description still summarizes the shape (which topics, partition count, RF, explicit configs). For a non-trivial multi-cluster comparison with no annotations, the description covers safety, staleness, filtering defaults, and follow-up actions with nothing material missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real meaning: a single comparison is an items array of length one, results follow item order, and an unreachable cluster is reported against its own item. The include_internal and search semantics are largely covered in the schema itself, so it sits just above baseline.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (compare), resource (topics/items across clusters), and scope (1 to 1000 other clusters against the one this endpoint serves). It explicitly enumerates what the report contains (only-here topics, only-there topics, disagreeing topics, broker/topic counts), which distinguishes it from siblings like describe_topic or list_topics.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Names concrete scenarios ('find what preproduction has that production does not', 'check whether two environments still match') and hands off the follow-up action explicitly: missing topics go to create_topic, which previews against the broker first. It also states the tool creates nothing and points to list_clusters for valid names.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

consumer_lagA

Measure consumer lag for 1 to 1000 topics in one call through items: production and consumption rates, and whether each topic's backlog is caught up, draining, growing, stalled or has no active consumers. Returns an ETA only when lag is shrinking.

Results follow items order, each carrying index with result or error. Measuring one topic is an items array of length one.

Consumption rate is sampled for sample_seconds, so the call waits that long. Up to 4 windows run at a time, so the call takes about sample_seconds per 4 items. Set skip_consume_rate for an immediate result without an ETA.

NameRequiredDescriptionDefault
itemsYesThe measurements to take, 1 to 1000 of them. Measuring one topic is an array of length one. Up to 4 sampling windows run at a time, so the call takes about sample_seconds for every 4 measurements.

Output Schema

NameRequiredDescription
atomicYes
failedYes
appliedYes
resultsYes
succeededYes

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so: it discloses that the call blocks, why (Kafka stores no commit history), the concurrency ceiling (4 windows), the resulting latency model ('about sample_seconds per 4 items'), per-item error reporting via index, and the conditional ETA. This is exactly the behavioral context an agent needs before committing to a blocking call.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded in the first sentence, then output shape, then cost model, then the opt-out flag — a sensible order. Slight redundancy: the '4 windows at a time / sample_seconds per 4 items' fact is stated twice and is also repeated in the schema's items description.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single batched parameter with an output schema present, everything an agent needs is covered: batch limits, ordering contract, failure isolation per item, latency, and the flag to skip sampling. Return-value internals are correctly left to the output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3; the description adds value beyond the schema by tying items ordering to results, clarifying the length-one case, and reinforcing the wall-clock cost of sample_seconds. It stops short of describing the group-resolution default in prose, leaving that to the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('Measure consumer lag') with explicit scope (1 to 1000 topics in one call) and enumerates the outputs produced (production/consumption rates, backlog state, ETA). However, it never names an overlapping sibling such as describe_consumer_group or list_consumer_groups, so sibling disambiguation is left to inference.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Clear operational guidance: 'Set skip_consume_rate for an immediate result without an ETA' and 'Measuring one topic is an items array of length one' tell the agent how to size and configure calls. It lacks explicit when-not-to-use or alternative-tool routing, so it falls short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

copy_messageA

Copy 1 to 500 existing messages in one call through items, each identified by topic, partition and offset, to an existing topic on this or another configured cluster. Preserves key, value and headers and adds traceable provenance headers. Copying one message is an items array of length one.

No message is written unless confirm is true; one confirm covers the whole batch, and writing is not atomic because Kafka cannot retract a record produced before a later item failed. Results follow items order, each carrying index with result or error.

The destination must be writable and requires Kafka write permission; a read-only cluster may still be the source.

A key or value carrying a Schema Registry id is copied byte for byte. When the destination cluster uses a different registry the id may mean nothing there, so the response warns; set translate_schema to register the schema in the destination registry and rewrite the id.

NameRequiredDescriptionDefault
itemsYesThe copies to make, 1 to 500 of them. Copying one message is an array of length one. Two items naming the same source and destination are refused before anything is written.
confirmNoOptional. When false or omitted, nothing is written and the response shows the messages that would be copied. Must be true to actually write them. One confirm covers the whole batch.

Output Schema

NameRequiredDescription
atomicYes
failedYes
appliedYes
resultsYes
succeededYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so well: it discloses the non-atomic batch write behavior, that confirm gates all writes, result ordering, index/error reporting, permission requirements, read-only source allowance, and schema-ID translation implications.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core operation and organized into short paragraphs covering batch behavior, confirmation, permissions, and schema handling. It is appropriately sized for a complex batch-copy tool, though it repeats the single-message case already covered by the items schema description.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the batch complexity, cross-cluster behavior, confirmation gate, and schema registry nuances, the description is complete enough for an agent to invoke it correctly. An output schema exists, so return-value details need not be explained further.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the parameter schema already documents fields in detail. The description still adds useful semantics beyond the schema, especially that one confirm covers the whole batch, that duplicate source/destination pairs are refused up front, and how translate_schema affects registry writes and ID rewriting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise verb and resource: copy 1 to 500 existing messages, identified by topic/partition/offset, to an existing topic. It distinguishes itself from read or produce siblings by emphasizing existing-message copying, provenance headers, and optional cross-cluster destination.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It clearly establishes usage context: batch copying, how to copy a single message, the confirm requirement, and destination/source permission constraints. It does not explicitly name alternatives such as get_message, produce_message, or transform_messages, nor state when not to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_topicA

Create 1 to 1000 Kafka topics in one call through items, each with optional partition count, replication factor and topic-level configs. Omitted values use broker defaults. Existing topics are refused rather than modified. Creating one topic is an items array of length one.

No topic is created unless confirm is true; otherwise the broker only validates the requests. One confirm covers the whole batch, and creation is not atomic: topics created before a later item failed stay, because Kafka cannot roll them back. Results follow items order, each carrying index with result or error.

Partition counts cannot be reduced later. Requires Kafka CREATE permission.

NameRequiredDescriptionDefault
itemsYesThe topics to create, 1 to 1000 of them. Creating one topic is an array of length one. Duplicate topic names are refused before anything is created.
confirmNoOptional. When false or omitted, nothing is created: every item is validated by the broker and the response describes what would happen. Must be true to actually create. One confirm covers the whole batch.

Output Schema

NameRequiredDescription
atomicYes
failedYes
appliedYes
resultsYes
succeededYes

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does it: confirm gating, non-atomic creation with partial failures that are not rolled back, refusal of existing topics and duplicates, broker defaults for omitted values, and the Kafka CREATE permission requirement. The irreversibility warning that partition counts cannot be reduced later is exactly the kind of consequence an agent must know before mutating.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action and scope, then layered semantics (confirm gate, atomicity, ordering) in short declarative sentences. No filler sentences and no restatement of the title.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive-capable batch write with no annotations, the description covers gating, failure modes, permissions and parameter defaults, and an output schema exists to explain return values. Nothing an agent needs in order to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description adds some framing around the items array and confirming the whole batch, but it largely restates what the schema already documents for items, confirm, partitions and replication_factor.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource (create Kafka topics) with explicit batch scope of 1 to 1000 and per-item fields. The line 'Existing topics are refused rather than modified' cleanly separates it from alter_topic_config, add_partitions and delete_topic without the agent needing to infer.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives concrete selection guidance: 'Creating one topic is an items array of length one', and confirm=false as a validate-only dry run versus confirm=true to actually create. It never names a sibling tool for adjacent needs (e.g. changing existing topic configs), so it stops short of explicit alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_consumer_groupA

Delete 1 to 1000 consumer groups in one call through items, removing each group's committed offsets. Use it to clean up groups whose consumers were decommissioned: their commits keep reporting lag that nobody will ever drain. Deleting one group is an items array of length one.

No group is deleted unless confirm is true. The preview lists each group's state, the topics it has committed on, every committed offset with its lag, and the total lag that would disappear with it. A group with active members is refused: it is not abandoned, and deleting it would reset where its consumers resume.

If a consumer later starts with the same group id, it begins wherever its auto.offset.reset points, not where the group left off. One confirm covers the whole batch, and deletion is not atomic: groups deleted before a later item failed stay deleted. Results follow items order, each carrying index with result or error. Requires Kafka DELETE permission on the group.

NameRequiredDescriptionDefault
itemsYesThe groups to delete, 1 to 1000 of them. Deleting one group is an array of length one. Naming the same group twice is refused before anything is deleted.
confirmNoOptional. When false or omitted, nothing is deleted and the response shows each group's committed offsets and lag. Must be true to delete. One confirm covers the whole batch.

Output Schema

NameRequiredDescription
atomicYes
failedYes
appliedYes
resultsYes
succeededYes

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does: confirm-gated preview, refusal of groups with active members, offset reset behavior for a reused group id, batch-level confirm, non-atomic partial failure, ordered per-item results, and the required Kafka DELETE permission. These are exactly the consequences an agent needs before calling a destructive tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the action and the cancel-by-default confirm gate, then each subsequent paragraph earns its place by covering preview contents, refusal, consequences, and partial-failure ordering. It runs long for a tool description, but there is almost no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The output schema covers the per-item index/result/error shape, and the description covers everything else an agent needs: batch limit, confirm gate, preview payload, refusal conditions, non-atomicity, and permissions. Nothing material is left for the agent to guess.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the schema already documents items, the one-group-as-array-of-one convention, duplicate-name refusal, and confirm semantics. The description restates those rather than adding new meaning such as validation rules, size limits per item, or error formats not in the schema, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource (delete consumer groups), the scope (1–1000 per call via items), and the concrete side effect (committed offsets removed). It also disambiguates from sibling operations like describe_consumer_group or consumer_lag by framing this as the cleanup path for abandoned groups.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a clear when-to-use condition — decommissioned consumers whose stale commits report lag that will never drain — and a when-not case (groups with active members are refused). It does not name a sibling alternative such as resetting offsets or describing the group first, so usage routing is inferred rather than explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_recordsA

Delete the oldest messages of 1 to 1000 partitions in one call through items, without deleting the topic. Each item removes every message of one partition with an offset lower than before_offset; the message at before_offset becomes the first readable one. Alternatively give before_timestamp to cut at a moment, resolved per partition, and all_partitions to apply that moment to every partition of the topic in one item. Use it to purge test data, or a run of bad messages at the head of a partition, while keeping the topic, its configuration and its consumer groups. Truncating one partition is an items array of length one.

No message is deleted unless confirm is true and the item sets acknowledge_data_loss. The preview reports the partition's start and end offsets, how many messages would be removed, and every consumer group whose committed offset is below the cut, with how many messages it would lose without ever processing them. Such a group resumes from the new start offset.

Kafka cannot restore deleted records. One confirm covers the whole batch, and deletion is not atomic: partitions truncated before a later item failed stay truncated. Results follow items order, each carrying index with result or error. Compacted topics are not supported by every broker. Requires Kafka DELETE permission on the topic.

NameRequiredDescriptionDefault
itemsYesThe partitions to truncate, 1 to 1000 of them. Truncating one partition is an array of length one. Two items naming the same topic and partition are refused before anything is deleted.
confirmNoOptional. When false or omitted, nothing is deleted and the response shows how many messages each item would remove and which consumer groups have not read them yet. Must be true to delete. One confirm covers the whole batch.

Output Schema

NameRequiredDescription
atomicYes
failedYes
appliedYes
resultsYes
succeededYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so thoroughly: two independent gates (confirm and acknowledge_data_loss), preview contents including per-consumer-group data-loss exposure, irreversibility ('Kafka cannot restore deleted records'), non-atomic batch failure semantics, result ordering, duplicate item refusal, compacted-topic caveat, and the required Kafka DELETE permission.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the operation and scope in the first sentence, then layers gating, preview, and failure semantics in short paragraphs that each carry distinct information. Length is justified by the destructive nature of the tool; nothing is padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Output schema exists, yet the description still summarizes the preview payload (start/end offsets, message counts, affected consumer groups). Combined with permissions, irreversibility, and partial-failure behavior, an agent has everything needed to call this safely.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds genuine cross-parameter meaning: one confirm covers the whole batch, all_partitions resolves a single moment per partition, and before_offset/before_timestamp are mutually exclusive. It does not add much to individual fields beyond what the schema already states.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a precise verb and resource ('Delete the oldest messages of 1 to 1000 partitions') and immediately bounds the scope with 'without deleting the topic', which cleanly separates it from delete_topic in the sibling list. An agent knows exactly what operation this performs without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives concrete motivating scenarios ('purge test data, or a run of bad messages at the head of a partition, while keeping the topic, its configuration and its consumer groups') and clarifies the single-partition case. It never names a sibling alternative explicitly, relying on the implicit 'without deleting the topic' contrast with delete_topic, so it falls short of a full when/when-not/alternatives statement.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_topicA

Delete 1 to 1000 Kafka topics in one call through items. Deleting one topic is an items array of length one.

This is the most destructive tool here. Deleting a topic destroys every message in it, and Kafka has no undo: recreating the topic does not bring the data back. Any consumer group reading it breaks.

Nothing is deleted unless confirm is true; otherwise the response reports, per topic, how many messages would be destroyed, how many partitions it had, and which consumer groups had committed offsets for it. A topic that still holds messages also requires acknowledge_data_loss on its item, so destroying data is never a single unconsidered call.

One confirm covers the whole batch, and deletion is not atomic: topics deleted before a later item failed stay deleted. Results follow items order, each carrying index with result or error. Internal topics are refused. Requires Kafka DELETE permission.

NameRequiredDescriptionDefault
itemsYesThe topics to delete, 1 to 1000 of them. Deleting one topic is an array of length one. Naming the same topic twice is refused before anything is deleted.
confirmNoOptional. When false or omitted, nothing is deleted and the response describes what each deletion would destroy. Must be true to actually delete. One confirm covers the whole batch.

Output Schema

NameRequiredDescription
atomicYes
failedYes
appliedYes
resultsYes
succeededYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does it well: it discloses irreversibility ('Kafka has no undo: recreating the topic does not bring the data back'), collateral damage (consumer groups break), the two-stage confirm/acknowledge gating, non-atomic partial-batch failure, result ordering with index/result/error, refusal of internal topics, and the required Kafka DELETE permission. This is exactly the behavioral context annotations would otherwise supply.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the action and scale, then escalates to the destruction warning, then the confirm mechanics, then operational caveats. Dense but every sentence carries weight, with only slight redundancy against the schema (the 'array of length one' point is repeated in the items description).

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a high-risk mutation tool with two params, nested items, and a real output schema, the description covers safety gating, partial-failure semantics, permission requirements, and per-item result shape. Nothing an agent needs before calling correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description nevertheless adds meaning by explaining that one confirm covers the whole batch, that acknowledge_data_loss is per-item and only needed when messages exist, and that results align to items order with an index. Minor overlap with schema text keeps it from a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a precise verb and resource (delete Kafka topics), the batch shape (1 to 1000 via items, single delete = array of length one), and explicitly positions itself as 'the most destructive tool here', which separates it from siblings like delete_records or delete_consumer_group. An agent knows exactly what this does without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear conditions for use: nothing is deleted unless confirm is true, and topics holding messages additionally require acknowledge_data_loss, plus the instruction to preview first for message_count. It does not name an alternative sibling (e.g. describe_topic or sample_messages) for pre-deletion inspection, so it stops short of explicit when-not/alternatives routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

describe_consumer_groupA

Describe 1 to 1000 consumer groups in one call through items: state, assignor, coordinator broker, every running member (member_id, client_id, host and the partitions assigned to it), and for every partition the group owns or has committed on, the committed offset, end offset, lag, and the member, client id and host consuming it. Partitions and members are sorted.

Use it to find which consumer instance owns a stuck or lagging partition, so the operator knows which pod or host to inspect. has_commit false means the group owns the partition but never committed there, so its starting point is decided by the consumer's auto.offset.reset rather than by an offset. An Empty group has no members but keeps its commits.

sample_seconds watches the group for that long first and reports observation: the states it passed through and the members that joined or left. A group that keeps cycling through PreparingRebalance, or whose members keep changing, is rebalancing; the departing members' client_id and host point at the consumer that keeps leaving.

Results follow items order, each carrying index with result or error.

NameRequiredDescriptionDefault
itemsYesThe groups to describe, 1 to 1000 of them. Describing one group is an array of length one. Results follow this order and a group that does not exist is reported against its own item.

Output Schema

NameRequiredDescription
atomicYes
failedYes
appliedYes
resultsYes
succeededYes

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it explains 'has_commit false' semantics, what an Empty group means, that sample_seconds blocks the call while observing, and that members/partitions are sorted with results returned in items order plus index. It omits cost/read-only framing and limits on describing 1000 groups at once, so it stops short of full disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the verb, resource, and payload, then usage, then edge-case semantics. Mostly earns its length, though a few clauses (sorting, result indexes) are minor and the second paragraph could be tightened.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema already exists, yet the description still explains the domain interpretation of results (has_commit, Empty groups, rebalance detection), which is genuinely useful for a Kafka operator. Gaps are minor: no statement of read-only safety or the latency cost of large items arrays with sample_seconds.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3, but the description adds real meaning: it explains what sample_seconds observation reports (states passed through, members joined/left) and how to read it as a rebalance signal. Items ordering and per-item result/error behavior are also clarified beyond the schema text.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Describe 1 to 1000 consumer groups in one call') and immediately enumerates the returned facts (state, assignor, coordinator, members, offsets, lag), which clearly separates it from list_consumer_groups or consumer_lag. An agent can identify the tool without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit operational scenario: 'Use it to find which consumer instance owns a stuck or lagging partition, so the operator knows which pod or host to inspect.' However, it never names alternatives (consumer_lag, list_consumer_groups) or states when to use those instead.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

describe_topicA

Describe 1 to 500 topics in one call through items: partitions, offset ranges, approximate message count, size on disk (size_bytes per partition and topic, replicated_size_bytes across replicas), oldest/newest timestamps and complete effective configuration. Config entries identify whether values are inherited or topic-specific.

Results follow items order, each carrying index with result or error, so a topic that does not exist is reported against its own item rather than failing the call. Describing one topic is an items array of length one.

Message counts are offset spans and may overcount after retention or compaction.

NameRequiredDescriptionDefault
itemsYesThe topics to describe, 1 to 500 of them. Describing one topic is an array of length one. Results follow this order and a missing topic is reported against its own item.

Output Schema

NameRequiredDescription
atomicYes
failedYes
appliedYes
resultsYes
succeededYes

TDQS

A3.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the burden and does well: it discloses per-item error isolation (a missing topic is reported against its own item instead of failing the call), result ordering by items index, and the important caveat that message counts are offset spans that may overcount after retention or compaction. It stops short of stating that the operation is read-only or of any permission/auth requirements.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action and scope in the first clause, then layered detail on return contents and caveats. The return-field enumeration (size_bytes, replicated_size_bytes, inherited vs topic-specific config) is dense but each item earns its place by disambiguating the payload.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be explained, yet the description adds genuinely useful semantics (offset-span overcounting, config inheritance) that a schema alone would not convey. For a single-parameter read tool, the only real omission is an explicit statement that the call is non-mutating.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% at one parameter, so the schema already documents `items` and exact/case-sensitive topic matching. The description restates the 1-to-500 bound, the single-topic-is-length-one convention and the error-isolation behavior, which mostly duplicates the schema's own items description rather than adding new semantics.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Describe ... topics') plus the batching scope (1 to 500) and enumerates exactly what is returned: partitions, offsets, sizes, timestamps, effective config. It is clearly distinguishable from mutation siblings, but it never names list_topics or explains how 'describe' differs from 'list', leaving that inference to the agent.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied rather than stated: the batching range and the note that 'describing one topic is an items array of length one' tell the agent how to call it, but there is no explicit guidance on when to reach for describe_topic over list_topics or when to split into multiple calls.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_messageA

Read 1 to 500 Kafka messages at exact addresses in one call through items, and optionally nearby messages for context. Returns key, value, headers, timestamp and original value size.

Values carrying a Schema Registry id (Avro, Protobuf, JSON Schema) and topics with a configured format are decoded to JSON; format, schema_id and message_type say what the bytes were. Anything else is returned as text, or base64 when it is binary, and decode_error explains a value that named a schema but could not be decoded.

Results follow items order, each carrying index with result or error, so an invalid partition or an offset beyond the partition end is reported against its own item rather than failing the call. Reading one message is an items array of length one.

NameRequiredDescriptionDefault
itemsYesThe message addresses to read, 1 to 500 of them. Reading one message is an array of length one. Results follow this order and an address that cannot be read is reported against its own item.

Output Schema

NameRequiredDescription
atomicYes
failedYes
appliedYes
resultsYes
succeededYes

TDQS

A3.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations the description carries the full burden and does it well: it discloses the decoding pipeline (Schema Registry ids, configured topic formats, format/schema_id/message_type fields), the text/base64 fallback for undecoded bytes, decode_error, truncation via max_value_bytes, and per-item error isolation so one bad address doesn't fail the call. It omits permission/auth requirements and any rate or cost characteristics, which keeps it off a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action and payload, then organized into addressing, decoding, and error-order paragraphs. It is four paragraphs long but nearly every sentence contributes new information (decoding rules, binary fallback, per-item error semantics); only the closing 'items array of length one' sentence is close to redundant with the schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so the description doesn't have to explain return shape, yet it usefully flags the envelope fields and decode metadata that matter for interpreting results. Combined with 100% schema coverage and detailed error-isolation behaviour, an agent has what it needs; only permissions and any preconditions for reading a topic are unaddressed.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and every nested field (topic, partition, offset, context, max_value_bytes) has its own description, so the baseline is 3. The description adds meaning about result ordering and index-keyed error reporting for items, but it doesn't restate the 1-500 bound or detail how context interacts with max_value_bytes beyond what the schema already says.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb and resource ('Read 1 to 500 Kafka messages at exact addresses ... through items') and pins the scope to exact partition/offset addressing plus optional context neighbours. It implicitly contrasts with sample_messages/search_messages by stressing 'exact addresses', but never names those siblings, so an agent must infer the distinction.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied rather than stated: 'at exact addresses' implies you already know the offset, and 'Reading one message is an items array of length one' covers the singleton case. There is no explicit 'use this instead of search_messages when you already have offsets' or prerequisite about discovering offsets first.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_schemaA

Read 1 to 1000 schemas from this cluster's Schema Registry in one call through items, each by subject (latest version unless version is given) or by the schema id a message carries. Returns the schema text, its type (avro, protobuf or json_schema), id, version, every version of the subject, the schemas it references, and for an id the subject versions that use it. A subject lookup also reports the compatibility level in force. check_schema tests a candidate schema against the subject's latest version without registering it, returning whether it is compatible and the registry's reasons if not.

Read the schema before producing to a schema-encoded topic: it names every field and enum a value needs, which a sampled message may not show. For Protobuf, message_types lists the names produce_message accepts as message_type. The subject for a topic's values is usually -value.

Results follow items order, each carrying index with result or error. Fails as a whole when the cluster has no schema_registry configured.

NameRequiredDescriptionDefault
itemsYesThe schemas to look up, 1 to 1000 of them. Looking up one schema is an array of length one. Results follow this order and a subject or id that does not exist is reported against its own item.

Output Schema

NameRequiredDescription
atomicYes
failedYes
appliedYes
resultsYes
succeededYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden and does so: it discloses wholesale failure when the cluster has no schema_registry configured, partial failure semantics (results follow items order, each carrying index with result or error, a missing subject/id reported against its own item), that check_schema does not register anything, and that a subject lookup additionally reports the compatibility level in force.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose, return shape, usage trigger and failure behavior are all front-loaded before details, and each paragraph is topical. It is somewhat long, and the check_schema sentence reads like a separate tool blurb, which slightly blurs that it is an items-level option.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given an output schema exists, return values need not be spelled out, yet the description covers them; combined with the per-item error semantics, the Protobuf message_type hint for produce_message, and the subject naming convention, an agent has everything needed to call this correctly and interpret partial failures.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3; the description adds genuine semantics beyond the schema — the schema id is 'the schema id a message carries' (linking to get_message's schema_id), latest is the default unless version is given, version is ignored when id is used, and the <topic>-value subject naming convention. It stops short of elaborating on the check_schema result fields beyond what the nested schema states.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence names a specific verb (read), resource (schemas from this cluster's Schema Registry), scope (1 to 1000 in one call via items) and the two lookup keys (subject or schema id). It also enumerates what comes back — schema text, type, id, version, all subject versions, references, and the compatibility level. No sibling tool in the list does this, so the agent can route unambiguously.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a concrete trigger: read the schema before producing to a schema-encoded topic, because a sampled message may not reveal every field or enum — this implicitly positions it against sample_messages. It also clarifies that check_schema (a nested option here) verifies a candidate without registering it. It lacks an explicit 'when not to use' or a direct pointer to an alternative sibling for plain message inspection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_aclsA

List the access control entries (ACLs) on the cluster this endpoint serves. Each entry has principal, host, resource_type, resource_name, pattern_type (literal, prefixed), operation (read, write, describe, ...) and permission (allow or deny). Results are sorted.

Use it when a client fails with TOPIC_AUTHORIZATION_FAILED, GROUP_AUTHORIZATION_FAILED or similar: filter by the client's principal, or by the resource it was refused on. Filtering by resource_name returns every ACL the broker applies to that name, including prefixed and wildcard entries, so the answer covers what actually decides access. A deny overrides any allow.

All filters are optional and combine. Fails with SECURITY_DISABLED when the broker has no authorizer, which means ACLs are not enforced at all. Needs DESCRIBE permission on the cluster.

NameRequiredDescriptionDefault
principalNoOptional principal to list ACLs for, including its type, such as User:payments. Matched exactly and case-sensitively. Omit for every principal.
resource_nameNoOptional resource name, such as a topic or group. Returns every ACL the broker applies to that name: the exact name, prefixed ACLs whose prefix it starts with, and the * wildcard. Case-sensitive. Needs resource_type.
resource_typeNoOptional resource type: topic, group, cluster, transactional_id or delegation_token. Case-insensitive. Omit for every type. Required when resource_name is given.

Output Schema

NameRequiredDescription
aclsYes
countYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations exist, so the description carries the full burden and does so well: it discloses the DESCRIBE permission requirement on the cluster, the SECURITY_DISABLED failure mode meaning no authorizer is present, that results are sorted, and the semantic rule that a deny overrides any allow.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with what the tool does, then usage, then caveats. Mostly every sentence earns its place, though phrasing like 'so the answer covers what actually decides access' is slightly wordy for the value it adds.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return values need no explanation, and the description covers purpose, triggers, permission requirements, failure modes, and filter combination semantics. Nothing needed to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real meaning: it explains that resource_name matches exact, prefixed, and wildcard entries and requires resource_type, and that a deny overrides an allow. That goes beyond the field-level schema text.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (List) and resource (access control entries/ACLs) plus the exact fields each entry contains (principal, host, resource_type, resource_name, pattern_type, operation, permission). No sibling tool covers ACLs, so the agent can route to it unambiguously.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit trigger scenario (client fails with TOPIC_AUTHORIZATION_FAILED, GROUP_AUTHORIZATION_FAILED) and prescribes how to narrow: filter by the client's principal or by the refused resource. This is a concrete when-to-use with actionable filtering guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_clustersA

List the Kafka clusters this server serves, with whether each is reachable, whether it is configured read_only, and whether this session may write to it (writable). Connectivity is checked at call time. A cluster is a valid copy_message or produce_message destination_cluster only when writable is true. Broker and credential details are not exposed.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
countYes
clustersYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it discloses that connectivity is checked at call time (a live probe, not cached data) and that broker and credential details are deliberately withheld, which is a meaningful security/data-exposure boundary. It doesn't cover call failure modes or whether the result is ordered, keeping it from a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three tight sentences, front-loaded with what is listed, then the field semantics, then the gating rule and the disclosure boundary. Every sentence adds distinct information and none restates the name or title.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so the description needn't re-explain return values, and with zero parameters the surface is small. It covers the field meanings, the liveness caveat, and the credential-exposure guard; only edge behavior (e.g., what a session-scoped writable=false implies for retries) is left implicit.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so the baseline of 4 applies; there are no argument semantics that could be under-specified. The description correctly implies a no-argument, whole-server listing.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('List the Kafka clusters this server serves') and enumerates the per-cluster attributes returned (reachable, read_only, writable). It is clearly distinguishable from sibling tools like cluster_health, list_topics, and server_config, which handle health metrics, topics, and server configuration respectively.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives a concrete downstream usage rule: a cluster is only a valid copy_message or produce_message destination_cluster when writable is true, which tells the agent exactly why and when to call this first. It stops short of explicitly excluding alternatives (e.g., when to prefer cluster_health instead), so it lands at 4 rather than 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_consumer_groupsA

List the consumer groups on the cluster, with their state, member count and the topics they consume. Filter by exact topic or group state. Empty groups may still hold committed offsets and lag; they have no active consumers to drain it. Topic filtering may inspect every group because Kafka has no topic-to-group index.

NameRequiredDescriptionDefault
topicNoOptional topic name. When given, only groups that consume or have committed offsets for this topic are returned. Matched exactly and case-sensitively.
statesNoOptional group states to return, such as Stable, Empty, PreparingRebalance or Dead. Defaults to every state.

Output Schema

NameRequiredDescription
countYes
groupsYes

TDQS

A3.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden, and it delivers two non-obvious behaviors: empty groups can still retain committed offsets and lag with no active consumers to drain them, and topic filtering may scan every group because Kafka lacks a topic-to-group index. That performance caveat is genuinely useful. It stops short of 5 by omitting any auth, pagination, or cost/limit disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action and returned fields, then the filtering and caveats. Four sentences with no filler; the final clause is slightly dense but each sentence carries information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be re-explained, and the description supplies the scope, filter semantics, and the empty-group/scan caveats. What is missing is routing guidance relative to the several sibling consumer-group tools, which an agent would still have to infer.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so both parameters (topic, states) are already fully documented, including exact and case-sensitive matching. The description's 'filter by exact topic or group state' restates the schema, and the only added semantics concern the cost of topic filtering rather than the parameters themselves; baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('List the consumer groups on the cluster') and enumerates the returned fields (state, member count, topics consumed). It distinguishes itself from describe_consumer_group and consumer_lag only implicitly, via the word 'list' and cluster scope, so it falls short of an explicit sibling contrast.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description says which filters exist ('filter by exact topic or group state') but never says when to choose this tool over describe_consumer_group, consumer_lag, or delete_consumer_group, and gives no when-not guidance. Usage must be inferred from the name alone.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_topicsA

List the topics on the endpoint's cluster, sorted by name, each with its partition count, replication factor and the configs it sets for itself.

Filter with an optional JavaScript predicate. A name match is return topic.indexOf('orders') >= 0, and the predicate can also read partitions, replication_factor, internal and configs — questions a substring filter cannot express, such as which topics have more than six partitions, only one replica, or a compacted cleanup policy.

Only configs a topic sets for itself are reported, because inherited cluster defaults would make every topic look configured. Topics whose predicate throws are counted in script_errors rather than listed, so a broken filter is never mistaken for an empty cluster.

NameRequiredDescriptionDefault
scriptNoOptional JavaScript that decides whether a topic is listed. Return true to keep it. In scope: topic (the name), partitions (number), replication_factor (number), internal (true for Kafka's own topics such as __consumer_offsets) configs (an object of the values this topic sets for itself, such as configs['retention.ms']; inherited cluster defaults are not included) and size_bytes (one copy of the topic's log segments on disk, -1 when the brokers did not report it). Examples: return topic.indexOf('orders') >= 0; return partitions > 6; return configs['cleanup.policy'] === 'compact'; return replication_factor === 1 && !internal. Omit to list every topic.
timeout_secondsNoOptional wall-clock limit in seconds for evaluating the script. Defaults to 30, at most 3600. A topic whose evaluation is cut short is counted in script_errors rather than listed.

Output Schema

NameRequiredDescription
countYes
topicsYes
warningsNo
script_errorsNo

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the burden and does well: it discloses that topics whose predicate throws are tallied in script_errors rather than silently dropped, and explains why only self-set configs appear (inherited defaults would make every topic look configured). It omits any statement about read-only nature, permissions, or result size limits.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the action and result shape, then filter guidance, then the two edge-case guarantees. Every sentence carries information, though the config-scoping rationale is stated in the description while also being restated in the schema's parameter text.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return values need no explanation; the description still covers the two things an agent could get wrong — filter expressiveness and silent-failure behavior via script_errors. No meaningful gap remains for a read-style listing tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3; the description goes further by explaining the predicate's purpose, naming the readable fields (partitions, replication_factor, internal, configs), and clarifying the error-routing semantics of a throwing script. The timeout parameter is left to the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('List the topics on the endpoint's cluster') plus the exact shape of each row: name sort order, partition count, replication factor, and self-set configs. That is clearly distinguishable from siblings like describe_topic (single topic detail) or list_clusters (cluster inventory).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives concrete usage context by showing what the optional predicate is for and contrasting it with a substring filter ('questions a substring filter cannot express, such as which topics have more than six partitions'). It never explicitly excludes or routes to a sibling such as describe_topic, so it stops short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

open_transactionsA

Find open transactions holding back read_committed consumers on 1 to 1000 topics in one call through items. A consumer with isolation.level=read_committed cannot read past the first message of a transaction that has not been committed or aborted, so a transactional producer that hangs or dies mid-transaction stalls every such consumer on that partition. In consumer_lag this looks exactly like a poison message: members present, nothing consumed.

For each topic, blocked says whether any partition's last stable offset trails its high watermark. Each such partition reports both offsets, how many messages read_committed consumers cannot see, and every producer with an open transaction there: producer id and epoch, the offset the transaction started at, and where the coordinator knows it, the transactional id (which names the application), state, when it started, how long it has been open and its timeout, after which the broker aborts it.

The fix is in the producer, not the consumer: restart or fence the producing application, or wait for the timeout. Skipping offsets does not help. Results follow items order, each carrying index with result or error. Needs DESCRIBE on the topic and on transactional ids.

NameRequiredDescriptionDefault
itemsYesThe topics to check, 1 to 1000 of them. Checking one topic is an array of length one. Results follow this order and a missing topic is reported against its own item.

Output Schema

NameRequiredDescription
atomicYes
failedYes
appliedYes
resultsYes
succeededYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so well: it discloses required DESCRIBE permissions, per-item success/error behavior, exact output fields (last stable offset, high watermark, producer id/epoch, transactional id, state, timing, timeout), and broker timeout-abort behavior. It also warns that skipping offsets does not help, which is valuable operational context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core action and diagnostic scenario are front-loaded, and nearly every sentence adds operational value about symptoms, output, remediation, or permissions. It is dense and somewhat long for a single-parameter tool, but the length is largely justified by the complexity of the failure mode.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's diagnostic complexity, an output schema exists, and the description reinforces the key return concepts, permissions, and remediation guidance. An agent has enough information to select and invoke the tool correctly, including why a stalled consumer is not the cause.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents the single items parameter and exact topic matching. The description still adds useful batch semantics: 1 to 1000 topics, result ordering following items order, and per-item error reporting for missing topics.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource ('Find open transactions') and precisely scopes it to read_committed consumers across 1 to 1000 topics via items. It explicitly distinguishes the symptom from consumer_lag's poison-message appearance, so an agent can route correctly without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a clear diagnostic context (read_committed consumers stalled past the first message of an open transaction) and contrasts the presentation with consumer_lag. It also states the remediation path belongs to the producer, not the consumer. It stops short of an explicit 'when not to use' or a named alternative tool beyond the consumer_lag comparison.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

produce_messageA

Write 1 to 500 new messages in one call through items, or any number from an upload, to existing topics on this or another configured cluster. The caller supplies the key, value and headers, so unlike copy_message this can write content no producer ever sent. Writing one message is an items array of length one.

Every message carries provenance headers naming this tool, the time and the principal, so a fabricated message stays distinguishable from a genuine one.

upload sends a file on the user's own machine, one item per line as JSON, such as the file a transform_messages download saves. Over stdio the file is read at once; over HTTP the response is a link and a command to run there, once, as each run sends the file again. The file is never stored on the server, and the summary counts lines, shows a few examples and the failing lines by number. Without confirm the upload only checks the file.

Nothing is written unless confirm is true; one confirm covers the whole batch, and writing is not atomic because Kafka cannot retract a record produced before a later item failed. Results follow items order, each carrying index with result or error.

A produced message cannot be deleted: it stays until retention removes it, and any consumer reading the topic will process it. The destination must be writable and requires Kafka write permission; the endpoint you call may itself be read-only.

Omit partition unless the exact partition matters. The key decides placement, and naming a partition puts a keyed message where its key does not hash to, which breaks ordering for that key.

A topic whose consumers read Avro, Protobuf or JSON Schema needs value_schema: give the value as JSON and it is encoded to the destination registry's schema, refused with the offending field when it does not fit. A topic with a format configured in topic_formats is encoded to it without being asked. Check get_message or sample_messages first: a schema_id on the existing messages means the topic needs value_schema, and writing plain JSON there breaks its consumers.

NameRequiredDescriptionDefault
itemsNoThe messages to write, 1 to 500 of them. Writing one message is an array of length one. Two identical items mean two messages, which is allowed: appending the same payload twice is a real request. Give either items or upload, not both.
uploadNoOptional. Instead of items, send messages from a file on the user's own machine. Over stdio the file is read right away and the response carries its summary in file; over HTTP the response is a link and a command, e.g. curl -T, to run there, which prints the summary. Run an upload command once: each run sends the file again. The file holds one item per line as JSON, with the same fields as an entry of items, such as the file transform_messages download saves. It is read as it uploads and never stored on the server. Use it for more messages than items allows, or for a set saved and inspected first. Without confirm the upload writes nothing and its response counts the valid lines and reports the invalid ones by line number; call again with confirm to send. Give either items or upload, not both.
confirmNoOptional. When false or omitted, nothing is written and the response shows the messages that would be produced. Must be true to actually write them. One confirm covers the whole batch.

Output Schema

NameRequiredDescription
fileNo
atomicYes
failedYes
uploadNo
appliedYes
resultsYes
succeededYes

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so: nothing is written unless confirm is true, one confirm covers the whole batch, writes are non-atomic because Kafka cannot retract an earlier record, produced messages cannot be deleted until retention, the destination needs Kafka write permission while the called endpoint may be read-only, and upload files are read as they stream and never stored on the server. Provenance headers and the stdio-vs-HTTP upload difference are also disclosed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core capability and otherwise dense with useful facts, but it runs seven paragraphs and repeats the partition guidance and the items-vs-upload exclusion that already appear verbatim in the schema property descriptions.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a multi-mode, high-risk write tool with a rich output schema, it covers invocation modes, confirmation gating, ordering of results, schema encoding requirements, and failure reporting (failing lines by number). An agent has everything needed to call it correctly without trial and error.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline would be 3, but the description adds non-obvious semantics: the items/upload mutual exclusion, that confirm gates the entire batch, that value_schema is required when existing messages carry a schema_id, and the partition/key-ordering tradeoff. These are decision-relevant facts not derivable from the schema text alone.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb and resource plus capacity bounds: write 1 to 500 new messages via items, or any number from an upload, to existing topics on this or another cluster. It explicitly distinguishes itself from copy_message by noting the caller supplies key/value/headers so it can write content no producer ever sent.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit routing rules: use items vs upload (items for up to 500, upload for larger or pre-inspected sets), call get_message or sample_messages first to detect a schema_id, use value_schema when the topic's consumers read Avro/Protobuf/JSON Schema, and omit partition unless the exact partition matters. Exclusions and named alternatives are both present.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sample_messagesA

Sample the newest messages of 1 to 500 topics in one call through items, and summarize value formats, field paths, key usage, the schemas values were written with, raw value sizes (value_bytes min/p50/max), and sampled offset ranges. Use this to design a search_messages predicate, and to find the schema to produce against.

Avro, Protobuf and JSON Schema values carrying a Schema Registry id, and topics with a configured format, are decoded, so their field paths are reported like JSON. value_formats.undecodable counts values that named a schema but could not be decoded; decode_error on each message says why.

Results follow items order, each carrying index with result or error. Sampling one topic is an items array of length one. The sample describes recent data only, and keys must not be used to guess partitions.

NameRequiredDescriptionDefault
itemsYesThe topics to sample, 1 to 500 of them. Sampling one topic is an array of length one. Results follow this order and a topic that cannot be sampled is reported against its own item.

Output Schema

NameRequiredDescription
atomicYes
failedYes
appliedYes
resultsYes
succeededYes

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full behavioral burden. It discloses decoding of Avro, Protobuf and JSON Schema values, how undecodable values are counted, per-message decode_error meaning, ordered results/errors, and the recency limitation; it does not explicitly discuss permissions or read-only side effects, but the sampling nature is clear.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core purpose and follows with decoding semantics and result-order caveats. It is dense but every sentence contributes; only the long facet list in the first sentence slightly reduces crispness.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given an output schema and full input-schema coverage, the description supplies the missing behavioral context an agent needs: decoding/error semantics, recency limits, result ordering, and the key-handling caveat. No essential invocation guidance appears missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so baseline is 3. The description reinforces that items holds 1 to 500 topics and that sampling one topic is a length-one array, and it explains result order/error reporting, but it adds little beyond the schema's own descriptions for partitions, sample_size, and max_value_bytes.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb, resource, and batch scope: 'Sample the newest messages of 1 to 500 topics in one call through items.' It also enumerates the summary facets returned and distinguishes the tool from search_messages by casting it as a predicate-design and schema-discovery step.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names search_messages as the downstream use case and says to use this to 'find the schema to produce against,' giving clear usage context. It also warns that the sample is recent-only and keys must not be used to guess partitions, though it does not state explicit when-not conditions versus get_message or get_schema.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_messagesA

Search message key, value, headers or metadata with a JavaScript predicate. The script returns true for a match and receives value (parsed JSON, a decoded Avro/Protobuf/JSON Schema record, or text), key, headers, partition, offset, timestamp, value_bytes, key_bytes, format, schema_id and decode_error. Omit it to match all messages. Schema-encoded values are searched by field exactly like JSON.

group_by summarises instead of listing: an expression over the same variables returns a bucket name, and every match in the range is counted per bucket with one example address each. Use it to break a DLQ down by error header, find hot keys on a partition, or count messages per schema_id.

Every partition is read together, one chunk deep at a time, so a limited newest-first search returns the newest matches in the topic rather than the newest in whichever partition was read first. Kafka orders records only within a partition, so matches are merged by timestamp; producers set timestamps unless the topic uses LogAppendTime.

Kafka has no server-side search, so scans are bounded. Check complete, stopped_reason and scanned_ranges before treating no matches as conclusive. Use count_only for large result sets, and download to save them to a file on the user's machine without storing anything on the server: over stdio the file is saved at once, and over HTTP the response is a link and a command to run there, the search streaming every match when it is fetched. A script that runs past timeout_seconds is interrupted and counted in script_errors; scripts are not memory-sandboxed, so keep predicates simple.

NameRequiredDescriptionDefault
topicYesTopic to search. Matched exactly and case-sensitively.
scriptNoOptional JavaScript that decides whether a message matches. Return true to keep it. In scope: value (the parsed document for JSON, and the decoded record for Avro, Protobuf, JSON Schema or a configured format; the raw text otherwise), key (string, decoded document when the key has a schema, or null), headers (object of header name to string), partition, offset, timestamp (a Date), value_bytes and key_bytes (raw sizes, before decoding), format (json, avro, protobuf, json_schema, msgpack, text, binary or null), schema_id (number or null) and decode_error (string or null). Examples: return key === 'order-123'; return value.eventType === 'NEW' && value.payload.amount >= 500; return value.payload.cancelledAt === null. Omit to match every message.
downloadNoOptional. Instead of returning matches, save every match to a file on the user's own machine. Over stdio the file is saved right away and download.path names it. Over HTTP the response is a link and a command, e.g. curl, to run on the user's machine; the search runs when the link is fetched. Nothing is stored on the server. Use it instead of returning thousands of messages, or whenever the user wants the messages in a file.
group_byNoOptional JavaScript expression, with the same variables as script, that returns a bucket name for each matching message, e.g. return headers['error-reason'] or return key or return schema_id. Every match in the range is counted per bucket, like count_only, and no message bodies are returned. null and undefined form the bucket null. Cannot be combined with download.
directionNoOptional scan direction: newest_first (default) or oldest_first. Decides which matches are kept when max_matches cuts the search short. Every partition is read together, so newest_first means newest in the topic, ordered by timestamp, rather than newest in one partition.
to_offsetNoOptional exclusive offset to stop scanning at, applied to every searched partition.
count_onlyNoOptional. When true, scan the whole range and return only how many messages matched, with a per-partition breakdown and no message bodies. Use this first when a query may match a great many messages, then ask the user how they want them before fetching any.
max_groupsNoOptional number of largest buckets to return with group_by. Defaults to 20, at most 1000. groups_truncated says whether more existed.
partitionsNoOptional partitions to restrict the search to. Defaults to every partition. Do not guess a partition from a message key: producers may set the partition explicitly, so the key does not determine it.
from_offsetNoOptional inclusive offset to start scanning from, applied to every searched partition.
max_matchesNoOptional maximum number of matches to return. Defaults to 10, at most 1000. Use download to export more.
parallelismNoOptional number of concurrent readers, from 1 to 16. Defaults to 1. It splits a single-partition topic's offsets between readers, which makes a full scan of one large partition faster. A multi-partition topic is already read across its partitions together, so this does not apply there. Worth using for count_only, download or a full scan of one partition.
to_timestampNoOptional exclusive end time (RFC3339). Resolved to the first offset at or after this time; messages whose own timestamp is at or after it are skipped and not counted as scanned.
from_timestampNoOptional inclusive start time (RFC3339). Resolved to the first offset at or after this time; messages whose own timestamp is earlier are skipped and not counted as scanned.
max_value_bytesNoOptional maximum value bytes to return per match. Defaults to 512, at most 1048576. Longer values are cut and flagged with truncated=true.
timeout_secondsNoOptional wall-clock limit for the scan in seconds. Defaults to 30, at most 3600.
max_messages_scannedNoOptional maximum number of messages to read before giving up. Defaults to 10000, at most 100000000.

Output Schema

NameRequiredDescription
topicYes
groupsNo
matchesYes
completeYes
downloadNo
match_countYes
script_errorsNo
scanned_rangesYes
stopped_reasonYes
groups_truncatedNo
scanned_messagesYes
written_messagesNo
matches_by_partitionNo

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With zero annotations the description carries the full burden and does so well: it discloses that scans are bounded, per-partition reads are merged by timestamp, results have complete/stopped_reason/scanned_ranges to check, scripts past timeout_seconds are interrupted and counted in script_errors, and scripts are not memory-sandboxed. Download semantics are spelled out per transport (stdio writes the file immediately; HTTP returns a link and curl command, nothing stored server-side).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded and most paragraphs (bounded scans, partition merge ordering, DLQ/group_by examples) earn their place. However, the enumerated decoder variable list and the stdio-vs-HTTP download explanation duplicate the corresponding schema property descriptions almost verbatim, adding length without new information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 17-parameter, no-annotation tool with an output schema, the description covers the gaps that matter: what the script receives, how aggregation modes differ, scan bounds and the fields that signal them, and the transport difference for downloads. An agent has enough to call it correctly and to interpret truncated or empty results; return-value shape is left to the output schema, as permitted.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description adds real meaning on top by explaining that group_by 'summarises instead of listing' with one example address per bucket, that direction chooses which matches survive truncation, and that every partition is read together ('newest in the topic rather than newest in one partition'). The script variable list and download explanation largely restate the schema text, limiting the added value.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb, resource and mechanism ('Search message key, value, headers or metadata with a JavaScript predicate') and then delimits the two output modes (listing vs. group_by summarisation). An agent can tell what it does without opening the schema. It stops short of naming a sibling (e.g. sample_messages or get_message) as the alternative, so it is clear but not sibling-differentiated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives concrete when-to-use scenarios ('break a DLQ down by error header, find hot keys on a partition, or count messages per schema_id') and workflow advice ('Use count_only for large result sets', 'Use this first when a query may match a great many messages, then ask the user'). It also warns how to interpret results ('Check complete, stopped_reason and scanned_ranges before treating no matches as conclusive'). No explicit when-NOT-to-use or named alternative tool, which keeps it off the top score.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

server_configA

Report this endpoint's name, path, purpose, cluster, brokers, authentication, TLS, read-only policy, the other clusters it may write to (destinations), Schema Registry, configured topic formats and exposed tools. Use it to confirm the target and permissions before acting; several endpoints may target one cluster with different policies. Passwords are never returned.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
tlsYes
noteYes
pathYes
toolsYes
brokersYes
clusterYes
endpointYes
read_onlyYes
sasl_userNo
config_fileNo
descriptionNo
destinationsYes
http_addressNo
sasl_optionsNo
topic_formatsNo
authenticationYes
schema_registryNo

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the burden and does so well: it discloses that this is a read-only reporting call, that read-only policy is itself part of the payload, and that "Passwords are never returned" — a meaningful security guarantee an agent could not infer from the schema. It omits any statement about side effects or cost/latency, which keeps it from a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the action and the returned fields, then pivots to usage guidance and the security caveat. The field enumeration is long but each item is a distinct capability an agent may need to know exists, so little is wasted — though the run-on list could be tightened.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return shape needs no explanation. For a zero-parameter read tool, the description covers what is reported, when to call it, the multi-endpoint-per-cluster nuance, and the password redaction guarantee — nothing an agent needs in order to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters and the schema is an empty object, so there is no parameter semantics to explain and no schema gap to compensate for. Baseline 4 applies; the description correctly spends no words on parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ("Report") and then enumerates the exact resource fields returned: endpoint name, path, cluster, brokers, auth, TLS, read-only policy, destinations, Schema Registry, topic formats and exposed tools. An agent can immediately tell this is an endpoint self-description tool rather than a cluster lister or topic inspector, which separates it from siblings like list_clusters.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit timing guidance: "Use it to confirm the target and permissions before acting," plus a non-obvious caveat that several endpoints may target one cluster with different policies. It stops short of naming a specific alternative or a when-not-to-use condition, so it is strong but not a full routing statement.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

transform_messagesA

Rewrite messages with a JavaScript transform and write the results as new messages: to the same topic, another topic, or a topic on another configured cluster. Use it to repair messages produced in the wrong shape, for example a document wrapped in a payload field: transform "return { value: value.payload }".

The transform is JavaScript run once per message. It sees the same variables as search_messages and returns an object with any of value, key and headers; what it leaves out is kept from the original, and null skips the message. An integer beyond 2^53 arrives as a BigInt so ids are written back exactly. An optional script selects which messages to transform. Schema-encoded values (Avro, Protobuf, JSON Schema) are decoded for the transform and re-encoded with the same schema id; set value_schema to encode to a different schema or registry.

The originals are never changed or deleted: Kafka cannot edit a message, so the new messages are appended. Writing back to the source topic stops at the end offset the call started with, so it never reads its own output. Every new message carries provenance headers naming the source topic, partition and offset.

Nothing is written to Kafka unless confirm is true. Without it the response counts what would be written, shows before/after examples and groups failures by reason with example addresses. download saves the new messages on the user's machine instead, as produce_message items, one per line, so the set can be inspected and later sent with produce_message upload: over stdio at once, over HTTP through a link and command the transform runs behind when fetched. Nothing is stored on the server, and downloading works on a read-only cluster.

Partitions are processed in ascending order and writing is not atomic: messages written before a timeout or failure stay. complete, stopped_reason and scanned_ranges say how far the run got; resume with partitions and from_offset.

NameRequiredDescriptionDefault
topicYesTopic to read the messages from. Matched exactly and case-sensitively.
scriptNoOptional JavaScript predicate, as in search_messages, choosing which messages to transform. Return true to transform the message. Omit to transform every message in the range.
confirmNoOptional. When false or omitted, nothing is written to Kafka: the response counts what would be written, shows examples and groups the failures. Must be true to produce the new messages.
downloadNoOptional. Instead of writing to Kafka, save every new message to a file on the user's own machine, one produce_message item per line, so the set can be inspected and later sent with produce_message upload. Over stdio the file is saved right away and download.path names it; over HTTP the response is a link and a command, e.g. curl, to run on the user's machine, and the transform runs when it is fetched. Nothing is stored on the server and nothing is written to Kafka, so it works on a read-only cluster; it cannot be combined with confirm.
to_offsetNoOptional exclusive offset to stop at, applied to every partition read.
transformYesJavaScript that returns the new message, with the same variables as search_messages' script: value (parsed JSON or the decoded Avro/Protobuf/JSON Schema record; text otherwise), key, headers, partition, offset, timestamp, value_bytes, key_bytes, format, schema_id and decode_error. Return an object with any of value, key and headers; a field left out is kept from the original. Return null to skip the message. Example: return { value: value.payload }. A string value is written as its text, any other value as JSON, and a schema-encoded or topic_formats topic is re-encoded to its schema. Returning headers replaces all of them; spread the originals to keep them and set one to null to remove it: { headers: { ...headers, 'error-reason': null } }. Standard JavaScript (ES2020: arrow functions, spread, destructuring, regular expressions, JSON, Math, Date) is available; console, require, timers and network are not. An integer beyond 2^53 arrives as a BigInt so it is written back exactly: pass it through, compare it with a BigInt literal such as 123n, or use String(); turning it into a Number rounds it, and the message is refused rather than written.
key_schemaNoOptional. Like value_schema, for the key.
partitionsNoOptional partitions to read. Defaults to every partition. Partitions are processed one after another in ascending order.
from_offsetNoOptional inclusive offset to start at, applied to every partition read. Use it with partitions to resume a run that stopped.
max_messagesNoOptional maximum number of messages to transform, counting only those the script selects. Defaults to 100000, at most 10000000. complete is false when the limit stops the run.
to_timestampNoOptional exclusive end time (RFC3339). Messages whose own timestamp is at or after it are skipped and not counted as scanned.
value_schemaNoOptional. Encode every new value, as JSON, to this Schema Registry schema on the destination cluster. Needed when a schema-encoded value moves to a cluster with a different registry, or to change the schema. An empty object {} uses the latest version of <destination_topic>-value. Without it a value read with a schema id is written with that same id.
from_timestampNoOptional inclusive start time (RFC3339). Messages whose own timestamp is earlier are skipped and not counted as scanned.
keep_partitionNoOptional. Write each message to the partition number it was read from, instead of letting its key decide. The destination needs at least as many partitions. Use it when the source producer chose partitions explicitly.
max_value_bytesNoOptional maximum number of value bytes to show in each example. Defaults to 4096. Affects the response only.
timeout_secondsNoOptional wall-clock limit in seconds. Defaults to 300, at most 3600. Messages already written when it fires stay written; scanned_ranges says how far each partition got.
destination_topicNoOptional topic to write the new messages to. Defaults to the source topic, where the new messages are appended after the originals; the originals stay. It must already exist.
destination_clusterNoOptional cluster to write to. Defaults to the cluster this endpoint serves. Use list_clusters to see which names are valid. It must be writable when confirm is true.

Output Schema

NameRequiredDescription
noteNo
topicYes
errorsYes
failedYes
appliedYes
matchedYes
scannedYes
skippedYes
writtenYes
completeYes
downloadNo
examplesYes
warningsNo
transformedYes
file_writtenNo
scanned_rangesYes
source_clusterYes
stopped_reasonYes
errors_truncatedNo
destination_topicYes
destination_clusterYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden and does so thoroughly: originals are never changed, writes are appended, confirm gates all writes, downloading works on a read-only cluster, and partial writes remain after timeout or failure. It also discloses provenance headers, BigInt handling, schema re-encoding behavior, and resume semantics.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but appropriately so for an 18-parameter tool with complex behavior. It is front-loaded with the core action and then organized by transform semantics, safety, download mode, and partial-run behavior with little wasted text.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity and the existence of an output schema, the description covers everything an agent needs to call it correctly and interpret the main behavioral risks. It explains transformation semantics, write safety, schema handling, download mode, and non-atomic partial execution.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents every parameter. The description still adds meaningful cross-parameter semantics, especially for transform return values, BigInt handling, script selection, confirm versus download, and value_schema encoding behavior.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: rewrite messages with a JavaScript transform and write the results as new messages. It distinguishes the tool's job from simple copying or producing by explaining the repair use case and the three possible destinations.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It clearly says when to use the tool (repair messages produced in the wrong shape) and when to use download instead of direct writes. It does not explicitly compare against siblings like copy_message or produce_message, but the core usage context and safety conditions are clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 23 tool updatesv0.2.7
    • Changedadd_partitions1 field changed
      • changedInput schema / properties / items / description
        Previous value: -"The topics to change, 1 to 100 of them. Changing one topic is an array of length one. Duplicate topic names are refused before anything changes."New value: +"The topics to change, 1 to 1000 of them. Changing one topic is an array of length one. Duplicate topic names are refused before anything changes."
    • Changedalter_topic_config1 field changed
      • changedInput schema / properties / items / description
        Previous value: -"The topics to change, 1 to 100 of them. Changing one topic is an array of length one. Duplicate topic names are refused before anything changes."New value: +"The topics to change, 1 to 1000 of them. Changing one topic is an array of length one. Duplicate topic names are refused before anything changes."
    • Changedcluster_health5 fields changed
      • addedOutput schema / properties / problems / items / properties / adding_replicas
        Added value: +{
        +  "items": {
        +    "maximum": 2147483647,
        +    "minimum": -2147483648,
        +    "type": "integer"
        +  },
        +  "type": [
        +    "null",
        +    "array"
        +  ]
        +}
      • addedOutput schema / properties / problems / items / properties / reassigning
        Added value: +{
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / problems / items / properties / removing_replicas
        Added value: +{
        +  "items": {
        +    "maximum": 2147483647,
        +    "minimum": -2147483648,
        +    "type": "integer"
        +  },
        +  "type": [
        +    "null",
        +    "array"
        +  ]
        +}
      • addedOutput schema / properties / summary / properties / reassigning
        Added value: +{
        +  "type": "integer"
        +}
      • changedOutput schema / properties / summary / required
        Previous value: -[
        -  "topics",
        -  "partitions",
        -  "offline",
        -  "under_replicated",
        -  "under_min_isr",
        -  "errored"
        -]New value: +[
        +  "topics",
        +  "partitions",
        +  "offline",
        +  "under_replicated",
        +  "under_min_isr",
        +  "errored",
        +  "reassigning"
        +]
    • Changedcommit_offset1 field changed
      • changedInput schema / properties / items / description
        Previous value: -"The moves to make, 1 to 100 of them. Moving one offset is an array of length one. Two items that would move the same group, topic and partition, including a whole-topic item and a partition item, are refused before anything changes."New value: +"The moves to make, 1 to 1000 of them. Moving one offset is an array of length one. Two items that would move the same group, topic and partition, including a whole-topic item and a partition item, are refused before anything changes."
    • Changedcompare_clusters1 field changed
      • changedInput schema / properties / items / description
        Previous value: -"The clusters to compare against, 1 to 100 of them. Comparing one cluster is an array of length one. Results follow this order and an unreachable cluster is reported against its own item."New value: +"The clusters to compare against, 1 to 1000 of them. Comparing one cluster is an array of length one. Results follow this order and an unreachable cluster is reported against its own item."
    • Changedconsumer_lag14 fields changed
      • changedInput schema / properties / items / description
        Previous value: -"The measurements to take, 1 to 100 of them. Measuring one topic is an array of length one. Sampling windows run concurrently, so several measurements do not add their wait times together."New value: +"The measurements to take, 1 to 1000 of them. Measuring one topic is an array of length one. Up to 4 sampling windows run at a time, so the call takes about sample_seconds for every 4 measurements."
      • addedInput schema / properties / items / items / properties / measure_backlog_age
        Added value: +{
        +  "description": "Optional. When true, read the next unconsumed message of every lagging partition to report how old the backlog is (committed_timestamp, lag_seconds, oldest_unconsumed_at) and whether it is at risk of being deleted by retention before it is consumed (retention_risk). Costs one fetch per lagging partition.",
        +  "type": "boolean"
        +}
      • changedInput schema / properties / items / items / properties / sample_seconds / description
        Previous value: -"Optional number of seconds to sample the consume rate over. Defaults to 5. The call blocks for this long, because Kafka stores no history of past commits and the rate can only be measured by comparing two readings."New value: +"Optional number of seconds to sample the consume rate over. Defaults to 5, at most 300. The call blocks for this long, because Kafka stores no history of past commits and the rate can only be measured by comparing two readings."
      • addedOutput schema / properties / results / items / properties / result / properties / groups / items / properties / oldest_unconsumed_at
        Added value: +{
        +  "type": [
        +    "null",
        +    "string"
        +  ]
        +}
      • addedOutput schema / properties / results / items / properties / result / properties / groups / items / properties / partitions / items / properties / committed_timestamp
        Added value: +{
        +  "type": [
        +    "null",
        +    "string"
        +  ]
        +}
      • addedOutput schema / properties / results / items / properties / result / properties / groups / items / properties / partitions / items / properties / lag_seconds
        Added value: +{
        +  "type": [
        +    "null",
        +    "number"
        +  ]
        +}
      • addedOutput schema / properties / results / items / properties / result / properties / groups / items / properties / partitions / items / properties / offset_expired
        Added value: +{
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / results / items / properties / result / properties / groups / items / properties / partitions / items / properties / start_offset
        Added value: +{
        +  "type": "integer"
        +}
      • changedOutput schema / properties / results / items / properties / result / properties / groups / items / properties / partitions / items / required
        Previous value: -[
        -  "partition",
        -  "committed_offset",
        -  "end_offset",
        -  "lag"
        -]New value: +[
        +  "partition",
        +  "committed_offset",
        +  "start_offset",
        +  "end_offset",
        +  "lag"
        +]
      • addedOutput schema / properties / results / items / properties / result / properties / groups / items / properties / retention_risk
        Added value: +{
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / results / items / properties / result / properties / groups / items / properties / warnings
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": [
        +    "null",
        +    "array"
        +  ]
        +}
      • addedOutput schema / properties / results / items / properties / result / properties / produce_rate / properties / partitions
        Added value: +{
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "last_hour": {
        +        "type": "integer"
        +      },
        +      "last_minute": {
        +        "type": "integer"
        +      },
        +      "partition": {
        +        "maximum": 2147483647,
        +        "minimum": -2147483648,
        +        "type": "integer"
        +      }
        +    },
        +    "required": [
        +      "partition",
        +      "last_minute",
        +      "last_hour"
        +    ],
        +    "type": "object"
        +  },
        +  "type": [
        +    "null",
        +    "array"
        +  ]
        +}
      • changedOutput schema / properties / results / items / properties / result / properties / produce_rate / required
        Previous value: -[
        -  "last_second",
        -  "last_minute",
        -  "last_hour",
        -  "note"
        -]New value: +[
        +  "last_second",
        +  "last_minute",
        +  "last_hour",
        +  "partitions",
        +  "note"
        +]
      • addedOutput schema / properties / results / items / properties / result / properties / retention_ms
        Added value: +{
        +  "type": [
        +    "null",
        +    "integer"
        +  ]
        +}
    • Changedcopy_message2 fields changed
      • changedInput schema / properties / items / description
        Previous value: -"The copies to make, 1 to 20 of them. Copying one message is an array of length one. Two items naming the same source and destination are refused before anything is written."New value: +"The copies to make, 1 to 500 of them. Copying one message is an array of length one. Two items naming the same source and destination are refused before anything is written."
      • changedOutput schema / properties / results / items / properties / result / required
        Previous value: -[
        -  "source_cluster",
        -  "destination_cluster",
        -  "source_topic",
        -  "source_partition",
        -  "source_offset",
        -  "destination_topic",
        -  "message",
        -  "applied"
        -]New value: +[
        +  "source_cluster",
        +  "destination_cluster",
        +  "source_topic",
        +  "source_partition",
        +  "source_offset",
        +  "destination_topic",
        +  "message",
        +  "applied",
        +  "written_partition",
        +  "written_offset"
        +]
    • Changedcreate_topic1 field changed
      • changedInput schema / properties / items / description
        Previous value: -"The topics to create, 1 to 100 of them. Creating one topic is an array of length one. Duplicate topic names are refused before anything is created."New value: +"The topics to create, 1 to 1000 of them. Creating one topic is an array of length one. Duplicate topic names are refused before anything is created."
    • Changeddelete_consumer_group1 field changed
      • changedInput schema / properties / items / description
        Previous value: -"The groups to delete, 1 to 100 of them. Deleting one group is an array of length one. Naming the same group twice is refused before anything is deleted."New value: +"The groups to delete, 1 to 1000 of them. Deleting one group is an array of length one. Naming the same group twice is refused before anything is deleted."
    • Changeddelete_records8 fields changed
      • changedInput schema / properties / items / description
        Previous value: -"The partitions to truncate, 1 to 100 of them. Truncating one partition is an array of length one. Two items naming the same topic and partition are refused before anything is deleted."New value: +"The partitions to truncate, 1 to 1000 of them. Truncating one partition is an array of length one. Two items naming the same topic and partition are refused before anything is deleted."
      • addedInput schema / properties / items / items / properties / all_partitions
        Added value: +{
        +  "description": "Optional. With before_timestamp, cut every partition of the topic at that moment; each partition gets its own resolved offset, reported in partitions. Partitions with nothing older are left alone.",
        +  "type": "boolean"
        +}
      • changedInput schema / properties / items / items / properties / before_offset / description
        Previous value: -"Every message with an offset lower than this is deleted; the message at before_offset is kept and becomes the first readable one. Must be greater than the partition's current start offset and at most its end offset. Use the end offset to empty the partition."New value: +"Every message with an offset lower than this is deleted; the message at before_offset is kept and becomes the first readable one. Must be greater than the partition's current start offset and at most its end offset. Use the end offset to empty the partition. Give before_offset or before_timestamp, not both."
      • addedInput schema / properties / items / items / properties / before_timestamp
        Added value: +{
        +  "description": "Optional RFC3339 time. Every message written before it is deleted: the cut is resolved per partition to the first offset at or after this time, and the preview shows the offset it resolved to. Give before_offset or before_timestamp, not both.",
        +  "type": [
        +    "null",
        +    "string"
        +  ]
        +}
      • changedInput schema / properties / items / items / properties / partition / description
        Previous value: -"Partition to delete records from."New value: +"Partition to delete records from. Ignored with all_partitions."
      • changedInput schema / properties / items / items / required
        Previous value: -[
        -  "topic",
        -  "partition",
        -  "before_offset"
        -]New value: +[
        +  "topic"
        +]
      • addedOutput schema / properties / results / items / properties / result / properties / all_partitions
        Added value: +{
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / results / items / properties / result / properties / partitions
        Added value: +{
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "affected_groups": {
        +        "items": {
        +          "additionalProperties": false,
        +          "properties": {
        +            "committed_offset": {
        +              "type": "integer"
        +            },
        +            "group": {
        +              "type": "string"
        +            },
        +            "unprocessed_lost": {
        +              "type": "integer"
        +            }
        +          },
        +          "required": [
        +            "group",
        +            "committed_offset",
        +            "unprocessed_lost"
        +          ],
        +          "type": "object"
        +        },
        +        "type": [
        +          "null",
        +          "array"
        +        ]
        +      },
        +      "before_offset": {
        +        "type": "integer"
        +      },
        +      "end_offset": {
        +        "type": "integer"
        +      },
        +      "messages_deleted": {
        +        "type": "integer"
        +      },
        +      "partition": {
        +        "maximum": 2147483647,
        +        "minimum": -2147483648,
        +        "type": "integer"
        +      },
        +      "resulting_start_offset": {
        +        "type": "integer"
        +      },
        +      "start_offset": {
        +        "type": "integer"
        +      }
        +    },
        +    "required": [
        +      "partition",
        +      "start_offset",
        +      "end_offset",
        +      "before_offset",
        +      "messages_deleted",
        +      "affected_groups"
        +    ],
        +    "type": "object"
        +  },
        +  "type": [
        +    "null",
        +    "array"
        +  ]
        +}
    • Changeddelete_topic1 field changed
      • changedInput schema / properties / items / description
        Previous value: -"The topics to delete, 1 to 100 of them. Deleting one topic is an array of length one. Naming the same topic twice is refused before anything is deleted."New value: +"The topics to delete, 1 to 1000 of them. Deleting one topic is an array of length one. Naming the same topic twice is refused before anything is deleted."
    • Changeddescribe_consumer_group3 fields changed
      • changedInput schema / properties / items / description
        Previous value: -"The groups to describe, 1 to 100 of them. Describing one group is an array of length one. Results follow this order and a group that does not exist is reported against its own item."New value: +"The groups to describe, 1 to 1000 of them. Describing one group is an array of length one. Results follow this order and a group that does not exist is reported against its own item."
      • addedInput schema / properties / items / items / properties / sample_seconds
        Added value: +{
        +  "description": "Optional number of seconds, at most 60, to watch the group before describing it. The group is read every second; observation then reports every state it passed through, and the members that joined or left. Use it when a group seems to keep rebalancing. The call blocks this long. Defaults to 0, no observation.",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / results / items / properties / result / properties / observation
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "joined": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "assignments": {
        +            "items": {
        +              "additionalProperties": false,
        +              "properties": {
        +                "partitions": {
        +                  "items": {
        +                    "maximum": 2147483647,
        +                    "minimum": -2147483648,
        +                    "type": "integer"
        +                  },
        +                  "type": [
        +                    "null",
        +                    "array"
        +                  ]
        +                },
        +                "topic": {
        +                  "type": "string"
        +                }
        +              },
        +              "required": [
        +                "topic",
        +                "partitions"
        +              ],
        +              "type": "object"
        +            },
        +            "type": [
        +              "null",
        +              "array"
        +            ]
        +          },
        +          "client_id": {
        +            "type": "string"
        +          },
        +          "host": {
        +            "type": "string"
        +          },
        +          "instance_id": {
        +            "type": "string"
        +          },
        +          "member_id": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "member_id",
        +          "client_id",
        +          "host",
        +          "assignments"
        +        ],
        +        "type": "object"
        +      },
        +      "type": [
        +        "null",
        +        "array"
        +      ]
        +    },
        +    "left": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "assignments": {
        +            "items": {
        +              "additionalProperties": false,
        +              "properties": {
        +                "partitions": {
        +                  "items": {
        +                    "maximum": 2147483647,
        +                    "minimum": -2147483648,
        +                    "type": "integer"
        +                  },
        +                  "type": [
        +                    "null",
        +                    "array"
        +                  ]
        +                },
        +                "topic": {
        +                  "type": "string"
        +                }
        +              },
        +              "required": [
        +                "topic",
        +                "partitions"
        +              ],
        +              "type": "object"
        +            },
        +            "type": [
        +              "null",
        +              "array"
        +            ]
        +          },
        +          "client_id": {
        +            "type": "string"
        +          },
        +          "host": {
        +            "type": "string"
        +          },
        +          "instance_id": {
        +            "type": "string"
        +          },
        +          "member_id": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "member_id",
        +          "client_id",
        +          "host",
        +          "assignments"
        +        ],
        +        "type": "object"
        +      },
        +      "type": [
        +        "null",
        +        "array"
        +      ]
        +    },
        +    "samples": {
        +      "type": "integer"
        +    },
        +    "seconds": {
        +      "type": "integer"
        +    },
        +    "states": {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": [
        +        "null",
        +        "array"
        +      ]
        +    },
        +    "unstable": {
        +      "type": "boolean"
        +    }
        +  },
        +  "required": [
        +    "seconds",
        +    "samples",
        +    "states",
        +    "joined",
        +    "left",
        +    "unstable"
        +  ],
        +  "type": [
        +    "null",
        +    "object"
        +  ]
        +}
    • Changeddescribe_topic5 fields changed
      • changedInput schema / properties / items / description
        Previous value: -"The topics to describe, 1 to 20 of them. Describing one topic is an array of length one. Results follow this order and a missing topic is reported against its own item."New value: +"The topics to describe, 1 to 500 of them. Describing one topic is an array of length one. Results follow this order and a missing topic is reported against its own item."
      • addedOutput schema / properties / results / items / properties / result / properties / partitions / items / properties / size_bytes
        Added value: +{
        +  "type": [
        +    "null",
        +    "integer"
        +  ]
        +}
      • addedOutput schema / properties / results / items / properties / result / properties / replicated_size_bytes
        Added value: +{
        +  "type": [
        +    "null",
        +    "integer"
        +  ]
        +}
      • addedOutput schema / properties / results / items / properties / result / properties / size_bytes
        Added value: +{
        +  "type": [
        +    "null",
        +    "integer"
        +  ]
        +}
      • addedOutput schema / properties / results / items / properties / result / properties / warnings
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": [
        +    "null",
        +    "array"
        +  ]
        +}
    • Changedget_message3 fields changed
      • changedInput schema / properties / items / description
        Previous value: -"The message addresses to read, 1 to 20 of them. Reading one message is an array of length one. Results follow this order and an address that cannot be read is reported against its own item."New value: +"The message addresses to read, 1 to 500 of them. Reading one message is an array of length one. Results follow this order and an address that cannot be read is reported against its own item."
      • changedInput schema / properties / items / items / properties / context / description
        Previous value: -"Optional number of messages to also return either side of this offset. Defaults to 0. Clamped to what the partition holds."New value: +"Optional number of messages to also return either side of this offset. Defaults to 0, at most 100. Clamped to what the partition holds."
      • changedInput schema / properties / items / items / properties / max_value_bytes / description
        Previous value: -"Optional maximum number of value bytes to return per message. Defaults to 4096. Values longer than this are cut and flagged with truncated=true."New value: +"Optional maximum number of value bytes to return per message. Defaults to 4096, at most 1048576. Values longer than this are cut and flagged with truncated=true."
    • Changedget_schema4 fields changed
      • changedInput schema / properties / items / description
        Previous value: -"The schemas to look up, 1 to 100 of them. Looking up one schema is an array of length one. Results follow this order and a subject or id that does not exist is reported against its own item."New value: +"The schemas to look up, 1 to 1000 of them. Looking up one schema is an array of length one. Results follow this order and a subject or id that does not exist is reported against its own item."
      • addedInput schema / properties / items / items / properties / check_schema
        Added value: +{
        +  "additionalProperties": false,
        +  "description": "Optional candidate schema to test against the subject's latest version under the subject's compatibility level, without registering it. Needs subject. Returns check.compatible and the registry's reasons in check.messages.",
        +  "properties": {
        +    "schema": {
        +      "description": "The candidate schema text.",
        +      "type": "string"
        +    },
        +    "type": {
        +      "description": "Optional: avro (default), protobuf or json_schema.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "schema"
        +  ],
        +  "type": [
        +    "null",
        +    "object"
        +  ]
        +}
      • addedOutput schema / properties / results / items / properties / result / properties / check
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "compatible": {
        +      "type": "boolean"
        +    },
        +    "messages": {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": [
        +        "null",
        +        "array"
        +      ]
        +    }
        +  },
        +  "required": [
        +    "compatible",
        +    "messages"
        +  ],
        +  "type": [
        +    "null",
        +    "object"
        +  ]
        +}
      • addedOutput schema / properties / results / items / properties / result / properties / compatibility
        Added value: +{
        +  "type": "string"
        +}
    • Changedlist_clusters2 fields changed
      • addedOutput schema / properties / clusters / items / properties / writable
        Added value: +{
        +  "type": "boolean"
        +}
      • changedOutput schema / properties / clusters / items / required
        Previous value: -[
        -  "name",
        -  "connected",
        -  "read_only"
        -]New value: +[
        +  "name",
        +  "connected",
        +  "read_only",
        +  "writable"
        +]
    • Changedlist_topics5 fields changed
      • changedInput schema / properties / script / description
        Previous value: -"Optional JavaScript that decides whether a topic is listed. Return true to keep it. In scope: topic (the name), partitions (number), replication_factor (number), internal (true for Kafka's own topics such as __consumer_offsets) and configs (an object of the values this topic sets for itself, such as configs['retention.ms']; inherited cluster defaults are not included). Examples: return topic.indexOf('orders') >= 0; return partitions > 6; return configs['cleanup.policy'] === 'compact'; return replication_factor === 1 && !internal. Omit to list every topic."New value: +"Optional JavaScript that decides whether a topic is listed. Return true to keep it. In scope: topic (the name), partitions (number), replication_factor (number), internal (true for Kafka's own topics such as __consumer_offsets) configs (an object of the values this topic sets for itself, such as configs['retention.ms']; inherited cluster defaults are not included) and size_bytes (one copy of the topic's log segments on disk, -1 when the brokers did not report it). Examples: return topic.indexOf('orders') >= 0; return partitions > 6; return configs['cleanup.policy'] === 'compact'; return replication_factor === 1 && !internal. Omit to list every topic."
      • changedInput schema / properties / timeout_seconds / description
        Previous value: -"Optional wall-clock limit in seconds for evaluating the script. Defaults to 30. A topic whose evaluation is cut short is counted in script_errors rather than listed."New value: +"Optional wall-clock limit in seconds for evaluating the script. Defaults to 30, at most 3600. A topic whose evaluation is cut short is counted in script_errors rather than listed."
      • addedOutput schema / properties / topics / items / properties / size_bytes
        Added value: +{
        +  "type": "integer"
        +}
      • changedOutput schema / properties / topics / items / required
        Previous value: -[
        -  "topic",
        -  "partitions",
        -  "replication_factor"
        -]New value: +[
        +  "topic",
        +  "partitions",
        +  "replication_factor",
        +  "size_bytes"
        +]
      • addedOutput schema / properties / warnings
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": [
        +    "null",
        +    "array"
        +  ]
        +}
    • Changedopen_transactions1 field changed
      • changedInput schema / properties / items / description
        Previous value: -"The topics to check, 1 to 100 of them. Checking one topic is an array of length one. Results follow this order and a missing topic is reported against its own item."New value: +"The topics to check, 1 to 1000 of them. Checking one topic is an array of length one. Results follow this order and a missing topic is reported against its own item."
    • Changedproduce_message5 fields changed
      • changedInput schema / properties / items / description
        Previous value: -"The messages to write, 1 to 20 of them. Writing one message is an array of length one. Two identical items mean two messages, which is allowed: appending the same payload twice is a real request."New value: +"The messages to write, 1 to 500 of them. Writing one message is an array of length one. Two identical items mean two messages, which is allowed: appending the same payload twice is a real request. Give either items or upload, not both."
      • addedInput schema / properties / upload
        Added value: +{
        +  "additionalProperties": false,
        +  "description": "Optional. Instead of items, send messages from a file on the user's own machine. Over stdio the file is read right away and the response carries its summary in file; over HTTP the response is a link and a command, e.g. curl -T, to run there, which prints the summary. Run an upload command once: each run sends the file again. The file holds one item per line as JSON, with the same fields as an entry of items, such as the file transform_messages download saves. It is read as it uploads and never stored on the server. Use it for more messages than items allows, or for a set saved and inspected first. Without confirm the upload writes nothing and its response counts the valid lines and reports the invalid ones by line number; call again with confirm to send. Give either items or upload, not both.",
        +  "properties": {
        +    "filename": {
        +      "description": "Optional name of the file on the user's machine. Over stdio it may be a path, relative to the server's working directory or absolute; over HTTP it is a plain file name used in the command. Defaults to messages.jsonl.",
        +      "type": "string"
        +    }
        +  },
        +  "type": [
        +    "null",
        +    "object"
        +  ]
        +}
      • removedInput schema / required
        Removed value: -[
        -  "items"
        -]
      • addedOutput schema / properties / file
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "applied": {
        +      "type": "boolean"
        +    },
        +    "confirm": {
        +      "type": "boolean"
        +    },
        +    "errors": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "error": {
        +            "type": "string"
        +          },
        +          "line": {
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "line",
        +          "error"
        +        ],
        +        "type": "object"
        +      },
        +      "type": [
        +        "null",
        +        "array"
        +      ]
        +    },
        +    "errors_truncated": {
        +      "type": "boolean"
        +    },
        +    "examples": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "applied": {
        +            "type": "boolean"
        +          },
        +          "destination_cluster": {
        +            "type": "string"
        +          },
        +          "key_encoding": {
        +            "additionalProperties": false,
        +            "properties": {
        +              "format": {
        +                "type": "string"
        +              },
        +              "message_type": {
        +                "type": "string"
        +              },
        +              "schema_id": {
        +                "type": "integer"
        +              },
        +              "subject": {
        +                "type": "string"
        +              },
        +              "version": {
        +                "type": "integer"
        +              }
        +            },
        +            "required": [
        +              "format"
        +            ],
        +            "type": [
        +              "null",
        +              "object"
        +            ]
        +          },
        +          "message": {
        +            "additionalProperties": false,
        +            "properties": {
        +              "decode_error": {
        +                "type": "string"
        +              },
        +              "encoding": {
        +                "type": "string"
        +              },
        +              "format": {
        +                "type": "string"
        +              },
        +              "headers": {
        +                "additionalProperties": {
        +                  "type": "string"
        +                },
        +                "type": "object"
        +              },
        +              "key": {
        +                "type": "string"
        +              },
        +              "key_format": {
        +                "type": "string"
        +              },
        +              "key_schema_id": {
        +                "type": "integer"
        +              },
        +              "message_type": {
        +                "type": "string"
        +              },
        +              "offset": {
        +                "type": "integer"
        +              },
        +              "partition": {
        +                "maximum": 2147483647,
        +                "minimum": -2147483648,
        +                "type": "integer"
        +              },
        +              "schema_id": {
        +                "type": "integer"
        +              },
        +              "timestamp": {
        +                "type": "string"
        +              },
        +              "truncated": {
        +                "type": "boolean"
        +              },
        +              "value": {
        +                "type": "string"
        +              },
        +              "value_bytes": {
        +                "type": "integer"
        +              }
        +            },
        +            "required": [
        +              "partition",
        +              "offset",
        +              "timestamp",
        +              "key",
        +              "value",
        +              "encoding",
        +              "format",
        +              "value_bytes",
        +              "truncated"
        +            ],
        +            "type": "object"
        +          },
        +          "note": {
        +            "type": "string"
        +          },
        +          "provenance_headers": {
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": [
        +              "null",
        +              "array"
        +            ]
        +          },
        +          "topic": {
        +            "type": "string"
        +          },
        +          "value_encoding": {
        +            "additionalProperties": false,
        +            "properties": {
        +              "format": {
        +                "type": "string"
        +              },
        +              "message_type": {
        +                "type": "string"
        +              },
        +              "schema_id": {
        +                "type": "integer"
        +              },
        +              "subject": {
        +                "type": "string"
        +              },
        +              "version": {
        +                "type": "integer"
        +              }
        +            },
        +            "required": [
        +              "format"
        +            ],
        +            "type": [
        +              "null",
        +              "object"
        +            ]
        +          },
        +          "warnings": {
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": [
        +              "null",
        +              "array"
        +            ]
        +          },
        +          "written_offset": {
        +            "type": "integer"
        +          },
        +          "written_partition": {
        +            "maximum": 2147483647,
        +            "minimum": -2147483648,
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "destination_cluster",
        +          "topic",
        +          "message",
        +          "applied",
        +          "written_partition",
        +          "written_offset"
        +        ],
        +        "type": "object"
        +      },
        +      "type": [
        +        "null",
        +        "array"
        +      ]
        +    },
        +    "failed": {
        +      "type": "integer"
        +    },
        +    "invalid": {
        +      "type": "integer"
        +    },
        +    "lines": {
        +      "type": "integer"
        +    },
        +    "note": {
        +      "type": "string"
        +    },
        +    "valid": {
        +      "type": "integer"
        +    },
        +    "written": {
        +      "type": "integer"
        +    }
        +  },
        +  "required": [
        +    "confirm",
        +    "lines",
        +    "valid",
        +    "invalid",
        +    "written",
        +    "failed",
        +    "applied",
        +    "examples",
        +    "errors"
        +  ],
        +  "type": [
        +    "null",
        +    "object"
        +  ]
        +}
      • addedOutput schema / properties / upload
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "command": {
        +      "type": "string"
        +    },
        +    "expires_at": {
        +      "type": [
        +        "null",
        +        "string"
        +      ]
        +    },
        +    "method": {
        +      "type": "string"
        +    },
        +    "note": {
        +      "type": "string"
        +    },
        +    "path": {
        +      "type": "string"
        +    },
        +    "result": true,
        +    "url": {
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "method",
        +    "note"
        +  ],
        +  "type": [
        +    "null",
        +    "object"
        +  ]
        +}
    • Changedsample_messages5 fields changed
      • changedInput schema / properties / items / description
        Previous value: -"The topics to sample, 1 to 20 of them. Sampling one topic is an array of length one. Results follow this order and a topic that cannot be sampled is reported against its own item."New value: +"The topics to sample, 1 to 500 of them. Sampling one topic is an array of length one. Results follow this order and a topic that cannot be sampled is reported against its own item."
      • changedInput schema / properties / items / items / properties / max_value_bytes / description
        Previous value: -"Optional maximum value bytes to return per message. Defaults to 512."New value: +"Optional maximum value bytes to return per message. Defaults to 512, at most 1048576."
      • changedInput schema / properties / items / items / properties / sample_size / description
        Previous value: -"Optional number of messages to read in total, spread across partitions. Defaults to 20."New value: +"Optional number of messages to read in total, spread across partitions. Defaults to 20, at most 1000."
      • addedOutput schema / properties / results / items / properties / result / properties / value_bytes
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "max": {
        +      "type": "integer"
        +    },
        +    "min": {
        +      "type": "integer"
        +    },
        +    "p50": {
        +      "type": "integer"
        +    }
        +  },
        +  "required": [
        +    "min",
        +    "p50",
        +    "max"
        +  ],
        +  "type": "object"
        +}
      • changedOutput schema / properties / results / items / properties / result / required
        Previous value: -[
        -  "topic",
        -  "messages",
        -  "sampled_ranges",
        -  "value_formats",
        -  "json_fields",
        -  "key_stats",
        -  "key_in_value",
        -  "schemas"
        -]New value: +[
        +  "topic",
        +  "messages",
        +  "sampled_ranges",
        +  "value_formats",
        +  "json_fields",
        +  "key_stats",
        +  "key_in_value",
        +  "schemas",
        +  "value_bytes"
        +]
    • Changedsearch_messages16 fields changed
      • addedInput schema / properties / download
        Added value: +{
        +  "additionalProperties": false,
        +  "description": "Optional. Instead of returning matches, save every match to a file on the user's own machine. Over stdio the file is saved right away and download.path names it. Over HTTP the response is a link and a command, e.g. curl, to run on the user's machine; the search runs when the link is fetched. Nothing is stored on the server. Use it instead of returning thousands of messages, or whenever the user wants the messages in a file.",
        +  "properties": {
        +    "filename": {
        +      "description": "Optional name to save the file as on the user's machine. Over stdio it may be a path, relative to the server's working directory or absolute, and an existing file is never replaced; over HTTP it is a plain file name used in the command. Defaults to <topic>.jsonl or <topic>.csv.",
        +      "type": "string"
        +    },
        +    "format": {
        +      "description": "Optional file format: jsonl (default, one JSON message per line) or csv. Both are kafka-console's export layout, so the file can be imported there: values are never cut (max_value_bytes does not apply), null keys and values stay null, and payloads are exact bytes (key_encoding, value_encoding utf8 or base64) or, with a Schema Registry schema id, the decoded document with that id.",
        +      "type": "string"
        +    }
        +  },
        +  "type": [
        +    "null",
        +    "object"
        +  ]
        +}
      • changedInput schema / properties / from_timestamp / description
        Previous value: -"Optional inclusive start time (RFC3339). Resolved to the first offset at or after this time."New value: +"Optional inclusive start time (RFC3339). Resolved to the first offset at or after this time; messages whose own timestamp is earlier are skipped and not counted as scanned."
      • addedInput schema / properties / group_by
        Added value: +{
        +  "description": "Optional JavaScript expression, with the same variables as script, that returns a bucket name for each matching message, e.g. return headers['error-reason'] or return key or return schema_id. Every match in the range is counted per bucket, like count_only, and no message bodies are returned. null and undefined form the bucket null. Cannot be combined with download.",
        +  "type": "string"
        +}
      • addedInput schema / properties / max_groups
        Added value: +{
        +  "description": "Optional number of largest buckets to return with group_by. Defaults to 20, at most 1000. groups_truncated says whether more existed.",
        +  "type": "integer"
        +}
      • changedInput schema / properties / max_matches / description
        Previous value: -"Optional maximum number of matches to return. Defaults to 10."New value: +"Optional maximum number of matches to return. Defaults to 10, at most 1000. Use download to export more."
      • changedInput schema / properties / max_messages_scanned / description
        Previous value: -"Optional maximum number of messages to read before giving up. Defaults to 10000."New value: +"Optional maximum number of messages to read before giving up. Defaults to 10000, at most 100000000."
      • changedInput schema / properties / max_value_bytes / description
        Previous value: -"Optional maximum value bytes to return per match. Defaults to 512. Longer values are cut and flagged with truncated=true."New value: +"Optional maximum value bytes to return per match. Defaults to 512, at most 1048576. Longer values are cut and flagged with truncated=true."
      • removedInput schema / properties / output_file
        Removed value: -{
        -  "description": "Optional file name to write every match to, as one JSON message per line. Use this instead of returning thousands of messages. A name only, not a path: the server chooses the directory. The response reports the path, the number written and a short preview.",
        -  "type": "string"
        -}
      • changedInput schema / properties / parallelism / description
        Previous value: -"Optional number of concurrent readers, from 1 to 16. Defaults to 1. It splits a single-partition topic's offsets between readers, which makes a full scan of one large partition faster. A multi-partition topic is already read across its partitions together, so this does not apply there. Worth using for count_only, output_file or a full scan of one partition."New value: +"Optional number of concurrent readers, from 1 to 16. Defaults to 1. It splits a single-partition topic's offsets between readers, which makes a full scan of one large partition faster. A multi-partition topic is already read across its partitions together, so this does not apply there. Worth using for count_only, download or a full scan of one partition."
      • changedInput schema / properties / script / description
        Previous value: -"Optional JavaScript that decides whether a message matches. Return true to keep it. In scope: value (the parsed document for JSON, and the decoded record for Avro, Protobuf, JSON Schema or a configured format; the raw text otherwise), key (string, decoded document when the key has a schema, or null), headers (object of header name to string), partition, offset and timestamp (a Date). Examples: return key === 'order-123'; return value.eventType === 'NEW' && value.payload.amount >= 500; return value.payload.cancelledAt === null. Omit to match every message."New value: +"Optional JavaScript that decides whether a message matches. Return true to keep it. In scope: value (the parsed document for JSON, and the decoded record for Avro, Protobuf, JSON Schema or a configured format; the raw text otherwise), key (string, decoded document when the key has a schema, or null), headers (object of header name to string), partition, offset, timestamp (a Date), value_bytes and key_bytes (raw sizes, before decoding), format (json, avro, protobuf, json_schema, msgpack, text, binary or null), schema_id (number or null) and decode_error (string or null). Examples: return key === 'order-123'; return value.eventType === 'NEW' && value.payload.amount >= 500; return value.payload.cancelledAt === null. Omit to match every message."
      • changedInput schema / properties / timeout_seconds / description
        Previous value: -"Optional wall-clock limit for the scan in seconds. Defaults to 30."New value: +"Optional wall-clock limit for the scan in seconds. Defaults to 30, at most 3600."
      • changedInput schema / properties / to_timestamp / description
        Previous value: -"Optional exclusive end time (RFC3339). Resolved to the first offset at or after this time."New value: +"Optional exclusive end time (RFC3339). Resolved to the first offset at or after this time; messages whose own timestamp is at or after it are skipped and not counted as scanned."
      • addedOutput schema / properties / download
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "command": {
        +      "type": "string"
        +    },
        +    "expires_at": {
        +      "type": [
        +        "null",
        +        "string"
        +      ]
        +    },
        +    "method": {
        +      "type": "string"
        +    },
        +    "note": {
        +      "type": "string"
        +    },
        +    "path": {
        +      "type": "string"
        +    },
        +    "result": true,
        +    "url": {
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "method",
        +    "note"
        +  ],
        +  "type": [
        +    "null",
        +    "object"
        +  ]
        +}
      • addedOutput schema / properties / groups
        Added value: +{
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "count": {
        +        "type": "integer"
        +      },
        +      "example": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "offset": {
        +            "type": "integer"
        +          },
        +          "partition": {
        +            "maximum": 2147483647,
        +            "minimum": -2147483648,
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "partition",
        +          "offset"
        +        ],
        +        "type": "object"
        +      },
        +      "key": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "key",
        +      "count",
        +      "example"
        +    ],
        +    "type": "object"
        +  },
        +  "type": [
        +    "null",
        +    "array"
        +  ]
        +}
      • addedOutput schema / properties / groups_truncated
        Added value: +{
        +  "type": "boolean"
        +}
      • removedOutput schema / properties / output_file
        Removed value: -{
        -  "type": "string"
        -}
    • Changedserver_config3 fields changed
      • addedOutput schema / properties / destinations
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": [
        +    "null",
        +    "array"
        +  ]
        +}
      • removedOutput schema / properties / output_dir
        Removed value: -{
        -  "type": "string"
        -}
      • changedOutput schema / required
        Previous value: -[
        -  "endpoint",
        -  "path",
        -  "cluster",
        -  "brokers",
        -  "authentication",
        -  "tls",
        -  "read_only",
        -  "output_dir",
        -  "tools",
        -  "note"
        -]New value: +[
        +  "endpoint",
        +  "path",
        +  "cluster",
        +  "brokers",
        +  "authentication",
        +  "tls",
        +  "read_only",
        +  "destinations",
        +  "tools",
        +  "note"
        +]
    • Addedtransform_messages
  2. 24 tool updatesv0.2.0
    • First observedadd_partitions
    • First observedalter_topic_config
    • First observedcluster_health
    • First observedcommit_offset
    • First observedcompare_clusters
    • First observedconsumer_lag
    • First observedcopy_message
    • First observedcreate_topic
    • First observeddelete_consumer_group
    • First observeddelete_records
    • First observeddelete_topic
    • First observeddescribe_consumer_group
    • First observeddescribe_topic
    • First observedget_message
    • First observedget_schema
    • First observedlist_acls
    • First observedlist_clusters
    • First observedlist_consumer_groups
    • First observedlist_topics
    • First observedopen_transactions
    • First observedproduce_message
    • First observedsample_messages
    • First observedsearch_messages
    • First observedserver_config

TDQS

A4/5.0

Scored across 25 tools

Disambiguation4/5

Most tools target a distinct resource+action (create_topic vs delete_topic vs alter_topic_config vs add_partitions; produce_message vs copy_message vs transform_messages), and descriptions draw clear boundaries. The main soft spots are the message-reading trio get_message, sample_messages and search_messages, which all return message content and could be confused by an agent that just wants 'to look at messages', and list_clusters vs server_config which both report connectivity/permission context.

Naming Consistency4/5

Overwhelmingly consistent snake_case verb_noun pattern (list_topics, create_topic, delete_records, commit_offset, describe_consumer_group). A few read-only report tools drop the verb (cluster_health, consumer_lag, server_config), which is a minor but noticeable deviation from the verb_noun convention.

Tool Count4/5

At 25 tools this is at the top of the reasonable range, but the domain is genuinely broad (topics, partitions/configs, messages, consumer groups, offsets, ACLs, schemas, clusters) and each tool covers a distinct operation with no obvious filler. It is heavy but earns its footprint rather than padding it.

Completeness4/5

Topic lifecycle (create/describe/list/delete, partitions, configs), message read/write/copy/transform/truncate, consumer group and offset management, lag, transactions, health, schema reads and ACL listing give near-complete coverage of Kafka administration. Notable gaps remain: ACLs are read-only (no create/delete_acl), schema registration is only indirect via produce, and open_transactions is diagnostic only (no abort), and there is no partition reassignment action even though cluster_health surfaces reassignments.

Maintenance

ActivityActive
ResponsivenessUnresponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    An MCP server that enables interaction with Kafka clusters to manage topics, monitor consumer groups, and stream messages. It provides a comprehensive suite of tools for broker metadata inspection and local Kafka user management.
    MIT
  • A
    license
    B
    quality
    B
    maintenance
    MCP server for Apache Kafka that allows LLM agents to inspect topics, consumer groups, and safely manage offsets (reset, rewind).
    19
    13
    Apache 2.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    An MCP server that enables AI assistants to safely interact with Apache Kafka clusters, providing tools for topic management, message operations, consumer groups, and cluster information.
    3
    MIT
  • F
    license
    C
    quality
    D
    maintenance
    Exposes Kafka administration operations as MCP tools, enabling AI agents to inspect Kafka clusters using natural language.
    1
    -