@@ -17,7 +17,6 @@ import { fileURLToPath } from "url";
1717import { dirname } from "path" ;
1818import fs from "fs" ;
1919import path from "path" ;
20-
2120// Add type imports for proxy agents
2221import { Agent } from "http" ;
2322import { URL } from "url" ;
@@ -84,6 +83,15 @@ import {
8483 UpdateWikiPageSchema ,
8584 DeleteWikiPageSchema ,
8685 GitLabWikiPageSchema ,
86+ GetRepositoryTreeSchema ,
87+ GitLabTreeItemSchema ,
88+ GitLabPipelineSchema ,
89+ GetPipelineSchema ,
90+ ListPipelinesSchema ,
91+ ListPipelineJobsSchema ,
92+ // pipeline job schemas
93+ GetPipelineJobOutputSchema ,
94+ GitLabPipelineJobSchema ,
8795 // Discussion Schemas
8896 GitLabDiscussionNoteSchema , // Added
8997 GitLabDiscussionSchema ,
@@ -108,6 +116,11 @@ import {
108116 type GitLabNamespaceExistsResponse ,
109117 type GitLabProject ,
110118 type GitLabLabel ,
119+ type GitLabPipeline ,
120+ type ListPipelinesOptions ,
121+ type GetPipelineOptions ,
122+ type ListPipelineJobsOptions ,
123+ type GitLabPipelineJob ,
111124 // Discussion Types
112125 type GitLabDiscussionNote , // Added
113126 type GitLabDiscussion ,
@@ -117,8 +130,6 @@ import {
117130 type UpdateWikiPageOptions ,
118131 type DeleteWikiPageOptions ,
119132 type GitLabWikiPage ,
120- GitLabTreeItemSchema ,
121- GetRepositoryTreeSchema ,
122133 type GitLabTreeItem ,
123134 type GetRepositoryTreeOptions ,
124135 UpdateIssueNoteSchema ,
@@ -430,6 +441,31 @@ const allTools = [
430441 "Get the repository tree for a GitLab project (list files and directories)" ,
431442 inputSchema : zodToJsonSchema ( GetRepositoryTreeSchema ) ,
432443 } ,
444+ {
445+ name : "list_pipelines" ,
446+ description : "List pipelines in a GitLab project with filtering options" ,
447+ inputSchema : zodToJsonSchema ( ListPipelinesSchema ) ,
448+ } ,
449+ {
450+ name : "get_pipeline" ,
451+ description : "Get details of a specific pipeline in a GitLab project" ,
452+ inputSchema : zodToJsonSchema ( GetPipelineSchema ) ,
453+ } ,
454+ {
455+ name : "list_pipeline_jobs" ,
456+ description : "List all jobs in a specific pipeline" ,
457+ inputSchema : zodToJsonSchema ( ListPipelineJobsSchema ) ,
458+ } ,
459+ {
460+ name : "get_pipeline_job" ,
461+ description : "Get details of a GitLab pipeline job number" ,
462+ inputSchema : zodToJsonSchema ( GetPipelineJobOutputSchema ) ,
463+ } ,
464+ {
465+ name : "get_pipeline_job_output" ,
466+ description : "Get the output/trace of a GitLab pipeline job number" ,
467+ inputSchema : zodToJsonSchema ( GetPipelineJobOutputSchema ) ,
468+ } ,
433469] ;
434470
435471// Define which tools are read-only
@@ -448,6 +484,11 @@ const readOnlyTools = [
448484 "get_namespace" ,
449485 "verify_namespace" ,
450486 "get_project" ,
487+ "get_pipeline" ,
488+ "list_pipelines" ,
489+ "list_pipeline_jobs" ,
490+ "get_pipeline_job" ,
491+ "get_pipeline_job_output" ,
451492 "list_projects" ,
452493 "list_labels" ,
453494 "get_label" ,
@@ -2300,6 +2341,166 @@ async function deleteWikiPage(projectId: string, slug: string): Promise<void> {
23002341 await handleGitLabError ( response ) ;
23012342}
23022343
2344+ /**
2345+ * List pipelines in a GitLab project
2346+ *
2347+ * @param {string } projectId - The ID or URL-encoded path of the project
2348+ * @param {ListPipelinesOptions } options - Options for filtering pipelines
2349+ * @returns {Promise<GitLabPipeline[]> } List of pipelines
2350+ */
2351+ async function listPipelines (
2352+ projectId : string ,
2353+ options : Omit < ListPipelinesOptions , "project_id" > = { }
2354+ ) : Promise < GitLabPipeline [ ] > {
2355+ projectId = decodeURIComponent ( projectId ) ; // Decode project ID
2356+ const url = new URL (
2357+ `${ GITLAB_API_URL } /projects/${ encodeURIComponent ( projectId ) } /pipelines`
2358+ ) ;
2359+
2360+ // Add all query parameters
2361+ Object . entries ( options ) . forEach ( ( [ key , value ] ) => {
2362+ if ( value !== undefined ) {
2363+ url . searchParams . append ( key , value . toString ( ) ) ;
2364+ }
2365+ } ) ;
2366+
2367+ const response = await fetch ( url . toString ( ) , {
2368+ ...DEFAULT_FETCH_CONFIG ,
2369+ } ) ;
2370+
2371+ await handleGitLabError ( response ) ;
2372+ const data = await response . json ( ) ;
2373+ return z . array ( GitLabPipelineSchema ) . parse ( data ) ;
2374+ }
2375+
2376+ /**
2377+ * Get details of a specific pipeline
2378+ *
2379+ * @param {string } projectId - The ID or URL-encoded path of the project
2380+ * @param {number } pipelineId - The ID of the pipeline
2381+ * @returns {Promise<GitLabPipeline> } Pipeline details
2382+ */
2383+ async function getPipeline (
2384+ projectId : string ,
2385+ pipelineId : number
2386+ ) : Promise < GitLabPipeline > {
2387+ projectId = decodeURIComponent ( projectId ) ; // Decode project ID
2388+ const url = new URL (
2389+ `${ GITLAB_API_URL } /projects/${ encodeURIComponent ( projectId ) } /pipelines/${ pipelineId } `
2390+ ) ;
2391+
2392+ const response = await fetch ( url . toString ( ) , {
2393+ ...DEFAULT_FETCH_CONFIG ,
2394+ } ) ;
2395+
2396+ if ( response . status === 404 ) {
2397+ throw new Error ( `Pipeline not found` ) ;
2398+ }
2399+
2400+ await handleGitLabError ( response ) ;
2401+ const data = await response . json ( ) ;
2402+ return GitLabPipelineSchema . parse ( data ) ;
2403+ }
2404+
2405+ /**
2406+ * List all jobs in a specific pipeline
2407+ *
2408+ * @param {string } projectId - The ID or URL-encoded path of the project
2409+ * @param {number } pipelineId - The ID of the pipeline
2410+ * @param {Object } options - Options for filtering jobs
2411+ * @returns {Promise<GitLabPipelineJob[]> } List of pipeline jobs
2412+ */
2413+ async function listPipelineJobs (
2414+ projectId : string ,
2415+ pipelineId : number ,
2416+ options : Omit < ListPipelineJobsOptions , "project_id" | "pipeline_id" > = { }
2417+ ) : Promise < GitLabPipelineJob [ ] > {
2418+ projectId = decodeURIComponent ( projectId ) ; // Decode project ID
2419+ const url = new URL (
2420+ `${ GITLAB_API_URL } /projects/${ encodeURIComponent ( projectId ) } /pipelines/${ pipelineId } /jobs`
2421+ ) ;
2422+
2423+ // Add all query parameters
2424+ Object . entries ( options ) . forEach ( ( [ key , value ] ) => {
2425+ if ( value !== undefined ) {
2426+ if ( typeof value === "boolean" ) {
2427+ url . searchParams . append ( key , value ? "true" : "false" ) ;
2428+ } else {
2429+ url . searchParams . append ( key , value . toString ( ) ) ;
2430+ }
2431+ }
2432+ } ) ;
2433+
2434+ const response = await fetch ( url . toString ( ) , {
2435+ ...DEFAULT_FETCH_CONFIG ,
2436+ } ) ;
2437+
2438+ if ( response . status === 404 ) {
2439+ throw new Error ( `Pipeline not found` ) ;
2440+ }
2441+
2442+ await handleGitLabError ( response ) ;
2443+ const data = await response . json ( ) ;
2444+ return z . array ( GitLabPipelineJobSchema ) . parse ( data ) ;
2445+ }
2446+ async function getPipelineJob (
2447+ projectId : string ,
2448+ jobId : number
2449+ ) : Promise < GitLabPipelineJob > {
2450+ projectId = decodeURIComponent ( projectId ) ; // Decode project ID
2451+ const url = new URL (
2452+ `${ GITLAB_API_URL } /projects/${ encodeURIComponent (
2453+ projectId
2454+ ) } /jobs/${ jobId } `
2455+ ) ;
2456+
2457+ const response = await fetch ( url . toString ( ) , {
2458+ ...DEFAULT_FETCH_CONFIG ,
2459+ } ) ;
2460+
2461+ if ( response . status === 404 ) {
2462+ throw new Error ( `Job not found` ) ;
2463+ }
2464+
2465+ await handleGitLabError ( response ) ;
2466+ const data = await response . json ( ) ;
2467+ return GitLabPipelineJobSchema . parse ( data ) ;
2468+ }
2469+
2470+ /**
2471+ * Get the output/trace of a pipeline job
2472+ *
2473+ * @param {string } projectId - The ID or URL-encoded path of the project
2474+ * @param {number } jobId - The ID of the job
2475+ * @returns {Promise<string> } The job output/trace
2476+ */
2477+ async function getPipelineJobOutput (
2478+ projectId : string ,
2479+ jobId : number
2480+ ) : Promise < string > {
2481+ projectId = decodeURIComponent ( projectId ) ; // Decode project ID
2482+ const url = new URL (
2483+ `${ GITLAB_API_URL } /projects/${ encodeURIComponent (
2484+ projectId
2485+ ) } /jobs/${ jobId } /trace`
2486+ ) ;
2487+
2488+ const response = await fetch ( url . toString ( ) , {
2489+ ...DEFAULT_FETCH_CONFIG ,
2490+ headers : {
2491+ ...DEFAULT_HEADERS ,
2492+ Accept : "text/plain" , // Override Accept header to get plain text
2493+ } ,
2494+ } ) ;
2495+
2496+ if ( response . status === 404 ) {
2497+ throw new Error ( `Job trace not found or job is not finished yet` ) ;
2498+ }
2499+
2500+ await handleGitLabError ( response ) ;
2501+ return await response . text ( ) ;
2502+ }
2503+
23032504/**
23042505 * Get the repository tree for a project
23052506 * @param {string } projectId - The ID or URL-encoded path of the project
@@ -3030,6 +3231,75 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
30303231 } ;
30313232 }
30323233
3234+ case "list_pipelines" : {
3235+ const args = ListPipelinesSchema . parse ( request . params . arguments ) ;
3236+ const { project_id, ...options } = args ;
3237+ const pipelines = await listPipelines ( project_id , options ) ;
3238+ return {
3239+ content : [ { type : "text" , text : JSON . stringify ( pipelines , null , 2 ) } ] ,
3240+ } ;
3241+ }
3242+
3243+ case "get_pipeline" : {
3244+ const { project_id, pipeline_id } = GetPipelineSchema . parse (
3245+ request . params . arguments
3246+ ) ;
3247+ const pipeline = await getPipeline ( project_id , pipeline_id ) ;
3248+ return {
3249+ content : [
3250+ {
3251+ type : "text" ,
3252+ text : JSON . stringify ( pipeline , null , 2 ) ,
3253+ } ,
3254+ ] ,
3255+ } ;
3256+ }
3257+
3258+ case "list_pipeline_jobs" : {
3259+ const { project_id, pipeline_id, ...options } = ListPipelineJobsSchema . parse (
3260+ request . params . arguments
3261+ ) ;
3262+ const jobs = await listPipelineJobs ( project_id , pipeline_id , options ) ;
3263+ return {
3264+ content : [
3265+ {
3266+ type : "text" ,
3267+ text : JSON . stringify ( jobs , null , 2 ) ,
3268+ } ,
3269+ ] ,
3270+ } ;
3271+ }
3272+
3273+ case "get_pipeline_job" : {
3274+ const { project_id, job_id } = GetPipelineJobOutputSchema . parse (
3275+ request . params . arguments
3276+ ) ;
3277+ const jobDetails = await getPipelineJob ( project_id , job_id ) ;
3278+ return {
3279+ content : [
3280+ {
3281+ type : "text" ,
3282+ text : JSON . stringify ( jobDetails , null , 2 ) ,
3283+ } ,
3284+ ] ,
3285+ } ;
3286+ }
3287+
3288+ case "get_pipeline_job_output" : {
3289+ const { project_id, job_id } = GetPipelineJobOutputSchema . parse (
3290+ request . params . arguments
3291+ ) ;
3292+ const jobOutput = await getPipelineJobOutput ( project_id , job_id ) ;
3293+ return {
3294+ content : [
3295+ {
3296+ type : "text" ,
3297+ text : jobOutput ,
3298+ } ,
3299+ ] ,
3300+ } ;
3301+ }
3302+
30333303 default :
30343304 throw new Error ( `Unknown tool: ${ request . params . name } ` ) ;
30353305 }
0 commit comments