# Directory Structure
```
├── LICENSE
├── package.json
├── readme.md
├── src
│ └── index.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
```markdown
1 | # Jenkins Server MCP
2 |
3 | A Model Context Protocol (MCP) server that provides tools for interacting with Jenkins CI/CD servers. This server enables AI assistants to check build statuses, trigger builds, and retrieve build logs through a standardized interface.
4 |
5 | ## Installation
6 |
7 | 1. Clone this repository:
8 | ```bash
9 | git clone https://github.com/hekmon8/jenkins-server-mcp.git
10 | cd jenkins-server-mcp
11 | ```
12 |
13 | 2. Install dependencies:
14 | ```bash
15 | npm install
16 | ```
17 |
18 | 3. Build the project:
19 | ```bash
20 | npm run build
21 | ```
22 |
23 | ## Configuration
24 |
25 | The server requires the following environment variables:
26 |
27 | - `JENKINS_URL`: The URL of your Jenkins server
28 | - `JENKINS_USER`: Jenkins username for authentication
29 | - `JENKINS_TOKEN`: Jenkins API token for authentication
30 |
31 | Configure these in your MCP settings file:
32 |
33 | ### For Claude Desktop
34 |
35 | MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
36 | Windows: `%APPDATA%/Claude/claude_desktop_config.json`
37 |
38 | ```json
39 | {
40 | "mcpServers": {
41 | "jenkins-server": {
42 | "command": "node",
43 | "args": ["/path/to/jenkins-server-mcp/build/index.js"],
44 | "env": {
45 | "JENKINS_URL": "https://your-jenkins-server.com",
46 | "JENKINS_USER": "your-username",
47 | "JENKINS_TOKEN": "your-api-token"
48 | }
49 | }
50 | }
51 | }
52 | ```
53 |
54 | ## Tools and Usage
55 |
56 | ### 1. Get Build Status
57 |
58 | Get the status of a Jenkins build:
59 |
60 | ```typescript
61 | // Example usage
62 | const result = await mcpClient.useTool("jenkins-server", "get_build_status", {
63 | jobPath: "view/xxx_debug",
64 | buildNumber: "lastBuild" // Optional, defaults to lastBuild
65 | });
66 | ```
67 |
68 | Input Schema:
69 | ```json
70 | {
71 | "jobPath": "string", // Path to Jenkins job
72 | "buildNumber": "string" // Optional, build number or "lastBuild"
73 | }
74 | ```
75 |
76 | ### 2. Trigger Build
77 |
78 | Trigger a new Jenkins build with parameters:
79 |
80 | ```typescript
81 | // Example usage
82 | const result = await mcpClient.useTool("jenkins-server", "trigger_build", {
83 | jobPath: "view/xxx_debug",
84 | parameters: {
85 | BRANCH: "main",
86 | BUILD_TYPE: "debug"
87 | }
88 | });
89 | ```
90 |
91 | Input Schema:
92 | ```json
93 | {
94 | "jobPath": "string", // Path to Jenkins job
95 | "parameters": {
96 | // Build parameters as key-value pairs
97 | }
98 | }
99 | ```
100 |
101 | ### 3. Get Build Log
102 |
103 | Retrieve the console output of a Jenkins build:
104 |
105 | ```typescript
106 | // Example usage
107 | const result = await mcpClient.useTool("jenkins-server", "get_build_log", {
108 | jobPath: "view/xxx_debug",
109 | buildNumber: "lastBuild"
110 | });
111 | ```
112 |
113 | Input Schema:
114 | ```json
115 | {
116 | "jobPath": "string", // Path to Jenkins job
117 | "buildNumber": "string" // Build number or "lastBuild"
118 | }
119 | ```
120 |
121 | ## Development
122 |
123 | For development with auto-rebuild:
124 | ```bash
125 | npm run watch
126 | ```
127 |
128 | ### Debugging
129 |
130 | Since MCP servers communicate over stdio, you can use the MCP Inspector for debugging:
131 |
132 | ```bash
133 | npm run inspector
134 | ```
135 |
136 | This will provide a URL to access debugging tools in your browser.
137 |
138 | ## Thanks
139 |
140 | Thanks AIMCP(https://www.aimcp.info).
141 |
142 | ## License
143 |
144 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
145 |
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "Node16",
5 | "moduleResolution": "Node16",
6 | "outDir": "./build",
7 | "rootDir": "./src",
8 | "strict": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "forceConsistentCasingInFileNames": true
12 | },
13 | "include": ["src/**/*"],
14 | "exclude": ["node_modules"]
15 | }
16 |
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "jenkins-server",
3 | "version": "0.1.0",
4 | "description": "Jenkins Model Context Protocol server",
5 | "private": true,
6 | "type": "module",
7 | "bin": {
8 | "jenkins-server": "./build/index.js"
9 | },
10 | "files": [
11 | "build"
12 | ],
13 | "scripts": {
14 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
15 | "prepare": "npm run build",
16 | "watch": "tsc --watch",
17 | "inspector": "npx @modelcontextprotocol/inspector build/index.js"
18 | },
19 | "dependencies": {
20 | "@modelcontextprotocol/sdk": "0.6.0",
21 | "axios": "^1.7.9"
22 | },
23 | "devDependencies": {
24 | "@types/node": "^20.11.24",
25 | "typescript": "^5.3.3"
26 | }
27 | }
28 |
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env node
2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4 | import {
5 | CallToolRequestSchema,
6 | ErrorCode,
7 | ListToolsRequestSchema,
8 | McpError,
9 | } from '@modelcontextprotocol/sdk/types.js';
10 | import axios from 'axios';
11 |
12 | const JENKINS_URL = process.env.JENKINS_URL || '';
13 | const JENKINS_USER = process.env.JENKINS_USER || '';
14 | const JENKINS_TOKEN = process.env.JENKINS_TOKEN || '';
15 |
16 | interface BuildStatus {
17 | building: boolean;
18 | result: string | null;
19 | timestamp: number;
20 | duration: number;
21 | url: string;
22 | }
23 |
24 | class JenkinsServer {
25 | private server: Server;
26 | private axiosInstance;
27 |
28 | constructor() {
29 | this.server = new Server(
30 | {
31 | name: 'jenkins-server',
32 | version: '0.1.0',
33 | },
34 | {
35 | capabilities: {
36 | tools: {},
37 | },
38 | }
39 | );
40 |
41 | this.axiosInstance = axios.create({
42 | baseURL: JENKINS_URL,
43 | auth: {
44 | username: JENKINS_USER,
45 | password: JENKINS_TOKEN,
46 | },
47 | });
48 |
49 | this.setupToolHandlers();
50 |
51 | // Error handling
52 | this.server.onerror = (error) => console.error('[MCP Error]', error);
53 | process.on('SIGINT', async () => {
54 | await this.server.close();
55 | process.exit(0);
56 | });
57 | }
58 |
59 | private setupToolHandlers() {
60 | this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
61 | tools: [
62 | {
63 | name: 'get_build_status',
64 | description: 'Get the status of a Jenkins build',
65 | inputSchema: {
66 | type: 'object',
67 | properties: {
68 | jobPath: {
69 | type: 'string',
70 | description: 'Path to the Jenkins job (e.g., "view/xxx_debug")',
71 | },
72 | buildNumber: {
73 | type: 'string',
74 | description: 'Build number (use "lastBuild" for most recent)',
75 | },
76 | },
77 | required: ['jobPath'],
78 | },
79 | },
80 | {
81 | name: 'trigger_build',
82 | description: 'Trigger a new Jenkins build',
83 | inputSchema: {
84 | type: 'object',
85 | properties: {
86 | jobPath: {
87 | type: 'string',
88 | description: 'Path to the Jenkins job',
89 | },
90 | parameters: {
91 | type: 'object',
92 | description: 'Build parameters (optional)',
93 | additionalProperties: true,
94 | },
95 | },
96 | required: ['jobPath', 'parameters'],
97 | },
98 | },
99 | {
100 | name: 'get_build_log',
101 | description: 'Get the console output of a Jenkins build',
102 | inputSchema: {
103 | type: 'object',
104 | properties: {
105 | jobPath: {
106 | type: 'string',
107 | description: 'Path to the Jenkins job',
108 | },
109 | buildNumber: {
110 | type: 'string',
111 | description: 'Build number (use "lastBuild" for most recent)',
112 | },
113 | },
114 | required: ['jobPath', 'buildNumber'],
115 | },
116 | },
117 | ],
118 | }));
119 |
120 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
121 | try {
122 | switch (request.params.name) {
123 | case 'get_build_status':
124 | return await this.getBuildStatus(request.params.arguments);
125 | case 'trigger_build':
126 | return await this.triggerBuild(request.params.arguments);
127 | case 'get_build_log':
128 | return await this.getBuildLog(request.params.arguments);
129 | default:
130 | throw new McpError(
131 | ErrorCode.MethodNotFound,
132 | `Unknown tool: ${request.params.name}`
133 | );
134 | }
135 | } catch (error) {
136 | if (error instanceof McpError) {
137 | throw error;
138 | }
139 | if (axios.isAxiosError(error)) {
140 | throw new McpError(
141 | ErrorCode.InternalError,
142 | `Jenkins API error: ${error.response?.data?.message || error.message}`
143 | );
144 | }
145 | throw new McpError(ErrorCode.InternalError, 'Unknown error occurred');
146 | }
147 | });
148 | }
149 |
150 | private async getBuildStatus(args: any) {
151 | const buildNumber = args.buildNumber || 'lastBuild';
152 | const response = await this.axiosInstance.get<BuildStatus>(
153 | `/${args.jobPath}/${buildNumber}/api/json`
154 | );
155 |
156 | return {
157 | content: [
158 | {
159 | type: 'text',
160 | text: JSON.stringify({
161 | building: response.data.building,
162 | result: response.data.result,
163 | timestamp: response.data.timestamp,
164 | duration: response.data.duration,
165 | url: response.data.url,
166 | }, null, 2),
167 | },
168 | ],
169 | };
170 | }
171 |
172 | private async triggerBuild(args: any) {
173 | const params = new URLSearchParams();
174 | if (args.parameters) {
175 | Object.entries(args.parameters).forEach(([key, value]) => {
176 | params.append(key, String(value));
177 | });
178 | }
179 |
180 | await this.axiosInstance.post(
181 | `/${args.jobPath}/buildWithParameters`,
182 | params
183 | );
184 |
185 | return {
186 | content: [
187 | {
188 | type: 'text',
189 | text: 'Build triggered successfully',
190 | },
191 | ],
192 | };
193 | }
194 |
195 | private async getBuildLog(args: any) {
196 | const response = await this.axiosInstance.get(
197 | `/${args.jobPath}/${args.buildNumber}/consoleText`
198 | );
199 |
200 | return {
201 | content: [
202 | {
203 | type: 'text',
204 | text: response.data,
205 | },
206 | ],
207 | };
208 | }
209 |
210 | async run() {
211 | const transport = new StdioServerTransport();
212 | await this.server.connect(transport);
213 | console.error('Jenkins MCP server running on stdio');
214 | }
215 | }
216 |
217 | const server = new JenkinsServer();
218 | server.run().catch(console.error);
219 |
```