@zereight/mcp-gitlab 2.1.25 → 2.1.27
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/README.ko.md +1 -0
- package/README.md +11 -1
- package/README.zh-CN.md +1 -0
- package/build/config.js +1 -0
- package/build/index.js +723 -343
- package/build/oauth.js +65 -3
- package/build/schemas.js +474 -197
- package/build/test/dynamic-api-url-allowlist.test.js +104 -0
- package/build/test/dynamic-api-url-test.js +3 -3
- package/build/test/oauth-tests.js +39 -0
- package/build/test/remote-auth-simple-test.js +13 -2
- package/build/test/schema-tests.js +51 -0
- package/build/test/sse-auth-guard.test.js +96 -0
- package/build/test/streamable-http-concurrent-session.test.js +92 -0
- package/build/test/streamable-http-unauthenticated-discovery.test.js +113 -0
- package/build/test/test-ci-catalog.js +177 -0
- package/build/test/test-create-repository.js +120 -0
- package/build/test/test-list-issues.js +15 -3
- package/build/test/test-toolset-filtering.js +6 -5
- package/build/test/test-update-project.js +112 -0
- package/build/test/utils/forwarded-public-base-url.test.js +38 -0
- package/build/tools/registry.js +25 -2
- package/build/utils/forwarded-public-base-url.js +62 -0
- package/build/utils/schema.js +15 -1
- package/package.json +2 -2
package/build/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { getConfig, ENABLE_DYNAMIC_API_URL, GITLAB_AUTH_COOKIE_PATH, GITLAB_CA_CERT_PATH, GITLAB_JOB_TOKEN, GITLAB_MCP_OAUTH, GITLAB_OAUTH_APP_ID, GITLAB_OAUTH_SCOPES, GITLAB_OAUTH_CALLBACK_PROXY, GITLAB_PERSONAL_ACCESS_TOKEN, GITLAB_POOL_MAX_SIZE, GITLAB_READ_ONLY_MODE, GITLAB_TOOLSETS_RAW, GITLAB_TOOLS_RAW, HOST, HTTP_PROXY, HTTPS_PROXY, IS_OLD, MCP_SERVER_URL, NODE_TLS_REJECT_UNAUTHORIZED, NO_PROXY, OAUTH_STATELESS_CLIENT_TTL_SECONDS, OAUTH_STATELESS_MODE, OAUTH_STATELESS_PENDING_TTL_SECONDS, OAUTH_STATELESS_SESSION_TTL_SECONDS, OAUTH_STATELESS_STORED_TTL_SECONDS, PORT, REMOTE_AUTHORIZATION, SESSION_TIMEOUT_SECONDS, SSE, STREAMABLE_HTTP, MCP_TRUST_PROXY, USE_GITLAB_WIKI, USE_MILESTONE, USE_OAUTH, USE_PIPELINE, GITLAB_TOOL_POLICY_APPROVE_RAW, GITLAB_TOOL_POLICY_HIDDEN_RAW, GITLAB_OAUTH_ALLOWED_GROUPS_RAW, GITLAB_ALLOWED_GROUPS_RAW, GITLAB_OAUTH_ALLOWED_GROUPS, } from "./config.js";
|
|
2
|
+
import { getConfig, ENABLE_DYNAMIC_API_URL, GITLAB_AUTH_COOKIE_PATH, GITLAB_ALLOW_UNAUTHENTICATED_TOOL_DISCOVERY, GITLAB_CA_CERT_PATH, GITLAB_JOB_TOKEN, GITLAB_MCP_OAUTH, GITLAB_OAUTH_APP_ID, GITLAB_OAUTH_SCOPES, GITLAB_OAUTH_CALLBACK_PROXY, GITLAB_PERSONAL_ACCESS_TOKEN, GITLAB_POOL_MAX_SIZE, GITLAB_READ_ONLY_MODE, GITLAB_TOOLSETS_RAW, GITLAB_TOOLS_RAW, HOST, HTTP_PROXY, HTTPS_PROXY, IS_OLD, MCP_SERVER_URL, NODE_TLS_REJECT_UNAUTHORIZED, NO_PROXY, OAUTH_STATELESS_CLIENT_TTL_SECONDS, OAUTH_STATELESS_MODE, OAUTH_STATELESS_PENDING_TTL_SECONDS, OAUTH_STATELESS_SESSION_TTL_SECONDS, OAUTH_STATELESS_STORED_TTL_SECONDS, PORT, REMOTE_AUTHORIZATION, SESSION_TIMEOUT_SECONDS, SSE, STREAMABLE_HTTP, MCP_TRUST_PROXY, USE_GITLAB_WIKI, USE_MILESTONE, USE_OAUTH, USE_PIPELINE, GITLAB_TOOL_POLICY_APPROVE_RAW, GITLAB_TOOL_POLICY_HIDDEN_RAW, GITLAB_OAUTH_ALLOWED_GROUPS_RAW, GITLAB_ALLOWED_GROUPS_RAW, GITLAB_OAUTH_ALLOWED_GROUPS, } from "./config.js";
|
|
3
3
|
/** True when the server is running in remote/network mode (SSE or StreamableHTTP transport). */
|
|
4
4
|
const IS_REMOTE = SSE || STREAMABLE_HTTP;
|
|
5
5
|
/**
|
|
@@ -59,62 +59,6 @@ function decryptDownloadToken(tokenStr) {
|
|
|
59
59
|
return null;
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
function getLastHeaderValue(value) {
|
|
63
|
-
const raw = Array.isArray(value) ? value[value.length - 1] : value;
|
|
64
|
-
if (!raw)
|
|
65
|
-
return undefined;
|
|
66
|
-
const parts = raw.split(",").map(part => part.trim()).filter(Boolean);
|
|
67
|
-
return parts.length > 0 ? parts[parts.length - 1] : undefined;
|
|
68
|
-
}
|
|
69
|
-
function unquoteHeaderValue(value) {
|
|
70
|
-
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
|
|
71
|
-
return value.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
72
|
-
}
|
|
73
|
-
return value;
|
|
74
|
-
}
|
|
75
|
-
function getForwardedPublicBaseUrl(req) {
|
|
76
|
-
if (!MCP_TRUST_PROXY)
|
|
77
|
-
return undefined;
|
|
78
|
-
const forwarded = getLastHeaderValue(req.headers.forwarded);
|
|
79
|
-
const forwardedValues = {};
|
|
80
|
-
if (forwarded) {
|
|
81
|
-
for (const part of forwarded.split(";")) {
|
|
82
|
-
const separator = part.indexOf("=");
|
|
83
|
-
if (separator <= 0)
|
|
84
|
-
continue;
|
|
85
|
-
const key = part.slice(0, separator).trim().toLowerCase();
|
|
86
|
-
const value = unquoteHeaderValue(part.slice(separator + 1).trim());
|
|
87
|
-
if (key && value)
|
|
88
|
-
forwardedValues[key] = value;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
const proto = (forwardedValues.proto || getLastHeaderValue(req.headers["x-forwarded-proto"]))?.toLowerCase();
|
|
92
|
-
const host = forwardedValues.host || getLastHeaderValue(req.headers["x-forwarded-host"]);
|
|
93
|
-
if (!proto || !host || !/^https?$/.test(proto))
|
|
94
|
-
return undefined;
|
|
95
|
-
if (/[\s/\\]/.test(host))
|
|
96
|
-
return undefined;
|
|
97
|
-
const prefix = getLastHeaderValue(req.headers["x-forwarded-prefix"]);
|
|
98
|
-
const safePrefix = prefix &&
|
|
99
|
-
prefix.startsWith("/") &&
|
|
100
|
-
!prefix.startsWith("//") &&
|
|
101
|
-
!prefix.includes("://") &&
|
|
102
|
-
!/[\s\\]/.test(prefix)
|
|
103
|
-
? prefix.replace(/\/+$/, "")
|
|
104
|
-
: undefined;
|
|
105
|
-
try {
|
|
106
|
-
const baseUrl = new URL(`${proto}://${host}`);
|
|
107
|
-
if (baseUrl.username || baseUrl.password || baseUrl.pathname !== "/" || baseUrl.search || baseUrl.hash) {
|
|
108
|
-
return undefined;
|
|
109
|
-
}
|
|
110
|
-
if (safePrefix)
|
|
111
|
-
baseUrl.pathname = safePrefix;
|
|
112
|
-
return baseUrl.toString().replace(/\/$/, "");
|
|
113
|
-
}
|
|
114
|
-
catch {
|
|
115
|
-
return undefined;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
62
|
/**
|
|
119
63
|
* Build a URL pointing to the download proxy endpoint.
|
|
120
64
|
* Embeds an encrypted auth token (and API URL for dynamic routing)
|
|
@@ -185,8 +129,9 @@ import { createGitLabOAuthProvider } from "./oauth-proxy.js";
|
|
|
185
129
|
import { mcpAuthRouter } from "@modelcontextprotocol/sdk/server/auth/router.js";
|
|
186
130
|
import { ipKeyGenerator } from "express-rate-limit";
|
|
187
131
|
import { normalizeProxyClientIpForRateLimit } from "./utils/proxy-client-ip.js";
|
|
132
|
+
import { getForwardedPublicBaseUrl } from "./utils/forwarded-public-base-url.js";
|
|
188
133
|
import { normalizeGitLabApiUrl } from "./utils/url.js";
|
|
189
|
-
import { estimateMergeCommitCount, filterDiffsByPatterns, summarizeWebhookEvents } from "./utils/helpers.js";
|
|
134
|
+
import { estimateMergeCommitCount, filterDiffsByPatterns, summarizeWebhookEvents, } from "./utils/helpers.js";
|
|
190
135
|
import { graphqlQueryContainsWriteOperation } from "./utils/graphql-query.js";
|
|
191
136
|
import { resolveNestedWikiUpdateTitle } from "./utils/wiki-title.js";
|
|
192
137
|
import { cleanMutuallyExclusiveIdUsernameOptions, LIST_MERGE_REQUESTS_ID_USERNAME_PAIRS, sanitizeToolArguments, } from "./utils/tool-args.js";
|
|
@@ -201,7 +146,7 @@ GitLabDiscussionNoteSchema, // Added
|
|
|
201
146
|
GitLabDiscussionSchema,
|
|
202
147
|
// Draft Notes Schemas
|
|
203
148
|
GitLabDraftNoteSchema, GitLabForkSchema, GitLabBranchSchema, GitLabProtectedBranchSchema, GitLabGroupSchema, GitLabIssueLinkSchema, GitLabIssueSchema, GitLabIssueWithLinkDetailsSchema, GitLabMarkdownUploadSchema, GitLabMergeRequestPipelineSchema, GitLabMergeRequestSchema, GitLabMilestonesSchema, GitLabNamespaceExistsResponseSchema, GitLabNamespaceSchema, GitLabPipelineJobSchema, GitLabDeploymentSchema, GitLabEnvironmentSchema, GitLabPipelineSchema, GitLabPipelineTriggerJobSchema, GitLabProjectMemberSchema, GitLabProjectSchema, GitLabTodoSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabSearchBlobResultSchema, GitLabSearchResponseSchema, GitLabTreeItemSchema, GitLabUserSchema, GitLabUsersResponseSchema, GitLabWikiPageSchema, GroupIteration, ListCommitStatusesSchema, ListBranchesSchema, ListCommitsSchema, ListDraftNotesSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, GitLabCiVariableSchema, ListProjectVariablesSchema, GetProjectVariableSchema, CreateProjectVariableSchema, UpdateProjectVariableSchema, DeleteProjectVariableSchema, ListGroupVariablesSchema, GetGroupVariableSchema, CreateGroupVariableSchema, UpdateGroupVariableSchema, DeleteGroupVariableSchema, GitLabDependencyProxySchema, GitLabDependencyProxyBlobSchema, GetDependencyProxySettingsSchema, UpdateDependencyProxySettingsSchema, ListDependencyProxyBlobsSchema, PurgeDependencyProxyCacheSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListTodosSchema, ListLabelsSchema, ListMergeRequestDiffsSchema, // Added
|
|
204
|
-
GetMergeRequestFileDiffSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestsSchema, ListMergeRequestVersionsSchema, GetMergeRequestVersionSchema, GitLabMergeRequestVersionSchema, GitLabMergeRequestVersionDetailSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelinesSchema, ListDeploymentsSchema, ListEnvironmentsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListWikiPagesSchema, GetGroupWikiPageSchema, ListGroupWikiPagesSchema, UpdateGroupWikiPageSchema, MarkdownUploadSchema, MarkdownUploadRemoteSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, GetJobArtifactFileSchema, GitLabArtifactEntrySchema, ListJobArtifactsSchema, MergeMergeRequestSchema, ApproveMergeRequestSchema, UnapproveMergeRequestSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GitLabMergeRequestApprovalsResponseSchema, GitLabMergeRequestApprovalStateSchema, MyIssuesSchema, MarkAllTodosDoneSchema, MarkTodoDoneSchema, PaginatedDiscussionsResponseSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PlayPipelineJobSchema, PushFilesSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UpdateDraftNoteSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateIssueDescriptionPatchSchema, UpdateLabelSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestSchema, UpdateWikiPageSchema, VerifyNamespaceSchema, GitLabEventSchema, ListEventsSchema, GetProjectEventsSchema, ExecuteGraphQLSchema, GitLabReleaseSchema, ListReleasesSchema, GetReleaseSchema, CreateReleaseSchema, UpdateReleaseSchema, DeleteReleaseSchema, CreateReleaseEvidenceSchema, DownloadReleaseAssetSchema, ListTagsSchema, GetTagSchema, CreateTagSchema, DeleteTagSchema, GetTagSignatureSchema, GitLabTagSchema, GitLabTagSignatureSchema, GetMergeRequestNotesSchema, GetMergeRequestNoteSchema, DeleteMergeRequestDiscussionNoteSchema, ResolveMergeRequestThreadSchema, GetWorkItemSchema, ListWorkItemsSchema, CreateWorkItemSchema, UpdateWorkItemSchema, ConvertWorkItemTypeSchema, ListWorkItemStatusesSchema, ListWorkItemNotesSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, MoveWorkItemSchema, ListCustomFieldDefinitionsSchema, GetTimelineEventsSchema, CreateTimelineEventSchema, ListWebhooksSchema, ListWebhookEventsSchema, GetWebhookEventSchema, HealthCheckSchema, } from "./schemas.js";
|
|
149
|
+
GetMergeRequestFileDiffSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestsSchema, ListMergeRequestVersionsSchema, GetMergeRequestVersionSchema, GitLabMergeRequestVersionSchema, GitLabMergeRequestVersionDetailSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelinesSchema, ListDeploymentsSchema, ListEnvironmentsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListCiCatalogResourcesSchema, GetCiCatalogResourceSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListWikiPagesSchema, GetGroupWikiPageSchema, ListGroupWikiPagesSchema, UpdateGroupWikiPageSchema, MarkdownUploadSchema, MarkdownUploadRemoteSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, GetJobArtifactFileSchema, GitLabArtifactEntrySchema, ListJobArtifactsSchema, MergeMergeRequestSchema, ApproveMergeRequestSchema, UnapproveMergeRequestSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GitLabMergeRequestApprovalsResponseSchema, GitLabMergeRequestApprovalStateSchema, MyIssuesSchema, MarkAllTodosDoneSchema, MarkTodoDoneSchema, PaginatedDiscussionsResponseSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PlayPipelineJobSchema, PushFilesSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UpdateDraftNoteSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateIssueDescriptionPatchSchema, UpdateLabelSchema, UpdateProjectSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestSchema, UpdateWikiPageSchema, VerifyNamespaceSchema, GitLabEventSchema, ListEventsSchema, GetProjectEventsSchema, ExecuteGraphQLSchema, GitLabReleaseSchema, ListReleasesSchema, GetReleaseSchema, CreateReleaseSchema, UpdateReleaseSchema, DeleteReleaseSchema, CreateReleaseEvidenceSchema, DownloadReleaseAssetSchema, ListTagsSchema, GetTagSchema, CreateTagSchema, DeleteTagSchema, GetTagSignatureSchema, GitLabTagSchema, GitLabTagSignatureSchema, GetMergeRequestNotesSchema, GetMergeRequestNoteSchema, DeleteMergeRequestDiscussionNoteSchema, ResolveMergeRequestThreadSchema, GetWorkItemSchema, ListWorkItemsSchema, CreateWorkItemSchema, UpdateWorkItemSchema, ConvertWorkItemTypeSchema, ListWorkItemStatusesSchema, ListWorkItemNotesSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, MoveWorkItemSchema, ListCustomFieldDefinitionsSchema, GetTimelineEventsSchema, CreateTimelineEventSchema, ListWebhooksSchema, ListWebhookEventsSchema, GetWebhookEventSchema, HealthCheckSchema, } from "./schemas.js";
|
|
205
150
|
import { randomUUID, createCipheriv, createDecipheriv, randomBytes, createHash } from "node:crypto";
|
|
206
151
|
import { pino } from "pino";
|
|
207
152
|
const logger = pino({
|
|
@@ -325,7 +270,9 @@ function createServer() {
|
|
|
325
270
|
const modified = { ...tool };
|
|
326
271
|
// Safety net: remove $schema if present (toJSONSchema strips it for zod schemas,
|
|
327
272
|
// but manually-defined schemas like discover_tools may still have it)
|
|
328
|
-
if (modified.inputSchema &&
|
|
273
|
+
if (modified.inputSchema &&
|
|
274
|
+
typeof modified.inputSchema === "object" &&
|
|
275
|
+
modified.inputSchema !== null) {
|
|
329
276
|
if ("$schema" in modified.inputSchema) {
|
|
330
277
|
modified.inputSchema = { ...modified.inputSchema };
|
|
331
278
|
delete modified.inputSchema.$schema;
|
|
@@ -371,7 +318,12 @@ function createServer() {
|
|
|
371
318
|
};
|
|
372
319
|
const logError = (error) => {
|
|
373
320
|
const durationMs = Date.now() - start;
|
|
374
|
-
logger.error({
|
|
321
|
+
logger.error({
|
|
322
|
+
tool: toolName,
|
|
323
|
+
event: "tool_call_error",
|
|
324
|
+
durationMs,
|
|
325
|
+
error: error instanceof Error ? error.message : String(error),
|
|
326
|
+
}, `tool_call_error: ${toolName} (${durationMs}ms)`);
|
|
375
327
|
throw error;
|
|
376
328
|
};
|
|
377
329
|
try {
|
|
@@ -390,16 +342,18 @@ function createServer() {
|
|
|
390
342
|
return logCompletion({
|
|
391
343
|
content: [{
|
|
392
344
|
type: "text",
|
|
393
|
-
text: JSON.stringify({ categories, hint: "Call discover_tools with a category name to activate it" }
|
|
345
|
+
text: JSON.stringify({ categories, hint: "Call discover_tools with a category name to activate it" }),
|
|
394
346
|
}],
|
|
395
347
|
});
|
|
396
348
|
}
|
|
397
349
|
if (!ALL_TOOLSET_IDS.has(category)) {
|
|
398
350
|
return logCompletion({
|
|
399
|
-
content: [
|
|
351
|
+
content: [
|
|
352
|
+
{
|
|
400
353
|
type: "text",
|
|
401
354
|
text: `Unknown category "${category}". Available: ${[...ALL_TOOLSET_IDS].join(", ")}`,
|
|
402
|
-
}
|
|
355
|
+
},
|
|
356
|
+
],
|
|
403
357
|
isError: true,
|
|
404
358
|
});
|
|
405
359
|
}
|
|
@@ -414,10 +368,12 @@ function createServer() {
|
|
|
414
368
|
const alreadyActive = [...toolsetDef.tools].every(t => currentToolNames.has(t));
|
|
415
369
|
if (alreadyActive) {
|
|
416
370
|
return logCompletion({
|
|
417
|
-
content: [
|
|
371
|
+
content: [
|
|
372
|
+
{
|
|
418
373
|
type: "text",
|
|
419
374
|
text: `Category "${category}" is already active (${toolsetDef.tools.size} tools).`,
|
|
420
|
-
}
|
|
375
|
+
},
|
|
376
|
+
],
|
|
421
377
|
});
|
|
422
378
|
}
|
|
423
379
|
// Add tools from this toolset, respecting all filtering policies
|
|
@@ -437,10 +393,12 @@ function createServer() {
|
|
|
437
393
|
}
|
|
438
394
|
if (newTools.length === 0) {
|
|
439
395
|
return logCompletion({
|
|
440
|
-
content: [
|
|
396
|
+
content: [
|
|
397
|
+
{
|
|
441
398
|
type: "text",
|
|
442
399
|
text: `Category "${category}" has no additional tools to activate (all already active or filtered).`,
|
|
443
|
-
}
|
|
400
|
+
},
|
|
401
|
+
],
|
|
444
402
|
});
|
|
445
403
|
}
|
|
446
404
|
filteredTools.push(...newTools);
|
|
@@ -460,7 +418,7 @@ function createServer() {
|
|
|
460
418
|
activated: category,
|
|
461
419
|
addedTools: addedNames,
|
|
462
420
|
totalTools: filteredTools.length,
|
|
463
|
-
}
|
|
421
|
+
}),
|
|
464
422
|
}],
|
|
465
423
|
});
|
|
466
424
|
}
|
|
@@ -470,10 +428,12 @@ function createServer() {
|
|
|
470
428
|
if (!confirmed) {
|
|
471
429
|
logger.info({ tool: toolName, event: "tool_call_approval_required" }, `Approval required: ${toolName}`);
|
|
472
430
|
return logCompletion({
|
|
473
|
-
content: [
|
|
431
|
+
content: [
|
|
432
|
+
{
|
|
474
433
|
type: "text",
|
|
475
434
|
text: `Tool "${toolName}" requires confirmation. This tool is marked as requiring approval before execution. Re-call with _confirmed: true to proceed.`,
|
|
476
|
-
}
|
|
435
|
+
},
|
|
436
|
+
],
|
|
477
437
|
});
|
|
478
438
|
}
|
|
479
439
|
// Strip _confirmed from args before forwarding to handler
|
|
@@ -507,6 +467,14 @@ function createServer() {
|
|
|
507
467
|
/**
|
|
508
468
|
* Validate configuration at startup
|
|
509
469
|
*/
|
|
470
|
+
function isLoopbackBindHost(host) {
|
|
471
|
+
const normalized = host.trim().toLowerCase().replace(/^\[|\]$/g, "");
|
|
472
|
+
const isIpv4Loopback = /^127(?:\.\d{1,3}){3}$/.test(normalized);
|
|
473
|
+
return (normalized === "localhost" ||
|
|
474
|
+
isIpv4Loopback ||
|
|
475
|
+
normalized === "::1" ||
|
|
476
|
+
normalized === "0:0:0:0:0:0:0:1");
|
|
477
|
+
}
|
|
510
478
|
function validateConfiguration() {
|
|
511
479
|
const errors = [];
|
|
512
480
|
// Validate SESSION_TIMEOUT_SECONDS
|
|
@@ -557,6 +525,12 @@ function validateConfiguration() {
|
|
|
557
525
|
}
|
|
558
526
|
}
|
|
559
527
|
}
|
|
528
|
+
const allowedHosts = getConfig("allowed-hosts", "GITLAB_ALLOWED_HOSTS")?.split(",") || [];
|
|
529
|
+
for (const host of allowedHosts) {
|
|
530
|
+
if (host.trim() && !toAllowedGitLabApiUrl(host)) {
|
|
531
|
+
errors.push(`GITLAB_ALLOWED_HOSTS contains an invalid host or URL: ${host.trim()}`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
560
534
|
// Validate auth configuration
|
|
561
535
|
const remoteAuth = getConfig("remote-auth", "REMOTE_AUTHORIZATION") === "true";
|
|
562
536
|
const useOAuth = getConfig("use-oauth", "GITLAB_USE_OAUTH") === "true";
|
|
@@ -566,12 +540,19 @@ function validateConfiguration() {
|
|
|
566
540
|
const mcpOAuth = getConfig("mcp-oauth", "GITLAB_MCP_OAUTH") === "true";
|
|
567
541
|
const mcpServerUrl = getConfig("mcp-server-url", "MCP_SERVER_URL");
|
|
568
542
|
const streamableHttp = getConfig("streamable-http", "STREAMABLE_HTTP") === "true";
|
|
543
|
+
const sse = getConfig("sse", "SSE") === "true";
|
|
544
|
+
const bindHost = getConfig("host", "HOST") || "127.0.0.1";
|
|
545
|
+
const sseAuthToken = getConfig("sse-auth-token", "SSE_AUTH_TOKEN");
|
|
546
|
+
const allowUnauthenticatedRemoteSse = getConfig("sse-dangerously-allow-unauthenticated-remote", "SSE_DANGEROUSLY_ALLOW_UNAUTHENTICATED_REMOTE") === "true";
|
|
569
547
|
if (!remoteAuth && !useOAuth && !hasToken && !hasJobToken && !hasCookie && !mcpOAuth) {
|
|
570
548
|
errors.push("Either --token, --job-token, --cookie-path, --use-oauth=true, --remote-auth=true, or --mcp-oauth=true must be set (or use environment variables)");
|
|
571
549
|
}
|
|
572
550
|
if (streamableHttp && (hasToken || hasJobToken) && !remoteAuth && !mcpOAuth) {
|
|
573
551
|
errors.push("STREAMABLE_HTTP=true/--streamable-http with GITLAB_PERSONAL_ACCESS_TOKEN/--token or GITLAB_JOB_TOKEN/--job-token requires REMOTE_AUTHORIZATION=true/--remote-auth=true or GITLAB_MCP_OAUTH=true/--mcp-oauth=true");
|
|
574
552
|
}
|
|
553
|
+
if (sse && !isLoopbackBindHost(bindHost) && !sseAuthToken && !allowUnauthenticatedRemoteSse) {
|
|
554
|
+
errors.push("SSE=true on a non-loopback HOST requires SSE_AUTH_TOKEN (or explicitly set SSE_DANGEROUSLY_ALLOW_UNAUTHENTICATED_REMOTE=true)");
|
|
555
|
+
}
|
|
575
556
|
if (mcpOAuth) {
|
|
576
557
|
if (!mcpServerUrl) {
|
|
577
558
|
errors.push("MCP_SERVER_URL is required when GITLAB_MCP_OAUTH=true (e.g. https://mcp.example.com)");
|
|
@@ -636,13 +617,24 @@ function redactSessionIdForLog(sid) {
|
|
|
636
617
|
function isInitializationRequestBody(body) {
|
|
637
618
|
if (!body)
|
|
638
619
|
return false;
|
|
639
|
-
const isInitObj = (m) => typeof m === "object" &&
|
|
640
|
-
m !== null &&
|
|
641
|
-
m.method === "initialize";
|
|
620
|
+
const isInitObj = (m) => typeof m === "object" && m !== null && m.method === "initialize";
|
|
642
621
|
if (Array.isArray(body))
|
|
643
622
|
return body.some(isInitObj);
|
|
644
623
|
return isInitObj(body);
|
|
645
624
|
}
|
|
625
|
+
function isUnauthenticatedDiscoveryRequestBody(body) {
|
|
626
|
+
if (!body)
|
|
627
|
+
return false;
|
|
628
|
+
const isDiscoveryMethod = (m) => {
|
|
629
|
+
if (typeof m !== "object" || m === null)
|
|
630
|
+
return false;
|
|
631
|
+
const method = m.method;
|
|
632
|
+
return (method === "initialize" || method === "notifications/initialized" || method === "tools/list");
|
|
633
|
+
};
|
|
634
|
+
if (Array.isArray(body))
|
|
635
|
+
return body.every(isDiscoveryMethod);
|
|
636
|
+
return isDiscoveryMethod(body);
|
|
637
|
+
}
|
|
646
638
|
/**
|
|
647
639
|
* Normalize an `Mcp-Session-Id` header value.
|
|
648
640
|
*
|
|
@@ -699,9 +691,7 @@ catch (err) {
|
|
|
699
691
|
* a 401 from the OAuth bearer middleware.
|
|
700
692
|
*/
|
|
701
693
|
export function hasStatelessSessionId(req) {
|
|
702
|
-
return Boolean(OAUTH_STATELESS_MODE &&
|
|
703
|
-
STATELESS_MATERIAL &&
|
|
704
|
-
readMcpSessionIdHeader(req));
|
|
694
|
+
return Boolean(OAUTH_STATELESS_MODE && STATELESS_MATERIAL && readMcpSessionIdHeader(req));
|
|
705
695
|
}
|
|
706
696
|
/**
|
|
707
697
|
* Ensure the OAuth token is valid before making an API call.
|
|
@@ -845,7 +835,9 @@ function defaultAuthRetryConfig() {
|
|
|
845
835
|
return {
|
|
846
836
|
isOAuthEnabled: () => USE_OAUTH && oauthClient != null,
|
|
847
837
|
refreshToken: (force) => oauthClient.getAccessToken(force),
|
|
848
|
-
onTokenRefreshed: (token) => {
|
|
838
|
+
onTokenRefreshed: (token) => {
|
|
839
|
+
OAUTH_ACCESS_TOKEN = token;
|
|
840
|
+
},
|
|
849
841
|
buildAuthHeaders,
|
|
850
842
|
logger,
|
|
851
843
|
};
|
|
@@ -1015,11 +1007,57 @@ if (GITLAB_TOOLSETS_RAW && (USE_PIPELINE || USE_MILESTONE || USE_GITLAB_WIKI)) {
|
|
|
1015
1007
|
"Legacy flags add tools additively on top of the toolset selection and may produce unexpected results.");
|
|
1016
1008
|
}
|
|
1017
1009
|
const MERGE_REQUEST_DEPLOYMENT_SUMMARY_LIMIT = 10;
|
|
1010
|
+
function toAllowedGitLabApiUrl(value) {
|
|
1011
|
+
const trimmed = value.trim();
|
|
1012
|
+
if (!trimmed)
|
|
1013
|
+
return null;
|
|
1014
|
+
try {
|
|
1015
|
+
const url = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`);
|
|
1016
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
1017
|
+
return null;
|
|
1018
|
+
return { host: url.host, apiUrl: normalizeGitLabApiUrl(url.toString()) };
|
|
1019
|
+
}
|
|
1020
|
+
catch {
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
function parseAllowedGitLabApiUrls(value) {
|
|
1025
|
+
return value
|
|
1026
|
+
.split(",")
|
|
1027
|
+
.map(toAllowedGitLabApiUrl)
|
|
1028
|
+
.filter((entry) => Boolean(entry));
|
|
1029
|
+
}
|
|
1030
|
+
function encodeGitLabPathSegment(value) {
|
|
1031
|
+
return encodeURIComponent(decodeURIComponent(value));
|
|
1032
|
+
}
|
|
1033
|
+
function encodeGitLabPath(value) {
|
|
1034
|
+
return value.split("/").map(encodeGitLabPathSegment).join("/");
|
|
1035
|
+
}
|
|
1036
|
+
function resolveTrustedGitLabApiUrl(value) {
|
|
1037
|
+
const parsed = new URL(normalizeGitLabApiUrl(value));
|
|
1038
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
1039
|
+
throw new Error("GitLab API URL must use HTTP or HTTPS");
|
|
1040
|
+
}
|
|
1041
|
+
const allowedApiUrl = GITLAB_ALLOWED_API_URLS_BY_HOST.get(parsed.host);
|
|
1042
|
+
if (!allowedApiUrl) {
|
|
1043
|
+
throw new Error(`GitLab API URL host is not allowed: ${parsed.host}`);
|
|
1044
|
+
}
|
|
1045
|
+
return allowedApiUrl;
|
|
1046
|
+
}
|
|
1018
1047
|
// Use the normalizeGitLabApiUrl function to handle various URL formats
|
|
1019
1048
|
const GITLAB_API_URLS = (getConfig("api-url", "GITLAB_API_URL") || "https://gitlab.com")
|
|
1020
1049
|
.split(",")
|
|
1021
1050
|
.map(normalizeGitLabApiUrl);
|
|
1022
1051
|
const GITLAB_API_URL = GITLAB_API_URLS[0];
|
|
1052
|
+
const GITLAB_ALLOWED_API_URLS_BY_HOST = new Map();
|
|
1053
|
+
for (const { host, apiUrl } of [
|
|
1054
|
+
...GITLAB_API_URLS.map(toAllowedGitLabApiUrl).filter((entry) => Boolean(entry)),
|
|
1055
|
+
...parseAllowedGitLabApiUrls(getConfig("allowed-hosts", "GITLAB_ALLOWED_HOSTS") || ""),
|
|
1056
|
+
]) {
|
|
1057
|
+
if (!GITLAB_ALLOWED_API_URLS_BY_HOST.has(host)) {
|
|
1058
|
+
GITLAB_ALLOWED_API_URLS_BY_HOST.set(host, apiUrl);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1023
1061
|
const GITLAB_PROJECT_ID = process.env.GITLAB_PROJECT_ID;
|
|
1024
1062
|
const GITLAB_ALLOWED_PROJECT_IDS = process.env.GITLAB_ALLOWED_PROJECT_IDS?.split(",")
|
|
1025
1063
|
.map(id => id.trim())
|
|
@@ -1058,7 +1096,12 @@ if (GITLAB_MCP_OAUTH) {
|
|
|
1058
1096
|
}
|
|
1059
1097
|
logger.info("MCP OAuth enabled: GitLab OAuth proxy active (Private-Token/JOB-TOKEN headers bypass OAuth)");
|
|
1060
1098
|
}
|
|
1061
|
-
if (!REMOTE_AUTHORIZATION &&
|
|
1099
|
+
if (!REMOTE_AUTHORIZATION &&
|
|
1100
|
+
!GITLAB_MCP_OAUTH &&
|
|
1101
|
+
!USE_OAUTH &&
|
|
1102
|
+
!GITLAB_PERSONAL_ACCESS_TOKEN &&
|
|
1103
|
+
!GITLAB_JOB_TOKEN &&
|
|
1104
|
+
!GITLAB_AUTH_COOKIE_PATH) {
|
|
1062
1105
|
// Standard mode: token must be in environment (unless using OAuth)
|
|
1063
1106
|
logger.error("GITLAB_PERSONAL_ACCESS_TOKEN environment variable is not set");
|
|
1064
1107
|
logger.info("Either set GITLAB_PERSONAL_ACCESS_TOKEN or enable OAuth with GITLAB_USE_OAUTH=true");
|
|
@@ -1483,18 +1526,28 @@ async function resolveNamesToIds(projectPath, labelNames, usernames) {
|
|
|
1483
1526
|
if (!labelNames?.length && !usernames?.length) {
|
|
1484
1527
|
return { labelIds: [], userIds: [] };
|
|
1485
1528
|
}
|
|
1486
|
-
|
|
1487
|
-
|
|
1529
|
+
labelNames ??= [];
|
|
1530
|
+
usernames ??= [];
|
|
1531
|
+
const labelVars = Object.fromEntries(labelNames.map((name, i) => [`l${i}`, name]));
|
|
1532
|
+
// One alias per label — exact title match via the `title` argument, includes ancestor
|
|
1533
|
+
// group labels, single round trip with no pagination needed.
|
|
1534
|
+
const varDefs = labelNames.map((_, i) => `$l${i}: String!`).join(", ");
|
|
1535
|
+
const aliases = labelNames.map((_, i) => `l${i}: labels(title: $l${i}, includeAncestorGroups: true, first: 1) { nodes { id } }`).join(" ");
|
|
1536
|
+
const { project, users } = await executeGraphQL(`query($path: ID!, $usernames: [String!]!${varDefs ? `, ${varDefs}` : ""}) {
|
|
1537
|
+
project(fullPath: $path) { ${aliases || "__typename"} }
|
|
1488
1538
|
users(usernames: $usernames) { nodes { id username } }
|
|
1489
|
-
}`, { path: projectPath, usernames
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1539
|
+
}`, { path: projectPath, usernames, ...labelVars });
|
|
1540
|
+
if (!project) {
|
|
1541
|
+
throw new Error(`Project '${projectPath}' not found or inaccessible`);
|
|
1542
|
+
}
|
|
1543
|
+
const labelIds = labelNames.map((name, i) => {
|
|
1544
|
+
const nodes = project[`l${i}`]?.nodes;
|
|
1545
|
+
if (!nodes?.length)
|
|
1493
1546
|
throw new Error(`Label '${name}' not found in project`);
|
|
1494
|
-
return
|
|
1547
|
+
return nodes[0].id;
|
|
1495
1548
|
});
|
|
1496
|
-
const userIds =
|
|
1497
|
-
const user =
|
|
1549
|
+
const userIds = usernames.map(name => {
|
|
1550
|
+
const user = users.nodes.find(u => u.username === name);
|
|
1498
1551
|
if (!user)
|
|
1499
1552
|
throw new Error(`User '${name}' not found`);
|
|
1500
1553
|
return user.id;
|
|
@@ -1534,7 +1587,7 @@ async function resolveWorkItemTypeGID(projectPath, typeName) {
|
|
|
1534
1587
|
}
|
|
1535
1588
|
}
|
|
1536
1589
|
}`, { path: projectPath });
|
|
1537
|
-
const typeNode = data.namespace?.workItemTypes?.nodes?.find(
|
|
1590
|
+
const typeNode = data.namespace?.workItemTypes?.nodes?.find(n => n.name === targetName);
|
|
1538
1591
|
if (!typeNode) {
|
|
1539
1592
|
throw new Error(`Work item type '${targetName}' not found in project ${projectPath}`);
|
|
1540
1593
|
}
|
|
@@ -2140,7 +2193,11 @@ async function getWorkItem(projectId, iid) {
|
|
|
2140
2193
|
if (wi.closedAt)
|
|
2141
2194
|
result.closedAt = wi.closedAt;
|
|
2142
2195
|
if (statusWidget?.status)
|
|
2143
|
-
result.status = {
|
|
2196
|
+
result.status = {
|
|
2197
|
+
name: statusWidget.status.name,
|
|
2198
|
+
id: statusWidget.status.id,
|
|
2199
|
+
category: statusWidget.status.category,
|
|
2200
|
+
};
|
|
2144
2201
|
const labels = (labelsWidget?.labels?.nodes || []).map((l) => l.title);
|
|
2145
2202
|
if (labels.length > 0)
|
|
2146
2203
|
result.labels = labels;
|
|
@@ -2178,10 +2235,23 @@ async function getWorkItem(projectId, iid) {
|
|
|
2178
2235
|
if (colorWidget?.color)
|
|
2179
2236
|
result.color = colorWidget.color;
|
|
2180
2237
|
if (hierarchyWidget?.parent)
|
|
2181
|
-
result.parent = {
|
|
2238
|
+
result.parent = {
|
|
2239
|
+
iid: hierarchyWidget.parent.iid,
|
|
2240
|
+
title: hierarchyWidget.parent.title,
|
|
2241
|
+
type: hierarchyWidget.parent.workItemType?.name,
|
|
2242
|
+
project: hierarchyWidget.parent.namespace?.fullPath,
|
|
2243
|
+
webUrl: hierarchyWidget.parent.webUrl,
|
|
2244
|
+
};
|
|
2182
2245
|
const children = hierarchyWidget?.children?.nodes || [];
|
|
2183
2246
|
if (children.length > 0)
|
|
2184
|
-
result.children = children.map((c) => ({
|
|
2247
|
+
result.children = children.map((c) => ({
|
|
2248
|
+
iid: c.iid,
|
|
2249
|
+
title: c.title,
|
|
2250
|
+
state: c.state,
|
|
2251
|
+
type: c.workItemType?.name,
|
|
2252
|
+
project: c.namespace?.fullPath,
|
|
2253
|
+
webUrl: c.webUrl,
|
|
2254
|
+
}));
|
|
2185
2255
|
if (linkedItemsWidget?.blocked)
|
|
2186
2256
|
result.blocked = true;
|
|
2187
2257
|
if (linkedItemsWidget?.blockedByCount > 0)
|
|
@@ -2253,7 +2323,7 @@ async function listWorkItems(projectId, options) {
|
|
|
2253
2323
|
first: options.first || 20,
|
|
2254
2324
|
};
|
|
2255
2325
|
if (options.types && options.types.length > 0) {
|
|
2256
|
-
variables.types = options.types.map(
|
|
2326
|
+
variables.types = options.types.map(t => typeMap[t] || t.replace(/ /g, "_").toUpperCase());
|
|
2257
2327
|
}
|
|
2258
2328
|
if (options.state) {
|
|
2259
2329
|
variables.state = options.state === "opened" ? "opened" : "closed";
|
|
@@ -2721,7 +2791,9 @@ async function updateWorkItem(projectId, iid, options) {
|
|
|
2721
2791
|
linked_items_added: options.linked_items_to_add?.length || 0,
|
|
2722
2792
|
linked_items_removed: options.linked_items_to_remove?.length || 0,
|
|
2723
2793
|
...(options.severity !== undefined && { severity: options.severity }),
|
|
2724
|
-
...(options.escalation_status !== undefined && {
|
|
2794
|
+
...(options.escalation_status !== undefined && {
|
|
2795
|
+
escalation_status: options.escalation_status,
|
|
2796
|
+
}),
|
|
2725
2797
|
};
|
|
2726
2798
|
}
|
|
2727
2799
|
/**
|
|
@@ -3023,9 +3095,7 @@ async function updateIssueNote(projectId, issueIid, discussionId, noteId, body,
|
|
|
3023
3095
|
async function createIssueNote(projectId, issueIid, discussionId, body, createdAt) {
|
|
3024
3096
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3025
3097
|
const basePath = `${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}`;
|
|
3026
|
-
const url = new URL(discussionId
|
|
3027
|
-
? `${basePath}/discussions/${discussionId}/notes`
|
|
3028
|
-
: `${basePath}/notes`);
|
|
3098
|
+
const url = new URL(discussionId ? `${basePath}/discussions/${discussionId}/notes` : `${basePath}/notes`);
|
|
3029
3099
|
const payload = { body };
|
|
3030
3100
|
if (createdAt) {
|
|
3031
3101
|
payload.created_at = createdAt;
|
|
@@ -3392,6 +3462,7 @@ async function createRepository(options) {
|
|
|
3392
3462
|
method: "POST",
|
|
3393
3463
|
body: JSON.stringify({
|
|
3394
3464
|
name: options.name,
|
|
3465
|
+
...(options.namespace_id !== undefined ? { namespace_id: options.namespace_id } : {}),
|
|
3395
3466
|
description: options.description,
|
|
3396
3467
|
visibility: options.visibility,
|
|
3397
3468
|
initialize_with_readme: options.initialize_with_readme,
|
|
@@ -5718,7 +5789,7 @@ async function getCurrentUser() {
|
|
|
5718
5789
|
if ((response.status === 401 || response.status === 403) && usesJobTokenHeader()) {
|
|
5719
5790
|
const jobResponse = await fetch(`${getEffectiveApiUrl()}/job`, getFetchConfig());
|
|
5720
5791
|
if (jobResponse.ok) {
|
|
5721
|
-
const jobData = await jobResponse.json();
|
|
5792
|
+
const jobData = (await jobResponse.json());
|
|
5722
5793
|
if (jobData.user) {
|
|
5723
5794
|
return GitLabUserSchema.parse(jobData.user);
|
|
5724
5795
|
}
|
|
@@ -5742,7 +5813,8 @@ async function myIssues(options = {}) {
|
|
|
5742
5813
|
effectiveProjectId = getEffectiveProjectId(options.project_id || "");
|
|
5743
5814
|
}
|
|
5744
5815
|
catch (err) {
|
|
5745
|
-
if (err instanceof Error &&
|
|
5816
|
+
if (err instanceof Error &&
|
|
5817
|
+
err.message.includes("No project ID provided and GITLAB_PROJECT_ID is not set")) {
|
|
5746
5818
|
effectiveProjectId = "";
|
|
5747
5819
|
}
|
|
5748
5820
|
else {
|
|
@@ -5956,7 +6028,11 @@ async function updateGroupVariable(groupId, key, options) {
|
|
|
5956
6028
|
if (filter?.environment_scope) {
|
|
5957
6029
|
url.searchParams.append("filter[environment_scope]", filter.environment_scope);
|
|
5958
6030
|
}
|
|
5959
|
-
const response = await fetch(url.toString(), {
|
|
6031
|
+
const response = await fetch(url.toString(), {
|
|
6032
|
+
...getFetchConfig(),
|
|
6033
|
+
method: "PUT",
|
|
6034
|
+
body: JSON.stringify(body),
|
|
6035
|
+
});
|
|
5960
6036
|
await handleGitLabError(response);
|
|
5961
6037
|
const data = await response.json();
|
|
5962
6038
|
return GitLabCiVariableSchema.parse(data);
|
|
@@ -6004,7 +6080,9 @@ async function getDependencyProxySettings(groupPath) {
|
|
|
6004
6080
|
});
|
|
6005
6081
|
}
|
|
6006
6082
|
async function updateDependencyProxySettings(groupPath, options) {
|
|
6007
|
-
if (options.enabled === undefined &&
|
|
6083
|
+
if (options.enabled === undefined &&
|
|
6084
|
+
options.identity === undefined &&
|
|
6085
|
+
options.secret === undefined) {
|
|
6008
6086
|
throw new Error("At least one of enabled, identity, or secret must be provided");
|
|
6009
6087
|
}
|
|
6010
6088
|
const fullPath = await resolveGroupFullPath(groupPath);
|
|
@@ -6038,7 +6116,11 @@ async function listDependencyProxyBlobs(groupPath, options = {}) {
|
|
|
6038
6116
|
if (!conn)
|
|
6039
6117
|
throw new Error(`Group not found or dependency proxy not enabled: ${fullPath}`);
|
|
6040
6118
|
return {
|
|
6041
|
-
blobs: conn.nodes.map(n => GitLabDependencyProxyBlobSchema.parse({
|
|
6119
|
+
blobs: conn.nodes.map(n => GitLabDependencyProxyBlobSchema.parse({
|
|
6120
|
+
file_name: n.fileName,
|
|
6121
|
+
size: n.size,
|
|
6122
|
+
created_at: n.createdAt,
|
|
6123
|
+
})),
|
|
6042
6124
|
pageInfo: conn.pageInfo,
|
|
6043
6125
|
};
|
|
6044
6126
|
}
|
|
@@ -6098,7 +6180,7 @@ async function markdownUpload(projectId, filePath, content, filename) {
|
|
|
6098
6180
|
const response = await fetch(url.toString(), {
|
|
6099
6181
|
...defaultFetchConfig,
|
|
6100
6182
|
method: "POST",
|
|
6101
|
-
body: form
|
|
6183
|
+
body: form,
|
|
6102
6184
|
});
|
|
6103
6185
|
if (!response.ok) {
|
|
6104
6186
|
await handleGitLabError(response);
|
|
@@ -6419,6 +6501,34 @@ async function getTagSignature(projectId, tagName) {
|
|
|
6419
6501
|
const data = await response.json();
|
|
6420
6502
|
return GitLabTagSignatureSchema.parse(data);
|
|
6421
6503
|
}
|
|
6504
|
+
async function executeGitLabGraphQL(query, variables = {}) {
|
|
6505
|
+
const apiUrl = new URL(getEffectiveApiUrl());
|
|
6506
|
+
const restPath = apiUrl.pathname || "";
|
|
6507
|
+
const idx = restPath.lastIndexOf("/api/v4");
|
|
6508
|
+
const prefix = idx >= 0 ? restPath.slice(0, idx) : "";
|
|
6509
|
+
const graphqlUrl = process.env.GITLAB_GRAPHQL_URL || `${apiUrl.origin}${prefix}/api/graphql`;
|
|
6510
|
+
const controller = new AbortController();
|
|
6511
|
+
const timeout = setTimeout(() => controller.abort(), 45000);
|
|
6512
|
+
try {
|
|
6513
|
+
const response = await fetch(graphqlUrl, {
|
|
6514
|
+
...getFetchConfig(),
|
|
6515
|
+
method: "POST",
|
|
6516
|
+
headers: {
|
|
6517
|
+
...BASE_HEADERS,
|
|
6518
|
+
...buildAuthHeaders(),
|
|
6519
|
+
},
|
|
6520
|
+
body: JSON.stringify({ query, variables }),
|
|
6521
|
+
signal: controller.signal,
|
|
6522
|
+
});
|
|
6523
|
+
if (!response.ok) {
|
|
6524
|
+
await handleGitLabError(response);
|
|
6525
|
+
}
|
|
6526
|
+
return await response.json();
|
|
6527
|
+
}
|
|
6528
|
+
finally {
|
|
6529
|
+
clearTimeout(timeout);
|
|
6530
|
+
}
|
|
6531
|
+
}
|
|
6422
6532
|
// Request handlers are now registered inside createServer() factory function
|
|
6423
6533
|
// to ensure each transport connection gets its own Server instance (GHSA-345p-7cg4-v4c7).
|
|
6424
6534
|
async function handleToolCall(params) {
|
|
@@ -6482,7 +6592,7 @@ async function handleToolCall(params) {
|
|
|
6482
6592
|
}
|
|
6483
6593
|
const json = await response.json();
|
|
6484
6594
|
return {
|
|
6485
|
-
content: [{ type: "text", text: JSON.stringify(json
|
|
6595
|
+
content: [{ type: "text", text: JSON.stringify(json) }],
|
|
6486
6596
|
};
|
|
6487
6597
|
}
|
|
6488
6598
|
catch (err) {
|
|
@@ -6491,7 +6601,7 @@ async function handleToolCall(params) {
|
|
|
6491
6601
|
content: [
|
|
6492
6602
|
{
|
|
6493
6603
|
type: "text",
|
|
6494
|
-
text: JSON.stringify({ error: `GraphQL request failed: ${message}` }
|
|
6604
|
+
text: JSON.stringify({ error: `GraphQL request failed: ${message}` }),
|
|
6495
6605
|
},
|
|
6496
6606
|
],
|
|
6497
6607
|
};
|
|
@@ -6506,7 +6616,7 @@ async function handleToolCall(params) {
|
|
|
6506
6616
|
try {
|
|
6507
6617
|
const forkedProject = await forkProject(forkArgs.project_id, forkArgs.namespace);
|
|
6508
6618
|
return {
|
|
6509
|
-
content: [{ type: "text", text: JSON.stringify(forkedProject
|
|
6619
|
+
content: [{ type: "text", text: JSON.stringify(forkedProject) }],
|
|
6510
6620
|
};
|
|
6511
6621
|
}
|
|
6512
6622
|
catch (forkError) {
|
|
@@ -6519,7 +6629,7 @@ async function handleToolCall(params) {
|
|
|
6519
6629
|
content: [
|
|
6520
6630
|
{
|
|
6521
6631
|
type: "text",
|
|
6522
|
-
text: JSON.stringify({ error: forkErrorMessage }
|
|
6632
|
+
text: JSON.stringify({ error: forkErrorMessage }),
|
|
6523
6633
|
},
|
|
6524
6634
|
],
|
|
6525
6635
|
};
|
|
@@ -6536,7 +6646,7 @@ async function handleToolCall(params) {
|
|
|
6536
6646
|
ref,
|
|
6537
6647
|
});
|
|
6538
6648
|
return {
|
|
6539
|
-
content: [{ type: "text", text: JSON.stringify(branch
|
|
6649
|
+
content: [{ type: "text", text: JSON.stringify(branch) }],
|
|
6540
6650
|
};
|
|
6541
6651
|
}
|
|
6542
6652
|
case "get_branch_diffs": {
|
|
@@ -6544,14 +6654,14 @@ async function handleToolCall(params) {
|
|
|
6544
6654
|
const diffResp = await getBranchDiffs(args.project_id, args.from, args.to, args.straight);
|
|
6545
6655
|
diffResp.diffs = filterDiffsByPatterns(diffResp.diffs, args.excluded_file_patterns);
|
|
6546
6656
|
return {
|
|
6547
|
-
content: [{ type: "text", text: JSON.stringify(diffResp
|
|
6657
|
+
content: [{ type: "text", text: JSON.stringify(diffResp) }],
|
|
6548
6658
|
};
|
|
6549
6659
|
}
|
|
6550
6660
|
case "search_repositories": {
|
|
6551
6661
|
const args = SearchRepositoriesSchema.parse(params.arguments);
|
|
6552
6662
|
const results = await searchProjects(args.search, args.page, args.per_page);
|
|
6553
6663
|
return {
|
|
6554
|
-
content: [{ type: "text", text: JSON.stringify(results
|
|
6664
|
+
content: [{ type: "text", text: JSON.stringify(results) }],
|
|
6555
6665
|
};
|
|
6556
6666
|
}
|
|
6557
6667
|
case "search_code": {
|
|
@@ -6565,7 +6675,7 @@ async function handleToolCall(params) {
|
|
|
6565
6675
|
per_page: args.per_page,
|
|
6566
6676
|
});
|
|
6567
6677
|
return {
|
|
6568
|
-
content: [{ type: "text", text: JSON.stringify(results
|
|
6678
|
+
content: [{ type: "text", text: JSON.stringify(results) }],
|
|
6569
6679
|
};
|
|
6570
6680
|
}
|
|
6571
6681
|
case "search_project_code": {
|
|
@@ -6581,7 +6691,7 @@ async function handleToolCall(params) {
|
|
|
6581
6691
|
per_page: args.per_page,
|
|
6582
6692
|
});
|
|
6583
6693
|
return {
|
|
6584
|
-
content: [{ type: "text", text: JSON.stringify(results
|
|
6694
|
+
content: [{ type: "text", text: JSON.stringify(results) }],
|
|
6585
6695
|
};
|
|
6586
6696
|
}
|
|
6587
6697
|
case "search_group_code": {
|
|
@@ -6596,7 +6706,7 @@ async function handleToolCall(params) {
|
|
|
6596
6706
|
per_page: args.per_page,
|
|
6597
6707
|
});
|
|
6598
6708
|
return {
|
|
6599
|
-
content: [{ type: "text", text: JSON.stringify(results
|
|
6709
|
+
content: [{ type: "text", text: JSON.stringify(results) }],
|
|
6600
6710
|
};
|
|
6601
6711
|
}
|
|
6602
6712
|
case "create_repository": {
|
|
@@ -6604,7 +6714,7 @@ async function handleToolCall(params) {
|
|
|
6604
6714
|
const args = CreateRepositorySchema.parse(params.arguments);
|
|
6605
6715
|
const repository = await createRepository(args);
|
|
6606
6716
|
return {
|
|
6607
|
-
content: [{ type: "text", text: JSON.stringify(repository
|
|
6717
|
+
content: [{ type: "text", text: JSON.stringify(repository) }],
|
|
6608
6718
|
};
|
|
6609
6719
|
}
|
|
6610
6720
|
case "create_group": {
|
|
@@ -6631,28 +6741,28 @@ async function handleToolCall(params) {
|
|
|
6631
6741
|
const data = await response.json();
|
|
6632
6742
|
const group = GitLabGroupSchema.parse(data);
|
|
6633
6743
|
return {
|
|
6634
|
-
content: [{ type: "text", text: JSON.stringify(group
|
|
6744
|
+
content: [{ type: "text", text: JSON.stringify(group) }],
|
|
6635
6745
|
};
|
|
6636
6746
|
}
|
|
6637
6747
|
case "get_file_contents": {
|
|
6638
6748
|
const args = GetFileContentsSchema.parse(params.arguments);
|
|
6639
6749
|
const contents = await getFileContents(args.project_id, args.file_path, args.ref);
|
|
6640
6750
|
return {
|
|
6641
|
-
content: [{ type: "text", text: JSON.stringify(contents
|
|
6751
|
+
content: [{ type: "text", text: JSON.stringify(contents) }],
|
|
6642
6752
|
};
|
|
6643
6753
|
}
|
|
6644
6754
|
case "create_or_update_file": {
|
|
6645
6755
|
const args = CreateOrUpdateFileSchema.parse(params.arguments);
|
|
6646
6756
|
const result = await createOrUpdateFile(args.project_id, args.file_path, args.content, args.commit_message, args.branch, args.previous_path, args.last_commit_id, args.commit_id);
|
|
6647
6757
|
return {
|
|
6648
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
6758
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
6649
6759
|
};
|
|
6650
6760
|
}
|
|
6651
6761
|
case "push_files": {
|
|
6652
6762
|
const args = PushFilesSchema.parse(params.arguments);
|
|
6653
6763
|
const result = await createCommit(args.project_id, args.commit_message, args.branch, args.files.map(f => ({ path: f.file_path, content: f.content })));
|
|
6654
6764
|
return {
|
|
6655
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
6765
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
6656
6766
|
};
|
|
6657
6767
|
}
|
|
6658
6768
|
case "create_issue": {
|
|
@@ -6660,7 +6770,7 @@ async function handleToolCall(params) {
|
|
|
6660
6770
|
const { project_id, ...options } = args;
|
|
6661
6771
|
const issue = await createIssue(project_id, options);
|
|
6662
6772
|
return {
|
|
6663
|
-
content: [{ type: "text", text: JSON.stringify(issue
|
|
6773
|
+
content: [{ type: "text", text: JSON.stringify(issue) }],
|
|
6664
6774
|
};
|
|
6665
6775
|
}
|
|
6666
6776
|
case "create_merge_request": {
|
|
@@ -6668,7 +6778,7 @@ async function handleToolCall(params) {
|
|
|
6668
6778
|
const { project_id, ...options } = args;
|
|
6669
6779
|
const mergeRequest = await createMergeRequest(project_id, options);
|
|
6670
6780
|
return {
|
|
6671
|
-
content: [{ type: "text", text: JSON.stringify(mergeRequest
|
|
6781
|
+
content: [{ type: "text", text: JSON.stringify(mergeRequest) }],
|
|
6672
6782
|
};
|
|
6673
6783
|
}
|
|
6674
6784
|
case "delete_merge_request_discussion_note": {
|
|
@@ -6685,21 +6795,21 @@ async function handleToolCall(params) {
|
|
|
6685
6795
|
args.resolved // Now one of body or resolved must be provided, not both
|
|
6686
6796
|
);
|
|
6687
6797
|
return {
|
|
6688
|
-
content: [{ type: "text", text: JSON.stringify(note
|
|
6798
|
+
content: [{ type: "text", text: JSON.stringify(note) }],
|
|
6689
6799
|
};
|
|
6690
6800
|
}
|
|
6691
6801
|
case "create_merge_request_discussion_note": {
|
|
6692
6802
|
const args = CreateMergeRequestDiscussionNoteSchema.parse(params.arguments);
|
|
6693
6803
|
const note = await createMergeRequestDiscussionNote(args.project_id, args.merge_request_iid, args.discussion_id, args.body, args.created_at);
|
|
6694
6804
|
return {
|
|
6695
|
-
content: [{ type: "text", text: JSON.stringify(note
|
|
6805
|
+
content: [{ type: "text", text: JSON.stringify(note) }],
|
|
6696
6806
|
};
|
|
6697
6807
|
}
|
|
6698
6808
|
case "create_merge_request_note": {
|
|
6699
6809
|
const args = CreateMergeRequestNoteSchema.parse(params.arguments);
|
|
6700
6810
|
const note = await createMergeRequestNote(args.project_id, args.merge_request_iid, args.body);
|
|
6701
6811
|
return {
|
|
6702
|
-
content: [{ type: "text", text: JSON.stringify(note
|
|
6812
|
+
content: [{ type: "text", text: JSON.stringify(note) }],
|
|
6703
6813
|
};
|
|
6704
6814
|
}
|
|
6705
6815
|
case "delete_merge_request_note": {
|
|
@@ -6713,121 +6823,141 @@ async function handleToolCall(params) {
|
|
|
6713
6823
|
const args = GetMergeRequestNoteSchema.parse(params.arguments);
|
|
6714
6824
|
const note = await getMergeRequestNote(args.project_id, args.merge_request_iid, args.note_id);
|
|
6715
6825
|
return {
|
|
6716
|
-
content: [{ type: "text", text: JSON.stringify(note
|
|
6826
|
+
content: [{ type: "text", text: JSON.stringify(note) }],
|
|
6717
6827
|
};
|
|
6718
6828
|
}
|
|
6719
6829
|
case "get_merge_request_notes": {
|
|
6720
6830
|
const args = GetMergeRequestNotesSchema.parse(params.arguments);
|
|
6721
6831
|
const notes = await getMergeRequestNotes(args.project_id, args.merge_request_iid, args.sort, args.order_by, args.per_page, args.page);
|
|
6722
6832
|
return {
|
|
6723
|
-
content: [{ type: "text", text: JSON.stringify(notes
|
|
6833
|
+
content: [{ type: "text", text: JSON.stringify(notes) }],
|
|
6724
6834
|
};
|
|
6725
6835
|
}
|
|
6726
6836
|
case "update_merge_request_note": {
|
|
6727
6837
|
const args = UpdateMergeRequestNoteSchema.parse(params.arguments);
|
|
6728
6838
|
const note = await updateMergeRequestNote(args.project_id, args.merge_request_iid, args.note_id, args.body);
|
|
6729
6839
|
return {
|
|
6730
|
-
content: [{ type: "text", text: JSON.stringify(note
|
|
6840
|
+
content: [{ type: "text", text: JSON.stringify(note) }],
|
|
6731
6841
|
};
|
|
6732
6842
|
}
|
|
6733
6843
|
case "list_merge_request_emoji_reactions": {
|
|
6734
6844
|
const args = ListMergeRequestEmojiReactionsSchema.parse(params.arguments);
|
|
6735
6845
|
const path = buildAwardEmojiPath("merge_requests", args.project_id, args.merge_request_iid);
|
|
6736
6846
|
const result = await listRestAwardEmoji(path);
|
|
6737
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
6847
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
6738
6848
|
}
|
|
6739
6849
|
case "list_merge_request_note_emoji_reactions": {
|
|
6740
6850
|
const args = ListMergeRequestNoteEmojiReactionsSchema.parse(params.arguments);
|
|
6741
6851
|
const path = buildAwardEmojiPath("merge_requests", args.project_id, args.merge_request_iid, { noteId: args.note_id, discussionId: args.discussion_id });
|
|
6742
6852
|
const result = await listRestAwardEmoji(path);
|
|
6743
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
6853
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
6744
6854
|
}
|
|
6745
6855
|
case "create_merge_request_emoji_reaction": {
|
|
6746
6856
|
const args = CreateMergeRequestEmojiReactionSchema.parse(params.arguments);
|
|
6747
6857
|
const path = buildAwardEmojiPath("merge_requests", args.project_id, args.merge_request_iid);
|
|
6748
6858
|
const result = await createRestAwardEmoji(path, args.name);
|
|
6749
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
6859
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
6750
6860
|
}
|
|
6751
6861
|
case "delete_merge_request_emoji_reaction": {
|
|
6752
6862
|
const args = DeleteMergeRequestEmojiReactionSchema.parse(params.arguments);
|
|
6753
6863
|
const path = buildAwardEmojiPath("merge_requests", args.project_id, args.merge_request_iid, { awardId: args.award_id });
|
|
6754
6864
|
await deleteRestAwardEmoji(path);
|
|
6755
|
-
return {
|
|
6865
|
+
return {
|
|
6866
|
+
content: [{ type: "text", text: "Merge request emoji reaction deleted successfully" }],
|
|
6867
|
+
};
|
|
6756
6868
|
}
|
|
6757
6869
|
case "create_merge_request_note_emoji_reaction": {
|
|
6758
6870
|
const args = CreateMergeRequestNoteEmojiReactionSchema.parse(params.arguments);
|
|
6759
6871
|
const path = buildAwardEmojiPath("merge_requests", args.project_id, args.merge_request_iid, { noteId: args.note_id, discussionId: args.discussion_id });
|
|
6760
6872
|
const result = await createRestAwardEmoji(path, args.name);
|
|
6761
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
6873
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
6762
6874
|
}
|
|
6763
6875
|
case "delete_merge_request_note_emoji_reaction": {
|
|
6764
6876
|
const args = DeleteMergeRequestNoteEmojiReactionSchema.parse(params.arguments);
|
|
6765
6877
|
const path = buildAwardEmojiPath("merge_requests", args.project_id, args.merge_request_iid, { noteId: args.note_id, discussionId: args.discussion_id, awardId: args.award_id });
|
|
6766
6878
|
await deleteRestAwardEmoji(path);
|
|
6767
|
-
return {
|
|
6879
|
+
return {
|
|
6880
|
+
content: [
|
|
6881
|
+
{ type: "text", text: "Merge request note emoji reaction deleted successfully" },
|
|
6882
|
+
],
|
|
6883
|
+
};
|
|
6768
6884
|
}
|
|
6769
6885
|
case "update_issue_note": {
|
|
6770
6886
|
const args = UpdateIssueNoteSchema.parse(params.arguments);
|
|
6771
6887
|
const note = await updateIssueNote(args.project_id, args.issue_iid, args.discussion_id, args.note_id, args.body, args.resolved);
|
|
6772
6888
|
return {
|
|
6773
|
-
content: [{ type: "text", text: JSON.stringify(note
|
|
6889
|
+
content: [{ type: "text", text: JSON.stringify(note) }],
|
|
6774
6890
|
};
|
|
6775
6891
|
}
|
|
6776
6892
|
case "create_issue_note": {
|
|
6777
6893
|
const args = CreateIssueNoteSchema.parse(params.arguments);
|
|
6778
6894
|
const note = await createIssueNote(args.project_id, args.issue_iid, args.discussion_id, args.body, args.created_at);
|
|
6779
6895
|
return {
|
|
6780
|
-
content: [{ type: "text", text: JSON.stringify(note
|
|
6896
|
+
content: [{ type: "text", text: JSON.stringify(note) }],
|
|
6781
6897
|
};
|
|
6782
6898
|
}
|
|
6783
6899
|
case "list_issue_emoji_reactions": {
|
|
6784
6900
|
const args = ListIssueEmojiReactionsSchema.parse(params.arguments);
|
|
6785
6901
|
const path = buildAwardEmojiPath("issues", args.project_id, args.issue_iid);
|
|
6786
6902
|
const result = await listRestAwardEmoji(path);
|
|
6787
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
6903
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
6788
6904
|
}
|
|
6789
6905
|
case "list_issue_note_emoji_reactions": {
|
|
6790
6906
|
const args = ListIssueNoteEmojiReactionsSchema.parse(params.arguments);
|
|
6791
|
-
const path = buildAwardEmojiPath("issues", args.project_id, args.issue_iid, {
|
|
6907
|
+
const path = buildAwardEmojiPath("issues", args.project_id, args.issue_iid, {
|
|
6908
|
+
noteId: args.note_id,
|
|
6909
|
+
discussionId: args.discussion_id,
|
|
6910
|
+
});
|
|
6792
6911
|
const result = await listRestAwardEmoji(path);
|
|
6793
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
6912
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
6794
6913
|
}
|
|
6795
6914
|
case "create_issue_emoji_reaction": {
|
|
6796
6915
|
const args = CreateIssueEmojiReactionSchema.parse(params.arguments);
|
|
6797
6916
|
const path = buildAwardEmojiPath("issues", args.project_id, args.issue_iid);
|
|
6798
6917
|
const result = await createRestAwardEmoji(path, args.name);
|
|
6799
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
6918
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
6800
6919
|
}
|
|
6801
6920
|
case "delete_issue_emoji_reaction": {
|
|
6802
6921
|
const args = DeleteIssueEmojiReactionSchema.parse(params.arguments);
|
|
6803
|
-
const path = buildAwardEmojiPath("issues", args.project_id, args.issue_iid, {
|
|
6922
|
+
const path = buildAwardEmojiPath("issues", args.project_id, args.issue_iid, {
|
|
6923
|
+
awardId: args.award_id,
|
|
6924
|
+
});
|
|
6804
6925
|
await deleteRestAwardEmoji(path);
|
|
6805
6926
|
return { content: [{ type: "text", text: "Issue emoji reaction deleted successfully" }] };
|
|
6806
6927
|
}
|
|
6807
6928
|
case "create_issue_note_emoji_reaction": {
|
|
6808
6929
|
const args = CreateIssueNoteEmojiReactionSchema.parse(params.arguments);
|
|
6809
|
-
const path = buildAwardEmojiPath("issues", args.project_id, args.issue_iid, {
|
|
6930
|
+
const path = buildAwardEmojiPath("issues", args.project_id, args.issue_iid, {
|
|
6931
|
+
noteId: args.note_id,
|
|
6932
|
+
discussionId: args.discussion_id,
|
|
6933
|
+
});
|
|
6810
6934
|
const result = await createRestAwardEmoji(path, args.name);
|
|
6811
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
6935
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
6812
6936
|
}
|
|
6813
6937
|
case "delete_issue_note_emoji_reaction": {
|
|
6814
6938
|
const args = DeleteIssueNoteEmojiReactionSchema.parse(params.arguments);
|
|
6815
|
-
const path = buildAwardEmojiPath("issues", args.project_id, args.issue_iid, {
|
|
6939
|
+
const path = buildAwardEmojiPath("issues", args.project_id, args.issue_iid, {
|
|
6940
|
+
noteId: args.note_id,
|
|
6941
|
+
discussionId: args.discussion_id,
|
|
6942
|
+
awardId: args.award_id,
|
|
6943
|
+
});
|
|
6816
6944
|
await deleteRestAwardEmoji(path);
|
|
6817
|
-
return {
|
|
6945
|
+
return {
|
|
6946
|
+
content: [{ type: "text", text: "Issue note emoji reaction deleted successfully" }],
|
|
6947
|
+
};
|
|
6818
6948
|
}
|
|
6819
6949
|
case "list_todos": {
|
|
6820
6950
|
const args = ListTodosSchema.parse(params.arguments);
|
|
6821
6951
|
const todos = await listTodos(args);
|
|
6822
6952
|
return {
|
|
6823
|
-
content: [{ type: "text", text: JSON.stringify(todos
|
|
6953
|
+
content: [{ type: "text", text: JSON.stringify(todos) }],
|
|
6824
6954
|
};
|
|
6825
6955
|
}
|
|
6826
6956
|
case "mark_todo_done": {
|
|
6827
6957
|
const args = MarkTodoDoneSchema.parse(params.arguments);
|
|
6828
6958
|
const todo = await markTodoDone(args.id);
|
|
6829
6959
|
return {
|
|
6830
|
-
content: [{ type: "text", text: JSON.stringify(todo
|
|
6960
|
+
content: [{ type: "text", text: JSON.stringify(todo) }],
|
|
6831
6961
|
};
|
|
6832
6962
|
}
|
|
6833
6963
|
case "mark_all_todos_done": {
|
|
@@ -6861,7 +6991,7 @@ async function handleToolCall(params) {
|
|
|
6861
6991
|
content: [
|
|
6862
6992
|
{
|
|
6863
6993
|
type: "text",
|
|
6864
|
-
text: JSON.stringify(mergeRequestWithDeploymentSummary
|
|
6994
|
+
text: JSON.stringify(mergeRequestWithDeploymentSummary),
|
|
6865
6995
|
},
|
|
6866
6996
|
],
|
|
6867
6997
|
};
|
|
@@ -6871,14 +7001,14 @@ async function handleToolCall(params) {
|
|
|
6871
7001
|
const diffs = await getMergeRequestDiffs(args.project_id, args.merge_request_iid, args.source_branch, args.view);
|
|
6872
7002
|
const filteredDiffs = filterDiffsByPatterns(diffs, args.excluded_file_patterns);
|
|
6873
7003
|
return {
|
|
6874
|
-
content: [{ type: "text", text: JSON.stringify(filteredDiffs
|
|
7004
|
+
content: [{ type: "text", text: JSON.stringify(filteredDiffs) }],
|
|
6875
7005
|
};
|
|
6876
7006
|
}
|
|
6877
7007
|
case "list_merge_request_changed_files": {
|
|
6878
7008
|
const args = ListMergeRequestChangedFilesSchema.parse(params.arguments);
|
|
6879
7009
|
const files = await listMergeRequestChangedFiles(args.project_id, args.merge_request_iid, args.source_branch, args.excluded_file_patterns);
|
|
6880
7010
|
return {
|
|
6881
|
-
content: [{ type: "text", text: JSON.stringify(files
|
|
7011
|
+
content: [{ type: "text", text: JSON.stringify(files) }],
|
|
6882
7012
|
};
|
|
6883
7013
|
}
|
|
6884
7014
|
case "list_merge_request_pipelines": {
|
|
@@ -6886,35 +7016,35 @@ async function handleToolCall(params) {
|
|
|
6886
7016
|
const { project_id, merge_request_iid, ...options } = args;
|
|
6887
7017
|
const pipelines = await listMergeRequestPipelines(project_id, merge_request_iid, options);
|
|
6888
7018
|
return {
|
|
6889
|
-
content: [{ type: "text", text: JSON.stringify(pipelines
|
|
7019
|
+
content: [{ type: "text", text: JSON.stringify(pipelines) }],
|
|
6890
7020
|
};
|
|
6891
7021
|
}
|
|
6892
7022
|
case "list_merge_request_diffs": {
|
|
6893
7023
|
const args = ListMergeRequestDiffsSchema.parse(params.arguments);
|
|
6894
7024
|
const changes = await listMergeRequestDiffs(args.project_id, args.merge_request_iid, args.source_branch, args.page, args.per_page, args.unidiff);
|
|
6895
7025
|
return {
|
|
6896
|
-
content: [{ type: "text", text: JSON.stringify(changes
|
|
7026
|
+
content: [{ type: "text", text: JSON.stringify(changes) }],
|
|
6897
7027
|
};
|
|
6898
7028
|
}
|
|
6899
7029
|
case "get_merge_request_file_diff": {
|
|
6900
7030
|
const args = GetMergeRequestFileDiffSchema.parse(params.arguments);
|
|
6901
7031
|
const fileDiff = await getMergeRequestFileDiff(args.project_id, args.file_paths, args.merge_request_iid, args.source_branch, args.unidiff);
|
|
6902
7032
|
return {
|
|
6903
|
-
content: [{ type: "text", text: JSON.stringify(fileDiff
|
|
7033
|
+
content: [{ type: "text", text: JSON.stringify(fileDiff) }],
|
|
6904
7034
|
};
|
|
6905
7035
|
}
|
|
6906
7036
|
case "list_merge_request_versions": {
|
|
6907
7037
|
const args = ListMergeRequestVersionsSchema.parse(params.arguments);
|
|
6908
7038
|
const versions = await listMergeRequestVersions(args.project_id, args.merge_request_iid);
|
|
6909
7039
|
return {
|
|
6910
|
-
content: [{ type: "text", text: JSON.stringify(versions
|
|
7040
|
+
content: [{ type: "text", text: JSON.stringify(versions) }],
|
|
6911
7041
|
};
|
|
6912
7042
|
}
|
|
6913
7043
|
case "get_merge_request_version": {
|
|
6914
7044
|
const args = GetMergeRequestVersionSchema.parse(params.arguments);
|
|
6915
7045
|
const version = await getMergeRequestVersion(args.project_id, args.merge_request_iid, args.version_id, args.unidiff);
|
|
6916
7046
|
return {
|
|
6917
|
-
content: [{ type: "text", text: JSON.stringify(version
|
|
7047
|
+
content: [{ type: "text", text: JSON.stringify(version) }],
|
|
6918
7048
|
};
|
|
6919
7049
|
}
|
|
6920
7050
|
case "update_merge_request": {
|
|
@@ -6922,7 +7052,7 @@ async function handleToolCall(params) {
|
|
|
6922
7052
|
const { project_id, merge_request_iid, source_branch, ...options } = args;
|
|
6923
7053
|
const mergeRequest = await updateMergeRequest(project_id, options, merge_request_iid, source_branch);
|
|
6924
7054
|
return {
|
|
6925
|
-
content: [{ type: "text", text: JSON.stringify(mergeRequest
|
|
7055
|
+
content: [{ type: "text", text: JSON.stringify(mergeRequest) }],
|
|
6926
7056
|
};
|
|
6927
7057
|
}
|
|
6928
7058
|
case "merge_merge_request": {
|
|
@@ -6930,35 +7060,35 @@ async function handleToolCall(params) {
|
|
|
6930
7060
|
const { project_id, merge_request_iid, ...options } = args;
|
|
6931
7061
|
const mergeRequest = await mergeMergeRequest(project_id, options, merge_request_iid);
|
|
6932
7062
|
return {
|
|
6933
|
-
content: [{ type: "text", text: JSON.stringify(mergeRequest
|
|
7063
|
+
content: [{ type: "text", text: JSON.stringify(mergeRequest) }],
|
|
6934
7064
|
};
|
|
6935
7065
|
}
|
|
6936
7066
|
case "approve_merge_request": {
|
|
6937
7067
|
const args = ApproveMergeRequestSchema.parse(params.arguments);
|
|
6938
7068
|
const approvalState = await approveMergeRequest(args.project_id, args.merge_request_iid, args.sha, args.approval_password);
|
|
6939
7069
|
return {
|
|
6940
|
-
content: [{ type: "text", text: JSON.stringify(approvalState
|
|
7070
|
+
content: [{ type: "text", text: JSON.stringify(approvalState) }],
|
|
6941
7071
|
};
|
|
6942
7072
|
}
|
|
6943
7073
|
case "unapprove_merge_request": {
|
|
6944
7074
|
const args = UnapproveMergeRequestSchema.parse(params.arguments);
|
|
6945
7075
|
const approvalState = await unapproveMergeRequest(args.project_id, args.merge_request_iid);
|
|
6946
7076
|
return {
|
|
6947
|
-
content: [{ type: "text", text: JSON.stringify(approvalState
|
|
7077
|
+
content: [{ type: "text", text: JSON.stringify(approvalState) }],
|
|
6948
7078
|
};
|
|
6949
7079
|
}
|
|
6950
7080
|
case "get_merge_request_approval_state": {
|
|
6951
7081
|
const args = GetMergeRequestApprovalStateSchema.parse(params.arguments);
|
|
6952
7082
|
const approvalState = await getMergeRequestApprovalState(args.project_id, args.merge_request_iid);
|
|
6953
7083
|
return {
|
|
6954
|
-
content: [{ type: "text", text: JSON.stringify(approvalState
|
|
7084
|
+
content: [{ type: "text", text: JSON.stringify(approvalState) }],
|
|
6955
7085
|
};
|
|
6956
7086
|
}
|
|
6957
7087
|
case "get_merge_request_conflicts": {
|
|
6958
7088
|
const args = GetMergeRequestConflictsSchema.parse(params.arguments);
|
|
6959
7089
|
const conflicts = await getMergeRequestConflicts(args.project_id, args.merge_request_iid);
|
|
6960
7090
|
return {
|
|
6961
|
-
content: [{ type: "text", text: JSON.stringify(conflicts
|
|
7091
|
+
content: [{ type: "text", text: JSON.stringify(conflicts) }],
|
|
6962
7092
|
};
|
|
6963
7093
|
}
|
|
6964
7094
|
case "mr_discussions": {
|
|
@@ -6966,7 +7096,7 @@ async function handleToolCall(params) {
|
|
|
6966
7096
|
const { project_id, merge_request_iid, ...options } = args;
|
|
6967
7097
|
const discussions = await listMergeRequestDiscussions(project_id, merge_request_iid, options);
|
|
6968
7098
|
return {
|
|
6969
|
-
content: [{ type: "text", text: JSON.stringify(discussions
|
|
7099
|
+
content: [{ type: "text", text: JSON.stringify(discussions) }],
|
|
6970
7100
|
};
|
|
6971
7101
|
}
|
|
6972
7102
|
case "list_namespaces": {
|
|
@@ -6991,7 +7121,7 @@ async function handleToolCall(params) {
|
|
|
6991
7121
|
const data = await response.json();
|
|
6992
7122
|
const namespaces = z.array(GitLabNamespaceSchema).parse(data);
|
|
6993
7123
|
return {
|
|
6994
|
-
content: [{ type: "text", text: JSON.stringify(namespaces
|
|
7124
|
+
content: [{ type: "text", text: JSON.stringify(namespaces) }],
|
|
6995
7125
|
};
|
|
6996
7126
|
}
|
|
6997
7127
|
case "get_namespace": {
|
|
@@ -7004,7 +7134,7 @@ async function handleToolCall(params) {
|
|
|
7004
7134
|
const data = await response.json();
|
|
7005
7135
|
const namespace = GitLabNamespaceSchema.parse(data);
|
|
7006
7136
|
return {
|
|
7007
|
-
content: [{ type: "text", text: JSON.stringify(namespace
|
|
7137
|
+
content: [{ type: "text", text: JSON.stringify(namespace) }],
|
|
7008
7138
|
};
|
|
7009
7139
|
}
|
|
7010
7140
|
case "verify_namespace": {
|
|
@@ -7019,7 +7149,7 @@ async function handleToolCall(params) {
|
|
|
7019
7149
|
const data = await response.json();
|
|
7020
7150
|
const namespaceExists = GitLabNamespaceExistsResponseSchema.parse(data);
|
|
7021
7151
|
return {
|
|
7022
|
-
content: [{ type: "text", text: JSON.stringify(namespaceExists
|
|
7152
|
+
content: [{ type: "text", text: JSON.stringify(namespaceExists) }],
|
|
7023
7153
|
};
|
|
7024
7154
|
}
|
|
7025
7155
|
case "get_project": {
|
|
@@ -7040,14 +7170,29 @@ async function handleToolCall(params) {
|
|
|
7040
7170
|
const data = await response.json();
|
|
7041
7171
|
// Return raw data without parsing through our schema to avoid type mismatches in tests
|
|
7042
7172
|
return {
|
|
7043
|
-
content: [{ type: "text", text: JSON.stringify(data
|
|
7173
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
7044
7174
|
};
|
|
7045
7175
|
}
|
|
7046
7176
|
case "list_projects": {
|
|
7047
7177
|
const args = ListProjectsSchema.parse(params.arguments);
|
|
7048
7178
|
const projects = await listProjects(args);
|
|
7049
7179
|
return {
|
|
7050
|
-
content: [{ type: "text", text: JSON.stringify(projects
|
|
7180
|
+
content: [{ type: "text", text: JSON.stringify(projects) }],
|
|
7181
|
+
};
|
|
7182
|
+
}
|
|
7183
|
+
case "update_project": {
|
|
7184
|
+
const { project_id, ...updates } = UpdateProjectSchema.parse(params.arguments);
|
|
7185
|
+
const effectiveProjectId = getEffectiveProjectId(project_id);
|
|
7186
|
+
const body = Object.fromEntries(Object.entries(updates).filter(([, value]) => value !== undefined));
|
|
7187
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}`, {
|
|
7188
|
+
...getFetchConfig(),
|
|
7189
|
+
method: "PUT",
|
|
7190
|
+
body: JSON.stringify(body),
|
|
7191
|
+
});
|
|
7192
|
+
await handleGitLabError(response);
|
|
7193
|
+
const data = await response.json();
|
|
7194
|
+
return {
|
|
7195
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
7051
7196
|
};
|
|
7052
7197
|
}
|
|
7053
7198
|
case "list_project_members": {
|
|
@@ -7055,14 +7200,14 @@ async function handleToolCall(params) {
|
|
|
7055
7200
|
const { project_id, ...options } = args;
|
|
7056
7201
|
const members = await listProjectMembers(project_id, options);
|
|
7057
7202
|
return {
|
|
7058
|
-
content: [{ type: "text", text: JSON.stringify(members
|
|
7203
|
+
content: [{ type: "text", text: JSON.stringify(members) }],
|
|
7059
7204
|
};
|
|
7060
7205
|
}
|
|
7061
7206
|
case "get_users": {
|
|
7062
7207
|
const args = GetUsersSchema.parse(params.arguments);
|
|
7063
7208
|
const usersMap = await getUsers(args.usernames);
|
|
7064
7209
|
return {
|
|
7065
|
-
content: [{ type: "text", text: JSON.stringify(usersMap
|
|
7210
|
+
content: [{ type: "text", text: JSON.stringify(usersMap) }],
|
|
7066
7211
|
};
|
|
7067
7212
|
}
|
|
7068
7213
|
case "get_user": {
|
|
@@ -7075,7 +7220,7 @@ async function handleToolCall(params) {
|
|
|
7075
7220
|
const data = await response.json();
|
|
7076
7221
|
const user = GitLabUserFullSchema.parse(data);
|
|
7077
7222
|
return {
|
|
7078
|
-
content: [{ type: "text", text: JSON.stringify(user
|
|
7223
|
+
content: [{ type: "text", text: JSON.stringify(user) }],
|
|
7079
7224
|
};
|
|
7080
7225
|
}
|
|
7081
7226
|
case "whoami": {
|
|
@@ -7088,7 +7233,7 @@ async function handleToolCall(params) {
|
|
|
7088
7233
|
const data = await response.json();
|
|
7089
7234
|
const user = GitLabCurrentUserSchema.parse(data);
|
|
7090
7235
|
return {
|
|
7091
|
-
content: [{ type: "text", text: JSON.stringify(user
|
|
7236
|
+
content: [{ type: "text", text: JSON.stringify(user) }],
|
|
7092
7237
|
};
|
|
7093
7238
|
}
|
|
7094
7239
|
case "create_note": {
|
|
@@ -7096,7 +7241,7 @@ async function handleToolCall(params) {
|
|
|
7096
7241
|
const { project_id, noteable_type, noteable_iid, body } = args;
|
|
7097
7242
|
const note = await createNote(project_id, noteable_type, noteable_iid, body);
|
|
7098
7243
|
return {
|
|
7099
|
-
content: [{ type: "text", text: JSON.stringify(note
|
|
7244
|
+
content: [{ type: "text", text: JSON.stringify(note) }],
|
|
7100
7245
|
};
|
|
7101
7246
|
}
|
|
7102
7247
|
case "get_draft_note": {
|
|
@@ -7104,7 +7249,7 @@ async function handleToolCall(params) {
|
|
|
7104
7249
|
const { project_id, merge_request_iid, draft_note_id } = args;
|
|
7105
7250
|
const draftNote = await getDraftNote(project_id, merge_request_iid, draft_note_id);
|
|
7106
7251
|
return {
|
|
7107
|
-
content: [{ type: "text", text: JSON.stringify(draftNote
|
|
7252
|
+
content: [{ type: "text", text: JSON.stringify(draftNote) }],
|
|
7108
7253
|
};
|
|
7109
7254
|
}
|
|
7110
7255
|
case "list_draft_notes": {
|
|
@@ -7112,15 +7257,15 @@ async function handleToolCall(params) {
|
|
|
7112
7257
|
const { project_id, merge_request_iid } = args;
|
|
7113
7258
|
const draftNotes = await listDraftNotes(project_id, merge_request_iid);
|
|
7114
7259
|
return {
|
|
7115
|
-
content: [{ type: "text", text: JSON.stringify(draftNotes
|
|
7260
|
+
content: [{ type: "text", text: JSON.stringify(draftNotes) }],
|
|
7116
7261
|
};
|
|
7117
7262
|
}
|
|
7118
7263
|
case "create_draft_note": {
|
|
7119
7264
|
const args = CreateDraftNoteSchema.parse(params.arguments);
|
|
7120
|
-
const { project_id, merge_request_iid, body, in_reply_to_discussion_id, position, resolve_discussion } = args;
|
|
7265
|
+
const { project_id, merge_request_iid, body, in_reply_to_discussion_id, position, resolve_discussion, } = args;
|
|
7121
7266
|
const draftNote = await createDraftNote(project_id, merge_request_iid, body, in_reply_to_discussion_id, position, resolve_discussion);
|
|
7122
7267
|
return {
|
|
7123
|
-
content: [{ type: "text", text: JSON.stringify(draftNote
|
|
7268
|
+
content: [{ type: "text", text: JSON.stringify(draftNote) }],
|
|
7124
7269
|
};
|
|
7125
7270
|
}
|
|
7126
7271
|
case "update_draft_note": {
|
|
@@ -7128,7 +7273,7 @@ async function handleToolCall(params) {
|
|
|
7128
7273
|
const { project_id, merge_request_iid, draft_note_id, body, position, resolve_discussion } = args;
|
|
7129
7274
|
const draftNote = await updateDraftNote(project_id, merge_request_iid, draft_note_id, body, position, resolve_discussion);
|
|
7130
7275
|
return {
|
|
7131
|
-
content: [{ type: "text", text: JSON.stringify(draftNote
|
|
7276
|
+
content: [{ type: "text", text: JSON.stringify(draftNote) }],
|
|
7132
7277
|
};
|
|
7133
7278
|
}
|
|
7134
7279
|
case "delete_draft_note": {
|
|
@@ -7144,7 +7289,7 @@ async function handleToolCall(params) {
|
|
|
7144
7289
|
const { project_id, merge_request_iid, draft_note_id } = args;
|
|
7145
7290
|
const publishedNote = await publishDraftNote(project_id, merge_request_iid, draft_note_id);
|
|
7146
7291
|
return {
|
|
7147
|
-
content: [{ type: "text", text: JSON.stringify(publishedNote
|
|
7292
|
+
content: [{ type: "text", text: JSON.stringify(publishedNote) }],
|
|
7148
7293
|
};
|
|
7149
7294
|
}
|
|
7150
7295
|
case "bulk_publish_draft_notes": {
|
|
@@ -7152,7 +7297,7 @@ async function handleToolCall(params) {
|
|
|
7152
7297
|
const { project_id, merge_request_iid } = args;
|
|
7153
7298
|
const publishedNotes = await bulkPublishDraftNotes(project_id, merge_request_iid);
|
|
7154
7299
|
return {
|
|
7155
|
-
content: [{ type: "text", text: JSON.stringify(publishedNotes
|
|
7300
|
+
content: [{ type: "text", text: JSON.stringify(publishedNotes) }],
|
|
7156
7301
|
};
|
|
7157
7302
|
}
|
|
7158
7303
|
case "create_merge_request_thread": {
|
|
@@ -7160,7 +7305,7 @@ async function handleToolCall(params) {
|
|
|
7160
7305
|
const { project_id, merge_request_iid, body, position, created_at } = args;
|
|
7161
7306
|
const thread = await createMergeRequestThread(project_id, merge_request_iid, body, position, created_at);
|
|
7162
7307
|
return {
|
|
7163
|
-
content: [{ type: "text", text: JSON.stringify(thread
|
|
7308
|
+
content: [{ type: "text", text: JSON.stringify(thread) }],
|
|
7164
7309
|
};
|
|
7165
7310
|
}
|
|
7166
7311
|
case "resolve_merge_request_thread": {
|
|
@@ -7177,21 +7322,21 @@ async function handleToolCall(params) {
|
|
|
7177
7322
|
const cleanedOptions = cleanMutuallyExclusiveIdUsernameOptions(options);
|
|
7178
7323
|
const issues = await listIssues(project_id, cleanedOptions);
|
|
7179
7324
|
return {
|
|
7180
|
-
content: [{ type: "text", text: JSON.stringify(issues
|
|
7325
|
+
content: [{ type: "text", text: JSON.stringify(issues) }],
|
|
7181
7326
|
};
|
|
7182
7327
|
}
|
|
7183
7328
|
case "my_issues": {
|
|
7184
7329
|
const args = MyIssuesSchema.parse(params.arguments);
|
|
7185
7330
|
const issues = await myIssues(args);
|
|
7186
7331
|
return {
|
|
7187
|
-
content: [{ type: "text", text: JSON.stringify(issues
|
|
7332
|
+
content: [{ type: "text", text: JSON.stringify(issues) }],
|
|
7188
7333
|
};
|
|
7189
7334
|
}
|
|
7190
7335
|
case "get_issue": {
|
|
7191
7336
|
const args = GetIssueSchema.parse(params.arguments);
|
|
7192
7337
|
const issue = await getIssue(args.project_id, args.issue_iid);
|
|
7193
7338
|
return {
|
|
7194
|
-
content: [{ type: "text", text: JSON.stringify(issue
|
|
7339
|
+
content: [{ type: "text", text: JSON.stringify(issue) }],
|
|
7195
7340
|
};
|
|
7196
7341
|
}
|
|
7197
7342
|
case "update_issue": {
|
|
@@ -7199,7 +7344,7 @@ async function handleToolCall(params) {
|
|
|
7199
7344
|
const { project_id, issue_iid, ...options } = args;
|
|
7200
7345
|
const issue = await updateIssue(project_id, issue_iid, options);
|
|
7201
7346
|
return {
|
|
7202
|
-
content: [{ type: "text", text: JSON.stringify(issue
|
|
7347
|
+
content: [{ type: "text", text: JSON.stringify(issue) }],
|
|
7203
7348
|
};
|
|
7204
7349
|
}
|
|
7205
7350
|
case "update_issue_description_patch": {
|
|
@@ -7293,7 +7438,7 @@ async function handleToolCall(params) {
|
|
|
7293
7438
|
const args = ListIssueLinksSchema.parse(params.arguments);
|
|
7294
7439
|
const links = await listIssueLinks(args.project_id, args.issue_iid);
|
|
7295
7440
|
return {
|
|
7296
|
-
content: [{ type: "text", text: JSON.stringify(links
|
|
7441
|
+
content: [{ type: "text", text: JSON.stringify(links) }],
|
|
7297
7442
|
};
|
|
7298
7443
|
}
|
|
7299
7444
|
case "list_issue_discussions": {
|
|
@@ -7301,21 +7446,21 @@ async function handleToolCall(params) {
|
|
|
7301
7446
|
const { project_id, issue_iid, ...options } = args;
|
|
7302
7447
|
const discussions = await listIssueDiscussions(project_id, issue_iid, options);
|
|
7303
7448
|
return {
|
|
7304
|
-
content: [{ type: "text", text: JSON.stringify(discussions
|
|
7449
|
+
content: [{ type: "text", text: JSON.stringify(discussions) }],
|
|
7305
7450
|
};
|
|
7306
7451
|
}
|
|
7307
7452
|
case "get_issue_link": {
|
|
7308
7453
|
const args = GetIssueLinkSchema.parse(params.arguments);
|
|
7309
7454
|
const link = await getIssueLink(args.project_id, args.issue_iid, args.issue_link_id);
|
|
7310
7455
|
return {
|
|
7311
|
-
content: [{ type: "text", text: JSON.stringify(link
|
|
7456
|
+
content: [{ type: "text", text: JSON.stringify(link) }],
|
|
7312
7457
|
};
|
|
7313
7458
|
}
|
|
7314
7459
|
case "create_issue_link": {
|
|
7315
7460
|
const args = CreateIssueLinkSchema.parse(params.arguments);
|
|
7316
7461
|
const link = await createIssueLink(args.project_id, args.issue_iid, args.target_project_id, args.target_issue_iid, args.link_type);
|
|
7317
7462
|
return {
|
|
7318
|
-
content: [{ type: "text", text: JSON.stringify(link
|
|
7463
|
+
content: [{ type: "text", text: JSON.stringify(link) }],
|
|
7319
7464
|
};
|
|
7320
7465
|
}
|
|
7321
7466
|
case "delete_issue_link": {
|
|
@@ -7337,7 +7482,7 @@ async function handleToolCall(params) {
|
|
|
7337
7482
|
const args = GetWorkItemSchema.parse(params.arguments);
|
|
7338
7483
|
const result = await getWorkItem(args.project_id, args.iid);
|
|
7339
7484
|
return {
|
|
7340
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
7485
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7341
7486
|
};
|
|
7342
7487
|
}
|
|
7343
7488
|
case "list_work_items": {
|
|
@@ -7345,7 +7490,7 @@ async function handleToolCall(params) {
|
|
|
7345
7490
|
const { project_id, ...options } = args;
|
|
7346
7491
|
const result = await listWorkItems(project_id, options);
|
|
7347
7492
|
return {
|
|
7348
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
7493
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7349
7494
|
};
|
|
7350
7495
|
}
|
|
7351
7496
|
case "create_work_item": {
|
|
@@ -7353,7 +7498,7 @@ async function handleToolCall(params) {
|
|
|
7353
7498
|
const { project_id, ...options } = args;
|
|
7354
7499
|
const result = await createWorkItem(project_id, options);
|
|
7355
7500
|
return {
|
|
7356
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
7501
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7357
7502
|
};
|
|
7358
7503
|
}
|
|
7359
7504
|
case "update_work_item": {
|
|
@@ -7361,117 +7506,117 @@ async function handleToolCall(params) {
|
|
|
7361
7506
|
const { project_id, iid, ...options } = args;
|
|
7362
7507
|
const result = await updateWorkItem(project_id, iid, options);
|
|
7363
7508
|
return {
|
|
7364
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
7509
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7365
7510
|
};
|
|
7366
7511
|
}
|
|
7367
7512
|
case "convert_work_item_type": {
|
|
7368
7513
|
const args = ConvertWorkItemTypeSchema.parse(params.arguments);
|
|
7369
7514
|
const result = await convertIssueType(args.project_id, args.iid, args.new_type);
|
|
7370
7515
|
return {
|
|
7371
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
7516
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7372
7517
|
};
|
|
7373
7518
|
}
|
|
7374
7519
|
case "list_work_item_statuses": {
|
|
7375
7520
|
const args = ListWorkItemStatusesSchema.parse(params.arguments);
|
|
7376
7521
|
const result = await listIssueStatuses(args.project_id, args.work_item_type);
|
|
7377
7522
|
return {
|
|
7378
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
7523
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7379
7524
|
};
|
|
7380
7525
|
}
|
|
7381
7526
|
case "list_custom_field_definitions": {
|
|
7382
7527
|
const args = ListCustomFieldDefinitionsSchema.parse(params.arguments);
|
|
7383
7528
|
const result = await listCustomFieldDefinitions(args.project_id, args.work_item_type);
|
|
7384
7529
|
return {
|
|
7385
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
7530
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7386
7531
|
};
|
|
7387
7532
|
}
|
|
7388
7533
|
case "move_work_item": {
|
|
7389
7534
|
const args = MoveWorkItemSchema.parse(params.arguments);
|
|
7390
7535
|
const result = await moveWorkItem(args.project_id, args.iid, args.target_project_id);
|
|
7391
7536
|
return {
|
|
7392
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
7537
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7393
7538
|
};
|
|
7394
7539
|
}
|
|
7395
7540
|
case "list_work_item_notes": {
|
|
7396
7541
|
const args = ListWorkItemNotesSchema.parse(params.arguments);
|
|
7397
7542
|
const result = await listWorkItemNotes(args.project_id, args.iid, args);
|
|
7398
7543
|
return {
|
|
7399
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
7544
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7400
7545
|
};
|
|
7401
7546
|
}
|
|
7402
7547
|
case "create_work_item_note": {
|
|
7403
7548
|
const args = CreateWorkItemNoteSchema.parse(params.arguments);
|
|
7404
7549
|
const result = await createWorkItemNote(args.project_id, args.iid, args.body, args);
|
|
7405
7550
|
return {
|
|
7406
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
7551
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7407
7552
|
};
|
|
7408
7553
|
}
|
|
7409
7554
|
case "list_work_item_emoji_reactions": {
|
|
7410
7555
|
const args = ListWorkItemEmojiReactionsSchema.parse(params.arguments);
|
|
7411
7556
|
const { workItemGID } = await resolveWorkItemGID(args.project_id, args.iid);
|
|
7412
7557
|
const result = await listGraphQLAwardEmoji(workItemGID);
|
|
7413
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
7558
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
7414
7559
|
}
|
|
7415
7560
|
case "list_work_item_note_emoji_reactions": {
|
|
7416
7561
|
const args = ListWorkItemNoteEmojiReactionsSchema.parse(params.arguments);
|
|
7417
7562
|
const result = await listGraphQLAwardEmoji(args.note_id);
|
|
7418
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
7563
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
7419
7564
|
}
|
|
7420
7565
|
case "create_work_item_emoji_reaction": {
|
|
7421
7566
|
const args = CreateWorkItemEmojiReactionSchema.parse(params.arguments);
|
|
7422
7567
|
const { workItemGID } = await resolveWorkItemGID(args.project_id, args.iid);
|
|
7423
7568
|
const result = await addGraphQLAwardEmoji(workItemGID, args.name);
|
|
7424
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
7569
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
7425
7570
|
}
|
|
7426
7571
|
case "delete_work_item_emoji_reaction": {
|
|
7427
7572
|
const args = DeleteWorkItemEmojiReactionSchema.parse(params.arguments);
|
|
7428
7573
|
const { workItemGID } = await resolveWorkItemGID(args.project_id, args.iid);
|
|
7429
7574
|
const result = await removeGraphQLAwardEmoji(workItemGID, args.name);
|
|
7430
|
-
return { content: [{ type: "text", text: JSON.stringify(result ?? { status: "success", message: "Work item emoji reaction removed" }
|
|
7575
|
+
return { content: [{ type: "text", text: JSON.stringify(result ?? { status: "success", message: "Work item emoji reaction removed" }) }] };
|
|
7431
7576
|
}
|
|
7432
7577
|
case "create_work_item_note_emoji_reaction": {
|
|
7433
7578
|
const args = CreateWorkItemNoteEmojiReactionSchema.parse(params.arguments);
|
|
7434
7579
|
const result = await addGraphQLAwardEmoji(args.note_id, args.name);
|
|
7435
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
7580
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
7436
7581
|
}
|
|
7437
7582
|
case "delete_work_item_note_emoji_reaction": {
|
|
7438
7583
|
const args = DeleteWorkItemNoteEmojiReactionSchema.parse(params.arguments);
|
|
7439
7584
|
const result = await removeGraphQLAwardEmoji(args.note_id, args.name);
|
|
7440
|
-
return { content: [{ type: "text", text: JSON.stringify(result ?? { status: "success", message: "Work item note emoji reaction removed" }
|
|
7585
|
+
return { content: [{ type: "text", text: JSON.stringify(result ?? { status: "success", message: "Work item note emoji reaction removed" }) }] };
|
|
7441
7586
|
}
|
|
7442
7587
|
case "get_timeline_events": {
|
|
7443
7588
|
const args = GetTimelineEventsSchema.parse(params.arguments);
|
|
7444
7589
|
const result = await getTimelineEvents(args.project_id, args.incident_iid);
|
|
7445
7590
|
return {
|
|
7446
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
7591
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7447
7592
|
};
|
|
7448
7593
|
}
|
|
7449
7594
|
case "create_timeline_event": {
|
|
7450
7595
|
const args = CreateTimelineEventSchema.parse(params.arguments);
|
|
7451
7596
|
const result = await createTimelineEvent(args.project_id, args.incident_iid, args.note, args.occurred_at, args.tag_names);
|
|
7452
7597
|
return {
|
|
7453
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
7598
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7454
7599
|
};
|
|
7455
7600
|
}
|
|
7456
7601
|
case "list_labels": {
|
|
7457
7602
|
const args = ListLabelsSchema.parse(params.arguments);
|
|
7458
7603
|
const labels = await listLabels(args.project_id, args);
|
|
7459
7604
|
return {
|
|
7460
|
-
content: [{ type: "text", text: JSON.stringify(labels
|
|
7605
|
+
content: [{ type: "text", text: JSON.stringify(labels) }],
|
|
7461
7606
|
};
|
|
7462
7607
|
}
|
|
7463
7608
|
case "get_label": {
|
|
7464
7609
|
const args = GetLabelSchema.parse(params.arguments);
|
|
7465
7610
|
const label = await getLabel(args.project_id, args.label_id, args.include_ancestor_groups);
|
|
7466
7611
|
return {
|
|
7467
|
-
content: [{ type: "text", text: JSON.stringify(label
|
|
7612
|
+
content: [{ type: "text", text: JSON.stringify(label) }],
|
|
7468
7613
|
};
|
|
7469
7614
|
}
|
|
7470
7615
|
case "create_label": {
|
|
7471
7616
|
const args = CreateLabelSchema.parse(params.arguments);
|
|
7472
7617
|
const label = await createLabel(args.project_id, args);
|
|
7473
7618
|
return {
|
|
7474
|
-
content: [{ type: "text", text: JSON.stringify(label
|
|
7619
|
+
content: [{ type: "text", text: JSON.stringify(label) }],
|
|
7475
7620
|
};
|
|
7476
7621
|
}
|
|
7477
7622
|
case "update_label": {
|
|
@@ -7479,7 +7624,7 @@ async function handleToolCall(params) {
|
|
|
7479
7624
|
const { project_id, label_id, ...options } = args;
|
|
7480
7625
|
const label = await updateLabel(project_id, label_id, options);
|
|
7481
7626
|
return {
|
|
7482
|
-
content: [{ type: "text", text: JSON.stringify(label
|
|
7627
|
+
content: [{ type: "text", text: JSON.stringify(label) }],
|
|
7483
7628
|
};
|
|
7484
7629
|
}
|
|
7485
7630
|
case "delete_label": {
|
|
@@ -7498,7 +7643,7 @@ async function handleToolCall(params) {
|
|
|
7498
7643
|
const args = ListGroupProjectsSchema.parse(params.arguments);
|
|
7499
7644
|
const projects = await listGroupProjects(args);
|
|
7500
7645
|
return {
|
|
7501
|
-
content: [{ type: "text", text: JSON.stringify(projects
|
|
7646
|
+
content: [{ type: "text", text: JSON.stringify(projects) }],
|
|
7502
7647
|
};
|
|
7503
7648
|
}
|
|
7504
7649
|
case "list_wiki_pages": {
|
|
@@ -7509,28 +7654,28 @@ async function handleToolCall(params) {
|
|
|
7509
7654
|
with_content,
|
|
7510
7655
|
});
|
|
7511
7656
|
return {
|
|
7512
|
-
content: [{ type: "text", text: JSON.stringify(wikiPages
|
|
7657
|
+
content: [{ type: "text", text: JSON.stringify(wikiPages) }],
|
|
7513
7658
|
};
|
|
7514
7659
|
}
|
|
7515
7660
|
case "get_wiki_page": {
|
|
7516
7661
|
const { project_id, slug } = GetWikiPageSchema.parse(params.arguments);
|
|
7517
7662
|
const wikiPage = await getWikiPage(project_id, slug);
|
|
7518
7663
|
return {
|
|
7519
|
-
content: [{ type: "text", text: JSON.stringify(wikiPage
|
|
7664
|
+
content: [{ type: "text", text: JSON.stringify(wikiPage) }],
|
|
7520
7665
|
};
|
|
7521
7666
|
}
|
|
7522
7667
|
case "create_wiki_page": {
|
|
7523
7668
|
const { project_id, title, content, format } = CreateWikiPageSchema.parse(params.arguments);
|
|
7524
7669
|
const wikiPage = await createWikiPage(project_id, title, content, format);
|
|
7525
7670
|
return {
|
|
7526
|
-
content: [{ type: "text", text: JSON.stringify(wikiPage
|
|
7671
|
+
content: [{ type: "text", text: JSON.stringify(wikiPage) }],
|
|
7527
7672
|
};
|
|
7528
7673
|
}
|
|
7529
7674
|
case "update_wiki_page": {
|
|
7530
7675
|
const { project_id, slug, title, content, format } = UpdateWikiPageSchema.parse(params.arguments);
|
|
7531
7676
|
const wikiPage = await updateWikiPage(project_id, slug, title, content, format);
|
|
7532
7677
|
return {
|
|
7533
|
-
content: [{ type: "text", text: JSON.stringify(wikiPage
|
|
7678
|
+
content: [{ type: "text", text: JSON.stringify(wikiPage) }],
|
|
7534
7679
|
};
|
|
7535
7680
|
}
|
|
7536
7681
|
case "delete_wiki_page": {
|
|
@@ -7556,28 +7701,28 @@ async function handleToolCall(params) {
|
|
|
7556
7701
|
with_content,
|
|
7557
7702
|
});
|
|
7558
7703
|
return {
|
|
7559
|
-
content: [{ type: "text", text: JSON.stringify(wikiPages
|
|
7704
|
+
content: [{ type: "text", text: JSON.stringify(wikiPages) }],
|
|
7560
7705
|
};
|
|
7561
7706
|
}
|
|
7562
7707
|
case "get_group_wiki_page": {
|
|
7563
7708
|
const { group_id, slug } = GetGroupWikiPageSchema.parse(params.arguments);
|
|
7564
7709
|
const wikiPage = await getGroupWikiPage(group_id, slug);
|
|
7565
7710
|
return {
|
|
7566
|
-
content: [{ type: "text", text: JSON.stringify(wikiPage
|
|
7711
|
+
content: [{ type: "text", text: JSON.stringify(wikiPage) }],
|
|
7567
7712
|
};
|
|
7568
7713
|
}
|
|
7569
7714
|
case "create_group_wiki_page": {
|
|
7570
7715
|
const { group_id, title, content, format } = CreateGroupWikiPageSchema.parse(params.arguments);
|
|
7571
7716
|
const wikiPage = await createGroupWikiPage(group_id, title, content, format);
|
|
7572
7717
|
return {
|
|
7573
|
-
content: [{ type: "text", text: JSON.stringify(wikiPage
|
|
7718
|
+
content: [{ type: "text", text: JSON.stringify(wikiPage) }],
|
|
7574
7719
|
};
|
|
7575
7720
|
}
|
|
7576
7721
|
case "update_group_wiki_page": {
|
|
7577
7722
|
const { group_id, slug, title, content, format } = UpdateGroupWikiPageSchema.parse(params.arguments);
|
|
7578
7723
|
const wikiPage = await updateGroupWikiPage(group_id, slug, title, content, format);
|
|
7579
7724
|
return {
|
|
7580
|
-
content: [{ type: "text", text: JSON.stringify(wikiPage
|
|
7725
|
+
content: [{ type: "text", text: JSON.stringify(wikiPage) }],
|
|
7581
7726
|
};
|
|
7582
7727
|
}
|
|
7583
7728
|
case "delete_group_wiki_page": {
|
|
@@ -7608,7 +7753,7 @@ async function handleToolCall(params) {
|
|
|
7608
7753
|
}
|
|
7609
7754
|
: items;
|
|
7610
7755
|
return {
|
|
7611
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
7756
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7612
7757
|
};
|
|
7613
7758
|
}
|
|
7614
7759
|
case "list_pipelines": {
|
|
@@ -7616,7 +7761,7 @@ async function handleToolCall(params) {
|
|
|
7616
7761
|
const { project_id, ...options } = args;
|
|
7617
7762
|
const pipelines = await listPipelines(project_id, options);
|
|
7618
7763
|
return {
|
|
7619
|
-
content: [{ type: "text", text: JSON.stringify(pipelines
|
|
7764
|
+
content: [{ type: "text", text: JSON.stringify(pipelines) }],
|
|
7620
7765
|
};
|
|
7621
7766
|
}
|
|
7622
7767
|
case "get_pipeline": {
|
|
@@ -7626,7 +7771,7 @@ async function handleToolCall(params) {
|
|
|
7626
7771
|
content: [
|
|
7627
7772
|
{
|
|
7628
7773
|
type: "text",
|
|
7629
|
-
text: JSON.stringify(pipeline
|
|
7774
|
+
text: JSON.stringify(pipeline),
|
|
7630
7775
|
},
|
|
7631
7776
|
],
|
|
7632
7777
|
};
|
|
@@ -7636,14 +7781,14 @@ async function handleToolCall(params) {
|
|
|
7636
7781
|
const { project_id, ...options } = args;
|
|
7637
7782
|
const deployments = await listDeployments(project_id, options);
|
|
7638
7783
|
return {
|
|
7639
|
-
content: [{ type: "text", text: JSON.stringify(deployments
|
|
7784
|
+
content: [{ type: "text", text: JSON.stringify(deployments) }],
|
|
7640
7785
|
};
|
|
7641
7786
|
}
|
|
7642
7787
|
case "get_deployment": {
|
|
7643
7788
|
const { project_id, deployment_id } = GetDeploymentSchema.parse(params.arguments);
|
|
7644
7789
|
const deployment = await getDeployment(project_id, deployment_id);
|
|
7645
7790
|
return {
|
|
7646
|
-
content: [{ type: "text", text: JSON.stringify(deployment
|
|
7791
|
+
content: [{ type: "text", text: JSON.stringify(deployment) }],
|
|
7647
7792
|
};
|
|
7648
7793
|
}
|
|
7649
7794
|
case "list_environments": {
|
|
@@ -7651,14 +7796,14 @@ async function handleToolCall(params) {
|
|
|
7651
7796
|
const { project_id, ...options } = args;
|
|
7652
7797
|
const environments = await listEnvironments(project_id, options);
|
|
7653
7798
|
return {
|
|
7654
|
-
content: [{ type: "text", text: JSON.stringify(environments
|
|
7799
|
+
content: [{ type: "text", text: JSON.stringify(environments) }],
|
|
7655
7800
|
};
|
|
7656
7801
|
}
|
|
7657
7802
|
case "get_environment": {
|
|
7658
7803
|
const { project_id, environment_id } = GetEnvironmentSchema.parse(params.arguments);
|
|
7659
7804
|
const environment = await getEnvironment(project_id, environment_id);
|
|
7660
7805
|
return {
|
|
7661
|
-
content: [{ type: "text", text: JSON.stringify(environment
|
|
7806
|
+
content: [{ type: "text", text: JSON.stringify(environment) }],
|
|
7662
7807
|
};
|
|
7663
7808
|
}
|
|
7664
7809
|
case "list_pipeline_jobs": {
|
|
@@ -7668,7 +7813,7 @@ async function handleToolCall(params) {
|
|
|
7668
7813
|
content: [
|
|
7669
7814
|
{
|
|
7670
7815
|
type: "text",
|
|
7671
|
-
text: JSON.stringify(jobs
|
|
7816
|
+
text: JSON.stringify(jobs),
|
|
7672
7817
|
},
|
|
7673
7818
|
],
|
|
7674
7819
|
};
|
|
@@ -7680,7 +7825,7 @@ async function handleToolCall(params) {
|
|
|
7680
7825
|
content: [
|
|
7681
7826
|
{
|
|
7682
7827
|
type: "text",
|
|
7683
|
-
text: JSON.stringify(triggerJobs
|
|
7828
|
+
text: JSON.stringify(triggerJobs),
|
|
7684
7829
|
},
|
|
7685
7830
|
],
|
|
7686
7831
|
};
|
|
@@ -7692,7 +7837,7 @@ async function handleToolCall(params) {
|
|
|
7692
7837
|
content: [
|
|
7693
7838
|
{
|
|
7694
7839
|
type: "text",
|
|
7695
|
-
text: JSON.stringify(jobDetails
|
|
7840
|
+
text: JSON.stringify(jobDetails),
|
|
7696
7841
|
},
|
|
7697
7842
|
],
|
|
7698
7843
|
};
|
|
@@ -7714,7 +7859,7 @@ async function handleToolCall(params) {
|
|
|
7714
7859
|
const { project_id, ...options } = args;
|
|
7715
7860
|
const result = await validateCiLint(project_id, options);
|
|
7716
7861
|
return {
|
|
7717
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
7862
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7718
7863
|
};
|
|
7719
7864
|
}
|
|
7720
7865
|
case "validate_project_ci_lint": {
|
|
@@ -7722,8 +7867,130 @@ async function handleToolCall(params) {
|
|
|
7722
7867
|
const { project_id, ...options } = args;
|
|
7723
7868
|
const result = await validateProjectCiLint(project_id, options);
|
|
7724
7869
|
return {
|
|
7725
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
7726
|
-
};
|
|
7870
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7871
|
+
};
|
|
7872
|
+
}
|
|
7873
|
+
case "list_ci_catalog_resources": {
|
|
7874
|
+
const args = ListCiCatalogResourcesSchema.parse(params.arguments);
|
|
7875
|
+
const result = await executeGitLabGraphQL(`query ListCiCatalogResources(
|
|
7876
|
+
$search: String,
|
|
7877
|
+
$first: Int,
|
|
7878
|
+
$after: String,
|
|
7879
|
+
$groupIds: [GroupID!],
|
|
7880
|
+
$scope: CiCatalogResourceScope,
|
|
7881
|
+
$sort: CiCatalogResourceSort,
|
|
7882
|
+
$topics: [String!],
|
|
7883
|
+
$verificationLevel: CiCatalogResourceVerificationLevel
|
|
7884
|
+
) {
|
|
7885
|
+
ciCatalogResources(
|
|
7886
|
+
search: $search,
|
|
7887
|
+
first: $first,
|
|
7888
|
+
after: $after,
|
|
7889
|
+
groupIds: $groupIds,
|
|
7890
|
+
scope: $scope,
|
|
7891
|
+
sort: $sort,
|
|
7892
|
+
topics: $topics,
|
|
7893
|
+
verificationLevel: $verificationLevel
|
|
7894
|
+
) {
|
|
7895
|
+
nodes {
|
|
7896
|
+
id
|
|
7897
|
+
name
|
|
7898
|
+
description
|
|
7899
|
+
fullPath
|
|
7900
|
+
icon
|
|
7901
|
+
starCount
|
|
7902
|
+
topics
|
|
7903
|
+
verificationLevel
|
|
7904
|
+
visibilityLevel
|
|
7905
|
+
webPath
|
|
7906
|
+
latestReleasedAt
|
|
7907
|
+
last30DayUsageCount
|
|
7908
|
+
}
|
|
7909
|
+
pageInfo { hasNextPage endCursor }
|
|
7910
|
+
}
|
|
7911
|
+
}`, {
|
|
7912
|
+
search: args.search,
|
|
7913
|
+
first: args.first ?? 20,
|
|
7914
|
+
after: args.after,
|
|
7915
|
+
groupIds: args.group_ids,
|
|
7916
|
+
scope: args.scope,
|
|
7917
|
+
sort: args.sort,
|
|
7918
|
+
topics: args.topics,
|
|
7919
|
+
verificationLevel: args.verification_level,
|
|
7920
|
+
});
|
|
7921
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
7922
|
+
}
|
|
7923
|
+
case "get_ci_catalog_resource": {
|
|
7924
|
+
const args = GetCiCatalogResourceSchema.parse(params.arguments);
|
|
7925
|
+
const result = await executeGitLabGraphQL(`query GetCiCatalogResource(
|
|
7926
|
+
$id: CiCatalogResourceID,
|
|
7927
|
+
$fullPath: ID,
|
|
7928
|
+
$versionLimit: Int!,
|
|
7929
|
+
$componentLimit: Int!,
|
|
7930
|
+
$includeReadme: Boolean!
|
|
7931
|
+
) {
|
|
7932
|
+
ciCatalogResource(id: $id, fullPath: $fullPath) {
|
|
7933
|
+
id
|
|
7934
|
+
name
|
|
7935
|
+
description
|
|
7936
|
+
fullPath
|
|
7937
|
+
icon
|
|
7938
|
+
starCount
|
|
7939
|
+
topics
|
|
7940
|
+
verificationLevel
|
|
7941
|
+
visibilityLevel
|
|
7942
|
+
webPath
|
|
7943
|
+
latestReleasedAt
|
|
7944
|
+
last30DayUsageCount
|
|
7945
|
+
versions(first: $versionLimit) {
|
|
7946
|
+
nodes {
|
|
7947
|
+
id
|
|
7948
|
+
name
|
|
7949
|
+
path
|
|
7950
|
+
createdAt
|
|
7951
|
+
releasedAt
|
|
7952
|
+
readme @include(if: $includeReadme)
|
|
7953
|
+
semver { major minor patch }
|
|
7954
|
+
components(first: $componentLimit) {
|
|
7955
|
+
nodes {
|
|
7956
|
+
id
|
|
7957
|
+
name
|
|
7958
|
+
description
|
|
7959
|
+
includePath
|
|
7960
|
+
last30DayUsageCount
|
|
7961
|
+
inputs {
|
|
7962
|
+
name
|
|
7963
|
+
description
|
|
7964
|
+
type
|
|
7965
|
+
required
|
|
7966
|
+
default
|
|
7967
|
+
options
|
|
7968
|
+
regex
|
|
7969
|
+
}
|
|
7970
|
+
}
|
|
7971
|
+
pageInfo { hasNextPage endCursor }
|
|
7972
|
+
}
|
|
7973
|
+
}
|
|
7974
|
+
pageInfo { hasNextPage endCursor }
|
|
7975
|
+
}
|
|
7976
|
+
}
|
|
7977
|
+
}`, {
|
|
7978
|
+
id: args.id,
|
|
7979
|
+
fullPath: args.full_path,
|
|
7980
|
+
versionLimit: args.version_limit ?? 5,
|
|
7981
|
+
componentLimit: args.component_limit ?? 20,
|
|
7982
|
+
includeReadme: args.include_readme ?? false,
|
|
7983
|
+
});
|
|
7984
|
+
if (args.component_name) {
|
|
7985
|
+
const resource = result?.data?.ciCatalogResource;
|
|
7986
|
+
for (const version of resource?.versions?.nodes ?? []) {
|
|
7987
|
+
const components = version?.components?.nodes;
|
|
7988
|
+
if (Array.isArray(components)) {
|
|
7989
|
+
version.components.nodes = components.filter(component => component?.name === args.component_name);
|
|
7990
|
+
}
|
|
7991
|
+
}
|
|
7992
|
+
}
|
|
7993
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
7727
7994
|
}
|
|
7728
7995
|
case "create_pipeline": {
|
|
7729
7996
|
const { project_id, ref, variables, inputs } = CreatePipelineSchema.parse(params.arguments);
|
|
@@ -7804,7 +8071,7 @@ async function handleToolCall(params) {
|
|
|
7804
8071
|
content: [
|
|
7805
8072
|
{
|
|
7806
8073
|
type: "text",
|
|
7807
|
-
text: JSON.stringify(artifacts
|
|
8074
|
+
text: JSON.stringify(artifacts),
|
|
7808
8075
|
},
|
|
7809
8076
|
],
|
|
7810
8077
|
};
|
|
@@ -7817,7 +8084,7 @@ async function handleToolCall(params) {
|
|
|
7817
8084
|
}
|
|
7818
8085
|
const downloadUrl = buildDownloadUrl("job-artifacts", { project_id, job_id });
|
|
7819
8086
|
return {
|
|
7820
|
-
content: [{ type: "text", text: JSON.stringify({ download_url: downloadUrl, filename: `artifacts_job_${job_id}.zip` }
|
|
8087
|
+
content: [{ type: "text", text: JSON.stringify({ download_url: downloadUrl, filename: `artifacts_job_${job_id}.zip` }) }],
|
|
7821
8088
|
};
|
|
7822
8089
|
}
|
|
7823
8090
|
const filePath = await downloadJobArtifacts(project_id, job_id, local_path);
|
|
@@ -7825,7 +8092,7 @@ async function handleToolCall(params) {
|
|
|
7825
8092
|
content: [
|
|
7826
8093
|
{
|
|
7827
8094
|
type: "text",
|
|
7828
|
-
text: JSON.stringify({ success: true, file_path: filePath }
|
|
8095
|
+
text: JSON.stringify({ success: true, file_path: filePath }),
|
|
7829
8096
|
},
|
|
7830
8097
|
],
|
|
7831
8098
|
};
|
|
@@ -7847,7 +8114,7 @@ async function handleToolCall(params) {
|
|
|
7847
8114
|
const cleanedOptions = cleanMutuallyExclusiveIdUsernameOptions(options, LIST_MERGE_REQUESTS_ID_USERNAME_PAIRS);
|
|
7848
8115
|
const mergeRequests = await listMergeRequests(project_id, cleanedOptions);
|
|
7849
8116
|
return {
|
|
7850
|
-
content: [{ type: "text", text: JSON.stringify(mergeRequests
|
|
8117
|
+
content: [{ type: "text", text: JSON.stringify(mergeRequests) }],
|
|
7851
8118
|
};
|
|
7852
8119
|
}
|
|
7853
8120
|
case "list_milestones": {
|
|
@@ -7857,7 +8124,7 @@ async function handleToolCall(params) {
|
|
|
7857
8124
|
content: [
|
|
7858
8125
|
{
|
|
7859
8126
|
type: "text",
|
|
7860
|
-
text: JSON.stringify(milestones
|
|
8127
|
+
text: JSON.stringify(milestones),
|
|
7861
8128
|
},
|
|
7862
8129
|
],
|
|
7863
8130
|
};
|
|
@@ -7869,7 +8136,7 @@ async function handleToolCall(params) {
|
|
|
7869
8136
|
content: [
|
|
7870
8137
|
{
|
|
7871
8138
|
type: "text",
|
|
7872
|
-
text: JSON.stringify(milestone
|
|
8139
|
+
text: JSON.stringify(milestone),
|
|
7873
8140
|
},
|
|
7874
8141
|
],
|
|
7875
8142
|
};
|
|
@@ -7881,7 +8148,7 @@ async function handleToolCall(params) {
|
|
|
7881
8148
|
content: [
|
|
7882
8149
|
{
|
|
7883
8150
|
type: "text",
|
|
7884
|
-
text: JSON.stringify(milestone
|
|
8151
|
+
text: JSON.stringify(milestone),
|
|
7885
8152
|
},
|
|
7886
8153
|
],
|
|
7887
8154
|
};
|
|
@@ -7893,7 +8160,7 @@ async function handleToolCall(params) {
|
|
|
7893
8160
|
content: [
|
|
7894
8161
|
{
|
|
7895
8162
|
type: "text",
|
|
7896
|
-
text: JSON.stringify(milestone
|
|
8163
|
+
text: JSON.stringify(milestone),
|
|
7897
8164
|
},
|
|
7898
8165
|
],
|
|
7899
8166
|
};
|
|
@@ -7920,7 +8187,7 @@ async function handleToolCall(params) {
|
|
|
7920
8187
|
content: [
|
|
7921
8188
|
{
|
|
7922
8189
|
type: "text",
|
|
7923
|
-
text: JSON.stringify(issues
|
|
8190
|
+
text: JSON.stringify(issues),
|
|
7924
8191
|
},
|
|
7925
8192
|
],
|
|
7926
8193
|
};
|
|
@@ -7932,7 +8199,7 @@ async function handleToolCall(params) {
|
|
|
7932
8199
|
content: [
|
|
7933
8200
|
{
|
|
7934
8201
|
type: "text",
|
|
7935
|
-
text: JSON.stringify(mergeRequests
|
|
8202
|
+
text: JSON.stringify(mergeRequests),
|
|
7936
8203
|
},
|
|
7937
8204
|
],
|
|
7938
8205
|
};
|
|
@@ -7944,7 +8211,7 @@ async function handleToolCall(params) {
|
|
|
7944
8211
|
content: [
|
|
7945
8212
|
{
|
|
7946
8213
|
type: "text",
|
|
7947
|
-
text: JSON.stringify(milestone
|
|
8214
|
+
text: JSON.stringify(milestone),
|
|
7948
8215
|
},
|
|
7949
8216
|
],
|
|
7950
8217
|
};
|
|
@@ -7956,7 +8223,7 @@ async function handleToolCall(params) {
|
|
|
7956
8223
|
content: [
|
|
7957
8224
|
{
|
|
7958
8225
|
type: "text",
|
|
7959
|
-
text: JSON.stringify(events
|
|
8226
|
+
text: JSON.stringify(events),
|
|
7960
8227
|
},
|
|
7961
8228
|
],
|
|
7962
8229
|
};
|
|
@@ -7965,21 +8232,21 @@ async function handleToolCall(params) {
|
|
|
7965
8232
|
const args = ListCommitsSchema.parse(params.arguments);
|
|
7966
8233
|
const commits = await listCommits(args.project_id, args);
|
|
7967
8234
|
return {
|
|
7968
|
-
content: [{ type: "text", text: JSON.stringify(commits
|
|
8235
|
+
content: [{ type: "text", text: JSON.stringify(commits) }],
|
|
7969
8236
|
};
|
|
7970
8237
|
}
|
|
7971
8238
|
case "get_commit": {
|
|
7972
8239
|
const args = GetCommitSchema.parse(params.arguments);
|
|
7973
8240
|
const commit = await getCommit(args.project_id, args.sha, args.stats);
|
|
7974
8241
|
return {
|
|
7975
|
-
content: [{ type: "text", text: JSON.stringify(commit
|
|
8242
|
+
content: [{ type: "text", text: JSON.stringify(commit) }],
|
|
7976
8243
|
};
|
|
7977
8244
|
}
|
|
7978
8245
|
case "get_commit_diff": {
|
|
7979
8246
|
const args = GetCommitDiffSchema.parse(params.arguments);
|
|
7980
8247
|
const diff = await getCommitDiff(args.project_id, args.sha, args.full_diff);
|
|
7981
8248
|
return {
|
|
7982
|
-
content: [{ type: "text", text: JSON.stringify(diff
|
|
8249
|
+
content: [{ type: "text", text: JSON.stringify(diff) }],
|
|
7983
8250
|
};
|
|
7984
8251
|
}
|
|
7985
8252
|
case "get_file_blame": {
|
|
@@ -7987,7 +8254,7 @@ async function handleToolCall(params) {
|
|
|
7987
8254
|
const { project_id, ...options } = args;
|
|
7988
8255
|
const blame = await getFileBlame(project_id, options);
|
|
7989
8256
|
return {
|
|
7990
|
-
content: [{ type: "text", text: JSON.stringify(blame
|
|
8257
|
+
content: [{ type: "text", text: JSON.stringify(blame) }],
|
|
7991
8258
|
};
|
|
7992
8259
|
}
|
|
7993
8260
|
case "list_commit_statuses": {
|
|
@@ -7995,7 +8262,7 @@ async function handleToolCall(params) {
|
|
|
7995
8262
|
const { project_id, sha, ...options } = args;
|
|
7996
8263
|
const statuses = await listCommitStatuses(project_id, sha, options);
|
|
7997
8264
|
return {
|
|
7998
|
-
content: [{ type: "text", text: JSON.stringify(statuses
|
|
8265
|
+
content: [{ type: "text", text: JSON.stringify(statuses) }],
|
|
7999
8266
|
};
|
|
8000
8267
|
}
|
|
8001
8268
|
case "create_commit_status": {
|
|
@@ -8003,14 +8270,14 @@ async function handleToolCall(params) {
|
|
|
8003
8270
|
const { project_id, sha, ...options } = args;
|
|
8004
8271
|
const status = await createCommitStatus(project_id, sha, options);
|
|
8005
8272
|
return {
|
|
8006
|
-
content: [{ type: "text", text: JSON.stringify(status
|
|
8273
|
+
content: [{ type: "text", text: JSON.stringify(status) }],
|
|
8007
8274
|
};
|
|
8008
8275
|
}
|
|
8009
8276
|
case "list_group_iterations": {
|
|
8010
8277
|
const args = ListGroupIterationsSchema.parse(params.arguments);
|
|
8011
8278
|
const iterations = await listGroupIterations(args.group_id, args);
|
|
8012
8279
|
return {
|
|
8013
|
-
content: [{ type: "text", text: JSON.stringify(iterations
|
|
8280
|
+
content: [{ type: "text", text: JSON.stringify(iterations) }],
|
|
8014
8281
|
};
|
|
8015
8282
|
}
|
|
8016
8283
|
// --- CI/CD Variables ---
|
|
@@ -8018,24 +8285,24 @@ async function handleToolCall(params) {
|
|
|
8018
8285
|
const args = ListProjectVariablesSchema.parse(params.arguments);
|
|
8019
8286
|
const { project_id, ...options } = args;
|
|
8020
8287
|
const variables = await listProjectVariables(project_id, options);
|
|
8021
|
-
return { content: [{ type: "text", text: JSON.stringify(variables
|
|
8288
|
+
return { content: [{ type: "text", text: JSON.stringify(variables) }] };
|
|
8022
8289
|
}
|
|
8023
8290
|
case "get_project_variable": {
|
|
8024
8291
|
const args = GetProjectVariableSchema.parse(params.arguments);
|
|
8025
8292
|
const variable = await getProjectVariable(args.project_id, args.key, args.filter);
|
|
8026
|
-
return { content: [{ type: "text", text: JSON.stringify(variable
|
|
8293
|
+
return { content: [{ type: "text", text: JSON.stringify(variable) }] };
|
|
8027
8294
|
}
|
|
8028
8295
|
case "create_project_variable": {
|
|
8029
8296
|
const args = CreateProjectVariableSchema.parse(params.arguments);
|
|
8030
8297
|
const { project_id, ...options } = args;
|
|
8031
8298
|
const variable = await createProjectVariable(project_id, options);
|
|
8032
|
-
return { content: [{ type: "text", text: JSON.stringify(variable
|
|
8299
|
+
return { content: [{ type: "text", text: JSON.stringify(variable) }] };
|
|
8033
8300
|
}
|
|
8034
8301
|
case "update_project_variable": {
|
|
8035
8302
|
const args = UpdateProjectVariableSchema.parse(params.arguments);
|
|
8036
8303
|
const { project_id, key, ...options } = args;
|
|
8037
8304
|
const variable = await updateProjectVariable(project_id, key, options);
|
|
8038
|
-
return { content: [{ type: "text", text: JSON.stringify(variable
|
|
8305
|
+
return { content: [{ type: "text", text: JSON.stringify(variable) }] };
|
|
8039
8306
|
}
|
|
8040
8307
|
case "delete_project_variable": {
|
|
8041
8308
|
const args = DeleteProjectVariableSchema.parse(params.arguments);
|
|
@@ -8054,27 +8321,27 @@ async function handleToolCall(params) {
|
|
|
8054
8321
|
const args = ListGroupVariablesSchema.parse(params.arguments);
|
|
8055
8322
|
const { group_id, ...options } = args;
|
|
8056
8323
|
const variables = await listGroupVariables(group_id, options);
|
|
8057
|
-
return { content: [{ type: "text", text: JSON.stringify(variables
|
|
8324
|
+
return { content: [{ type: "text", text: JSON.stringify(variables) }] };
|
|
8058
8325
|
}
|
|
8059
8326
|
case "get_group_variable": {
|
|
8060
8327
|
rejectIfProjectScopedDeployment("get_group_variable");
|
|
8061
8328
|
const args = GetGroupVariableSchema.parse(params.arguments);
|
|
8062
8329
|
const variable = await getGroupVariable(args.group_id, args.key, args.filter);
|
|
8063
|
-
return { content: [{ type: "text", text: JSON.stringify(variable
|
|
8330
|
+
return { content: [{ type: "text", text: JSON.stringify(variable) }] };
|
|
8064
8331
|
}
|
|
8065
8332
|
case "create_group_variable": {
|
|
8066
8333
|
rejectIfProjectScopedDeployment("create_group_variable");
|
|
8067
8334
|
const args = CreateGroupVariableSchema.parse(params.arguments);
|
|
8068
8335
|
const { group_id, ...options } = args;
|
|
8069
8336
|
const variable = await createGroupVariable(group_id, options);
|
|
8070
|
-
return { content: [{ type: "text", text: JSON.stringify(variable
|
|
8337
|
+
return { content: [{ type: "text", text: JSON.stringify(variable) }] };
|
|
8071
8338
|
}
|
|
8072
8339
|
case "update_group_variable": {
|
|
8073
8340
|
rejectIfProjectScopedDeployment("update_group_variable");
|
|
8074
8341
|
const args = UpdateGroupVariableSchema.parse(params.arguments);
|
|
8075
8342
|
const { group_id, key, ...options } = args;
|
|
8076
8343
|
const variable = await updateGroupVariable(group_id, key, options);
|
|
8077
|
-
return { content: [{ type: "text", text: JSON.stringify(variable
|
|
8344
|
+
return { content: [{ type: "text", text: JSON.stringify(variable) }] };
|
|
8078
8345
|
}
|
|
8079
8346
|
case "delete_group_variable": {
|
|
8080
8347
|
rejectIfProjectScopedDeployment("delete_group_variable");
|
|
@@ -8094,7 +8361,7 @@ async function handleToolCall(params) {
|
|
|
8094
8361
|
const args = GetDependencyProxySettingsSchema.parse(params.arguments);
|
|
8095
8362
|
const settings = await getDependencyProxySettings(args.group_id);
|
|
8096
8363
|
return {
|
|
8097
|
-
content: [{ type: "text", text: JSON.stringify(settings
|
|
8364
|
+
content: [{ type: "text", text: JSON.stringify(settings) }],
|
|
8098
8365
|
};
|
|
8099
8366
|
}
|
|
8100
8367
|
case "update_dependency_proxy_settings": {
|
|
@@ -8103,7 +8370,7 @@ async function handleToolCall(params) {
|
|
|
8103
8370
|
const { group_id, ...options } = args;
|
|
8104
8371
|
const settings = await updateDependencyProxySettings(group_id, options);
|
|
8105
8372
|
return {
|
|
8106
|
-
content: [{ type: "text", text: JSON.stringify(settings
|
|
8373
|
+
content: [{ type: "text", text: JSON.stringify(settings) }],
|
|
8107
8374
|
};
|
|
8108
8375
|
}
|
|
8109
8376
|
case "list_dependency_proxy_blobs": {
|
|
@@ -8112,7 +8379,7 @@ async function handleToolCall(params) {
|
|
|
8112
8379
|
const { group_id, ...options } = args;
|
|
8113
8380
|
const result = await listDependencyProxyBlobs(group_id, options);
|
|
8114
8381
|
return {
|
|
8115
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
8382
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
8116
8383
|
};
|
|
8117
8384
|
}
|
|
8118
8385
|
case "purge_dependency_proxy_cache": {
|
|
@@ -8133,13 +8400,13 @@ async function handleToolCall(params) {
|
|
|
8133
8400
|
const args = MarkdownUploadRemoteSchema.parse(params.arguments);
|
|
8134
8401
|
const upload = await markdownUpload(args.project_id, undefined, args.content, args.filename);
|
|
8135
8402
|
return {
|
|
8136
|
-
content: [{ type: "text", text: JSON.stringify(upload
|
|
8403
|
+
content: [{ type: "text", text: JSON.stringify(upload) }],
|
|
8137
8404
|
};
|
|
8138
8405
|
}
|
|
8139
8406
|
const args = MarkdownUploadSchema.parse(params.arguments);
|
|
8140
8407
|
const upload = await markdownUpload(args.project_id, args.file_path);
|
|
8141
8408
|
return {
|
|
8142
|
-
content: [{ type: "text", text: JSON.stringify(upload
|
|
8409
|
+
content: [{ type: "text", text: JSON.stringify(upload) }],
|
|
8143
8410
|
};
|
|
8144
8411
|
}
|
|
8145
8412
|
case "download_attachment": {
|
|
@@ -8151,10 +8418,12 @@ async function handleToolCall(params) {
|
|
|
8151
8418
|
const mimeType = getImageMimeType(args.filename);
|
|
8152
8419
|
if (IS_REMOTE && !mimeType) {
|
|
8153
8420
|
const downloadUrl = buildDownloadUrl("attachment", {
|
|
8154
|
-
project_id: args.project_id,
|
|
8421
|
+
project_id: args.project_id,
|
|
8422
|
+
secret: args.secret,
|
|
8423
|
+
filename: args.filename,
|
|
8155
8424
|
});
|
|
8156
8425
|
return {
|
|
8157
|
-
content: [{ type: "text", text: JSON.stringify({ download_url: downloadUrl, filename: args.filename }
|
|
8426
|
+
content: [{ type: "text", text: JSON.stringify({ download_url: downloadUrl, filename: args.filename }) }],
|
|
8158
8427
|
};
|
|
8159
8428
|
}
|
|
8160
8429
|
const result = await downloadAttachment(args.project_id, args.secret, args.filename, args.local_path);
|
|
@@ -8166,7 +8435,7 @@ async function handleToolCall(params) {
|
|
|
8166
8435
|
{ type: "image", data: base64, mimeType: result.mimeType },
|
|
8167
8436
|
{
|
|
8168
8437
|
type: "text",
|
|
8169
|
-
text: JSON.stringify({ filename: result.filename, mimeType: result.mimeType }
|
|
8438
|
+
text: JSON.stringify({ filename: result.filename, mimeType: result.mimeType }),
|
|
8170
8439
|
},
|
|
8171
8440
|
],
|
|
8172
8441
|
};
|
|
@@ -8175,7 +8444,7 @@ async function handleToolCall(params) {
|
|
|
8175
8444
|
content: [
|
|
8176
8445
|
{
|
|
8177
8446
|
type: "text",
|
|
8178
|
-
text: JSON.stringify({ success: true, file_path: result.savedPath }
|
|
8447
|
+
text: JSON.stringify({ success: true, file_path: result.savedPath }),
|
|
8179
8448
|
},
|
|
8180
8449
|
],
|
|
8181
8450
|
};
|
|
@@ -8184,7 +8453,7 @@ async function handleToolCall(params) {
|
|
|
8184
8453
|
const args = ListEventsSchema.parse(params.arguments);
|
|
8185
8454
|
const events = await listEvents(args);
|
|
8186
8455
|
return {
|
|
8187
|
-
content: [{ type: "text", text: JSON.stringify(events
|
|
8456
|
+
content: [{ type: "text", text: JSON.stringify(events) }],
|
|
8188
8457
|
};
|
|
8189
8458
|
}
|
|
8190
8459
|
case "get_project_events": {
|
|
@@ -8192,7 +8461,7 @@ async function handleToolCall(params) {
|
|
|
8192
8461
|
const { project_id, ...options } = args;
|
|
8193
8462
|
const events = await getProjectEvents(project_id, options);
|
|
8194
8463
|
return {
|
|
8195
|
-
content: [{ type: "text", text: JSON.stringify(events
|
|
8464
|
+
content: [{ type: "text", text: JSON.stringify(events) }],
|
|
8196
8465
|
};
|
|
8197
8466
|
}
|
|
8198
8467
|
case "list_releases": {
|
|
@@ -8200,14 +8469,14 @@ async function handleToolCall(params) {
|
|
|
8200
8469
|
const { project_id, ...options } = args;
|
|
8201
8470
|
const releases = await listReleases(project_id, options);
|
|
8202
8471
|
return {
|
|
8203
|
-
content: [{ type: "text", text: JSON.stringify(releases
|
|
8472
|
+
content: [{ type: "text", text: JSON.stringify(releases) }],
|
|
8204
8473
|
};
|
|
8205
8474
|
}
|
|
8206
8475
|
case "get_release": {
|
|
8207
8476
|
const args = GetReleaseSchema.parse(params.arguments);
|
|
8208
8477
|
const release = await getRelease(args.project_id, args.tag_name, args.include_html_description);
|
|
8209
8478
|
return {
|
|
8210
|
-
content: [{ type: "text", text: JSON.stringify(release
|
|
8479
|
+
content: [{ type: "text", text: JSON.stringify(release) }],
|
|
8211
8480
|
};
|
|
8212
8481
|
}
|
|
8213
8482
|
case "create_release": {
|
|
@@ -8215,7 +8484,7 @@ async function handleToolCall(params) {
|
|
|
8215
8484
|
const { project_id, ...options } = args;
|
|
8216
8485
|
const release = await createRelease(project_id, options);
|
|
8217
8486
|
return {
|
|
8218
|
-
content: [{ type: "text", text: JSON.stringify(release
|
|
8487
|
+
content: [{ type: "text", text: JSON.stringify(release) }],
|
|
8219
8488
|
};
|
|
8220
8489
|
}
|
|
8221
8490
|
case "update_release": {
|
|
@@ -8223,7 +8492,7 @@ async function handleToolCall(params) {
|
|
|
8223
8492
|
const { project_id, tag_name, ...options } = args;
|
|
8224
8493
|
const release = await updateRelease(project_id, tag_name, options);
|
|
8225
8494
|
return {
|
|
8226
|
-
content: [{ type: "text", text: JSON.stringify(release
|
|
8495
|
+
content: [{ type: "text", text: JSON.stringify(release) }],
|
|
8227
8496
|
};
|
|
8228
8497
|
}
|
|
8229
8498
|
case "delete_release": {
|
|
@@ -8254,10 +8523,12 @@ async function handleToolCall(params) {
|
|
|
8254
8523
|
const args = DownloadReleaseAssetSchema.parse(params.arguments);
|
|
8255
8524
|
if (IS_REMOTE) {
|
|
8256
8525
|
const downloadUrl = buildDownloadUrl("release-asset", {
|
|
8257
|
-
project_id: args.project_id,
|
|
8526
|
+
project_id: args.project_id,
|
|
8527
|
+
tag_name: args.tag_name,
|
|
8528
|
+
direct_asset_path: args.direct_asset_path,
|
|
8258
8529
|
});
|
|
8259
8530
|
return {
|
|
8260
|
-
content: [{ type: "text", text: JSON.stringify({ download_url: downloadUrl, filename: args.direct_asset_path.split("/").pop() || args.direct_asset_path }
|
|
8531
|
+
content: [{ type: "text", text: JSON.stringify({ download_url: downloadUrl, filename: args.direct_asset_path.split("/").pop() || args.direct_asset_path }) }],
|
|
8261
8532
|
};
|
|
8262
8533
|
}
|
|
8263
8534
|
const assetContent = await downloadReleaseAsset(args.project_id, args.tag_name, args.direct_asset_path);
|
|
@@ -8270,14 +8541,14 @@ async function handleToolCall(params) {
|
|
|
8270
8541
|
const { project_id, ...options } = args;
|
|
8271
8542
|
const tags = await listTags(project_id, options);
|
|
8272
8543
|
return {
|
|
8273
|
-
content: [{ type: "text", text: JSON.stringify(tags
|
|
8544
|
+
content: [{ type: "text", text: JSON.stringify(tags) }],
|
|
8274
8545
|
};
|
|
8275
8546
|
}
|
|
8276
8547
|
case "get_tag": {
|
|
8277
8548
|
const args = GetTagSchema.parse(params.arguments);
|
|
8278
8549
|
const tag = await getTag(args.project_id, args.tag_name);
|
|
8279
8550
|
return {
|
|
8280
|
-
content: [{ type: "text", text: JSON.stringify(tag
|
|
8551
|
+
content: [{ type: "text", text: JSON.stringify(tag) }],
|
|
8281
8552
|
};
|
|
8282
8553
|
}
|
|
8283
8554
|
case "create_tag": {
|
|
@@ -8285,7 +8556,7 @@ async function handleToolCall(params) {
|
|
|
8285
8556
|
const { project_id, ...options } = args;
|
|
8286
8557
|
const tag = await createTag(project_id, options);
|
|
8287
8558
|
return {
|
|
8288
|
-
content: [{ type: "text", text: JSON.stringify(tag
|
|
8559
|
+
content: [{ type: "text", text: JSON.stringify(tag) }],
|
|
8289
8560
|
};
|
|
8290
8561
|
}
|
|
8291
8562
|
case "delete_tag": {
|
|
@@ -8304,30 +8575,28 @@ async function handleToolCall(params) {
|
|
|
8304
8575
|
const args = GetTagSignatureSchema.parse(params.arguments);
|
|
8305
8576
|
const signature = await getTagSignature(args.project_id, args.tag_name);
|
|
8306
8577
|
return {
|
|
8307
|
-
content: [{ type: "text", text: JSON.stringify(signature
|
|
8578
|
+
content: [{ type: "text", text: JSON.stringify(signature) }],
|
|
8308
8579
|
};
|
|
8309
8580
|
}
|
|
8310
8581
|
case "list_webhooks": {
|
|
8311
8582
|
const args = ListWebhooksSchema.parse(params.arguments);
|
|
8312
8583
|
const webhooks = await listWebhooks(args);
|
|
8313
8584
|
return {
|
|
8314
|
-
content: [{ type: "text", text: JSON.stringify(webhooks
|
|
8585
|
+
content: [{ type: "text", text: JSON.stringify(webhooks) }],
|
|
8315
8586
|
};
|
|
8316
8587
|
}
|
|
8317
8588
|
case "list_webhook_events": {
|
|
8318
8589
|
const args = ListWebhookEventsSchema.parse(params.arguments);
|
|
8319
8590
|
const events = await listWebhookEvents(args);
|
|
8320
8591
|
return {
|
|
8321
|
-
content: [{ type: "text", text: JSON.stringify(events
|
|
8592
|
+
content: [{ type: "text", text: JSON.stringify(events) }],
|
|
8322
8593
|
};
|
|
8323
8594
|
}
|
|
8324
8595
|
case "get_webhook_event": {
|
|
8325
8596
|
const args = GetWebhookEventSchema.parse(params.arguments);
|
|
8326
8597
|
const event = await getWebhookEvent(args);
|
|
8327
8598
|
if (!event) {
|
|
8328
|
-
const searchScope = args.page
|
|
8329
|
-
? `on page ${args.page}`
|
|
8330
|
-
: "in the 500 most recent events";
|
|
8599
|
+
const searchScope = args.page ? `on page ${args.page}` : "in the 500 most recent events";
|
|
8331
8600
|
return {
|
|
8332
8601
|
content: [
|
|
8333
8602
|
{
|
|
@@ -8338,7 +8607,7 @@ async function handleToolCall(params) {
|
|
|
8338
8607
|
};
|
|
8339
8608
|
}
|
|
8340
8609
|
return {
|
|
8341
|
-
content: [{ type: "text", text: JSON.stringify(event
|
|
8610
|
+
content: [{ type: "text", text: JSON.stringify(event) }],
|
|
8342
8611
|
};
|
|
8343
8612
|
}
|
|
8344
8613
|
case "health_check": {
|
|
@@ -8346,13 +8615,24 @@ async function handleToolCall(params) {
|
|
|
8346
8615
|
const url = new URL(`${getEffectiveApiUrl()}/user`);
|
|
8347
8616
|
const response = await fetch(url.toString(), getFetchConfig());
|
|
8348
8617
|
let authenticated = response.ok;
|
|
8349
|
-
if (!authenticated &&
|
|
8618
|
+
if (!authenticated &&
|
|
8619
|
+
(response.status === 401 || response.status === 403) &&
|
|
8620
|
+
(GITLAB_JOB_TOKEN || usesJobTokenHeader())) {
|
|
8350
8621
|
const jobUrl = new URL(`${getEffectiveApiUrl()}/job`);
|
|
8351
8622
|
const jobResponse = await fetch(jobUrl.toString(), getFetchConfig());
|
|
8352
8623
|
authenticated = jobResponse.ok;
|
|
8353
8624
|
}
|
|
8354
8625
|
return {
|
|
8355
|
-
content: [
|
|
8626
|
+
content: [
|
|
8627
|
+
{
|
|
8628
|
+
type: "text",
|
|
8629
|
+
text: JSON.stringify({
|
|
8630
|
+
status: authenticated ? "ok" : "error",
|
|
8631
|
+
authenticated,
|
|
8632
|
+
gitlab_url: getEffectiveApiUrl(),
|
|
8633
|
+
}),
|
|
8634
|
+
},
|
|
8635
|
+
],
|
|
8356
8636
|
};
|
|
8357
8637
|
}
|
|
8358
8638
|
case "get_branch": {
|
|
@@ -8366,7 +8646,7 @@ async function handleToolCall(params) {
|
|
|
8366
8646
|
const data = await response.json();
|
|
8367
8647
|
const branch = GitLabBranchSchema.parse(data);
|
|
8368
8648
|
return {
|
|
8369
|
-
content: [{ type: "text", text: JSON.stringify(branch
|
|
8649
|
+
content: [{ type: "text", text: JSON.stringify(branch) }],
|
|
8370
8650
|
};
|
|
8371
8651
|
}
|
|
8372
8652
|
case "list_branches": {
|
|
@@ -8389,7 +8669,7 @@ async function handleToolCall(params) {
|
|
|
8389
8669
|
const data = await response.json();
|
|
8390
8670
|
const branches = z.array(GitLabBranchSchema).parse(data);
|
|
8391
8671
|
return {
|
|
8392
|
-
content: [{ type: "text", text: JSON.stringify(branches
|
|
8672
|
+
content: [{ type: "text", text: JSON.stringify(branches) }],
|
|
8393
8673
|
};
|
|
8394
8674
|
}
|
|
8395
8675
|
case "delete_branch": {
|
|
@@ -8402,7 +8682,7 @@ async function handleToolCall(params) {
|
|
|
8402
8682
|
});
|
|
8403
8683
|
await handleGitLabError(response);
|
|
8404
8684
|
return {
|
|
8405
|
-
content: [{ type: "text", text: JSON.stringify({ status: "deleted", branch: args.branch_name }
|
|
8685
|
+
content: [{ type: "text", text: JSON.stringify({ status: "deleted", branch: args.branch_name }) }],
|
|
8406
8686
|
};
|
|
8407
8687
|
}
|
|
8408
8688
|
case "list_protected_branches": {
|
|
@@ -8422,7 +8702,7 @@ async function handleToolCall(params) {
|
|
|
8422
8702
|
await handleGitLabError(response);
|
|
8423
8703
|
const data = z.array(GitLabProtectedBranchSchema).parse(await response.json());
|
|
8424
8704
|
return {
|
|
8425
|
-
content: [{ type: "text", text: JSON.stringify(data
|
|
8705
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
8426
8706
|
};
|
|
8427
8707
|
}
|
|
8428
8708
|
case "get_protected_branch": {
|
|
@@ -8436,7 +8716,7 @@ async function handleToolCall(params) {
|
|
|
8436
8716
|
await handleGitLabError(response);
|
|
8437
8717
|
const data = GitLabProtectedBranchSchema.parse(await response.json());
|
|
8438
8718
|
return {
|
|
8439
|
-
content: [{ type: "text", text: JSON.stringify(data
|
|
8719
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
8440
8720
|
};
|
|
8441
8721
|
}
|
|
8442
8722
|
case "protect_branch": {
|
|
@@ -8463,7 +8743,7 @@ async function handleToolCall(params) {
|
|
|
8463
8743
|
await handleGitLabError(response);
|
|
8464
8744
|
const data = GitLabProtectedBranchSchema.parse(await response.json());
|
|
8465
8745
|
return {
|
|
8466
|
-
content: [{ type: "text", text: JSON.stringify(data
|
|
8746
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
8467
8747
|
};
|
|
8468
8748
|
}
|
|
8469
8749
|
case "unprotect_branch": {
|
|
@@ -8477,7 +8757,7 @@ async function handleToolCall(params) {
|
|
|
8477
8757
|
});
|
|
8478
8758
|
await handleGitLabError(response);
|
|
8479
8759
|
return {
|
|
8480
|
-
content: [{ type: "text", text: JSON.stringify({ status: "unprotected", branch: args.branch_name }
|
|
8760
|
+
content: [{ type: "text", text: JSON.stringify({ status: "unprotected", branch: args.branch_name }) }],
|
|
8481
8761
|
};
|
|
8482
8762
|
}
|
|
8483
8763
|
case "update_default_branch": {
|
|
@@ -8493,7 +8773,7 @@ async function handleToolCall(params) {
|
|
|
8493
8773
|
await handleGitLabError(response);
|
|
8494
8774
|
const data = await response.json();
|
|
8495
8775
|
return {
|
|
8496
|
-
content: [{ type: "text", text: JSON.stringify({ status: "updated", default_branch: args.default_branch, project: data }
|
|
8776
|
+
content: [{ type: "text", text: JSON.stringify({ status: "updated", default_branch: args.default_branch, project: data }) }],
|
|
8497
8777
|
};
|
|
8498
8778
|
}
|
|
8499
8779
|
default:
|
|
@@ -8631,18 +8911,15 @@ function registerDownloadProxy(app, maxRequestsPerMinute = Number.parseInt(proce
|
|
|
8631
8911
|
return;
|
|
8632
8912
|
}
|
|
8633
8913
|
// API URL: prefer token-embedded URL, then X-GitLab-API-URL header, then default
|
|
8634
|
-
let apiUrl =
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
}
|
|
8642
|
-
|
|
8643
|
-
res.status(400).json({ error: "Invalid X-GitLab-API-URL" });
|
|
8644
|
-
return;
|
|
8645
|
-
}
|
|
8914
|
+
let apiUrl = GITLAB_API_URL;
|
|
8915
|
+
const requestedApiUrl = tokenApiUrl || req.headers["x-gitlab-api-url"]?.trim();
|
|
8916
|
+
if (ENABLE_DYNAMIC_API_URL && requestedApiUrl) {
|
|
8917
|
+
try {
|
|
8918
|
+
apiUrl = resolveTrustedGitLabApiUrl(requestedApiUrl);
|
|
8919
|
+
}
|
|
8920
|
+
catch {
|
|
8921
|
+
res.status(400).json({ error: "Invalid X-GitLab-API-URL" });
|
|
8922
|
+
return;
|
|
8646
8923
|
}
|
|
8647
8924
|
}
|
|
8648
8925
|
const { type } = req.params;
|
|
@@ -8656,7 +8933,7 @@ function registerDownloadProxy(app, maxRequestsPerMinute = Number.parseInt(proce
|
|
|
8656
8933
|
return;
|
|
8657
8934
|
}
|
|
8658
8935
|
const effectiveProjectId = getEffectiveProjectId(decodeURIComponent(project_id));
|
|
8659
|
-
gitlabUrl = `${apiUrl}/projects/${encodeURIComponent(effectiveProjectId)}/jobs/${job_id}/artifacts`;
|
|
8936
|
+
gitlabUrl = `${apiUrl}/projects/${encodeURIComponent(effectiveProjectId)}/jobs/${encodeGitLabPathSegment(job_id)}/artifacts`;
|
|
8660
8937
|
break;
|
|
8661
8938
|
}
|
|
8662
8939
|
case "attachment": {
|
|
@@ -8666,17 +8943,19 @@ function registerDownloadProxy(app, maxRequestsPerMinute = Number.parseInt(proce
|
|
|
8666
8943
|
return;
|
|
8667
8944
|
}
|
|
8668
8945
|
const effectiveProjectId = getEffectiveProjectId(decodeURIComponent(project_id));
|
|
8669
|
-
gitlabUrl = `${apiUrl}/projects/${encodeURIComponent(effectiveProjectId)}/uploads/${secret}/${filename}`;
|
|
8946
|
+
gitlabUrl = `${apiUrl}/projects/${encodeURIComponent(effectiveProjectId)}/uploads/${encodeGitLabPathSegment(secret)}/${encodeGitLabPath(filename)}`;
|
|
8670
8947
|
break;
|
|
8671
8948
|
}
|
|
8672
8949
|
case "release-asset": {
|
|
8673
8950
|
const { project_id, tag_name, direct_asset_path } = req.query;
|
|
8674
8951
|
if (!project_id || !tag_name || !direct_asset_path) {
|
|
8675
|
-
res
|
|
8952
|
+
res
|
|
8953
|
+
.status(400)
|
|
8954
|
+
.json({ error: "project_id, tag_name, and direct_asset_path are required" });
|
|
8676
8955
|
return;
|
|
8677
8956
|
}
|
|
8678
8957
|
const effectiveProjectId = getEffectiveProjectId(decodeURIComponent(project_id));
|
|
8679
|
-
gitlabUrl = `${apiUrl}/projects/${encodeURIComponent(effectiveProjectId)}/releases/${encodeURIComponent(tag_name)}/downloads/${direct_asset_path}`;
|
|
8958
|
+
gitlabUrl = `${apiUrl}/projects/${encodeURIComponent(effectiveProjectId)}/releases/${encodeURIComponent(tag_name)}/downloads/${encodeGitLabPath(direct_asset_path)}`;
|
|
8680
8959
|
break;
|
|
8681
8960
|
}
|
|
8682
8961
|
default:
|
|
@@ -8728,12 +9007,21 @@ function registerDownloadProxy(app, maxRequestsPerMinute = Number.parseInt(proce
|
|
|
8728
9007
|
*/
|
|
8729
9008
|
async function startSSEServer() {
|
|
8730
9009
|
const app = express();
|
|
9010
|
+
const sseAuthToken = getConfig("sse-auth-token", "SSE_AUTH_TOKEN");
|
|
8731
9011
|
if (MCP_TRUST_PROXY) {
|
|
8732
9012
|
app.set("trust proxy", 1);
|
|
8733
9013
|
}
|
|
9014
|
+
const requireSseAuth = (req, res, next) => {
|
|
9015
|
+
if (!sseAuthToken)
|
|
9016
|
+
return next();
|
|
9017
|
+
const match = /^Bearer\s+(\S+)$/i.exec(req.headers.authorization || "");
|
|
9018
|
+
if (match?.[1] === sseAuthToken)
|
|
9019
|
+
return next();
|
|
9020
|
+
res.status(401).json({ error: "SSE authentication required" });
|
|
9021
|
+
};
|
|
8734
9022
|
const transports = {};
|
|
8735
9023
|
let shuttingDown = false;
|
|
8736
|
-
app.get("/sse", async (_, res) => {
|
|
9024
|
+
app.get("/sse", requireSseAuth, async (_, res) => {
|
|
8737
9025
|
const serverInstance = createServer();
|
|
8738
9026
|
const transport = new SSEServerTransport("/messages", res);
|
|
8739
9027
|
transports[transport.sessionId] = transport;
|
|
@@ -8742,7 +9030,7 @@ async function startSSEServer() {
|
|
|
8742
9030
|
});
|
|
8743
9031
|
await serverInstance.connect(transport);
|
|
8744
9032
|
});
|
|
8745
|
-
app.post("/messages", async (req, res) => {
|
|
9033
|
+
app.post("/messages", requireSseAuth, async (req, res) => {
|
|
8746
9034
|
const sessionId = req.query.sessionId;
|
|
8747
9035
|
const transport = transports[sessionId];
|
|
8748
9036
|
if (transport) {
|
|
@@ -8866,12 +9154,11 @@ async function startStreamableHTTPServer() {
|
|
|
8866
9154
|
// Only process dynamic URL if the feature is enabled
|
|
8867
9155
|
if (ENABLE_DYNAMIC_API_URL && dynamicApiUrl) {
|
|
8868
9156
|
try {
|
|
8869
|
-
|
|
8870
|
-
apiUrl = normalizeGitLabApiUrl(dynamicApiUrl);
|
|
9157
|
+
apiUrl = resolveTrustedGitLabApiUrl(dynamicApiUrl);
|
|
8871
9158
|
}
|
|
8872
9159
|
catch {
|
|
8873
9160
|
logger.warn(`Invalid X-GitLab-API-URL provided: ${dynamicApiUrl}. Auth will fail.`);
|
|
8874
|
-
return null; // Reject if URL is malformed
|
|
9161
|
+
return null; // Reject if URL is malformed or not allowed
|
|
8875
9162
|
}
|
|
8876
9163
|
}
|
|
8877
9164
|
// Extract token — priority: Private-Token > JOB-TOKEN > Authorization Bearer
|
|
@@ -9079,7 +9366,7 @@ async function startStreamableHTTPServer() {
|
|
|
9079
9366
|
token: effective.token,
|
|
9080
9367
|
lastUsed: Date.now(),
|
|
9081
9368
|
apiUrl: effective.apiUrl,
|
|
9082
|
-
publicBaseUrl: getForwardedPublicBaseUrl(req),
|
|
9369
|
+
publicBaseUrl: getForwardedPublicBaseUrl(req, MCP_TRUST_PROXY),
|
|
9083
9370
|
};
|
|
9084
9371
|
// Step 4: create a fresh transport per request.
|
|
9085
9372
|
const transport = isInit
|
|
@@ -9273,16 +9560,14 @@ async function startStreamableHTTPServer() {
|
|
|
9273
9560
|
// Streamable HTTP endpoint - handles both session creation and message handling
|
|
9274
9561
|
app.post("/mcp", mcpBearerAuth, async (req, res) => {
|
|
9275
9562
|
const sessionId = readMcpSessionIdHeader(req);
|
|
9276
|
-
const publicBaseUrl = getForwardedPublicBaseUrl(req);
|
|
9563
|
+
const publicBaseUrl = getForwardedPublicBaseUrl(req, MCP_TRUST_PROXY);
|
|
9277
9564
|
// Track request
|
|
9278
9565
|
metrics.requestsProcessed++;
|
|
9279
9566
|
// Stateless-mode branch: bypass authBySession / streamableTransports
|
|
9280
9567
|
// entirely and derive the session auth from either the current request
|
|
9281
9568
|
// headers (init) or a sealed Mcp-Session-Id (subsequent requests).
|
|
9282
9569
|
// Rate limiting is disabled here because there is no shared counter.
|
|
9283
|
-
if (OAUTH_STATELESS_MODE &&
|
|
9284
|
-
STATELESS_MATERIAL &&
|
|
9285
|
-
(REMOTE_AUTHORIZATION || GITLAB_MCP_OAUTH)) {
|
|
9570
|
+
if (OAUTH_STATELESS_MODE && STATELESS_MATERIAL && (REMOTE_AUTHORIZATION || GITLAB_MCP_OAUTH)) {
|
|
9286
9571
|
await handleStatelessMcpRequest(req, res, STATELESS_MATERIAL, OAUTH_STATELESS_SESSION_TTL_SECONDS);
|
|
9287
9572
|
return;
|
|
9288
9573
|
}
|
|
@@ -9307,9 +9592,11 @@ async function startStreamableHTTPServer() {
|
|
|
9307
9592
|
// Handle remote authorization: extract and store auth headers per session
|
|
9308
9593
|
if (REMOTE_AUTHORIZATION) {
|
|
9309
9594
|
const authData = parseAuthHeaders(req);
|
|
9595
|
+
const allowUnauthenticatedDiscovery = GITLAB_ALLOW_UNAUTHENTICATED_TOOL_DISCOVERY &&
|
|
9596
|
+
isUnauthenticatedDiscoveryRequestBody(req.body);
|
|
9310
9597
|
if (sessionId && !authBySession[sessionId]) {
|
|
9311
|
-
// New session: require auth headers
|
|
9312
|
-
if (!authData) {
|
|
9598
|
+
// New session: require auth headers unless public discovery was explicitly enabled.
|
|
9599
|
+
if (!authData && !allowUnauthenticatedDiscovery) {
|
|
9313
9600
|
metrics.authFailures++;
|
|
9314
9601
|
res.status(401).json({
|
|
9315
9602
|
error: "Missing Private-Token, JOB-TOKEN, or Authorization header",
|
|
@@ -9317,10 +9604,16 @@ async function startStreamableHTTPServer() {
|
|
|
9317
9604
|
});
|
|
9318
9605
|
return;
|
|
9319
9606
|
}
|
|
9320
|
-
// Store auth
|
|
9321
|
-
|
|
9322
|
-
|
|
9323
|
-
|
|
9607
|
+
// Store auth only when provided. Public discovery intentionally leaves the session unauthenticated.
|
|
9608
|
+
if (authData) {
|
|
9609
|
+
authBySession[sessionId] = withPublicBaseUrl(authData, publicBaseUrl);
|
|
9610
|
+
logger.info(`Session ${sessionId}: stored ${authData.header} header`);
|
|
9611
|
+
setAuthTimeout(sessionId);
|
|
9612
|
+
}
|
|
9613
|
+
else if (allowUnauthenticatedDiscovery) {
|
|
9614
|
+
// Schedule cleanup for unauthenticated discovery sessions to prevent slot exhaustion
|
|
9615
|
+
setAuthTimeout(sessionId);
|
|
9616
|
+
}
|
|
9324
9617
|
}
|
|
9325
9618
|
else if (sessionId && authData) {
|
|
9326
9619
|
// Existing session: allow auth rotation/update
|
|
@@ -9493,25 +9786,112 @@ async function startStreamableHTTPServer() {
|
|
|
9493
9786
|
message: "GET /mcp is not supported when STREAMABLE_HTTP is enabled. Use POST to communicate with the MCP server.",
|
|
9494
9787
|
});
|
|
9495
9788
|
});
|
|
9789
|
+
const getMetricsSnapshot = () => ({
|
|
9790
|
+
...metrics,
|
|
9791
|
+
activeSessions: Object.keys(streamableTransports).length,
|
|
9792
|
+
authenticatedSessions: Object.keys(authBySession).length,
|
|
9793
|
+
gitlabClientPool: clientPool.getStats(),
|
|
9794
|
+
uptime: process.uptime(),
|
|
9795
|
+
memoryUsage: process.memoryUsage(),
|
|
9796
|
+
config: {
|
|
9797
|
+
maxSessions: MAX_SESSIONS,
|
|
9798
|
+
maxRequestsPerMinute: MAX_REQUESTS_PER_MINUTE,
|
|
9799
|
+
sessionTimeoutSeconds: SESSION_TIMEOUT_SECONDS,
|
|
9800
|
+
remoteAuthEnabled: REMOTE_AUTHORIZATION,
|
|
9801
|
+
mcpOAuthEnabled: GITLAB_MCP_OAUTH,
|
|
9802
|
+
statelessModeEnabled: OAUTH_STATELESS_MODE && STATELESS_MATERIAL !== null,
|
|
9803
|
+
statelessRotationKey: OAUTH_STATELESS_MODE && STATELESS_MATERIAL?.previous != null,
|
|
9804
|
+
},
|
|
9805
|
+
});
|
|
9806
|
+
const escapePrometheusLabel = (value) => String(value).replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/"/g, '\\"');
|
|
9807
|
+
const formatPrometheusMetrics = () => {
|
|
9808
|
+
const snapshot = getMetricsSnapshot();
|
|
9809
|
+
const configLabels = Object.entries({
|
|
9810
|
+
max_sessions: snapshot.config.maxSessions,
|
|
9811
|
+
max_requests_per_minute: snapshot.config.maxRequestsPerMinute,
|
|
9812
|
+
session_timeout_seconds: snapshot.config.sessionTimeoutSeconds,
|
|
9813
|
+
remote_auth_enabled: snapshot.config.remoteAuthEnabled,
|
|
9814
|
+
mcp_oauth_enabled: snapshot.config.mcpOAuthEnabled,
|
|
9815
|
+
stateless_mode_enabled: snapshot.config.statelessModeEnabled,
|
|
9816
|
+
stateless_rotation_key: snapshot.config.statelessRotationKey,
|
|
9817
|
+
})
|
|
9818
|
+
.map(([key, value]) => `${key}="${escapePrometheusLabel(value)}"`)
|
|
9819
|
+
.join(",");
|
|
9820
|
+
return [
|
|
9821
|
+
"# HELP gitlab_mcp_requests_processed_total Total MCP requests processed",
|
|
9822
|
+
"# TYPE gitlab_mcp_requests_processed_total counter",
|
|
9823
|
+
`gitlab_mcp_requests_processed_total ${snapshot.requestsProcessed}`,
|
|
9824
|
+
"",
|
|
9825
|
+
"# HELP gitlab_mcp_requests_rejected_total Requests rejected, by reason",
|
|
9826
|
+
"# TYPE gitlab_mcp_requests_rejected_total counter",
|
|
9827
|
+
`gitlab_mcp_requests_rejected_total{reason="rate_limit"} ${snapshot.rejectedByRateLimit}`,
|
|
9828
|
+
`gitlab_mcp_requests_rejected_total{reason="capacity"} ${snapshot.rejectedByCapacity}`,
|
|
9829
|
+
"",
|
|
9830
|
+
"# HELP gitlab_mcp_auth_failures_total Authentication failures",
|
|
9831
|
+
"# TYPE gitlab_mcp_auth_failures_total counter",
|
|
9832
|
+
`gitlab_mcp_auth_failures_total ${snapshot.authFailures}`,
|
|
9833
|
+
"",
|
|
9834
|
+
"# HELP gitlab_mcp_sessions_total Total sessions created",
|
|
9835
|
+
"# TYPE gitlab_mcp_sessions_total counter",
|
|
9836
|
+
`gitlab_mcp_sessions_total ${snapshot.totalSessions}`,
|
|
9837
|
+
"",
|
|
9838
|
+
"# HELP gitlab_mcp_sessions_expired_total Sessions expired due to inactivity",
|
|
9839
|
+
"# TYPE gitlab_mcp_sessions_expired_total counter",
|
|
9840
|
+
`gitlab_mcp_sessions_expired_total ${snapshot.expiredSessions}`,
|
|
9841
|
+
"",
|
|
9842
|
+
"# HELP gitlab_mcp_active_sessions Currently active sessions",
|
|
9843
|
+
"# TYPE gitlab_mcp_active_sessions gauge",
|
|
9844
|
+
`gitlab_mcp_active_sessions ${snapshot.activeSessions}`,
|
|
9845
|
+
"",
|
|
9846
|
+
"# HELP gitlab_mcp_authenticated_sessions Currently authenticated sessions",
|
|
9847
|
+
"# TYPE gitlab_mcp_authenticated_sessions gauge",
|
|
9848
|
+
`gitlab_mcp_authenticated_sessions ${snapshot.authenticatedSessions}`,
|
|
9849
|
+
"",
|
|
9850
|
+
"# HELP gitlab_mcp_client_pool_size Current GitLab client pool size",
|
|
9851
|
+
"# TYPE gitlab_mcp_client_pool_size gauge",
|
|
9852
|
+
`gitlab_mcp_client_pool_size ${snapshot.gitlabClientPool.size}`,
|
|
9853
|
+
"",
|
|
9854
|
+
"# HELP gitlab_mcp_client_pool_max_size Maximum GitLab client pool size",
|
|
9855
|
+
"# TYPE gitlab_mcp_client_pool_max_size gauge",
|
|
9856
|
+
`gitlab_mcp_client_pool_max_size ${snapshot.gitlabClientPool.maxSize}`,
|
|
9857
|
+
"",
|
|
9858
|
+
"# HELP gitlab_mcp_uptime_seconds Process uptime in seconds",
|
|
9859
|
+
"# TYPE gitlab_mcp_uptime_seconds gauge",
|
|
9860
|
+
`gitlab_mcp_uptime_seconds ${snapshot.uptime}`,
|
|
9861
|
+
"",
|
|
9862
|
+
"# HELP gitlab_mcp_memory_usage_bytes Node.js memory usage by type",
|
|
9863
|
+
"# TYPE gitlab_mcp_memory_usage_bytes gauge",
|
|
9864
|
+
...Object.entries(snapshot.memoryUsage).map(([key, value]) => `gitlab_mcp_memory_usage_bytes{type="${escapePrometheusLabel(key)}"} ${value}`),
|
|
9865
|
+
"",
|
|
9866
|
+
"# HELP gitlab_mcp_stateless_requests_total Stateless MCP requests processed",
|
|
9867
|
+
"# TYPE gitlab_mcp_stateless_requests_total counter",
|
|
9868
|
+
`gitlab_mcp_stateless_requests_total ${snapshot.statelessRequests}`,
|
|
9869
|
+
"",
|
|
9870
|
+
"# HELP gitlab_mcp_stateless_auth_total Stateless auth successes, by source",
|
|
9871
|
+
"# TYPE gitlab_mcp_stateless_auth_total counter",
|
|
9872
|
+
`gitlab_mcp_stateless_auth_total{source="header"} ${snapshot.statelessAuthFromHeader}`,
|
|
9873
|
+
`gitlab_mcp_stateless_auth_total{source="sealed_session_id"} ${snapshot.statelessAuthFromSealedSid}`,
|
|
9874
|
+
"",
|
|
9875
|
+
"# HELP gitlab_mcp_stateless_auth_failures_total Stateless auth failures",
|
|
9876
|
+
"# TYPE gitlab_mcp_stateless_auth_failures_total counter",
|
|
9877
|
+
`gitlab_mcp_stateless_auth_failures_total ${snapshot.statelessAuthFailures}`,
|
|
9878
|
+
"",
|
|
9879
|
+
"# HELP gitlab_mcp_stateless_session_id_rotations_total Stateless session id rotations",
|
|
9880
|
+
"# TYPE gitlab_mcp_stateless_session_id_rotations_total counter",
|
|
9881
|
+
`gitlab_mcp_stateless_session_id_rotations_total ${snapshot.statelessSidRotated}`,
|
|
9882
|
+
"",
|
|
9883
|
+
"# HELP gitlab_mcp_config_info Static configuration (value is always 1)",
|
|
9884
|
+
"# TYPE gitlab_mcp_config_info gauge",
|
|
9885
|
+
`gitlab_mcp_config_info{${configLabels}} 1`,
|
|
9886
|
+
"",
|
|
9887
|
+
].join("\n");
|
|
9888
|
+
};
|
|
9496
9889
|
// Metrics endpoint
|
|
9497
9890
|
app.get("/metrics", (_req, res) => {
|
|
9498
|
-
res.
|
|
9499
|
-
|
|
9500
|
-
|
|
9501
|
-
|
|
9502
|
-
gitlabClientPool: clientPool.getStats(),
|
|
9503
|
-
uptime: process.uptime(),
|
|
9504
|
-
memoryUsage: process.memoryUsage(),
|
|
9505
|
-
config: {
|
|
9506
|
-
maxSessions: MAX_SESSIONS,
|
|
9507
|
-
maxRequestsPerMinute: MAX_REQUESTS_PER_MINUTE,
|
|
9508
|
-
sessionTimeoutSeconds: SESSION_TIMEOUT_SECONDS,
|
|
9509
|
-
remoteAuthEnabled: REMOTE_AUTHORIZATION,
|
|
9510
|
-
mcpOAuthEnabled: GITLAB_MCP_OAUTH,
|
|
9511
|
-
statelessModeEnabled: OAUTH_STATELESS_MODE && STATELESS_MATERIAL !== null,
|
|
9512
|
-
statelessRotationKey: OAUTH_STATELESS_MODE && STATELESS_MATERIAL?.previous != null,
|
|
9513
|
-
},
|
|
9514
|
-
});
|
|
9891
|
+
res.type("text/plain; version=0.0.4").send(formatPrometheusMetrics());
|
|
9892
|
+
});
|
|
9893
|
+
app.get("/metrics.json", (_req, res) => {
|
|
9894
|
+
res.json(getMetricsSnapshot());
|
|
9515
9895
|
});
|
|
9516
9896
|
// Health check endpoint
|
|
9517
9897
|
app.get("/health", (_req, res) => {
|