opencode-gitlab-plugin 3.0.0 → 3.1.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 +14 -0
- package/dist/index.js +185 -48
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.cjs +190 -53
- package/dist/mcp-server.cjs.map +1 -1
- package/package.json +1 -1
package/dist/mcp-server.cjs
CHANGED
|
@@ -34201,6 +34201,109 @@ Examples:
|
|
|
34201
34201
|
|
|
34202
34202
|
// src/tools/orbit.ts
|
|
34203
34203
|
var z18 = tool.schema;
|
|
34204
|
+
var MAX_REL_TYPES = 10;
|
|
34205
|
+
var SCHEMA_CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
34206
|
+
var schemaCache = /* @__PURE__ */ new Map();
|
|
34207
|
+
var schemaInflight = /* @__PURE__ */ new Map();
|
|
34208
|
+
async function loadOrbitSchema(client) {
|
|
34209
|
+
const key = process.env["GITLAB_INSTANCE_URL"] || "https://gitlab.com";
|
|
34210
|
+
const cached2 = schemaCache.get(key);
|
|
34211
|
+
if (cached2 && cached2.expiresAt > Date.now()) {
|
|
34212
|
+
return cached2.schema;
|
|
34213
|
+
}
|
|
34214
|
+
const inflight = schemaInflight.get(key);
|
|
34215
|
+
if (inflight) {
|
|
34216
|
+
return inflight;
|
|
34217
|
+
}
|
|
34218
|
+
const request = (async () => {
|
|
34219
|
+
try {
|
|
34220
|
+
const schema = await client.fetch("GET", "/orbit/schema");
|
|
34221
|
+
if (!schema || !Array.isArray(schema.edges)) {
|
|
34222
|
+
return void 0;
|
|
34223
|
+
}
|
|
34224
|
+
schemaCache.set(key, { schema, expiresAt: Date.now() + SCHEMA_CACHE_TTL_MS });
|
|
34225
|
+
return schema;
|
|
34226
|
+
} catch {
|
|
34227
|
+
return void 0;
|
|
34228
|
+
}
|
|
34229
|
+
})();
|
|
34230
|
+
schemaInflight.set(key, request);
|
|
34231
|
+
void request.finally(() => {
|
|
34232
|
+
if (schemaInflight.get(key) === request) {
|
|
34233
|
+
schemaInflight.delete(key);
|
|
34234
|
+
}
|
|
34235
|
+
});
|
|
34236
|
+
return request;
|
|
34237
|
+
}
|
|
34238
|
+
function deriveRelTypes(schema, entityType, preferred) {
|
|
34239
|
+
const touching = /* @__PURE__ */ new Set();
|
|
34240
|
+
for (const edge of schema.edges ?? []) {
|
|
34241
|
+
const touchesEntity = (edge.variants ?? []).some(
|
|
34242
|
+
(variant) => variant.source_type === entityType || variant.target_type === entityType
|
|
34243
|
+
);
|
|
34244
|
+
if (touchesEntity) {
|
|
34245
|
+
touching.add(edge.name);
|
|
34246
|
+
}
|
|
34247
|
+
}
|
|
34248
|
+
const rank = (name) => {
|
|
34249
|
+
const index = preferred.indexOf(name);
|
|
34250
|
+
return index === -1 ? preferred.length : index;
|
|
34251
|
+
};
|
|
34252
|
+
return [...touching].sort((a, b) => rank(a) - rank(b) || a.localeCompare(b)).slice(0, MAX_REL_TYPES);
|
|
34253
|
+
}
|
|
34254
|
+
async function defaultRelTypes(client, entityType) {
|
|
34255
|
+
const fallback = orbitRelTypes(entityType);
|
|
34256
|
+
const schema = await loadOrbitSchema(client);
|
|
34257
|
+
if (!schema) {
|
|
34258
|
+
return fallback;
|
|
34259
|
+
}
|
|
34260
|
+
const derived = deriveRelTypes(schema, entityType, fallback);
|
|
34261
|
+
return derived.length > 0 ? derived : fallback;
|
|
34262
|
+
}
|
|
34263
|
+
function orbitRelTypes(entityType) {
|
|
34264
|
+
switch (entityType) {
|
|
34265
|
+
case "MergeRequest":
|
|
34266
|
+
return [
|
|
34267
|
+
"AUTHORED",
|
|
34268
|
+
"REVIEWER",
|
|
34269
|
+
"ASSIGNED",
|
|
34270
|
+
"CLOSES",
|
|
34271
|
+
"MENTIONS",
|
|
34272
|
+
"IN_PROJECT",
|
|
34273
|
+
"HAS_HEAD_PIPELINE",
|
|
34274
|
+
"TRIGGERED",
|
|
34275
|
+
"IN_MILESTONE",
|
|
34276
|
+
"HAS_LABEL"
|
|
34277
|
+
];
|
|
34278
|
+
case "WorkItem":
|
|
34279
|
+
return [
|
|
34280
|
+
"AUTHORED",
|
|
34281
|
+
"ASSIGNED",
|
|
34282
|
+
"CLOSES",
|
|
34283
|
+
"MENTIONS",
|
|
34284
|
+
"RELATED_TO",
|
|
34285
|
+
"CONTAINS",
|
|
34286
|
+
"IN_PROJECT",
|
|
34287
|
+
"IN_GROUP",
|
|
34288
|
+
"IN_MILESTONE",
|
|
34289
|
+
"HAS_LABEL"
|
|
34290
|
+
];
|
|
34291
|
+
case "User":
|
|
34292
|
+
return ["AUTHORED", "ASSIGNED", "REVIEWER", "MEMBER_OF", "OWNER", "CREATOR"];
|
|
34293
|
+
case "Project":
|
|
34294
|
+
return ["CONTAINS", "IN_PROJECT", "MEMBER_OF", "CREATOR"];
|
|
34295
|
+
case "Pipeline":
|
|
34296
|
+
return ["HAS_HEAD_PIPELINE", "TRIGGERED", "IN_PROJECT", "HAS_JOB", "HAS_STAGE"];
|
|
34297
|
+
case "File":
|
|
34298
|
+
return ["CONTAINS", "IMPORTS", "DEFINES", "CALLS", "ON_BRANCH"];
|
|
34299
|
+
case "Group":
|
|
34300
|
+
return ["CONTAINS", "MEMBER_OF", "OWNER", "IN_GROUP"];
|
|
34301
|
+
case "Commit":
|
|
34302
|
+
return ["MERGED_AT_COMMIT"];
|
|
34303
|
+
default:
|
|
34304
|
+
return ["AUTHORED", "ASSIGNED", "REVIEWER", "CLOSES", "MENTIONS", "RELATED_TO", "IN_PROJECT"];
|
|
34305
|
+
}
|
|
34306
|
+
}
|
|
34204
34307
|
var orbitTools = {
|
|
34205
34308
|
gitlab_orbit_status: tool({
|
|
34206
34309
|
description: `Check GitLab Orbit Knowledge Graph API availability and health.
|
|
@@ -34334,47 +34437,64 @@ Note: Requires Orbit API to be available (Premium/Ultimate tier).`,
|
|
|
34334
34437
|
description: `Execute a query against the Orbit Knowledge Graph.
|
|
34335
34438
|
|
|
34336
34439
|
Query types:
|
|
34337
|
-
- traversal:
|
|
34338
|
-
- neighbors:
|
|
34339
|
-
|
|
34340
|
-
|
|
34341
|
-
|
|
34342
|
-
|
|
34343
|
-
|
|
34344
|
-
|
|
34440
|
+
- traversal: Fetch matching nodes or follow relationships between nodes
|
|
34441
|
+
- neighbors: Return nodes connected to one bounded node
|
|
34442
|
+
- aggregation: Count/sum/avg/group graph results
|
|
34443
|
+
- path_finding: Find a bounded path between two nodes
|
|
34444
|
+
|
|
34445
|
+
QUERY SHAPE (current DSL, format_version 5.x):
|
|
34446
|
+
Every query has a "query_type" and a "nodes" array of node selectors (max 5).
|
|
34447
|
+
A node selector names an entity and is made SELECTIVE via "node_ids" (exact
|
|
34448
|
+
internal ids) or "filters" (natural properties). Relationships between selectors
|
|
34449
|
+
go in a "relationships" array referencing selector "id" aliases.
|
|
34450
|
+
|
|
34451
|
+
IMPORTANT changes from older docs:
|
|
34452
|
+
- Use a top-level "nodes" ARRAY, not a single "node" object.
|
|
34453
|
+
- Pin ids with "node_ids": [123], NOT filters:{id:{op:"eq",value:123}}.
|
|
34454
|
+
- Filters use {op: value}, e.g. {"state": "merged"} or {"created_at": {"gte": "2026-01-01"}}.
|
|
34455
|
+
- A "neighbors" query uses a 1-element "nodes" array plus a top-level
|
|
34456
|
+
"neighbors": {direction, rel_types:[...]} object. rel_types is REQUIRED to
|
|
34457
|
+
bound the lookup and is capped at 10 entries.
|
|
34458
|
+
|
|
34459
|
+
Example neighbors query (an MR's graph neighbours):
|
|
34345
34460
|
{
|
|
34346
34461
|
"query_type": "neighbors",
|
|
34347
|
-
"
|
|
34348
|
-
|
|
34349
|
-
"
|
|
34350
|
-
"
|
|
34462
|
+
"nodes": [{ "id": "mr", "entity": "MergeRequest", "node_ids": [507483917] }],
|
|
34463
|
+
"neighbors": {
|
|
34464
|
+
"direction": "both",
|
|
34465
|
+
"rel_types": ["AUTHORED", "REVIEWER", "IN_PROJECT", "CLOSES", "HAS_HEAD_PIPELINE"]
|
|
34351
34466
|
},
|
|
34352
|
-
"
|
|
34353
|
-
"aggregations": [],
|
|
34354
|
-
"path": {"type": "shortest", "from": "p", "to": "p", "max_depth": 1}
|
|
34467
|
+
"limit": 30
|
|
34355
34468
|
}
|
|
34356
34469
|
|
|
34357
|
-
Example traversal
|
|
34470
|
+
Example traversal (merged MRs in a project):
|
|
34358
34471
|
{
|
|
34359
34472
|
"query_type": "traversal",
|
|
34360
|
-
"
|
|
34361
|
-
"entity": "
|
|
34362
|
-
"id": "mr",
|
|
34363
|
-
|
|
34364
|
-
|
|
34365
|
-
"
|
|
34366
|
-
"
|
|
34367
|
-
|
|
34368
|
-
|
|
34369
|
-
|
|
34370
|
-
|
|
34371
|
-
|
|
34473
|
+
"nodes": [
|
|
34474
|
+
{ "id": "project", "entity": "Project", "filters": {"full_path": "gitlab-org/gitlab"} },
|
|
34475
|
+
{ "id": "mr", "entity": "MergeRequest", "filters": {"state": "merged"},
|
|
34476
|
+
"columns": ["iid", "title", "merged_at"] }
|
|
34477
|
+
],
|
|
34478
|
+
"relationships": [{ "type": "IN_PROJECT", "from": "mr", "to": "project" }],
|
|
34479
|
+
"limit": 25
|
|
34480
|
+
}
|
|
34481
|
+
|
|
34482
|
+
Notes:
|
|
34483
|
+
- node_ids are GitLab *internal* numeric ids (not IIDs). For an MR, get it via
|
|
34484
|
+
gitlab_get_merge_request (the "id" field). For epics, use work_item_id.
|
|
34485
|
+
- Traversal/aggregation queries must include at least one selective node.
|
|
34486
|
+
- aggregation adds an "aggregations" array (e.g. [{"count":"mr"}]) with optional
|
|
34487
|
+
"group_by"; path_finding adds a "path":{from,to,max_depth} object between two
|
|
34488
|
+
node selectors. Both use the same envelope; see gitlab_orbit_schema and the
|
|
34489
|
+
Orbit query-language docs for the full grammar.
|
|
34490
|
+
- Use gitlab_orbit_schema to discover entities and relationship types.
|
|
34491
|
+
Returns nodes and edges arrays.`,
|
|
34372
34492
|
args: {
|
|
34373
34493
|
// Using a JSON string rather than a structured zod schema because the Orbit query
|
|
34374
34494
|
// format is still evolving. This allows flexibility as the API changes without
|
|
34375
34495
|
// requiring schema updates for each iteration.
|
|
34376
34496
|
query: z18.string().describe(
|
|
34377
|
-
|
|
34497
|
+
'JSON string containing the full query object. Must include "query_type" and a top-level "nodes" array of selectors (each pinned by "node_ids" or "filters"); add "relationships"/"neighbors"/"aggregations"/"path" per the query_type. See the tool description for the current DSL and worked examples.'
|
|
34378
34498
|
),
|
|
34379
34499
|
limit: z18.number().optional().describe("Maximum number of results (default: 30)")
|
|
34380
34500
|
},
|
|
@@ -34393,12 +34513,23 @@ Returns nodes array and edges array with relationship types.`,
|
|
|
34393
34513
|
2
|
|
34394
34514
|
);
|
|
34395
34515
|
}
|
|
34516
|
+
if (typeof queryObj !== "object" || queryObj === null || Array.isArray(queryObj)) {
|
|
34517
|
+
return JSON.stringify(
|
|
34518
|
+
{
|
|
34519
|
+
error: "Invalid query parameter",
|
|
34520
|
+
suggestion: 'The query must be a JSON object with a "query_type" and a "nodes" array.'
|
|
34521
|
+
},
|
|
34522
|
+
null,
|
|
34523
|
+
2
|
|
34524
|
+
);
|
|
34525
|
+
}
|
|
34526
|
+
if (args.limit !== void 0) {
|
|
34527
|
+
queryObj.limit = args.limit;
|
|
34528
|
+
}
|
|
34396
34529
|
const requestBody = {
|
|
34397
|
-
query: queryObj
|
|
34530
|
+
query: queryObj,
|
|
34531
|
+
response_format: "raw"
|
|
34398
34532
|
};
|
|
34399
|
-
if (args.limit) {
|
|
34400
|
-
requestBody.limit = args.limit;
|
|
34401
|
-
}
|
|
34402
34533
|
try {
|
|
34403
34534
|
const result = await client.fetch("POST", "/orbit/query", requestBody);
|
|
34404
34535
|
if (result.code) {
|
|
@@ -34407,7 +34538,7 @@ Returns nodes array and edges array with relationship types.`,
|
|
|
34407
34538
|
error: "Query compilation error",
|
|
34408
34539
|
code: result.code,
|
|
34409
34540
|
message: result.message,
|
|
34410
|
-
suggestion: 'Check query structure
|
|
34541
|
+
suggestion: 'Check query structure against the current DSL: top-level "nodes" array, pin ids with "node_ids", filters use {op: value}, and a "neighbors" query needs neighbors:{direction, rel_types} (max 10 rel_types).'
|
|
34411
34542
|
},
|
|
34412
34543
|
null,
|
|
34413
34544
|
2
|
|
@@ -34455,7 +34586,7 @@ Common use cases:
|
|
|
34455
34586
|
|
|
34456
34587
|
Note: Use GitLab internal numeric IDs (not IIDs).
|
|
34457
34588
|
Example: For MR !123 in gitlab-org/gitlab, first get the internal ID via gitlab_get_merge_request,
|
|
34458
|
-
then use that ID (e.g., 377844873).`,
|
|
34589
|
+
then use that ID (e.g., 377844873). For epics, use the work_item_id.`,
|
|
34459
34590
|
args: {
|
|
34460
34591
|
entity_type: z18.string().describe(
|
|
34461
34592
|
"Type of the entity (e.g., Project, MergeRequest, User, Pipeline, Commit, WorkItem, Group)"
|
|
@@ -34464,33 +34595,38 @@ then use that ID (e.g., 377844873).`,
|
|
|
34464
34595
|
"GitLab internal numeric ID (not IID). Get this from REST/GraphQL API responses."
|
|
34465
34596
|
),
|
|
34466
34597
|
direction: z18.enum(["incoming", "outgoing", "both"]).optional().describe("Direction of relationships to traverse (default: both)"),
|
|
34598
|
+
rel_types: z18.array(z18.string()).optional().describe(
|
|
34599
|
+
'Relationship types to follow. Required by the API to bound the lookup; if omitted, up to 10 edge types that touch the entity type are taken from gitlab_orbit_schema (with a built-in default set as fallback if the schema is unavailable). The API caps this at 10 per selector, so more than 10 entries are truncated to the first 10 and the response includes a "warnings" field. See gitlab_orbit_schema for valid edge types.'
|
|
34600
|
+
),
|
|
34467
34601
|
limit: z18.number().optional().describe("Maximum number of neighbors to return (default: 30)")
|
|
34468
34602
|
},
|
|
34469
34603
|
execute: async (args, _ctx) => {
|
|
34470
34604
|
const client = getGitLabClient();
|
|
34471
34605
|
const direction = args.direction || "both";
|
|
34606
|
+
const requestedRelTypes = args.rel_types?.length ? args.rel_types : await defaultRelTypes(client, args.entity_type);
|
|
34607
|
+
const relTypes = requestedRelTypes.slice(0, MAX_REL_TYPES);
|
|
34608
|
+
const warnings = requestedRelTypes.length > relTypes.length ? [`rel_types truncated to 10 (got ${requestedRelTypes.length})`] : [];
|
|
34472
34609
|
const queryObj = {
|
|
34473
34610
|
query_type: "neighbors",
|
|
34474
|
-
|
|
34475
|
-
|
|
34476
|
-
|
|
34477
|
-
|
|
34478
|
-
|
|
34611
|
+
nodes: [
|
|
34612
|
+
{
|
|
34613
|
+
id: "n",
|
|
34614
|
+
entity: args.entity_type,
|
|
34615
|
+
node_ids: [args.entity_id]
|
|
34479
34616
|
}
|
|
34480
|
-
|
|
34617
|
+
],
|
|
34481
34618
|
neighbors: {
|
|
34482
|
-
|
|
34483
|
-
|
|
34484
|
-
}
|
|
34485
|
-
aggregations: [],
|
|
34486
|
-
path: { type: "shortest", from: "n", to: "n", max_depth: 1 }
|
|
34619
|
+
direction,
|
|
34620
|
+
rel_types: relTypes
|
|
34621
|
+
}
|
|
34487
34622
|
};
|
|
34623
|
+
if (args.limit !== void 0) {
|
|
34624
|
+
queryObj.limit = args.limit;
|
|
34625
|
+
}
|
|
34488
34626
|
const requestBody = {
|
|
34489
|
-
query: queryObj
|
|
34627
|
+
query: queryObj,
|
|
34628
|
+
response_format: "raw"
|
|
34490
34629
|
};
|
|
34491
|
-
if (args.limit) {
|
|
34492
|
-
requestBody.limit = args.limit;
|
|
34493
|
-
}
|
|
34494
34630
|
try {
|
|
34495
34631
|
const result = await client.fetch("POST", "/orbit/query", requestBody);
|
|
34496
34632
|
if (result.code) {
|
|
@@ -34500,13 +34636,14 @@ then use that ID (e.g., 377844873).`,
|
|
|
34500
34636
|
code: result.code,
|
|
34501
34637
|
message: result.message,
|
|
34502
34638
|
entity_type: args.entity_type,
|
|
34503
|
-
entity_id: args.entity_id
|
|
34639
|
+
entity_id: args.entity_id,
|
|
34640
|
+
...warnings.length > 0 && { warnings }
|
|
34504
34641
|
},
|
|
34505
34642
|
null,
|
|
34506
34643
|
2
|
|
34507
34644
|
);
|
|
34508
34645
|
}
|
|
34509
|
-
return JSON.stringify(result, null, 2);
|
|
34646
|
+
return JSON.stringify(warnings.length > 0 ? { ...result, warnings } : result, null, 2);
|
|
34510
34647
|
} catch (error45) {
|
|
34511
34648
|
const errorMessage = error45 instanceof Error ? error45.message : String(error45);
|
|
34512
34649
|
if (errorMessage.includes("404") || errorMessage.includes("not found")) {
|
|
@@ -34590,7 +34727,7 @@ async function main() {
|
|
|
34590
34727
|
`);
|
|
34591
34728
|
process.exit(1);
|
|
34592
34729
|
}
|
|
34593
|
-
const version2 = true ? "3.
|
|
34730
|
+
const version2 = true ? "3.1.0" : "0.0.0";
|
|
34594
34731
|
const server = new McpServer({ name: "gitlab", version: version2 });
|
|
34595
34732
|
adaptToolsToMcp(server, mcpTools);
|
|
34596
34733
|
const transport = new StdioServerTransport();
|