@@ -197,7 +197,16 @@ import {
197197 ListEventsSchema ,
198198 GetProjectEventsSchema ,
199199 GitLabEvent ,
200- ExecuteGraphQLSchema
200+ ExecuteGraphQLSchema ,
201+ type GitLabRelease ,
202+ GitLabReleaseSchema ,
203+ ListReleasesSchema ,
204+ GetReleaseSchema ,
205+ CreateReleaseSchema ,
206+ UpdateReleaseSchema ,
207+ DeleteReleaseSchema ,
208+ CreateReleaseEvidenceSchema ,
209+ DownloadReleaseAssetSchema ,
201210} from "./schemas.js" ;
202211
203212import { randomUUID } from "crypto" ;
@@ -973,6 +982,41 @@ const allTools = [
973982 description : "List all visible events for a specified project. Note: before/after parameters accept date format YYYY-MM-DD only" ,
974983 inputSchema : toJSONSchema ( GetProjectEventsSchema ) ,
975984 } ,
985+ {
986+ name : "list_releases" ,
987+ description : "List all releases for a project" ,
988+ inputSchema : toJSONSchema ( ListReleasesSchema ) ,
989+ } ,
990+ {
991+ name : "get_release" ,
992+ description : "Get a release by tag name" ,
993+ inputSchema : toJSONSchema ( GetReleaseSchema ) ,
994+ } ,
995+ {
996+ name : "create_release" ,
997+ description : "Create a new release in a GitLab project" ,
998+ inputSchema : toJSONSchema ( CreateReleaseSchema ) ,
999+ } ,
1000+ {
1001+ name : "update_release" ,
1002+ description : "Update an existing release in a GitLab project" ,
1003+ inputSchema : toJSONSchema ( UpdateReleaseSchema ) ,
1004+ } ,
1005+ {
1006+ name : "delete_release" ,
1007+ description : "Delete a release from a GitLab project (does not delete the associated tag)" ,
1008+ inputSchema : toJSONSchema ( DeleteReleaseSchema ) ,
1009+ } ,
1010+ {
1011+ name : "create_release_evidence" ,
1012+ description : "Create release evidence for an existing release (GitLab Premium/Ultimate only)" ,
1013+ inputSchema : toJSONSchema ( CreateReleaseEvidenceSchema ) ,
1014+ } ,
1015+ {
1016+ name : "download_release_asset" ,
1017+ description : "Download a release asset file by direct asset path" ,
1018+ inputSchema : toJSONSchema ( DownloadReleaseAssetSchema ) ,
1019+ } ,
9761020] ;
9771021
9781022// Define which tools are read-only
@@ -1023,6 +1067,9 @@ const readOnlyTools = [
10231067 "download_attachment" ,
10241068 "list_events" ,
10251069 "get_project_events" ,
1070+ "list_releases" ,
1071+ "get_release" ,
1072+ "download_release_asset" ,
10261073] ;
10271074
10281075// Define which tools are related to wiki and can be toggled by USE_GITLAB_WIKI
@@ -4461,6 +4508,200 @@ async function getProjectEvents(projectId: string, options: Omit<z.infer<typeof
44614508 return GitLabEventSchema . array ( ) . parse ( data ) ;
44624509}
44634510
4511+ /**
4512+ * List all releases for a project
4513+ *
4514+ * @param projectId The ID or URL-encoded path of the project
4515+ * @param options Optional parameters for listing releases
4516+ * @returns Array of GitLab releases
4517+ */
4518+ async function listReleases (
4519+ projectId : string ,
4520+ options : Omit < z . infer < typeof ListReleasesSchema > , "project_id" > = { }
4521+ ) : Promise < GitLabRelease [ ] > {
4522+ const effectiveProjectId = getEffectiveProjectId ( projectId ) ;
4523+ const url = new URL (
4524+ `${ GITLAB_API_URL } /projects/${ encodeURIComponent ( effectiveProjectId ) } /releases`
4525+ ) ;
4526+
4527+ // Add query parameters
4528+ Object . entries ( options ) . forEach ( ( [ key , value ] ) => {
4529+ if ( value !== undefined ) {
4530+ url . searchParams . append ( key , value . toString ( ) ) ;
4531+ }
4532+ } ) ;
4533+
4534+ const response = await fetch ( url . toString ( ) , {
4535+ ...DEFAULT_FETCH_CONFIG ,
4536+ } ) ;
4537+
4538+ await handleGitLabError ( response ) ;
4539+
4540+ const data = await response . json ( ) ;
4541+ return GitLabReleaseSchema . array ( ) . parse ( data ) ;
4542+ }
4543+
4544+ /**
4545+ * Get a release by tag name
4546+ *
4547+ * @param projectId The ID or URL-encoded path of the project
4548+ * @param tagName The Git tag the release is associated with
4549+ * @param includeHtmlDescription If true, includes HTML rendered Markdown
4550+ * @returns GitLab release
4551+ */
4552+ async function getRelease (
4553+ projectId : string ,
4554+ tagName : string ,
4555+ includeHtmlDescription ?: boolean
4556+ ) : Promise < GitLabRelease > {
4557+ const effectiveProjectId = getEffectiveProjectId ( projectId ) ;
4558+ const url = new URL (
4559+ `${ GITLAB_API_URL } /projects/${ encodeURIComponent ( effectiveProjectId ) } /releases/${ encodeURIComponent ( tagName ) } `
4560+ ) ;
4561+
4562+ if ( includeHtmlDescription !== undefined ) {
4563+ url . searchParams . append ( "include_html_description" , includeHtmlDescription . toString ( ) ) ;
4564+ }
4565+
4566+ const response = await fetch ( url . toString ( ) , {
4567+ ...DEFAULT_FETCH_CONFIG ,
4568+ } ) ;
4569+
4570+ await handleGitLabError ( response ) ;
4571+
4572+ const data = await response . json ( ) ;
4573+ return GitLabReleaseSchema . parse ( data ) ;
4574+ }
4575+
4576+ /**
4577+ * Create a new release
4578+ *
4579+ * @param projectId The ID or URL-encoded path of the project
4580+ * @param options Options for creating the release
4581+ * @returns Created GitLab release
4582+ */
4583+ async function createRelease (
4584+ projectId : string ,
4585+ options : Omit < z . infer < typeof CreateReleaseSchema > , "project_id" >
4586+ ) : Promise < GitLabRelease > {
4587+ const effectiveProjectId = getEffectiveProjectId ( projectId ) ;
4588+
4589+ const response = await fetch (
4590+ `${ GITLAB_API_URL } /projects/${ encodeURIComponent ( effectiveProjectId ) } /releases` ,
4591+ {
4592+ ...DEFAULT_FETCH_CONFIG ,
4593+ method : "POST" ,
4594+ body : JSON . stringify ( options ) ,
4595+ }
4596+ ) ;
4597+
4598+ await handleGitLabError ( response ) ;
4599+
4600+ const data = await response . json ( ) ;
4601+ return GitLabReleaseSchema . parse ( data ) ;
4602+ }
4603+
4604+ /**
4605+ * Update an existing release
4606+ *
4607+ * @param projectId The ID or URL-encoded path of the project
4608+ * @param tagName The Git tag the release is associated with
4609+ * @param options Options for updating the release
4610+ * @returns Updated GitLab release
4611+ */
4612+ async function updateRelease (
4613+ projectId : string ,
4614+ tagName : string ,
4615+ options : Omit < z . infer < typeof UpdateReleaseSchema > , "project_id " | "tag_name ">
4616+ ) : Promise < GitLabRelease > {
4617+ const effectiveProjectId = getEffectiveProjectId ( projectId ) ;
4618+
4619+ const response = await fetch (
4620+ `${ GITLAB_API_URL } /projects/${ encodeURIComponent ( effectiveProjectId ) } /releases/${ encodeURIComponent ( tagName ) } ` ,
4621+ {
4622+ ...DEFAULT_FETCH_CONFIG ,
4623+ method : "PUT" ,
4624+ body : JSON . stringify ( options ) ,
4625+ }
4626+ ) ;
4627+
4628+ await handleGitLabError ( response ) ;
4629+
4630+ const data = await response . json ( ) ;
4631+ return GitLabReleaseSchema . parse ( data ) ;
4632+ }
4633+
4634+ /**
4635+ * Delete a release
4636+ *
4637+ * @param projectId The ID or URL-encoded path of the project
4638+ * @param tagName The Git tag the release is associated with
4639+ * @returns Deleted GitLab release
4640+ */
4641+ async function deleteRelease ( projectId : string , tagName : string ) : Promise < GitLabRelease > {
4642+ const effectiveProjectId = getEffectiveProjectId ( projectId ) ;
4643+
4644+ const response = await fetch (
4645+ `${ GITLAB_API_URL } /projects/${ encodeURIComponent ( effectiveProjectId ) } /releases/${ encodeURIComponent ( tagName ) } ` ,
4646+ {
4647+ ...DEFAULT_FETCH_CONFIG ,
4648+ method : "DELETE" ,
4649+ }
4650+ ) ;
4651+
4652+ await handleGitLabError ( response ) ;
4653+
4654+ const data = await response . json ( ) ;
4655+ return GitLabReleaseSchema . parse ( data ) ;
4656+ }
4657+
4658+ /**
4659+ * Create release evidence (GitLab Premium/Ultimate only)
4660+ *
4661+ * @param projectId The ID or URL-encoded path of the project
4662+ * @param tagName The Git tag the release is associated with
4663+ */
4664+ async function createReleaseEvidence ( projectId : string , tagName : string ) : Promise < void > {
4665+ const effectiveProjectId = getEffectiveProjectId ( projectId ) ;
4666+
4667+ const response = await fetch (
4668+ `${ GITLAB_API_URL } /projects/${ encodeURIComponent ( effectiveProjectId ) } /releases/${ encodeURIComponent ( tagName ) } /evidence` ,
4669+ {
4670+ ...DEFAULT_FETCH_CONFIG ,
4671+ method : "POST" ,
4672+ }
4673+ ) ;
4674+
4675+ await handleGitLabError ( response ) ;
4676+ }
4677+
4678+ /**
4679+ * Download a release asset
4680+ *
4681+ * @param projectId The ID or URL-encoded path of the project
4682+ * @param tagName The Git tag the release is associated with
4683+ * @param directAssetPath Path to the release asset file
4684+ * @returns The asset file content
4685+ */
4686+ async function downloadReleaseAsset (
4687+ projectId : string ,
4688+ tagName : string ,
4689+ directAssetPath : string
4690+ ) : Promise < string > {
4691+ const effectiveProjectId = getEffectiveProjectId ( projectId ) ;
4692+
4693+ const response = await fetch (
4694+ `${ GITLAB_API_URL } /projects/${ encodeURIComponent ( effectiveProjectId ) } /releases/${ encodeURIComponent ( tagName ) } /downloads/${ directAssetPath } ` ,
4695+ {
4696+ ...DEFAULT_FETCH_CONFIG ,
4697+ }
4698+ ) ;
4699+
4700+ await handleGitLabError ( response ) ;
4701+
4702+ return await response . text ( ) ;
4703+ }
4704+
44644705server . setRequestHandler ( ListToolsRequestSchema , async ( ) => {
44654706 // Apply read-only filter first
44664707 const tools0 = GITLAB_READ_ONLY_MODE
@@ -5651,6 +5892,91 @@ server.setRequestHandler(CallToolRequestSchema, async (request: any) => {
56515892 } ;
56525893 }
56535894
5895+ case "list_releases" : {
5896+ const args = ListReleasesSchema . parse ( request . params . arguments ) ;
5897+ const { project_id, ...options } = args ;
5898+ const releases = await listReleases ( project_id , options ) ;
5899+ return {
5900+ content : [ { type : "text" , text : JSON . stringify ( releases , null , 2 ) } ] ,
5901+ } ;
5902+ }
5903+
5904+ case "get_release" : {
5905+ const args = GetReleaseSchema . parse ( request . params . arguments ) ;
5906+ const release = await getRelease (
5907+ args . project_id ,
5908+ args . tag_name ,
5909+ args . include_html_description
5910+ ) ;
5911+ return {
5912+ content : [ { type : "text" , text : JSON . stringify ( release , null , 2 ) } ] ,
5913+ } ;
5914+ }
5915+
5916+ case "create_release" : {
5917+ const args = CreateReleaseSchema . parse ( request . params . arguments ) ;
5918+ const { project_id, ...options } = args ;
5919+ const release = await createRelease ( project_id , options ) ;
5920+ return {
5921+ content : [ { type : "text" , text : JSON . stringify ( release , null , 2 ) } ] ,
5922+ } ;
5923+ }
5924+
5925+ case "update_release" : {
5926+ const args = UpdateReleaseSchema . parse ( request . params . arguments ) ;
5927+ const { project_id, tag_name, ...options } = args ;
5928+ const release = await updateRelease ( project_id , tag_name , options ) ;
5929+ return {
5930+ content : [ { type : "text" , text : JSON . stringify ( release , null , 2 ) } ] ,
5931+ } ;
5932+ }
5933+
5934+ case "delete_release" : {
5935+ const args = DeleteReleaseSchema . parse ( request . params . arguments ) ;
5936+ const release = await deleteRelease ( args . project_id , args . tag_name ) ;
5937+ return {
5938+ content : [
5939+ {
5940+ type : "text" ,
5941+ text : JSON . stringify (
5942+ { status : "success" , message : "Release deleted successfully" , release } ,
5943+ null ,
5944+ 2
5945+ ) ,
5946+ } ,
5947+ ] ,
5948+ } ;
5949+ }
5950+
5951+ case "create_release_evidence" : {
5952+ const args = CreateReleaseEvidenceSchema . parse ( request . params . arguments ) ;
5953+ await createReleaseEvidence ( args . project_id , args . tag_name ) ;
5954+ return {
5955+ content : [
5956+ {
5957+ type : "text" ,
5958+ text : JSON . stringify (
5959+ { status : "success" , message : "Release evidence created successfully" } ,
5960+ null ,
5961+ 2
5962+ ) ,
5963+ } ,
5964+ ] ,
5965+ } ;
5966+ }
5967+
5968+ case "download_release_asset" : {
5969+ const args = DownloadReleaseAssetSchema . parse ( request . params . arguments ) ;
5970+ const assetContent = await downloadReleaseAsset (
5971+ args . project_id ,
5972+ args . tag_name ,
5973+ args . direct_asset_path
5974+ ) ;
5975+ return {
5976+ content : [ { type : "text" , text : assetContent } ] ,
5977+ } ;
5978+ }
5979+
56545980 default :
56555981 throw new Error ( `Unknown tool: ${ request . params . name } ` ) ;
56565982 }
@@ -6165,4 +6491,4 @@ async function runServer() {
61656491runServer ( ) . catch ( error => {
61666492 logger . error ( "Fatal error in main():" , error ) ;
61676493 process . exit ( 1 ) ;
6168- } ) ;
6494+ } ) ;
0 commit comments