feat(release): 1.0.44 adds pipeline jobs tool
This commit is contained in:
@ -75,7 +75,6 @@ When using with the Claude App, you need to set up your API key and URLs directl
|
||||
## Tools 🛠️
|
||||
|
||||
+<!-- TOOLS-START -->
|
||||
|
||||
1. `create_or_update_file` - Create or update a single file in a GitLab project
|
||||
2. `search_repositories` - Search for GitLab projects
|
||||
3. `create_repository` - Create a new GitLab project
|
||||
@ -93,7 +92,7 @@ When using with the Claude App, you need to set up your API key and URLs directl
|
||||
15. `mr_discussions` - List discussion items for a merge request
|
||||
16. `update_merge_request_note` - Modify an existing merge request thread note
|
||||
17. `create_merge_request_note` - Add a new note to an existing merge request thread
|
||||
18. `update_issue_note` - Update the content of an existing issue note
|
||||
18. `update_issue_note` - Modify an existing issue thread note
|
||||
19. `create_issue_note` - Add a new note to an existing issue thread
|
||||
20. `list_issues` - List issues in a GitLab project with filtering options
|
||||
21. `get_issue` - Get details of a specific issue in a GitLab project
|
||||
@ -121,4 +120,9 @@ When using with the Claude App, you need to set up your API key and URLs directl
|
||||
43. `update_wiki_page` - Update an existing wiki page in a GitLab project
|
||||
44. `delete_wiki_page` - Delete a wiki page from a GitLab project
|
||||
45. `get_repository_tree` - Get the repository tree for a GitLab project (list files and directories)
|
||||
46. `list_pipelines` - List pipelines in a GitLab project with filtering options
|
||||
47. `get_pipeline` - Get details of a specific pipeline in a GitLab project
|
||||
48. `list_pipeline_jobs` - List all jobs in a specific pipeline
|
||||
49. `get_pipeline_job` - Get details of a GitLab pipeline job number
|
||||
50. `get_pipeline_job_output` - Get the output/trace of a GitLab pipeline job number
|
||||
<!-- TOOLS-END -->
|
||||
|
276
index.ts
276
index.ts
@ -17,7 +17,6 @@ import { fileURLToPath } from "url";
|
||||
import { dirname } from "path";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
// Add type imports for proxy agents
|
||||
import { Agent } from "http";
|
||||
import { URL } from "url";
|
||||
@ -84,6 +83,15 @@ import {
|
||||
UpdateWikiPageSchema,
|
||||
DeleteWikiPageSchema,
|
||||
GitLabWikiPageSchema,
|
||||
GetRepositoryTreeSchema,
|
||||
GitLabTreeItemSchema,
|
||||
GitLabPipelineSchema,
|
||||
GetPipelineSchema,
|
||||
ListPipelinesSchema,
|
||||
ListPipelineJobsSchema,
|
||||
// pipeline job schemas
|
||||
GetPipelineJobOutputSchema,
|
||||
GitLabPipelineJobSchema,
|
||||
// Discussion Schemas
|
||||
GitLabDiscussionNoteSchema, // Added
|
||||
GitLabDiscussionSchema,
|
||||
@ -108,6 +116,11 @@ import {
|
||||
type GitLabNamespaceExistsResponse,
|
||||
type GitLabProject,
|
||||
type GitLabLabel,
|
||||
type GitLabPipeline,
|
||||
type ListPipelinesOptions,
|
||||
type GetPipelineOptions,
|
||||
type ListPipelineJobsOptions,
|
||||
type GitLabPipelineJob,
|
||||
// Discussion Types
|
||||
type GitLabDiscussionNote, // Added
|
||||
type GitLabDiscussion,
|
||||
@ -117,8 +130,6 @@ import {
|
||||
type UpdateWikiPageOptions,
|
||||
type DeleteWikiPageOptions,
|
||||
type GitLabWikiPage,
|
||||
GitLabTreeItemSchema,
|
||||
GetRepositoryTreeSchema,
|
||||
type GitLabTreeItem,
|
||||
type GetRepositoryTreeOptions,
|
||||
UpdateIssueNoteSchema,
|
||||
@ -430,6 +441,31 @@ const allTools = [
|
||||
"Get the repository tree for a GitLab project (list files and directories)",
|
||||
inputSchema: zodToJsonSchema(GetRepositoryTreeSchema),
|
||||
},
|
||||
{
|
||||
name: "list_pipelines",
|
||||
description: "List pipelines in a GitLab project with filtering options",
|
||||
inputSchema: zodToJsonSchema(ListPipelinesSchema),
|
||||
},
|
||||
{
|
||||
name: "get_pipeline",
|
||||
description: "Get details of a specific pipeline in a GitLab project",
|
||||
inputSchema: zodToJsonSchema(GetPipelineSchema),
|
||||
},
|
||||
{
|
||||
name: "list_pipeline_jobs",
|
||||
description: "List all jobs in a specific pipeline",
|
||||
inputSchema: zodToJsonSchema(ListPipelineJobsSchema),
|
||||
},
|
||||
{
|
||||
name: "get_pipeline_job",
|
||||
description: "Get details of a GitLab pipeline job number",
|
||||
inputSchema: zodToJsonSchema(GetPipelineJobOutputSchema),
|
||||
},
|
||||
{
|
||||
name: "get_pipeline_job_output",
|
||||
description: "Get the output/trace of a GitLab pipeline job number",
|
||||
inputSchema: zodToJsonSchema(GetPipelineJobOutputSchema),
|
||||
},
|
||||
];
|
||||
|
||||
// Define which tools are read-only
|
||||
@ -448,6 +484,11 @@ const readOnlyTools = [
|
||||
"get_namespace",
|
||||
"verify_namespace",
|
||||
"get_project",
|
||||
"get_pipeline",
|
||||
"list_pipelines",
|
||||
"list_pipeline_jobs",
|
||||
"get_pipeline_job",
|
||||
"get_pipeline_job_output",
|
||||
"list_projects",
|
||||
"list_labels",
|
||||
"get_label",
|
||||
@ -2300,6 +2341,166 @@ async function deleteWikiPage(projectId: string, slug: string): Promise<void> {
|
||||
await handleGitLabError(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* List pipelines in a GitLab project
|
||||
*
|
||||
* @param {string} projectId - The ID or URL-encoded path of the project
|
||||
* @param {ListPipelinesOptions} options - Options for filtering pipelines
|
||||
* @returns {Promise<GitLabPipeline[]>} List of pipelines
|
||||
*/
|
||||
async function listPipelines(
|
||||
projectId: string,
|
||||
options: Omit<ListPipelinesOptions, "project_id"> = {}
|
||||
): Promise<GitLabPipeline[]> {
|
||||
projectId = decodeURIComponent(projectId); // Decode project ID
|
||||
const url = new URL(
|
||||
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/pipelines`
|
||||
);
|
||||
|
||||
// Add all query parameters
|
||||
Object.entries(options).forEach(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
url.searchParams.append(key, value.toString());
|
||||
}
|
||||
});
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
...DEFAULT_FETCH_CONFIG,
|
||||
});
|
||||
|
||||
await handleGitLabError(response);
|
||||
const data = await response.json();
|
||||
return z.array(GitLabPipelineSchema).parse(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get details of a specific pipeline
|
||||
*
|
||||
* @param {string} projectId - The ID or URL-encoded path of the project
|
||||
* @param {number} pipelineId - The ID of the pipeline
|
||||
* @returns {Promise<GitLabPipeline>} Pipeline details
|
||||
*/
|
||||
async function getPipeline(
|
||||
projectId: string,
|
||||
pipelineId: number
|
||||
): Promise<GitLabPipeline> {
|
||||
projectId = decodeURIComponent(projectId); // Decode project ID
|
||||
const url = new URL(
|
||||
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}`
|
||||
);
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
...DEFAULT_FETCH_CONFIG,
|
||||
});
|
||||
|
||||
if (response.status === 404) {
|
||||
throw new Error(`Pipeline not found`);
|
||||
}
|
||||
|
||||
await handleGitLabError(response);
|
||||
const data = await response.json();
|
||||
return GitLabPipelineSchema.parse(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all jobs in a specific pipeline
|
||||
*
|
||||
* @param {string} projectId - The ID or URL-encoded path of the project
|
||||
* @param {number} pipelineId - The ID of the pipeline
|
||||
* @param {Object} options - Options for filtering jobs
|
||||
* @returns {Promise<GitLabPipelineJob[]>} List of pipeline jobs
|
||||
*/
|
||||
async function listPipelineJobs(
|
||||
projectId: string,
|
||||
pipelineId: number,
|
||||
options: Omit<ListPipelineJobsOptions, "project_id" | "pipeline_id"> = {}
|
||||
): Promise<GitLabPipelineJob[]> {
|
||||
projectId = decodeURIComponent(projectId); // Decode project ID
|
||||
const url = new URL(
|
||||
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}/jobs`
|
||||
);
|
||||
|
||||
// Add all query parameters
|
||||
Object.entries(options).forEach(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
if (typeof value === "boolean") {
|
||||
url.searchParams.append(key, value ? "true" : "false");
|
||||
} else {
|
||||
url.searchParams.append(key, value.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
...DEFAULT_FETCH_CONFIG,
|
||||
});
|
||||
|
||||
if (response.status === 404) {
|
||||
throw new Error(`Pipeline not found`);
|
||||
}
|
||||
|
||||
await handleGitLabError(response);
|
||||
const data = await response.json();
|
||||
return z.array(GitLabPipelineJobSchema).parse(data);
|
||||
}
|
||||
async function getPipelineJob(
|
||||
projectId: string,
|
||||
jobId: number
|
||||
): Promise<GitLabPipelineJob> {
|
||||
projectId = decodeURIComponent(projectId); // Decode project ID
|
||||
const url = new URL(
|
||||
`${GITLAB_API_URL}/projects/${encodeURIComponent(
|
||||
projectId
|
||||
)}/jobs/${jobId}`
|
||||
);
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
...DEFAULT_FETCH_CONFIG,
|
||||
});
|
||||
|
||||
if (response.status === 404) {
|
||||
throw new Error(`Job not found`);
|
||||
}
|
||||
|
||||
await handleGitLabError(response);
|
||||
const data = await response.json();
|
||||
return GitLabPipelineJobSchema.parse(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the output/trace of a pipeline job
|
||||
*
|
||||
* @param {string} projectId - The ID or URL-encoded path of the project
|
||||
* @param {number} jobId - The ID of the job
|
||||
* @returns {Promise<string>} The job output/trace
|
||||
*/
|
||||
async function getPipelineJobOutput(
|
||||
projectId: string,
|
||||
jobId: number
|
||||
): Promise<string> {
|
||||
projectId = decodeURIComponent(projectId); // Decode project ID
|
||||
const url = new URL(
|
||||
`${GITLAB_API_URL}/projects/${encodeURIComponent(
|
||||
projectId
|
||||
)}/jobs/${jobId}/trace`
|
||||
);
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
...DEFAULT_FETCH_CONFIG,
|
||||
headers: {
|
||||
...DEFAULT_HEADERS,
|
||||
Accept: "text/plain", // Override Accept header to get plain text
|
||||
},
|
||||
});
|
||||
|
||||
if (response.status === 404) {
|
||||
throw new Error(`Job trace not found or job is not finished yet`);
|
||||
}
|
||||
|
||||
await handleGitLabError(response);
|
||||
return await response.text();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the repository tree for a project
|
||||
* @param {string} projectId - The ID or URL-encoded path of the project
|
||||
@ -3030,6 +3231,75 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
};
|
||||
}
|
||||
|
||||
case "list_pipelines": {
|
||||
const args = ListPipelinesSchema.parse(request.params.arguments);
|
||||
const { project_id, ...options } = args;
|
||||
const pipelines = await listPipelines(project_id, options);
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(pipelines, null, 2) }],
|
||||
};
|
||||
}
|
||||
|
||||
case "get_pipeline": {
|
||||
const { project_id, pipeline_id } = GetPipelineSchema.parse(
|
||||
request.params.arguments
|
||||
);
|
||||
const pipeline = await getPipeline(project_id, pipeline_id);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(pipeline, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case "list_pipeline_jobs": {
|
||||
const { project_id, pipeline_id, ...options } = ListPipelineJobsSchema.parse(
|
||||
request.params.arguments
|
||||
);
|
||||
const jobs = await listPipelineJobs(project_id, pipeline_id, options);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(jobs, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case "get_pipeline_job": {
|
||||
const { project_id, job_id } = GetPipelineJobOutputSchema.parse(
|
||||
request.params.arguments
|
||||
);
|
||||
const jobDetails = await getPipelineJob(project_id, job_id);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(jobDetails, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case "get_pipeline_job_output": {
|
||||
const { project_id, job_id } = GetPipelineJobOutputSchema.parse(
|
||||
request.params.arguments
|
||||
);
|
||||
const jobOutput = await getPipelineJobOutput(project_id, job_id);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: jobOutput,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${request.params.name}`);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@zereight/mcp-gitlab",
|
||||
"version": "1.0.43",
|
||||
"version": "1.0.44",
|
||||
"description": "MCP server for using the GitLab API",
|
||||
"license": "MIT",
|
||||
"author": "zereight",
|
||||
|
118
schemas.ts
118
schemas.ts
@ -7,6 +7,119 @@ export const GitLabAuthorSchema = z.object({
|
||||
date: z.string(),
|
||||
});
|
||||
|
||||
// Pipeline related schemas
|
||||
export const GitLabPipelineSchema = z.object({
|
||||
id: z.number(),
|
||||
project_id: z.number(),
|
||||
sha: z.string(),
|
||||
ref: z.string(),
|
||||
status: z.string(),
|
||||
source: z.string().optional(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string(),
|
||||
web_url: z.string(),
|
||||
duration: z.number().nullable().optional(),
|
||||
started_at: z.string().nullable().optional(),
|
||||
finished_at: z.string().nullable().optional(),
|
||||
coverage: z.number().nullable().optional(),
|
||||
user: z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
username: z.string(),
|
||||
avatar_url: z.string().nullable().optional(),
|
||||
}).optional(),
|
||||
detailed_status: z.object({
|
||||
icon: z.string().optional(),
|
||||
text: z.string().optional(),
|
||||
label: z.string().optional(),
|
||||
group: z.string().optional(),
|
||||
tooltip: z.string().optional(),
|
||||
has_details: z.boolean().optional(),
|
||||
details_path: z.string().optional(),
|
||||
illustration: z.object({
|
||||
image: z.string().optional(),
|
||||
size: z.string().optional(),
|
||||
title: z.string().optional(),
|
||||
}).optional(),
|
||||
favicon: z.string().optional(),
|
||||
}).optional(),
|
||||
});
|
||||
|
||||
// Pipeline job related schemas
|
||||
export const GitLabPipelineJobSchema = z.object({
|
||||
id: z.number(),
|
||||
status: z.string(),
|
||||
stage: z.string(),
|
||||
name: z.string(),
|
||||
ref: z.string(),
|
||||
tag: z.boolean(),
|
||||
coverage: z.number().nullable().optional(),
|
||||
created_at: z.string(),
|
||||
started_at: z.string().nullable().optional(),
|
||||
finished_at: z.string().nullable().optional(),
|
||||
duration: z.number().nullable().optional(),
|
||||
user: z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
username: z.string(),
|
||||
avatar_url: z.string().nullable().optional(),
|
||||
}).optional(),
|
||||
commit: z.object({
|
||||
id: z.string(),
|
||||
short_id: z.string(),
|
||||
title: z.string(),
|
||||
author_name: z.string(),
|
||||
author_email: z.string(),
|
||||
}).optional(),
|
||||
pipeline: z.object({
|
||||
id: z.number(),
|
||||
project_id: z.number(),
|
||||
status: z.string(),
|
||||
ref: z.string(),
|
||||
sha: z.string(),
|
||||
}).optional(),
|
||||
web_url: z.string().optional(),
|
||||
});
|
||||
|
||||
// Schema for listing pipelines
|
||||
export const ListPipelinesSchema = z.object({
|
||||
project_id: z.string().describe("Project ID or URL-encoded path"),
|
||||
scope: z.enum(['running', 'pending', 'finished', 'branches', 'tags']).optional().describe("The scope of pipelines"),
|
||||
status: z.enum(['created', 'waiting_for_resource', 'preparing', 'pending', 'running', 'success', 'failed', 'canceled', 'skipped', 'manual', 'scheduled']).optional().describe("The status of pipelines"),
|
||||
ref: z.string().optional().describe("The ref of pipelines"),
|
||||
sha: z.string().optional().describe("The SHA of pipelines"),
|
||||
yaml_errors: z.boolean().optional().describe("Returns pipelines with invalid configurations"),
|
||||
username: z.string().optional().describe("The username of the user who triggered pipelines"),
|
||||
updated_after: z.string().optional().describe("Return pipelines updated after the specified date"),
|
||||
updated_before: z.string().optional().describe("Return pipelines updated before the specified date"),
|
||||
order_by: z.enum(['id', 'status', 'ref', 'updated_at', 'user_id']).optional().describe("Order pipelines by"),
|
||||
sort: z.enum(['asc', 'desc']).optional().describe("Sort pipelines"),
|
||||
page: z.number().optional().describe("Page number for pagination"),
|
||||
per_page: z.number().optional().describe("Number of items per page (max 100)"),
|
||||
});
|
||||
|
||||
// Schema for getting a specific pipeline
|
||||
export const GetPipelineSchema = z.object({
|
||||
project_id: z.string().describe("Project ID or URL-encoded path"),
|
||||
pipeline_id: z.number().describe("The ID of the pipeline"),
|
||||
});
|
||||
|
||||
// Schema for listing jobs in a pipeline
|
||||
export const ListPipelineJobsSchema = z.object({
|
||||
project_id: z.string().describe("Project ID or URL-encoded path"),
|
||||
pipeline_id: z.number().describe("The ID of the pipeline"),
|
||||
scope: z.enum(['created', 'pending', 'running', 'failed', 'success', 'canceled', 'skipped', 'manual']).optional().describe("The scope of jobs to show"),
|
||||
include_retried: z.boolean().optional().describe("Whether to include retried jobs"),
|
||||
page: z.number().optional().describe("Page number for pagination"),
|
||||
per_page: z.number().optional().describe("Number of items per page (max 100)"),
|
||||
});
|
||||
|
||||
// Schema for the input parameters for pipeline job operations
|
||||
export const GetPipelineJobOutputSchema = z.object({
|
||||
project_id: z.string().describe("Project ID or URL-encoded path"),
|
||||
job_id: z.number().describe("The ID of the job"),
|
||||
});
|
||||
|
||||
// Namespace related schemas
|
||||
|
||||
// Base schema for project-related operations
|
||||
@ -1120,3 +1233,8 @@ export type GetRepositoryTreeOptions = z.infer<typeof GetRepositoryTreeSchema>;
|
||||
export type MergeRequestThreadPosition = z.infer<typeof MergeRequestThreadPositionSchema>;
|
||||
export type CreateMergeRequestThreadOptions = z.infer<typeof CreateMergeRequestThreadSchema>;
|
||||
export type CreateMergeRequestNoteOptions = z.infer<typeof CreateMergeRequestNoteSchema>;
|
||||
export type GitLabPipelineJob = z.infer<typeof GitLabPipelineJobSchema>;
|
||||
export type GitLabPipeline = z.infer<typeof GitLabPipelineSchema>;
|
||||
export type ListPipelinesOptions = z.infer<typeof ListPipelinesSchema>;
|
||||
export type GetPipelineOptions = z.infer<typeof GetPipelineSchema>;
|
||||
export type ListPipelineJobsOptions = z.infer<typeof ListPipelineJobsSchema>;
|
||||
|
Reference in New Issue
Block a user