Skip to content

Commit 57a1d1e

Browse files
committed
mcp: jira client
1 parent be21ddf commit 57a1d1e

File tree

13 files changed

+217
-74
lines changed

13 files changed

+217
-74
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ BugZooka supports two complementary modes for monitoring Slack channels that can
7979
# Run with both polling AND socket mode
8080
make run ARGS="--product openshift --ci prow --enable-socket-mode"
8181
```
82-
82+
8383
**Socket Mode Requirements:**
8484
- An app-level token (`xapp-*`) must be configured as `SLACK_APP_TOKEN`
8585
- Socket Mode must be enabled in your Slack app settings
@@ -124,6 +124,7 @@ GENERIC_INFERENCE_URL="YOUR_INFERENCE_ENDPOINT"
124124
GENERIC_INFERENCE_TOKEN="YOUR_INFERENCE_TOKEN"
125125
GENERIC_MODEL="YOUR_INFERENCE_MODEL"
126126
```
127+
127128
**Note**: Please make sure to provide details for all the mandatory attributes and for the product that is intended to be used for testing along with fallback (i.e. GENERIC details) to handle failover use-cases.
128129
129130
@@ -263,6 +264,7 @@ make podman-run # Requires .env file in project root
263264
export QUAY_CRED='<base64 encoded pull secret>'
264265
export BUGZOOKA_IMAGE='<image tag>'
265266
export BUGZOOKA_NAMESPACE='<your namespace>'
267+
266268
kustomize build ./kustomize | envsubst | oc apply -f -
267269
268270
# Cleanup resources

bugzooka/analysis/prompts.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,13 @@
5151
),
5252
"assistant": "Here is a context-aware analysis of the most relevant failures:",
5353
}
54+
55+
# Jira tool prompt - used when Jira MCP tools are available
56+
JIRA_TOOL_PROMPT = {
57+
"system_suffix": (
58+
"\n\nIMPORTANT: You have access to JIRA search tools. After analyzing the error, "
59+
"ALWAYS search for related issues in JIRA using the search_jira_issues tool with the OCPBUGS project. "
60+
"Extract key error terms, component names, or operators from the log summary to search for similar issues. "
61+
"Include the top 3 most relevant JIRA issues in your final response under a 'Related JIRA Issues' section."
62+
),
63+
}

bugzooka/integrations/gemini_client.py

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import ssl
44
import httpx
55
import json
6-
import asyncio
76

87
from openai import OpenAI
98
from langchain_core.utils.function_calling import convert_to_openai_tool
@@ -13,6 +12,7 @@
1312
INFERENCE_MAX_TOOL_ITERATIONS,
1413
)
1514
from bugzooka.integrations.inference import InferenceAPIUnavailableError
15+
from bugzooka.analysis.prompts import JIRA_TOOL_PROMPT
1616

1717

1818
logger = logging.getLogger(__name__)
@@ -127,10 +127,10 @@ async def execute_tool_call(tool_name, tool_args, available_tools):
127127
logger.info("Executing tool: %s with args: %s", tool_name, tool_args)
128128

129129
# Check if the tool is async (has coroutine attribute or ainvoke method)
130-
if hasattr(tool, 'coroutine') and tool.coroutine:
130+
if hasattr(tool, "coroutine") and tool.coroutine:
131131
# MCP tools have a coroutine attribute
132132
result = await tool.ainvoke(tool_args)
133-
elif hasattr(tool, 'ainvoke'):
133+
elif hasattr(tool, "ainvoke"):
134134
# Some tools have ainvoke method
135135
result = await tool.ainvoke(tool_args)
136136
else:
@@ -151,7 +151,7 @@ async def analyze_log_with_gemini(
151151
error_summary: str,
152152
model="gemini-2.0-flash",
153153
tools=None,
154-
max_iterations=None
154+
max_iterations=None,
155155
):
156156
"""
157157
Analyzes log summaries using Gemini API with product-specific prompts and optional tool calling.
@@ -171,6 +171,17 @@ async def analyze_log_with_gemini(
171171
gemini_client = GeminiClient()
172172

173173
prompt_config = product_config["prompt"][product]
174+
175+
# Check if Jira MCP tools are available and inject Jira prompt
176+
has_jira = tools and any(
177+
hasattr(t, "name") and t.name == "search_jira_issues" for t in tools
178+
)
179+
if has_jira:
180+
logger.info("Jira MCP tools detected - injecting Jira prompt")
181+
system_prompt = prompt_config["system"] + JIRA_TOOL_PROMPT["system_suffix"]
182+
else:
183+
system_prompt = prompt_config["system"]
184+
174185
try:
175186
formatted_content = prompt_config["user"].format(
176187
error_summary=error_summary
@@ -179,7 +190,7 @@ async def analyze_log_with_gemini(
179190
formatted_content = prompt_config["user"].format(summary=error_summary)
180191

181192
messages = [
182-
{"role": "system", "content": prompt_config["system"]},
193+
{"role": "system", "content": system_prompt},
183194
{"role": "user", "content": formatted_content},
184195
{"role": "assistant", "content": prompt_config["assistant"]},
185196
]
@@ -188,9 +199,11 @@ async def analyze_log_with_gemini(
188199
openai_tools = None
189200
if tools:
190201
openai_tools = convert_langchain_tools_to_openai_format(tools)
191-
logger.info("Enabled %d tools for Gemini: %s",
192-
len(openai_tools),
193-
[t["function"]["name"] for t in openai_tools])
202+
logger.info(
203+
"Enabled %d tools for Gemini: %s",
204+
len(openai_tools),
205+
[t["function"]["name"] for t in openai_tools],
206+
)
194207

195208
# Tool calling loop - iterate until we get a final answer or hit max iterations
196209
iteration = 0
@@ -214,7 +227,7 @@ async def analyze_log_with_gemini(
214227
response_message = response.choices[0].message
215228

216229
# Check if Gemini wants to call tools
217-
tool_calls = getattr(response_message, 'tool_calls', None)
230+
tool_calls = getattr(response_message, "tool_calls", None)
218231

219232
if not tool_calls:
220233
# No tool calls - we have the final answer
@@ -229,21 +242,23 @@ async def analyze_log_with_gemini(
229242
logger.info("Gemini requested %d tool call(s)", len(tool_calls))
230243

231244
# Add the assistant's message with tool calls to conversation
232-
messages.append({
233-
"role": "assistant",
234-
"content": response_message.content or "",
235-
"tool_calls": [
236-
{
237-
"id": tc.id,
238-
"type": "function",
239-
"function": {
240-
"name": tc.function.name,
241-
"arguments": tc.function.arguments
245+
messages.append(
246+
{
247+
"role": "assistant",
248+
"content": response_message.content or "",
249+
"tool_calls": [
250+
{
251+
"id": tc.id,
252+
"type": "function",
253+
"function": {
254+
"name": tc.function.name,
255+
"arguments": tc.function.arguments,
256+
},
242257
}
243-
}
244-
for tc in tool_calls
245-
]
246-
})
258+
for tc in tool_calls
259+
],
260+
}
261+
)
247262

248263
# Execute each tool call and add results to messages
249264
for tool_call in tool_calls:
@@ -256,25 +271,25 @@ async def analyze_log_with_gemini(
256271
else:
257272
# Execute the tool (await since it's now async)
258273
function_result = await execute_tool_call(
259-
function_name,
260-
function_args,
261-
tools
274+
function_name, function_args, tools
262275
)
263276

264277
# Add tool result to messages
265-
messages.append({
266-
"role": "tool",
267-
"tool_call_id": tool_call.id,
268-
"name": function_name,
269-
"content": function_result
270-
})
278+
messages.append(
279+
{
280+
"role": "tool",
281+
"tool_call_id": tool_call.id,
282+
"name": function_name,
283+
"content": function_result,
284+
}
285+
)
271286

272287
# Continue loop to let Gemini process tool results
273288

274289
# If we hit max iterations without a final answer
275290
logger.warning(
276291
"Reached maximum tool calling iterations (%d) without final answer",
277-
max_iterations
292+
max_iterations,
278293
)
279294
return "Analysis incomplete: Maximum tool calling iterations reached. Please try again with a simpler query."
280295

kustomize/base/configmap-mcp.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: bugzooka-mcp-config
5+
namespace: ${BUGZOOKA_NAMESPACE}
6+
labels:
7+
app: ${BUGZOOKA_NAMESPACE}
8+
data:
9+
mcp_config.json: |
10+
{
11+
"mcp_servers": {
12+
"jira_mcp_server": {
13+
"url": "http://jira-mcp.${BUGZOOKA_NAMESPACE}:3031/mcp",
14+
"transport": "streamable_http"
15+
}
16+
}
17+
}

kustomize/base/configmap-prompts.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ metadata:
88
data:
99
prompts.json: |
1010
{
11-
"OPENSHIFT_PROMPT": {
12-
"system": "You are an expert in OpenShift, Kubernetes, and cloud infrastructure. Your task is to analyze logs and summaries related to OpenShift environments. Given a log summary, identify the root cause, potential fixes, and affected components. Be as consise as possible (under 5000 characters), but precise and avoid generic troubleshooting steps. Prioritize OpenShift-specific debugging techniques. Keep in mind that the cluster is ephemeral and is destroyed after the build is complete, but all relevant logs and metrics are available. Use markdown formatting for the output with only one level of bullet points, do not use bold text except for the headers.",
13-
"user": "Here is the log summary from an OpenShift environment:\n\n{summary}\n\nBased on this summary, provide a structured breakdown of:\n- The OpenShift component likely affected (e.g., etcd, kube-apiserver, ingress, SDN, Machine API)\n- The probable root cause\n- Steps to verify the issue further\n- Suggested resolution, including OpenShift-specific commands or configurations.",
14-
"assistant": "**Affected Component:** <Identified component>\n\n**Probable Root Cause:** <Describe why this issue might be occurring>\n\n**Verification Steps:**\n- <Step 1>\n- <Step 2>\n- <Step 3>\n\n**Suggested Resolution:**\n- <OpenShift CLI commands>\n- <Relevant OpenShift configurations>"
15-
},
11+
"OPENSHIFT_PROMPT": {
12+
"system": "You are an expert in OpenShift, Kubernetes, and cloud infrastructure. Your task is to analyze logs and summaries related to OpenShift environments. Given a log summary, identify the root cause, potential fixes, and affected components. Be as consise as possible (under 5000 characters), but precise and avoid generic troubleshooting steps. Prioritize OpenShift-specific debugging techniques. Keep in mind that the cluster is ephemeral and is destroyed after the build is complete, but all relevant logs and metrics are available. Use markdown formatting for the output with only one level of bullet points, do not use bold text except for the headers.",
13+
"user": "Here is the log summary from an OpenShift environment:\n\n{summary}\n\nBased on this summary, provide a structured breakdown of:\n- The OpenShift component likely affected (e.g., etcd, kube-apiserver, ingress, SDN, Machine API)\n- The probable root cause\n- Steps to verify the issue further\n- Suggested resolution, including OpenShift-specific commands or configurations",
14+
"assistant": "**Affected Component:** <Identified component>\n\n**Probable Root Cause:** <Describe why this issue might be occurring>\n\n**Verification Steps:**\n- <Step 1>\n- <Step 2>\n- <Step 3>\n\n**Suggested Resolution:**\n- <OpenShift CLI commands>\n- <Relevant OpenShift configurations>"
15+
},
1616
"ANSIBLE_PROMPT": {
1717
"system": "You are an expert in Ansible automation, playbook debugging, and infrastructure as code (IaC). Your task is to analyze log summaries related to Ansible execution, playbook failures, and task errors. Given a log summary, identify the root cause, affected tasks, and potential fixes. Prioritize Ansible-specific debugging techniques over generic troubleshooting.",
1818
"user": "Here is the log summary from an Ansible execution:\n\n{summary}\n\nBased on this summary, provide a structured breakdown of:\n- The failed Ansible task and module involved\n- The probable root cause\n- Steps to reproduce or verify the issue\n- Suggested resolution, including relevant playbook changes or command-line fixes.",
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
labels:
5+
app.kubernetes.io/instance: jira-mcp
6+
name: jira-mcp
7+
namespace: ${BUGZOOKA_NAMESPACE}
8+
spec:
9+
progressDeadlineSeconds: 600
10+
replicas: 1
11+
selector:
12+
matchLabels:
13+
app.kubernetes.io/instance: jira-mcp
14+
app.kubernetes.io/name: jira-mcp
15+
strategy:
16+
rollingUpdate:
17+
maxSurge: 1
18+
maxUnavailable: 0
19+
type: RollingUpdate
20+
template:
21+
metadata:
22+
annotations:
23+
alpha.image.policy.openshift.io/resolve-names: '*'
24+
labels:
25+
app.kubernetes.io/instance: jira-mcp
26+
app.kubernetes.io/name: jira-mcp
27+
spec:
28+
containers:
29+
- image: ${JIRA_MCP_IMAGE}
30+
imagePullPolicy: Always
31+
name: mcp
32+
env:
33+
- name: JIRA_BASE_URL
34+
valueFrom:
35+
secretKeyRef:
36+
name: bugzooka-jira
37+
key: JIRA_BASE_URL
38+
- name: JIRA_TOKEN
39+
valueFrom:
40+
secretKeyRef:
41+
name: bugzooka-jira
42+
key: JIRA_TOKEN
43+
- name: JIRA_ALLOWED_PROJECTS
44+
valueFrom:
45+
secretKeyRef:
46+
name: bugzooka-jira
47+
key: JIRA_ALLOWED_PROJECTS
48+
ports:
49+
- containerPort: 3031
50+
protocol: TCP
51+
resources: {}
52+
terminationMessagePath: /dev/termination-log
53+
terminationMessagePolicy: File
54+
dnsPolicy: ClusterFirst
55+
restartPolicy: Always
56+
schedulerName: default-scheduler
57+
securityContext: {}
58+
terminationGracePeriodSeconds: 30

kustomize/base/deploymentconfig.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,15 @@ spec:
4545
envFrom:
4646
- secretRef:
4747
name: bugzooka-env
48+
- secretRef:
49+
name: bugzooka-jira
4850
volumeMounts:
4951
- name: prompts
5052
mountPath: /app/prompts.json
5153
subPath: prompts.json
54+
- name: mcp-config
55+
mountPath: /app/mcp_config.json
56+
subPath: mcp_config.json
5257
resources:
5358
requests:
5459
cpu: "1"
@@ -63,3 +68,9 @@ spec:
6368
items:
6469
- key: prompts.json
6570
path: prompts.json
71+
- name: mcp-config
72+
configMap:
73+
name: bugzooka-mcp-config
74+
items:
75+
- key: mcp_config.json
76+
path: mcp_config.json

kustomize/base/kustomization.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@ namespace: ${BUGZOOKA_NAMESPACE}
66
resources:
77
- namespace.yaml
88
- secret-quay.yaml
9+
- secret-jira.yaml
910
- serviceaccount-patch.yaml
1011
- imagestream.yaml
1112
- configmap-prompts.yaml
13+
- configmap-mcp.yaml
1214
- deploymentconfig.yaml
15+
- deployment-jira-mcp.yaml
16+
- service-jira-mcp.yaml
1317

1418
generatorOptions:
1519
disableNameSuffixHash: true

kustomize/base/secret-jira.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
apiVersion: v1
2+
kind: Secret
3+
metadata:
4+
name: bugzooka-jira
5+
namespace: ${BUGZOOKA_NAMESPACE}
6+
annotations:
7+
kubernetes.io/service-account.name: default
8+
type: Opaque
9+
stringData:
10+
JIRA_BASE_URL: "${JIRA_BASE_URL}"
11+
JIRA_TOKEN: "${JIRA_TOKEN}"
12+
JIRA_ALLOWED_PROJECTS: "${JIRA_ALLOWED_PROJECTS}"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
labels:
5+
app.kubernetes.io/instance: jira-mcp
6+
name: jira-mcp
7+
namespace: ${BUGZOOKA_NAMESPACE}
8+
spec:
9+
ports:
10+
- name: http
11+
port: 3031
12+
protocol: TCP
13+
targetPort: 3031
14+
selector:
15+
app.kubernetes.io/instance: jira-mcp
16+
app.kubernetes.io/name: jira-mcp
17+
type: ClusterIP

0 commit comments

Comments
 (0)