opencode-gitlab-plugin 2.3.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +452 -27
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.cjs +118 -36
- package/dist/mcp-server.cjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [2.5.0](https://gitlab.com/vglafirov/opencode-gitlab-plugin/compare/v2.4.0...v2.5.0) (2026-05-20)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### ✨ Features
|
|
9
|
+
|
|
10
|
+
* **notes:** add gitlab_update_note tool for editing comments ([54a87e7](https://gitlab.com/vglafirov/opencode-gitlab-plugin/commit/54a87e70d4d276c077ab25619e1d910d6d709575))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### 🐛 Bug Fixes
|
|
14
|
+
|
|
15
|
+
* handle ToolResult union type in mcp-adapter and tests ([a91d5dd](https://gitlab.com/vglafirov/opencode-gitlab-plugin/commit/a91d5ddc5574d1cf9b7dab2d9624cdddeefd158f))
|
|
16
|
+
* **tests:** update orbit.test.ts for ToolResult type ([29fecec](https://gitlab.com/vglafirov/opencode-gitlab-plugin/commit/29fecec6a8ba436b3d95b720cf67f162c8c62104))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### ♻️ Code Refactoring
|
|
20
|
+
|
|
21
|
+
* **notes:** address review feedback on !28 ([11e13ba](https://gitlab.com/vglafirov/opencode-gitlab-plugin/commit/11e13ba5a6dafeb77dfb256bde3e9b7b1f5ddd32))
|
|
22
|
+
|
|
23
|
+
## [2.4.0](https://gitlab.com/vglafirov/opencode-gitlab-plugin/compare/v2.3.0...v2.4.0) (2026-05-19)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### ✨ Features
|
|
27
|
+
|
|
28
|
+
* **orbit:** add GitLab Orbit Knowledge Graph API tools ([17fd452](https://gitlab.com/vglafirov/opencode-gitlab-plugin/commit/17fd4521ec32f760b26a6596c5d023d7b55eed47))
|
|
29
|
+
|
|
5
30
|
## [2.3.0](https://gitlab.com/vglafirov/opencode-gitlab-plugin/compare/v2.2.0...v2.3.0) (2026-05-18)
|
|
6
31
|
|
|
7
32
|
|
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { Plugin } from '@opencode-ai/plugin';
|
|
|
20
20
|
* - Discussions: unified tools for all resource types (list, get, create/reply, resolve)
|
|
21
21
|
* - Notes: list/get notes (flat view) for MR, issue, epic, snippet
|
|
22
22
|
* - Git: execute safe, read-only git commands in repository
|
|
23
|
+
* - Orbit: Knowledge Graph API (status, schema, query, neighbors)
|
|
23
24
|
*/
|
|
24
25
|
declare const gitlabPlugin: Plugin;
|
|
25
26
|
|
package/dist/index.js
CHANGED
|
@@ -438,6 +438,14 @@ var MergeRequestsClient = class extends GitLabApiClient {
|
|
|
438
438
|
{ body }
|
|
439
439
|
);
|
|
440
440
|
}
|
|
441
|
+
async updateMrNote(projectId, mrIid, noteId, body) {
|
|
442
|
+
const encodedProject = this.encodeProjectId(projectId);
|
|
443
|
+
return this.fetch(
|
|
444
|
+
"PUT",
|
|
445
|
+
`/projects/${encodedProject}/merge_requests/${mrIid}/notes/${noteId}`,
|
|
446
|
+
{ body }
|
|
447
|
+
);
|
|
448
|
+
}
|
|
441
449
|
async createMergeRequest(projectId, options) {
|
|
442
450
|
const encodedProject = this.encodeProjectId(projectId);
|
|
443
451
|
return this.fetch(
|
|
@@ -816,6 +824,14 @@ var IssuesClient = class extends GitLabApiClient {
|
|
|
816
824
|
`/projects/${encodedProject}/issues/${issueIid}/notes/${noteId}`
|
|
817
825
|
);
|
|
818
826
|
}
|
|
827
|
+
async updateIssueNote(projectId, issueIid, noteId, body) {
|
|
828
|
+
const encodedProject = this.encodeProjectId(projectId);
|
|
829
|
+
return this.fetch(
|
|
830
|
+
"PUT",
|
|
831
|
+
`/projects/${encodedProject}/issues/${issueIid}/notes/${noteId}`,
|
|
832
|
+
{ body }
|
|
833
|
+
);
|
|
834
|
+
}
|
|
819
835
|
};
|
|
820
836
|
|
|
821
837
|
// src/client/work-items.ts
|
|
@@ -2150,6 +2166,14 @@ var EpicsClient = class extends GitLabApiClient {
|
|
|
2150
2166
|
`/groups/${encodedGroup}/epics/${epicIid}/notes/${noteId}`
|
|
2151
2167
|
);
|
|
2152
2168
|
}
|
|
2169
|
+
async updateEpicNote(groupId, epicIid, noteId, body) {
|
|
2170
|
+
const encodedGroup = encodeURIComponent(groupId);
|
|
2171
|
+
return this.fetch(
|
|
2172
|
+
"PUT",
|
|
2173
|
+
`/groups/${encodedGroup}/epics/${epicIid}/notes/${noteId}`,
|
|
2174
|
+
{ body }
|
|
2175
|
+
);
|
|
2176
|
+
}
|
|
2153
2177
|
};
|
|
2154
2178
|
|
|
2155
2179
|
// src/client/snippets.ts
|
|
@@ -2278,6 +2302,14 @@ var SnippetsClient = class extends GitLabApiClient {
|
|
|
2278
2302
|
{ body }
|
|
2279
2303
|
);
|
|
2280
2304
|
}
|
|
2305
|
+
async updateSnippetNote(projectId, snippetId, noteId, body) {
|
|
2306
|
+
const encodedProject = this.encodeProjectId(projectId);
|
|
2307
|
+
return this.fetch(
|
|
2308
|
+
"PUT",
|
|
2309
|
+
`/projects/${encodedProject}/snippets/${snippetId}/notes/${noteId}`,
|
|
2310
|
+
{ body }
|
|
2311
|
+
);
|
|
2312
|
+
}
|
|
2281
2313
|
};
|
|
2282
2314
|
|
|
2283
2315
|
// src/client/discussions.ts
|
|
@@ -4489,19 +4521,22 @@ function filterNotesByResolved(result, resolved) {
|
|
|
4489
4521
|
}
|
|
4490
4522
|
};
|
|
4491
4523
|
}
|
|
4492
|
-
var
|
|
4493
|
-
var
|
|
4524
|
+
var NOTE_RESOURCE_TYPES = ["merge_request", "issue", "epic", "snippet"];
|
|
4525
|
+
var GET_NOTE_RESOURCE_TYPES = ["issue", "epic"];
|
|
4494
4526
|
function validationError3(param, resourceType) {
|
|
4495
4527
|
return new Error(
|
|
4496
4528
|
`Missing required parameter: '${param}' is required for resource_type '${resourceType}'`
|
|
4497
4529
|
);
|
|
4498
4530
|
}
|
|
4499
|
-
function
|
|
4500
|
-
if (!
|
|
4531
|
+
function validateNoteParams(resourceType, args, opts) {
|
|
4532
|
+
if (!opts.allowedTypes.includes(resourceType)) {
|
|
4501
4533
|
throw new Error(
|
|
4502
|
-
`Invalid resource_type '${resourceType}'. Must be one of: ${
|
|
4534
|
+
`Invalid resource_type '${resourceType}'. Must be one of: ${opts.allowedTypes.join(", ")}`
|
|
4503
4535
|
);
|
|
4504
4536
|
}
|
|
4537
|
+
if (opts.requireNoteId && args.note_id == null) {
|
|
4538
|
+
throw validationError3("note_id", resourceType);
|
|
4539
|
+
}
|
|
4505
4540
|
switch (resourceType) {
|
|
4506
4541
|
case "merge_request":
|
|
4507
4542
|
case "issue":
|
|
@@ -4518,24 +4553,6 @@ function validateListCreateParams(resourceType, args) {
|
|
|
4518
4553
|
break;
|
|
4519
4554
|
}
|
|
4520
4555
|
}
|
|
4521
|
-
function validateGetNoteParams(resourceType, args) {
|
|
4522
|
-
if (!VALID_GET_NOTE_TYPES.includes(resourceType)) {
|
|
4523
|
-
throw new Error(
|
|
4524
|
-
`Invalid resource_type '${resourceType}'. Must be one of: ${VALID_GET_NOTE_TYPES.join(", ")}`
|
|
4525
|
-
);
|
|
4526
|
-
}
|
|
4527
|
-
if (args.note_id == null) throw validationError3("note_id", resourceType);
|
|
4528
|
-
switch (resourceType) {
|
|
4529
|
-
case "issue":
|
|
4530
|
-
if (!args.project_id) throw validationError3("project_id", resourceType);
|
|
4531
|
-
if (args.iid == null) throw validationError3("iid", resourceType);
|
|
4532
|
-
break;
|
|
4533
|
-
case "epic":
|
|
4534
|
-
if (!args.group_id) throw validationError3("group_id", resourceType);
|
|
4535
|
-
if (args.iid == null) throw validationError3("iid", resourceType);
|
|
4536
|
-
break;
|
|
4537
|
-
}
|
|
4538
|
-
}
|
|
4539
4556
|
var notesUnifiedTools = {
|
|
4540
4557
|
/**
|
|
4541
4558
|
* List notes/comments for any GitLab resource type
|
|
@@ -4571,7 +4588,7 @@ Examples:
|
|
|
4571
4588
|
before: z14.string().optional().describe("Cursor for backward pagination - use startCursor from previous response")
|
|
4572
4589
|
},
|
|
4573
4590
|
execute: async (args, _ctx) => {
|
|
4574
|
-
|
|
4591
|
+
validateNoteParams(args.resource_type, args, { allowedTypes: NOTE_RESOURCE_TYPES });
|
|
4575
4592
|
const client = getGitLabClient();
|
|
4576
4593
|
const paginationOptions = {
|
|
4577
4594
|
first: args.first,
|
|
@@ -4625,7 +4642,10 @@ Examples:
|
|
|
4625
4642
|
iid: z14.number().describe("Internal ID of the issue or epic")
|
|
4626
4643
|
},
|
|
4627
4644
|
execute: async (args, _ctx) => {
|
|
4628
|
-
|
|
4645
|
+
validateNoteParams(args.resource_type, args, {
|
|
4646
|
+
allowedTypes: GET_NOTE_RESOURCE_TYPES,
|
|
4647
|
+
requireNoteId: true
|
|
4648
|
+
});
|
|
4629
4649
|
const client = getGitLabClient();
|
|
4630
4650
|
switch (args.resource_type) {
|
|
4631
4651
|
case "issue":
|
|
@@ -4669,7 +4689,7 @@ Examples:
|
|
|
4669
4689
|
snippet_id: z14.number().optional().describe("Snippet ID (required for snippet)")
|
|
4670
4690
|
},
|
|
4671
4691
|
execute: async (args, _ctx) => {
|
|
4672
|
-
|
|
4692
|
+
validateNoteParams(args.resource_type, args, { allowedTypes: NOTE_RESOURCE_TYPES });
|
|
4673
4693
|
const client = getGitLabClient();
|
|
4674
4694
|
switch (args.resource_type) {
|
|
4675
4695
|
case "merge_request":
|
|
@@ -4700,6 +4720,68 @@ Examples:
|
|
|
4700
4720
|
throw new Error(`Unsupported resource type: ${args.resource_type}`);
|
|
4701
4721
|
}
|
|
4702
4722
|
}
|
|
4723
|
+
}),
|
|
4724
|
+
/**
|
|
4725
|
+
* Update an existing note/comment on any GitLab resource
|
|
4726
|
+
*/
|
|
4727
|
+
gitlab_update_note: tool14({
|
|
4728
|
+
description: `Update an existing note/comment on any GitLab resource.
|
|
4729
|
+
Modifies the body content of an existing note. You can only update notes you authored.
|
|
4730
|
+
|
|
4731
|
+
Examples:
|
|
4732
|
+
- MR note: resource_type="merge_request", project_id="group/project", iid=123, note_id=456, body="Updated comment"
|
|
4733
|
+
- Issue note: resource_type="issue", project_id="group/project", iid=456, note_id=789, body="Updated comment"
|
|
4734
|
+
- Epic note: resource_type="epic", group_id="my-group", iid=1, note_id=456, body="Updated comment"
|
|
4735
|
+
- Snippet note: resource_type="snippet", project_id="group/project", snippet_id=789, note_id=123, body="Updated comment"`,
|
|
4736
|
+
args: {
|
|
4737
|
+
resource_type: z14.enum(["merge_request", "issue", "epic", "snippet"]).describe("Type of GitLab resource"),
|
|
4738
|
+
note_id: z14.number().describe("The ID of the note to update"),
|
|
4739
|
+
body: z14.string().describe("The new content for the note (supports Markdown)"),
|
|
4740
|
+
project_id: z14.string().optional().describe("Project ID or path. Required for merge_request, issue, snippet"),
|
|
4741
|
+
group_id: z14.string().optional().describe("Group ID or path. Required for epic"),
|
|
4742
|
+
iid: z14.number().optional().describe("Internal ID of the resource (for merge_request, issue, epic)"),
|
|
4743
|
+
snippet_id: z14.number().optional().describe("Snippet ID (required for snippet)")
|
|
4744
|
+
},
|
|
4745
|
+
execute: async (args, _ctx) => {
|
|
4746
|
+
validateNoteParams(args.resource_type, args, {
|
|
4747
|
+
allowedTypes: NOTE_RESOURCE_TYPES,
|
|
4748
|
+
requireNoteId: true
|
|
4749
|
+
});
|
|
4750
|
+
const client = getGitLabClient();
|
|
4751
|
+
switch (args.resource_type) {
|
|
4752
|
+
case "merge_request":
|
|
4753
|
+
return JSON.stringify(
|
|
4754
|
+
await client.updateMrNote(args.project_id, args.iid, args.note_id, args.body),
|
|
4755
|
+
null,
|
|
4756
|
+
2
|
|
4757
|
+
);
|
|
4758
|
+
case "issue":
|
|
4759
|
+
return JSON.stringify(
|
|
4760
|
+
await client.updateIssueNote(args.project_id, args.iid, args.note_id, args.body),
|
|
4761
|
+
null,
|
|
4762
|
+
2
|
|
4763
|
+
);
|
|
4764
|
+
case "epic":
|
|
4765
|
+
return JSON.stringify(
|
|
4766
|
+
await client.updateEpicNote(args.group_id, args.iid, args.note_id, args.body),
|
|
4767
|
+
null,
|
|
4768
|
+
2
|
|
4769
|
+
);
|
|
4770
|
+
case "snippet":
|
|
4771
|
+
return JSON.stringify(
|
|
4772
|
+
await client.updateSnippetNote(
|
|
4773
|
+
args.project_id,
|
|
4774
|
+
args.snippet_id,
|
|
4775
|
+
args.note_id,
|
|
4776
|
+
args.body
|
|
4777
|
+
),
|
|
4778
|
+
null,
|
|
4779
|
+
2
|
|
4780
|
+
);
|
|
4781
|
+
default:
|
|
4782
|
+
throw new Error(`Unsupported resource type: ${args.resource_type}`);
|
|
4783
|
+
}
|
|
4784
|
+
}
|
|
4703
4785
|
})
|
|
4704
4786
|
};
|
|
4705
4787
|
|
|
@@ -5138,6 +5220,348 @@ Examples:
|
|
|
5138
5220
|
})
|
|
5139
5221
|
};
|
|
5140
5222
|
|
|
5223
|
+
// src/tools/orbit.ts
|
|
5224
|
+
import { tool as tool18 } from "@opencode-ai/plugin";
|
|
5225
|
+
var z18 = tool18.schema;
|
|
5226
|
+
var orbitTools = {
|
|
5227
|
+
gitlab_orbit_status: tool18({
|
|
5228
|
+
description: `Check GitLab Orbit Knowledge Graph API availability and health.
|
|
5229
|
+
|
|
5230
|
+
Returns the status of the Orbit API for the connected GitLab instance.
|
|
5231
|
+
Orbit is GitLab's Knowledge Graph API that provides rich SDLC graph data.
|
|
5232
|
+
|
|
5233
|
+
Note: Orbit is only available on Premium/Ultimate tiers and requires the feature flag to be enabled.
|
|
5234
|
+
If unavailable, returns an error with suggestions for REST API alternatives.`,
|
|
5235
|
+
args: {},
|
|
5236
|
+
execute: async (_args, _ctx) => {
|
|
5237
|
+
const client = getGitLabClient();
|
|
5238
|
+
try {
|
|
5239
|
+
const status = await client.fetch("GET", "/orbit/status");
|
|
5240
|
+
return JSON.stringify(
|
|
5241
|
+
{
|
|
5242
|
+
...status,
|
|
5243
|
+
available: true,
|
|
5244
|
+
message: "Orbit Knowledge Graph API is available"
|
|
5245
|
+
},
|
|
5246
|
+
null,
|
|
5247
|
+
2
|
|
5248
|
+
);
|
|
5249
|
+
} catch (error) {
|
|
5250
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5251
|
+
if (errorMessage.includes("404") || errorMessage.includes("not found")) {
|
|
5252
|
+
return JSON.stringify(
|
|
5253
|
+
{
|
|
5254
|
+
available: false,
|
|
5255
|
+
error: "Orbit API not available",
|
|
5256
|
+
reason: "The Orbit Knowledge Graph API is not enabled on this GitLab instance. Orbit requires GitLab Premium/Ultimate tier and the feature flag to be enabled.",
|
|
5257
|
+
alternatives: [
|
|
5258
|
+
"Use REST API endpoints for MRs, issues, pipelines",
|
|
5259
|
+
"Use GraphQL API for complex queries",
|
|
5260
|
+
"Use gitlab_search for finding related entities"
|
|
5261
|
+
]
|
|
5262
|
+
},
|
|
5263
|
+
null,
|
|
5264
|
+
2
|
|
5265
|
+
);
|
|
5266
|
+
}
|
|
5267
|
+
if (errorMessage.includes("403") || errorMessage.includes("forbidden")) {
|
|
5268
|
+
return JSON.stringify(
|
|
5269
|
+
{
|
|
5270
|
+
available: false,
|
|
5271
|
+
error: "Orbit API access forbidden",
|
|
5272
|
+
reason: "Your access token does not have permission to use the Orbit API. This may require specific scopes or Premium/Ultimate tier.",
|
|
5273
|
+
alternatives: [
|
|
5274
|
+
"Check token scopes include api or read_api",
|
|
5275
|
+
"Verify your GitLab tier supports Orbit",
|
|
5276
|
+
"Use REST/GraphQL APIs as fallback"
|
|
5277
|
+
]
|
|
5278
|
+
},
|
|
5279
|
+
null,
|
|
5280
|
+
2
|
|
5281
|
+
);
|
|
5282
|
+
}
|
|
5283
|
+
if (errorMessage.includes("502")) {
|
|
5284
|
+
return JSON.stringify(
|
|
5285
|
+
{
|
|
5286
|
+
available: false,
|
|
5287
|
+
error: "Orbit API temporarily unavailable",
|
|
5288
|
+
reason: "The Orbit API returned a 502 error. This may be temporary.",
|
|
5289
|
+
suggestion: "Try again later or use REST/GraphQL APIs as fallback."
|
|
5290
|
+
},
|
|
5291
|
+
null,
|
|
5292
|
+
2
|
|
5293
|
+
);
|
|
5294
|
+
}
|
|
5295
|
+
return JSON.stringify(
|
|
5296
|
+
{
|
|
5297
|
+
available: false,
|
|
5298
|
+
error: "Orbit API error",
|
|
5299
|
+
message: errorMessage,
|
|
5300
|
+
alternatives: ["Use REST/GraphQL APIs as fallback"]
|
|
5301
|
+
},
|
|
5302
|
+
null,
|
|
5303
|
+
2
|
|
5304
|
+
);
|
|
5305
|
+
}
|
|
5306
|
+
}
|
|
5307
|
+
}),
|
|
5308
|
+
gitlab_orbit_schema: tool18({
|
|
5309
|
+
description: `Get the Orbit Knowledge Graph schema.
|
|
5310
|
+
|
|
5311
|
+
Returns the available node types, relationship types, and their properties.
|
|
5312
|
+
Use this to understand what entities and relationships can be queried.
|
|
5313
|
+
|
|
5314
|
+
Node type domains include:
|
|
5315
|
+
- Core: Project, Group, User
|
|
5316
|
+
- Source Code: File, Definition, Commit
|
|
5317
|
+
- Code Review: MergeRequest, Diff, Review
|
|
5318
|
+
- CI/CD: Pipeline, Job, Deployment
|
|
5319
|
+
- Planning: WorkItem (issues, epics, tasks)
|
|
5320
|
+
- Security: Vulnerability
|
|
5321
|
+
|
|
5322
|
+
Note: Requires Orbit API to be available (Premium/Ultimate tier).`,
|
|
5323
|
+
args: {},
|
|
5324
|
+
execute: async (_args, _ctx) => {
|
|
5325
|
+
const client = getGitLabClient();
|
|
5326
|
+
try {
|
|
5327
|
+
const schema = await client.fetch("GET", "/orbit/schema");
|
|
5328
|
+
return JSON.stringify(schema, null, 2);
|
|
5329
|
+
} catch (error) {
|
|
5330
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5331
|
+
if (errorMessage.includes("404") || errorMessage.includes("not found")) {
|
|
5332
|
+
return JSON.stringify(
|
|
5333
|
+
{
|
|
5334
|
+
error: "Orbit API not available",
|
|
5335
|
+
suggestion: "Use gitlab_orbit_status to check API availability"
|
|
5336
|
+
},
|
|
5337
|
+
null,
|
|
5338
|
+
2
|
|
5339
|
+
);
|
|
5340
|
+
}
|
|
5341
|
+
if (errorMessage.includes("502")) {
|
|
5342
|
+
return JSON.stringify(
|
|
5343
|
+
{
|
|
5344
|
+
error: "Orbit API temporarily unavailable",
|
|
5345
|
+
suggestion: "The Orbit API returned a 502 error. This may be temporary - try again later."
|
|
5346
|
+
},
|
|
5347
|
+
null,
|
|
5348
|
+
2
|
|
5349
|
+
);
|
|
5350
|
+
}
|
|
5351
|
+
throw error;
|
|
5352
|
+
}
|
|
5353
|
+
}
|
|
5354
|
+
}),
|
|
5355
|
+
gitlab_orbit_query: tool18({
|
|
5356
|
+
description: `Execute a query against the Orbit Knowledge Graph.
|
|
5357
|
+
|
|
5358
|
+
Query types:
|
|
5359
|
+
- traversal: Walk the graph from a starting node
|
|
5360
|
+
- neighbors: Get immediate neighbors of a node
|
|
5361
|
+
|
|
5362
|
+
IMPORTANT: The query format uses variable names for node references.
|
|
5363
|
+
- "id" in node definition is a VARIABLE NAME (alias), not the actual entity ID
|
|
5364
|
+
- Actual IDs go in "filters" with {"field": {"op": "eq", "value": X}} format
|
|
5365
|
+
|
|
5366
|
+
Example neighbors query to get a project's connections:
|
|
5367
|
+
{
|
|
5368
|
+
"query_type": "neighbors",
|
|
5369
|
+
"node": {
|
|
5370
|
+
"entity": "Project",
|
|
5371
|
+
"id": "p",
|
|
5372
|
+
"filters": {"id": {"op": "eq", "value": 278964}}
|
|
5373
|
+
},
|
|
5374
|
+
"neighbors": {"node": "p", "direction": "incoming"},
|
|
5375
|
+
"aggregations": [],
|
|
5376
|
+
"path": {"type": "shortest", "from": "p", "to": "p", "max_depth": 1}
|
|
5377
|
+
}
|
|
5378
|
+
|
|
5379
|
+
Example traversal query:
|
|
5380
|
+
{
|
|
5381
|
+
"query_type": "traversal",
|
|
5382
|
+
"node": {
|
|
5383
|
+
"entity": "MergeRequest",
|
|
5384
|
+
"id": "mr",
|
|
5385
|
+
"filters": {"id": {"op": "eq", "value": 377844873}}
|
|
5386
|
+
},
|
|
5387
|
+
"neighbors": {"node": "mr", "direction": "both"},
|
|
5388
|
+
"aggregations": [],
|
|
5389
|
+
"path": {"type": "shortest", "from": "mr", "to": "mr", "max_depth": 1}
|
|
5390
|
+
}
|
|
5391
|
+
|
|
5392
|
+
Note: Use gitlab_get_merge_request to get the internal ID from an IID.
|
|
5393
|
+
Returns nodes array and edges array with relationship types.`,
|
|
5394
|
+
args: {
|
|
5395
|
+
// Using a JSON string rather than a structured zod schema because the Orbit query
|
|
5396
|
+
// format is still evolving. This allows flexibility as the API changes without
|
|
5397
|
+
// requiring schema updates for each iteration.
|
|
5398
|
+
query: z18.string().describe(
|
|
5399
|
+
"JSON string containing the full query object. Must include query_type, node, neighbors, aggregations, and path fields. See description for format details."
|
|
5400
|
+
),
|
|
5401
|
+
limit: z18.number().optional().describe("Maximum number of results (default: 30)")
|
|
5402
|
+
},
|
|
5403
|
+
execute: async (args, _ctx) => {
|
|
5404
|
+
const client = getGitLabClient();
|
|
5405
|
+
let queryObj;
|
|
5406
|
+
try {
|
|
5407
|
+
queryObj = JSON.parse(args.query);
|
|
5408
|
+
} catch {
|
|
5409
|
+
return JSON.stringify(
|
|
5410
|
+
{
|
|
5411
|
+
error: "Invalid JSON in query parameter",
|
|
5412
|
+
suggestion: "Ensure the query parameter is valid JSON"
|
|
5413
|
+
},
|
|
5414
|
+
null,
|
|
5415
|
+
2
|
|
5416
|
+
);
|
|
5417
|
+
}
|
|
5418
|
+
const requestBody = {
|
|
5419
|
+
query: queryObj
|
|
5420
|
+
};
|
|
5421
|
+
if (args.limit) {
|
|
5422
|
+
requestBody.limit = args.limit;
|
|
5423
|
+
}
|
|
5424
|
+
try {
|
|
5425
|
+
const result = await client.fetch("POST", "/orbit/query", requestBody);
|
|
5426
|
+
if (result.code) {
|
|
5427
|
+
return JSON.stringify(
|
|
5428
|
+
{
|
|
5429
|
+
error: "Query compilation error",
|
|
5430
|
+
code: result.code,
|
|
5431
|
+
message: result.message,
|
|
5432
|
+
suggestion: 'Check query structure. Node "id" is a variable name, actual IDs go in filters.'
|
|
5433
|
+
},
|
|
5434
|
+
null,
|
|
5435
|
+
2
|
|
5436
|
+
);
|
|
5437
|
+
}
|
|
5438
|
+
return JSON.stringify(result, null, 2);
|
|
5439
|
+
} catch (error) {
|
|
5440
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5441
|
+
if (errorMessage.includes("404") || errorMessage.includes("not found")) {
|
|
5442
|
+
return JSON.stringify(
|
|
5443
|
+
{
|
|
5444
|
+
error: "Orbit API not available",
|
|
5445
|
+
suggestion: "Use gitlab_orbit_status to check API availability"
|
|
5446
|
+
},
|
|
5447
|
+
null,
|
|
5448
|
+
2
|
|
5449
|
+
);
|
|
5450
|
+
}
|
|
5451
|
+
if (errorMessage.includes("502")) {
|
|
5452
|
+
return JSON.stringify(
|
|
5453
|
+
{
|
|
5454
|
+
error: "Orbit query failed",
|
|
5455
|
+
message: "The Orbit API returned a 502 error. This is a known issue with some entity types (e.g., source_code domain).",
|
|
5456
|
+
suggestion: "Try a different entity type or use REST/GraphQL APIs as fallback."
|
|
5457
|
+
},
|
|
5458
|
+
null,
|
|
5459
|
+
2
|
|
5460
|
+
);
|
|
5461
|
+
}
|
|
5462
|
+
throw error;
|
|
5463
|
+
}
|
|
5464
|
+
}
|
|
5465
|
+
}),
|
|
5466
|
+
gitlab_orbit_neighbors: tool18({
|
|
5467
|
+
description: `Get immediate neighbors of a node in the Orbit Knowledge Graph.
|
|
5468
|
+
|
|
5469
|
+
This is a simplified wrapper that builds the query for you.
|
|
5470
|
+
Returns nodes directly connected to the specified node.
|
|
5471
|
+
|
|
5472
|
+
Common use cases:
|
|
5473
|
+
- Get all connections to a project (users, groups, pipelines, MRs)
|
|
5474
|
+
- Get pipelines for an MR
|
|
5475
|
+
- Get files changed in a commit
|
|
5476
|
+
- Get deployments from a pipeline
|
|
5477
|
+
|
|
5478
|
+
Note: Use GitLab internal numeric IDs (not IIDs).
|
|
5479
|
+
Example: For MR !123 in gitlab-org/gitlab, first get the internal ID via gitlab_get_merge_request,
|
|
5480
|
+
then use that ID (e.g., 377844873).`,
|
|
5481
|
+
args: {
|
|
5482
|
+
entity_type: z18.string().describe(
|
|
5483
|
+
"Type of the entity (e.g., Project, MergeRequest, User, Pipeline, Commit, WorkItem, Group)"
|
|
5484
|
+
),
|
|
5485
|
+
entity_id: z18.number().describe(
|
|
5486
|
+
"GitLab internal numeric ID (not IID). Get this from REST/GraphQL API responses."
|
|
5487
|
+
),
|
|
5488
|
+
direction: z18.enum(["incoming", "outgoing", "both"]).optional().describe("Direction of relationships to traverse (default: both)"),
|
|
5489
|
+
limit: z18.number().optional().describe("Maximum number of neighbors to return (default: 30)")
|
|
5490
|
+
},
|
|
5491
|
+
execute: async (args, _ctx) => {
|
|
5492
|
+
const client = getGitLabClient();
|
|
5493
|
+
const direction = args.direction || "both";
|
|
5494
|
+
const queryObj = {
|
|
5495
|
+
query_type: "neighbors",
|
|
5496
|
+
node: {
|
|
5497
|
+
entity: args.entity_type,
|
|
5498
|
+
id: "n",
|
|
5499
|
+
filters: {
|
|
5500
|
+
id: { op: "eq", value: args.entity_id }
|
|
5501
|
+
}
|
|
5502
|
+
},
|
|
5503
|
+
neighbors: {
|
|
5504
|
+
node: "n",
|
|
5505
|
+
direction
|
|
5506
|
+
},
|
|
5507
|
+
aggregations: [],
|
|
5508
|
+
path: { type: "shortest", from: "n", to: "n", max_depth: 1 }
|
|
5509
|
+
};
|
|
5510
|
+
const requestBody = {
|
|
5511
|
+
query: queryObj
|
|
5512
|
+
};
|
|
5513
|
+
if (args.limit) {
|
|
5514
|
+
requestBody.limit = args.limit;
|
|
5515
|
+
}
|
|
5516
|
+
try {
|
|
5517
|
+
const result = await client.fetch("POST", "/orbit/query", requestBody);
|
|
5518
|
+
if (result.code) {
|
|
5519
|
+
return JSON.stringify(
|
|
5520
|
+
{
|
|
5521
|
+
error: "Query error",
|
|
5522
|
+
code: result.code,
|
|
5523
|
+
message: result.message,
|
|
5524
|
+
entity_type: args.entity_type,
|
|
5525
|
+
entity_id: args.entity_id
|
|
5526
|
+
},
|
|
5527
|
+
null,
|
|
5528
|
+
2
|
|
5529
|
+
);
|
|
5530
|
+
}
|
|
5531
|
+
return JSON.stringify(result, null, 2);
|
|
5532
|
+
} catch (error) {
|
|
5533
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5534
|
+
if (errorMessage.includes("404") || errorMessage.includes("not found")) {
|
|
5535
|
+
return JSON.stringify(
|
|
5536
|
+
{
|
|
5537
|
+
error: "Orbit API not available",
|
|
5538
|
+
entity_type: args.entity_type,
|
|
5539
|
+
entity_id: args.entity_id,
|
|
5540
|
+
suggestion: "Use gitlab_orbit_status to check API availability"
|
|
5541
|
+
},
|
|
5542
|
+
null,
|
|
5543
|
+
2
|
|
5544
|
+
);
|
|
5545
|
+
}
|
|
5546
|
+
if (errorMessage.includes("502")) {
|
|
5547
|
+
return JSON.stringify(
|
|
5548
|
+
{
|
|
5549
|
+
error: "Orbit query failed",
|
|
5550
|
+
entity_type: args.entity_type,
|
|
5551
|
+
entity_id: args.entity_id,
|
|
5552
|
+
message: "The Orbit API returned a 502 error. This is a known issue with some entity types.",
|
|
5553
|
+
suggestion: "Try a different entity type or use REST/GraphQL APIs as fallback."
|
|
5554
|
+
},
|
|
5555
|
+
null,
|
|
5556
|
+
2
|
|
5557
|
+
);
|
|
5558
|
+
}
|
|
5559
|
+
throw error;
|
|
5560
|
+
}
|
|
5561
|
+
}
|
|
5562
|
+
})
|
|
5563
|
+
};
|
|
5564
|
+
|
|
5141
5565
|
// src/index.ts
|
|
5142
5566
|
var gitlabPlugin = async (_input, options) => {
|
|
5143
5567
|
if (options?.tools === false) {
|
|
@@ -5169,7 +5593,8 @@ DAP tools (agents, flows, memory, skills) are on the "gitlab-dap" server instead
|
|
|
5169
5593
|
...notesUnifiedTools,
|
|
5170
5594
|
...gitTools,
|
|
5171
5595
|
...auditTools,
|
|
5172
|
-
...awardEmojiTools
|
|
5596
|
+
...awardEmojiTools,
|
|
5597
|
+
...orbitTools
|
|
5173
5598
|
}
|
|
5174
5599
|
};
|
|
5175
5600
|
};
|