@zereight/mcp-gitlab 2.1.14 โ†’ 2.1.15

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/build/schemas.js CHANGED
@@ -2580,7 +2580,7 @@ export const GitLabProjectMemberSchema = z.object({
2580
2580
  });
2581
2581
  // Markdown upload schemas
2582
2582
  export const GitLabMarkdownUploadSchema = z.object({
2583
- id: z.coerce.number(),
2583
+ id: z.preprocess((val) => (val == null ? undefined : val), z.coerce.number().optional()),
2584
2584
  alt: z.string(),
2585
2585
  url: z.string(),
2586
2586
  full_path: z.string(),
@@ -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 } 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, } from '../schemas.js';
3
3
  function runGetFileContentsSchemaTests() {
4
4
  console.log('๐Ÿงช Testing GetFileContentsSchema...');
5
5
  const cases = [
@@ -1122,6 +1122,75 @@ function runGetRepositoryTreeSchemaTests() {
1122
1122
  console.log(`\nResults: ${passed} passed, ${failed} failed`);
1123
1123
  return { passed, failed };
1124
1124
  }
1125
+ function runGitLabMarkdownUploadSchemaTests() {
1126
+ console.log('\n=== GitLabMarkdownUpload Schema Tests ===');
1127
+ const idlessUpload = {
1128
+ alt: 'report.md',
1129
+ url: '/uploads/c617e74a47dfb1a6dd59d419619b725d/report.md',
1130
+ full_path: '/group/project/uploads/c617e74a47dfb1a6dd59d419619b725d/report.md',
1131
+ markdown: '[report.md](/uploads/c617e74a47dfb1a6dd59d419619b725d/report.md)',
1132
+ };
1133
+ const cases = [
1134
+ {
1135
+ name: 'schema:markdown_upload:accepts-idless-response',
1136
+ input: idlessUpload,
1137
+ expectedId: 'absent',
1138
+ },
1139
+ {
1140
+ name: 'schema:markdown_upload:accepts-numeric-id',
1141
+ input: { ...idlessUpload, id: 42 },
1142
+ expectedId: 42,
1143
+ },
1144
+ {
1145
+ name: 'schema:markdown_upload:coerces-string-id',
1146
+ input: { ...idlessUpload, id: '99' },
1147
+ expectedId: 99,
1148
+ },
1149
+ {
1150
+ name: 'schema:markdown_upload:treats-null-id-as-absent',
1151
+ input: { ...idlessUpload, id: null },
1152
+ expectedId: 'absent',
1153
+ },
1154
+ {
1155
+ name: 'schema:markdown_upload:rejects-invalid-id',
1156
+ input: { ...idlessUpload, id: 'not-a-number' },
1157
+ shouldFail: true,
1158
+ },
1159
+ ];
1160
+ let passed = 0;
1161
+ let failed = 0;
1162
+ cases.forEach(testCase => {
1163
+ const result = { name: testCase.name, status: 'failed' };
1164
+ const parsed = GitLabMarkdownUploadSchema.safeParse(testCase.input);
1165
+ if (testCase.shouldFail) {
1166
+ result.status = parsed.success ? 'failed' : 'passed';
1167
+ if (parsed.success)
1168
+ result.error = 'Expected schema validation to fail';
1169
+ }
1170
+ else if (!parsed.success) {
1171
+ result.error = parsed.error?.message || 'Schema validation failed';
1172
+ }
1173
+ else if (testCase.expectedId === 'absent' && parsed.data.id !== undefined) {
1174
+ result.error = `Expected id undefined, got ${parsed.data.id}`;
1175
+ }
1176
+ else if (typeof testCase.expectedId === 'number' && parsed.data.id !== testCase.expectedId) {
1177
+ result.error = `Expected id ${testCase.expectedId}, got ${parsed.data.id}`;
1178
+ }
1179
+ else {
1180
+ result.status = 'passed';
1181
+ }
1182
+ if (result.status === 'passed') {
1183
+ passed++;
1184
+ console.log(`โœ… ${result.name}`);
1185
+ }
1186
+ else {
1187
+ failed++;
1188
+ console.log(`โŒ ${result.name}: ${result.error}`);
1189
+ }
1190
+ });
1191
+ console.log(`\nResults: ${passed} passed, ${failed} failed`);
1192
+ return { passed, failed };
1193
+ }
1125
1194
  function runGitLabUserFullSchemaTests() {
1126
1195
  console.log('๐Ÿงช Testing GitLabUserFullSchema...');
1127
1196
  const adminResponse = {
@@ -1219,8 +1288,9 @@ if (import.meta.url === `file://${process.argv[1]}`) {
1219
1288
  const treeItemResult = runGitLabTreeItemSchemaTests();
1220
1289
  const repositoryTreeResult = runGetRepositoryTreeSchemaTests();
1221
1290
  const gitLabUserFullResult = runGitLabUserFullSchemaTests();
1222
- const totalPassed = getFileContentsResult.passed + fileContentResult.passed + createPipelineResult.passed + commitStatusResult.passed + createIssueNoteResult.passed + getMergeRequestResult.passed + listMergeRequestPipelinesResult.passed + gitLabMergeRequestResult.passed + emojiReactionResult.passed + repositorySchemaResult.passed + labelsCoercionResult.passed + listLabelsResult.passed + treeItemResult.passed + repositoryTreeResult.passed + gitLabUserFullResult.passed;
1223
- const totalFailed = getFileContentsResult.failed + fileContentResult.failed + createPipelineResult.failed + commitStatusResult.failed + createIssueNoteResult.failed + getMergeRequestResult.failed + listMergeRequestPipelinesResult.failed + gitLabMergeRequestResult.failed + emojiReactionResult.failed + repositorySchemaResult.failed + labelsCoercionResult.failed + listLabelsResult.failed + treeItemResult.failed + repositoryTreeResult.failed + gitLabUserFullResult.failed;
1291
+ const gitLabMarkdownUploadResult = runGitLabMarkdownUploadSchemaTests();
1292
+ const totalPassed = getFileContentsResult.passed + fileContentResult.passed + createPipelineResult.passed + commitStatusResult.passed + createIssueNoteResult.passed + getMergeRequestResult.passed + listMergeRequestPipelinesResult.passed + gitLabMergeRequestResult.passed + emojiReactionResult.passed + repositorySchemaResult.passed + labelsCoercionResult.passed + listLabelsResult.passed + treeItemResult.passed + repositoryTreeResult.passed + gitLabUserFullResult.passed + gitLabMarkdownUploadResult.passed;
1293
+ const totalFailed = getFileContentsResult.failed + fileContentResult.failed + createPipelineResult.failed + commitStatusResult.failed + createIssueNoteResult.failed + getMergeRequestResult.failed + listMergeRequestPipelinesResult.failed + gitLabMergeRequestResult.failed + emojiReactionResult.failed + repositorySchemaResult.failed + labelsCoercionResult.failed + listLabelsResult.failed + treeItemResult.failed + repositoryTreeResult.failed + gitLabUserFullResult.failed + gitLabMarkdownUploadResult.failed;
1224
1294
  console.log(`\nTotal Results: ${totalPassed} passed, ${totalFailed} failed`);
1225
1295
  if (totalFailed > 0) {
1226
1296
  process.exit(1);
@@ -1,4 +1,4 @@
1
- import { describe, test, before, after } from 'node:test';
1
+ import { describe, test, before, after, beforeEach } from 'node:test';
2
2
  import assert from 'node:assert';
3
3
  import { spawn } from 'node:child_process';
4
4
  import fs from 'node:fs';
@@ -55,12 +55,18 @@ const MOCK_UPLOAD_RESPONSE = {
55
55
  full_path: '/test-group/test-project/uploads/abc123secret/test-file.txt',
56
56
  markdown: '[test-file.txt](/uploads/abc123secret/test-file.txt)',
57
57
  };
58
+ let uploadResponse = MOCK_UPLOAD_RESPONSE;
58
59
  describe('upload_markdown', () => {
59
60
  let mockGitLab;
60
61
  let env;
61
62
  // Captured per-request state, reset before each invocation via the handler
62
63
  let lastContentType;
63
64
  let lastRawBody;
65
+ beforeEach(() => {
66
+ uploadResponse = MOCK_UPLOAD_RESPONSE;
67
+ lastContentType = undefined;
68
+ lastRawBody = undefined;
69
+ });
64
70
  before(async () => {
65
71
  const port = await findMockServerPort(9200);
66
72
  mockGitLab = new MockGitLabServer({ port, validTokens: [MOCK_TOKEN] });
@@ -75,7 +81,7 @@ describe('upload_markdown', () => {
75
81
  req.on('data', (chunk) => chunks.push(chunk));
76
82
  req.on('end', () => {
77
83
  lastRawBody = Buffer.concat(chunks).toString('binary');
78
- res.status(201).json(MOCK_UPLOAD_RESPONSE);
84
+ res.status(201).json(uploadResponse);
79
85
  });
80
86
  });
81
87
  });
@@ -139,6 +145,27 @@ describe('upload_markdown', () => {
139
145
  fs.unlinkSync(tmpFile);
140
146
  }
141
147
  });
148
+ test('accepts upload responses without id from older self-hosted GitLab', async () => {
149
+ const { id: _id, ...idlessUploadResponse } = MOCK_UPLOAD_RESPONSE;
150
+ uploadResponse = idlessUploadResponse;
151
+ const tmpFile = path.join(os.tmpdir(), 'mcp-upload-idless-response-test.txt');
152
+ fs.writeFileSync(tmpFile, 'idless response field test');
153
+ try {
154
+ const raw = await callUploadMarkdown({ project_id: TEST_PROJECT_ID, file_path: tmpFile }, env);
155
+ assert.ok(!raw.error, `Unexpected RPC error: ${raw.error?.message}`);
156
+ const text = raw.result?.content?.[0]?.text;
157
+ assert.ok(text, 'Result should contain a text content block');
158
+ const parsed = JSON.parse(text);
159
+ assert.strictEqual(parsed.id, undefined);
160
+ assert.strictEqual(parsed.markdown, MOCK_UPLOAD_RESPONSE.markdown);
161
+ assert.strictEqual(parsed.url, MOCK_UPLOAD_RESPONSE.url);
162
+ assert.strictEqual(parsed.alt, MOCK_UPLOAD_RESPONSE.alt);
163
+ assert.strictEqual(parsed.full_path, MOCK_UPLOAD_RESPONSE.full_path);
164
+ }
165
+ finally {
166
+ fs.unlinkSync(tmpFile);
167
+ }
168
+ });
142
169
  test('returns an error when the file does not exist', async () => {
143
170
  const raw = await callUploadMarkdown({ project_id: TEST_PROJECT_ID, file_path: '/nonexistent/no-such-file.txt' }, env);
144
171
  const hasError = typeof raw.error?.message === 'string' ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zereight/mcp-gitlab",
3
- "version": "2.1.14",
3
+ "version": "2.1.15",
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": [
@@ -51,7 +51,7 @@
51
51
  "changelog": "auto-changelog -p",
52
52
  "test": "npm run test:all",
53
53
  "test:all": "npm run build && npm run test:mock && npm run test:live",
54
- "test:mock": "node --import tsx/esm --test test/remote-auth-simple-test.ts && node --import tsx/esm --test test/mcp-oauth-tests.ts && node --import tsx/esm --test test/streamable-http-static-token-auth.test.ts && tsx test/oauth-tests.ts && tsx test/test-list-merge-requests.ts && node --import tsx/esm --test test/test-merge-request-pipelines.ts && tsx test/test-list-project-members.ts && tsx test/test-download-attachment.ts && node --import tsx/esm --test test/test-job-artifacts.ts && node --import tsx/esm --test test/test-deployment-tools.ts && node --import tsx/esm --test test/test-merge-request-approval-state-tools.ts && node --import tsx/esm --test test/test-search-code.ts && node --import tsx/esm --test test/test-tags.ts && node --import tsx/esm --test test/test-toolset-filtering.ts && node --import tsx/esm --test test/test-ci-lint.ts && node --import tsx/esm --test test/test-todos.ts && node --import tsx/esm --test test/test-auth-retry.ts && node --import tsx/esm --test test/test-issue-description-patch.ts && node --import tsx/esm --test test/test-geteffectiveprojectid.ts && node --import tsx/esm --test test/test-get-file-blame.ts && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
54
+ "test:mock": "node --import tsx/esm --test test/remote-auth-simple-test.ts && node --import tsx/esm --test test/mcp-oauth-tests.ts && node --import tsx/esm --test test/streamable-http-static-token-auth.test.ts && tsx test/oauth-tests.ts && tsx test/test-list-merge-requests.ts && node --import tsx/esm --test test/test-merge-request-pipelines.ts && tsx test/test-list-project-members.ts && tsx test/test-download-attachment.ts && node --import tsx/esm --test test/test-upload-markdown.ts && node --import tsx/esm --test test/test-job-artifacts.ts && node --import tsx/esm --test test/test-deployment-tools.ts && node --import tsx/esm --test test/test-merge-request-approval-state-tools.ts && node --import tsx/esm --test test/test-search-code.ts && node --import tsx/esm --test test/test-tags.ts && node --import tsx/esm --test test/test-toolset-filtering.ts && node --import tsx/esm --test test/test-ci-lint.ts && node --import tsx/esm --test test/test-todos.ts && node --import tsx/esm --test test/test-auth-retry.ts && node --import tsx/esm --test test/test-issue-description-patch.ts && node --import tsx/esm --test test/test-geteffectiveprojectid.ts && node --import tsx/esm --test test/test-get-file-blame.ts && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
55
55
  "test:stateless": "npm run build && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
56
56
  "test:mcp-oauth": "npm run build && node --import tsx/esm --test test/mcp-oauth-tests.ts",
57
57
  "test:live": "node test/validate-api.js",