Skip to content

Commit 43730bc

Browse files
authored
Merge pull request #26 from oslabs-beta/alex
update the frontend and backend of jenkins module
2 parents 4cfe220 + e688874 commit 43730bc

File tree

4 files changed

+113
-27
lines changed

4 files changed

+113
-27
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,7 @@ coverage/
148148
.DS_Store
149149

150150
# SSH key for AWS
151-
jenkins.pem
151+
jenkins.pem
152+
153+
# Local change log (Chinese summary)
154+
change_log_cn.md

client/src/routes/Jenkins.tsx

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,54 @@
1-
import { useState } from "react";
1+
import { useEffect, useState } from "react";
22
import { BASE } from "../lib/api";
33

44
const SERVER_BASE = BASE.replace(/\/api$/, "");
5+
const MCP_URL_FALLBACK = "http://192.168.1.35/mcp-server/mcp";
6+
const MCP_URL_GENERAL_HINT = "Enter the MCP server URL (e.g. https://<host>/mcp-server/mcp)";
57

68
export default function Jenkins() {
79
const [question, setQuestion] = useState("");
810
const [answer, setAnswer] = useState("");
911
const [error, setError] = useState<string | null>(null);
1012
const [loading, setLoading] = useState(false);
13+
const [mcpUrl, setMcpUrl] = useState(MCP_URL_FALLBACK);
14+
const [jenkinsToken, setJenkinsToken] = useState("");
15+
const [mcpUrlHint, setMcpUrlHint] = useState(MCP_URL_FALLBACK);
16+
const [tokenHint, setTokenHint] = useState("");
17+
const [configError, setConfigError] = useState<string | null>(null);
18+
19+
useEffect(() => {
20+
let isMounted = true;
21+
async function loadHints() {
22+
try {
23+
const res = await fetch(`${SERVER_BASE}/jenkins/config`, {
24+
credentials: "include",
25+
});
26+
if (!res.ok) {
27+
throw new Error(res.statusText);
28+
}
29+
const data = await res.json().catch(() => ({}));
30+
if (!isMounted) return;
31+
const hintUrl = data?.mcpUrlHint || MCP_URL_FALLBACK;
32+
const hintToken = data?.tokenHint || "";
33+
setMcpUrlHint(hintUrl);
34+
setTokenHint(hintToken);
35+
setMcpUrl(hintUrl);
36+
setJenkinsToken(hintToken);
37+
setConfigError(null);
38+
} catch (err: any) {
39+
if (!isMounted) return;
40+
setConfigError(err?.message || "Unable to load Jenkins defaults");
41+
}
42+
}
43+
44+
loadHints();
45+
return () => {
46+
isMounted = false;
47+
};
48+
}, []);
1149

1250
async function handleAsk() {
13-
if (!question.trim()) return;
51+
if (!question.trim() || !mcpUrl.trim() || !jenkinsToken.trim()) return;
1452
setLoading(true);
1553
setError(null);
1654
setAnswer("");
@@ -19,7 +57,11 @@ export default function Jenkins() {
1957
method: "POST",
2058
headers: { "Content-Type": "application/json" },
2159
credentials: "include",
22-
body: JSON.stringify({ question: question.trim() }),
60+
body: JSON.stringify({
61+
question: question.trim(),
62+
mcpUrl: mcpUrl.trim() || undefined,
63+
token: jenkinsToken.trim() || undefined,
64+
}),
2365
});
2466
const data = await res.json().catch(() => ({}));
2567
if (!res.ok) throw new Error((data as any)?.error || res.statusText);
@@ -34,6 +76,32 @@ export default function Jenkins() {
3476
return (
3577
<section style={{ display: "grid", gap: 12 }}>
3678
<h1>Jenkins</h1>
79+
<label style={{ display: "grid", gap: 4 }}>
80+
<span style={{ fontWeight: 500 }}>MCP URL</span>
81+
<input
82+
type="text"
83+
value={mcpUrl}
84+
onChange={(e) => setMcpUrl(e.target.value)}
85+
placeholder={MCP_URL_GENERAL_HINT}
86+
style={{ width: "100%", padding: 8, fontSize: 14 }}
87+
/>
88+
<span style={{ fontSize: 12, color: "#555" }}>
89+
Hint: {MCP_URL_GENERAL_HINT}
90+
</span>
91+
</label>
92+
<label style={{ display: "grid", gap: 4 }}>
93+
<span style={{ fontWeight: 500 }}>Jenkins Token</span>
94+
<input
95+
type="password"
96+
value={jenkinsToken}
97+
onChange={(e) => setJenkinsToken(e.target.value)}
98+
placeholder={tokenHint || "Enter Jenkins token"}
99+
style={{ width: "100%", padding: 8, fontSize: 14 }}
100+
/>
101+
<span style={{ fontSize: 12, color: "#555" }}>
102+
Hint: {tokenHint ? tokenHint : "Set JENKINS_TOKEN to prefill"}
103+
</span>
104+
</label>
37105
<textarea
38106
rows={4}
39107
value={question}
@@ -42,7 +110,12 @@ export default function Jenkins() {
42110
style={{ width: "100%", padding: 8, fontSize: 14 }}
43111
/>
44112
<div style={{ display: "flex", gap: 8 }}>
45-
<button onClick={handleAsk} disabled={loading || !question.trim()}>
113+
<button
114+
onClick={handleAsk}
115+
disabled={
116+
loading || !question.trim() || !mcpUrl.trim() || !jenkinsToken.trim()
117+
}
118+
>
46119
{loading ? "Sending..." : "Ask"}
47120
</button>
48121
<button
@@ -51,11 +124,18 @@ export default function Jenkins() {
51124
setQuestion("");
52125
setAnswer("");
53126
setError(null);
127+
setMcpUrl(mcpUrlHint);
128+
setJenkinsToken(tokenHint);
54129
}}
55130
>
56131
Clear
57132
</button>
58133
</div>
134+
{configError && (
135+
<div style={{ color: "#a67c00", fontSize: 13 }}>
136+
Using fallback MCP defaults: {configError}
137+
</div>
138+
)}
59139
{error && <div style={{ color: "red", fontSize: 13 }}>{error}</div>}
60140
<textarea
61141
readOnly

server/routes/jenkins.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,24 @@ import { askJenkins } from '../src/agents/jenkins-agent.js';
33

44
const router = express.Router();
55

6-
router.post('/ask', async (req, res) => {
6+
router.get("/config", (req, res) => {
7+
res.json({
8+
mcpUrlHint: process.env.JENKINS_MCP_URL || "http://192.168.1.35/mcp-server/mcp",
9+
tokenHint: process.env.JENKINS_TOKEN || "",
10+
});
11+
});
12+
13+
router.post("/ask", async (req, res) => {
714
try {
8-
const { question } = req.body;
15+
const { question, mcpUrl, token } = req.body;
916
if (!question) {
1017
return res
1118
.status(400)
1219
.json({ error: "Missing 'question' field in body" });
1320
}
1421

1522
console.log(`[JENKINS ASK] ${question}`);
16-
const answer = await askJenkins(question);
23+
const answer = await askJenkins(question, { mcpUrl, token });
1724

1825
res.json({
1926
question,

server/src/agents/jenkins-agent.js

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,34 @@
1-
import path from "node:path";
21
import { Agent, run, MCPServerStreamableHttp } from "@openai/agents";
32

3+
const DEFAULT_JENKINS_MCP_URL = "http://192.168.1.35:8090/mcp-server/mcp";
44

5-
export async function askJenkins(question) {
5+
export async function askJenkins(question, options = {}) {
66
const user = process.env.JENKINS_USER;
7-
const token = process.env.JENKINS_TOKEN;
8-
// construct Basic Auth
9-
const authToken = Buffer.from(
10-
`${process.env.JENKINS_USER}:${process.env.JENKINS_TOKEN}`
11-
).toString("base64");
7+
const token = options.token || process.env.JENKINS_TOKEN;
8+
const mcpUrl = options.mcpUrl || process.env.JENKINS_MCP_URL || DEFAULT_JENKINS_MCP_URL;
129

13-
if (!user || !token) {
14-
throw new Error("JENKINS_USER or JENKINS_TOKEN is not set in the environment");
10+
if (!user) {
11+
throw new Error("JENKINS_USER is not set in the environment");
1512
}
1613

17-
// 1. URL change /mcp-server/mcp
18-
// 2. use requestInit.headers pass Authorization
19-
console.log(authToken);
14+
if (!token) {
15+
throw new Error("JENKINS_TOKEN is required. Provide it in the request body or environment");
16+
}
17+
18+
const authToken = Buffer.from(`${user}:${token}`).toString("base64");
19+
2020
const jenkinsMcp = new MCPServerStreamableHttp({
2121
name: "jenkins-mcp",
22-
url: "https://jenkins.ilessai.com/mcp-server/mcp",
22+
url: mcpUrl,
2323
requestInit: {
2424
headers: {
2525
Authorization: `Basic ${authToken}`,
2626
},
2727
},
2828
});
2929

30-
31-
await jenkinsMcp.connect();
32-
33-
3430
try {
35-
console.log("[askJenkins] connecting to Jenkins MCP…");
31+
console.log(`[askJenkins] connecting to Jenkins MCP at ${mcpUrl}…`);
3632
await jenkinsMcp.connect();
3733
console.log("[askJenkins] connected. Listing tools…");
3834

@@ -68,4 +64,4 @@ export async function askJenkins(question) {
6864
await jenkinsMcp.close();
6965
console.log("[askJenkins] MCP connection closed.");
7066
}
71-
}
67+
}

0 commit comments

Comments
 (0)