@zereight/mcp-gitlab 2.1.49 โ 2.1.51
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 +5 -2
- package/README.md +7 -3
- package/README.zh-CN.md +6 -2
- package/build/auth-cli.js +95 -0
- package/build/cli-command.js +33 -0
- package/build/index.js +40 -20
- package/build/oauth-device-flow.js +158 -0
- package/build/oauth.js +20 -0
- package/build/schemas.js +58 -10
- package/build/scripts/generate-tool-docs.js +1 -1
- package/build/test/create-commit-actions.test.js +102 -0
- package/build/test/gitlab-artifact-entry-schema.test.js +44 -0
- package/build/test/merge-merge-request-schema.test.js +75 -0
- package/build/test/oauth-device-flow-tests.js +440 -0
- package/build/test/schema-tests.js +117 -3
- package/build/test/test-job-artifacts.js +30 -0
- package/build/test/test-permission-mode.js +28 -0
- package/build/tools/tool-descriptions.js +3 -3
- package/build/utils/gitlab-commit-actions.js +42 -0
- package/package.json +3 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env ts-node
|
|
2
|
-
import { GetFileContentsSchema, GitLabFileContentSchema, GitLabRepositorySchema, CreatePipelineSchema, CreateCommitStatusSchema, ListCommitStatusesSchema, CreateIssueNoteSchema, CreateMergeRequestEmojiReactionSchema, CreateIssueEmojiReactionSchema, DeleteMergeRequestEmojiReactionSchema, DeleteIssueEmojiReactionSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, CreateIssueSchema, ListIssuesSchema, ListMergeRequestsSchema, ListLabelsSchema, GitLabMergeRequestSchema, GitLabTreeItemSchema, GetMergeRequestSchema, ListMergeRequestPipelinesSchema, GetRepositoryTreeSchema, GitLabUserFullSchema, GitLabMarkdownUploadSchema, GitLabDependencyProxySchema, GitLabDependencyProxyBlobSchema, } from '../schemas.js';
|
|
2
|
+
import { GetFileContentsSchema, GitLabFileContentSchema, GitLabRepositorySchema, CreatePipelineSchema, CreateCommitStatusSchema, ListCommitStatusesSchema, CreateIssueNoteSchema, CreateMergeRequestEmojiReactionSchema, CreateIssueEmojiReactionSchema, DeleteMergeRequestEmojiReactionSchema, DeleteIssueEmojiReactionSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, CreateIssueSchema, ListIssuesSchema, ListMergeRequestsSchema, ListLabelsSchema, GitLabMergeRequestSchema, GitLabTreeItemSchema, GetMergeRequestSchema, ListMergeRequestPipelinesSchema, GetRepositoryTreeSchema, GitLabUserFullSchema, GitLabMarkdownUploadSchema, GitLabDependencyProxySchema, GitLabDependencyProxyBlobSchema, PushFilesSchema, CreateOrUpdateFileSchema, } from '../schemas.js';
|
|
3
3
|
function runGetFileContentsSchemaTests() {
|
|
4
4
|
console.log('๐งช Testing GetFileContentsSchema...');
|
|
5
5
|
const cases = [
|
|
@@ -822,6 +822,21 @@ function runGitLabMergeRequestSchemaTests() {
|
|
|
822
822
|
},
|
|
823
823
|
validate: (data) => data.merge_user === undefined && data.merged_by === undefined,
|
|
824
824
|
},
|
|
825
|
+
{
|
|
826
|
+
name: 'schema:gitlab_merge_request:preserves-sha',
|
|
827
|
+
input: {
|
|
828
|
+
...baseMergeRequest,
|
|
829
|
+
sha: 'e82eb4a098e32c796079ca3915e07487fc4db24c',
|
|
830
|
+
},
|
|
831
|
+
validate: (data) => data.sha === 'e82eb4a098e32c796079ca3915e07487fc4db24c',
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
name: 'schema:gitlab_merge_request:allows-omitted-sha',
|
|
835
|
+
input: {
|
|
836
|
+
...baseMergeRequest,
|
|
837
|
+
},
|
|
838
|
+
validate: (data) => data.sha === undefined,
|
|
839
|
+
},
|
|
825
840
|
];
|
|
826
841
|
let passed = 0;
|
|
827
842
|
let failed = 0;
|
|
@@ -1536,6 +1551,104 @@ function runGitLabDependencyProxyBlobSchemaTests() {
|
|
|
1536
1551
|
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
1537
1552
|
return { passed, failed };
|
|
1538
1553
|
}
|
|
1554
|
+
function runPushFilesSchemaTests() {
|
|
1555
|
+
console.log('๐งช Testing PushFilesSchema / CreateOrUpdateFileSchema (per-file action + encoding)...');
|
|
1556
|
+
const base = { project_id: '1', branch: 'main', commit_message: 'm' };
|
|
1557
|
+
function record(name, ok, error) {
|
|
1558
|
+
const result = { name, status: ok ? 'passed' : 'failed', error };
|
|
1559
|
+
if (result.status === 'passed') {
|
|
1560
|
+
console.log(`โ
${result.name}`);
|
|
1561
|
+
}
|
|
1562
|
+
else {
|
|
1563
|
+
console.log(`โ ${result.name}: ${result.error}`);
|
|
1564
|
+
}
|
|
1565
|
+
return result;
|
|
1566
|
+
}
|
|
1567
|
+
const results = [];
|
|
1568
|
+
{
|
|
1569
|
+
const parsed = PushFilesSchema.safeParse({ ...base, files: [{ file_path: 'a.txt', content: 'hi' }] });
|
|
1570
|
+
results.push(record('schema:push_files:plain-text-default', parsed.success &&
|
|
1571
|
+
parsed.data.files[0]?.action === undefined &&
|
|
1572
|
+
parsed.data.files[0]?.encoding === undefined, parsed.success ? undefined : parsed.error.message));
|
|
1573
|
+
}
|
|
1574
|
+
{
|
|
1575
|
+
const parsed = PushFilesSchema.safeParse({
|
|
1576
|
+
...base,
|
|
1577
|
+
files: [{ file_path: 'logo.png', content: 'aGk=', encoding: 'base64' }],
|
|
1578
|
+
});
|
|
1579
|
+
results.push(record('schema:push_files:binary-base64', parsed.success && parsed.data.files[0]?.encoding === 'base64', parsed.success ? undefined : parsed.error.message));
|
|
1580
|
+
}
|
|
1581
|
+
{
|
|
1582
|
+
const parsed = PushFilesSchema.safeParse({
|
|
1583
|
+
...base,
|
|
1584
|
+
files: [{ file_path: 'a.txt', content: 'new', action: 'update' }],
|
|
1585
|
+
});
|
|
1586
|
+
results.push(record('schema:push_files:action-update', parsed.success && parsed.data.files[0]?.action === 'update', parsed.success ? undefined : parsed.error.message));
|
|
1587
|
+
}
|
|
1588
|
+
{
|
|
1589
|
+
const parsed = PushFilesSchema.safeParse({
|
|
1590
|
+
...base,
|
|
1591
|
+
files: [{ file_path: 'a.txt', action: 'delete' }],
|
|
1592
|
+
});
|
|
1593
|
+
results.push(record('schema:push_files:action-delete-content-optional', parsed.success &&
|
|
1594
|
+
parsed.data.files[0]?.action === 'delete' &&
|
|
1595
|
+
parsed.data.files[0]?.content === undefined, parsed.success ? undefined : parsed.error.message));
|
|
1596
|
+
}
|
|
1597
|
+
{
|
|
1598
|
+
const parsed = PushFilesSchema.safeParse({
|
|
1599
|
+
...base,
|
|
1600
|
+
files: [{ file_path: 'b.txt', action: 'move', previous_path: 'a.txt' }],
|
|
1601
|
+
});
|
|
1602
|
+
results.push(record('schema:push_files:action-move-previous-path', parsed.success &&
|
|
1603
|
+
parsed.data.files[0]?.action === 'move' &&
|
|
1604
|
+
parsed.data.files[0]?.previous_path === 'a.txt', parsed.success ? undefined : parsed.error.message));
|
|
1605
|
+
}
|
|
1606
|
+
{
|
|
1607
|
+
const parsed = PushFilesSchema.safeParse({
|
|
1608
|
+
...base,
|
|
1609
|
+
files: [{ file_path: 'a.txt', content: 'hi', encoding: 'text' }],
|
|
1610
|
+
});
|
|
1611
|
+
results.push(record('schema:push_files:explicit-text-encoding', parsed.success && parsed.data.files[0]?.encoding === 'text', parsed.success ? undefined : parsed.error.message));
|
|
1612
|
+
}
|
|
1613
|
+
results.push(record('schema:push_files:reject-create-without-content', !PushFilesSchema.safeParse({ ...base, files: [{ file_path: 'a.txt' }] }).success, 'Expected schema validation to fail'));
|
|
1614
|
+
results.push(record('schema:push_files:reject-update-without-content', !PushFilesSchema.safeParse({ ...base, files: [{ file_path: 'a.txt', action: 'update' }] }).success, 'Expected schema validation to fail'));
|
|
1615
|
+
results.push(record('schema:push_files:reject-move-without-previous-path', !PushFilesSchema.safeParse({ ...base, files: [{ file_path: 'b.txt', action: 'move' }] }).success, 'Expected schema validation to fail'));
|
|
1616
|
+
results.push(record('schema:push_files:reject-invalid-action', !PushFilesSchema.safeParse({
|
|
1617
|
+
...base,
|
|
1618
|
+
files: [{ file_path: 'a.txt', content: 'hi', action: 'copy' }],
|
|
1619
|
+
}).success, 'Expected schema validation to fail'));
|
|
1620
|
+
results.push(record('schema:push_files:reject-invalid-encoding', !PushFilesSchema.safeParse({
|
|
1621
|
+
...base,
|
|
1622
|
+
files: [{ file_path: 'a.txt', content: 'hi', encoding: 'utf16' }],
|
|
1623
|
+
}).success, 'Expected schema validation to fail'));
|
|
1624
|
+
{
|
|
1625
|
+
const parsed = CreateOrUpdateFileSchema.safeParse({
|
|
1626
|
+
...base,
|
|
1627
|
+
file_path: 'a.txt',
|
|
1628
|
+
content: 'hi',
|
|
1629
|
+
});
|
|
1630
|
+
results.push(record('schema:create_or_update_file:plain-text-default', parsed.success && parsed.data.encoding === undefined, parsed.success ? undefined : parsed.error.message));
|
|
1631
|
+
}
|
|
1632
|
+
{
|
|
1633
|
+
const parsed = CreateOrUpdateFileSchema.safeParse({
|
|
1634
|
+
...base,
|
|
1635
|
+
file_path: 'logo.png',
|
|
1636
|
+
content: 'aGk=',
|
|
1637
|
+
encoding: 'base64',
|
|
1638
|
+
});
|
|
1639
|
+
results.push(record('schema:create_or_update_file:encoding-base64', parsed.success && parsed.data.encoding === 'base64', parsed.success ? undefined : parsed.error.message));
|
|
1640
|
+
}
|
|
1641
|
+
results.push(record('schema:create_or_update_file:reject-invalid-encoding', !CreateOrUpdateFileSchema.safeParse({
|
|
1642
|
+
...base,
|
|
1643
|
+
file_path: 'a.txt',
|
|
1644
|
+
content: 'hi',
|
|
1645
|
+
encoding: 'utf16',
|
|
1646
|
+
}).success, 'Expected schema validation to fail'));
|
|
1647
|
+
const passed = results.filter(result => result.status === 'passed').length;
|
|
1648
|
+
const failed = results.filter(result => result.status === 'failed').length;
|
|
1649
|
+
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
1650
|
+
return { passed, failed };
|
|
1651
|
+
}
|
|
1539
1652
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
1540
1653
|
const getFileContentsResult = runGetFileContentsSchemaTests();
|
|
1541
1654
|
const fileContentResult = runGitLabFileContentSchemaTests();
|
|
@@ -1556,8 +1669,9 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
1556
1669
|
const gitLabMarkdownUploadResult = runGitLabMarkdownUploadSchemaTests();
|
|
1557
1670
|
const dependencyProxyResult = runGitLabDependencyProxySchemaTests();
|
|
1558
1671
|
const dependencyProxyBlobResult = runGitLabDependencyProxyBlobSchemaTests();
|
|
1559
|
-
const
|
|
1560
|
-
const
|
|
1672
|
+
const pushFilesResult = runPushFilesSchemaTests();
|
|
1673
|
+
const totalPassed = getFileContentsResult.passed + fileContentResult.passed + createPipelineResult.passed + commitStatusResult.passed + createIssueNoteResult.passed + getMergeRequestResult.passed + listMergeRequestPipelinesResult.passed + gitLabMergeRequestResult.passed + emojiReactionResult.passed + repositorySchemaResult.passed + labelsCoercionResult.passed + approvedByUsernamesResult.passed + listLabelsResult.passed + treeItemResult.passed + repositoryTreeResult.passed + gitLabUserFullResult.passed + gitLabMarkdownUploadResult.passed + dependencyProxyResult.passed + dependencyProxyBlobResult.passed + pushFilesResult.passed;
|
|
1674
|
+
const totalFailed = getFileContentsResult.failed + fileContentResult.failed + createPipelineResult.failed + commitStatusResult.failed + createIssueNoteResult.failed + getMergeRequestResult.failed + listMergeRequestPipelinesResult.failed + gitLabMergeRequestResult.failed + emojiReactionResult.failed + repositorySchemaResult.failed + labelsCoercionResult.failed + approvedByUsernamesResult.failed + listLabelsResult.failed + treeItemResult.failed + repositoryTreeResult.failed + gitLabUserFullResult.failed + gitLabMarkdownUploadResult.failed + dependencyProxyResult.failed + dependencyProxyBlobResult.failed + pushFilesResult.failed;
|
|
1561
1675
|
console.log(`\nTotal Results: ${totalPassed} passed, ${totalFailed} failed`);
|
|
1562
1676
|
if (totalFailed > 0) {
|
|
1563
1677
|
process.exit(1);
|
|
@@ -8,6 +8,7 @@ import os from 'node:os';
|
|
|
8
8
|
const MOCK_TOKEN = 'glpat-mock-token-12345';
|
|
9
9
|
const TEST_PROJECT_ID = '123';
|
|
10
10
|
const TEST_JOB_ID = '456';
|
|
11
|
+
const NULL_MODE_JOB_ID = '457';
|
|
11
12
|
const TEST_ENCODED_ARTIFACT_PATH = 'reports/report#1.txt';
|
|
12
13
|
// Helper to run an MCP tool via the built server
|
|
13
14
|
async function callTool(toolName, args, env) {
|
|
@@ -117,6 +118,25 @@ describe('job artifacts tools', () => {
|
|
|
117
118
|
res.set('Content-Type', 'text/plain');
|
|
118
119
|
res.send('encoded artifact content');
|
|
119
120
|
});
|
|
121
|
+
// GitLab.com can return mode/size as null on artifacts/tree entries (#661)
|
|
122
|
+
mockGitLab.addMockHandler('get', `/projects/${TEST_PROJECT_ID}/jobs/${NULL_MODE_JOB_ID}/artifacts/tree`, (req, res) => {
|
|
123
|
+
res.json([
|
|
124
|
+
{
|
|
125
|
+
name: 'report.xml',
|
|
126
|
+
path: 'report.xml',
|
|
127
|
+
type: 'file',
|
|
128
|
+
size: 1024,
|
|
129
|
+
mode: null,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'logs',
|
|
133
|
+
path: 'logs',
|
|
134
|
+
type: 'directory',
|
|
135
|
+
size: null,
|
|
136
|
+
mode: '040755',
|
|
137
|
+
},
|
|
138
|
+
]);
|
|
139
|
+
});
|
|
120
140
|
// Add mock handler for 404 on non-existent artifact
|
|
121
141
|
mockGitLab.addMockHandler('get', `/projects/${TEST_PROJECT_ID}/jobs/999/artifacts/tree`, (req, res) => {
|
|
122
142
|
res.status(404).json({ message: 'Not Found' });
|
|
@@ -147,6 +167,16 @@ describe('job artifacts tools', () => {
|
|
|
147
167
|
assert.strictEqual(result[1].name, 'logs');
|
|
148
168
|
assert.strictEqual(result[1].type, 'directory');
|
|
149
169
|
});
|
|
170
|
+
test('list_job_artifacts returns artifact entries when mode is null', async () => {
|
|
171
|
+
const result = await callTool('list_job_artifacts', { project_id: TEST_PROJECT_ID, job_id: NULL_MODE_JOB_ID }, {
|
|
172
|
+
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
173
|
+
GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
|
|
174
|
+
});
|
|
175
|
+
assert.ok(Array.isArray(result), 'Response should be an array');
|
|
176
|
+
assert.strictEqual(result.length, 2);
|
|
177
|
+
assert.strictEqual(result[0].mode, null);
|
|
178
|
+
assert.strictEqual(result[1].size, null);
|
|
179
|
+
});
|
|
150
180
|
test('download_job_artifacts saves archive to disk', async () => {
|
|
151
181
|
const result = await callTool('download_job_artifacts', { project_id: TEST_PROJECT_ID, job_id: TEST_JOB_ID, local_path: tmpDir }, {
|
|
152
182
|
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
@@ -140,6 +140,34 @@ describe("Permission Mode", { concurrency: 1 }, () => {
|
|
|
140
140
|
await client.disconnect();
|
|
141
141
|
}
|
|
142
142
|
});
|
|
143
|
+
test("rejects push_files delete actions", async () => {
|
|
144
|
+
const client = await connectClient(server);
|
|
145
|
+
try {
|
|
146
|
+
await assert.rejects(() => client.callTool("push_files", {
|
|
147
|
+
project_id: "1",
|
|
148
|
+
branch: "main",
|
|
149
|
+
commit_message: "m",
|
|
150
|
+
files: [{ file_path: "a.txt", action: "delete" }],
|
|
151
|
+
}), (error) => error.message.includes("delete or move actions in modify mode"), "push_files delete should be rejected in modify mode");
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
await client.disconnect();
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
test("rejects push_files move actions", async () => {
|
|
158
|
+
const client = await connectClient(server);
|
|
159
|
+
try {
|
|
160
|
+
await assert.rejects(() => client.callTool("push_files", {
|
|
161
|
+
project_id: "1",
|
|
162
|
+
branch: "main",
|
|
163
|
+
commit_message: "m",
|
|
164
|
+
files: [{ file_path: "b.txt", action: "move", previous_path: "a.txt" }],
|
|
165
|
+
}), (error) => error.message.includes("delete or move actions in modify mode"), "push_files move should be rejected in modify mode");
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
await client.disconnect();
|
|
169
|
+
}
|
|
170
|
+
});
|
|
143
171
|
test("does not apply the modify-mode guard to non-delete mutations", async () => {
|
|
144
172
|
const client = await connectClient(server);
|
|
145
173
|
try {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
const TOOL_GUIDANCE = {
|
|
2
|
-
merge_merge_request: "Use this only after checking the merge request approval, conflict, and pipeline state; use `approve_merge_request` to approve rather than merge. The operation changes repository state and may squash commits, schedule auto-merge, or delete the source branch, so it requires merge permission and returns GitLab's merge result or a mergeability error.",
|
|
2
|
+
merge_merge_request: "Use this only after checking the merge request approval, conflict, and pipeline state; use `approve_merge_request` to approve rather than merge. The operation changes repository state and may squash commits, schedule auto-merge, or delete the source branch, so it requires merge permission and returns GitLab's merge result or a mergeability error. Pass `sha` from `get_merge_request` (`sha` or `diff_refs.head_sha`); GitLab 19.2+ groups may reject merges without it.",
|
|
3
3
|
approve_merge_request: "Use this to record an approval on an existing merge request; it does not merge the request or change its source branch. The operation changes review state, may require re-authentication or approval permission, and returns the updated approval result or a permission/state error.",
|
|
4
4
|
unapprove_merge_request: "Use this to remove the current user's approval from an existing merge request; use `merge_merge_request` only when you intend to merge. The operation changes review state and requires approval permission, and GitLab returns the updated result or an error when the request or approval is unavailable.",
|
|
5
5
|
get_merge_request_approval_state: "Use this to inspect approval rules and approvers before deciding whether a merge request can be merged; use `approve_merge_request` to change approval state. It is read-only and returns the approval-state response, while missing requests, unsupported GitLab versions, and permission failures are reported as errors.",
|
|
6
6
|
get_merge_request_conflicts: "Use this to inspect merge conflicts before attempting `merge_merge_request`; it reports conflicts and does not resolve them. It is read-only, requires access to the project and merge request, and returns GitLab's conflict data or an error when the request cannot be evaluated.",
|
|
7
7
|
list_merge_request_pipelines: "Use this to inspect pipelines associated with one merge request; use `list_pipelines` for project-wide pipeline filtering. It is read-only and paginated, requires project access, and returns pipeline records or GitLab errors for invalid identifiers, missing resources, or rate limits.",
|
|
8
|
-
create_or_update_file: "Use this for a single repository file when you know whether the target path is new or already exists; use `push_files` for a multi-file commit. The operation creates or updates remote content in a commit, requires repository write permission, and returns the commit result or a conflict/validation error.",
|
|
9
|
-
push_files: "Use this to commit several file changes atomically; use `create_or_update_file` when only one path is involved. The operation writes repository history on the selected branch, requires repository write permission, and returns the commit result or a validation, conflict, or protected-branch error.",
|
|
8
|
+
create_or_update_file: "Use this for a single repository file when you know whether the target path is new or already exists; use `push_files` for a multi-file commit. Optional `encoding` (`text` or `base64`) defaults to `GITLAB_REPO_FILE_ENCODING` so existing callers stay unchanged. The operation creates or updates remote content in a commit, requires repository write permission, and returns the commit result or a conflict/validation error.",
|
|
9
|
+
push_files: "Use this to commit several file changes atomically; use `create_or_update_file` when only one path is involved. Each file defaults to action `create`; optional per-file `action` (create/update/delete/move) and `encoding` (text/base64) are additive. `GITLAB_PERMISSION_MODE=modify` rejects `delete` and `move`. The operation writes repository history on the selected branch, requires repository write permission, and returns the commit result or a validation, conflict, or protected-branch error.",
|
|
10
10
|
create_issue: "Use this to open a new issue; use `update_issue` for an existing issue and `create_issue_note` to add discussion without changing issue fields. The operation creates remote project data, requires issue creation permission, and returns the new issue or a validation, permission, or duplicate-related error.",
|
|
11
11
|
create_merge_request: "Use this to open a new merge request from an existing source branch to a target branch; use `update_merge_request` after it exists. The operation creates remote review state, requires project access, and returns the new merge request or a validation, permission, branch, or duplicate-related error.",
|
|
12
12
|
fork_repository: "Use this to create a copy of an existing project in the current user's namespace or a permitted namespace; use `search_repositories` or `get_project` to inspect projects without copying them. The operation creates a new project, requires fork permission, and returns the forked project or a namespace/permission error.",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export function fileOperationsIncludeDeleteOrMove(files) {
|
|
2
|
+
return files.some(file => file.action === "delete" || file.action === "move");
|
|
3
|
+
}
|
|
4
|
+
export function encodeRepoFilePayloadContent(content, globalEncoding) {
|
|
5
|
+
if (globalEncoding === "base64") {
|
|
6
|
+
return Buffer.from(content).toString("base64");
|
|
7
|
+
}
|
|
8
|
+
return content;
|
|
9
|
+
}
|
|
10
|
+
function applyContentAndEncoding(entry, content, encoding, globalEncoding) {
|
|
11
|
+
if (encoding !== undefined) {
|
|
12
|
+
entry.content = content;
|
|
13
|
+
entry.encoding = encoding;
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
entry.content = encodeRepoFilePayloadContent(content, globalEncoding);
|
|
17
|
+
entry.encoding = globalEncoding;
|
|
18
|
+
}
|
|
19
|
+
export function toGitLabCommitActions(actions, globalEncoding) {
|
|
20
|
+
return actions.map(action => {
|
|
21
|
+
const act = action.action ?? "create";
|
|
22
|
+
const entry = { action: act, file_path: action.path };
|
|
23
|
+
if (act === "delete") {
|
|
24
|
+
return entry;
|
|
25
|
+
}
|
|
26
|
+
if (act === "move") {
|
|
27
|
+
if (action.previous_path) {
|
|
28
|
+
entry.previous_path = action.previous_path;
|
|
29
|
+
}
|
|
30
|
+
if (action.content === undefined) {
|
|
31
|
+
return entry;
|
|
32
|
+
}
|
|
33
|
+
applyContentAndEncoding(entry, action.content, action.encoding, globalEncoding);
|
|
34
|
+
return entry;
|
|
35
|
+
}
|
|
36
|
+
if (action.content === undefined) {
|
|
37
|
+
throw new Error(`content is required when action is '${act}'`);
|
|
38
|
+
}
|
|
39
|
+
applyContentAndEncoding(entry, action.content, action.encoding, globalEncoding);
|
|
40
|
+
return entry;
|
|
41
|
+
});
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zereight/mcp-gitlab",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.51",
|
|
4
4
|
"mcpName": "io.github.zereight/gitlab-mcp",
|
|
5
5
|
"description": "GitLab MCP server for projects, merge requests, issues, pipelines, wiki, releases, and more",
|
|
6
6
|
"keywords": [
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
"test:live": "node test/validate-api.js",
|
|
59
59
|
"test:consumer-smoke": "bash scripts/consumer-install-smoke.sh",
|
|
60
60
|
"test:remote-auth": "npm run build && node --import tsx/esm --test --experimental-test-isolation=none test/remote-auth-simple-test.ts",
|
|
61
|
-
"test:schema": "tsx test/schema-tests.ts && tsx test/test-json-schema.ts",
|
|
62
|
-
"test:oauth": "tsx test/oauth-tests.ts",
|
|
61
|
+
"test:schema": "tsx test/schema-tests.ts && tsx test/test-json-schema.ts && node --import tsx/esm --test --experimental-test-isolation=none --test-concurrency=1 test/*-schema.test.ts",
|
|
62
|
+
"test:oauth": "tsx test/oauth-tests.ts && node --import tsx/esm --test --experimental-test-isolation=none test/oauth-device-flow-tests.ts",
|
|
63
63
|
"test:list-merge-requests": "npm run build && tsx test/test-list-merge-requests.ts",
|
|
64
64
|
"test:approvals": "npm run build && tsx test/test-merge-request-approvals.ts",
|
|
65
65
|
"lint": "eslint . --ext .ts",
|