Skip to content

Commit 5714450

Browse files
committed
mcp: jira client
1 parent de15da1 commit 5714450

File tree

13 files changed

+213
-39
lines changed

13 files changed

+213
-39
lines changed

README.md

Lines changed: 2 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
@@ -140,6 +140,7 @@ GENERIC_INFERENCE_URL="YOUR_INFERENCE_ENDPOINT"
140140
GENERIC_INFERENCE_TOKEN="YOUR_INFERENCE_TOKEN"
141141
GENERIC_MODEL="YOUR_INFERENCE_MODEL"
142142
```
143+
143144
**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.
144145
145146

bugzooka/analysis/prompts.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,12 @@
127127
Beginning analysis now.
128128
""",
129129
}
130+
# Jira tool prompt - used when Jira MCP tools are available
131+
JIRA_TOOL_PROMPT = {
132+
"system_suffix": (
133+
"\n\nIMPORTANT: You have access to JIRA search tools. After analyzing the error, "
134+
"ALWAYS search for related issues in JIRA using the search_jira_issues tool with the OCPBUGS project. "
135+
"Extract key error terms, component names, or operators from the log summary to search for similar issues. "
136+
"Include the top 3 most relevant JIRA issues in your final response under a 'Related JIRA Issues' section."
137+
),
138+
}

bugzooka/integrations/gemini_client.py

Lines changed: 56 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
INFERENCE_MAX_TOOL_ITERATIONS,
1313
)
1414
from bugzooka.integrations.inference import InferenceAPIUnavailableError
15+
from bugzooka.analysis.prompts import JIRA_TOOL_PROMPT
1516

1617

1718
logger = logging.getLogger(__name__)
@@ -145,10 +146,10 @@ async def execute_tool_call(tool_name, tool_args, available_tools):
145146
logger.debug("Tool arguments: %s", json.dumps(tool_args, indent=2))
146147

147148
# Check if the tool is async (has coroutine attribute or ainvoke method)
148-
if hasattr(tool, 'coroutine') and tool.coroutine:
149+
if hasattr(tool, "coroutine") and tool.coroutine:
149150
# MCP tools have a coroutine attribute
150151
result = await tool.ainvoke(tool_args)
151-
elif hasattr(tool, 'ainvoke'):
152+
elif hasattr(tool, "ainvoke"):
152153
# Some tools have ainvoke method
153154
result = await tool.ainvoke(tool_args)
154155
else:
@@ -206,15 +207,40 @@ async def analyze_with_gemini_agentic(
206207

207208
try:
208209
gemini_client = GeminiClient()
209-
210+
211+
prompt_config = product_config["prompt"][product]
212+
213+
# Check if Jira MCP tools are available and inject Jira prompt
214+
has_jira = tools and any(
215+
hasattr(t, "name") and t.name == "search_jira_issues" for t in tools
216+
)
217+
if has_jira:
218+
logger.info("Jira MCP tools detected - injecting Jira prompt")
219+
system_prompt = prompt_config["system"] + JIRA_TOOL_PROMPT["system_suffix"]
220+
else:
221+
system_prompt = prompt_config["system"]
222+
223+
try:
224+
formatted_content = prompt_config["user"].format(
225+
error_summary=error_summary
226+
)
227+
except KeyError:
228+
formatted_content = prompt_config["user"].format(summary=error_summary)
229+
230+
messages = [
231+
{"role": "system", "content": system_prompt},
232+
{"role": "user", "content": formatted_content},
233+
{"role": "assistant", "content": prompt_config["assistant"]},
234+
]
235+
210236
# Convert LangChain tools to OpenAI format if provided
211237
openai_tools = None
212238
if tools:
213239
openai_tools = convert_langchain_tools_to_openai_format(tools)
214240
tool_names = [t["function"]["name"] for t in openai_tools]
215241
logger.info("Starting Gemini analysis with %d tools: %s",
216242
len(openai_tools), ", ".join(tool_names))
217-
243+
218244
logger.debug("Starting agentic loop with %d messages", len(messages))
219245

220246
# Tool calling loop - iterate until we get a final answer or hit max iterations
@@ -240,7 +266,7 @@ async def analyze_with_gemini_agentic(
240266
response_message = response.choices[0].message
241267

242268
# Check if Gemini wants to call tools
243-
tool_calls = getattr(response_message, 'tool_calls', None)
269+
tool_calls = getattr(response_message, "tool_calls", None)
244270

245271
if not tool_calls:
246272
# No tool calls - we have the final answer
@@ -258,21 +284,23 @@ async def analyze_with_gemini_agentic(
258284
logger.info("Calling %d tool(s): %s", len(tool_calls), ", ".join(tool_names_called))
259285

260286
# Add the assistant's message with tool calls to conversation
261-
messages.append({
262-
"role": "assistant",
263-
"content": response_message.content or "",
264-
"tool_calls": [
265-
{
266-
"id": tc.id,
267-
"type": "function",
268-
"function": {
269-
"name": tc.function.name,
270-
"arguments": tc.function.arguments
287+
messages.append(
288+
{
289+
"role": "assistant",
290+
"content": response_message.content or "",
291+
"tool_calls": [
292+
{
293+
"id": tc.id,
294+
"type": "function",
295+
"function": {
296+
"name": tc.function.name,
297+
"arguments": tc.function.arguments,
298+
},
271299
}
272-
}
273-
for tc in tool_calls
274-
]
275-
})
300+
for tc in tool_calls
301+
],
302+
}
303+
)
276304

277305
# Execute each tool call and add results to messages
278306
for tool_call in tool_calls:
@@ -286,18 +314,18 @@ async def analyze_with_gemini_agentic(
286314
else:
287315
# Execute the tool (await since it's now async)
288316
function_result = await execute_tool_call(
289-
function_name,
290-
function_args,
291-
tools
317+
function_name, function_args, tools
292318
)
293319

294320
# Add tool result to messages
295-
messages.append({
296-
"role": "tool",
297-
"tool_call_id": tool_call.id,
298-
"name": function_name,
299-
"content": function_result
300-
})
321+
messages.append(
322+
{
323+
"role": "tool",
324+
"tool_call_id": tool_call.id,
325+
"name": function_name,
326+
"content": function_result,
327+
}
328+
)
301329

302330
# Continue loop to let Gemini process tool results
303331

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/deployment.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ spec:
5252
envFrom:
5353
- secretRef:
5454
name: bugzooka-env
55+
- secretRef:
56+
name: bugzooka-jira
5557
volumeMounts:
5658
- name: prompts
5759
mountPath: /app/prompts.json

kustomize/base/kustomization.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@ 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
1213
- configmap-mcp-config.yaml
1314
- deployment.yaml
15+
- configmap-mcp.yaml
16+
- deploymentconfig.yaml
17+
- deployment-jira-mcp.yaml
18+
- service-jira-mcp.yaml
1419

1520
generatorOptions:
1621
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)