Skip to content

Commit 813e131

Browse files
authored
Add tool to get bootstrap server addresses (#7)
Signed-off-by: David Kornel <kornys@outlook.com>
1 parent aab9324 commit 813e131

4 files changed

Lines changed: 134 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ All tools support **smart discovery** - if you don't specify a namespace, they a
111111
- **`strimzi_kafka_clusters`** - Discover and list all Kafka clusters
112112
- **`strimzi_cluster_pods`** - Get cluster pod status and health
113113
- **`strimzi_kafka_topics`** - Get topic configuration and status
114+
- **`strimzi_bootstrap_servers`** - Get Kafka bootstrap servers for client connections
114115
- **`strimzi_operator_status`** - Check operator deployment health
115116
- **`strimzi_operator_logs`** - Get operator logs with error analysis
116117

src/main/java/io/strimzi/mcp/mcp/StrimziOperatorMcpTools.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,24 @@ public Object getKafkaTopics(
120120
return ToolError.of("Failed to retrieve Kafka topics", e);
121121
}
122122
}
123+
124+
@Tool(
125+
name = "strimzi_bootstrap_servers",
126+
description = "Get Kafka bootstrap servers (connection endpoints) from Kafka Custom Resource. " +
127+
"Extracts all available listener addresses and ports for client connections. " +
128+
"Returns bootstrap server URLs for internal, external, and other configured listeners. " +
129+
"Smart discovery: If namespace not specified, automatically searches for Strimzi installations."
130+
)
131+
public Object getBootstrapServers(
132+
@ToolArg(description = "Namespace of the Kafka cluster (optional - auto-discovered if not specified)")
133+
String namespace,
134+
@ToolArg(description = "Name of the Kafka cluster to query (required, e.g., 'my-cluster')")
135+
String clusterName
136+
) {
137+
try {
138+
return clusterService.getBootstrapServers(namespace, clusterName);
139+
} catch (Exception e) {
140+
return ToolError.of("Failed to get bootstrap servers", e);
141+
}
142+
}
123143
}

src/main/java/io/strimzi/mcp/service/KafkaClusterService.java

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import io.fabric8.kubernetes.api.model.ContainerStatus;
44
import io.fabric8.kubernetes.api.model.Pod;
55
import io.fabric8.kubernetes.client.KubernetesClient;
6+
import io.strimzi.api.kafka.model.kafka.Kafka;
7+
import io.strimzi.mcp.dto.BootstrapServersResult;
8+
import io.strimzi.mcp.dto.BootstrapServersResult.BootstrapServerInfo;
69
import io.strimzi.mcp.dto.ClusterPodsResult;
710
import io.strimzi.mcp.dto.KafkaClustersResult;
811
import io.strimzi.mcp.service.StrimziDiscoveryService.KafkaClusterInfo;
@@ -234,4 +237,95 @@ private Map<String, Integer> calculateComponentBreakdown(List<ClusterPodsResult.
234237
Collectors.collectingAndThen(Collectors.counting(), Math::toIntExact)
235238
));
236239
}
240+
241+
/**
242+
* Get bootstrap servers for a Kafka cluster from its Custom Resource.
243+
*/
244+
public BootstrapServersResult getBootstrapServers(String namespace, String clusterName) {
245+
String normalizedNamespace = discoveryService.normalizeNamespace(namespace);
246+
String normalizedClusterName = discoveryService.normalizeClusterName(clusterName);
247+
248+
// Auto-discover namespace if not specified
249+
if (normalizedNamespace == null) {
250+
List<String> discoveredNamespaces = discoveryService.discoverStrimziNamespaces();
251+
if (discoveredNamespaces.size() == 1) {
252+
normalizedNamespace = discoveredNamespaces.get(0);
253+
LOG.infof("Auto-discovered Strimzi in namespace: %s", normalizedNamespace);
254+
} else if (!discoveredNamespaces.isEmpty()) {
255+
String namespaceList = String.join(", ", discoveredNamespaces);
256+
return BootstrapServersResult.error(null, normalizedClusterName,
257+
String.format("Found Strimzi in multiple namespaces: %s. " +
258+
"Please specify: 'Get bootstrap servers for %s in the %s namespace'",
259+
namespaceList, normalizedClusterName, discoveredNamespaces.get(0)));
260+
} else {
261+
return BootstrapServersResult.error(null, normalizedClusterName,
262+
"No Strimzi installation found. Please ensure Kafka is deployed.");
263+
}
264+
}
265+
266+
if (normalizedClusterName == null) {
267+
return BootstrapServersResult.error(normalizedNamespace, null,
268+
"Cluster name is required. Please specify a cluster name like 'my-cluster'");
269+
}
270+
271+
LOG.infof("KafkaClusterService: getBootstrapServers (namespace=%s, cluster=%s)",
272+
normalizedNamespace, normalizedClusterName);
273+
274+
try {
275+
Kafka kafka = kubernetesClient.resources(Kafka.class)
276+
.inNamespace(normalizedNamespace)
277+
.withName(normalizedClusterName)
278+
.get();
279+
280+
if (kafka == null) {
281+
return BootstrapServersResult.notFound(normalizedNamespace, normalizedClusterName);
282+
}
283+
284+
List<BootstrapServerInfo> bootstrapServers = extractBootstrapServers(kafka);
285+
286+
if (bootstrapServers.isEmpty()) {
287+
return BootstrapServersResult.empty(normalizedNamespace, normalizedClusterName);
288+
}
289+
290+
return BootstrapServersResult.of(normalizedNamespace, normalizedClusterName, bootstrapServers);
291+
292+
} catch (Exception e) {
293+
LOG.errorf(e, "Error retrieving bootstrap servers for cluster %s in namespace %s",
294+
normalizedClusterName, normalizedNamespace);
295+
return BootstrapServersResult.error(normalizedNamespace, normalizedClusterName, e.getMessage());
296+
}
297+
}
298+
299+
private List<BootstrapServerInfo> extractBootstrapServers(Kafka kafka) {
300+
List<BootstrapServerInfo> servers = new ArrayList<>();
301+
302+
if (kafka.getStatus() == null || kafka.getStatus().getListeners() == null) {
303+
return servers;
304+
}
305+
306+
// Extract from listeners status
307+
kafka.getStatus().getListeners().forEach(listener -> {
308+
String listenerName = listener.getName();
309+
String listenerType = listener.getType();
310+
311+
if (listener.getAddresses() != null) {
312+
listener.getAddresses().forEach(address -> {
313+
String host = address.getHost();
314+
Integer port = address.getPort();
315+
316+
if (host != null && port != null) {
317+
servers.add(new BootstrapServerInfo(
318+
host,
319+
port,
320+
listenerName,
321+
listenerType,
322+
String.format("%s:%d", host, port)
323+
));
324+
}
325+
});
326+
}
327+
});
328+
329+
return servers;
330+
}
237331
}

src/main/java/io/strimzi/mcp/tool/StrimziOperatorTools.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,23 @@ public KafkaClustersResult getKafkaClusters(String namespace) {
114114
public KafkaTopicsResult getKafkaTopics(String namespace, String clusterName) {
115115
return topicService.getKafkaTopics(namespace, clusterName);
116116
}
117+
118+
/**
119+
* Get Kafka bootstrap servers (connection endpoints) from Kafka Custom Resource.
120+
*
121+
* Extracts all available listener addresses and ports for client connections from
122+
* the Kafka Custom Resource status. Returns bootstrap server URLs for internal,
123+
* external, and other configured listeners that clients can use to connect.
124+
*
125+
* Smart discovery: If namespace not specified, automatically searches for
126+
* Strimzi installations across all namespaces.
127+
*
128+
* @param namespace namespace of the Kafka cluster (optional - auto-discovered if not specified)
129+
* @param clusterName name of the Kafka cluster to query (required, e.g., 'my-cluster')
130+
* @return structured result with bootstrap server endpoints, listeners, and connection details
131+
*/
132+
@Tool("Get Kafka bootstrap servers for client connections. Extracts listener endpoints from Kafka Custom Resource.")
133+
public BootstrapServersResult getBootstrapServers(String namespace, String clusterName) {
134+
return clusterService.getBootstrapServers(namespace, clusterName);
135+
}
117136
}

0 commit comments

Comments
 (0)